use crate::{files, Files};
use flume as mpsc;
use futures::future::join_all;
use infinitree::{fields::QueryAction, object, Infinitree, *};
use memmap2::MmapOptions;
use std::{env, path::PathBuf, sync::Arc};
use tokio::task;
use tracing::{error, trace};
type ThreadWork = (PathBuf, Arc<files::Entry>);
type Sender = mpsc::Sender<ThreadWork>;
type Receiver = mpsc::Receiver<ThreadWork>;
pub type FileIterator<'a> = Box<(dyn Iterator<Item = (String, Arc<files::Entry>)> + Send + 'a)>;
#[derive(clap::Args, Debug, Clone, Default)]
pub struct Options {
pub globs: Vec<String>,
#[clap(flatten)]
pub preserve: files::PreserveMetadata,
#[clap(short = 'f', long)]
pub force: bool,
#[clap(short = 'M', long = "max-size")]
pub max_size: Option<u64>,
#[clap(short = 'm', long = "min-size")]
pub min_size: Option<u64>,
#[clap(short = 'c', long = "chdir")]
pub chdir: Option<PathBuf>,
#[cfg(target_family = "unix")]
#[clap(short = 'C', long = "chroot")]
pub chroot: Option<PathBuf>,
}
fn iter<V: AsRef<[T]>, T: AsRef<str>>(stash: &Infinitree<Files>, glob: V) -> FileIterator {
let matchers = glob
.as_ref()
.iter()
.map(|g| glob::Pattern::new(g.as_ref()).unwrap())
.collect::<Vec<glob::Pattern>>();
let match_c = matchers.clone();
let filtered_tree = stash
.index()
.tree
.iter_files()
.filter(move |(path, _)| match_c.iter().any(|m| m.matches(path)))
.collect::<Vec<_>>();
let filtered_files = stash
.iter(stash.index().files(), move |fname| {
if matchers.iter().any(|m| m.matches(fname)) {
QueryAction::Take
} else {
QueryAction::Skip
}
})
.unwrap()
.filter_map(|(path, entry)| entry.map(|e| (path, e)));
Box::new(filtered_files.chain(filtered_tree))
}
impl Options {
pub fn list<'stash>(
&'stash self,
stash: &'stash Infinitree<Files>,
) -> impl Iterator<Item = (String, Arc<crate::files::Entry>)> + 'stash {
let globs = if !self.globs.is_empty() {
self.globs.clone()
} else {
vec!["*".into()]
};
iter(stash, globs).filter(|(_, md)| {
if let Some(max) = self.max_size {
if max > md.size {
return false;
}
}
if let Some(min) = self.min_size {
if min < md.size {
return false;
}
}
true
})
}
pub async fn from_iter(
&self,
stash: &Infinitree<Files>,
threads: usize,
) -> anyhow::Result<u64> {
self.setup_env()?;
let (sender, workers) = self.start_workers(stash, threads)?;
for (path, md) in self.list(stash) {
trace!(?path, "queued");
sender.send_async((path.into(), md)).await.unwrap();
}
drop(sender);
join_all(workers).await;
Ok(0)
}
#[cfg(unix)]
fn setup_env(&self) -> anyhow::Result<()> {
if let Some(ref path) = self.chroot {
std::os::unix::fs::chroot(path).unwrap();
}
if let Some(ref path) = self.chdir {
env::set_current_dir(path)?;
}
Ok(())
}
#[cfg(windows)]
fn setup_env(&self) -> anyhow::Result<()> {
if let Some(ref path) = self.chdir {
env::set_current_dir(path)?;
}
Ok(())
}
fn start_workers(
&self,
stash: &Infinitree<Files>,
threads: usize,
) -> anyhow::Result<(Sender, Vec<task::JoinHandle<()>>)> {
let mut preserve = self.preserve.clone();
#[cfg(not(target_os = "windows"))]
if !nix::unistd::Uid::effective().is_root() {
preserve.ownership = false;
}
let (sender, receiver) = mpsc::bounded(threads);
let workers = (0..threads)
.map(|_| {
task::spawn(process_packet_loop(
self.force,
preserve.clone(),
receiver.clone(),
stash.storage_reader().unwrap(),
))
})
.collect::<Vec<_>>();
Ok((sender, workers))
}
}
async fn process_packet_loop(
force: bool,
preserve: files::PreserveMetadata,
r: Receiver,
mut objreader: impl object::Reader + 'static,
) {
while let Ok((path, metadata)) = r.recv_async().await {
match metadata.restore_to(&path, &preserve) {
Ok(Some(fd)) => {
let mut mmap = unsafe {
MmapOptions::new()
.len(metadata.size as usize)
.map_mut(&fd)
.expect("mmap")
};
for (start, cp) in metadata.chunks.iter() {
let start = *start as usize;
objreader.read_chunk(cp, &mut mmap[start..]).unwrap();
}
trace!(?path, "restored");
}
Ok(None) => {
trace!(?path, file_type = ?metadata.file_type, "no chunks restored for file");
}
Err(error) => {
error!(%error, ?path, "failed to restore file");
if !force {
panic!("error while restoring file");
}
}
}
}
}