- Add Cargo project with TUI and async dependencies - Implement cache-only architecture for low bandwidth operation - Add file scanner with media type detection - Create two-panel TUI layout (file tree and status) - Add config file support for scan path management - Implement XDG-compliant cache and config directories - Add Gitea CI/CD workflow for automated releases
90 lines
2.7 KiB
Rust
90 lines
2.7 KiB
Rust
use anyhow::{Context, Result};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FileMetadata {
|
|
pub path: PathBuf,
|
|
pub size: u64,
|
|
pub duration: Option<f64>,
|
|
pub codec: Option<String>,
|
|
pub hash: Option<String>,
|
|
pub is_video: bool,
|
|
pub is_audio: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FileTreeNode {
|
|
pub name: String,
|
|
pub path: PathBuf,
|
|
pub is_dir: bool,
|
|
pub children: Vec<FileTreeNode>,
|
|
pub metadata: Option<FileMetadata>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct Cache {
|
|
pub file_tree: Vec<FileTreeNode>,
|
|
pub metadata: HashMap<PathBuf, FileMetadata>,
|
|
}
|
|
|
|
impl Cache {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
file_tree: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn load(cache_dir: &Path) -> Result<Self> {
|
|
let tree_path = cache_dir.join("file_tree.json");
|
|
let metadata_path = cache_dir.join("metadata.json");
|
|
|
|
if !tree_path.exists() || !metadata_path.exists() {
|
|
return Ok(Self::new());
|
|
}
|
|
|
|
let tree_data = fs::read_to_string(&tree_path)
|
|
.context("Failed to read file_tree.json")?;
|
|
let file_tree: Vec<FileTreeNode> = serde_json::from_str(&tree_data)
|
|
.context("Failed to parse file_tree.json")?;
|
|
|
|
let metadata_data = fs::read_to_string(&metadata_path)
|
|
.context("Failed to read metadata.json")?;
|
|
let metadata: HashMap<PathBuf, FileMetadata> = serde_json::from_str(&metadata_data)
|
|
.context("Failed to parse metadata.json")?;
|
|
|
|
Ok(Self {
|
|
file_tree,
|
|
metadata,
|
|
})
|
|
}
|
|
|
|
pub fn save(&self, cache_dir: &Path) -> Result<()> {
|
|
fs::create_dir_all(cache_dir)
|
|
.context("Failed to create cache directory")?;
|
|
|
|
let tree_path = cache_dir.join("file_tree.json");
|
|
let tree_data = serde_json::to_string_pretty(&self.file_tree)
|
|
.context("Failed to serialize file_tree")?;
|
|
fs::write(&tree_path, tree_data)
|
|
.context("Failed to write file_tree.json")?;
|
|
|
|
let metadata_path = cache_dir.join("metadata.json");
|
|
let metadata_data = serde_json::to_string_pretty(&self.metadata)
|
|
.context("Failed to serialize metadata")?;
|
|
fs::write(&metadata_path, metadata_data)
|
|
.context("Failed to write metadata.json")?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn get_cache_dir() -> Result<PathBuf> {
|
|
let cache_home = dirs::cache_dir()
|
|
.context("Failed to get cache directory")?;
|
|
Ok(cache_home.join("cm-player"))
|
|
}
|