diff options
author | fxqnlr <[email protected]> | 2024-09-08 17:21:27 +0200 |
---|---|---|
committer | fxqnlr <[email protected]> | 2024-09-08 17:21:27 +0200 |
commit | a8d1be9536bce6d6be2cf1586c8bac049e820d31 (patch) | |
tree | 05c5c78c11f4506ba2eaa8a0751d3d896cfb81e9 /src | |
parent | 695556c3441f5ffd40c35387a5b45e4459684c2c (diff) | |
download | arbs-a8d1be9536bce6d6be2cf1586c8bac049e820d31.tar arbs-a8d1be9536bce6d6be2cf1586c8bac049e820d31.tar.gz arbs-a8d1be9536bce6d6be2cf1586c8bac049e820d31.zip |
save files, real last modified check (doesn't work correctly)
Diffstat (limited to 'src')
-rw-r--r-- | src/backup.rs | 126 | ||||
-rw-r--r-- | src/config.rs | 6 | ||||
-rw-r--r-- | src/error.rs | 7 | ||||
-rw-r--r-- | src/main.rs | 15 | ||||
-rw-r--r-- | src/packages.rs | 5 | ||||
-rw-r--r-- | src/packages/pacman.rs | 14 | ||||
-rw-r--r-- | src/packages/portage.rs | 13 | ||||
-rw-r--r-- | src/pathinfo.rs | 383 |
8 files changed, 387 insertions, 182 deletions
diff --git a/src/backup.rs b/src/backup.rs index 8cc94f1..a643cb2 100644 --- a/src/backup.rs +++ b/src/backup.rs | |||
@@ -5,7 +5,6 @@ use std::{ | |||
5 | time::{SystemTime, UNIX_EPOCH}, | 5 | time::{SystemTime, UNIX_EPOCH}, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | use gethostname::gethostname; | ||
9 | use serde::{Deserialize, Serialize}; | 8 | use serde::{Deserialize, Serialize}; |
10 | use uuid::Uuid; | 9 | use uuid::Uuid; |
11 | 10 | ||
@@ -20,10 +19,11 @@ pub type BackupId = String; | |||
20 | 19 | ||
21 | #[derive(Debug, Serialize, Deserialize)] | 20 | #[derive(Debug, Serialize, Deserialize)] |
22 | pub struct Backup { | 21 | pub struct Backup { |
23 | id: String, | 22 | pub id: String, |
24 | timestamp: u64, | 23 | timestamp: u64, |
25 | packages: Vec<Package>, | 24 | packages: Vec<Package>, |
26 | files: Vec<PathInfo>, | 25 | pub files: Vec<PathInfo>, |
26 | device: String, | ||
27 | } | 27 | } |
28 | 28 | ||
29 | impl Backup { | 29 | impl Backup { |
@@ -33,63 +33,82 @@ impl Backup { | |||
33 | files.push(PathInfo::from_path(config, dir)?); | 33 | files.push(PathInfo::from_path(config, dir)?); |
34 | } | 34 | } |
35 | Ok(Self { | 35 | Ok(Self { |
36 | // UUID not really needed, maybe a shorter hash | 36 | // TODO: UUID not really needed, maybe a shorter hash |
37 | id: Uuid::new_v4().to_string(), | 37 | id: Uuid::new_v4().to_string(), |
38 | timestamp: Self::get_timestamp(), | 38 | timestamp: Self::get_timestamp(), |
39 | packages, | 39 | packages, |
40 | files, | 40 | files, |
41 | device: config.device.clone(), | ||
41 | }) | 42 | }) |
42 | } | 43 | } |
43 | 44 | ||
44 | pub fn save(&self, config: &Config) -> Result<()> { | 45 | pub fn save(&self, config: &Config) -> Result<()> { |
45 | let rel_location = format!( | 46 | println!("Save Backup {:?}", self.get_location(config)); |
46 | "{}_{}", | 47 | // println!("{self:#?}"); |
47 | gethostname() | 48 | self.get_location(config).append_to_root(config)?; |
48 | .into_string() | ||
49 | .map_err(|_| Error::InvalidOsString)?, | ||
50 | Self::get_timestamp() | ||
51 | ); | ||
52 | |||
53 | let bl = BackupLocation { | ||
54 | id: self.id.to_string(), | ||
55 | rel_location, | ||
56 | }; | ||
57 | |||
58 | Self::append_to_root_index(config, bl.clone())?; | ||
59 | 49 | ||
60 | let backup_root = format!("{}/{}", config.root, bl.rel_location); | 50 | let backup_root = self.get_location(config).get_absolute_dir(config); |
61 | create_dir_all(&backup_root).unwrap(); | 51 | create_dir_all(&backup_root).unwrap(); |
62 | let path = format!("{}/index.json", backup_root); | 52 | let path = format!("{}/index.json", backup_root); |
63 | let mut f = File::create(path).unwrap(); | 53 | let mut f = File::create(path).unwrap(); |
64 | f.write_all(&serde_json::to_vec(self).unwrap()).unwrap(); | 54 | f.write_all(&serde_json::to_vec(self).unwrap()).unwrap(); |
65 | 55 | ||
56 | for path in &self.files { | ||
57 | path.save(&backup_root)?; | ||
58 | } | ||
59 | |||
66 | Ok(()) | 60 | Ok(()) |
67 | } | 61 | } |
68 | 62 | ||
69 | pub fn get_index(config: &Config, id: Option<BackupId>) -> Result<Self> { | 63 | pub fn get_last(config: &Config) -> Result<Option<Self>> { |
70 | let backup_index_root = format!("{}/index.json", config.root); | 64 | let backup_index_root = format!("{}/index.json", config.root); |
71 | let list: Vec<BackupLocation> = Self::get_json_content(&backup_index_root)?; | 65 | let list: Vec<BackupLocation> = match Self::get_json_content(&backup_index_root) { |
72 | println!("{list:#?}"); | 66 | Ok(list) => list, |
73 | 67 | Err(err) => { | |
74 | let index_loc = if let Some(id) = id { | 68 | if err.to_string() == "io: No such file or directory (os error 2)" { |
75 | list.iter() | 69 | return Ok(None); |
76 | .find(|bl| bl.id == id) | 70 | }; |
77 | .ok_or(Error::BackupNotFound)? | 71 | return Err(err); |
78 | .rel_location | 72 | } |
79 | .clone() | ||
80 | } else { | ||
81 | list.last() | ||
82 | .ok_or(Error::BackupNotFound)? | ||
83 | .rel_location | ||
84 | .clone() | ||
85 | }; | 73 | }; |
86 | 74 | ||
75 | Ok(Some(Self::from_index( | ||
76 | config, | ||
77 | list.last().ok_or(Error::BackupNotFound)?.id.clone(), | ||
78 | )?)) | ||
79 | } | ||
80 | |||
81 | pub fn from_index(config: &Config, id: BackupId) -> Result<Self> { | ||
82 | let backup_index_root = format!("{}/index.json", config.root); | ||
83 | let list: Vec<BackupLocation> = Self::get_json_content(&backup_index_root)?; | ||
84 | let index_loc = list | ||
85 | .iter() | ||
86 | .find(|bl| bl.id == id) | ||
87 | .ok_or(Error::BackupNotFound)? | ||
88 | .rel_location | ||
89 | .clone(); | ||
90 | |||
87 | let path = format!("{}/{index_loc}/index.json", config.root); | 91 | let path = format!("{}/{index_loc}/index.json", config.root); |
88 | let index_file: Self = Self::get_json_content(&path)?; | 92 | let index_file: Self = Self::get_json_content(&path)?; |
89 | 93 | ||
90 | Ok(index_file) | 94 | Ok(index_file) |
91 | } | 95 | } |
92 | 96 | ||
97 | pub fn get_location(&self, config: &Config) -> BackupLocation { | ||
98 | let rel_location = format!("{}_{}", config.device, self.timestamp); | ||
99 | |||
100 | BackupLocation { | ||
101 | id: self.id.to_string(), | ||
102 | rel_location, | ||
103 | } | ||
104 | } | ||
105 | |||
106 | pub fn get_absolute_file_location(&self, config: &Config, rel_location: &str) -> String { | ||
107 | let loc = self.get_location(config).get_absolute_dir(config); | ||
108 | |||
109 | format!("{}/{}", loc, rel_location) | ||
110 | } | ||
111 | |||
93 | fn get_json_content<T: for<'a> Deserialize<'a>>(path: &str) -> Result<T> { | 112 | fn get_json_content<T: for<'a> Deserialize<'a>>(path: &str) -> Result<T> { |
94 | let mut file = File::open(path)?; | 113 | let mut file = File::open(path)?; |
95 | let mut content = String::new(); | 114 | let mut content = String::new(); |
@@ -97,7 +116,26 @@ impl Backup { | |||
97 | Ok(serde_json::from_str(&content)?) | 116 | Ok(serde_json::from_str(&content)?) |
98 | } | 117 | } |
99 | 118 | ||
100 | fn append_to_root_index(config: &Config, new_backup: BackupLocation) -> Result<()> { | 119 | fn get_timestamp() -> u64 { |
120 | SystemTime::now() | ||
121 | .duration_since(UNIX_EPOCH) | ||
122 | .unwrap() | ||
123 | .as_secs() | ||
124 | } | ||
125 | } | ||
126 | |||
127 | #[derive(Debug, Clone, Serialize, Deserialize)] | ||
128 | pub struct BackupLocation { | ||
129 | id: BackupId, | ||
130 | rel_location: String, | ||
131 | } | ||
132 | |||
133 | impl BackupLocation { | ||
134 | pub fn get_absolute_dir(&self, config: &Config) -> String { | ||
135 | format!("{}/{}", config.root, self.rel_location) | ||
136 | } | ||
137 | |||
138 | pub fn append_to_root(&self, config: &Config) -> Result<()> { | ||
101 | let backup_index_root = format!("{}/index.json", config.root); | 139 | let backup_index_root = format!("{}/index.json", config.root); |
102 | let path = PathBuf::from(&backup_index_root); | 140 | let path = PathBuf::from(&backup_index_root); |
103 | if path.exists() { | 141 | if path.exists() { |
@@ -107,27 +145,15 @@ impl Backup { | |||
107 | let mut loc: Vec<BackupLocation> = serde_json::from_str(&content)?; | 145 | let mut loc: Vec<BackupLocation> = serde_json::from_str(&content)?; |
108 | 146 | ||
109 | let mut f = File::create(path)?; | 147 | let mut f = File::create(path)?; |
110 | loc.push(new_backup); | 148 | loc.push(self.clone()); |
111 | 149 | ||
112 | f.write_all(&serde_json::to_vec(&loc)?)?; | 150 | f.write_all(&serde_json::to_vec(&loc)?)?; |
113 | } else { | 151 | } else { |
152 | create_dir_all(&config.root).unwrap(); | ||
114 | let mut f = File::create(backup_index_root)?; | 153 | let mut f = File::create(backup_index_root)?; |
115 | f.write_all(&serde_json::to_vec(&vec![new_backup])?)?; | 154 | f.write_all(&serde_json::to_vec(&vec![self])?)?; |
116 | }; | 155 | }; |
117 | 156 | ||
118 | Ok(()) | 157 | Ok(()) |
119 | } | 158 | } |
120 | |||
121 | fn get_timestamp() -> u64 { | ||
122 | SystemTime::now() | ||
123 | .duration_since(UNIX_EPOCH) | ||
124 | .unwrap() | ||
125 | .as_secs() | ||
126 | } | ||
127 | } | ||
128 | |||
129 | #[derive(Debug, Clone, Serialize, Deserialize)] | ||
130 | struct BackupLocation { | ||
131 | id: BackupId, | ||
132 | rel_location: String, | ||
133 | } | 159 | } |
diff --git a/src/config.rs b/src/config.rs index 625118a..439c17c 100644 --- a/src/config.rs +++ b/src/config.rs | |||
@@ -7,7 +7,8 @@ pub struct Config { | |||
7 | pub root: String, | 7 | pub root: String, |
8 | pub user: Vec<String>, | 8 | pub user: Vec<String>, |
9 | pub directories: Vec<String>, | 9 | pub directories: Vec<String>, |
10 | pub custom_directories: Map<String, String> | 10 | pub custom_directories: Map<String, String>, |
11 | pub device: String, | ||
11 | } | 12 | } |
12 | 13 | ||
13 | impl Default for Config { | 14 | impl Default for Config { |
@@ -17,6 +18,9 @@ impl Default for Config { | |||
17 | user: vec![], | 18 | user: vec![], |
18 | directories: vec![], | 19 | directories: vec![], |
19 | custom_directories: Map::new(), | 20 | custom_directories: Map::new(), |
21 | device: gethostname::gethostname() | ||
22 | .into_string() | ||
23 | .expect("invalid hostname string"), | ||
20 | } | 24 | } |
21 | } | 25 | } |
22 | } | 26 | } |
diff --git a/src/error.rs b/src/error.rs index 6afa3d0..c43c1fc 100644 --- a/src/error.rs +++ b/src/error.rs | |||
@@ -17,12 +17,13 @@ pub enum Error { | |||
17 | #[error("Only exactly one user allowed in config")] | 17 | #[error("Only exactly one user allowed in config")] |
18 | MultiUser, | 18 | MultiUser, |
19 | 19 | ||
20 | #[error("OsString couldn't be converted to string")] | ||
21 | InvalidOsString, | ||
22 | |||
23 | #[error("Requested backup not found")] | 20 | #[error("Requested backup not found")] |
24 | BackupNotFound, | 21 | BackupNotFound, |
25 | 22 | ||
23 | // Packages | ||
24 | #[error("Unknown Package Manger Output")] | ||
25 | UnknownOutput, | ||
26 | |||
26 | #[error("json: {source}")] | 27 | #[error("json: {source}")] |
27 | SerdeJson { | 28 | SerdeJson { |
28 | #[from] | 29 | #[from] |
diff --git a/src/main.rs b/src/main.rs index d5ccb75..acb728f 100644 --- a/src/main.rs +++ b/src/main.rs | |||
@@ -16,18 +16,23 @@ fn main() -> color_eyre::Result<()> { | |||
16 | cfg.directories.push("~/.config/nvim".to_string()); | 16 | cfg.directories.push("~/.config/nvim".to_string()); |
17 | cfg.directories.push("~/.config/hypr".to_string()); | 17 | cfg.directories.push("~/.config/hypr".to_string()); |
18 | cfg.root = "./backup".to_string(); | 18 | cfg.root = "./backup".to_string(); |
19 | // cfg.root = "./backup-test".to_string(); | ||
20 | // cfg.directories.push("u:/code/proj/fxbaup/backup-test-dir".to_string()); | ||
19 | 21 | ||
20 | let pacman = Pacman; | 22 | let pacman = Pacman; |
21 | let pkgs = pacman.get_installed(); | 23 | let pkgs = pacman.get_installed()?; |
22 | 24 | ||
23 | let backup = Backup::create(&cfg, pkgs)?; | 25 | let backup = Backup::create(&cfg, pkgs)?; |
24 | println!("{backup:#?}"); | 26 | // println!("{backup:#?}"); |
25 | 27 | ||
26 | // backup.save(&cfg)?; | 28 | backup.save(&cfg)?; |
27 | 29 | ||
28 | let index = Backup::get_index(&cfg, None)?; | 30 | // PathInfo::compare_to_last_modified(&cfg, &LocationRoot::User("fx".to_string()), "code/proj/fxbaub/backup-test-dir/size.txt")?; |
31 | // PathInfo::compare_to_last_modified(&cfg, &LocationRoot::User("fx".to_string()), "code/proj/fxbaub/backup-test-dir/content.txt")?; | ||
29 | 32 | ||
30 | println!("{index:#?}"); | 33 | // let index = Backup::get_index(&cfg, None)?; |
34 | |||
35 | // println!("{index:#?}"); | ||
31 | 36 | ||
32 | // let fi = FileInfo::new("~/.config/nvim", &cfg)?; | 37 | // let fi = FileInfo::new("~/.config/nvim", &cfg)?; |
33 | // println!("{:?}", fi.get_absolute_path()); | 38 | // println!("{:?}", fi.get_absolute_path()); |
diff --git a/src/packages.rs b/src/packages.rs index 9f765d6..7ac1736 100644 --- a/src/packages.rs +++ b/src/packages.rs | |||
@@ -1,6 +1,9 @@ | |||
1 | use serde::{Deserialize, Serialize}; | 1 | use serde::{Deserialize, Serialize}; |
2 | 2 | ||
3 | use crate::error::Result; | ||
4 | |||
3 | pub mod pacman; | 5 | pub mod pacman; |
6 | pub mod portage; | ||
4 | 7 | ||
5 | #[derive(Debug, Serialize, Deserialize)] | 8 | #[derive(Debug, Serialize, Deserialize)] |
6 | pub struct Package { | 9 | pub struct Package { |
@@ -10,7 +13,7 @@ pub struct Package { | |||
10 | } | 13 | } |
11 | 14 | ||
12 | pub trait PackageManager { | 15 | pub trait PackageManager { |
13 | fn get_installed(&self) -> Vec<Package>; | 16 | fn get_installed(&self) -> Result<Vec<Package>>; |
14 | 17 | ||
15 | fn install(&self, pkgs: Vec<Package>); | 18 | fn install(&self, pkgs: Vec<Package>); |
16 | } | 19 | } |
diff --git a/src/packages/pacman.rs b/src/packages/pacman.rs index 0a9e1ff..b5be4c0 100644 --- a/src/packages/pacman.rs +++ b/src/packages/pacman.rs | |||
@@ -1,13 +1,13 @@ | |||
1 | use std::process::Command; | 1 | use std::process::Command; |
2 | 2 | ||
3 | use crate::packages::Package; | 3 | use super::{Package, PackageManager}; |
4 | 4 | ||
5 | use super::PackageManager; | 5 | use crate::error::{Error, Result}; |
6 | 6 | ||
7 | pub struct Pacman; | 7 | pub struct Pacman; |
8 | 8 | ||
9 | impl PackageManager for Pacman { | 9 | impl PackageManager for Pacman { |
10 | fn get_installed(&self) -> Vec<super::Package> { | 10 | fn get_installed(&self) -> Result<Vec<super::Package>> { |
11 | let pm_pkgs = Command::new("pacman").args(["-Q"]).output().unwrap(); | 11 | let pm_pkgs = Command::new("pacman").args(["-Q"]).output().unwrap(); |
12 | let pm_e_pkgs = Command::new("pacman") | 12 | let pm_e_pkgs = Command::new("pacman") |
13 | .args(["-Q", "--explicit"]) | 13 | .args(["-Q", "--explicit"]) |
@@ -25,7 +25,7 @@ impl PackageManager for Pacman { | |||
25 | }; | 25 | }; |
26 | let split: Vec<&str> = pkg.split_whitespace().collect(); | 26 | let split: Vec<&str> = pkg.split_whitespace().collect(); |
27 | if split.len() != 2 { | 27 | if split.len() != 2 { |
28 | panic!("Unknown Pacman Output"); | 28 | return Err(Error::UnknownOutput); |
29 | }; | 29 | }; |
30 | 30 | ||
31 | let explicit = pm_e_pkgs_out.contains(pkg); | 31 | let explicit = pm_e_pkgs_out.contains(pkg); |
@@ -33,14 +33,14 @@ impl PackageManager for Pacman { | |||
33 | pkgs.push(Package { | 33 | pkgs.push(Package { |
34 | id: split[0].to_string(), | 34 | id: split[0].to_string(), |
35 | version: split[1].to_string(), | 35 | version: split[1].to_string(), |
36 | explicit | 36 | explicit, |
37 | }) | 37 | }) |
38 | } | 38 | } |
39 | 39 | ||
40 | pkgs | 40 | Ok(pkgs) |
41 | } | 41 | } |
42 | 42 | ||
43 | fn install(&self, pkgs: Vec<Package>) { | 43 | fn install(&self, _pkgs: Vec<super::Package>) { |
44 | todo!(); | 44 | todo!(); |
45 | } | 45 | } |
46 | } | 46 | } |
diff --git a/src/packages/portage.rs b/src/packages/portage.rs new file mode 100644 index 0000000..6b9e508 --- /dev/null +++ b/src/packages/portage.rs | |||
@@ -0,0 +1,13 @@ | |||
1 | use super::PackageManager; | ||
2 | |||
3 | pub struct Portage; | ||
4 | |||
5 | impl PackageManager for Portage { | ||
6 | fn get_installed(&self) -> crate::error::Result<Vec<super::Package>> { | ||
7 | todo!() | ||
8 | } | ||
9 | |||
10 | fn install(&self, pkgs: Vec<super::Package>) { | ||
11 | todo!() | ||
12 | } | ||
13 | } | ||
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 | } | ||