summaryrefslogtreecommitdiff
path: root/src/pathinfo.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/pathinfo.rs')
-rw-r--r--src/pathinfo.rs383
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 @@
1use std::{ 1use 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
6use serde::{Deserialize, Serialize}; 8use serde::{Deserialize, Serialize};
7 9
8use crate::{ 10use 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)]
15pub struct PathInfo { 17pub 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
24impl PathInfo { 25impl 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 { 230mod 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}