1
use super::slint_generatedAppWindow::{AppWindow, GlobalState};
2
use serde::{Deserialize, Serialize};
3
use slint::ComponentHandle;
4
use std::env;
5
use std::error::Error;
6
use std::fs;
7
use std::path::Path;
8

            
9
pub const XDG_STATE_HOME_DIR: &str = "io.github.heathcliff26.turbo-clicker";
10
pub const XDG_STATE_HOME: &str = "XDG_STATE_HOME";
11
pub const XDG_STATE_HOME_DEFAULT: &str = ".local/state";
12
pub const HOME: &str = "HOME";
13

            
14
#[cfg(test)]
15
mod test;
16

            
17
/// Contains all values from GlobalState of the UI.
18
/// Defaults will be set in GlobalState in the UI.
19
#[derive(Debug, Serialize, Deserialize, PartialEq)]
20
pub struct State {
21
    pub delay: u64,
22
    pub start_delay: u64,
23
    pub duration: u64,
24
    pub use_start_delay: bool,
25
    pub use_duration: bool,
26
    pub dark_mode: bool,
27
}
28

            
29
impl State {
30
    /// Create a new State instance from the GlobalState in the App.
31
2
    pub fn from_app(app: &AppWindow) -> Self {
32
2
        let global_state = app.global::<GlobalState>();
33

            
34
2
        Self {
35
2
            delay: global_state.get_delay().try_into().unwrap(),
36
2
            start_delay: global_state.get_start_delay().try_into().unwrap(),
37
2
            duration: global_state.get_duration().try_into().unwrap(),
38
2
            use_start_delay: global_state.get_use_start_delay(),
39
2
            use_duration: global_state.get_use_duration(),
40
2
            dark_mode: global_state.get_dark_mode(),
41
2
        }
42
2
    }
43

            
44
    /// Load the state from the user specific state file.
45
4
    pub fn from_file() -> Result<Option<Self>, Box<dyn Error>> {
46
4
        Self::from_path(get_state_file_path())
47
4
    }
48

            
49
    /// Load the state from the given file path.
50
5
    pub fn from_path<P>(path: P) -> Result<Option<Self>, Box<dyn Error>>
51
5
    where
52
5
        P: AsRef<Path>,
53
    {
54
5
        if !fs::exists(&path)? {
55
1
            return Ok(None);
56
4
        };
57
4
        let file = fs::File::open(&path)?;
58
4
        let state: State = serde_json::from_reader(file)?;
59
4
        Ok(Some(state))
60
5
    }
61

            
62
    /// Update the GlobalState in the App with this State instance.
63
2
    pub fn update_app(&self, app: &AppWindow) {
64
2
        let global_state = app.global::<GlobalState>();
65

            
66
2
        global_state.set_delay(self.delay as i32);
67
2
        global_state.set_start_delay(self.start_delay as i32);
68
2
        global_state.set_duration(self.duration as i32);
69
2
        global_state.set_use_start_delay(self.use_start_delay);
70
2
        global_state.set_use_duration(self.use_duration);
71
2
        global_state.set_dark_mode(self.dark_mode);
72
2
    }
73

            
74
    /// Save the state to user specific state file.
75
2
    pub fn save_to_file(&self) -> Result<(), Box<dyn Error>> {
76
2
        let path = get_state_file_path();
77
2
        create_parent_folder_if_not_exists(&path)?;
78
2
        let file = fs::File::create(path)?;
79
2
        serde_json::to_writer(file, self)?;
80
2
        Ok(())
81
2
    }
82
}
83

            
84
/// Read the XDG state directory from the environment and return the full path to the state file.
85
9
fn get_state_file_path() -> String {
86
9
    let mut path = match env::var(XDG_STATE_HOME) {
87
9
        Ok(path) if !path.is_empty() => Some(path),
88
2
        _ => None,
89
    };
90

            
91
9
    if path.is_none() {
92
2
        path = match env::var(HOME) {
93
1
            Ok(home) if !home.is_empty() => Some(format!("{home}/{XDG_STATE_HOME_DEFAULT}")),
94
1
            _ => None,
95
        }
96
7
    }
97

            
98
9
    let path = path.unwrap_or(format!("./{XDG_STATE_HOME_DEFAULT}"));
99

            
100
9
    format!("{path}/{XDG_STATE_HOME_DIR}/state.json")
101
9
}
102

            
103
/// Create the parent directory of the given file if it does not exist
104
2
fn create_parent_folder_if_not_exists(path: &str) -> Result<(), Box<dyn Error>> {
105
2
    let dir = std::path::Path::new(path)
106
2
        .parent()
107
2
        .ok_or("Failed to get parent directory")?
108
2
        .to_str()
109
2
        .ok_or("Failed to convert parent directory to string")?;
110
2
    if !fs::exists(dir)? {
111
2
        match fs::create_dir_all(dir) {
112
2
            Ok(_) => {}
113
            Err(e) => {
114
                eprintln!("Failed to create directory '{dir}' for states");
115
                return Err(Box::new(e));
116
            }
117
        };
118
    }
119
2
    Ok(())
120
2
}