speki_backend/
lib.rs

1use std::{sync::Arc, time::Duration};
2
3use cache::CardCache;
4use card::SavedCard;
5use filter::FilterUtil;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use uuid::Uuid;
8
9pub mod cache;
10pub mod card;
11pub mod categories;
12pub mod common;
13pub mod config;
14pub mod filter;
15pub mod git;
16pub mod media;
17//pub mod ml;
18//pub mod openai;
19
20pub enum CardsAction {
21    Review,
22    SetSuspended(bool),
23}
24
25#[derive(Default, Clone)]
26enum Field {
27    _Strength,
28    #[default]
29    LastModified,
30}
31
32#[derive(Default, Clone)]
33struct CardSorter {
34    ascending: bool,
35    field: Field,
36}
37
38#[derive(Clone)]
39enum Mode {
40    List { index: usize },
41    View { index: usize },
42}
43
44impl Mode {
45    fn toggle(&mut self) {
46        *self = match self {
47            Self::List { index } => Self::View { index: *index },
48            Self::View { index } => Self::List { index: *index },
49        }
50    }
51
52    fn is_view(&self) -> bool {
53        match self {
54            Mode::List { .. } => false,
55            Mode::View { .. } => true,
56        }
57    }
58
59    fn is_list(&self) -> bool {
60        !self.is_view()
61    }
62
63    fn enter_list(&mut self) {
64        let index = self.idx();
65        *self = Self::List { index };
66    }
67    fn enter_view(&mut self) {
68        let index = self.idx();
69        *self = Self::View { index };
70    }
71
72    fn idx(&self) -> usize {
73        match self {
74            Mode::List { index } => *index,
75            Mode::View { index } => *index,
76        }
77    }
78
79    fn mut_idx(&mut self) -> &mut usize {
80        match self {
81            Mode::List { index } => index,
82            Mode::View { index } => index,
83        }
84    }
85
86    pub fn set_idx(&mut self, idx: usize) {
87        *self.mut_idx() = idx;
88    }
89}
90
91impl Default for Mode {
92    fn default() -> Self {
93        Self::List { index: 0 }
94    }
95}
96
97/// Struct meant for viewing cards, sort of a wrapper around FilterUtil.
98#[derive(Clone)]
99pub struct CardViewer {
100    cards: Vec<Id>,
101    filtered_cards: Vec<Id>,
102    filter: FilterUtil,
103    sorter: CardSorter,
104    mode: Mode,
105}
106
107impl CardViewer {
108    pub fn new(cards: Vec<Id>) -> Self {
109        if cards.is_empty() {
110            panic!("plz at least one element");
111        }
112
113        Self {
114            cards: cards.clone(),
115            filtered_cards: cards,
116            filter: FilterUtil::default(),
117            sorter: CardSorter::default(),
118            mode: Mode::default(),
119        }
120    }
121
122    pub fn total_qty(&self) -> usize {
123        self.cards.len()
124    }
125
126    pub fn set_idx(&mut self, idx: usize) {
127        self.mode.set_idx(idx);
128    }
129
130    pub fn filtered_qty(&self) -> usize {
131        self.filtered_cards.len()
132    }
133
134    fn update_filtered_cards(&mut self, mut cards: Vec<Id>, cache: &mut CardCache) {
135        match self.sorter.field {
136            Field::_Strength => {
137                cards.sort_by_key(|card| cache.get_ref(*card).strength().unwrap_or_default())
138            }
139            Field::LastModified => cards.sort_by_key(|card| cache.get_ref(*card).last_modified()),
140        }
141        if !self.sorter.ascending {
142            cards.reverse();
143        }
144        self.filtered_cards = cards;
145    }
146
147    pub fn go_forward(&mut self) {
148        let card_len = self.filtered_cards.len();
149        let idx = self.mode.mut_idx();
150
151        if *idx < card_len - 1 {
152            *idx += 1;
153        }
154    }
155
156    pub fn filtered_cards(&self) -> &Vec<Id> {
157        &self.filtered_cards
158    }
159
160    pub fn is_view(&self) -> bool {
161        self.mode.is_view()
162    }
163
164    pub fn is_list(&self) -> bool {
165        self.mode.is_list()
166    }
167
168    pub fn filter_ref(&self) -> &FilterUtil {
169        &self.filter
170    }
171
172    pub fn enter_view(&mut self) {
173        self.mode.enter_view();
174    }
175
176    pub fn enter_list(&mut self) {
177        self.mode.enter_list();
178    }
179
180    pub fn idx(&self) -> usize {
181        self.mode.idx()
182    }
183
184    pub fn selected_card_id(&self) -> Id {
185        self.filtered_cards[self.idx()]
186    }
187
188    pub fn selected_card(&self, cache: &mut CardCache) -> Arc<SavedCard> {
189        let idx = self.mode.idx();
190        cache.get_ref(self.filtered_cards[idx])
191    }
192
193    pub fn go_back(&mut self) {
194        let idx = self.mode.mut_idx();
195
196        if *idx > 0 {
197            *idx -= 1;
198        }
199    }
200
201    pub fn filter_mut(&mut self) -> &mut FilterUtil {
202        &mut self.filter
203    }
204
205    pub fn re_apply_rules(&mut self, cache: &mut CardCache) {
206        let cards = self.filter.evaluate_cards(self.cards.clone(), cache);
207
208        if !cards.is_empty() {
209            self.filtered_cards = cards;
210        }
211    }
212
213    pub fn filter(mut self, filter: FilterUtil, cache: &mut CardCache) -> Self {
214        let filtered_cards = filter.evaluate_cards(self.cards.clone(), cache);
215        if filtered_cards.is_empty() {
216            return self;
217        }
218        self.update_filtered_cards(filtered_cards, cache);
219        self.filter = filter;
220        *self.mode.mut_idx() = 0;
221        self
222    }
223
224    pub fn toggle_mode(&mut self) {
225        self.mode.toggle();
226    }
227}
228
229pub mod paths {
230    use std::path::PathBuf;
231
232    pub fn get_import_csv() -> PathBuf {
233        get_share_path().join("import.csv")
234    }
235
236    pub fn get_cards_path() -> PathBuf {
237        get_share_path().join("cards")
238    }
239
240    pub fn get_ml_path() -> PathBuf {
241        get_share_path().join("ml/")
242    }
243
244    pub fn get_runmodel_path() -> PathBuf {
245        get_ml_path().join("runmodel.py")
246    }
247
248    pub fn get_media_path() -> PathBuf {
249        get_share_path().join("media/")
250    }
251
252    #[cfg(not(test))]
253    pub fn get_share_path() -> PathBuf {
254        let home = dirs::home_dir().unwrap();
255        home.join(".local/share/speki/")
256    }
257
258    #[cfg(test)]
259    pub fn get_share_path() -> PathBuf {
260        PathBuf::from("./test_dir/")
261    }
262}
263
264pub type Id = Uuid;
265
266pub fn duration_to_days(dur: &Duration) -> f32 {
267    dur.as_secs_f32() / 86400.
268}
269
270pub fn days_to_duration(days: f32) -> Duration {
271    Duration::from_secs_f32(days * 86400.)
272}
273
274pub fn serialize_duration<S>(duration: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
275where
276    S: Serializer,
277{
278    let days = duration.as_ref().map(duration_to_days);
279    days.serialize(serializer)
280}
281
282pub fn deserialize_duration<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
283where
284    D: Deserializer<'de>,
285{
286    let days = Option::<f32>::deserialize(deserializer)?;
287    Ok(days.map(days_to_duration))
288}