1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use crate::paths::MakeDirs;
use dialoguer::{theme::ColorfulTheme, Select};
use std::path::Path;
#[derive(Debug, clap::Args)]
/// Restore a backup - decompress archive if compressed, then distribute files
/// to their original local location.
pub(crate) struct RestoreCmd {
#[arg(short, long)]
/// Don't bother backing up.
no_backup: bool,
}
impl super::Command for RestoreCmd {
fn run(
&self,
_: &super::nedots::RootCmd,
config: &crate::config::Config,
) -> anyhow::Result<()> {
// Read backup directories - filter errors, and return Strings.
let mut items: Vec<String> = config
.root
.join(&config.backup_dir)
.read_dir()?
.map(|e| e.unwrap().path().display().to_string())
.collect();
// Place the latest backup at the top of the list - when we set default
// selection to index 0, this is the latest backup.
items.reverse();
if items.len().gt(&0) {
if !self.no_backup {
super::backup::backup(
&config.sources,
&config
.root
.join(&config.backup_dir)
.join(super::backup::get_timestamp()),
true,
)?;
}
let selection = Select::with_theme(&ColorfulTheme::default())
.items(&items)
.default(0)
.interact_on_opt(&console::Term::stderr())?;
if let Some(index) = selection {
let path = Path::new(&items[index]);
let file = std::fs::File::open(path)?;
let mut archive = zip::ZipArchive::new(file)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let dst = path.with_extension("").join(file.enclosed_name().unwrap());
if (*file.name()).ends_with('/') {
log::debug!("File {} extracted to {}", i, dst.display());
dst.make_all_dirs()?;
} else {
log::debug!(
"File {} extracted to {} ({} bytes)",
i,
dst.display(),
file.size()
);
if let Some(p) = dst.parent() {
if !p.exists() {
p.make_all_dirs()?;
}
}
let mut outfile = std::fs::File::create(&dst)?;
std::io::copy(&mut file, &mut outfile)?;
}
#[cfg(unix)]
{
use std::os::unix::prelude::PermissionsExt;
if let Some(mode) = file.unix_mode() {
std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode))?;
log::debug!(
"Set {} permissions to {}",
path.display(),
std::fs::Permissions::from_mode(mode).mode()
)
}
}
}
log::info!(
"🛠️ Extracted {}",
console::style(path.with_extension("").display())
.green()
.bold()
);
let src = path.with_extension("");
for entry in src.read_dir()? {
let path = entry?.path();
let dst = path
.display()
.to_string()
.replace(&src.display().to_string(), "");
super::gather::gather_file(&path, Path::new(&dst))?;
log::info!(
"✅ Restored {}",
console::style(path.display()).green().bold()
);
}
log::trace!("Tidying up! Removing {}", path.display());
trash::delete_all([path, &path.with_extension("")])?;
}
} else {
log::warn!("No backups to restore");
return Ok(());
}
Ok(())
}
}