From fbe746a776025fcce764795d9cf42345549de82a Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:58:59 +0200 Subject: [PATCH] Cap startup operator problems at the latest 200 Fixes #409 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- apps/figcraft-server/src/store.rs | 17 ++++---- .../tests/problem_retention.rs | 40 +++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 apps/figcraft-server/tests/problem_retention.rs diff --git a/apps/figcraft-server/src/store.rs b/apps/figcraft-server/src/store.rs index 8dfbb708..409e8613 100644 --- a/apps/figcraft-server/src/store.rs +++ b/apps/figcraft-server/src/store.rs @@ -347,14 +347,14 @@ impl Store { let key = load_key(root)?; let dir = root.join(FILES); fs::create_dir_all(&dir).map_err(|e| format!("can't create {}: {e}", dir.display()))?; - let (directory, problems) = Directory::open(root, cfg.directory); + let (directory, startup_problems) = Directory::open(root, cfg.directory); let abuse = Abuse::new(cfg.hosting.abuse); let mut store = Store { root: root.to_path_buf(), cfg, key, files: BTreeMap::new(), - problems, + problems: Vec::new(), directory, abuse, served: figcraft_collab_net::net::Bucket::new(LIBRARY_RATE, LIBRARY_BURST), @@ -368,6 +368,9 @@ impl Store { free: None, _lock: lock, }; + for problem in startup_problems { + store.note(problem); + } store.scan(&dir); store.blocked = store.read_list::(BLOCKED).iter().filter_map(|s| s.parse().ok()).map(ip_key).collect(); store.reports = store.read_list(REPORTS); @@ -385,7 +388,7 @@ impl Store { match disk::read_capped(&path, 16 << 20).and_then(|b| serde_json::from_slice(&b).map_err(|e| format!("{name} is damaged ({e})"))) { Ok(list) => list, Err(e) => { - self.problems.push(e); + self.note(e); Vec::new() } } @@ -394,7 +397,7 @@ impl Store { fn scan(&mut self, dir: &Path) { let entries = match fs::read_dir(dir) { Ok(e) => e, - Err(e) => return self.problems.push(format!("can't list {}: {e}", dir.display())), + Err(e) => return self.note(format!("can't list {}: {e}", dir.display())), }; let mut ids: Vec<(String, PathBuf)> = entries.flatten().map(|e| (e.file_name().to_string_lossy().into_owned(), e.path())).collect(); ids.sort(); @@ -403,18 +406,18 @@ impl Store { continue; } if !valid_id(&id) { - self.problems.push(format!("skipped {}: not a file folder of this server", path.display())); + self.note(format!("skipped {}: not a file folder of this server", path.display())); continue; } if self.files.len() >= self.cfg.max_files { - self.problems.push(format!("skipped file {id}: over the {}-file cap", self.cfg.max_files)); + self.note(format!("skipped file {id}: over the {}-file cap", self.cfg.max_files)); continue; } match self.check(&path) { Ok(entry) => { self.files.insert(id, entry); } - Err(why) => self.problems.push(format!("skipped file {id}: {why}")), + Err(why) => self.note(format!("skipped file {id}: {why}")), } } } diff --git a/apps/figcraft-server/tests/problem_retention.rs b/apps/figcraft-server/tests/problem_retention.rs new file mode 100644 index 00000000..221eaa6e --- /dev/null +++ b/apps/figcraft-server/tests/problem_retention.rs @@ -0,0 +1,40 @@ +use figcraft_server::config::Config; +use figcraft_server::store::{FILES, Store}; + +#[test] +fn startup_problem_retention() { + let root = std::env::temp_dir().join(format!("figcraft-problem-cap-{}", std::process::id())); + assert!(!root.exists()); + let result = (|| -> Result<(), String> { + for count in [3u32, 200, 201] { + let data = root.join(count.to_string()); + for i in 0..count { + std::fs::create_dir_all(data.join(FILES).join(format!("{i:016x}"))).map_err(|e| e.to_string())?; + } + let cfg = Config { open_publish: true, data: data.clone(), ..Config::default() }; + cfg.check()?; + let store = Store::open(&data, cfg.store())?; + let expected = (count as usize).min(200); + let problems = store.problems().to_vec(); + let no_loaded_files = store.is_empty(); + drop(store); + if !problems.iter().all(|p| p.contains("meta.json")) || !no_loaded_files { + return Err("unexpected diagnostic or loaded file".into()); + } + if problems.len() != expected { + return Err(format!("expected {expected} retained problems, got {}", problems.len())); + } + // The latest problems stay, in scan order: the first folder's is the one dropped. + let first_kept = count as usize - expected; + for (n, p) in problems.iter().enumerate() { + let id = format!("{:016x}", first_kept + n); + if !p.contains(&id) { + return Err(format!("problem {n} should be about {id}: {p}")); + } + } + } + Ok(()) + })(); + std::fs::remove_dir_all(&root).unwrap(); + assert!(result.is_ok(), "{}", result.unwrap_err()); +}