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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
pub mod apk;
|
pub mod apk;
|
||||||
pub mod cdn;
|
pub mod cdn;
|
||||||
|
pub mod cleaner;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod fflags;
|
pub mod fflags;
|
||||||
pub mod mods;
|
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