arbor_cli/util/
backup.rs

1use std::error::Error;
2use std::sync::Arc;
3use tokio::fs::{File, OpenOptions};
4use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
5use tokio::sync::Mutex;
6
7// TODO: find out how to switch back to this path => "/var/lib/arbor/backup"
8// in production mode
9const 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}