All checks were successful
Build and Release / build-and-release (push) Successful in 50s
- Remove tokio async runtime dependency (~2MB reduction) - Optimize fuzzy search to avoid string allocations - Optimize incremental search to only rebuild tree when needed - Extract duplicate scrolling logic to helper function - Replace magic numbers with named constants - Fix terminal cleanup to run even on error - Fix context menu item count mismatch - Remove unused metadata fields (duration, codec, hash)
129 lines
3.5 KiB
Rust
129 lines
3.5 KiB
Rust
use crate::cache::{Cache, FileMetadata, FileTreeNode};
|
|
use anyhow::Result;
|
|
use std::path::{Path, PathBuf};
|
|
use walkdir::WalkDir;
|
|
|
|
const AUDIO_EXTENSIONS: &[&str] = &["mp3", "flac", "wav", "ogg", "m4a", "aac", "opus", "wma"];
|
|
const VIDEO_EXTENSIONS: &[&str] = &["mp4", "mkv", "avi", "mov", "wmv", "flv", "webm", "m4v"];
|
|
|
|
pub fn is_media_file(path: &Path) -> bool {
|
|
if let Some(ext) = path.extension() {
|
|
let ext_str = ext.to_string_lossy().to_lowercase();
|
|
AUDIO_EXTENSIONS.contains(&ext_str.as_str()) || VIDEO_EXTENSIONS.contains(&ext_str.as_str())
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn is_audio_file(path: &Path) -> bool {
|
|
if let Some(ext) = path.extension() {
|
|
let ext_str = ext.to_string_lossy().to_lowercase();
|
|
AUDIO_EXTENSIONS.contains(&ext_str.as_str())
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn is_video_file(path: &Path) -> bool {
|
|
if let Some(ext) = path.extension() {
|
|
let ext_str = ext.to_string_lossy().to_lowercase();
|
|
VIDEO_EXTENSIONS.contains(&ext_str.as_str())
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn scan_directory(root_path: &Path) -> Result<FileTreeNode> {
|
|
let name = root_path
|
|
.file_name()
|
|
.unwrap_or_default()
|
|
.to_string_lossy()
|
|
.to_string();
|
|
|
|
let mut node = FileTreeNode {
|
|
name,
|
|
path: root_path.to_path_buf(),
|
|
is_dir: true,
|
|
children: Vec::new(),
|
|
metadata: None,
|
|
};
|
|
|
|
let mut entries: Vec<_> = WalkDir::new(root_path)
|
|
.max_depth(1)
|
|
.min_depth(1)
|
|
.into_iter()
|
|
.filter_map(|e| e.ok())
|
|
.collect();
|
|
|
|
entries.sort_by_key(|e| e.path().to_path_buf());
|
|
|
|
for entry in entries {
|
|
let path = entry.path();
|
|
|
|
if entry.file_type().is_dir() {
|
|
// Recursively scan subdirectories
|
|
if let Ok(child_node) = scan_directory(path) {
|
|
node.children.push(child_node);
|
|
}
|
|
} else if is_media_file(path) {
|
|
// Add media file
|
|
let file_name = path
|
|
.file_name()
|
|
.unwrap_or_default()
|
|
.to_string_lossy()
|
|
.to_string();
|
|
|
|
let size = entry.metadata().map(|m| m.len()).unwrap_or(0);
|
|
|
|
let metadata = FileMetadata {
|
|
path: path.to_path_buf(),
|
|
size,
|
|
is_video: is_video_file(path),
|
|
is_audio: is_audio_file(path),
|
|
};
|
|
|
|
let file_node = FileTreeNode {
|
|
name: file_name,
|
|
path: path.to_path_buf(),
|
|
is_dir: false,
|
|
children: Vec::new(),
|
|
metadata: Some(metadata),
|
|
};
|
|
|
|
node.children.push(file_node);
|
|
}
|
|
}
|
|
|
|
Ok(node)
|
|
}
|
|
|
|
pub fn scan_paths(paths: &[PathBuf]) -> Result<Cache> {
|
|
let mut cache = Cache::new();
|
|
|
|
for path in paths {
|
|
if path.exists() {
|
|
tracing::info!("Scanning path: {:?}", path);
|
|
let tree_node = scan_directory(path)?;
|
|
|
|
// Collect all metadata from the tree
|
|
collect_metadata(&tree_node, &mut cache);
|
|
|
|
cache.file_tree.push(tree_node);
|
|
} else {
|
|
tracing::warn!("Path does not exist: {:?}", path);
|
|
}
|
|
}
|
|
|
|
Ok(cache)
|
|
}
|
|
|
|
fn collect_metadata(node: &FileTreeNode, cache: &mut Cache) {
|
|
if let Some(ref metadata) = node.metadata {
|
|
cache.metadata.insert(node.path.clone(), metadata.clone());
|
|
}
|
|
|
|
for child in &node.children {
|
|
collect_metadata(child, cache);
|
|
}
|
|
}
|