refactor(qconnect): move device_uuid persistence to qbz-app (slice 1)

Relocate the persistent QConnect device-identity resolution out of the
Tauri adapter into the frontend-agnostic qbz-app crate
(qbz-app/src/qconnect_identity.rs) so both the Tauri and a future Slint
frontend resolve the SAME uuid for the same install.

Moved: resolve_qconnect_device_uuid, device_uuid_from_db,
load/persist_device_uuid, qconnect_settings_db_path.

Byte-identical persistence contract preserved: DB path
(<data_dir>/qbz/qconnect_settings.db), the 'device_uuid' key, and the
QBZ_QCONNECT_DEVICE_UUID env override are unchanged, so existing installs
keep their uuid. The Tauri transport.rs::resolve_qconnect_device_uuid is
now a one-line delegate; the device-NAME persistence shares the same DB
file via the qbz-app path helper.

qbz-app gains a uuid dependency (it already had rusqlite + dirs, so no
SQLite dep was added to qconnect-app per R6). The 2 device_uuid tests
move to qbz-app.

cc
This commit is contained in:
vicrodh committed 2026-06-01 09:03:13 -06:00
1 parent f100cf4d75
commit f907d9d984
7 files changed
+141 -97

No files matched your search

+4 -26
View File
@@ -1062,32 +1062,10 @@ fn resolves_remote_previous_to_prior_item_even_mid_track() {
);
}
#[test]
fn device_uuid_env_override_takes_precedence() {
// SAFETY: single-threaded test process for this var; restore after.
std::env::set_var("QBZ_QCONNECT_DEVICE_UUID", "env-override-uuid-123");
let uuid = super::transport::resolve_qconnect_device_uuid();
std::env::remove_var("QBZ_QCONNECT_DEVICE_UUID");
assert_eq!(uuid, "env-override-uuid-123");
}
#[test]
fn device_uuid_persists_and_is_reused_across_calls() {
let tmp = std::env::temp_dir().join(format!(
"qbz_qconnect_uuid_test_{}.db",
std::process::id()
));
let _ = std::fs::remove_file(&tmp);
// First call generates and persists.
let first = super::transport::device_uuid_from_db(&tmp);
assert!(!first.trim().is_empty(), "generated uuid must be non-empty");
// Second call must return the SAME value (read back from disk, not a fresh v4).
let second = super::transport::device_uuid_from_db(&tmp);
assert_eq!(first, second, "device_uuid must be stable across calls");
let _ = std::fs::remove_file(&tmp);
}
// The persistent device-uuid env override + persistence round-trip now live in
// `qbz_app::qconnect_identity`; their unit tests live in that crate's test
// module. The Tauri `transport::resolve_qconnect_device_uuid` is a one-line
// delegate.
// `RendererStatus::from_wire` and `should_arm_renderer_watchdog` are now in
// `qconnect_app::session`; their unit tests live in that crate's `session` test
+9 -71
View File
@@ -6,7 +6,6 @@
use qconnect_transport_ws::WsTransportConfig;
use serde_json::Value;
use uuid::Uuid;
use crate::AppState;
@@ -78,73 +77,12 @@ pub(super) fn default_qconnect_device_info_with_name(
}
}
/// Persistent QConnect device identity moved to the frontend-agnostic
/// `qbz_app::qconnect_identity` module so both the Tauri and Slint frontends
/// resolve the SAME uuid for the same install (byte-identical DB path/key/env
/// override). This is a one-line delegate.
pub(super) fn resolve_qconnect_device_uuid() -> String {
if let Some(explicit) = std::env::var("QBZ_QCONNECT_DEVICE_UUID")
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
{
return explicit;
}
match qconnect_settings_db_path() {
Some(path) => device_uuid_from_db(&path),
// Fail-open: if we cannot resolve a settings path, fall back to a
// fresh v4 (same behavior as before persistence existed). This keeps
// QConnect functional on exotic systems without a data dir.
None => Uuid::new_v4().to_string(),
}
}
/// Load the persisted device_uuid from `path`, generating + persisting one on
/// first run. Mirrors `load_persisted_device_name`/`persist_device_name`. Split
/// out so the persistence round-trip is unit-testable against a temp path.
pub(super) fn device_uuid_from_db(path: &std::path::Path) -> String {
if let Some(existing) = load_persisted_device_uuid(path) {
return existing;
}
let generated = Uuid::new_v4().to_string();
persist_device_uuid(path, &generated);
generated
}
/// Load the persisted device_uuid. Returns None if not set or on any error.
fn load_persisted_device_uuid(path: &std::path::Path) -> Option<String> {
let conn = rusqlite::Connection::open(path).ok()?;
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;")
.ok()?;
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
)",
)
.ok()?;
conn.query_row(
"SELECT value FROM settings WHERE key = 'device_uuid'",
[],
|row| row.get::<_, String>(0),
)
.ok()
.filter(|v| !v.trim().is_empty())
}
/// Persist the device_uuid to disk (INSERT OR REPLACE).
fn persist_device_uuid(path: &std::path::Path, uuid: &str) {
let Ok(conn) = rusqlite::Connection::open(path) else {
return;
};
let _ = conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;");
let _ = conn.execute_batch(
"CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
)",
);
let _ = conn.execute(
"INSERT OR REPLACE INTO settings (key, value) VALUES ('device_uuid', ?1)",
rusqlite::params![uuid],
);
qbz_app::qconnect_identity::resolve_qconnect_device_uuid()
}
pub(super) fn resolve_system_hostname() -> String {
@@ -171,11 +109,11 @@ pub(super) fn resolve_default_qconnect_device_name() -> String {
format!("Qbz - {hostname}")
}
/// Path to the QConnect settings database (global, not per-user).
/// Path to the QConnect settings database (global, not per-user). Delegates to
/// `qbz_app::qconnect_identity` so the device-name persistence here shares the
/// exact same DB file as the relocated device-uuid persistence.
fn qconnect_settings_db_path() -> Option<std::path::PathBuf> {
let data_dir = dirs::data_dir()?.join("qbz");
std::fs::create_dir_all(&data_dir).ok()?;
Some(data_dir.join("qconnect_settings.db"))
qbz_app::qconnect_identity::qconnect_settings_db_path()
}
/// Load the persisted custom device name from disk.