use std::fs; use std::io; use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use crate::project::{Bank, Project}; const VERSION: u8 = 1; pub const EXTENSION: &str = "cagire"; pub fn ensure_extension(path: &Path) -> PathBuf { if path.extension().map(|e| e == EXTENSION).unwrap_or(false) { path.to_path_buf() } else { path.with_extension(EXTENSION) } } #[derive(Serialize, Deserialize)] struct ProjectFile { version: u8, banks: Vec, #[serde(default)] sample_paths: Vec, #[serde(default = "default_tempo")] tempo: f64, #[serde(default)] playing_patterns: Vec<(usize, usize)>, #[serde(default, skip_serializing_if = "String::is_empty")] prelude: String, } fn default_tempo() -> f64 { 120.0 } impl From<&Project> for ProjectFile { fn from(project: &Project) -> Self { Self { version: VERSION, banks: project.banks.clone(), sample_paths: project.sample_paths.clone(), tempo: project.tempo, playing_patterns: project.playing_patterns.clone(), prelude: project.prelude.clone(), } } } impl From for Project { fn from(file: ProjectFile) -> Self { let mut project = Self { banks: file.banks, sample_paths: file.sample_paths, tempo: file.tempo, playing_patterns: file.playing_patterns, prelude: file.prelude, }; project.normalize(); project } } #[derive(Debug)] pub enum FileError { Io(io::Error), Json(serde_json::Error), Version(u8), } impl std::fmt::Display for FileError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { FileError::Io(e) => write!(f, "IO error: {e}"), FileError::Json(e) => write!(f, "JSON error: {e}"), FileError::Version(v) => write!(f, "Unsupported version: {v}"), } } } impl From for FileError { fn from(e: io::Error) -> Self { FileError::Io(e) } } impl From for FileError { fn from(e: serde_json::Error) -> Self { FileError::Json(e) } } pub fn save(project: &Project, path: &Path) -> Result { let path = ensure_extension(path); let file = ProjectFile::from(project); let json = serde_json::to_string_pretty(&file)?; fs::write(&path, json)?; Ok(path) } pub fn load(path: &Path) -> Result { let json = fs::read_to_string(path)?; let file: ProjectFile = serde_json::from_str(&json)?; if file.version > VERSION { return Err(FileError::Version(file.version)); } Ok(Project::from(file)) }