Migrate config load/save/atomic_write to bread_utils::tomlcfg
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.
This commit is contained in:
parent
da6af6abf9
commit
f3a5839cf1
3 changed files with 21 additions and 49 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
|
@ -32,6 +32,7 @@ version = "0.6.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
"bread-theme",
|
"bread-theme",
|
||||||
|
"bread-utils",
|
||||||
"glib",
|
"glib",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -51,6 +52,16 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bread-utils"
|
||||||
|
version = "0.2.3"
|
||||||
|
dependencies = [
|
||||||
|
"dirs",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"toml_edit 0.22.27",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cairo-rs"
|
name = "cairo-rs"
|
||||||
version = "0.22.0"
|
version = "0.22.0"
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,5 @@ toml = "0.8"
|
||||||
# drops the rest of the user's config file.
|
# drops the rest of the user's config file.
|
||||||
toml_edit = "0.22"
|
toml_edit = "0.22"
|
||||||
async-channel = "2"
|
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"] }
|
||||||
|
|
|
||||||
|
|
@ -21,27 +21,12 @@ use toml_edit::{value, Array, DocumentMut, Item, Table, Value};
|
||||||
/// breadcrumbs' saved network passwords, ...). Back up the unparseable file
|
/// breadcrumbs' saved network passwords, ...). Back up the unparseable file
|
||||||
/// once before falling back, so a bad edit is always recoverable.
|
/// once before falling back, so a bad edit is always recoverable.
|
||||||
pub fn load_doc(path: &Path) -> DocumentMut {
|
pub fn load_doc(path: &Path) -> DocumentMut {
|
||||||
let Ok(text) = std::fs::read_to_string(path) else {
|
bread_utils::tomlcfg::load_doc("bos-settings", path)
|
||||||
return DocumentMut::default();
|
|
||||||
};
|
|
||||||
match text.parse::<DocumentMut>() {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the document back to disk, creating parent dirs as needed.
|
/// Write the document back to disk, creating parent dirs as needed.
|
||||||
pub fn save_doc(path: &Path, doc: &DocumentMut) -> Result<(), Box<dyn Error>> {
|
pub fn save_doc(path: &Path, doc: &DocumentMut) -> Result<(), Box<dyn Error>> {
|
||||||
atomic_write(path, &doc.to_string())?;
|
bread_utils::tomlcfg::save_doc(path, doc)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,42 +35,16 @@ pub fn save_doc(path: &Path, doc: &DocumentMut) -> Result<(), Box<dyn Error>> {
|
||||||
///
|
///
|
||||||
/// Every config-writing view in this app (TOML via `save_doc` above, and the
|
/// Every config-writing view in this app (TOML via `save_doc` above, and the
|
||||||
/// plain-JSON views — keybinds, autostart, appearance/settings.json,
|
/// plain-JSON views — keybinds, autostart, appearance/settings.json,
|
||||||
/// monitors.json, breadbar's CSS) should go through this instead of a bare
|
/// monitors.json, breadbar's CSS) goes through this instead of a bare
|
||||||
/// `std::fs::write`: writing straight to the target path means a crash,
|
/// `std::fs::write` — see `bread_utils::atomic::write_atomic_backed_up`'s
|
||||||
/// power loss, or disk-full error mid-write can leave the file truncated or
|
/// doc comment for why (crash/power-loss safety via temp-then-rename, plus
|
||||||
/// corrupted with no way back. Writing to a temp file in the *same*
|
/// a `.bak` of whatever was there before).
|
||||||
/// 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 `<path>.bak`.
|
|
||||||
pub fn atomic_write(path: &Path, contents: &str) -> std::io::Result<()> {
|
pub fn atomic_write(path: &Path, contents: &str) -> std::io::Result<()> {
|
||||||
if let Some(parent) = path.parent() {
|
bread_utils::atomic::write_atomic_backed_up(path, contents)
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config_dir() -> PathBuf {
|
pub fn config_dir() -> PathBuf {
|
||||||
// Honour XDG_CONFIG_HOME if set; otherwise fall back to $HOME/.config.
|
bread_utils::xdg::config_home()
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- typed readers (walk a dotted path, return None if absent/wrong type) ---
|
// --- typed readers (walk a dotted path, return None if absent/wrong type) ---
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue