wirc_server/
channel.rs

1use std::str::FromStr;
2
3use tokio::{fs, prelude::*};
4
5use rayon::{prelude::*, str::Lines};
6
7use serde::{Deserialize, Serialize};
8
9use fs::OpenOptions;
10use uuid::Uuid;
11
12use crate::{get_system_millis, ApiActionError, ID};
13
14static HUB_DATA_FOLDER: &str = "data/hubs/data";
15
16#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
17pub struct Channel {
18    #[serde(skip)]
19    pub messages: Vec<Message>,
20    pub id: ID,
21    pub server_id: ID,
22    pub name: String,
23    pub created: u128,
24}
25
26impl Channel {
27    pub async fn new(name: String, id: ID, server_id: ID) -> Result<Self, ()> {
28        let new = Self {
29            name,
30            id,
31            server_id,
32            messages: Vec::new(),
33            created: crate::get_system_millis(),
34        };
35        if let Ok(_) = new.create_dir().await {
36            Ok(new)
37        } else {
38            Err(())
39        }
40    }
41
42    pub fn get_folder(&self) -> String {
43        format!("{}/{}/{}", HUB_DATA_FOLDER, self.server_id, self.id)
44    }
45
46    pub async fn create_dir(&self) -> tokio::io::Result<()> {
47        tokio::fs::create_dir_all(self.get_folder()).await
48    }
49
50    pub async fn add_message(&mut self, message: Message) -> Result<(), ApiActionError> {
51        let message_string = &message.to_string();
52        if let Ok(mut file) = OpenOptions::new()
53            .write(true)
54            .create(true)
55            .append(true)
56            .open(self.get_current_file().await)
57            .await
58        {
59            if let Ok(_) = file
60                .write((message_string.to_owned() + "\n").as_bytes())
61                .await
62            {
63                self.messages.push(message);
64                Ok(())
65            } else {
66                Err(ApiActionError::WriteFileError)
67            }
68        } else {
69            Err(ApiActionError::OpenFileError)
70        }
71    }
72
73    pub async fn get_last_messages(&self, max: usize) -> Vec<Message> {
74        let mut result: Vec<Message> = Vec::new();
75        if let Ok(mut dir) = fs::read_dir(self.get_folder()).await {
76            let mut files = Vec::new();
77            while let Ok(Some(entry)) = dir.next_entry().await {
78                if entry.path().is_file() {
79                    files.push(entry)
80                }
81            }
82            files.par_sort_by_key(|f| f.file_name());
83            files.reverse();
84            let mut whole_file = String::new();
85            for file in files.iter() {
86                if let Ok(mut file) = fs::File::open(file.path()).await {
87                    whole_file.clear();
88                    if let Ok(_) = file.read_to_string(&mut whole_file).await {
89                        let lines = whole_file.par_lines().collect::<Vec<&str>>();
90                        let found = &mut lines
91                            .par_iter()
92                            .filter_map(|l| {
93                                if let Ok(message) = l.parse::<Message>() {
94                                    Some(message)
95                                } else {
96                                    None
97                                }
98                            })
99                            .collect::<Vec<Message>>();
100                        found.par_sort_by_key(|m| m.created);
101                        found.reverse();
102                        result.append(found);
103                        if result.len() >= max {
104                            result.truncate(max);
105                            return result;
106                        }
107                    }
108                }
109            }
110        }
111        result
112    }
113
114    pub async fn get_messages(
115        &self,
116        from: u128,
117        to: u128,
118        invert: bool,
119        max: usize,
120    ) -> Vec<Message> {
121        let mut result: Vec<Message> = Vec::new();
122        if let Ok(mut dir) = fs::read_dir(self.get_folder()).await {
123            let mut files = Vec::new();
124            while let Ok(Some(entry)) = dir.next_entry().await {
125                if entry.path().is_file() {
126                    files.push(entry)
127                }
128            }
129            files.par_sort_by_key(|f| f.file_name());
130            if invert {
131                files.reverse()
132            }
133            let mut whole_file = String::new();
134            for file in files.iter() {
135                if let Ok(mut file) = fs::File::open(file.path()).await {
136                    whole_file.clear();
137                    if let Ok(_) = file.read_to_string(&mut whole_file).await {
138                        let lines = whole_file.par_lines();
139                        let mut filtered: Vec<Message> = lines
140                            .filter_map(|l| {
141                                let created = l
142                                    .splitn(4, ',')
143                                    .skip(2)
144                                    .next()
145                                    .unwrap_or("0")
146                                    .parse::<u128>()
147                                    .unwrap_or(0);
148                                if created >= from && created <= to {
149                                    if let Ok(message) = l.parse::<Message>() {
150                                        Some(message)
151                                    } else {
152                                        None
153                                    }
154                                } else {
155                                    None
156                                }
157                            })
158                            .collect();
159                        if invert {
160                            filtered.reverse()
161                        }
162                        filtered.truncate(max - result.len());
163                        result.append(&mut filtered);
164                        if result.len() >= max {
165                            result.truncate(max);
166                            return result;
167                        }
168                    }
169                }
170            }
171        }
172        result
173    }
174
175    pub async fn on_all_raw_lines<F: FnMut(Lines) -> ()>(&self, mut action: F) {
176        if let Ok(mut dir) = fs::read_dir(self.get_folder()).await {
177            let mut whole_file = String::new();
178            while let Ok(Some(entry)) = dir.next_entry().await {
179                if entry.path().is_file() {
180                    if let Ok(mut file) = fs::File::open(entry.path()).await {
181                        whole_file.clear();
182                        if let Ok(_) = file.read_to_string(&mut whole_file).await {
183                            let lines = whole_file.par_lines();
184                            action(lines)
185                        }
186                    }
187                }
188            }
189        }
190    }
191
192    pub async fn find_messages_containing(
193        &self,
194        string: String,
195        case_sensitive: bool,
196    ) -> Vec<Message> {
197        let mut results: Vec<Message> = Vec::new();
198        let mut search = string.clone();
199        if !case_sensitive {
200            search.make_ascii_uppercase()
201        }
202        self.on_all_raw_lines(|lines| {
203            let mut result: Vec<Message> = lines
204                .filter_map(|l| {
205                    let mut compare_string = l.splitn(4, ',').last().unwrap_or("").to_string();
206                    if !case_sensitive {
207                        compare_string.make_ascii_uppercase();
208                    }
209                    if compare_string.contains(&search) {
210                        if let Ok(message) = l.parse::<Message>() {
211                            Some(message)
212                        } else {
213                            None
214                        }
215                    } else {
216                        None
217                    }
218                })
219                .collect();
220            results.append(&mut result);
221        })
222        .await;
223        results
224    }
225
226    pub async fn get_message(&self, id: String) -> Option<Message> {
227        for message in self.messages.iter() {
228            if message.id.to_string() == id {
229                return Some(message.clone());
230            }
231        }
232        let id = id.as_str();
233        let mut result: Option<Message> = None;
234        self.on_all_raw_lines(|lines| {
235            let results: Vec<Message> = lines
236                .filter_map(|l| {
237                    if l.starts_with(id) {
238                        if let Ok(message) = l.parse::<Message>() {
239                            Some(message)
240                        } else {
241                            None
242                        }
243                    } else {
244                        None
245                    }
246                })
247                .collect();
248            if let Some(message) = results.first() {
249                result = Some(message.clone());
250            }
251        })
252        .await;
253        return result;
254    }
255
256    pub async fn get_current_file(&mut self) -> String {
257        let now = get_system_millis() / 1000 / 60 / 60 / 24;
258        self.messages.reverse();
259        self.messages.truncate(100);
260        self.messages.reverse();
261        format!("{}/{}", self.get_folder(), now)
262    }
263}
264
265#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
266pub struct Message {
267    pub id: ID,
268    pub sender: ID,
269    pub created: u128,
270    pub content: String,
271}
272
273impl ToString for Message {
274    fn to_string(&self) -> String {
275        format!(
276            "{},{},{},{}",
277            self.id.as_u128(),
278            self.sender.as_u128(),
279            self.created,
280            self.content.replace('\n', r#"\n"#)
281        )
282    }
283}
284
285impl FromStr for Message {
286    type Err = ();
287
288    fn from_str(s: &str) -> Result<Self, Self::Err> {
289        let mut parts = s.splitn(4, ',');
290        if let Some(id_str) = parts.next() {
291            if let Ok(id) = uuid_from_num_string(id_str) {
292                if let Some(sender_str) = parts.next() {
293                    if let Ok(sender) = uuid_from_num_string(sender_str) {
294                        if let Some(created_str) = parts.next() {
295                            if let Ok(created) = created_str.parse::<u128>() {
296                                if let Some(content) = parts.next() {
297                                    return Ok(Self {
298                                        id,
299                                        sender,
300                                        created,
301                                        content: content.replace(r#"\n"#, "\n"),
302                                    });
303                                }
304                            }
305                        }
306                    }
307                }
308            }
309        }
310        return Err(());
311    }
312}
313
314fn uuid_from_num_string(string: &str) -> Result<Uuid, ()> {
315    if let Ok(num) = string.parse::<u128>() {
316        Ok(Uuid::from_u128(num))
317    } else {
318        Err(())
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use std::str::FromStr;
325
326    use uuid::Uuid;
327
328    use super::{Channel, Message};
329
330    fn new_test_message(variation: u128) -> Message {
331        Message {
332            id: Uuid::from_u128(123456789 + variation),
333            sender: Uuid::from_u128(0987654321),
334            created: 12222020,
335            content: "This is a test message.\nWith a newline.".to_string()
336                + &variation.to_string(),
337        }
338    }
339
340    async fn new_test_channel() -> Channel {
341        let _remove = tokio::fs::remove_dir_all("data/hubs/data/00000000-0000-0000-0000-0000075bcd15/00000000-0000-0000-0000-0000075bcd15").await;
342        let channel = Channel::new(
343            "test".to_string(),
344            Uuid::from_u128(123456789),
345            Uuid::from_u128(123456789),
346        )
347        .await
348        .expect("Could not create a channel with ID \"123456789\".");
349        channel
350    }
351
352    #[test]
353    fn message_serialize() {
354        let message_struct = new_test_message(0);
355        let message_string =
356            r#"123456789,987654321,12222020,This is a test message.\nWith a newline.0"#.to_string();
357        assert_eq!(message_struct.to_string(), message_string);
358    }
359
360    #[test]
361    fn message_deserialize() {
362        let message_struct = new_test_message(0);
363        let message_string =
364            r#"123456789,987654321,12222020,This is a test message.\nWith a newline.0"#.to_string();
365        assert_eq!(Message::from_str(&message_string).unwrap(), message_struct);
366    }
367
368    #[tokio::test]
369    async fn new_channel() {
370        new_test_channel().await;
371        assert!(std::path::Path::new("data/hubs/data/00000000-0000-0000-0000-0000075bcd15/00000000-0000-0000-0000-0000075bcd15").exists());
372    }
373
374    #[tokio::test]
375    #[serial]
376    async fn add_message() {
377        let mut channel = new_test_channel().await;
378        let message = new_test_message(0);
379        channel.add_message(message.clone()).await.unwrap();
380        let mut file_string = tokio::fs::read_to_string(channel.get_current_file().await)
381            .await
382            .unwrap();
383        if let Some(string) = file_string.strip_suffix('\n') {
384            file_string = string.to_string();
385        }
386        assert_eq!(Message::from_str(&file_string).unwrap(), message);
387    }
388
389    #[tokio::test]
390    #[serial]
391    async fn get_last_n_messages() {
392        let mut channel = new_test_channel().await;
393        let message_0 = new_test_message(1);
394        let message_1 = new_test_message(2);
395        let message_2 = new_test_message(3);
396        channel.add_message(message_0.clone()).await.unwrap();
397        channel.add_message(message_1.clone()).await.unwrap();
398        channel.add_message(message_2.clone()).await.unwrap();
399        assert_eq!(
400            channel.get_last_messages(2).await,
401            vec![message_2, message_1]
402        );
403    }
404
405    #[tokio::test]
406    #[serial]
407    async fn get_message() {
408        let mut channel = new_test_channel().await;
409        let message_0 = new_test_message(4);
410        let message_1 = new_test_message(5);
411        let message_2 = new_test_message(6);
412        channel.add_message(message_0.clone()).await.unwrap();
413        channel.add_message(message_1.clone()).await.unwrap();
414        channel.add_message(message_2.clone()).await.unwrap();
415        assert_eq!(
416            channel
417                .get_message("00000000-0000-0000-0000-0000075bcd19".to_string())
418                .await
419                .unwrap()
420                .content,
421            "This is a test message.\nWith a newline.4".to_string()
422        );
423    }
424
425    #[tokio::test]
426    #[serial]
427    async fn find_messages_containing() {
428        let mut channel = new_test_channel().await;
429        let message_0 = new_test_message(7);
430        let message_1 = new_test_message(8);
431        let message_2 = new_test_message(9);
432        channel.add_message(message_0.clone()).await.unwrap();
433        channel.add_message(message_1.clone()).await.unwrap();
434        channel.add_message(message_2.clone()).await.unwrap();
435        assert_eq!(
436            channel
437                .find_messages_containing("newline.7".to_string(), true)
438                .await,
439            vec![message_0.clone()]
440        );
441        assert_eq!(
442            channel
443                .find_messages_containing("NeWlInE.7".to_string(), true)
444                .await,
445            vec![]
446        );
447        assert_eq!(
448            channel
449                .find_messages_containing("newline.8".to_string(), false)
450                .await,
451            vec![message_1.clone()]
452        );
453        assert_eq!(
454            channel
455                .find_messages_containing("NeWlInE.8".to_string(), false)
456                .await,
457            vec![message_1.clone()]
458        );
459        let all = vec![message_0, message_1, message_2];
460        assert_eq!(
461            channel
462                .find_messages_containing("This".to_string(), true)
463                .await,
464            all.clone()
465        );
466        assert_eq!(
467            channel
468                .find_messages_containing("this".to_string(), true)
469                .await,
470            vec![]
471        );
472        assert_eq!(
473            channel
474                .find_messages_containing("This".to_string(), false)
475                .await,
476            all.clone()
477        );
478        assert_eq!(
479            channel
480                .find_messages_containing("this".to_string(), false)
481                .await,
482            all.clone()
483        );
484    }
485
486    #[tokio::test]
487    #[serial]
488    async fn messages_between_dates() {
489        let mut channel = new_test_channel().await;
490        let message_0 = Message {
491            id: Uuid::from_u128(134),
492            sender: Uuid::from_u128(0987654321),
493            created: 1,
494            content: "This is a test message.\nWith a newline.".to_string(),
495        };
496        let message_1 = Message {
497            id: Uuid::from_u128(135),
498            sender: Uuid::from_u128(0987654321),
499            created: 2,
500            content: "This is a test message.\nWith a newline.".to_string(),
501        };
502        let message_2 = Message {
503            id: Uuid::from_u128(136),
504            sender: Uuid::from_u128(0987654321),
505            created: 3,
506            content: "This is a test message.\nWith a newline.".to_string(),
507        };
508        let message_3 = Message {
509            id: Uuid::from_u128(137),
510            sender: Uuid::from_u128(0987654321),
511            created: 4,
512            content: "This is a test message.\nWith a newline.".to_string(),
513        };
514        channel.add_message(message_0.clone()).await.unwrap();
515        channel.add_message(message_1.clone()).await.unwrap();
516        channel.add_message(message_2.clone()).await.unwrap();
517        channel.add_message(message_3.clone()).await.unwrap();
518        assert_eq!(
519            channel.get_messages(2, 3, true, 2).await,
520            vec![message_2.clone(), message_1.clone()]
521        );
522        assert_eq!(
523            channel.get_messages(2, 3, false, 2).await,
524            vec![message_1.clone(), message_2.clone()]
525        );
526        assert_eq!(channel.get_messages(2, 3, true, 1).await, vec![message_2]);
527        assert_eq!(channel.get_messages(2, 3, false, 1).await, vec![message_1]);
528    }
529}