mirror of
https://github.com/rustic-rs/rustic.git
synced 2025-10-26 11:18:51 +00:00
diff/restore: Treat single file destination properly
This commit is contained in:
parent
34a0667e5d
commit
db671febfd
@ -146,7 +146,39 @@ impl WriteBackend for LocalBackend {
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalBackend {
|
||||
#[derive(Clone)]
|
||||
pub struct LocalDestination {
|
||||
path: PathBuf,
|
||||
is_file: bool,
|
||||
}
|
||||
|
||||
impl LocalDestination {
|
||||
pub fn new(path: &str, create: bool, expect_file: bool) -> Result<Self> {
|
||||
let is_dir = path.ends_with('/');
|
||||
let path: PathBuf = path.into();
|
||||
let is_file = path.is_file() || (!path.is_dir() && !is_dir && expect_file);
|
||||
|
||||
if create {
|
||||
if is_file {
|
||||
if let Some(path) = path.parent() {
|
||||
fs::create_dir_all(path)?;
|
||||
}
|
||||
} else {
|
||||
fs::create_dir_all(&path)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { path, is_file })
|
||||
}
|
||||
|
||||
fn path(&self, item: impl AsRef<Path>) -> PathBuf {
|
||||
if self.is_file {
|
||||
self.path.clone()
|
||||
} else {
|
||||
self.path.join(item)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_dir(&self, dirname: impl AsRef<Path>) -> Result<()> {
|
||||
Ok(fs::remove_dir_all(dirname)?)
|
||||
}
|
||||
@ -162,7 +194,7 @@ impl LocalBackend {
|
||||
}
|
||||
|
||||
pub fn set_times(&self, item: impl AsRef<Path>, meta: &Metadata) -> Result<()> {
|
||||
let filename = self.path.join(item);
|
||||
let filename = self.path(item);
|
||||
if let Some(mtime) = meta.mtime.map(|t| FileTime::from_system_time(t.into())) {
|
||||
set_file_mtime(&filename, mtime)?;
|
||||
}
|
||||
@ -173,7 +205,7 @@ impl LocalBackend {
|
||||
}
|
||||
|
||||
pub fn set_user_group(&self, item: impl AsRef<Path>, meta: &Metadata) -> Result<()> {
|
||||
let filename = self.path.join(item);
|
||||
let filename = self.path(item);
|
||||
|
||||
let user = meta
|
||||
.user
|
||||
@ -195,7 +227,7 @@ impl LocalBackend {
|
||||
}
|
||||
|
||||
pub fn set_uid_gid(&self, item: impl AsRef<Path>, meta: &Metadata) -> Result<()> {
|
||||
let filename = self.path.join(item);
|
||||
let filename = self.path(item);
|
||||
|
||||
let uid = meta.uid.map(Uid::from_raw);
|
||||
let gid = meta.gid.map(Gid::from_raw);
|
||||
@ -205,7 +237,7 @@ impl LocalBackend {
|
||||
}
|
||||
|
||||
pub fn set_permission(&self, item: impl AsRef<Path>, meta: &Metadata) -> Result<()> {
|
||||
let filename = self.path.join(item);
|
||||
let filename = self.path(item);
|
||||
|
||||
if let Some(mode) = meta.mode() {
|
||||
let mode = map_mode_from_go(*mode);
|
||||
@ -216,7 +248,7 @@ impl LocalBackend {
|
||||
|
||||
// set_length sets the length of the given file. If it doesn't exist, create a new (empty) one with given length
|
||||
pub fn set_length(&self, item: impl AsRef<Path>, size: u64) -> Result<()> {
|
||||
let filename = self.path.join(item);
|
||||
let filename = self.path(item);
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
@ -226,7 +258,7 @@ impl LocalBackend {
|
||||
}
|
||||
|
||||
pub fn create_special(&self, item: impl AsRef<Path>, node: &Node) -> Result<()> {
|
||||
let filename = self.path.join(item);
|
||||
let filename = self.path(item);
|
||||
|
||||
match node.node_type() {
|
||||
NodeType::Symlink { linktarget } => {
|
||||
@ -270,7 +302,7 @@ impl LocalBackend {
|
||||
}
|
||||
|
||||
pub fn read_at(&self, item: impl AsRef<Path>, offset: u64, length: u64) -> Result<Bytes> {
|
||||
let filename = self.path.join(item);
|
||||
let filename = self.path(item);
|
||||
let mut file = File::open(filename)?;
|
||||
file.seek(SeekFrom::Start(offset))?;
|
||||
let mut vec = vec![0; length.try_into()?];
|
||||
@ -279,7 +311,7 @@ impl LocalBackend {
|
||||
}
|
||||
|
||||
pub fn get_matching_file(&self, item: impl AsRef<Path>, size: u64) -> Option<File> {
|
||||
let filename = self.path.join(item);
|
||||
let filename = self.path(item);
|
||||
match fs::symlink_metadata(&filename) {
|
||||
Ok(meta) => {
|
||||
if meta.is_file() && meta.len() == size {
|
||||
@ -293,7 +325,7 @@ impl LocalBackend {
|
||||
}
|
||||
|
||||
pub fn write_at(&self, item: impl AsRef<Path>, offset: u64, data: &[u8]) -> Result<()> {
|
||||
let filename = self.path.join(item);
|
||||
let filename = self.path(item);
|
||||
let file = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
|
||||
@ -5,7 +5,7 @@ use anyhow::{anyhow, bail, Context, Result};
|
||||
use clap::Parser;
|
||||
|
||||
use super::{progress_counter, RusticConfig};
|
||||
use crate::backend::{LocalBackend, LocalSource, LocalSourceOptions};
|
||||
use crate::backend::{LocalDestination, LocalSource, LocalSourceOptions};
|
||||
use crate::blob::{Node, NodeStreamer, NodeType, Tree};
|
||||
use crate::commands::helpers::progress_spinner;
|
||||
use crate::crypto::hash;
|
||||
@ -79,7 +79,7 @@ pub(super) fn execute(
|
||||
|
||||
let index = IndexBackend::new(be, progress_counter(""))?;
|
||||
let node1 = Tree::node_from_path(&index, snap1.tree, Path::new(path1))?;
|
||||
let local = LocalBackend::new(path2)?;
|
||||
let local = LocalDestination::new(path2, false, !node1.is_dir())?;
|
||||
let path2 = PathBuf::from(path2);
|
||||
let is_dir = path2
|
||||
.metadata()
|
||||
@ -123,7 +123,7 @@ fn arg_to_snap_path<'a>(arg: &'a str, default_path: &'a str) -> (Option<&'a str>
|
||||
}
|
||||
|
||||
fn identical_content_local(
|
||||
local: &LocalBackend,
|
||||
local: &LocalDestination,
|
||||
index: &impl ReadIndex,
|
||||
path: &Path,
|
||||
node: &Node,
|
||||
|
||||
@ -15,7 +15,7 @@ use rayon::ThreadPoolBuilder;
|
||||
|
||||
use super::rustic_config::RusticConfig;
|
||||
use super::{bytes, progress_bytes, progress_counter, warm_up_wait};
|
||||
use crate::backend::{DecryptReadBackend, FileType, LocalBackend};
|
||||
use crate::backend::{DecryptReadBackend, FileType, LocalDestination};
|
||||
use crate::blob::{Node, NodeStreamer, NodeType, Tree};
|
||||
use crate::commands::helpers::progress_spinner;
|
||||
use crate::crypto::hash;
|
||||
@ -89,7 +89,7 @@ pub(super) fn execute(
|
||||
let index = IndexBackend::new(be, progress_counter(""))?;
|
||||
let node = Tree::node_from_path(&index, snap.tree, Path::new(path))?;
|
||||
|
||||
let dest = LocalBackend::new(&opts.dest)?;
|
||||
let dest = LocalDestination::new(&opts.dest, true, !node.is_dir())?;
|
||||
|
||||
let p = progress_spinner("collecting file information...");
|
||||
let (file_infos, stats) = allocate_and_collect(&dest, index.clone(), &node, &opts)?;
|
||||
@ -150,7 +150,7 @@ struct RestoreStats {
|
||||
|
||||
/// collect restore information, scan existing files and allocate non-existing files
|
||||
fn allocate_and_collect(
|
||||
dest: &LocalBackend,
|
||||
dest: &LocalDestination,
|
||||
index: impl IndexedBackend + Unpin,
|
||||
node: &Node,
|
||||
opts: &Opts,
|
||||
@ -332,7 +332,7 @@ fn allocate_and_collect(
|
||||
/// using the [`DecryptReadBackend`] `be` and writing them into the [`LocalBackend`] `dest`.
|
||||
fn restore_contents(
|
||||
be: &impl DecryptReadBackend,
|
||||
dest: &LocalBackend,
|
||||
dest: &LocalDestination,
|
||||
file_infos: FileInfos,
|
||||
) -> Result<()> {
|
||||
let (filenames, restore_info, total_size, _) = file_infos.dissolve();
|
||||
@ -400,7 +400,7 @@ fn restore_contents(
|
||||
}
|
||||
|
||||
fn restore_metadata(
|
||||
dest: &LocalBackend,
|
||||
dest: &LocalDestination,
|
||||
index: impl IndexedBackend + Unpin,
|
||||
node: &Node,
|
||||
opts: &Opts,
|
||||
@ -435,7 +435,7 @@ fn restore_metadata(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_metadata(dest: &LocalBackend, path: &PathBuf, node: &Node, opts: &Opts) {
|
||||
fn set_metadata(dest: &LocalDestination, path: &PathBuf, node: &Node, opts: &Opts) {
|
||||
debug!("setting metadata for {:?}", path);
|
||||
dest.create_special(path, node)
|
||||
.unwrap_or_else(|_| warn!("restore {:?}: creating special file failed.", path));
|
||||
@ -512,7 +512,7 @@ impl FileInfos {
|
||||
/// Returns the computed length of the file
|
||||
fn add_file(
|
||||
&mut self,
|
||||
dest: &LocalBackend,
|
||||
dest: &LocalDestination,
|
||||
file: &Node,
|
||||
name: PathBuf,
|
||||
index: &impl IndexedBackend,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user