feat(bootstrap): implement disk cache and version cleaner
This commit is contained in:
parent
c64c9635fa
commit
17a7dbb1ec
3 changed files with 110 additions and 0 deletions
74
crates/molotov-bootstrap/src/cleaner.rs
Normal file
74
crates/molotov-bootstrap/src/cleaner.rs
Normal 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
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
35
crates/molotov-bootstrap/tests/cleaner_test.rs
Normal file
35
crates/molotov-bootstrap/tests/cleaner_test.rs
Normal 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(¤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");
|
||||
}
|
||||
Reference in a new issue