Refactor ProfileManager to encapsulate profile IDs

- Make Profile::id private, expose only through ProfileManager
- Add std::optional<Profile> returns for profileByName/defaultProfile
- Add profiles(), createProfile(), deleteProfile(), setDefaultProfile()
- Update setActiveProfile() to take Profile instead of id string
- Move profile operations from main.cpp lambdas into ProfileManager
- Remove profile-aware methods from Paths (dataDir, cacheDir, logDir)
- All callers now use ProfileManager::activeProfile().dataDir() etc.
This commit is contained in:
Andrew Rabert committed 2025-12-27 01:45:52 -05:00
1 parent c563de40ac
commit c8cd28e6ca
12 files changed
+264 -636

No files matched your search

-4
View File
@@ -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
//
+158 -324
View File
@@ -1,360 +1,194 @@
#include "ProfileManager.h"
#include "Paths.h"
#include "Names.h"
#include <QDir>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QUuid>
#include <QDebug>
#include <QRegularExpression>
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/<AppName>/profiles/<id>/
QString home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
return home + "/Library/Logs/" + Names::DataName() + "/profiles/" + id;
#else
// Other platforms: profiles/<id>/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<Profile> 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<Profile> ProfileManager::profiles()
{
QList<Profile> 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<Profile> 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);
}
+34 -39
View File
@@ -2,70 +2,65 @@
#define PROFILEMANAGER_H
#include <QObject>
#include <QVariantList>
#include <QVariantMap>
#include <QDateTime>
#include "ComponentManager.h"
#include <QList>
#include <optional>
#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<Profile> profileByName(const QString& name);
static QList<Profile> 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<Profile> 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<QString, Profile> m_profiles;
QString m_activeProfileId;
Profile m_activeProfile;
};
#endif // PROFILEMANAGER_H
+5 -4
View File
@@ -9,6 +9,7 @@
#include <QDebug>
#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()));
+35 -149
View File
@@ -3,11 +3,8 @@
#include <QDateTime>
#include <QFileInfo>
#include <QIcon>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QtQml>
#include <optional>
#include <Qt>
#include <QtWebEngineQuick>
#include <qtwebenginecoreglobal.h>
@@ -16,13 +13,13 @@
#include <QtWebEngineCore/QWebEngineScript>
#include <QCommandLineOption>
#include <QDebug>
#include <QRegularExpression>
#include <QSettings>
#include "shared/Names.h"
#include "system/SystemComponent.h"
#include <QDebug>
#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<Profile> 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();
+6 -4
View File
@@ -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 <QDBusConnection>
#include <QDBusMessage>
#include <QDBusError>
#include <QApplication>
#include <QDebug>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
@@ -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);
+3 -2
View File
@@ -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<QString> codecFiles;
+9 -8
View File
@@ -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"));
}
/////////////////////////////////////////////////////////////////////////////////////////
+6 -88
View File
@@ -12,11 +12,11 @@
#include <QDebug>
#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;
}
-7
View File
@@ -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
+6 -5
View File
@@ -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())
+2 -2
View File
@@ -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();
}
/////////////////////////////////////////////////////////////////////////////////////////