mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
refactor(cache): extract dir_size + prune_empty_dirs into fs_utils
Lift the duplicated `dir_size` walker and the empty-directory upward-prune loop out of cache/offline.rs and cache/hot.rs into a shared cache/fs_utils.rs module. Behaviour-preserving: same boundary semantics, same skip-on-error policy, same `remove_dir`-only pruning (never remove_dir_all). Net delta: -76 LOC across cache/, single source of truth for cache cleanup.
This commit is contained in:
+53
@@ -0,0 +1,53 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// Recursively sums the size of all files under `root`.
|
||||||
|
/// Missing roots, unreadable directories, and unreadable files are silently skipped.
|
||||||
|
pub(crate) fn dir_size_recursive(root: &Path) -> u64 {
|
||||||
|
if !root.exists() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let mut total: u64 = 0;
|
||||||
|
let mut stack = vec![root.to_path_buf()];
|
||||||
|
while let Some(dir) = stack.pop() {
|
||||||
|
let rd = match std::fs::read_dir(&dir) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
for entry in rd.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
stack.push(path);
|
||||||
|
} else if let Ok(meta) = std::fs::metadata(&path) {
|
||||||
|
total += meta.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walks upward from `start_dir`, removing each empty directory using `remove_dir`
|
||||||
|
/// (never `remove_dir_all`). Stops as soon as a non-empty directory is hit, the
|
||||||
|
/// boundary is reached, or removal fails.
|
||||||
|
///
|
||||||
|
/// `boundary` is never removed and is treated as a hard stop. If `start_dir` is
|
||||||
|
/// not under `boundary`, the function is a no-op.
|
||||||
|
pub(crate) fn prune_empty_dirs_up_to(start_dir: &Path, boundary: &Path) {
|
||||||
|
let mut current = Some(start_dir.to_path_buf());
|
||||||
|
while let Some(dir) = current {
|
||||||
|
if dir == boundary || !dir.starts_with(boundary) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match std::fs::read_dir(&dir) {
|
||||||
|
Ok(mut entries) => {
|
||||||
|
if entries.next().is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if std::fs::remove_dir(&dir).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current = dir.parent().map(|p| p.to_path_buf());
|
||||||
|
}
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-42
@@ -172,31 +172,8 @@ pub(crate) async fn promote_stream_cache_to_hot_cache(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) async fn get_hot_cache_size(custom_dir: Option<String>, app: tauri::AppHandle) -> u64 {
|
pub(crate) async fn get_hot_cache_size(custom_dir: Option<String>, app: tauri::AppHandle) -> u64 {
|
||||||
fn dir_size(root: std::path::PathBuf) -> u64 {
|
|
||||||
if !root.exists() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let mut total: u64 = 0;
|
|
||||||
let mut stack = vec![root];
|
|
||||||
while let Some(dir) = stack.pop() {
|
|
||||||
let rd = match std::fs::read_dir(&dir) {
|
|
||||||
Ok(r) => r,
|
|
||||||
Err(_) => continue,
|
|
||||||
};
|
|
||||||
for entry in rd.flatten() {
|
|
||||||
let path = entry.path();
|
|
||||||
if path.is_dir() {
|
|
||||||
stack.push(path);
|
|
||||||
} else if let Ok(meta) = std::fs::metadata(&path) {
|
|
||||||
total += meta.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
total
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve_hot_cache_root(custom_dir, &app)
|
resolve_hot_cache_root(custom_dir, &app)
|
||||||
.map(|root| dir_size(root))
|
.map(|root| super::fs_utils::dir_size_recursive(&root))
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,24 +200,8 @@ pub(crate) async fn delete_hot_cache_track(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let boundary = resolve_hot_cache_root(custom_dir, &app)?;
|
let boundary = resolve_hot_cache_root(custom_dir, &app)?;
|
||||||
|
if let Some(parent) = file_path.parent() {
|
||||||
let mut current = file_path.parent().map(|p| p.to_path_buf());
|
super::fs_utils::prune_empty_dirs_up_to(parent, &boundary);
|
||||||
while let Some(dir) = current {
|
|
||||||
if dir == boundary || !dir.starts_with(&boundary) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
match std::fs::read_dir(&dir) {
|
|
||||||
Ok(mut entries) => {
|
|
||||||
if entries.next().is_some() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if std::fs::remove_dir(&dir).is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
current = dir.parent().map(|p| p.to_path_buf());
|
|
||||||
}
|
|
||||||
Err(_) => break,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
+1
@@ -1,5 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
mod fs_utils;
|
||||||
mod offline;
|
mod offline;
|
||||||
mod downloads;
|
mod downloads;
|
||||||
mod hot;
|
mod hot;
|
||||||
|
|||||||
+4
-42
@@ -145,37 +145,16 @@ pub(crate) async fn download_track_offline(
|
|||||||
/// Returns the total size in bytes of all files in the offline cache directory (and optional custom dir).
|
/// Returns the total size in bytes of all files in the offline cache directory (and optional custom dir).
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) async fn get_offline_cache_size(custom_dir: Option<String>, app: tauri::AppHandle) -> u64 {
|
pub(crate) async fn get_offline_cache_size(custom_dir: Option<String>, app: tauri::AppHandle) -> u64 {
|
||||||
fn dir_size(root: std::path::PathBuf) -> u64 {
|
|
||||||
if !root.exists() { return 0; }
|
|
||||||
let mut total: u64 = 0;
|
|
||||||
let mut stack = vec![root];
|
|
||||||
while let Some(dir) = stack.pop() {
|
|
||||||
let rd = match std::fs::read_dir(&dir) {
|
|
||||||
Ok(r) => r,
|
|
||||||
Err(_) => continue,
|
|
||||||
};
|
|
||||||
for entry in rd.flatten() {
|
|
||||||
let path = entry.path();
|
|
||||||
if path.is_dir() {
|
|
||||||
stack.push(path);
|
|
||||||
} else if let Ok(meta) = std::fs::metadata(&path) {
|
|
||||||
total += meta.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
total
|
|
||||||
}
|
|
||||||
|
|
||||||
let default_dir = match app.path().app_data_dir() {
|
let default_dir = match app.path().app_data_dir() {
|
||||||
Ok(d) => d.join("psysonic-offline"),
|
Ok(d) => d.join("psysonic-offline"),
|
||||||
Err(_) => return 0,
|
Err(_) => return 0,
|
||||||
};
|
};
|
||||||
let mut total = dir_size(default_dir);
|
let mut total = super::fs_utils::dir_size_recursive(&default_dir);
|
||||||
|
|
||||||
if let Some(cd) = custom_dir {
|
if let Some(cd) = custom_dir {
|
||||||
let custom = std::path::PathBuf::from(cd);
|
let custom = std::path::PathBuf::from(cd);
|
||||||
if custom != std::path::PathBuf::from("") {
|
if custom != std::path::PathBuf::from("") {
|
||||||
total += dir_size(custom);
|
total += super::fs_utils::dir_size_recursive(&custom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
total
|
total
|
||||||
@@ -208,25 +187,8 @@ pub(crate) async fn delete_offline_track(
|
|||||||
.join("psysonic-offline")
|
.join("psysonic-offline")
|
||||||
};
|
};
|
||||||
|
|
||||||
// Walk upward, pruning directories that have become empty.
|
if let Some(parent) = file_path.parent() {
|
||||||
// Stops as soon as a non-empty directory or the boundary is reached.
|
super::fs_utils::prune_empty_dirs_up_to(parent, &boundary);
|
||||||
let mut current = file_path.parent().map(|p| p.to_path_buf());
|
|
||||||
while let Some(dir) = current {
|
|
||||||
if dir == boundary || !dir.starts_with(&boundary) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
match std::fs::read_dir(&dir) {
|
|
||||||
Ok(mut entries) => {
|
|
||||||
if entries.next().is_some() {
|
|
||||||
break; // Directory still has contents — stop pruning.
|
|
||||||
}
|
|
||||||
if std::fs::remove_dir(&dir).is_err() {
|
|
||||||
break; // Could not remove (e.g. permissions) — stop.
|
|
||||||
}
|
|
||||||
current = dir.parent().map(|p| p.to_path_buf());
|
|
||||||
}
|
|
||||||
Err(_) => break,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user