mtracker/media/
repo.rs

1use anyhow::{anyhow, Result};
2use std::{fs, path};
3extern crate dirs;
4
5use crate::media;
6
7pub struct Repo {
8    pub path: path::PathBuf,
9    items: Vec<media::Media>,
10}
11
12impl Repo {
13    pub fn new(path: &path::Path) -> Result<Self> {
14        let mut repo = Repo {
15            path: path.to_path_buf(),
16            items: vec![],
17        };
18        repo.read()?;
19        Ok(repo)
20    }
21
22    pub fn get(&mut self, handle: &media::handle::Handle) -> Option<&mut media::Media> {
23        self.items.iter_mut().find(|m| m.matches_handle(handle))
24    }
25
26    pub fn get_or_create(&mut self, handle: &media::handle::Handle) -> Result<&mut media::Media> {
27        if self.get(handle).is_none() {
28            self.add(media::Media::from_handle(handle))?;
29            println!("Added new item: {handle}");
30        }
31
32        Ok(self.get(handle).unwrap())
33    }
34
35    pub fn get_all(&self) -> Vec<&media::Media> {
36        self.items.iter().collect()
37    }
38
39    pub fn update(
40        &mut self,
41        handle: &media::handle::Handle,
42        f: impl FnOnce(&mut media::Media),
43    ) -> Result<()> {
44        match self.get(handle) {
45            Some(item) => {
46                f(item);
47                Ok(())
48            }
49            None => Err(anyhow!("item not found: {handle}")),
50        }
51    }
52
53    pub fn add(&mut self, item: media::Media) -> Result<()> {
54        self.items.push(item);
55        Ok(())
56    }
57
58    pub fn remove_by_handle(&mut self, handle: &media::handle::Handle) -> Result<()> {
59        match self.items.iter().position(|m| m.matches_handle(handle)) {
60            Some(index) => {
61                self.items.swap_remove(index);
62                Ok(())
63            }
64            None => Err(anyhow!("item not found: {}", &handle)),
65        }
66    }
67
68    // Read all items from file into memory
69    fn read(&mut self) -> Result<()> {
70        let file_content = fs::read_to_string(&self.path).unwrap_or_default();
71
72        // Get blocks of text separated by empty lines
73        let blocks = file_content
74            .split("\n\n")
75            .filter(|b| !b.is_empty())
76            .map(str::trim);
77
78        // Parse blocks of text into media items
79        for block in blocks {
80            self.items.push(media::Media::from_db_entry(block)?);
81        }
82
83        Ok(())
84    }
85
86    /// Write all items to file
87    pub fn write(&self) -> Result<()> {
88        // Create path if it doesn't exist
89        std::fs::create_dir_all(self.path.parent().unwrap())?;
90
91        let mut output = String::new();
92        for entry in self.items.iter().map(media::Media::to_db_entry) {
93            output += &entry;
94            output += "\n\n";
95        }
96        Ok(fs::write(&self.path, output.trim())?)
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn reads() {
106        let mut path = std::env::temp_dir();
107        path.push("mtracker_test_reads.txt");
108
109        fs::write(
110            &path,
111            "Forrest Gump
112year: 1994
113
114Alien
115year: 1979
116
117
118Aliens
119year: 1986
120
121
122
123Alien 3
124year: 1992
125
126
127
128
129The Terminator
130year: 1984
131",
132        )
133        .unwrap();
134
135        let repo = Repo::new(&path).unwrap();
136        let items = repo.get_all();
137
138        assert_eq!(items[0].name, "Forrest Gump");
139        assert_eq!(items[0].year, Some(1994));
140
141        assert_eq!(items[1].name, "Alien");
142        assert_eq!(items[1].year, Some(1979));
143
144        assert_eq!(items[2].name, "Aliens");
145        assert_eq!(items[2].year, Some(1986));
146
147        assert_eq!(items[3].name, "Alien 3");
148        assert_eq!(items[3].year, Some(1992));
149
150        assert_eq!(items[4].name, "The Terminator");
151        assert_eq!(items[4].year, Some(1984));
152
153        fs::remove_file(&path).ok();
154    }
155
156    #[test]
157    fn writes() {
158        let mut path = std::env::temp_dir();
159        path.push("mtracker_test_writes.txt");
160        fs::remove_file(&path).ok();
161
162        let mut repo = Repo::new(&path).unwrap();
163        repo.add(media::Media::new("Forrest Gump", Some(1994))).ok();
164        repo.add(media::Media::new("Alien", Some(1979))).ok();
165        repo.write().unwrap();
166
167        assert_eq!(
168            fs::read_to_string(&path).unwrap(),
169            "Forrest Gump
170year: 1994
171
172Alien
173year: 1979"
174        );
175        fs::remove_file(&path).ok();
176    }
177}