From 17a7dbb1ec07ab7fbc5e29b2006d07d11f7cc400 Mon Sep 17 00:00:00 2001 From: oiupoyt Date: Thu, 1 Oct 2026 22:42:22 +0530 Subject: [PATCH] feat(bootstrap): implement disk cache and version cleaner --- crates/molotov-bootstrap/src/cleaner.rs | 74 +++++++++++++++++++ crates/molotov-bootstrap/src/lib.rs | 1 + .../molotov-bootstrap/tests/cleaner_test.rs | 35 +++++++++ 3 files changed, 110 insertions(+) create mode 100644 crates/molotov-bootstrap/src/cleaner.rs create mode 100644 crates/molotov-bootstrap/tests/cleaner_test.rs diff --git a/crates/molotov-bootstrap/src/cleaner.rs b/crates/molotov-bootstrap/src/cleaner.rs new file mode 100644 index 0000000..f3d4c65 --- /dev/null +++ b/crates/molotov-bootstrap/src/cleaner.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use crate::error::MolotovError; +use std::path::PathBuf; + +#[derive(Debug, Clone, Default)] +pub struct CleanReport { + pub purged_versions: Vec, + pub purged_parts_count: usize, + pub reclaimed_bytes: u64, +} + +pub struct Cleaner { + versions_root: PathBuf, + downloads_root: PathBuf, +} + +impl Cleaner { + pub fn new(versions_root: PathBuf, downloads_root: PathBuf) -> Self { + Self { + versions_root, + downloads_root, + } + } + + pub fn clean_stale_data(&self, active_version_guid: &str) -> Result { + let mut report = CleanReport::default(); + + // 1. Scan versions directory + if self.versions_root.exists() { + for entry in std::fs::read_dir(&self.versions_root)? { + let entry = entry?; + let file_name = entry.file_name().to_string_lossy().to_string(); + if entry.file_type()?.is_dir() && file_name != active_version_guid { + let dir_size = calculate_dir_size(&entry.path()); + std::fs::remove_dir_all(entry.path())?; + report.purged_versions.push(file_name); + report.reclaimed_bytes += dir_size; + } + } + } + + // 2. Scan downloads directory for leftover .part files + if self.downloads_root.exists() { + for entry in std::fs::read_dir(&self.downloads_root)? { + let entry = entry?; + let path = entry.path(); + if path.extension().and_then(|e| e.to_str()) == Some("part") { + let file_size = entry.metadata()?.len(); + std::fs::remove_file(path)?; + report.purged_parts_count += 1; + report.reclaimed_bytes += file_size; + } + } + } + + Ok(report) + } +} + +fn calculate_dir_size(path: &std::path::Path) -> u64 { + let mut total = 0; + if let Ok(entries) = std::fs::read_dir(path) { + for entry in entries.flatten() { + if let Ok(meta) = entry.metadata() { + if meta.is_dir() { + total += calculate_dir_size(&entry.path()); + } else { + total += meta.len(); + } + } + } + } + total +} diff --git a/crates/molotov-bootstrap/src/lib.rs b/crates/molotov-bootstrap/src/lib.rs index 3e8f1ba..4c4f196 100644 --- a/crates/molotov-bootstrap/src/lib.rs +++ b/crates/molotov-bootstrap/src/lib.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pub mod apk; pub mod cdn; +pub mod cleaner; pub mod error; pub mod fflags; pub mod mods; diff --git a/crates/molotov-bootstrap/tests/cleaner_test.rs b/crates/molotov-bootstrap/tests/cleaner_test.rs new file mode 100644 index 0000000..e43c586 --- /dev/null +++ b/crates/molotov-bootstrap/tests/cleaner_test.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use molotov_bootstrap::cleaner::Cleaner; +use tempfile::tempdir; + +#[test] +fn test_cleaner_purges_stale_versions_and_part_files() { + let temp = tempdir().unwrap(); + let versions_dir = temp.path().join("versions"); + let downloads_dir = temp.path().join("downloads"); + std::fs::create_dir_all(&versions_dir).unwrap(); + std::fs::create_dir_all(&downloads_dir).unwrap(); + + // Create current version and stale version + let current_ver = versions_dir.join("version-current"); + let stale_ver = versions_dir.join("version-stale-1"); + std::fs::create_dir_all(¤t_ver).unwrap(); + std::fs::create_dir_all(&stale_ver).unwrap(); + std::fs::write(current_ver.join("libroblox.so"), b"ACTIVE_LIB").unwrap(); + std::fs::write(stale_ver.join("libroblox.so"), b"STALE_LIB").unwrap(); + + // Create leftover .part file + let part_file = downloads_dir.join("download.apk.part"); + std::fs::write(&part_file, b"DANGLING_PART").unwrap(); + + let cleaner = Cleaner::new(versions_dir, downloads_dir); + let report = cleaner.clean_stale_data("version-current").unwrap(); + + assert_eq!(report.purged_versions.len(), 1); + assert_eq!(report.purged_versions[0], "version-stale-1"); + assert_eq!(report.purged_parts_count, 1); + assert!(report.reclaimed_bytes > 0); + assert!(!stale_ver.exists(), "Stale version must be deleted"); + assert!(!part_file.exists(), "Dangling .part file must be deleted"); + assert!(current_ver.exists(), "Active version must NOT be deleted"); +}