diff --git a/CMakeLists.txt b/CMakeLists.txt index 65bcc01..461d291 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,7 @@ set(SRCS Source/UI/fileio.cpp Source/UI/font.cpp Source/UI/input.cpp + Source/UI/m3u.cpp Source/UI/main.cpp Source/UI/menu.cpp Source/UI/menuitem.cpp @@ -191,6 +192,7 @@ if(BUILD_TESTING) add_executable(clidisk_test Tests/clidisk_test.cpp Source/UI/clidisk.cpp + Source/UI/m3u.cpp ) target_include_directories(clidisk_test PRIVATE Source/UI) add_test(NAME clidisk_test COMMAND clidisk_test) diff --git a/Source/UI/clidisk.cpp b/Source/UI/clidisk.cpp index 6a802df..23f6513 100644 --- a/Source/UI/clidisk.cpp +++ b/Source/UI/clidisk.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "m3u.h" namespace { @@ -119,6 +120,16 @@ bool ParseClock(const std::string& value, CliClockMode *clock) return true; } +bool IsM3U(const std::string& path) +{ + if (path.length() < 4) { + return false; + } + + std::string ext = ToUpper(path.substr(path.length() - 4)); + return ext == ".M3U"; +} + } // namespace CliOptions ParseCommandLine(int argc, char *argv[]) @@ -187,13 +198,47 @@ CliOptions ParseCommandLine(int argc, char *argv[]) return Error("too many disk images; maximum is 2"); } - DiskSpec spec; - std::string error; - if (!ParseDiskSpec(argument, static_cast(options.disks.size()), - &spec, &error)) { - return Error(error); + if (IsM3U(argument)) { + + M3UResult playlist = LoadM3U(argument); + + if (!playlist.success) { + return Error(playlist.error); + } + + for (const auto& entry : playlist.entries) { + + if (options.disks.size() >=2) { + break; + } + + DiskSpec spec; + std::string error; + + if (!ParseDiskSpec(entry, + static_cast(options.disks.size()), + &spec, + &error)) { + return Error(error); + } + + options.disks.push_back(spec); + } + + } else { + + DiskSpec spec; + std::string error; + + if (!ParseDiskSpec(argument, + static_cast(options.disks.size()), + &spec, + &error)) { + return Error(error); + } + + options.disks.push_back(spec); } - options.disks.push_back(spec); } if (action_seen && (system_seen || clock_seen || !options.disks.empty())) { diff --git a/Source/UI/m3u.cpp b/Source/UI/m3u.cpp new file mode 100644 index 0000000..78b58e3 --- /dev/null +++ b/Source/UI/m3u.cpp @@ -0,0 +1,86 @@ +#include "m3u.h" + +#include +#include +#include + +namespace { + +std::string Trim(const std::string& value) +{ + const auto first = value.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) { + return ""; + } + + const auto last = value.find_last_not_of(" \t\r\n"); + return value.substr(first, last - first + 1); +} + +std::string ParentDirectory(const std::string& path) +{ + const std::string::size_type slash = path.find_last_of("/\\"); + if (slash == std::string::npos) { + return ""; + } + return path.substr(0, slash + 1); +} + +std::string ResolveM3UEntry(const std::string& baseDir, + const std::string& entry) +{ + if (entry.empty()) { + return entry; + } + + if (entry[0] == '/') { + return entry; + } + + if (entry.size() >= 2 && + std::isalpha(static_cast(entry[0])) && + entry[1] == ':') { + return entry; + } + + return baseDir + entry; +} + +} // namespace + + +M3UResult LoadM3U(const std::string& path) +{ + M3UResult result; + result.success = false; + + std::ifstream file(path); + if (!file.is_open()) { + result.error = "unable to open m3u: " + path; + return result; + } + + // 👇 get base directory ONCE + const std::string baseDir = ParentDirectory(path); + + std::string line; + while (std::getline(file, line)) { + line = Trim(line); + + if (line.empty()) { + continue; + } + + if (line[0] == '#') { + continue; + } + + // 👇 resolve relative paths properly + std::string resolved = ResolveM3UEntry(baseDir, line); + + result.entries.push_back(resolved); + } + + result.success = true; + return result; +} diff --git a/Source/UI/m3u.h b/Source/UI/m3u.h new file mode 100644 index 0000000..b6b31ec --- /dev/null +++ b/Source/UI/m3u.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +struct M3UResult { + bool success; + std::string error; + std::vector entries; +}; + +M3UResult LoadM3U(const std::string& path);