mirror of https://github.com/dswd/zvault
Prune
This commit is contained in:
parent
fc45fa4e33
commit
7cadaaf359
|
@ -31,6 +31,16 @@ pub enum Arguments {
|
||||||
vacuum: bool,
|
vacuum: bool,
|
||||||
inode: Option<String>
|
inode: Option<String>
|
||||||
},
|
},
|
||||||
|
Prune {
|
||||||
|
repo_path: String,
|
||||||
|
prefix: String,
|
||||||
|
daily: Option<usize>,
|
||||||
|
weekly: Option<usize>,
|
||||||
|
monthly: Option<usize>,
|
||||||
|
yearly: Option<usize>,
|
||||||
|
vacuum: bool,
|
||||||
|
simulate: bool
|
||||||
|
},
|
||||||
Vacuum {
|
Vacuum {
|
||||||
repo_path: String,
|
repo_path: String,
|
||||||
ratio: f32,
|
ratio: f32,
|
||||||
|
@ -209,10 +219,21 @@ pub fn parse() -> Arguments {
|
||||||
(@arg vacuum: --vacuum "run vacuum afterwards to reclaim space")
|
(@arg vacuum: --vacuum "run vacuum afterwards to reclaim space")
|
||||||
(@arg BACKUP: +required "repository::backup[::subpath] path")
|
(@arg BACKUP: +required "repository::backup[::subpath] path")
|
||||||
)
|
)
|
||||||
|
(@subcommand prune =>
|
||||||
|
(about: "removes backups based on age")
|
||||||
|
(@arg prefix: --prefix +takes_value "only consider backups starting with this prefix")
|
||||||
|
(@arg daily: --daily +takes_value "keep this number of daily backups")
|
||||||
|
(@arg weekly: --weekly +takes_value "keep this number of weekly backups")
|
||||||
|
(@arg monthly: --monthly +takes_value "keep this number of monthly backups")
|
||||||
|
(@arg yearly: --yearly +takes_value "keep this number of yearly backups")
|
||||||
|
(@arg vacuum: --vacuum "run vacuum afterwards to reclaim space")
|
||||||
|
(@arg simulate: --simulate "only simulate the prune, do not remove any backups")
|
||||||
|
(@arg REPO: +required "path of the repository")
|
||||||
|
)
|
||||||
(@subcommand vacuum =>
|
(@subcommand vacuum =>
|
||||||
(about: "saves space by combining and recompressing bundles")
|
(about: "saves space by combining and recompressing bundles")
|
||||||
(@arg ratio: --ratio -r +takes_value "ratio of unused chunks in a bundle to rewrite that bundle")
|
(@arg ratio: --ratio -r +takes_value "ratio of unused chunks in a bundle to rewrite that bundle")
|
||||||
(@arg ratio: --simulate "only simulate the vacuum, do not remove any bundles")
|
(@arg simulate: --simulate "only simulate the vacuum, do not remove any bundles")
|
||||||
(@arg REPO: +required "path of the repository")
|
(@arg REPO: +required "path of the repository")
|
||||||
)
|
)
|
||||||
(@subcommand check =>
|
(@subcommand check =>
|
||||||
|
@ -325,6 +346,23 @@ pub fn parse() -> Arguments {
|
||||||
inode: inode.map(|v| v.to_string())
|
inode: inode.map(|v| v.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(args) = args.subcommand_matches("prune") {
|
||||||
|
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||||
|
if backup.is_some() || inode.is_some() {
|
||||||
|
println!("No backups or subpaths may be given here");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return Arguments::Prune {
|
||||||
|
repo_path: repository.to_string(),
|
||||||
|
prefix: args.value_of("prefix").unwrap_or("").to_string(),
|
||||||
|
vacuum: args.is_present("vacuum"),
|
||||||
|
simulate: args.is_present("simulate"),
|
||||||
|
daily: args.value_of("daily").map(|v| parse_num(v, "daily backups") as usize),
|
||||||
|
weekly: args.value_of("weekly").map(|v| parse_num(v, "weekly backups") as usize),
|
||||||
|
monthly: args.value_of("monthly").map(|v| parse_num(v, "monthly backups") as usize),
|
||||||
|
yearly: args.value_of("yearly").map(|v| parse_num(v, "yearly backups") as usize),
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(args) = args.subcommand_matches("vacuum") {
|
if let Some(args) = args.subcommand_matches("vacuum") {
|
||||||
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||||
if backup.is_some() || inode.is_some() {
|
if backup.is_some() || inode.is_some() {
|
||||||
|
|
|
@ -85,6 +85,17 @@ pub fn run() {
|
||||||
repo.vacuum(0.5, false).unwrap();
|
repo.vacuum(0.5, false).unwrap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Arguments::Prune{repo_path, prefix, daily, weekly, monthly, yearly, simulate, vacuum} => {
|
||||||
|
let mut repo = open_repository(&repo_path);
|
||||||
|
if daily.is_none() && weekly.is_none() && monthly.is_none() && yearly.is_none() {
|
||||||
|
error!("This would remove all those backups");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
repo.prune_backups(&prefix, daily, weekly, monthly, yearly, simulate).unwrap();
|
||||||
|
if !simulate && vacuum {
|
||||||
|
repo.vacuum(0.5, false).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
Arguments::Vacuum{repo_path, ratio, simulate} => {
|
Arguments::Vacuum{repo_path, ratio, simulate} => {
|
||||||
let mut repo = open_repository(&repo_path);
|
let mut repo = open_repository(&repo_path);
|
||||||
repo.vacuum(ratio, simulate).unwrap();
|
repo.vacuum(ratio, simulate).unwrap();
|
||||||
|
@ -118,8 +129,8 @@ pub fn run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for backup in repo.list_backups().unwrap() {
|
for (name, backup) in repo.list_backups().unwrap() {
|
||||||
println!("{}", backup);
|
println!("{} - {} - {} files, {} dirs, {}", name, Local.timestamp(backup.date, 0).to_rfc2822(), backup.file_count, backup.dir_count, to_file_size(backup.total_data_size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,7 +30,6 @@ mod cli;
|
||||||
// TODO: - Write backup files there as well
|
// TODO: - Write backup files there as well
|
||||||
// TODO: Remove backup subtrees
|
// TODO: Remove backup subtrees
|
||||||
// TODO: Recompress & combine bundles
|
// TODO: Recompress & combine bundles
|
||||||
// TODO: Prune backups (based on age like attic)
|
|
||||||
// TODO: Encrypt backup files too
|
// TODO: Encrypt backup files too
|
||||||
// TODO: list --tree
|
// TODO: list --tree
|
||||||
// TODO: Partial backups
|
// TODO: Partial backups
|
||||||
|
|
|
@ -42,8 +42,8 @@ serde_impl!(Backup(u8) {
|
||||||
|
|
||||||
|
|
||||||
impl Repository {
|
impl Repository {
|
||||||
pub fn list_backups(&self) -> Result<Vec<String>, RepositoryError> {
|
pub fn list_backups(&self) -> Result<HashMap<String, Backup>, RepositoryError> {
|
||||||
let mut backups = Vec::new();
|
let mut backups = HashMap::new();
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
let base_path = self.path.join("backups");
|
let base_path = self.path.join("backups");
|
||||||
paths.push(base_path.clone());
|
paths.push(base_path.clone());
|
||||||
|
@ -55,7 +55,9 @@ impl Repository {
|
||||||
paths.push(path);
|
paths.push(path);
|
||||||
} else {
|
} else {
|
||||||
let relpath = path.strip_prefix(&base_path).unwrap();
|
let relpath = path.strip_prefix(&base_path).unwrap();
|
||||||
backups.push(relpath.to_string_lossy().to_string());
|
let name = relpath.to_string_lossy().to_string();
|
||||||
|
let backup = try!(self.get_backup(&name));
|
||||||
|
backups.insert(name, backup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,6 +88,99 @@ impl Repository {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prune_backups(&self, prefix: &str, daily: Option<usize>, weekly: Option<usize>, monthly: Option<usize>, yearly: Option<usize>, simulate: bool) -> Result<(), RepositoryError> {
|
||||||
|
let mut backups = Vec::new();
|
||||||
|
for (name, backup) in try!(self.list_backups()) {
|
||||||
|
if name.starts_with(prefix) {
|
||||||
|
let date = Local.timestamp(backup.date, 0);
|
||||||
|
backups.push((name, date, backup));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
backups.sort_by_key(|backup| backup.2.date);
|
||||||
|
let mut keep = Bitmap::new(backups.len());
|
||||||
|
if let Some(max) = yearly {
|
||||||
|
let mut unique = VecDeque::with_capacity(max+1);
|
||||||
|
let mut last = None;
|
||||||
|
for (i, backup) in backups.iter().enumerate() {
|
||||||
|
let val = backup.1.year();
|
||||||
|
if Some(val) != last {
|
||||||
|
last = Some(val);
|
||||||
|
unique.push_back(i);
|
||||||
|
if unique.len() > max {
|
||||||
|
unique.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in unique {
|
||||||
|
keep.set(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(max) = monthly {
|
||||||
|
let mut unique = VecDeque::with_capacity(max+1);
|
||||||
|
let mut last = None;
|
||||||
|
for (i, backup) in backups.iter().enumerate() {
|
||||||
|
let val = (backup.1.year(), backup.1.month());
|
||||||
|
if Some(val) != last {
|
||||||
|
last = Some(val);
|
||||||
|
unique.push_back(i);
|
||||||
|
if unique.len() > max {
|
||||||
|
unique.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in unique {
|
||||||
|
keep.set(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(max) = weekly {
|
||||||
|
let mut unique = VecDeque::with_capacity(max+1);
|
||||||
|
let mut last = None;
|
||||||
|
for (i, backup) in backups.iter().enumerate() {
|
||||||
|
let val = (backup.1.isoweekdate().0, backup.1.isoweekdate().1);
|
||||||
|
if Some(val) != last {
|
||||||
|
last = Some(val);
|
||||||
|
unique.push_back(i);
|
||||||
|
if unique.len() > max {
|
||||||
|
unique.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in unique {
|
||||||
|
keep.set(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(max) = daily {
|
||||||
|
let mut unique = VecDeque::with_capacity(max+1);
|
||||||
|
let mut last = None;
|
||||||
|
for (i, backup) in backups.iter().enumerate() {
|
||||||
|
let val = (backup.1.year(), backup.1.month(), backup.1.day());
|
||||||
|
if Some(val) != last {
|
||||||
|
last = Some(val);
|
||||||
|
unique.push_back(i);
|
||||||
|
if unique.len() > max {
|
||||||
|
unique.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in unique {
|
||||||
|
keep.set(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut remove = Vec::new();
|
||||||
|
for (i, backup) in backups.into_iter().enumerate() {
|
||||||
|
if !keep.get(i) {
|
||||||
|
remove.push(backup.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!("Removing the following backups: {:?}", remove);
|
||||||
|
if !simulate {
|
||||||
|
for name in remove {
|
||||||
|
try!(self.delete_backup(&name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn restore_inode_tree<P: AsRef<Path>>(&mut self, inode: Inode, path: P) -> Result<(), RepositoryError> {
|
pub fn restore_inode_tree<P: AsRef<Path>>(&mut self, inode: Inode, path: P) -> Result<(), RepositoryError> {
|
||||||
let mut queue = VecDeque::new();
|
let mut queue = VecDeque::new();
|
||||||
queue.push_back((path.as_ref().to_owned(), inode));
|
queue.push_back((path.as_ref().to_owned(), inode));
|
||||||
|
|
|
@ -90,8 +90,7 @@ impl Repository {
|
||||||
|
|
||||||
fn check_backups(&mut self) -> Result<(), RepositoryError> {
|
fn check_backups(&mut self) -> Result<(), RepositoryError> {
|
||||||
let mut checked = Bitmap::new(self.index.capacity());
|
let mut checked = Bitmap::new(self.index.capacity());
|
||||||
for name in try!(self.list_backups()) {
|
for (_name, backup) in try!(self.list_backups()) {
|
||||||
let backup = try!(self.get_backup(&name));
|
|
||||||
let mut todo = VecDeque::new();
|
let mut todo = VecDeque::new();
|
||||||
todo.push_back(backup.root);
|
todo.push_back(backup.root);
|
||||||
while let Some(chunks) = todo.pop_front() {
|
while let Some(chunks) = todo.pop_front() {
|
||||||
|
|
|
@ -48,8 +48,7 @@ impl Repository {
|
||||||
used_size: 0
|
used_size: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for name in try!(self.list_backups()) {
|
for (_name, backup) in try!(self.list_backups()).into_iter() {
|
||||||
let backup = try!(self.get_backup(&name));
|
|
||||||
let mut todo = VecDeque::new();
|
let mut todo = VecDeque::new();
|
||||||
todo.push_back(backup.root);
|
todo.push_back(backup.root);
|
||||||
while let Some(chunks) = todo.pop_front() {
|
while let Some(chunks) = todo.pop_front() {
|
||||||
|
|
Loading…
Reference in New Issue