cuqueclicker_lib/platform/
native.rs1use anyhow::Result;
10use std::env;
11use std::fs::{self, File, OpenOptions, TryLockError};
12use std::io;
13use std::path::PathBuf;
14
15use super::Capabilities;
16use crate::game::state::GameState;
17
18pub const CAPABILITIES: Capabilities = Capabilities { can_quit: true };
21
22fn save_path() -> Option<PathBuf> {
23 let base = env::var_os("XDG_CONFIG_HOME")
24 .map(PathBuf::from)
25 .or_else(|| env::var_os("HOME").map(|h| PathBuf::from(h).join(".config")))
26 .or_else(|| env::var_os("APPDATA").map(PathBuf::from))
27 .or_else(|| env::var_os("USERPROFILE").map(|h| PathBuf::from(h).join(".config")))?;
28 Some(base.join("cuqueclicker").join("save.json"))
29}
30
31fn lock_path() -> Option<PathBuf> {
32 save_path().map(|p| p.with_extension("json.lock"))
33}
34
35pub struct Persistence;
38
39impl Default for Persistence {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45impl Persistence {
46 pub fn new() -> Self {
47 Self
48 }
49
50 pub fn load(&self) -> GameState {
55 if let Some(path) = save_path()
56 && let Ok(data) = fs::read_to_string(&path)
57 {
58 return crate::save::load_from_str(&data);
59 }
60 GameState::default().migrate_runtime()
61 }
62
63 pub fn save(&self, state: &GameState) -> Result<()> {
67 if let Some(path) = save_path() {
68 if let Some(parent) = path.parent() {
69 fs::create_dir_all(parent)?;
70 }
71 let tmp = path.with_extension("json.tmp");
72 let data = crate::save::save_to_string(state)?;
73 fs::write(&tmp, data)?;
74 fs::rename(&tmp, &path)?;
75 }
76 Ok(())
77 }
78}
79
80pub struct InstanceLock {
84 _file: File,
85}
86
87impl InstanceLock {
88 pub fn try_acquire() -> io::Result<Self> {
92 let Some(path) = lock_path() else {
93 return Err(io::Error::new(
94 io::ErrorKind::NotFound,
95 "no XDG_CONFIG_HOME / HOME / APPDATA / USERPROFILE set; cannot locate save dir",
96 ));
97 };
98 if let Some(parent) = path.parent() {
99 fs::create_dir_all(parent)?;
100 }
101 let file = OpenOptions::new()
102 .write(true)
103 .create(true)
104 .truncate(false)
105 .open(&path)?;
106 file.try_lock().map_err(|e| match e {
107 TryLockError::WouldBlock => io::Error::new(
108 io::ErrorKind::WouldBlock,
109 "save lock held by another process",
110 ),
111 TryLockError::Error(io_err) => io_err,
112 })?;
113 Ok(InstanceLock { _file: file })
114 }
115}