speki_backend/
common.rs

1use std::fmt::Display;
2
3use std::path::{Path, PathBuf};
4use std::process::Command;
5use std::time::{Duration, UNIX_EPOCH};
6
7use crate::paths::get_cards_path;
8use std::io::{self, ErrorKind};
9
10use std::time::SystemTime;
11
12pub fn duration_to_days(dur: &Duration) -> f32 {
13    dur.as_secs_f32() / 86400.
14}
15
16pub fn days_to_duration(days: &f32) -> Duration {
17    Duration::from_secs_f32(days * 86400.)
18}
19
20pub fn current_time() -> Duration {
21    system_time_as_unix_time(SystemTime::now())
22}
23
24pub fn system_time_as_unix_time(time: SystemTime) -> Duration {
25    time.duration_since(SystemTime::UNIX_EPOCH)
26        .expect("Time went backwards")
27}
28
29/// Safe way to truncate string.
30pub fn truncate_string(input: String, max_len: usize) -> String {
31    let mut graphemes = input.chars();
32    let mut result = String::new();
33
34    for _ in 0..max_len {
35        if let Some(c) = graphemes.next() {
36            result.push(c);
37        } else {
38            break;
39        }
40    }
41
42    result
43}
44
45pub mod serde_duration_as_float_secs {
46    use serde::{Deserialize, Deserializer, Serializer};
47    use std::time::Duration;
48
49    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
50    where
51        S: Serializer,
52    {
53        let secs = duration.as_secs_f32();
54        serializer.serialize_f32(secs)
55    }
56
57    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
58    where
59        D: Deserializer<'de>,
60    {
61        let secs = f32::deserialize(deserializer)?;
62        Ok(Duration::from_secs_f32(secs))
63    }
64}
65
66pub mod serde_duration_as_secs {
67    use serde::{Deserialize, Deserializer, Serializer};
68    use std::time::Duration;
69
70    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
71    where
72        S: Serializer,
73    {
74        let secs = duration.as_secs();
75        serializer.serialize_u64(secs)
76    }
77
78    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
79    where
80        D: Deserializer<'de>,
81    {
82        let secs = u64::deserialize(deserializer)?;
83        Ok(Duration::from_secs(secs))
84    }
85}
86
87pub fn open_file_with_vim(path: &Path) -> io::Result<()> {
88    let status = Command::new("vim").arg(path).status()?;
89
90    if status.success() {
91        Ok(())
92    } else {
93        Err(io::Error::new(
94            ErrorKind::Other,
95            "Failed to open file with vim",
96        ))
97    }
98}
99
100pub trait MenuItem: Display {
101    fn action(&self) -> Box<dyn FnMut() -> bool>;
102}
103
104pub fn view_cards_in_explorer() {
105    open_folder_in_explorer(&get_cards_path()).unwrap()
106}
107
108fn open_folder_in_explorer(path: &Path) -> std::io::Result<()> {
109    #[cfg(target_os = "windows")]
110    {
111        Command::new("explorer").arg(path).status()?;
112    }
113
114    #[cfg(target_os = "macos")]
115    {
116        Command::new("open").arg(path).status()?;
117    }
118
119    #[cfg(target_os = "linux")]
120    {
121        Command::new("xdg-open").arg(path).status()?;
122    }
123
124    Ok(())
125}
126
127/// will generate a number between 0 and 100 and check that it's below the given percentage.
128/// so if you input '10', then ofc, 10% of the times it will return true as the number will be below 10
129pub fn within_percentage(percentage: u32) -> bool {
130    rand_int(100) < percentage
131}
132
133pub fn rand_int(max: u32) -> u32 {
134    let time = current_time();
135    (time.as_micros() ^ time.as_nanos() ^ time.as_millis()) as u32 % max
136}
137
138pub fn get_last_modified(path: PathBuf) -> Duration {
139    let metadata = std::fs::metadata(path).unwrap();
140    let modified_time = metadata.modified().unwrap();
141    let secs = modified_time
142        .duration_since(UNIX_EPOCH)
143        .map(|s| s.as_secs())
144        .unwrap();
145    Duration::from_secs(secs)
146}
147
148pub enum FileDir {
149    File(PathBuf),
150    Dir(PathBuf),
151}
152
153impl FileDir {
154    /// Returns the files and folders of the given directory.
155    pub fn non_rec(path: PathBuf) -> Vec<Self> {
156        let mut vec = Vec::new();
157        if !path.is_dir() {
158            panic!("damn bro");
159        }
160
161        for entry in path.read_dir().unwrap() {
162            let entry = entry.unwrap();
163            let file_type = entry.file_type().unwrap();
164
165            if entry.file_name().to_str().unwrap().starts_with('_') {
166                continue;
167            }
168
169            if file_type.is_dir() {
170                vec.push(Self::Dir(entry.path()));
171            } else if file_type.is_file() {
172                vec.push(Self::File(entry.path()));
173            };
174        }
175
176        vec
177    }
178
179    /// Returns the directories within a directory.
180    pub fn dirs(path: PathBuf) -> Vec<PathBuf> {
181        Self::non_rec(path)
182            .into_iter()
183            .filter_map(|x| match x {
184                FileDir::File(_) => None,
185                FileDir::Dir(path) => Some(path),
186            })
187            .collect()
188    }
189
190    /// Returns the files within a directory.
191    pub fn files(path: PathBuf) -> Vec<PathBuf> {
192        Self::non_rec(path)
193            .into_iter()
194            .filter_map(|x| match x {
195                FileDir::File(path) => Some(path),
196                FileDir::Dir(_) => None,
197            })
198            .collect()
199    }
200}