1use std::error::Error;
2use std::sync::Arc;
3use tokio::fs::{File, OpenOptions};
4use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
5use tokio::sync::Mutex;
6
7const BACKUP_FILE_PATH: &str = "/tmp/arbor/backup";
10
11pub struct Backup {
12 pub file_path: String,
13 file: Arc<Mutex<File>>,
14}
15
16impl Backup {
17 pub async fn build(file_path: Option<&str>) -> Result<Self, Box<dyn Error>> {
18 let path = std::path::Path::new(match file_path {
19 Some(path) => path,
20 None => BACKUP_FILE_PATH,
21 });
22
23 if !path.exists() {
24 let prefix = path.parent().unwrap();
25 std::fs::create_dir_all(prefix).unwrap();
26 }
27
28 let file = OpenOptions::new()
29 .append(true)
30 .read(true)
31 .create(true)
32 .open(path)
33 .await
34 .expect("Could not open file");
35
36 Ok(Self {
37 file_path: path.to_str().unwrap().to_owned(),
38 file: Arc::new(Mutex::new(file)),
39 })
40 }
41
42 pub async fn save_data(&self, words: Vec<String>) -> Result<(), Box<dyn Error>> {
43 let mut file = self.file.lock().await;
44
45 for word in words {
46 file.write_all(word.as_bytes()).await?;
47 file.write_all(b"\n").await?;
48 }
49
50 Ok(())
51 }
52
53 pub async fn load_data(&self) -> Result<Vec<String>, Box<dyn Error>> {
54 let mut file = self.file.lock().await;
55
56 file.seek(std::io::SeekFrom::Start(0)).await?;
57
58 let mut contents = String::new();
59 file.read_to_string(&mut contents).await?;
60
61 let lines: Vec<String> = contents.lines().map(|line| line.to_string()).collect();
62
63 Ok(lines)
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 async fn init_backup(file_path: Option<&str>) -> Backup {
72 return Backup::build(file_path).await.unwrap();
73 }
74
75 #[tokio::test]
76 async fn it_builds_backup_file() {
77 let backup = init_backup(Some("file")).await;
78 let file = backup.file.lock().await;
79
80 assert!(file.metadata().await.unwrap().is_file());
81 }
82
83 #[tokio::test]
84 async fn it_saves_data() {
85 let backup = init_backup(Some("file")).await;
86
87 let words = Vec::from([
88 "hello".to_string(),
89 "hellium".to_string(),
90 "hundred".to_string(),
91 ]);
92
93 backup.save_data(words.clone()).await.unwrap();
94
95 let data_from_file = backup.load_data().await.unwrap();
96
97 assert_eq!(data_from_file, words);
98
99 std::fs::remove_file(backup.file_path).unwrap();
100 }
101}