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,
|
||||
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 {
|
||||
repo_path: String,
|
||||
ratio: f32,
|
||||
|
@ -209,10 +219,21 @@ pub fn parse() -> Arguments {
|
|||
(@arg vacuum: --vacuum "run vacuum afterwards to reclaim space")
|
||||
(@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 =>
|
||||
(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: --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")
|
||||
)
|
||||
(@subcommand check =>
|
||||
|
@ -325,6 +346,23 @@ pub fn parse() -> Arguments {
|
|||
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") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||
if backup.is_some() || inode.is_some() {
|
||||
|
|
|
@ -85,6 +85,17 @@ pub fn run() {
|
|||
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} => {
|
||||
let mut repo = open_repository(&repo_path);
|
||||
repo.vacuum(ratio, simulate).unwrap();
|
||||
|
@ -118,8 +129,8 @@ pub fn run() {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
for backup in repo.list_backups().unwrap() {
|
||||
println!("{}", backup);
|
||||
for (name, backup) in repo.list_backups().unwrap() {
|
||||
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: Remove backup subtrees
|
||||
// TODO: Recompress & combine bundles
|
||||
// TODO: Prune backups (based on age like attic)
|
||||
// TODO: Encrypt backup files too
|
||||
// TODO: list --tree
|
||||
// TODO: Partial backups
|
||||
|
|
|
@ -42,8 +42,8 @@ serde_impl!(Backup(u8) {
|
|||
|
||||
|
||||
impl Repository {
|
||||
pub fn list_backups(&self) -> Result<Vec<String>, RepositoryError> {
|
||||
let mut backups = Vec::new();
|
||||
pub fn list_backups(&self) -> Result<HashMap<String, Backup>, RepositoryError> {
|
||||
let mut backups = HashMap::new();
|
||||
let mut paths = Vec::new();
|
||||
let base_path = self.path.join("backups");
|
||||
paths.push(base_path.clone());
|
||||
|
@ -55,7 +55,9 @@ impl Repository {
|
|||
paths.push(path);
|
||||
} else {
|
||||
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(())
|
||||
}
|
||||
|
||||
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> {
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back((path.as_ref().to_owned(), inode));
|
||||
|
|
|
@ -90,8 +90,7 @@ impl Repository {
|
|||
|
||||
fn check_backups(&mut self) -> Result<(), RepositoryError> {
|
||||
let mut checked = Bitmap::new(self.index.capacity());
|
||||
for name in try!(self.list_backups()) {
|
||||
let backup = try!(self.get_backup(&name));
|
||||
for (_name, backup) in try!(self.list_backups()) {
|
||||
let mut todo = VecDeque::new();
|
||||
todo.push_back(backup.root);
|
||||
while let Some(chunks) = todo.pop_front() {
|
||||
|
|
|
@ -48,8 +48,7 @@ impl Repository {
|
|||
used_size: 0
|
||||
});
|
||||
}
|
||||
for name in try!(self.list_backups()) {
|
||||
let backup = try!(self.get_backup(&name));
|
||||
for (_name, backup) in try!(self.list_backups()).into_iter() {
|
||||
let mut todo = VecDeque::new();
|
||||
todo.push_back(backup.root);
|
||||
while let Some(chunks) = todo.pop_front() {
|
||||
|
|
Loading…
Reference in New Issue