use std::path::PathBuf;
use serde::{de::DeserializeOwned, Serialize};
pub mod cache;
pub mod config;
pub mod downloads;
pub mod library;
pub mod lyrics_cache;
pub mod playlists;
pub mod search_history;
pub mod session;
pub mod thumbnails;
fn project_dirs() -> Option<&'static directories::ProjectDirs> {
static DIRS: std::sync::OnceLock<Option<directories::ProjectDirs>> = std::sync::OnceLock::new();
DIRS.get_or_init(|| directories::ProjectDirs::from("", "", "goosemusic"))
.as_ref()
}
pub fn config_path(file: &str) -> PathBuf {
project_dirs().map_or_else(|| PathBuf::from(file), |d| d.config_dir().join(file))
}
pub fn cache_path(sub: &str) -> PathBuf {
project_dirs().map_or_else(|| PathBuf::from(sub), |d| d.cache_dir().join(sub))
}
pub fn data_path(file: &str) -> PathBuf {
project_dirs().map_or_else(|| PathBuf::from(file), |d| d.data_local_dir().join(file))
}
pub enum StoreLocation {
Config,
Data,
Cache,
}
pub trait JsonStore: Serialize + DeserializeOwned + Default {
const FILE: &'static str;
const LOCATION: StoreLocation = StoreLocation::Config;
fn path() -> PathBuf {
match Self::LOCATION {
StoreLocation::Config => config_path(Self::FILE),
StoreLocation::Data => data_path(Self::FILE),
StoreLocation::Cache => cache_path(Self::FILE),
}
}
fn load() -> Self {
std::fs::read_to_string(Self::path())
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default()
}
#[cfg(not(test))]
fn save(&self) {
let path = Self::path();
if let Some(dir) = path.parent() {
if let Err(e) = std::fs::create_dir_all(dir) {
tracing::warn!("Failed to create {}: {e}", dir.display());
return;
}
}
let Ok(s) = serde_json::to_string_pretty(self) else {
tracing::warn!("Failed to serialize {}", path.display());
return;
};
let tmp = path.with_extension("tmp");
if let Err(e) = std::fs::write(&tmp, s) {
tracing::warn!("Failed to write {}: {e}", tmp.display());
return;
}
if let Err(e) = std::fs::rename(&tmp, &path) {
tracing::warn!("Failed to rename {}: {e}", tmp.display());
let _ = std::fs::remove_file(&tmp);
}
}
#[cfg(test)]
fn save(&self) {}
}