fix(settings): le impostazioni ora persistono davvero dal launcher (v0.5.3)
Causa (il "solito" problema dei progetti Sailfish): QSettings di default scrive in ~/.config/harbour/hermes.conf, FUORI dalla directory che la sandbox SailJail rende persistente (~/.config/harbour/hermes/). Dal launcher le modifiche finivano in una vista effimera e sparivano a ogni riavvio; da terminale invece restavano (da qui l'effetto "a volte si' a volte no"). Stesso problema gia' risolto in harbour-calibreweb e harbour-quill con il percorso esplicito. - Settings: QSettings(configPath(), IniFormat) con configPath() = AppConfigLocation + "/hermes.conf" (mkpath della directory) - store(): ogni setter fa setValue + sync immediato (nessuna modifica persa se il sistema termina l'app) - migrateIfNeeded(): al primo avvio importa i valori rimasti nel vecchio percorso di default (~/.config/harbour/hermes.conf, piu' i path legacy nemo/defaultuser) - diagnostica: log del file impostazioni e dei valori letti all'avvio - core-test: test di persistenza (scrittura con una istanza, rilettura con una SECONDA istanza: prova che passa dal file su disco)
This commit is contained in:
@@ -76,8 +76,9 @@ speaks (validated live against a real instance).
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Version 0.5.2 — protocol and C++ core validated against a real `hermes-webui`
|
Version 0.5.3 — protocol and C++ core validated against a real `hermes-webui`
|
||||||
(login, sessions, streaming chat, TTS, STT, voice dialog, profile selection);
|
(login, sessions, streaming chat, TTS, STT, voice dialog, profile selection);
|
||||||
UI in English, Italian, French and German; replies rendered as formatted
|
UI in English, Italian, French and German; replies rendered as formatted
|
||||||
markdown and cleaned before read-aloud; every request has a watchdog
|
markdown and cleaned before read-aloud; settings persist correctly from the
|
||||||
timeout; on-device testing still ongoing.
|
launcher (SailJail-safe config path); every request has a watchdog timeout;
|
||||||
|
on-device testing still ongoing.
|
||||||
|
|||||||
+3
-3
@@ -76,12 +76,12 @@ richiede il probe WebUI su :8899). Esito 12/09/2026: unit 9/9, live completo OK.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/workspace
|
cd ~/workspace
|
||||||
tar czf harbour-hermes-0.5.2.tar.gz \
|
tar czf harbour-hermes-0.5.3.tar.gz \
|
||||||
--transform 's,^harbour-hermes,harbour-hermes-0.5.2,' \
|
--transform 's,^harbour-hermes,harbour-hermes-0.5.3,' \
|
||||||
--exclude='.git' harbour-hermes
|
--exclude='.git' harbour-hermes
|
||||||
```
|
```
|
||||||
|
|
||||||
Lo spec si aspetta la directory `harbour-hermes-0.5.2/` (pattern degli altri
|
Lo spec si aspetta la directory `harbour-hermes-0.5.3/` (pattern degli altri
|
||||||
progetti: cercato in modo robusto anche per i sorgenti live di sfdk).
|
progetti: cercato in modo robusto anche per i sorgenti live di sfdk).
|
||||||
|
|
||||||
## Traduzioni
|
## Traduzioni
|
||||||
|
|||||||
+11
-1
@@ -1,6 +1,6 @@
|
|||||||
Name: harbour-hermes
|
Name: harbour-hermes
|
||||||
Summary: Client for the Hermes Web UI with voice
|
Summary: Client for the Hermes Web UI with voice
|
||||||
Version: 0.5.2
|
Version: 0.5.3
|
||||||
Release: 1
|
Release: 1
|
||||||
Group: Qt/Qt
|
Group: Qt/Qt
|
||||||
License: MIT
|
License: MIT
|
||||||
@@ -25,6 +25,16 @@ Sessions list, chat with live streaming replies, voice dictation
|
|||||||
(server-side TTS), plus a hands-free conversation mode.
|
(server-side TTS), plus a hands-free conversation mode.
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Sun Sep 13 2026 Carlo Baratto <carlo@carlobaratto.it> - 0.5.3-1
|
||||||
|
- Fix persistenza delle impostazioni quando l'app parte dal launcher:
|
||||||
|
QSettings "di default" scriveva in ~/.config/harbour/hermes.conf, FUORI
|
||||||
|
dalla directory che la sandbox SailJail rende persistente
|
||||||
|
(~/.config/harbour/hermes/): le modifiche si perdevano a ogni riavvio
|
||||||
|
(da terminale invece restavano). Ora QSettings punta esplicitamente a
|
||||||
|
~/.config/harbour/hermes/hermes.conf (stesso pattern di
|
||||||
|
harbour-calibreweb e harbour-quill); ogni scrittura fa sync immediato;
|
||||||
|
migrazione automatica dei valori rimasti nel vecchio percorso; log
|
||||||
|
diagnostico del percorso impostazioni all'avvio
|
||||||
* Sun Sep 13 2026 Carlo Baratto <carlo@carlobaratto.it> - 0.5.2-1
|
* Sun Sep 13 2026 Carlo Baratto <carlo@carlobaratto.it> - 0.5.2-1
|
||||||
- Chat: le risposte di Hermes sono mostrate formattate (grassetti, corsivi,
|
- Chat: le risposte di Hermes sono mostrate formattate (grassetti, corsivi,
|
||||||
titoli, liste, citazioni, codice, link) con un convertitore markdown ->
|
titoli, liste, citazioni, codice, link) con un convertitore markdown ->
|
||||||
|
|||||||
+2
-2
@@ -21,9 +21,9 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
|
|||||||
// AppConfigLocation = ~/.config/harbour/hermes (zona persistente della sandbox).
|
// AppConfigLocation = ~/.config/harbour/hermes (zona persistente della sandbox).
|
||||||
app->setOrganizationName(QStringLiteral("harbour"));
|
app->setOrganizationName(QStringLiteral("harbour"));
|
||||||
app->setApplicationName(QStringLiteral("hermes"));
|
app->setApplicationName(QStringLiteral("hermes"));
|
||||||
app->setApplicationVersion(QStringLiteral("0.5.2"));
|
app->setApplicationVersion(QStringLiteral("0.5.3"));
|
||||||
|
|
||||||
qDebug() << "harbour-hermes v0.5.2 build" << __DATE__ << __TIME__;
|
qDebug() << "harbour-hermes v0.5.3 build" << __DATE__ << __TIME__;
|
||||||
|
|
||||||
// Traduzioni: it/fr/de da /usr/share/harbour-hermes/translations, in base
|
// Traduzioni: it/fr/de da /usr/share/harbour-hermes/translations, in base
|
||||||
// alla lingua di sistema (l'inglese e' la lingua sorgente: nessun .qm).
|
// alla lingua di sistema (l'inglese e' la lingua sorgente: nessun .qm).
|
||||||
|
|||||||
+74
-12
@@ -1,5 +1,11 @@
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
// Nessuna configurazione personale e' compilata nel binario: i default sono
|
// Nessuna configurazione personale e' compilata nel binario: i default sono
|
||||||
// neutri e TUTTO si imposta dalle Impostazioni dell'app (QSettings in
|
// neutri e TUTTO si imposta dalle Impostazioni dell'app (QSettings in
|
||||||
// ~/.config/harbour/hermes.conf). Vuoto = "decide il server".
|
// ~/.config/harbour/hermes.conf). Vuoto = "decide il server".
|
||||||
@@ -7,9 +13,65 @@ static const char *kDefaultBaseUrl = "";
|
|||||||
static const char *kDefaultTtsVoice = "";
|
static const char *kDefaultTtsVoice = "";
|
||||||
static const char *kDefaultTtsEngine = "";
|
static const char *kDefaultTtsEngine = "";
|
||||||
|
|
||||||
|
QString Settings::configPath()
|
||||||
|
{
|
||||||
|
// Path canonico per la sandbox SailJail di Sailfish 5.x: con org/app
|
||||||
|
// dichiarati (harbour/hermes, vedi main.cpp e X-Sailjail nel desktop
|
||||||
|
// file) AppConfigLocation = ~/.config/harbour/hermes e' l'unica zona
|
||||||
|
// config che la sandbox rende PERSISTENTE tra gli avvii. NB: il
|
||||||
|
// QSettings "di default" scriverebbe in ~/.config/harbour/hermes.conf,
|
||||||
|
// FUORI da questa directory: dal launcher le impostazioni andrebbero
|
||||||
|
// perse a ogni riavvio (era il bug del "non salva le impostazioni").
|
||||||
|
const QString dir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
||||||
|
QDir().mkpath(dir);
|
||||||
|
return dir + QStringLiteral("/hermes.conf");
|
||||||
|
}
|
||||||
|
|
||||||
Settings::Settings(QObject *parent)
|
Settings::Settings(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
|
, m_settings(configPath(), QSettings::IniFormat)
|
||||||
{
|
{
|
||||||
|
migrateIfNeeded();
|
||||||
|
// Diagnostica visibile nel log del device: dove vivono le impostazioni.
|
||||||
|
qDebug() << "Settings file:" << m_settings.fileName();
|
||||||
|
if (m_settings.status() != QSettings::NoError)
|
||||||
|
qDebug() << "Settings WARNING: file di configurazione non accessibile";
|
||||||
|
qDebug() << "Settings read: baseUrl=[" << baseUrl() << "] voice=["
|
||||||
|
<< ttsVoice() << "] profile=[" << profile() << "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::migrateIfNeeded()
|
||||||
|
{
|
||||||
|
if (QFile::exists(m_settings.fileName()))
|
||||||
|
return; // c'e' gia' il file canonico: nessuna migrazione da fare
|
||||||
|
// Importa i valori rimasti nella vecchia posizione "di default"
|
||||||
|
// (~/.config/harbour/hermes.conf), ad esempio lasciati dagli avvii da
|
||||||
|
// terminale prima del fix del percorso. La prima sorgente con valori vince.
|
||||||
|
const QStringList candidates = QStringList()
|
||||||
|
<< QDir::homePath() + QStringLiteral("/.config/harbour/hermes.conf")
|
||||||
|
<< QStringLiteral("/home/defaultuser/.config/harbour/hermes.conf")
|
||||||
|
<< QStringLiteral("/home/nemo/.config/harbour/hermes.conf");
|
||||||
|
for (const QString &cand : candidates) {
|
||||||
|
if (cand == m_settings.fileName() || !QFile::exists(cand))
|
||||||
|
continue;
|
||||||
|
QSettings src(cand, QSettings::IniFormat);
|
||||||
|
const QStringList keys = src.allKeys();
|
||||||
|
if (keys.isEmpty())
|
||||||
|
continue;
|
||||||
|
for (const QString &k : keys)
|
||||||
|
m_settings.setValue(k, src.value(k));
|
||||||
|
m_settings.sync();
|
||||||
|
qDebug() << "Settings: importati" << keys.size() << "valori da" << cand;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::store(const QString &key, const QVariant &value)
|
||||||
|
{
|
||||||
|
m_settings.setValue(key, value);
|
||||||
|
// Flush immediato: se l'app viene terminata dal sistema, le modifiche
|
||||||
|
// alle impostazioni restano comunque su disco.
|
||||||
|
m_settings.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Settings::baseUrl() const
|
QString Settings::baseUrl() const
|
||||||
@@ -28,7 +90,7 @@ void Settings::setBaseUrl(const QString &url)
|
|||||||
v.chop(1);
|
v.chop(1);
|
||||||
if (v == baseUrl())
|
if (v == baseUrl())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("baseUrl"), v);
|
store(QStringLiteral("baseUrl"), v);
|
||||||
emit baseUrlChanged();
|
emit baseUrlChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +103,7 @@ void Settings::setTtsVoice(const QString &voice)
|
|||||||
{
|
{
|
||||||
if (voice == ttsVoice())
|
if (voice == ttsVoice())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("ttsVoice"), voice);
|
store(QStringLiteral("ttsVoice"), voice);
|
||||||
emit ttsVoiceChanged();
|
emit ttsVoiceChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +116,7 @@ void Settings::setTtsEngine(const QString &engine)
|
|||||||
{
|
{
|
||||||
if (engine == ttsEngine())
|
if (engine == ttsEngine())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("ttsEngine"), engine);
|
store(QStringLiteral("ttsEngine"), engine);
|
||||||
emit ttsEngineChanged();
|
emit ttsEngineChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +129,7 @@ void Settings::setTtsAutoRead(bool on)
|
|||||||
{
|
{
|
||||||
if (on == ttsAutoRead())
|
if (on == ttsAutoRead())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("ttsAutoRead"), on);
|
store(QStringLiteral("ttsAutoRead"), on);
|
||||||
emit ttsAutoReadChanged();
|
emit ttsAutoReadChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +142,7 @@ void Settings::setDictationAutoSend(bool on)
|
|||||||
{
|
{
|
||||||
if (on == dictationAutoSend())
|
if (on == dictationAutoSend())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("dictationAutoSend"), on);
|
store(QStringLiteral("dictationAutoSend"), on);
|
||||||
emit dictationAutoSendChanged();
|
emit dictationAutoSendChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +155,7 @@ void Settings::setVadSilenceMs(int ms)
|
|||||||
{
|
{
|
||||||
if (ms == vadSilenceMs())
|
if (ms == vadSilenceMs())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("vadSilenceMs"), ms);
|
store(QStringLiteral("vadSilenceMs"), ms);
|
||||||
emit vadSilenceMsChanged();
|
emit vadSilenceMsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +168,7 @@ void Settings::setVadThreshold(int threshold)
|
|||||||
{
|
{
|
||||||
if (threshold == vadThreshold())
|
if (threshold == vadThreshold())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("vadThreshold"), threshold);
|
store(QStringLiteral("vadThreshold"), threshold);
|
||||||
emit vadThresholdChanged();
|
emit vadThresholdChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +181,7 @@ void Settings::setVadMaxMs(int ms)
|
|||||||
{
|
{
|
||||||
if (ms == vadMaxMs())
|
if (ms == vadMaxMs())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("vadMaxMs"), ms);
|
store(QStringLiteral("vadMaxMs"), ms);
|
||||||
emit vadMaxMsChanged();
|
emit vadMaxMsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +197,7 @@ void Settings::setProfile(const QString &name)
|
|||||||
const QString clean = name.trimmed();
|
const QString clean = name.trimmed();
|
||||||
if (clean == profile())
|
if (clean == profile())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("profile"), clean);
|
store(QStringLiteral("profile"), clean);
|
||||||
emit profileChanged();
|
emit profileChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +216,7 @@ void Settings::setTtsVoices(const QStringList &list)
|
|||||||
}
|
}
|
||||||
if (clean == ttsVoices())
|
if (clean == ttsVoices())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("ttsVoices"), clean);
|
store(QStringLiteral("ttsVoices"), clean);
|
||||||
emit ttsVoicesChanged();
|
emit ttsVoicesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +235,7 @@ void Settings::setTtsEngines(const QStringList &list)
|
|||||||
}
|
}
|
||||||
if (clean == ttsEngines())
|
if (clean == ttsEngines())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("ttsEngines"), clean);
|
store(QStringLiteral("ttsEngines"), clean);
|
||||||
emit ttsEnginesChanged();
|
emit ttsEnginesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +248,6 @@ void Settings::setDefaultWorkspace(const QString &ws)
|
|||||||
{
|
{
|
||||||
if (ws == defaultWorkspace())
|
if (ws == defaultWorkspace())
|
||||||
return;
|
return;
|
||||||
m_settings.setValue(QStringLiteral("defaultWorkspace"), ws);
|
store(QStringLiteral("defaultWorkspace"), ws);
|
||||||
emit defaultWorkspaceChanged();
|
emit defaultWorkspaceChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
// Impostazioni persistenti dell'app.
|
// Impostazioni persistenti dell'app.
|
||||||
// QSettings con OrganizationName "harbour" / ApplicationName "hermes" (impostati
|
// QSettings con OrganizationName "harbour" / ApplicationName "hermes" (impostati
|
||||||
@@ -40,6 +41,10 @@ class Settings : public QObject
|
|||||||
public:
|
public:
|
||||||
explicit Settings(QObject *parent = nullptr);
|
explicit Settings(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
// Percorso del file impostazioni, nella zona persistente della sandbox
|
||||||
|
// SailJail (~/.config/harbour/hermes/hermes.conf). Pubblico per i test.
|
||||||
|
static QString configPath();
|
||||||
|
|
||||||
QString baseUrl() const;
|
QString baseUrl() const;
|
||||||
void setBaseUrl(const QString &url);
|
void setBaseUrl(const QString &url);
|
||||||
|
|
||||||
@@ -93,6 +98,9 @@ signals:
|
|||||||
void defaultWorkspaceChanged();
|
void defaultWorkspaceChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void migrateIfNeeded();
|
||||||
|
void store(const QString &key, const QVariant &value);
|
||||||
|
|
||||||
QSettings m_settings;
|
QSettings m_settings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -124,6 +124,28 @@ static void testMarkdown()
|
|||||||
"speech: righe vuote compattate");
|
"speech: righe vuote compattate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void testSettingsPersistence()
|
||||||
|
{
|
||||||
|
qDebug() << "== unit: persistenza impostazioni ==";
|
||||||
|
const QString cfg = Settings::configPath();
|
||||||
|
qDebug() << "file di test:" << cfg;
|
||||||
|
QFile::remove(cfg); // stato pulito: nessun valore precedente
|
||||||
|
{
|
||||||
|
Settings s1;
|
||||||
|
s1.setBaseUrl(QStringLiteral("https://esempio.test"));
|
||||||
|
s1.setTtsVoice(QStringLiteral("it-IT-ElsaNeural"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Una SECONDA istanza rilegge dallo stesso file: prova che i valori
|
||||||
|
// sono finiti su disco (non solo in memoria) -> persistenza vera.
|
||||||
|
Settings s2;
|
||||||
|
CHECK(s2.baseUrl() == QLatin1String("https://esempio.test"),
|
||||||
|
"baseUrl riletto da una nuova istanza (passa dal file)");
|
||||||
|
CHECK(s2.ttsVoice() == QLatin1String("it-IT-ElsaNeural"),
|
||||||
|
"voce riletta da una nuova istanza (passa dal file)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
QCoreApplication app(argc, argv);
|
QCoreApplication app(argc, argv);
|
||||||
@@ -132,6 +154,7 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
testParser();
|
testParser();
|
||||||
testMarkdown();
|
testMarkdown();
|
||||||
|
testSettingsPersistence();
|
||||||
|
|
||||||
if (!app.arguments().contains(QStringLiteral("--live"))) {
|
if (!app.arguments().contains(QStringLiteral("--live"))) {
|
||||||
qDebug() << (failures == 0 ? "UNIT: tutti i test passati" : "UNIT: FALLIMENTI");
|
qDebug() << (failures == 0 ? "UNIT: tutti i test passati" : "UNIT: FALLIMENTI");
|
||||||
|
|||||||
Reference in New Issue
Block a user