Files
cm-player/src/scanner/mod.rs
Christoffer Martinsson 6ad522f27c
All checks were successful
Build and Release / build-and-release (push) Successful in 50s
Optimize performance and reduce binary size
- 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)
2025-12-11 19:27:50 +01:00

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);
}
}