caffeine_cli/caffeine/
mod.rs

1use core::fmt;
2use std::{
3    error::Error,
4    fs,
5    path::PathBuf,
6    process::Command,
7    thread,
8    time::{Duration, SystemTime, UNIX_EPOCH},
9};
10
11use chrono::{DateTime, Utc};
12use daemonize::Daemonize;
13use serde::{Deserialize, Serialize};
14
15#[derive(Serialize, Deserialize, Debug, Clone)]
16pub struct CaffeineSession {
17    pub proccess_id: String,
18    pub start_time: u64,
19    pub session_length: Option<u64>,
20}
21
22#[derive(Debug)]
23pub enum SessionError {
24    ConflictingSession,
25    NoActiveSession,
26}
27
28impl Error for SessionError {}
29
30impl fmt::Display for SessionError {
31    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32        match self {
33            SessionError::ConflictingSession => write!(f, "A session already exists"),
34            SessionError::NoActiveSession => write!(f, "Couldn't find an active session"),
35        }
36    }
37}
38
39/// The session path. **/tmp/caffeine-session.json**
40pub fn get_session_path() -> PathBuf {
41    PathBuf::from("/tmp/caffeine-session.json")
42}
43
44/// Inits a caffeine session.
45pub fn init_session(seconds: Option<u64>) -> Result<CaffeineSession, Box<dyn Error>> {
46    let seconds_str = if let Some(seconds) = seconds {
47        seconds.to_string()
48    } else {
49        String::from("infinity")
50    };
51
52    let process = Command::new("systemd-inhibit")
53        .arg("--what=idle")
54        .arg("sleep")
55        .arg(seconds_str)
56        .spawn()?;
57
58    let process_id = process.id();
59
60    let start_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
61
62    Ok(CaffeineSession {
63        proccess_id: process_id.to_string(),
64        start_time,
65        session_length: if let Some(seconds) = seconds {
66            Some(seconds)
67        } else {
68            None
69        },
70    })
71}
72
73/// Inits a protected caffeine session (recommended). It writes a file so that no other session can be also active
74pub fn init_protected_session(seconds: Option<u64>) -> Result<CaffeineSession, Box<dyn Error>> {
75    let session = get_session();
76
77    if session.is_some() {
78        return Err(Box::new(SessionError::ConflictingSession));
79    }
80
81    let new_session = init_session(seconds)?;
82
83    let json = serde_json::to_string(&new_session)?;
84
85    fs::write(get_session_path(), &json)?;
86
87    if let Some(seconds) = seconds {
88        let daemon = Daemonize::new();
89
90        if let Ok(_) = daemon.start() {
91            thread::sleep(Duration::from_secs(seconds));
92
93            let _ = fs::remove_file(get_session_path());
94        }
95    }
96
97    Ok(new_session)
98}
99
100/// Ends the given session
101pub fn end_session(session: CaffeineSession) -> Result<(), Box<dyn Error>> {
102    Command::new("kill")
103        .arg(&session.proccess_id)
104        .spawn()
105        .expect("Error killing caffeine session");
106
107    Ok(())
108}
109
110pub fn end_protected_session() -> Result<(), Box<dyn Error>> {
111    let session = get_session();
112
113    if session.is_none() {
114        return Err(Box::new(SessionError::NoActiveSession));
115    }
116
117    end_session(session.unwrap())?;
118
119    fs::remove_file(get_session_path())?;
120
121    Ok(())
122}
123
124/// Get the active session
125pub fn get_session() -> Option<CaffeineSession> {
126    let session_json = fs::read_to_string(get_session_path());
127
128    if session_json.is_err() {
129        return None;
130    }
131
132    let session = serde_json::from_str(&session_json.unwrap());
133
134    if session.is_err() {
135        return None;
136    }
137
138    Some(session.unwrap())
139}
140
141impl CaffeineSession {
142    pub fn get_elapsed_time(&self) -> String {
143        let now = SystemTime::now()
144            .duration_since(UNIX_EPOCH)
145            .unwrap()
146            .as_secs();
147
148        let elapsed = UNIX_EPOCH + Duration::from_secs(now - self.start_time);
149
150        let datetime: DateTime<Utc> = elapsed.into();
151
152        datetime.format("%Hh %Mm %Ss").to_string()
153    }
154
155    pub fn get_session_length(&self) -> Option<String> {
156        return if let Some(session_length) = self.session_length {
157            let timestamp = UNIX_EPOCH + Duration::from_secs(session_length);
158
159            let datetime: DateTime<Utc> = timestamp.into();
160
161            Some(datetime.format("%Hh %Mm %Ss").to_string())
162        } else {
163            None
164        };
165    }
166
167    pub fn get_remaining_time(&self) -> Option<String> {
168        return if let Some(session_length) = self.session_length {
169            let now = SystemTime::now()
170                .duration_since(UNIX_EPOCH)
171                .unwrap()
172                .as_secs();
173
174            let timestamp =
175                UNIX_EPOCH + Duration::from_secs((session_length + self.start_time) - now);
176
177            let datetime: DateTime<Utc> = timestamp.into();
178
179            Some(datetime.format("%Hh %Mm %Ss").to_string())
180        } else {
181            None
182        };
183    }
184}