use anyhow::Result;
use std::env;
use std::fs::{self, File, OpenOptions, TryLockError};
use std::io;
use std::path::PathBuf;
use super::Capabilities;
use crate::game::state::GameState;
pub const CAPABILITIES: Capabilities = Capabilities { can_quit: true };
fn save_path() -> Option<PathBuf> {
let base = env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.or_else(|| env::var_os("HOME").map(|h| PathBuf::from(h).join(".config")))
.or_else(|| env::var_os("APPDATA").map(PathBuf::from))
.or_else(|| env::var_os("USERPROFILE").map(|h| PathBuf::from(h).join(".config")))?;
Some(base.join("cuqueclicker").join("save.json"))
}
fn lock_path() -> Option<PathBuf> {
save_path().map(|p| p.with_extension("json.lock"))
}
pub struct Persistence;
impl Default for Persistence {
fn default() -> Self {
Self::new()
}
}
impl Persistence {
pub fn new() -> Self {
Self
}
pub fn load(&self) -> GameState {
if let Some(path) = save_path()
&& let Ok(data) = fs::read_to_string(&path)
{
return crate::save::load_from_str(&data);
}
GameState::default().migrate_runtime()
}
pub fn save(&self, state: &GameState) -> Result<()> {
if let Some(path) = save_path() {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let tmp = path.with_extension("json.tmp");
let data = crate::save::save_to_string(state)?;
fs::write(&tmp, data)?;
fs::rename(&tmp, &path)?;
}
Ok(())
}
}
pub struct InstanceLock {
_file: File,
}
impl InstanceLock {
pub fn try_acquire() -> io::Result<Self> {
let Some(path) = lock_path() else {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"no XDG_CONFIG_HOME / HOME / APPDATA / USERPROFILE set; cannot locate save dir",
));
};
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let file = OpenOptions::new()
.write(true)
.create(true)
.truncate(false)
.open(&path)?;
file.try_lock().map_err(|e| match e {
TryLockError::WouldBlock => io::Error::new(
io::ErrorKind::WouldBlock,
"save lock held by another process",
),
TryLockError::Error(io_err) => io_err,
})?;
Ok(InstanceLock { _file: file })
}
}