diff --git a/src/core/ComponentManager.cpp b/src/core/ComponentManager.cpp index ef3708b..a4a2478 100644 --- a/src/core/ComponentManager.cpp +++ b/src/core/ComponentManager.cpp @@ -16,7 +16,6 @@ #ifdef LINUX_DBUS #include "mpris/MprisComponent.h" #endif -#include "ProfileManager.h" #ifdef KONVERGO_OPENELEC #include "system/openelec/OESystemComponent.h" @@ -53,9 +52,6 @@ void ComponentManager::registerComponent(ComponentBase* comp) /////////////////////////////////////////////////////////////////////////////////////////////////// void ComponentManager::initialize() { - // ProfileManager first - it sets up profile-aware paths before other components - registerComponent(&ProfileManager::Get()); - // then settings, since all other components // might have some settings // diff --git a/src/core/ProfileManager.cpp b/src/core/ProfileManager.cpp index 35c03c9..0c4aa9b 100644 --- a/src/core/ProfileManager.cpp +++ b/src/core/ProfileManager.cpp @@ -1,360 +1,194 @@ #include "ProfileManager.h" #include "Paths.h" +#include "Names.h" #include #include #include #include -#include +#include +#include #include #include -#include -static const QString PROFILES_FILE = "profiles.json"; static const QString PROFILES_DIR = "profiles"; -QVariantMap Profile::toVariantMap() const +QString Profile::dataDir(const QString& subpath) const { - QVariantMap map; - map["id"] = id; - map["name"] = name; - map["url"] = url; - map["created"] = created.toString(Qt::ISODate); - return map; + QString path = Paths::globalDataDir(PROFILES_DIR + "/" + id); + if (!subpath.isEmpty()) + path += "/" + subpath; + return path; } -Profile Profile::fromVariantMap(const QVariantMap& map) +QString Profile::cacheDir(const QString& subpath) const { - Profile p; - p.id = map["id"].toString(); - p.name = map["name"].toString(); - p.url = map["url"].toString(); - p.created = QDateTime::fromString(map["created"].toString(), Qt::ISODate); - return p; + QString path = Paths::globalCacheDir(PROFILES_DIR + "/" + id); + if (!subpath.isEmpty()) + path += "/" + subpath; + return path; +} + +QString Profile::logDir() const +{ +#ifdef Q_OS_MAC + // macOS: ~/Library/Logs//profiles// + QString home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + return home + "/Library/Logs/" + Names::DataName() + "/profiles/" + id; +#else + // Other platforms: profiles//logs/ + return dataDir("logs"); +#endif +} + +QString Profile::name() const +{ + QFile meta(dataDir("profile.json")); + if (meta.open(QIODevice::ReadOnly)) + { + QJsonDocument doc = QJsonDocument::fromJson(meta.readAll()); + meta.close(); + return doc.object()["name"].toString(); + } + return QString(); +} + +void Profile::ensureDirectories() const +{ + QDir().mkpath(dataDir()); + QDir().mkpath(logDir()); + QDir().mkpath(cacheDir()); } ProfileManager::ProfileManager(QObject* parent) - : ComponentBase(parent) + : QObject(parent) { } -bool ProfileManager::componentInitialize() +void ProfileManager::setActiveProfile(const Profile& profile) { - loadProfiles(); - ensureActiveProfile(); - return true; -} - -void ProfileManager::ensureActiveProfile() -{ - // CLI --profile flag takes precedence (already set in Paths before ComponentManager) - QString cliProfileId = Paths::activeProfileId(); - if (!cliProfileId.isEmpty()) - { - m_activeProfileId = cliProfileId; - // Ensure profile is in our list - if (!m_profiles.contains(cliProfileId)) - { - scanProfilesDirectory(); - } - return; - } - - // Already have valid active profile from profiles.json - if (!m_activeProfileId.isEmpty() && m_profiles.contains(m_activeProfileId)) - { - Paths::setActiveProfileId(m_activeProfileId); - return; - } - - // Try to find first existing profile from profiles directory - if (m_profiles.isEmpty()) - { - scanProfilesDirectory(); - } - - // Use first available profile - if (!m_profiles.isEmpty()) - { - QString firstId = m_profiles.keys().first(); - qInfo() << "Activating first available profile:" << firstId; - m_activeProfileId = firstId; - Paths::setActiveProfileId(firstId); - saveProfiles(); - return; - } - - // No profiles exist - create one - qInfo() << "No profiles found, creating new profile"; - QString id = generateProfileId(); - - Profile p; - p.id = id; - p.name = "Default"; - p.url = ""; - p.created = QDateTime::currentDateTime(); - - ensureProfileDirectories(id); - m_profiles[id] = p; - m_activeProfileId = id; - Paths::setActiveProfileId(id); - saveProfiles(); -} - -void ProfileManager::scanProfilesDirectory() -{ - QString profilesPath = Paths::globalDataDir(PROFILES_DIR); - QDir profilesDir(profilesPath); - - if (!profilesDir.exists()) + if (m_activeProfile.id == profile.id) return; - for (const QString& dirName : profilesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name)) - { - // Check if it looks like a UUID - if (dirName.length() == 32 && dirName.contains(QRegularExpression("^[0-9a-f]+$"))) - { - Profile p; - p.id = dirName; - p.name = "Recovered Profile"; - p.url = ""; - p.created = QDateTime::currentDateTime(); - m_profiles[dirName] = p; - qInfo() << "Found orphan profile directory:" << dirName; - } - } -} - -void ProfileManager::loadProfiles() -{ - QString filePath = Paths::globalDataDir(PROFILES_FILE); - QFile file(filePath); - - if (!file.exists()) - { - qInfo() << "No profiles.json found, starting fresh"; - return; - } - - if (!file.open(QIODevice::ReadOnly)) - { - qWarning() << "Failed to open profiles.json for reading"; - return; - } - - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); - file.close(); - - if (error.error != QJsonParseError::NoError) - { - qWarning() << "Failed to parse profiles.json:" << error.errorString(); - return; - } - - QJsonObject root = doc.object(); - - // Load default profile ID - m_activeProfileId = root["defaultProfile"].toString(); - - // Load profiles array - QJsonArray profilesArray = root["profiles"].toArray(); - for (const QJsonValue& val : profilesArray) - { - QJsonObject obj = val.toObject(); - Profile p; - p.id = obj["id"].toString(); - p.name = obj["name"].toString(); - p.url = obj["url"].toString(); - p.created = QDateTime::fromString(obj["created"].toString(), Qt::ISODate); - - if (!p.id.isEmpty()) - { - m_profiles[p.id] = p; - } - } - - // Validate active profile exists - if (!m_activeProfileId.isEmpty() && !m_profiles.contains(m_activeProfileId)) - { - qWarning() << "Active profile" << m_activeProfileId << "not found in profiles list"; - m_activeProfileId.clear(); - } - - qInfo() << "Loaded" << m_profiles.size() << "profiles from profiles.json"; -} - -void ProfileManager::saveProfiles() -{ - QJsonObject root; - root["defaultProfile"] = m_activeProfileId; - - QJsonDocument doc(root); - QByteArray data = doc.toJson(QJsonDocument::Indented); - - QString filePath = Paths::globalDataDir(PROFILES_FILE); - if (!Utils::safelyWriteFile(filePath, data)) - { - qCritical() << "Failed to save profiles.json"; - } -} - -QString ProfileManager::generateProfileId() -{ - return QUuid::createUuid().toString(QUuid::Id128); -} - -void ProfileManager::ensureProfileDirectories(const QString& id) -{ - // Data directory (use global since profile may not be active yet) - QString dataPath = Paths::globalDataDir(PROFILES_DIR + "/" + id); - QDir dataDir(dataPath); - if (!dataDir.exists()) - { - dataDir.mkpath(dataPath); - } - - // Logs subdirectory - QString logsPath = dataPath + "/logs"; - QDir logsDir(logsPath); - if (!logsDir.exists()) - { - logsDir.mkpath(logsPath); - } - - // Cache directory (use global since profile may not be active yet) - QString cachePath = Paths::globalCacheDir(PROFILES_DIR + "/" + id); - QDir cacheDir(cachePath); - if (!cacheDir.exists()) - { - cacheDir.mkpath(cachePath); - } -} - -QString ProfileManager::createProfile(const QString& name, const QString& url) -{ - QString id = generateProfileId(); - - Profile p; - p.id = id; - p.name = name; - p.url = url; - p.created = QDateTime::currentDateTime(); - - ensureProfileDirectories(id); - - m_profiles[id] = p; - saveProfiles(); - - emit profilesChanged(); - - qInfo() << "Created profile" << id << ":" << name << "for" << url; - return id; -} - -bool ProfileManager::deleteProfile(const QString& id) -{ - if (!m_profiles.contains(id)) - { - qWarning() << "Cannot delete profile" << id << "- not found"; - return false; - } - - // Clear active if deleting current - if (m_activeProfileId == id) - { - m_activeProfileId.clear(); - emit activeProfileChanged(); - } - - m_profiles.remove(id); - saveProfiles(); - - // Optionally delete profile directories (leave for now, user can clean manually) - // This avoids accidental data loss - - emit profilesChanged(); - - qInfo() << "Deleted profile" << id; - return true; -} - -bool ProfileManager::renameProfile(const QString& id, const QString& newName) -{ - if (!m_profiles.contains(id)) - { - qWarning() << "Cannot rename profile" << id << "- not found"; - return false; - } - - m_profiles[id].name = newName; - saveProfiles(); - - emit profilesChanged(); - - qInfo() << "Renamed profile" << id << "to" << newName; - return true; -} - -QVariantList ProfileManager::listProfiles() -{ - QVariantList list; - for (const Profile& p : m_profiles) - { - list.append(p.toVariantMap()); - } - return list; -} - -QVariantMap ProfileManager::getProfile(const QString& id) -{ - if (m_profiles.contains(id)) - { - return m_profiles[id].toVariantMap(); - } - return QVariantMap(); -} - -bool ProfileManager::setActiveProfile(const QString& id) -{ - if (id.isEmpty()) - { - m_activeProfileId.clear(); - Paths::setActiveProfileId(QString()); - saveProfiles(); - emit activeProfileChanged(); - return true; - } - - if (!m_profiles.contains(id)) - { - qWarning() << "Cannot activate profile" << id << "- not found"; - return false; - } - - m_activeProfileId = id; - Paths::setActiveProfileId(id); - ensureProfileDirectories(id); - saveProfiles(); + m_activeProfile = profile; + m_activeProfile.ensureDirectories(); + qInfo() << "Activated profile:" << profile.name(); emit activeProfileChanged(); +} + +Profile ProfileManager::profileById(const QString& id) +{ + Profile p; + p.id = id; + return p; +} + +std::optional ProfileManager::profileByName(const QString& name) +{ + QDir dir(profilesDir()); + if (!dir.exists()) + return std::nullopt; + + for (const QString& id : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + if (id.length() != 32 || !id.contains(QRegularExpression("^[0-9a-f]+$"))) + continue; + + Profile p = profileById(id); + if (p.name() == name) + return p; + } + return std::nullopt; +} + +QList ProfileManager::profiles() +{ + QList result; + QDir dir(profilesDir()); + if (!dir.exists()) + return result; + + for (const QString& id : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name)) + { + if (id.length() != 32 || !id.contains(QRegularExpression("^[0-9a-f]+$"))) + continue; + + result.append(profileById(id)); + } + return result; +} + +Profile ProfileManager::createProfile(const QString& name) +{ + QString id = QUuid::createUuid().toString(QUuid::Id128); + Profile profile = profileById(id); + profile.ensureDirectories(); + + // Write profile.json with name + QFile meta(profile.dataDir("profile.json")); + if (meta.open(QIODevice::WriteOnly)) + { + QJsonObject obj; + obj["name"] = name; + meta.write(QJsonDocument(obj).toJson()); + meta.close(); + } + + return profile; +} + +bool ProfileManager::deleteProfile(const Profile& profile) +{ + QDir dir(profile.dataDir()); + if (!dir.removeRecursively()) + return false; + + // Update default if we deleted it + auto current = defaultProfile(); + if (current && current->id == profile.id) + { + auto remaining = profiles(); + if (!remaining.isEmpty()) + setDefaultProfile(remaining.first()); + } - qInfo() << "Activated profile" << id << ":" << m_profiles[id].name; return true; } -QString ProfileManager::profileDataDir() const +std::optional ProfileManager::defaultProfile() { - if (m_activeProfileId.isEmpty()) - return QString(); - - return Paths::dataDir(); + QString profilesFile = Paths::globalDataDir("profiles.json"); + QFile file(profilesFile); + if (file.open(QIODevice::ReadOnly)) + { + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + file.close(); + QString id = doc.object()["defaultProfile"].toString(); + if (!id.isEmpty()) + { + Profile p = profileById(id); + if (QDir(p.dataDir()).exists()) + return p; + } + } + return std::nullopt; } -QString ProfileManager::profileCacheDir() const +void ProfileManager::setDefaultProfile(const Profile& profile) { - if (m_activeProfileId.isEmpty()) - return QString(); - - return Paths::cacheDir(); + QString profilesFile = Paths::globalDataDir("profiles.json"); + QJsonObject root; + root["defaultProfile"] = profile.id; + QFile out(profilesFile); + if (out.open(QIODevice::WriteOnly)) + { + out.write(QJsonDocument(root).toJson()); + out.close(); + } +} + +QString ProfileManager::profilesDir() +{ + return Paths::globalDataDir(PROFILES_DIR); } diff --git a/src/core/ProfileManager.h b/src/core/ProfileManager.h index 0f4d6b6..00a3d57 100644 --- a/src/core/ProfileManager.h +++ b/src/core/ProfileManager.h @@ -2,70 +2,65 @@ #define PROFILEMANAGER_H #include -#include -#include -#include -#include "ComponentManager.h" +#include +#include #include "utils/Utils.h" +class ProfileManager; + struct Profile { - QString id; - QString name; - QString url; - QDateTime created; + // Paths + QString dataDir(const QString& subpath = QString()) const; + QString cacheDir(const QString& subpath = QString()) const; + QString logDir() const; - QVariantMap toVariantMap() const; - static Profile fromVariantMap(const QVariantMap& map); + // Metadata + QString name() const; + + // Directory management + void ensureDirectories() const; + +private: + QString id; + friend class ProfileManager; }; -class ProfileManager : public ComponentBase +class ProfileManager : public QObject { Q_OBJECT DEFINE_SINGLETON(ProfileManager); - Q_PROPERTY(QString activeProfileId READ activeProfileId NOTIFY activeProfileChanged) - Q_PROPERTY(QString profileDataDir READ profileDataDir NOTIFY activeProfileChanged) - Q_PROPERTY(QString profileCacheDir READ profileCacheDir NOTIFY activeProfileChanged) Q_PROPERTY(bool hasActiveProfile READ hasActiveProfile NOTIFY activeProfileChanged) public: - bool componentExport() override { return true; } - const char* componentName() override { return "profiles"; } - bool componentInitialize() override; + // Active profile + static Profile& activeProfile() { return Get().m_activeProfile; } + void setActiveProfile(const Profile& profile); + bool hasActiveProfile() const { return !m_activeProfile.id.isEmpty(); } + + // Profile lookup + static std::optional profileByName(const QString& name); + static QList profiles(); // Profile management - Q_INVOKABLE QString createProfile(const QString& name, const QString& url); - Q_INVOKABLE bool deleteProfile(const QString& id); - Q_INVOKABLE bool renameProfile(const QString& id, const QString& newName); - Q_INVOKABLE QVariantList listProfiles(); - Q_INVOKABLE QVariantMap getProfile(const QString& id); + static Profile createProfile(const QString& name); + static bool deleteProfile(const Profile& profile); - // Active profile - Q_INVOKABLE bool setActiveProfile(const QString& id); - QString activeProfileId() const { return m_activeProfileId; } - bool hasActiveProfile() const { return !m_activeProfileId.isEmpty(); } - - // Path accessors for QML - QString profileDataDir() const; - QString profileCacheDir() const; + // Default profile + static std::optional defaultProfile(); + static void setDefaultProfile(const Profile& profile); Q_SIGNALS: void activeProfileChanged(); - void profilesChanged(); private: explicit ProfileManager(QObject* parent = nullptr); - void loadProfiles(); - void saveProfiles(); - void ensureActiveProfile(); - void scanProfilesDirectory(); - QString generateProfileId(); - void ensureProfileDirectories(const QString& id); + static Profile profileById(const QString& id); + static QString profilesDir(); - QMap m_profiles; - QString m_activeProfileId; + Profile m_activeProfile; }; #endif // PROFILEMANAGER_H diff --git a/src/input/InputMapping.cpp b/src/input/InputMapping.cpp index 61793b1..1bec93c 100644 --- a/src/input/InputMapping.cpp +++ b/src/input/InputMapping.cpp @@ -9,6 +9,7 @@ #include #include "Paths.h" +#include "core/ProfileManager.h" #include "utils/Utils.h" /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -34,7 +35,7 @@ bool InputMapping::loadMappings() // don't watch the path while we potentially copy files to the directory if (m_watcher->directories().size() > 0) - m_watcher->removePath(Paths::dataDir("inputmaps")); + m_watcher->removePath(ProfileManager::activeProfile().dataDir("inputmaps")); // first we load the bundled mappings loadMappingDirectory(":/inputmaps", true); @@ -42,10 +43,10 @@ bool InputMapping::loadMappings() // now we load the user ones, if there are any // they will now overload the built-in ones. // - loadMappingDirectory(Paths::dataDir("inputmaps"), false); + loadMappingDirectory(ProfileManager::activeProfile().dataDir("inputmaps"), false); // we want to watch this dir for new files and changed files - m_watcher->addPath(Paths::dataDir("inputmaps")); + m_watcher->addPath(ProfileManager::activeProfile().dataDir("inputmaps")); emit mappingChanged(); return true; @@ -120,7 +121,7 @@ bool InputMapping::loadMappingDirectory(const QString& path, bool copy) // make a copy of the original file to the example directory if (copy) { - QDir userdir(Paths::dataDir()); + QDir userdir(ProfileManager::activeProfile().dataDir()); userdir.mkpath("inputmaps/examples/"); QString examplePath(userdir.filePath("inputmaps/examples/" + finfo.fileName())); diff --git a/src/main.cpp b/src/main.cpp index 2742662..bb5f562 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,11 +3,8 @@ #include #include #include -#include -#include -#include -#include #include +#include #include #include #include @@ -16,13 +13,13 @@ #include #include #include -#include #include #include "shared/Names.h" #include "system/SystemComponent.h" #include #include "Paths.h" +#include "core/ProfileManager.h" #include "player/CodecsComponent.h" #include "player/PlayerComponent.h" #include "player/OpenGLDetect.h" @@ -278,106 +275,21 @@ int main(int argc, char *argv[]) QSettings::setPath(QSettings::NativeFormat, QSettings::UserScope, absPath); } - // Helper lambdas for profile operations - auto profileExists = [](const QString& id) { - return QDir(Paths::globalDataDir("profiles/" + id)).exists(); - }; - - auto getProfilesList = []() { - QStringList profiles; - QDir profilesDir(Paths::globalDataDir("profiles")); - if (profilesDir.exists()) - { - for (const QString& dir : profilesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name)) - { - if (dir.length() == 32 && dir.contains(QRegularExpression("^[0-9a-f]+$"))) - profiles << dir; - } - } - return profiles; - }; - - auto getProfileName = [](const QString& id) { - QFile meta(Paths::globalDataDir("profiles/" + id + "/profile.json")); - if (meta.open(QIODevice::ReadOnly)) - { - QJsonDocument doc = QJsonDocument::fromJson(meta.readAll()); - meta.close(); - return doc.object()["name"].toString(); - } - return QString(); - }; - - auto findProfileByName = [&getProfilesList, &getProfileName](const QString& name) { - for (const QString& id : getProfilesList()) - { - if (getProfileName(id) == name) - return id; - } - return QString(); - }; - - auto readDefaultProfile = []() { - QString profilesFile = Paths::globalDataDir("profiles.json"); - QFile file(profilesFile); - if (file.open(QIODevice::ReadOnly)) - { - QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); - file.close(); - return doc.object()["defaultProfile"].toString(); - } - return QString(); - }; - - auto writeDefaultProfile = [](const QString& id) { - QString profilesFile = Paths::globalDataDir("profiles.json"); - QJsonObject root; - root["defaultProfile"] = id; - QFile out(profilesFile); - if (out.open(QIODevice::WriteOnly)) - { - out.write(QJsonDocument(root).toJson()); - out.close(); - return true; - } - return false; - }; - - auto createNewProfile = [](const QString& name) { - QString id = QUuid::createUuid().toString(QUuid::Id128); - QDir().mkpath(Paths::globalDataDir("profiles/" + id)); - QDir().mkpath(Paths::globalDataDir("profiles/" + id + "/logs")); - // Always write profile.json with name - QFile meta(Paths::globalDataDir("profiles/" + id + "/profile.json")); - if (meta.open(QIODevice::WriteOnly)) - { - QJsonObject obj; - obj["name"] = name; - meta.write(QJsonDocument(obj).toJson()); - meta.close(); - } - return id; - }; - // Handle --list-profiles if (parser.isSet("list-profiles")) { - QStringList profiles = getProfilesList(); - QString defaultId = readDefaultProfile(); - if (profiles.isEmpty()) + auto allProfiles = ProfileManager::profiles(); + auto defaultProf = ProfileManager::defaultProfile(); + if (allProfiles.isEmpty()) { printf("No profiles found.\n"); } else { - for (const QString& id : profiles) + for (const Profile& p : allProfiles) { - QString marker = (id == defaultId) ? " *" : ""; - QString name = getProfileName(id); - if (name.isEmpty()) - printf("%s%s\n", qPrintable(id), qPrintable(marker)); - else - printf("%s%s\n", qPrintable(name), qPrintable(marker)); + QString marker = (defaultProf && p.name() == defaultProf->name()) ? " *" : ""; + printf("%s%s\n", qPrintable(p.name()), qPrintable(marker)); } } return EXIT_SUCCESS; @@ -392,12 +304,12 @@ int main(int argc, char *argv[]) fprintf(stderr, "Profile name is required\n"); return EXIT_FAILURE; } - if (!findProfileByName(name).isEmpty()) + if (ProfileManager::profileByName(name)) { fprintf(stderr, "Profile already exists: %s\n", qPrintable(name)); return EXIT_FAILURE; } - createNewProfile(name); + ProfileManager::createProfile(name); printf("Created profile: %s\n", qPrintable(name)); return EXIT_SUCCESS; } @@ -406,25 +318,17 @@ int main(int argc, char *argv[]) if (parser.isSet("delete-profile")) { QString name = parser.value("delete-profile"); - QString id = findProfileByName(name); - if (id.isEmpty()) + auto profile = ProfileManager::profileByName(name); + if (!profile) { fprintf(stderr, "Profile not found: %s\n", qPrintable(name)); return EXIT_FAILURE; } - QString profilePath = Paths::globalDataDir("profiles/" + id); - QDir dir(profilePath); - if (!dir.removeRecursively()) + if (!ProfileManager::deleteProfile(*profile)) { fprintf(stderr, "Failed to delete profile: %s\n", qPrintable(name)); return EXIT_FAILURE; } - // Update default if we deleted it - if (readDefaultProfile() == id) - { - QStringList remaining = getProfilesList(); - writeDefaultProfile(remaining.isEmpty() ? QString() : remaining.first()); - } printf("Deleted profile: %s\n", qPrintable(name)); return EXIT_SUCCESS; } @@ -433,30 +337,26 @@ int main(int argc, char *argv[]) if (parser.isSet("set-default-profile")) { QString name = parser.value("set-default-profile"); - QString id = findProfileByName(name); - if (id.isEmpty()) + auto profile = ProfileManager::profileByName(name); + if (!profile) { fprintf(stderr, "Profile not found: %s\n", qPrintable(name)); return EXIT_FAILURE; } - if (!writeDefaultProfile(id)) - { - fprintf(stderr, "Failed to write profiles.json\n"); - return EXIT_FAILURE; - } + ProfileManager::setDefaultProfile(*profile); printf("Default profile set to: %s\n", qPrintable(name)); return EXIT_SUCCESS; } // Determine which profile to use for this session - QString profileId; + std::optional activeProfile; // --profile overrides everything if (parser.isSet("profile")) { QString name = parser.value("profile"); - profileId = findProfileByName(name); - if (profileId.isEmpty()) + activeProfile = ProfileManager::profileByName(name); + if (!activeProfile) { fprintf(stderr, "Profile not found: %s\n", qPrintable(name)); return EXIT_FAILURE; @@ -464,40 +364,26 @@ int main(int argc, char *argv[]) } else { - // Use default profile - profileId = readDefaultProfile(); - bool needsWrite = false; + // Try default profile + activeProfile = ProfileManager::defaultProfile(); - // Validate default exists - if (!profileId.isEmpty() && !profileExists(profileId)) + // Try first available profile + if (!activeProfile) { - profileId.clear(); - needsWrite = true; + auto allProfiles = ProfileManager::profiles(); + if (!allProfiles.isEmpty()) + activeProfile = allProfiles.first(); } - // Find first profile - if (profileId.isEmpty()) - { - QStringList profiles = getProfilesList(); - if (!profiles.isEmpty()) - { - profileId = profiles.first(); - needsWrite = true; - } - } + // Create new profile if none exist + if (!activeProfile) + activeProfile = ProfileManager::createProfile("Default"); - // Create new profile - if (profileId.isEmpty()) - { - profileId = createNewProfile("Default"); - needsWrite = true; - } - - if (needsWrite) - writeDefaultProfile(profileId); + // Update default + ProfileManager::setDefaultProfile(*activeProfile); } - Paths::setActiveProfileId(profileId); + ProfileManager::Get().setActiveProfile(*activeProfile); // Now everything else - all paths are profile-specific from here on QString logLevel = parser.value("log-level"); @@ -574,8 +460,8 @@ int main(int argc, char *argv[]) // Configure default WebEngineProfile paths early (profile is already set) { QWebEngineProfile* defaultProfile = QWebEngineProfile::defaultProfile(); - defaultProfile->setCachePath(Paths::cacheDir("QtWebEngine")); - defaultProfile->setPersistentStoragePath(Paths::dataDir("QtWebEngine")); + defaultProfile->setCachePath(ProfileManager::activeProfile().cacheDir("QtWebEngine")); + defaultProfile->setPersistentStoragePath(ProfileManager::activeProfile().dataDir("QtWebEngine")); } #if defined(Q_OS_MAC) && defined(NDEBUG) @@ -605,7 +491,7 @@ int main(int argc, char *argv[]) // Rotate temp log to main log file and rotate old logs Log::RotateLog(); - qInfo() << "Config directory:" << qPrintable(Paths::dataDir()); + qInfo() << "Config directory:" << qPrintable(ProfileManager::activeProfile().dataDir()); Codecs::preinitCodecs(); diff --git a/src/mpris/MprisComponent.cpp b/src/mpris/MprisComponent.cpp index 992425d..098ad9e 100644 --- a/src/mpris/MprisComponent.cpp +++ b/src/mpris/MprisComponent.cpp @@ -6,14 +6,15 @@ #include "system/SystemComponent.h" #include "settings/SettingsComponent.h" #include "core/Globals.h" +#include "core/ProfileManager.h" #include "ui/WindowManager.h" -#include "shared/Paths.h" #include #include #include #include #include +#include #include #include #include @@ -70,8 +71,9 @@ bool MprisComponent::componentInitialize() return true; } - // Generate profile-specific service name - m_serviceName = QString("org.mpris.MediaPlayer2.JellyfinDesktop.profile_%1").arg(Paths::activeProfileId()); + // Generate profile-specific service name using cache dir basename (profile ID) + QString profileCacheDir = ProfileManager::activeProfile().cacheDir(); + m_serviceName = QString("org.mpris.MediaPlayer2.JellyfinDesktop.profile_%1").arg(QFileInfo(profileCacheDir).fileName()); qDebug() << "Attempting to register MPRIS service:" << m_serviceName; if (!QDBusConnection::sessionBus().registerService(m_serviceName)) @@ -101,7 +103,7 @@ bool MprisComponent::componentInitialize() if (cacheSize > 0) { auto* cache = new QNetworkDiskCache(this); - QString cacheDir = Paths::cacheDir("mpris-albumart"); + QString cacheDir = ProfileManager::activeProfile().cacheDir("mpris-albumart"); cache->setCacheDirectory(cacheDir); cache->setMaximumCacheSize(cacheSize); m_albumArtManager->setCache(cache); diff --git a/src/player/CodecsComponent.cpp b/src/player/CodecsComponent.cpp index c87a4f3..aa0e74a 100644 --- a/src/player/CodecsComponent.cpp +++ b/src/player/CodecsComponent.cpp @@ -29,6 +29,7 @@ #include "system/SystemComponent.h" #include "settings/SettingsComponent.h" +#include "core/ProfileManager.h" #include "utils/Utils.h" #include "shared/Paths.h" #include "PlayerComponent.h" @@ -146,7 +147,7 @@ QString Codecs::plexNameFromFF(QString ffname) /////////////////////////////////////////////////////////////////////////////////////////////////// static QString codecsRootPath() { - return Paths::dataDir("Codecs") + QDir::separator(); + return ProfileManager::activeProfile().dataDir("Codecs") + QDir::separator(); } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -504,7 +505,7 @@ static void updateCodecs() { QStringList candidates = { codecsRootPath(), - Paths::dataDir() + "/codecs/", + ProfileManager::activeProfile().dataDir("codecs"), }; QSet codecFiles; diff --git a/src/settings/SettingsComponent.cpp b/src/settings/SettingsComponent.cpp index fcb416d..23f2fcd 100644 --- a/src/settings/SettingsComponent.cpp +++ b/src/settings/SettingsComponent.cpp @@ -4,6 +4,7 @@ #include "SettingsComponent.h" #include "SettingsSection.h" #include "Paths.h" +#include "core/ProfileManager.h" #include "utils/Utils.h" #include "AudioSettingsController.h" #include "Names.h" @@ -217,7 +218,7 @@ static void writeJson(const QString& filename, const QJsonObject& data, bool pre ///////////////////////////////////////////////////////////////////////////////////////// QVariant SettingsComponent::readPreinitValue(const QString& sectionID, const QString& key) { - QString path = Paths::dataDir("jellyfin-desktop.conf"); + QString path = ProfileManager::activeProfile().dataDir("jellyfin-desktop.conf"); if (path.isEmpty()) return QVariant(); QJsonObject json = loadJson(path); @@ -227,15 +228,15 @@ QVariant SettingsComponent::readPreinitValue(const QString& sectionID, const QSt ///////////////////////////////////////////////////////////////////////////////////////// void SettingsComponent::load() { - QString profileDir = Paths::dataDir(); + QString profileDir = ProfileManager::activeProfile().dataDir(); if (profileDir.isEmpty()) { qInfo() << "No active profile, skipping settings load (using defaults)"; return; } - loadConf(Paths::dataDir("jellyfin-desktop.conf"), false); - loadConf(Paths::dataDir("storage.json"), true); + loadConf(ProfileManager::activeProfile().dataDir("jellyfin-desktop.conf"), false); + loadConf(ProfileManager::activeProfile().dataDir("storage.json"), true); } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -295,7 +296,7 @@ void SettingsComponent::loadConf(const QString& path, bool storage) /////////////////////////////////////////////////////////////////////////////////////////////////// void SettingsComponent::saveSettings() { - QString path = Paths::dataDir("jellyfin-desktop.conf"); + QString path = ProfileManager::activeProfile().dataDir("jellyfin-desktop.conf"); if (path.isEmpty()) { qWarning() << "No active profile, cannot save settings"; @@ -319,7 +320,7 @@ void SettingsComponent::saveSettings() /////////////////////////////////////////////////////////////////////////////////////////////////// void SettingsComponent::saveStorage() { - QString path = Paths::dataDir("storage.json"); + QString path = ProfileManager::activeProfile().dataDir("storage.json"); if (path.isEmpty()) { qWarning() << "No active profile, cannot save storage"; @@ -716,11 +717,11 @@ bool SettingsComponent::componentInitialize() ///////////////////////////////////////////////////////////////////////////////////////// bool SettingsComponent::resetAndSaveOldConfiguration() { - QString path = Paths::dataDir("jellyfin-desktop.conf"); + QString path = ProfileManager::activeProfile().dataDir("jellyfin-desktop.conf"); if (path.isEmpty()) return false; QFile settingsFile(path); - return settingsFile.rename(Paths::dataDir("jellyfin-desktop.conf.old")); + return settingsFile.rename(ProfileManager::activeProfile().dataDir("jellyfin-desktop.conf.old")); } ///////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/shared/Paths.cpp b/src/shared/Paths.cpp index 2efe0d9..8261c57 100644 --- a/src/shared/Paths.cpp +++ b/src/shared/Paths.cpp @@ -12,11 +12,11 @@ #include #include "Names.h" #include "Version.h" +#include "core/ProfileManager.h" ///////////////////////////////////////////////////////////////////////////////////////// static QString g_configDirOverride; static QString g_cacheDirOverride; -static QString g_activeProfileId; static bool g_portableMode = false; ///////////////////////////////////////////////////////////////////////////////////////// @@ -152,83 +152,13 @@ QString Paths::globalCacheDir(const QString& file) return d.filePath(file); } -///////////////////////////////////////////////////////////////////////////////////////// -QString Paths::dataDir(const QString& file) -{ - // Use profile-specific directory when profile is active - if (!g_activeProfileId.isEmpty()) - { - QDir d(globalDataDir("profiles/" + g_activeProfileId)); - if (!d.mkpath(d.absolutePath())) - { - qWarning() << "Failed to create profile data directory:" << d.absolutePath(); - return globalDataDir(file); - } - - if (file.isEmpty()) - return d.absolutePath(); - return d.filePath(file); - } - - // Fallback to global when no profile active - return globalDataDir(file); -} - -///////////////////////////////////////////////////////////////////////////////////////// -QString Paths::cacheDir(const QString& file) -{ - // Use profile-specific directory when profile is active - if (!g_activeProfileId.isEmpty()) - { - QDir d(globalCacheDir("profiles/" + g_activeProfileId)); - if (!d.mkpath(d.absolutePath())) - { - qWarning() << "Failed to create profile cache directory:" << d.absolutePath(); - return globalCacheDir(file); - } - - if (file.isEmpty()) - return d.absolutePath(); - return d.filePath(file); - } - - // Fallback to global when no profile active - return globalCacheDir(file); -} - -///////////////////////////////////////////////////////////////////////////////////////// -QString Paths::logDir(const QString& file) -{ -#ifdef Q_OS_MAC - // On macOS, logs go to ~/Library/Logs/ - QDir ldir = QDir(QStandardPaths::locate(QStandardPaths::HomeLocation, "", QStandardPaths::LocateDirectory)); - - if (!g_activeProfileId.isEmpty()) - { - ldir.mkpath(ldir.absolutePath() + "/Library/Logs/" + Names::DataName() + "/" + g_activeProfileId); - ldir.cd("Library/Logs/" + Names::DataName() + "/" + g_activeProfileId); - } - else - { - ldir.mkpath(ldir.absolutePath() + "/Library/Logs/" + Names::DataName()); - ldir.cd("Library/Logs/" + Names::DataName()); - } - - return ldir.filePath(file); -#else - // On other platforms, logs go to dataDir/logs/ - QDir ldir(dataDir()); - ldir.mkpath(ldir.absolutePath() + "/logs"); - ldir.cd("logs"); - return ldir.filePath(file); -#endif -} - ///////////////////////////////////////////////////////////////////////////////////////// QString Paths::socketName(const QString& serverName) { - QString profileId = g_activeProfileId.isEmpty() ? "default" : g_activeProfileId; - QString socketFile = QString("jellyfin-desktop.%1.%2").arg(profileId, serverName); + QString profileName = ProfileManager::activeProfile().name(); + if (profileName.isEmpty()) + profileName = "default"; + QString socketFile = QString("jellyfin-desktop.%1.%2").arg(profileName, serverName); #ifdef Q_OS_UNIX QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); @@ -244,7 +174,7 @@ QString Paths::socketName(const QString& serverName) QString Paths::soundsPath(const QString& sound) { // check local filesystem first - auto localSound = dataDir("sounds/" + sound); + auto localSound = ProfileManager::activeProfile().dataDir("sounds/" + sound); QFileInfo f(localSound); if (f.exists()) @@ -273,15 +203,3 @@ QString Paths::webExtensionPath(const QString& mode) QString webName = QString("web-client/%1").arg(mode); return resourceDir(webName + "/"); } - -///////////////////////////////////////////////////////////////////////////////////////// -void Paths::setActiveProfileId(const QString& id) -{ - g_activeProfileId = id; -} - -///////////////////////////////////////////////////////////////////////////////////////// -QString Paths::activeProfileId() -{ - return g_activeProfileId; -} diff --git a/src/shared/Paths.h b/src/shared/Paths.h index 41b52d5..4296805 100644 --- a/src/shared/Paths.h +++ b/src/shared/Paths.h @@ -24,13 +24,6 @@ namespace Paths // Global paths (always ~/.local/share/jellyfin-desktop or equivalent) QString globalDataDir(const QString& file = QString()); QString globalCacheDir(const QString& file = QString()); - - // Profile-aware paths (profile-specific when active, fallback to global) - void setActiveProfileId(const QString& id); - QString activeProfileId(); - QString dataDir(const QString& file = QString()); - QString cacheDir(const QString& file = QString()); - QString logDir(const QString& file = QString()); }; #endif //KONVERGO_PATHS_H diff --git a/src/system/SystemComponent.cpp b/src/system/SystemComponent.cpp index 2f9fd6c..16e57d2 100644 --- a/src/system/SystemComponent.cpp +++ b/src/system/SystemComponent.cpp @@ -29,6 +29,7 @@ #include "settings/SettingsComponent.h" #include "settings/SettingsSection.h" #include "Paths.h" +#include "core/ProfileManager.h" #include "Names.h" #include "utils/Utils.h" #include "utils/Log.h" @@ -102,8 +103,8 @@ SystemComponent::SystemComponent(QObject* parent) : ComponentBase(parent), m_pla ///////////////////////////////////////////////////////////////////////////////////////// bool SystemComponent::componentInitialize() { - QDir().mkpath(Paths::dataDir("scripts")); - QDir().mkpath(Paths::dataDir("sounds")); + QDir().mkpath(ProfileManager::activeProfile().dataDir("scripts")); + QDir().mkpath(ProfileManager::activeProfile().dataDir("sounds")); return true; } @@ -457,8 +458,8 @@ QString SystemComponent::debugInformation() stream << "\n"; stream << "Files\n"; - stream << " Log file: " << Paths::logDir(Names::DataName() + ".log") << "\n"; - stream << " Config file: " << Paths::dataDir(Names::DataName() + ".conf") << "\n"; + stream << " Log file: " << ProfileManager::activeProfile().logDir() + "/" + Names::DataName() + ".log" << "\n"; + stream << " Config file: " << ProfileManager::activeProfile().dataDir(Names::DataName() + ".conf") << "\n"; stream << "\n"; stream << "Network Addresses\n"; @@ -506,7 +507,7 @@ void SystemComponent::runUserScript(QString script) // to be careful with their keymaps. // QFileInfo fi(script); - QString scriptPath = Paths::dataDir("scripts/" + fi.fileName()); + QString scriptPath = ProfileManager::activeProfile().dataDir("scripts/" + fi.fileName()); QFile scriptFile(scriptPath); if (scriptFile.exists()) diff --git a/src/utils/Log.cpp b/src/utils/Log.cpp index 569114c..f34aee0 100644 --- a/src/utils/Log.cpp +++ b/src/utils/Log.cpp @@ -10,6 +10,7 @@ #include "shared/Names.h" #include "shared/Paths.h" +#include "core/ProfileManager.h" #include "settings/SettingsComponent.h" #include "Version.h" @@ -87,8 +88,7 @@ void Log::CensorAuthTokens(QString& msg) ///////////////////////////////////////////////////////////////////////////////////////// static QString getLogDir() { - // Paths::logDir() returns profile-specific path when profile is active - return Paths::logDir(""); + return ProfileManager::activeProfile().logDir(); } /////////////////////////////////////////////////////////////////////////////////////////