From f3a5839cf150e69c571886a4c3842b83a694662b Mon Sep 17 00:00:00 2001 From: Breadway Date: Fri, 17 Jul 2026 09:30:36 +0800 Subject: [PATCH] Migrate config load/save/atomic_write to bread_utils::tomlcfg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit load_doc/save_doc/atomic_write's bodies now delegate to bread_utils::tomlcfg and bread_utils::atomic (path dependency for now, see the TODO in Cargo.toml) instead of owning the temp-then-rename + .bak-before-overwrite logic locally. Public function names/signatures in config/mod.rs are unchanged, so none of the ~10 call sites across ui/views/*.rs needed touching. This is the other half of tonight's earlier BOS fix pass: that pass gave breadhelp/src/config.rs its own byte-for-byte copy of this exact logic (its own doc comment says "same discipline as bos-settings/src/config/ mod.rs::atomic_write") rather than sharing it — breadhelp's migration follows in the next commit. Builds clean; all 11 existing tests pass, including the atomic-write backup/no-leftover-tmp-file test that now exercises the delegated code. --- Cargo.lock | 11 +++++++++ Cargo.toml | 2 ++ src/config/mod.rs | 57 +++++++---------------------------------------- 3 files changed, 21 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0ecbab..d236a5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,7 @@ version = "0.6.3" dependencies = [ "async-channel", "bread-theme", + "bread-utils", "glib", "gtk4", "serde", @@ -51,6 +52,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "bread-utils" +version = "0.2.3" +dependencies = [ + "dirs", + "serde", + "serde_json", + "toml_edit 0.22.27", +] + [[package]] name = "cairo-rs" version = "0.22.0" diff --git a/Cargo.toml b/Cargo.toml index e89c231..556befa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,5 @@ toml = "0.8" # drops the rest of the user's config file. toml_edit = "0.22" async-channel = "2" +# TODO(owner): switch to tag-pinned git dependency once bread-utils is merged and tagged, matching the bread-theme pattern +bread-utils = { path = "../bread-ecosystem-fix-worktree/bread-utils", features = ["toml"] } diff --git a/src/config/mod.rs b/src/config/mod.rs index e87a7c8..c1e4034 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -21,27 +21,12 @@ use toml_edit::{value, Array, DocumentMut, Item, Table, Value}; /// breadcrumbs' saved network passwords, ...). Back up the unparseable file /// once before falling back, so a bad edit is always recoverable. pub fn load_doc(path: &Path) -> DocumentMut { - let Ok(text) = std::fs::read_to_string(path) else { - return DocumentMut::default(); - }; - match text.parse::() { - Ok(doc) => doc, - Err(e) => { - let backup = PathBuf::from(format!("{}.bak", path.display())); - eprintln!( - "bos-settings: {} failed to parse ({e}); backed up to {} before falling back to defaults", - path.display(), - backup.display() - ); - let _ = std::fs::write(&backup, &text); - DocumentMut::default() - } - } + bread_utils::tomlcfg::load_doc("bos-settings", path) } /// Write the document back to disk, creating parent dirs as needed. pub fn save_doc(path: &Path, doc: &DocumentMut) -> Result<(), Box> { - atomic_write(path, &doc.to_string())?; + bread_utils::tomlcfg::save_doc(path, doc)?; Ok(()) } @@ -50,42 +35,16 @@ pub fn save_doc(path: &Path, doc: &DocumentMut) -> Result<(), Box> { /// /// Every config-writing view in this app (TOML via `save_doc` above, and the /// plain-JSON views — keybinds, autostart, appearance/settings.json, -/// monitors.json, breadbar's CSS) should go through this instead of a bare -/// `std::fs::write`: writing straight to the target path means a crash, -/// power loss, or disk-full error mid-write can leave the file truncated or -/// corrupted with no way back. Writing to a temp file in the *same* -/// directory first, then `rename`-ing it over the target, avoids that — a -/// rename within one filesystem is atomic, so the target either has the old -/// complete contents or the new complete contents, never a partial write. -/// Backing up the previous file first (best-effort — the write can still -/// proceed if the backup fails, e.g. read-only source) means even a -/// successful-but-wrong write is always recoverable from `.bak`. +/// monitors.json, breadbar's CSS) goes through this instead of a bare +/// `std::fs::write` — see `bread_utils::atomic::write_atomic_backed_up`'s +/// doc comment for why (crash/power-loss safety via temp-then-rename, plus +/// a `.bak` of whatever was there before). pub fn atomic_write(path: &Path, contents: &str) -> std::io::Result<()> { - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent)?; - } - if path.exists() { - let backup = PathBuf::from(format!("{}.bak", path.display())); - let _ = std::fs::copy(path, &backup); - } - let dir = path.parent().map(Path::to_path_buf).unwrap_or_else(|| PathBuf::from(".")); - let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("config"); - let tmp_path = dir.join(format!(".{file_name}.tmp.{}", std::process::id())); - std::fs::write(&tmp_path, contents)?; - std::fs::rename(&tmp_path, path)?; - Ok(()) + bread_utils::atomic::write_atomic_backed_up(path, contents) } pub fn config_dir() -> PathBuf { - // Honour XDG_CONFIG_HOME if set; otherwise fall back to $HOME/.config. - if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { - let p = PathBuf::from(xdg); - if p.is_absolute() { - return p; - } - } - let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string()); - PathBuf::from(home).join(".config") + bread_utils::xdg::config_home() } // --- typed readers (walk a dotted path, return None if absent/wrong type) ---