diff --git a/README.md b/README.md index b5dab98..b1ccea3 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,9 @@ After onboarding, settings are saved to a file whose location depends on the OS: | macOS | `~/Library/Preferences/Logos.LogosStorage.plist` | | Windows | `HKCU\Software\Logos\LogosStorage` (Registry) | -## Configuration Management +The settings are saved to the preferences file to preserve the onboarding defaults, but the active configuration is stored in `${HOME}/.logos_storage/config.json`. You can tweak the values there directly. Note that running the onboarding again will override any onboarding-related values. -To restart the onboarding process, simply delete the configuration file and relaunch the application. - -You can override the configuration by placing a `config.json` file in the app's startup folder. This file takes precedence over any existing configuration. +To restart the onboarding process, simply delete the prefences file and relaunch the application. The application also provides a JSON editor in the debug panel for runtime configuration tweaks. To apply changes, restart the Storage Module. diff --git a/app/main.cpp b/app/main.cpp index 71883d7..05fb2ab 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -30,8 +30,10 @@ int main(int argc, char* argv[]) { QApplication app(argc, argv); // Set application properties for Qt Settings + // QCoreApplication::setOrganizationName("Logos"); + // QCoreApplication::setOrganizationDomain("logos.co"); + // QCoreApplication::setApplicationName("LogosStorage"); QCoreApplication::setOrganizationName("Logos"); - QCoreApplication::setOrganizationDomain("logos.co"); QCoreApplication::setApplicationName("LogosStorage"); // Set the plugins directory diff --git a/src/StorageBackend.cpp b/src/StorageBackend.cpp index bcd1a8d..592b3f7 100644 --- a/src/StorageBackend.cpp +++ b/src/StorageBackend.cpp @@ -201,6 +201,8 @@ LogosResult StorageBackend::init(const QString& configJson = "{}") { debug("new config is: " + m_configJson); } + emit initCompleted(); + return {true, ""}; } @@ -827,9 +829,20 @@ void StorageBackend::reloadIfChanged(const QString& configJson) { emit configJsonChanged(); } -bool StorageBackend::validateDataDir(const QString& path) { - QFileInfo info(path); - return info.exists() && info.isDir() && info.isReadable() && info.isWritable(); +void StorageBackend::saveUserConfig(const QString& configJson) { + qDebug() << "StorageBackend::saveUserConfig"; + + QString configPath = getUserConfigPath(); + QString folderPath = QFileInfo(configPath).absolutePath(); + QDir().mkpath(folderPath); + QFile file(configPath); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + file.write(configJson.toUtf8()); + file.close(); + debug("Config saved to " + configPath); + } else { + debug("Failed to save config to " + configPath); + } } QString StorageBackend::buildConfig(const QString& dataDir, int discPort, int tcpPort) { @@ -871,6 +884,17 @@ QString StorageBackend::buildConfigFromFile(const QString& path) { void StorageBackend::status(StorageStatus status) { m_status = status; } +QString StorageBackend::getUserConfigPath() { return QDir::homePath() + "/.logos_storage/config.json"; } + +QString StorageBackend::getUserConfig() { + QFile file(getUserConfigPath()); + if (file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return QString::fromUtf8(file.readAll()); + } + + return "{}"; +} + QString StorageBackend::defaultDataDir() { QString home = QDir::homePath(); #ifdef Q_OS_WIN diff --git a/src/StorageBackend.h b/src/StorageBackend.h index 0745991..e46ed89 100644 --- a/src/StorageBackend.h +++ b/src/StorageBackend.h @@ -54,6 +54,8 @@ class StorageBackend : public QObject { qint64 quotaReservedBytes() const; Q_INVOKABLE static QString defaultDataDir(); + static QString getUserConfig(); + static QString getUserConfigPath(); explicit StorageBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr); ~StorageBackend(); @@ -78,13 +80,13 @@ class StorageBackend : public QObject { void downloadManifest(const QString& cid); void downloadManifests(); void space(); - bool validateDataDir(const QString& path); LogosResult init(const QString& configJson); void updateLogLevel(const QString& logLevel); void reloadIfChanged(const QString& configJson); void status(StorageStatus status); QString buildConfig(const QString& dataDir, int discPort, int tcpPort); QString buildConfigFromFile(const QString& path); + void saveUserConfig(const QString& configJson); signals: void startCompleted(); @@ -98,6 +100,7 @@ class StorageBackend : public QObject { void uploadStatusChanged(); void manifestsChanged(); void quotaChanged(); + void initCompleted(); private slots: diff --git a/src/StorageUIPlugin.cpp b/src/StorageUIPlugin.cpp index 89acd7c..09ffcfd 100644 --- a/src/StorageUIPlugin.cpp +++ b/src/StorageUIPlugin.cpp @@ -14,7 +14,6 @@ QWidget* StorageUIPlugin::createWidget(LogosAPI* logosAPI) { qDebug() << "StorageUIPlugin::createWidget called"; QCoreApplication::setOrganizationName("Logos"); - QCoreApplication::setOrganizationDomain("logos.co"); QCoreApplication::setApplicationName("LogosStorage"); QQuickWidget* quickWidget = new QQuickWidget(); @@ -27,29 +26,23 @@ QWidget* StorageUIPlugin::createWidget(LogosAPI* logosAPI) { qDebug() << "StorageUIPlugin: Loading settings..."; - QSettings settings("Logos", "LogosStorage"); - int discoveryPort = settings.value("Storage/discoveryPort", 0).toInt(); - int tcpPort = settings.value("Storage/tcpPort", 0).toInt(); - QString dataDir = settings.value("Storage/dataDir", "").toString(); - bool onboardingCompleted = settings.value("Storage/onboardingCompleted", false).toBool(); + // Default constructor uses QCoreApplication org/domain/app — same path as QML QtCore.Settings + // QSettings settings; + // int discoveryPort = settings.value("Storage/discoveryPort", 8090).toInt(); + // int tcpPort = settings.value("Storage/tcpPort", 0).toInt(); + // QString dataDir = settings.value("Storage/dataDir", "").toString(); + // bool onboardingCompleted = settings.value("Storage/onboardingCompleted", false).toBool(); - qDebug() << "StorageUIPlugin: Settings Loaded onboardingCompleted=" << onboardingCompleted; - qDebug() << "StorageUIPlugin: Settings Loaded dataDir=" << dataDir; - qDebug() << "StorageUIPlugin: Settings Loaded discoveryPort=" << discoveryPort; - qDebug() << "StorageUIPlugin: Settings Loaded tcpPort=" << tcpPort; + // qDebug() << "StorageUIPlugin: Settings file:" << settings.fileName(); + // qDebug() << "StorageUIPlugin: onboardingCompleted=" << onboardingCompleted; + // qDebug() << "StorageUIPlugin: dataDir=" << dataDir; + // qDebug() << "StorageUIPlugin: discoveryPort=" << discoveryPort; + // qDebug() << "StorageUIPlugin: tcpPort=" << tcpPort; - QString qmlPath = "qrc:/Main.qml"; - - // Create backend instance + // Always load Main.qml — QML handles navigation (onboarding vs startNode) StorageBackend* backend = new StorageBackend(logosAPI, quickWidget); - if (onboardingCompleted) { - qmlPath = "qrc:/StorageView.qml"; - } - - qDebug() << "StorageUIPlugin: qmlPath=" << qmlPath; - - quickWidget->setSource(QUrl(qmlPath)); + quickWidget->setSource(QUrl("qrc:/Main.qml")); if (quickWidget->status() == QQuickWidget::Error) { qWarning() << "StorageUIPlugin: Failed to load QML:" << quickWidget->errors(); @@ -61,33 +54,27 @@ QWidget* StorageUIPlugin::createWidget(LogosAPI* logosAPI) { root->setProperty("backend", QVariant::fromValue(static_cast(backend))); - QString configJson = "{}"; + // Build config from settings if onboarding was done, otherwise use empty config + QString configJson = StorageBackend::getUserConfig(); + qDebug() << "UserConfig" << StorageBackend::getUserConfigPath(); + qDebug() << "configJson" << configJson; + // if (onboardingCompleted && !dataDir.isEmpty()) { + // configJson = backend->buildConfig(dataDir, discoveryPort, tcpPort); + // } - if (onboardingCompleted) { - configJson = backend->buildConfig(dataDir, discoveryPort, tcpPort); - } + // config.json overrides everything (dev/debug use) + // QFileInfo info("config.json"); + // if (info.exists() && info.isFile()) { + // qWarning() << "StorageUIPlugin: config.json found — overriding settings config"; + // configJson = backend->buildConfigFromFile("config.json"); + // } - QFileInfo info("config.json"); - - if (info.exists() && info.isFile()) { - qWarning() - << "StorageUIPlugin: config.json is found ! It will override the configuration loaded by the onboarding !"; - configJson = backend->buildConfigFromFile("config.json"); - } - - qDebug() << "StorageUIPlugin: configuration loaded configLoaded=" << configJson; + // qDebug() << "StorageUIPlugin: configJson=" << configJson; LogosResult result = backend->init(configJson); if (!result.success) { - QString error = result.getError(); - qWarning() << "StorageUIPlugin: Failed to init backend, will use mock version:" << error; - } else if (onboardingCompleted) { - LogosResult result = backend->start(); - - if (!result.success) { - qWarning() << "StorageUIPlugin: Failed to start the Storage Module."; - } + qWarning() << "StorageUIPlugin: Failed to init backend:" << result.getError(); } return quickWidget; diff --git a/src/qml/LogosStorageButton.qml b/src/qml/LogosStorageButton.qml index 0ecf2f0..8b5bfb1 100644 --- a/src/qml/LogosStorageButton.qml +++ b/src/qml/LogosStorageButton.qml @@ -18,8 +18,6 @@ Button { color: { if (!control.enabled) return Theme.palette.backgroundElevated - if (control.hovered) - return Theme.palette.backgroundTertiary return Theme.palette.backgroundSecondary } border.width: 1 diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 93317cd..1a13bf0 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -12,20 +12,19 @@ Item { property var backend: mockBackend - Timer { - readonly property int running: 2 - - id: timer - interval: 2000 - repeat: false - onTriggered: { - console.log("timer triggered") - root.backend.status = running - root.backend.startCompleted() - console.info(root.backend.status) - } - } + // Timer { + // readonly property int running: 2 + // id: timer + // interval: 2000 + // repeat: false + // onTriggered: { + // console.log("timer triggered") + // // root.backend.status = running + // // root.backend.startCompleted() + // // console.info(root.backend.status) + // } + // } QtObject { id: mockBackend @@ -40,7 +39,8 @@ Item { } function start() { - timer.start() + // timer.start() + console.log("mock start callde") } function stop() { @@ -62,18 +62,10 @@ Item { id: settings category: "Storage" - property int discoveryPort: 0 + property int discoveryPort: 8090 property int tcpPort: 0 property string dataDir: "" property bool onboardingCompleted: false - - Component.onCompleted: { - if (onboardingCompleted) { - - // stackView.replace(storageView) - // root.backend.start(); - } - } } StackView { @@ -87,6 +79,10 @@ Item { OnBoarding { id: onboardingInstance + backend: root.backend + discoveryPort: settings.discoveryPort + tcpPort: settings.tcpPort + dataDir: settings.dataDir.length > 0 ? settings.dataDir : root.backend.defaultDataDir() onCompleted: { settings.discoveryPort = discoveryPort @@ -96,6 +92,7 @@ Item { let config = root.backend.buildConfig(dataDir, discoveryPort, tcpPort) + root.backend.saveUserConfig(config) root.backend.reloadIfChanged(config) root.backend.start() @@ -107,7 +104,7 @@ Item { Component { id: storageView StorageView { - backend: root.backend // @disable-check M228 + backend: root.backend } } @@ -132,5 +129,12 @@ Item { function onStopCompleted() { stackView.pop() } + + function onInitCompleted() { + if (settings.onboardingCompleted) { + root.backend.start() + stackView.replace(storageView, StackView.Immediate) + } + } } } diff --git a/src/qml/OnBoarding.qml b/src/qml/OnBoarding.qml index ad97fc9..c60464c 100644 --- a/src/qml/OnBoarding.qml +++ b/src/qml/OnBoarding.qml @@ -22,10 +22,6 @@ Rectangle { QtObject { id: mockBackend - function validateDataDir(path) { - return path != "error" - } - function defaultDataDir() { return ".cache/storage" } @@ -111,17 +107,12 @@ Rectangle { spacing: Theme.spacing.tiny LogosTextField { + isValid: text.trim().length > 0 id: dataDirTextField placeholderText: "Enter the data dir" text: root.dataDir Layout.fillWidth: true onTextChanged: { - if (text.length > 0) { - isValid = root.backend.validateDataDir(text) - } else { - isValid = false - } - root.dataDir = text } } diff --git a/src/qml/StorageView.qml b/src/qml/StorageView.qml index f8976f3..f7cebda 100644 --- a/src/qml/StorageView.qml +++ b/src/qml/StorageView.qml @@ -128,16 +128,20 @@ Rectangle { function updateLogLevel(logLevel) {} property var manifests: [] - property var quotaMaxBytes: 20 * 1024 * 1024 * 1024 // 20 GB default + property var quotaMaxBytes: 20 * 1024 * 1024 * 1024 // 20 GB default property var quotaUsedBytes: 0 property var quotaReservedBytes: 0 } function formatBytes(bytes) { - if (bytes <= 0) return "0 B" - if (bytes < 1024) return bytes + " B" - if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB" - if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + " MB" + if (bytes <= 0) + return "0 B" + if (bytes < 1024) + return bytes + " B" + if (bytes < 1024 * 1024) + return (bytes / 1024).toFixed(1) + " KB" + if (bytes < 1024 * 1024 * 1024) + return (bytes / (1024 * 1024)).toFixed(1) + " MB" return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB" } @@ -417,8 +421,8 @@ Rectangle { width: parent.width - 40 height: root.backend.quotaMaxBytes > 0 ? 36 : 20 - readonly property real total: root.backend.quotaMaxBytes - readonly property real used: root.backend.quotaUsedBytes + readonly property real total: root.backend.quotaMaxBytes + readonly property real used: root.backend.quotaUsedBytes readonly property real reserved: root.backend.quotaReservedBytes // No quota configured @@ -446,7 +450,9 @@ Rectangle { // Used (green) Rectangle { - width: Math.min(parent.width * (spaceBarSection.used / spaceBarSection.total), parent.width) + width: Math.min( + parent.width * (spaceBarSection.used / spaceBarSection.total), + parent.width) height: parent.height radius: parent.radius color: "#4CAF50" @@ -455,8 +461,9 @@ Rectangle { // Reserved (orange), stacked after used Rectangle { x: parent.width * (spaceBarSection.used / spaceBarSection.total) - width: Math.min(parent.width * (spaceBarSection.reserved / spaceBarSection.total), - parent.width - x) + width: Math.min( + parent.width * (spaceBarSection.reserved / spaceBarSection.total), + parent.width - x) height: parent.height color: "#FF9800" } @@ -481,7 +488,8 @@ Rectangle { font.pixelSize: 10 } Text { - text: "Free: " + root.formatBytes(spaceBarSection.total - spaceBarSection.used - spaceBarSection.reserved) + text: "Free: " + root.formatBytes( + spaceBarSection.total - spaceBarSection.used - spaceBarSection.reserved) color: "#888888" font.pixelSize: 10 } @@ -618,7 +626,7 @@ Rectangle { font.family: "monospace" elide: Text.ElideMiddle anchors.verticalCenter: parent.verticalCenter - ToolTip.visible: hovered + // ToolTip.visible: hovered ToolTip.text: modelData["cid"] ?? "" HoverHandler {} } @@ -899,8 +907,19 @@ Rectangle { } onTextChanged: { + + // try { + // const jsonData = JSON.parse(text) + // isValid = true + // } catch (e) { + // isValid = false + // } + } + + onEditingFinished: { try { const jsonData = JSON.parse(text) + root.backend.saveUserConfig(text) isValid = true } catch (e) { isValid = false