Add bread-utils and bread-onnx: shared crates for ecosystem-wide duplication
bread-utils extracts genuinely duplicated logic found across breadbox, breadclip, breadmon, breadcrumbs, bos-settings, and breadhelp: - hypr: Hyprland socket1 request/response client (breadbox's get_active_workspace + breadclip's position.rs hyprctl_json were near-identical), socket2 path resolution (breadmon), and a version-tolerant `fullscreen` field parser (Hyprland has shipped both bool and int representations across versions). - singleton: correct flock-based single-instance toggle, replacing the TOCTOU-prone read-pid/check-proc/kill/write-pid pattern duplicated verbatim between breadbox and breadclip (breadclip's own comment says "matches breadbox pattern"). - proc: breadcrumbs' timeout-guarded subprocess runner, promoted verbatim as the one implementation in the ecosystem that already got this right. - atomic + xdg: atomic (temp-then-rename) file writes with an optional .bak-before-overwrite variant, and XDG path helpers that never fall back to a literal "~/..." string (the exact breadclip-core and breadpad-shared bug: PathBuf never expands `~`). - tomlcfg (feature "toml"): the load_doc/save_doc TOML-editing discipline bos-settings and breadhelp both implemented byte-for-byte identically in the same fix pass that introduced it. - gtk_popup (feature "gtk"): layer-shell overlay window setup, visible-row navigation, and click-outside-close, deduplicated from breadbox and breadclip (~150 duplicated lines, per both apps' own "same as breadbox" comments). bread-onnx extracts the embedding pipeline (tokenize -> tensor build -> mean-pool -> L2-normalize) duplicated near-verbatim between breadarr and breadsearch, a shared execution-provider session builder with loud EP- registration logging, and a model download+integrity helper. Defaults AMD iGPU acceleration to ort::ep::MIGraphX (not ROCm) per this machine's own breadsearch-gpu-backends lesson: ROCMExecutionProvider silently no-ops to CPU on distro ROCm onnxruntime builds compiled with --use_migraphx. Both crates build and pass their own test suites standalone. Consumer migrations follow in subsequent commits.
This commit is contained in:
parent
394a252f9e
commit
853ee33415
17 changed files with 2503 additions and 5 deletions
95
bread-utils/src/xdg.rs
Normal file
95
bread-utils/src/xdg.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//! XDG base directory helpers.
|
||||
//!
|
||||
//! Several repos independently rolled `dirs::data_local_dir().unwrap_or_else(||
|
||||
//! PathBuf::from("~/.local/share"))`-shaped fallbacks. The literal-tilde
|
||||
//! string is the bug: `PathBuf`/`std::fs` never expand `~`, so on the rare
|
||||
//! box where `dirs` can't resolve a home directory (no `HOME` env var, e.g.
|
||||
//! some container/systemd-service contexts) the fallback silently resolves
|
||||
//! to a directory literally named `~` in the process's current working
|
||||
//! directory instead of the user's actual home. Confirmed present in:
|
||||
//! - `breadclip-core/src/lib.rs:171-175` (`data_dir`)
|
||||
//! - `breadpad-shared/src/classifier.rs:34-39` (`model_dir`)
|
||||
//! - `breadpad-shared/src/config.rs:214-219` and `:221-226`
|
||||
//! (`config_path`, `style_css_path`)
|
||||
//!
|
||||
//! The helpers here resolve a real `$HOME` (via `dirs::home_dir()`, which
|
||||
//! itself falls back to reading `HOME` directly) before ever falling back,
|
||||
//! so the fallback path is always an absolute, expanded path.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn home_or_root() -> PathBuf {
|
||||
dirs::home_dir().unwrap_or_else(|| PathBuf::from("/root"))
|
||||
}
|
||||
|
||||
/// `$XDG_CONFIG_HOME` (only if it's set to an absolute path) or `~/.config`,
|
||||
/// joined with `app`.
|
||||
pub fn config_dir(app: &str) -> PathBuf {
|
||||
base_config_dir().join(app)
|
||||
}
|
||||
|
||||
/// `$XDG_DATA_HOME` (only if absolute) or `~/.local/share`, joined with `app`.
|
||||
pub fn data_dir(app: &str) -> PathBuf {
|
||||
dirs::data_local_dir()
|
||||
.unwrap_or_else(|| home_or_root().join(".local/share"))
|
||||
.join(app)
|
||||
}
|
||||
|
||||
/// `$XDG_CACHE_HOME` (only if absolute) or `~/.cache`, joined with `app`.
|
||||
pub fn cache_dir(app: &str) -> PathBuf {
|
||||
dirs::cache_dir()
|
||||
.unwrap_or_else(|| home_or_root().join(".cache"))
|
||||
.join(app)
|
||||
}
|
||||
|
||||
/// `$XDG_RUNTIME_DIR`, falling back to `/tmp` — matches the fallback every
|
||||
/// consumer (breadbox, breadclip, breadmon) already used for PID/socket
|
||||
/// scratch files, which don't need to survive a reboot.
|
||||
pub fn runtime_dir() -> PathBuf {
|
||||
std::env::var_os("XDG_RUNTIME_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("/tmp"))
|
||||
}
|
||||
|
||||
fn base_config_dir() -> PathBuf {
|
||||
if let Some(xdg) = std::env::var_os("XDG_CONFIG_HOME") {
|
||||
let p = PathBuf::from(xdg);
|
||||
if p.is_absolute() {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
dirs::config_dir().unwrap_or_else(|| home_or_root().join(".config"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn config_dir_joins_app_name() {
|
||||
let d = config_dir("breadpad");
|
||||
assert!(d.ends_with("breadpad"));
|
||||
assert!(d.is_absolute());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_dir_never_contains_literal_tilde() {
|
||||
// Regression guard for the exact bug this module replaces: the
|
||||
// fallback must never be a literal "~/..." path component.
|
||||
let d = data_dir("breadclip");
|
||||
assert!(!d.components().any(|c| c.as_os_str() == "~"));
|
||||
assert!(d.is_absolute());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_dir_is_absolute() {
|
||||
assert!(cache_dir("breadsearch").is_absolute());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_dir_falls_back_to_tmp() {
|
||||
// We don't unset XDG_RUNTIME_DIR here (test isolation), just confirm
|
||||
// the function returns *something* absolute either way.
|
||||
assert!(runtime_dir().is_absolute());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue