feat(bootstrap): implement disk cache and version cleaner

This commit is contained in:
katR 2026-10-01 22:42:22 +05:30
parent c64c9635fa
commit 17a7dbb1ec
3 changed files with 110 additions and 0 deletions

View file

@ -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<String>,
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<CleanReport, MolotovError> {
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
}

View file

@ -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;

View file

@ -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(&current_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");
}