diff --git a/CHANGELOG.md b/CHANGELOG.md index fee2fdb..0191bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This section tracks work landing on `dev` toward **v0.2.0** (next minor; pre-1.0 - **`make:bridge:read-model ` maker.** Generates a query-only projection: `src/ReadModel/ReadModel.php` (query service stub injecting `EntityManagerInterface`), `src/Controller/Controller.php` (single GET handler at `/api/`), and `{qml_path}/List.qml` (`ReactiveListModel` bound to the route, deliberately *no* Mercure topic — read-models aren't auto-reactive; invalidation is event-driven via `make:bridge:event`). Closes the fourth row of PLAN.md §8's makers table. - **Pre-migration auto-backup of `var/data.sqlite`.** Bundled-mode supervisor copies the SQLite file to `var/data.sqlite..bak` before invoking `doctrine:migrations:migrate`; trims to the 5 most recent. SQLite's lack of transactional DDL means a half-applied migration can corrupt the database with no rollback path; cheap insurance against that. Skipped on first launch (no DB to back up); failure to copy logs a warning and continues (a missing safety-net is not a reason to refuse to boot). Backup runs only in bundled mode — dev mode users own their `var/data.sqlite` lifecycle. Bundled-supervisor integration test gained an assertion that a `.bak` file appears under the user data dir on second launch. - **`bridge:export` console command + QML hook.** New `bin/console bridge:export ` copies the active SQLite database to a user-chosen path (overwrites if the destination exists; reads the source path from `DATABASE_URL` so it works in both dev and bundled mode). Mirrored on the QML side as `BackendConnection.exportDatabase(path)` (`Q_INVOKABLE bool`) returning success synchronously and emitting `databaseExported(path)` / `databaseExportFailed(reason)` for async UX. QML callers typically pair it with `Qt.labs.platform.FileDialog` (see `docs/native-dialogs.md`). 4 unit tests cover the command's success / non-SQLite-URL / missing-source / overwrite paths. +- **Periodic auto-update check.** Bundled-mode supervisor arms an `AppImageUpdate` poll on the first `Online` transition: a launch-time check 10 s after backend ready, then a recurring check every 6 hours. PLAN.md §11 *Auto-update* called for "check on launch and once per N hours; offer install on next restart, never auto-restart" — the existing `checkForUpdates()` Q_INVOKABLE remains the install trigger, this just automates the polling. Disable with `BRIDGE_AUTO_UPDATE_DISABLE=1`; override the period with `BRIDGE_AUTO_UPDATE_PERIOD_MIN=`. Dev mode skips entirely. ### Changed diff --git a/framework/qml/src/BackendConnection.cpp b/framework/qml/src/BackendConnection.cpp index 8af03d1..092790a 100644 --- a/framework/qml/src/BackendConnection.cpp +++ b/framework/qml/src/BackendConnection.cpp @@ -615,6 +615,45 @@ void BackendConnection::setState(ConnectionState s) if (m_state == s) return; m_state = s; emit connectionStateChanged(); + if (s == ConnectionState::Online) { + armAutoUpdateOnFirstOnline(); + } +} + +void BackendConnection::armAutoUpdateOnFirstOnline() +{ + if (m_autoUpdateArmed) return; + if (m_mode != Mode::Bundled) return; + if (qEnvironmentVariableIsSet("BRIDGE_AUTO_UPDATE_DISABLE") + && qgetenv("BRIDGE_AUTO_UPDATE_DISABLE") == "1") { + qCInfo(lcBundled) << "auto-update disabled via BRIDGE_AUTO_UPDATE_DISABLE"; + m_autoUpdateArmed = true; + return; + } + m_autoUpdateArmed = true; + + int periodMs = kAutoUpdateDefaultPeriodMs; + bool ok = false; + const int overrideMin = qgetenv("BRIDGE_AUTO_UPDATE_PERIOD_MIN").toInt(&ok); + if (ok && overrideMin > 0) { + periodMs = overrideMin * 60 * 1000; + } + + // First check: a few seconds after first Online so a fresh launch + // doesn't fight for bandwidth/CPU with the cold boot. Subsequent + // checks: every periodMs (default 6h). + QTimer::singleShot(kAutoUpdateLaunchDelayMs, this, &BackendConnection::checkForUpdates); + + if (!m_autoUpdateTimer) { + m_autoUpdateTimer = new QTimer(this); + m_autoUpdateTimer->setSingleShot(false); + connect(m_autoUpdateTimer, &QTimer::timeout, + this, &BackendConnection::checkForUpdates); + } + m_autoUpdateTimer->start(periodMs); + qCInfo(lcBundled) << "auto-update armed: launch check in" + << kAutoUpdateLaunchDelayMs << "ms, period" + << (periodMs / 60000) << "min"; } void BackendConnection::setError(const QString& msg) diff --git a/framework/qml/src/BackendConnection.h b/framework/qml/src/BackendConnection.h index 2521f8c..6ddbab5 100644 --- a/framework/qml/src/BackendConnection.h +++ b/framework/qml/src/BackendConnection.h @@ -175,6 +175,17 @@ private: QProcess* m_updateCheck = nullptr; QProcess* m_updateApply = nullptr; + /// Periodic AppImageUpdate poll. Armed on first Online transition + /// in bundled mode; PLAN.md §11 *Auto-update*. Disabled by env + /// `BRIDGE_AUTO_UPDATE_DISABLE=1`; period override via + /// `BRIDGE_AUTO_UPDATE_PERIOD_MIN=`. + QTimer* m_autoUpdateTimer = nullptr; + bool m_autoUpdateArmed = false; + static constexpr int kAutoUpdateLaunchDelayMs = 10'000; // 10s after first Online + static constexpr int kAutoUpdateDefaultPeriodMs = 6 * 60 * 60 * 1000; // 6h + + void armAutoUpdateOnFirstOnline(); + QString resolveSidecarUpdater() const; QString currentAppImagePath() const; };