]>
Commit | Line | Data |
---|---|---|
b3483782 DM |
1 | use crate::tools; |
2 | ||
3 | use failure::*; | |
4 | use regex::Regex; | |
5 | ||
6 | use chrono::{DateTime, TimeZone, Local}; | |
7 | ||
8 | use std::path::{PathBuf, Path}; | |
9 | use lazy_static::lazy_static; | |
10 | ||
11 | macro_rules! BACKUP_ID_RE { () => (r"[A-Za-z0-9][A-Za-z0-9_-]+") } | |
12 | macro_rules! BACKUP_TYPE_RE { () => (r"(?:host|vm|ct)") } | |
13 | macro_rules! BACKUP_TIME_RE { () => (r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\+[0-9]{2}:[0-9]{2}") } | |
14 | ||
15 | lazy_static!{ | |
16 | static ref BACKUP_FILE_REGEX: Regex = Regex::new( | |
17 | r"^.*\.([fd]idx)$").unwrap(); | |
18 | ||
19 | static ref BACKUP_TYPE_REGEX: Regex = Regex::new( | |
20 | concat!(r"^(", BACKUP_TYPE_RE!(), r")$")).unwrap(); | |
21 | ||
22 | static ref BACKUP_ID_REGEX: Regex = Regex::new( | |
23 | concat!(r"^", BACKUP_ID_RE!(), r"$")).unwrap(); | |
24 | ||
25 | static ref BACKUP_DATE_REGEX: Regex = Regex::new( | |
26 | concat!(r"^", BACKUP_TIME_RE!() ,r"$")).unwrap(); | |
27 | ||
28 | static ref GROUP_PATH_REGEX: Regex = Regex::new( | |
29 | concat!(r"(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), r")$")).unwrap(); | |
30 | ||
31 | static ref SNAPSHOT_PATH_REGEX: Regex = Regex::new( | |
32 | concat!(r"(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), ")/(", BACKUP_TIME_RE!(), r")$")).unwrap(); | |
33 | ||
34 | } | |
35 | ||
d57474e0 | 36 | /// BackupGroup is a directory containing a list of BackupDir |
35a2d8a6 | 37 | #[derive(Debug, Clone)] |
b3483782 DM |
38 | pub struct BackupGroup { |
39 | /// Type of backup | |
40 | backup_type: String, | |
41 | /// Unique (for this type) ID | |
42 | backup_id: String, | |
43 | } | |
44 | ||
45 | impl BackupGroup { | |
46 | ||
93b49ce3 | 47 | pub fn new<T: Into<String>, U: Into<String>>(backup_type: T, backup_id: U) -> Self { |
b3483782 DM |
48 | Self { backup_type: backup_type.into(), backup_id: backup_id.into() } |
49 | } | |
50 | ||
51 | pub fn backup_type(&self) -> &str { | |
52 | &self.backup_type | |
53 | } | |
54 | ||
55 | pub fn backup_id(&self) -> &str { | |
56 | &self.backup_id | |
57 | } | |
58 | ||
59 | pub fn parse(path: &str) -> Result<Self, Error> { | |
60 | ||
61 | let cap = GROUP_PATH_REGEX.captures(path) | |
62 | .ok_or_else(|| format_err!("unable to parse backup group path '{}'", path))?; | |
63 | ||
64 | Ok(Self { | |
65 | backup_type: cap.get(1).unwrap().as_str().to_owned(), | |
66 | backup_id: cap.get(2).unwrap().as_str().to_owned(), | |
67 | }) | |
68 | } | |
69 | ||
70 | pub fn group_path(&self) -> PathBuf { | |
71 | ||
72 | let mut relative_path = PathBuf::new(); | |
73 | ||
74 | relative_path.push(&self.backup_type); | |
75 | ||
76 | relative_path.push(&self.backup_id); | |
77 | ||
78 | relative_path | |
79 | } | |
80 | } | |
81 | ||
82 | /// Uniquely identify a Backup (relative to data store) | |
d57474e0 DM |
83 | /// |
84 | /// We also call this a backup snaphost. | |
35a2d8a6 | 85 | #[derive(Debug, Clone)] |
b3483782 DM |
86 | pub struct BackupDir { |
87 | /// Backup group | |
88 | group: BackupGroup, | |
89 | /// Backup timestamp | |
90 | backup_time: DateTime<Local>, | |
91 | } | |
92 | ||
93 | impl BackupDir { | |
94 | ||
391d3107 WB |
95 | pub fn new<T, U>(backup_type: T, backup_id: U, timestamp: i64) -> Self |
96 | where | |
97 | T: Into<String>, | |
98 | U: Into<String>, | |
99 | { | |
b3483782 | 100 | // Note: makes sure that nanoseconds is 0 |
391d3107 WB |
101 | Self { |
102 | group: BackupGroup::new(backup_type.into(), backup_id.into()), | |
103 | backup_time: Local.timestamp(timestamp, 0), | |
104 | } | |
b3483782 DM |
105 | } |
106 | ||
107 | pub fn group(&self) -> &BackupGroup { | |
108 | &self.group | |
109 | } | |
110 | ||
111 | pub fn backup_time(&self) -> DateTime<Local> { | |
112 | self.backup_time | |
113 | } | |
114 | ||
115 | pub fn parse(path: &str) -> Result<Self, Error> { | |
116 | ||
117 | let cap = SNAPSHOT_PATH_REGEX.captures(path) | |
118 | .ok_or_else(|| format_err!("unable to parse backup snapshot path '{}'", path))?; | |
119 | ||
120 | let group = BackupGroup::new(cap.get(1).unwrap().as_str(), cap.get(2).unwrap().as_str()); | |
121 | let backup_time = cap.get(3).unwrap().as_str().parse::<DateTime<Local>>()?; | |
391d3107 | 122 | Ok(BackupDir::from((group, backup_time.timestamp()))) |
b3483782 DM |
123 | } |
124 | ||
125 | pub fn relative_path(&self) -> PathBuf { | |
126 | ||
127 | let mut relative_path = self.group.group_path(); | |
128 | ||
129 | relative_path.push(self.backup_time.to_rfc3339()); | |
130 | ||
131 | relative_path | |
132 | } | |
133 | } | |
134 | ||
391d3107 WB |
135 | impl From<(BackupGroup, i64)> for BackupDir { |
136 | fn from((group, timestamp): (BackupGroup, i64)) -> Self { | |
137 | Self { group, backup_time: Local.timestamp(timestamp, 0) } | |
138 | } | |
139 | } | |
140 | ||
d57474e0 | 141 | /// Detailed Backup Information, lists files inside a BackupDir |
b3483782 DM |
142 | #[derive(Debug)] |
143 | pub struct BackupInfo { | |
144 | /// the backup directory | |
145 | pub backup_dir: BackupDir, | |
146 | /// List of data files | |
147 | pub files: Vec<String>, | |
148 | } | |
149 | ||
150 | impl BackupInfo { | |
151 | ||
152 | pub fn sort_list(list: &mut Vec<BackupInfo>, ascendending: bool) { | |
153 | if ascendending { // oldest first | |
154 | list.sort_unstable_by(|a, b| a.backup_dir.backup_time.cmp(&b.backup_dir.backup_time)); | |
155 | } else { // newest first | |
156 | list.sort_unstable_by(|a, b| b.backup_dir.backup_time.cmp(&a.backup_dir.backup_time)); | |
157 | } | |
158 | } | |
159 | ||
58e99e13 DM |
160 | pub fn list_files(base_path: &Path, backup_dir: &BackupDir) -> Result<Vec<String>, Error> { |
161 | let mut path = base_path.to_owned(); | |
162 | path.push(backup_dir.relative_path()); | |
163 | ||
164 | let mut files = vec![]; | |
165 | ||
166 | tools::scandir(libc::AT_FDCWD, &path, &BACKUP_FILE_REGEX, |_, filename, file_type| { | |
167 | if file_type != nix::dir::Type::File { return Ok(()); } | |
168 | files.push(filename.to_owned()); | |
169 | Ok(()) | |
170 | })?; | |
171 | ||
172 | Ok(files) | |
173 | } | |
174 | ||
175 | pub fn list_backups(base_path: &Path) -> Result<Vec<BackupInfo>, Error> { | |
b3483782 DM |
176 | let mut list = vec![]; |
177 | ||
58e99e13 | 178 | tools::scandir(libc::AT_FDCWD, base_path, &BACKUP_TYPE_REGEX, |l0_fd, backup_type, file_type| { |
b3483782 DM |
179 | if file_type != nix::dir::Type::Directory { return Ok(()); } |
180 | tools::scandir(l0_fd, backup_type, &BACKUP_ID_REGEX, |l1_fd, backup_id, file_type| { | |
181 | if file_type != nix::dir::Type::Directory { return Ok(()); } | |
182 | tools::scandir(l1_fd, backup_id, &BACKUP_DATE_REGEX, |l2_fd, backup_time, file_type| { | |
183 | if file_type != nix::dir::Type::Directory { return Ok(()); } | |
184 | ||
185 | let dt = backup_time.parse::<DateTime<Local>>()?; | |
186 | ||
187 | let mut files = vec![]; | |
188 | ||
189 | tools::scandir(l2_fd, backup_time, &BACKUP_FILE_REGEX, |_, filename, file_type| { | |
190 | if file_type != nix::dir::Type::File { return Ok(()); } | |
191 | files.push(filename.to_owned()); | |
192 | Ok(()) | |
193 | })?; | |
194 | ||
195 | list.push(BackupInfo { | |
391d3107 | 196 | backup_dir: BackupDir::new(backup_type, backup_id, dt.timestamp()), |
b3483782 DM |
197 | files, |
198 | }); | |
199 | ||
200 | Ok(()) | |
201 | }) | |
202 | }) | |
203 | })?; | |
204 | Ok(list) | |
205 | } | |
206 | } |