use std::fs; use std::io; use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use crate::project::{Bank, Project}; const VERSION: u8 = 1; #[derive(Serialize, Deserialize)] struct ProjectFile { version: u8, banks: Vec, #[serde(default)] sample_paths: Vec, #[serde(default = "default_tempo")] tempo: f64, } 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, } } } impl From for Project { fn from(file: ProjectFile) -> Self { let mut project = Self { banks: file.banks, sample_paths: file.sample_paths, tempo: file.tempo, }; 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<(), FileError> { let file = ProjectFile::from(project); let json = serde_json::to_string_pretty(&file)?; fs::write(path, json)?; Ok(()) } 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)) }