diff options
Diffstat (limited to 'src/pathinfo.rs')
-rw-r--r-- | src/pathinfo.rs | 383 |
1 files changed, 268 insertions, 115 deletions
diff --git a/src/pathinfo.rs b/src/pathinfo.rs index be43b6e..641e7ef 100644 --- a/src/pathinfo.rs +++ b/src/pathinfo.rs | |||
@@ -1,31 +1,32 @@ | |||
1 | use std::{ | 1 | use std::{ |
2 | fmt::Display, | 2 | fmt::Display, |
3 | path::PathBuf, | 3 | fs::{create_dir_all, File}, |
4 | io::Read, | ||
5 | path::{Path, PathBuf}, | ||
4 | }; | 6 | }; |
5 | 7 | ||
6 | use serde::{Deserialize, Serialize}; | 8 | use serde::{Deserialize, Serialize}; |
7 | 9 | ||
8 | use crate::{ | 10 | use crate::{ |
9 | backup::BackupId, | 11 | backup::{Backup, BackupId}, |
10 | config::Config, | 12 | config::Config, |
11 | error::{Error, Result}, | 13 | error::{Error, Result}, |
12 | }; | 14 | }; |
13 | 15 | ||
14 | #[derive(Debug, Clone, Serialize, Deserialize)] | 16 | #[derive(Debug, Clone, Serialize, Deserialize)] |
15 | pub struct PathInfo { | 17 | pub struct PathInfo { |
16 | pub modified: bool, | ||
17 | pub is_file: bool, | 18 | pub is_file: bool, |
18 | rel_location: String, | 19 | rel_location: String, |
19 | location_root: LocationRoot, | 20 | location_root: LocationRoot, |
20 | last_modified: BackupId, | 21 | last_modified: Option<BackupId>, |
21 | children: Vec<PathInfo> | 22 | pub children: Vec<PathInfo>, |
22 | } | 23 | } |
23 | 24 | ||
24 | impl PathInfo { | 25 | impl PathInfo { |
25 | pub fn from_path(config: &Config, path: &str) -> Result<Self> { | 26 | pub fn from_path(config: &Config, path: &str) -> Result<Self> { |
26 | let locations = Self::parse_location(path, config)?; | 27 | let locations = Self::parse_location(path, config)?; |
27 | 28 | ||
28 | Ok(Self::handle_dir(config, &locations.0, &locations.1)?) | 29 | Self::handle_dir(config, &locations.0, &locations.1) |
29 | } | 30 | } |
30 | 31 | ||
31 | fn handle_dir( | 32 | fn handle_dir( |
@@ -36,54 +37,129 @@ impl PathInfo { | |||
36 | println!("Handling {rel_location}"); | 37 | println!("Handling {rel_location}"); |
37 | let path = Self::get_abs_path(&location_root.to_string(), rel_location); | 38 | let path = Self::get_abs_path(&location_root.to_string(), rel_location); |
38 | Ok(if path.is_dir() { | 39 | Ok(if path.is_dir() { |
39 | let mut modified = false; | 40 | let mut last_modified = None; |
40 | let mut children: Vec<PathInfo> = Vec::new(); | 41 | let mut children: Vec<PathInfo> = Vec::new(); |
41 | 42 | ||
42 | let paths = std::fs::read_dir(path).unwrap(); | 43 | let paths = std::fs::read_dir(path).unwrap(); |
43 | for path in paths { | 44 | for path in paths { |
44 | let pathstr = path.unwrap().path().to_string_lossy().to_string(); | 45 | let pathstr = path.unwrap().path().to_string_lossy().to_string(); |
45 | let root = format!("{}/", location_root.to_string()); | 46 | let root = format!("{location_root}/"); |
46 | let Some(rl) = pathstr.split_once(&root) else { | 47 | let Some(rl) = pathstr.split_once(&root) else { |
47 | panic!("HUH"); | 48 | panic!("HUH"); |
48 | }; | 49 | }; |
49 | let handle = Self::handle_dir(config, rl.1, location_root)?; | 50 | let handle = Self::handle_dir(config, rl.1, location_root)?; |
50 | if handle.modified { | 51 | if handle.last_modified.is_some() { |
51 | modified = true; | 52 | // FIX: Check if new last modified is newer than old one |
53 | last_modified = handle.last_modified.clone(); | ||
52 | }; | 54 | }; |
53 | children.push(handle); | 55 | children.push(handle); |
54 | } | 56 | } |
55 | Self { | 57 | Self { |
56 | modified, | ||
57 | is_file: false, | 58 | is_file: false, |
58 | rel_location: rel_location.to_string(), | 59 | rel_location: rel_location.to_string(), |
59 | location_root: location_root.clone(), | 60 | location_root: location_root.clone(), |
60 | last_modified: "".to_string(), | 61 | last_modified, |
61 | children | 62 | children, |
62 | } | 63 | } |
63 | } else { | 64 | } else { |
64 | Self::from_file(rel_location, location_root.clone())? | 65 | Self::from_file(config, rel_location, location_root)? |
65 | }) | 66 | }) |
66 | } | 67 | } |
67 | 68 | ||
68 | fn from_file(rel_location: &str, location_root: LocationRoot) -> Result<Self> { | 69 | fn from_file( |
69 | println!("From file {rel_location}"); | 70 | config: &Config, |
71 | rel_location: &str, | ||
72 | location_root: &LocationRoot, | ||
73 | ) -> Result<Self> { | ||
74 | let last_modified = Self::compare_to_last_modified(config, location_root, rel_location)?; | ||
70 | 75 | ||
71 | let modified = false; | 76 | println!("From file {rel_location} ({:?})", last_modified); |
72 | 77 | ||
73 | Ok(Self { | 78 | Ok(Self { |
74 | rel_location: rel_location.to_string(), | 79 | rel_location: rel_location.to_string(), |
75 | location_root, | 80 | location_root: location_root.clone(), |
76 | modified, | 81 | last_modified, |
77 | last_modified: "".to_string(), | ||
78 | is_file: true, | 82 | is_file: true, |
79 | children: Vec::new() | 83 | children: Vec::new(), |
80 | }) | 84 | }) |
81 | } | 85 | } |
82 | 86 | ||
87 | pub fn compare_to_last_modified( | ||
88 | config: &Config, | ||
89 | location_root: &LocationRoot, | ||
90 | rel_location: &str, | ||
91 | ) -> Result<Option<String>> { | ||
92 | let Some(last_backup) = Backup::get_last(config)? else { | ||
93 | // First Backup | ||
94 | return Ok(None); | ||
95 | }; | ||
96 | |||
97 | |||
98 | let files = last_backup.files.clone(); | ||
99 | let last_file_opt = files.iter().find(|file| file.rel_location == rel_location && file.location_root == *location_root); | ||
100 | |||
101 | let Some(last_file) = last_file_opt else { | ||
102 | // File didn't exist last Backup | ||
103 | println!("File didn't exist last Backup"); | ||
104 | return Ok(None); | ||
105 | }; | ||
106 | |||
107 | let modified_backup = if let Some(modified_backup_id) = last_file.last_modified.clone() { | ||
108 | Backup::from_index(config, modified_backup_id)? | ||
109 | } else { | ||
110 | last_backup | ||
111 | }; | ||
112 | |||
113 | let old_path = modified_backup.get_absolute_file_location(config, &last_file.rel_location); | ||
114 | let new_path = format!("{location_root}/{rel_location}"); | ||
115 | |||
116 | let mut old = File::open(old_path)?; | ||
117 | let mut new = File::open(new_path)?; | ||
118 | |||
119 | let old_len = old.metadata()?.len(); | ||
120 | let new_len = new.metadata()?.len(); | ||
121 | if old_len != new_len { | ||
122 | return Ok(None); | ||
123 | } | ||
124 | |||
125 | let mut old_content = String::new(); | ||
126 | old.read_to_string(&mut old_content)?; | ||
127 | let mut new_content = String::new(); | ||
128 | new.read_to_string(&mut new_content)?; | ||
129 | if old_content != new_content { | ||
130 | return Ok(None); | ||
131 | } | ||
132 | |||
133 | Ok(Some(modified_backup.id.clone())) | ||
134 | } | ||
135 | |||
83 | pub fn get_absolute_path(&self) -> PathBuf { | 136 | pub fn get_absolute_path(&self) -> PathBuf { |
84 | Self::get_abs_path(&self.location_root.to_string(), &self.rel_location) | 137 | Self::get_abs_path(&self.location_root.to_string(), &self.rel_location) |
85 | } | 138 | } |
86 | 139 | ||
140 | pub fn save(&self, backup_root: &str) -> Result<()> { | ||
141 | if self.last_modified.is_some() { | ||
142 | return Ok(()); | ||
143 | } | ||
144 | println!("Save File {:?}", self.rel_location); | ||
145 | if !self.is_file { | ||
146 | for child in &self.children { | ||
147 | child.save(backup_root)?; | ||
148 | } | ||
149 | } else { | ||
150 | let new_path = format!("{}/{}", backup_root, self.rel_location); | ||
151 | // println!("New Path: {new_path}"); | ||
152 | // println!("Old Path: {:?}", self.get_absolute_path()); | ||
153 | let np = Path::new(&new_path); | ||
154 | if let Some(parent) = np.parent() { | ||
155 | create_dir_all(parent)?; | ||
156 | } | ||
157 | std::fs::copy(self.get_absolute_path(), new_path)?; | ||
158 | }; | ||
159 | |||
160 | Ok(()) | ||
161 | } | ||
162 | |||
87 | fn get_abs_path(location_root: &str, rel_location: &str) -> PathBuf { | 163 | fn get_abs_path(location_root: &str, rel_location: &str) -> PathBuf { |
88 | let path = format!("{}/{}", location_root, rel_location); | 164 | let path = format!("{}/{}", location_root, rel_location); |
89 | PathBuf::from(path) | 165 | PathBuf::from(path) |
@@ -150,97 +226,174 @@ impl LocationRoot { | |||
150 | } | 226 | } |
151 | } | 227 | } |
152 | 228 | ||
153 | // #[cfg(test)] | 229 | #[cfg(test)] |
154 | // mod tests { | 230 | mod tests { |
155 | // use crate::{ | 231 | use std::{ |
156 | // config::Config, | 232 | fs::{create_dir_all, remove_dir_all, File}, |
157 | // error::{Error, Result}, | 233 | io::Write, |
158 | // pathinfo::PathInfo, | 234 | }; |
159 | // }; | 235 | |
160 | // | 236 | use crate::{ |
161 | // use super::LocationRoot; | 237 | backup::Backup, |
162 | // | 238 | config::Config, |
163 | // #[test] | 239 | error::{Error, Result}, |
164 | // fn from_op_str() -> Result<()> { | 240 | packages::{pacman::Pacman, PackageManager}, |
165 | // let mut config = Config::default(); | 241 | }; |
166 | // config | 242 | |
167 | // .custom_directories | 243 | use super::LocationRoot; |
168 | // .insert("test".to_string(), "/usr/local/test".to_string()); | 244 | use super::PathInfo; |
169 | // | 245 | |
170 | // let mut values: Vec<(&str, Result<LocationRoot>)> = Vec::new(); | 246 | #[test] |
171 | // values.push(("u:test", Ok(LocationRoot::User("test".to_string())))); | 247 | fn from_op_str() -> Result<()> { |
172 | // values.push(("s:", Ok(LocationRoot::SystemSettings))); | 248 | let mut config = Config::default(); |
173 | // values.push(("r:", Ok(LocationRoot::Root))); | 249 | config |
174 | // values.push(( | 250 | .custom_directories |
175 | // "c:test", | 251 | .insert("test".to_string(), "/usr/local/test".to_string()); |
176 | // Ok(LocationRoot::Custom("/usr/local/test".to_string())), | 252 | |
177 | // )); | 253 | let mut values_ok: Vec<(&str, LocationRoot)> = Vec::new(); |
178 | // values.push(("c:rest", Err(Error::CustomDirectory("rest".to_string())))); | 254 | values_ok.push(("u:test", LocationRoot::User("test".to_string()))); |
179 | // values.push(("t:test/", Err(Error::InvalidIndex("t".to_string())))); | 255 | values_ok.push(("s:", LocationRoot::SystemSettings)); |
180 | // values.push(( | 256 | values_ok.push(("r:", LocationRoot::Root)); |
181 | // "test:test/usr", | 257 | values_ok.push(( |
182 | // Err(Error::InvalidIndex("test".to_string())), | 258 | "c:test", |
183 | // )); | 259 | LocationRoot::Custom("/usr/local/test".to_string()), |
184 | // values.push(("/usr/local/test", Err(Error::NoIndex))); | 260 | )); |
185 | // values.push(("c/usr/local/test", Err(Error::NoIndex))); | 261 | |
186 | // | 262 | for value in values_ok { |
187 | // for value in values { | 263 | println!("Testing {value:?}"); |
188 | // print!("Testing {value:?}"); | 264 | assert_eq!(LocationRoot::from_op_str(value.0, &config)?, value.1); |
189 | // assert_eq!(LocationRoot::from_op_str(value.0, &config), value.1); | 265 | println!("\x1B[FTesting {value:?} ✓"); |
190 | // println!("\rTesting {value:?} ✓"); | 266 | } |
191 | // } | 267 | |
192 | // | 268 | let mut values_err: Vec<(&str, String)> = Vec::new(); |
193 | // Ok(()) | 269 | values_err.push(( |
194 | // } | 270 | "c:rest", |
195 | // | 271 | Error::CustomDirectory("rest".to_string()).to_string(), |
196 | // #[test] | 272 | )); |
197 | // fn parse_location() -> Result<()> { | 273 | values_err.push(("t:test/", Error::InvalidIndex("t".to_string()).to_string())); |
198 | // let mut config = Config::default(); | 274 | values_err.push(( |
199 | // config.user.push("test".to_string()); | 275 | "test:test/usr", |
200 | // config | 276 | Error::InvalidIndex("test".to_string()).to_string(), |
201 | // .custom_directories | 277 | )); |
202 | // .insert("test".to_string(), "/usr/local/test".to_string()); | 278 | values_err.push(("/usr/local/test", Error::NoIndex.to_string())); |
203 | // | 279 | values_err.push(("c/usr/local/test", Error::NoIndex.to_string())); |
204 | // let mut values: Vec<(&str, Result<(String, LocationRoot)>)> = Vec::new(); | 280 | |
205 | // values.push(( | 281 | for value in values_err { |
206 | // "~/.config/nvim", | 282 | println!("Testing {value:?}"); |
207 | // Ok(( | 283 | assert_eq!( |
208 | // ".config/nvim".to_string(), | 284 | LocationRoot::from_op_str(value.0, &config) |
209 | // LocationRoot::User("test".to_string()), | 285 | .err() |
210 | // )), | 286 | .unwrap() |
211 | // )); | 287 | .to_string(), |
212 | // values.push(( | 288 | value.1 |
213 | // "u:test/.config/nvim", | 289 | ); |
214 | // Ok(( | 290 | println!("\x1B[FTesting {value:?} ✓"); |
215 | // ".config/nvim".to_string(), | 291 | } |
216 | // LocationRoot::User("test".to_string()), | 292 | |
217 | // )), | 293 | Ok(()) |
218 | // )); | 294 | } |
219 | // values.push(( | 295 | |
220 | // "r:/.config/nvim", | 296 | #[test] |
221 | // Ok((".config/nvim".to_string(), LocationRoot::Root)), | 297 | fn parse_location() -> Result<()> { |
222 | // )); | 298 | let mut config = Config::default(); |
223 | // values.push(( | 299 | config.user.push("test".to_string()); |
224 | // "r:/.config/nvim", | 300 | config |
225 | // Ok((".config/nvim".to_string(), LocationRoot::Root)), | 301 | .custom_directories |
226 | // )); | 302 | .insert("test".to_string(), "/usr/local/test".to_string()); |
227 | // values.push(( | 303 | |
228 | // "s:/.config/nvim", | 304 | let mut values_ok: Vec<(&str, (String, LocationRoot))> = Vec::new(); |
229 | // Ok((".config/nvim".to_string(), LocationRoot::SystemSettings)), | 305 | values_ok.push(( |
230 | // )); | 306 | "~/.config/nvim", |
231 | // values.push(( | 307 | ( |
232 | // "c:test/.config/nvim", | 308 | ".config/nvim".to_string(), |
233 | // Ok(( | 309 | LocationRoot::User("test".to_string()), |
234 | // ".config/nvim".to_string(), | 310 | ), |
235 | // LocationRoot::Custom("/usr/local/test".to_string()), | 311 | )); |
236 | // )), | 312 | values_ok.push(( |
237 | // )); | 313 | "u:test/.config/nvim", |
238 | // | 314 | ( |
239 | // for value in values { | 315 | ".config/nvim".to_string(), |
240 | // print!("Testing {value:?}"); | 316 | LocationRoot::User("test".to_string()), |
241 | // assert_eq!(PathInfo::parse_location(&value.0, &config), value.1); | 317 | ), |
242 | // println!("\rTesting {value:?} ✓"); | 318 | )); |
243 | // } | 319 | values_ok.push(( |
244 | // Ok(()) | 320 | "r:/.config/nvim", |
245 | // } | 321 | (".config/nvim".to_string(), LocationRoot::Root), |
246 | // } | 322 | )); |
323 | values_ok.push(( | ||
324 | "r:/.config/nvim", | ||
325 | (".config/nvim".to_string(), LocationRoot::Root), | ||
326 | )); | ||
327 | values_ok.push(( | ||
328 | "s:/.config/nvim", | ||
329 | (".config/nvim".to_string(), LocationRoot::SystemSettings), | ||
330 | )); | ||
331 | values_ok.push(( | ||
332 | "c:test/.config/nvim", | ||
333 | ( | ||
334 | ".config/nvim".to_string(), | ||
335 | LocationRoot::Custom("/usr/local/test".to_string()), | ||
336 | ), | ||
337 | )); | ||
338 | |||
339 | for value in values_ok { | ||
340 | print!("Testing {value:?}"); | ||
341 | assert_eq!(PathInfo::parse_location(&value.0, &config)?, value.1); | ||
342 | println!("\x1B[FTesting {value:?} ✓"); | ||
343 | } | ||
344 | Ok(()) | ||
345 | } | ||
346 | |||
347 | #[test] | ||
348 | fn compare_to_last_modified() -> color_eyre::Result<()> { | ||
349 | let mut config = Config::default(); | ||
350 | config.root = "./backup-test".to_string(); | ||
351 | config | ||
352 | .directories | ||
353 | .push("u:fx/code/proj/fxbaup/backup-test-dir".to_string()); | ||
354 | |||
355 | create_dir_all("./backup-test-dir")?; | ||
356 | let mut f = File::create("./backup-test-dir/size.txt")?; | ||
357 | f.write_all("unmodified".as_bytes())?; | ||
358 | let mut f = File::create("./backup-test-dir/content.txt")?; | ||
359 | f.write_all("unmodified".as_bytes())?; | ||
360 | let mut f = File::create("./backup-test-dir/nothing.txt")?; | ||
361 | f.write_all("unmodified".as_bytes())?; | ||
362 | |||
363 | let pacman = Pacman; | ||
364 | let pkgs = pacman.get_installed()?; | ||
365 | let backup = Backup::create(&config, pkgs)?; | ||
366 | backup.save(&config)?; | ||
367 | |||
368 | let mut f = File::create("./backup-test-dir/size.txt")?; | ||
369 | f.write_all("modified".as_bytes())?; | ||
370 | let mut f = File::create("./backup-test-dir/content.txt")?; | ||
371 | f.write_all("unmodefied".as_bytes())?; | ||
372 | |||
373 | let pi = PathInfo::from_path(&config, "u:fx/code/proj/fxbaup/backup-test-dir")?; | ||
374 | |||
375 | let last_backup = Backup::get_last(&config)?.unwrap(); | ||
376 | for file in pi.children { | ||
377 | println!("test rel: {}", file.rel_location); | ||
378 | let res = if file.rel_location == "code/proj/fxbaup/backup-test-dir/nothing.txt" { | ||
379 | Some(last_backup.id.clone()) | ||
380 | } else { | ||
381 | None | ||
382 | }; | ||
383 | println!("Testing {file:?}"); | ||
384 | assert_eq!( | ||
385 | PathInfo::compare_to_last_modified( | ||
386 | &config, | ||
387 | &file.location_root, | ||
388 | &file.rel_location | ||
389 | )?, | ||
390 | res | ||
391 | ); | ||
392 | println!("\x1B[FTesting {file:?} ✓"); | ||
393 | } | ||
394 | |||
395 | remove_dir_all("./backup-test-dir")?; | ||
396 | remove_dir_all("./backup-test")?; | ||
397 | Ok(()) | ||
398 | } | ||
399 | } | ||