Skip to content

Commit

Permalink
refactor backup command
Browse files Browse the repository at this point in the history
  • Loading branch information
aawsome committed Jul 13, 2023
1 parent 088f1f9 commit ce40f1d
Show file tree
Hide file tree
Showing 14 changed files with 381 additions and 256 deletions.
32 changes: 32 additions & 0 deletions crates/rustic_core/examples/backup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! `backup` example
use rustic_core::{BackupOpts, PathList, Repository, RepositoryOptions, SnapshotFile};
use simplelog::{Config, LevelFilter, SimpleLogger};

fn main() {
// Display info logs
let _ = SimpleLogger::init(LevelFilter::Info, Config::default());

// Open repository
let repo_opts = RepositoryOptions {
repository: Some("/tmp/repo".to_string()),
password: Some("test".to_string()),
..Default::default()
};

let repo = Repository::new(&repo_opts)
.unwrap()
.open()
.unwrap()
.to_indexed_ids()
.unwrap();

let backup_opts = BackupOpts::default();
let source = PathList::from_string(".", true).unwrap(); // true: sanitize the given string
let dry_run = false;

let snap = repo
.backup(&backup_opts, source, SnapshotFile::default(), dry_run)
.unwrap();

println!("successfully created snapshot:\n{snap:#?}")
}
17 changes: 6 additions & 11 deletions crates/rustic_core/src/archiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use crate::{
},
backend::{decrypt::DecryptWriteBackend, ReadSource, ReadSourceEntry},
blob::BlobType,
id::Id,
index::{indexer::Indexer, indexer::SharedIndexer, IndexedBackend},
repofile::{configfile::ConfigFile, snapshotfile::SnapshotFile},
Progress, RusticResult,
Expand All @@ -25,8 +24,7 @@ use crate::{
pub struct Archiver<BE: DecryptWriteBackend, I: IndexedBackend> {
file_archiver: FileArchiver<BE, I>,
tree_archiver: TreeArchiver<BE, I>,
parent_tree: Option<Id>,
parent: Parent<I>,
parent: Parent,
indexer: SharedIndexer<BE>,
be: BE,
snap: SnapshotFile,
Expand All @@ -37,22 +35,18 @@ impl<BE: DecryptWriteBackend, I: IndexedBackend> Archiver<BE, I> {
be: BE,
index: I,
config: &ConfigFile,
parent_tree: Option<Id>,
ignore_ctime: bool,
ignore_inode: bool,
parent: Parent,
mut snap: SnapshotFile,
) -> RusticResult<Self> {
let indexer = Indexer::new(be.clone()).into_shared();
let mut summary = snap.summary.take().unwrap();
let mut summary = snap.summary.take().unwrap_or_default();
summary.backup_start = Local::now();

let parent = Parent::new(&index, parent_tree, ignore_ctime, ignore_inode);
let file_archiver = FileArchiver::new(be.clone(), index.clone(), indexer.clone(), config)?;
let tree_archiver = TreeArchiver::new(be.clone(), index, indexer.clone(), config, summary)?;
Ok(Self {
file_archiver,
tree_archiver,
parent_tree,
parent,
indexer,
be,
Expand All @@ -62,6 +56,7 @@ impl<BE: DecryptWriteBackend, I: IndexedBackend> Archiver<BE, I> {

pub fn archive<R>(
mut self,
index: &I,
src: R,
backup_path: &Path,
as_path: Option<&PathBuf>,
Expand Down Expand Up @@ -112,7 +107,7 @@ impl<BE: DecryptWriteBackend, I: IndexedBackend> Archiver<BE, I> {

scope(|scope| -> RusticResult<_> {
// use parent snapshot
iter.filter_map(|item| match self.parent.process(item) {
iter.filter_map(|item| match self.parent.process(index, item) {
Ok(item) => Some(item),
Err(err) => {
warn!("ignoring error reading parent snapshot: {err:?}");
Expand All @@ -134,7 +129,7 @@ impl<BE: DecryptWriteBackend, I: IndexedBackend> Archiver<BE, I> {
.unwrap()?;

let stats = self.file_archiver.finalize()?;
let (id, mut summary) = self.tree_archiver.finalize(self.parent_tree)?;
let (id, mut summary) = self.tree_archiver.finalize(self.parent.tree_id())?;
stats.apply(&mut summary, BlobType::Data);
self.snap.tree = id;

Expand Down
52 changes: 28 additions & 24 deletions crates/rustic_core/src/archiver/parent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ use crate::{
id::Id, index::IndexedBackend, RusticResult,
};

pub(crate) struct Parent<BE: IndexedBackend> {
#[derive(Debug)]
pub struct Parent {
tree_id: Option<Id>,
tree: Option<Tree>,
node_idx: usize,
stack: Vec<(Option<Tree>, usize)>,
be: BE,
ignore_ctime: bool,
ignore_inode: bool,
}
Expand All @@ -38,8 +39,8 @@ impl<T> ParentResult<T> {

pub(crate) type ItemWithParent<O> = TreeType<(O, ParentResult<()>), ParentResult<Id>>;

impl<BE: IndexedBackend> Parent<BE> {
pub(crate) fn new(
impl Parent {
pub(crate) fn new<BE: IndexedBackend>(
be: &BE,
tree_id: Option<Id>,
ignore_ctime: bool,
Expand All @@ -54,10 +55,10 @@ impl<BE: IndexedBackend> Parent<BE> {
}
});
Self {
tree_id,
tree,
node_idx: 0,
stack: Vec::new(),
be: be.clone(),
ignore_ctime,
ignore_inode,
}
Expand Down Expand Up @@ -105,24 +106,22 @@ impl<BE: IndexedBackend> Parent<BE> {
})
}

fn set_dir(&mut self, name: &OsStr) {
let tree = match self.p_node(name) {
Some(p_node) => {
if let Some(tree_id) = p_node.subtree {
match Tree::from_backend(&self.be, tree_id) {
Ok(tree) => Some(tree),
Err(err) => {
warn!("ignoring error when loading parent tree {tree_id}: {err}");
None
}
}
} else {
fn set_dir<BE: IndexedBackend>(&mut self, be: &BE, name: &OsStr) {
let tree = self.p_node(name).and_then(|p_node| {
p_node.subtree.map_or_else(
|| {
warn!("ignoring parent node {}: is no tree!", p_node.name);
None
}
}
None => None,
};
},
|tree_id| match Tree::from_backend(be, tree_id) {
Ok(tree) => Some(tree),
Err(err) => {
warn!("ignoring error when loading parent tree {tree_id}: {err}");
None
}
},
)
});
self.stack.push((self.tree.take(), self.node_idx));
self.tree = tree;
self.node_idx = 0;
Expand All @@ -140,24 +139,29 @@ impl<BE: IndexedBackend> Parent<BE> {
Ok(())
}

pub(crate) fn process<O>(
pub(crate) fn tree_id(&self) -> Option<Id> {
self.tree_id
}

pub(crate) fn process<BE: IndexedBackend, O>(
&mut self,
be: &BE,
item: TreeType<O, OsString>,
) -> RusticResult<ItemWithParent<O>> {
let result = match item {
TreeType::NewTree((path, node, tree)) => {
let parent_result = self
.is_parent(&node, &tree)
.map(|node| node.subtree.unwrap());
self.set_dir(&tree);
self.set_dir(be, &tree);
TreeType::NewTree((path, node, parent_result))
}
TreeType::EndTree => {
self.finish_dir()?;
TreeType::EndTree
}
TreeType::Other((path, mut node, open)) => {
let be = self.be.clone();
let be = be.clone();
let parent = self.is_parent(&node, &node.name());
let parent = match parent {
ParentResult::Matched(p_node) => {
Expand Down
53 changes: 18 additions & 35 deletions crates/rustic_core/src/backend/ignore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,73 +63,55 @@ pub struct LocalSourceSaveOptions {
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
pub struct LocalSourceFilterOptions {
/// Glob pattern to exclude/include (can be specified multiple times)
#[cfg_attr(feature = "clap", clap(long, help_heading = "Exclude options"))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = merge::vec::overwrite_empty))]
glob: Vec<String>,

/// Same as --glob pattern but ignores the casing of filenames
#[cfg_attr(
feature = "clap",
clap(long, value_name = "GLOB", help_heading = "Exclude options")
)]
#[cfg_attr(feature = "clap", clap(long, value_name = "GLOB"))]
#[cfg_attr(feature = "merge", merge(strategy = merge::vec::overwrite_empty))]
iglob: Vec<String>,

/// Read glob patterns to exclude/include from this file (can be specified multiple times)
#[cfg_attr(
feature = "clap",
clap(long, value_name = "FILE", help_heading = "Exclude options")
)]
#[cfg_attr(feature = "clap", clap(long, value_name = "FILE"))]
#[cfg_attr(feature = "merge", merge(strategy = merge::vec::overwrite_empty))]
glob_file: Vec<String>,

/// Same as --glob-file ignores the casing of filenames in patterns
#[cfg_attr(
feature = "clap",
clap(long, value_name = "FILE", help_heading = "Exclude options")
)]
#[cfg_attr(feature = "clap", clap(long, value_name = "FILE"))]
#[cfg_attr(feature = "merge", merge(strategy = merge::vec::overwrite_empty))]
iglob_file: Vec<String>,

/// Ignore files based on .gitignore files
#[cfg_attr(feature = "clap", clap(long, help_heading = "Exclude options"))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = merge::bool::overwrite_false))]
git_ignore: bool,

/// Do not require a git repository to apply git-ignore rule
#[cfg_attr(feature = "clap", clap(long, help_heading = "Exclude options"))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = merge::bool::overwrite_false))]
no_require_git: bool,

/// Exclude contents of directories containing this filename (can be specified multiple times)
#[cfg_attr(
feature = "clap",
clap(long, value_name = "FILE", help_heading = "Exclude options")
)]
#[cfg_attr(feature = "clap", clap(long, value_name = "FILE"))]
#[cfg_attr(feature = "merge", merge(strategy = merge::vec::overwrite_empty))]
exclude_if_present: Vec<String>,

/// Exclude other file systems, don't cross filesystem boundaries and subvolumes
#[cfg_attr(
feature = "clap",
clap(long, short = 'x', help_heading = "Exclude options")
)]
#[cfg_attr(feature = "clap", clap(long, short = 'x'))]
#[cfg_attr(feature = "merge", merge(strategy = merge::bool::overwrite_false))]
one_file_system: bool,

/// Maximum size of files to be backuped. Larger files will be excluded.
#[cfg_attr(
feature = "clap",
clap(long, value_name = "SIZE", help_heading = "Exclude options")
)]
#[cfg_attr(feature = "clap", clap(long, value_name = "SIZE"))]
#[serde_as(as = "Option<DisplayFromStr>")]
exclude_larger_than: Option<ByteSize>,
}

impl LocalSource {
pub fn new(
save_opts: LocalSourceSaveOptions,
filter_opts: LocalSourceFilterOptions,
filter_opts: &LocalSourceFilterOptions,
backup_paths: &[impl AsRef<Path>],
) -> RusticResult<Self> {
let mut walk_builder = WalkBuilder::new(&backup_paths[0]);
Expand All @@ -140,13 +122,13 @@ impl LocalSource {

let mut override_builder = OverrideBuilder::new("/");

for g in filter_opts.glob {
for g in &filter_opts.glob {
_ = override_builder
.add(&g)
.add(g)
.map_err(IgnoreErrorKind::GenericError)?;
}

for file in filter_opts.glob_file {
for file in &filter_opts.glob_file {
for line in std::fs::read_to_string(file)
.map_err(IgnoreErrorKind::FromIoError)?
.lines()
Expand All @@ -160,13 +142,13 @@ impl LocalSource {
_ = override_builder
.case_insensitive(true)
.map_err(IgnoreErrorKind::GenericError)?;
for g in filter_opts.iglob {
for g in &filter_opts.iglob {
_ = override_builder
.add(&g)
.add(g)
.map_err(IgnoreErrorKind::GenericError)?;
}

for file in filter_opts.iglob_file {
for file in &filter_opts.iglob_file {
for line in std::fs::read_to_string(file)
.map_err(IgnoreErrorKind::FromIoError)?
.lines()
Expand All @@ -192,10 +174,11 @@ impl LocalSource {
.map_err(IgnoreErrorKind::GenericError)?,
);

let exclude_if_present = filter_opts.exclude_if_present.clone();
if !filter_opts.exclude_if_present.is_empty() {
_ = walk_builder.filter_entry(move |entry| match entry.file_type() {
Some(tpe) if tpe.is_dir() => {
for file in &filter_opts.exclude_if_present {
for file in &exclude_if_present {
if entry.path().join(file).exists() {
return false;
}
Expand Down
1 change: 1 addition & 0 deletions crates/rustic_core/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod backup;
pub mod cat;
pub mod check;
pub mod config;
Expand Down
Loading

0 comments on commit ce40f1d

Please sign in to comment.