Find shared directory for multiple files, use as archive base directory
This commit is contained in:
parent
dee5289597
commit
58d44f09d9
1 changed files with 104 additions and 24 deletions
|
@ -3,6 +3,8 @@ use std::env::current_dir;
|
||||||
use std::io::Error as IoError;
|
use std::io::Error as IoError;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
#[cfg(feature = "archive")]
|
#[cfg(feature = "archive")]
|
||||||
|
use std::path::PathBuf;
|
||||||
|
#[cfg(feature = "archive")]
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
@ -57,7 +59,11 @@ impl<'a> Upload<'a> {
|
||||||
|
|
||||||
// Get API parameters
|
// Get API parameters
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut paths = matcher_upload.files();
|
let mut paths: Vec<_> = matcher_upload
|
||||||
|
.files()
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| Path::new(p).to_path_buf())
|
||||||
|
.collect();
|
||||||
let mut path = Path::new(paths.first().unwrap()).to_path_buf();
|
let mut path = Path::new(paths.first().unwrap()).to_path_buf();
|
||||||
let host = matcher_upload.host();
|
let host = matcher_upload.host();
|
||||||
|
|
||||||
|
@ -65,6 +71,17 @@ impl<'a> Upload<'a> {
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut file_name = matcher_upload.name().map(|s| s.to_owned());
|
let mut file_name = matcher_upload.name().map(|s| s.to_owned());
|
||||||
|
|
||||||
|
// All paths must exist
|
||||||
|
// TODO: ensure the file exists and is accessible
|
||||||
|
for path in &paths {
|
||||||
|
if !path.exists() {
|
||||||
|
quit_error_msg(
|
||||||
|
format!("the path '{}' does not exist", path.to_str().unwrap_or("?")),
|
||||||
|
ErrorHintsBuilder::default().build().unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A temporary archive file, only used when archiving
|
// A temporary archive file, only used when archiving
|
||||||
// The temporary file is stored here, to ensure it's lifetime exceeds the upload process
|
// The temporary file is stored here, to ensure it's lifetime exceeds the upload process
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
|
@ -148,37 +165,34 @@ impl<'a> Upload<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current working directory, canonicalize it
|
// Get the current working directory, including working directory as highest possible root, canonicalize it
|
||||||
let mut working_dir =
|
let working_dir =
|
||||||
current_dir().expect("failed to get current working directory");
|
current_dir().expect("failed to get current working directory");
|
||||||
if let Ok(p) = working_dir.canonicalize() {
|
let shared_dir = {
|
||||||
working_dir = p;
|
let mut paths = paths.clone();
|
||||||
}
|
paths.push(working_dir.clone());
|
||||||
|
match shared_dir(paths) {
|
||||||
|
Some(p) => p,
|
||||||
|
None => quit_error_msg(
|
||||||
|
"when archiving, all files must be within a same directory",
|
||||||
|
ErrorHintsBuilder::default().verbose(false).build().unwrap(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Build an archiver, append each file
|
// Build an archiver, append each file
|
||||||
let mut archiver = Archiver::new(archive_file);
|
let mut archiver = Archiver::new(archive_file);
|
||||||
for path in &paths {
|
for path in &paths {
|
||||||
// Find the relative path, get path name of file to add
|
// Canonicalize the path
|
||||||
let mut path = Path::new(path).to_path_buf();
|
let mut path = Path::new(path).to_path_buf();
|
||||||
if let Ok(p) = path.canonicalize() {
|
if let Ok(p) = path.canonicalize() {
|
||||||
path = p;
|
path = p;
|
||||||
}
|
}
|
||||||
let path = diff_paths(&path, &working_dir)
|
|
||||||
.expect("failed to determine relative path of file to archive");
|
|
||||||
let name = path.to_str().expect("failed to get file path");
|
|
||||||
|
|
||||||
// Files must all be in this directory
|
// Find relative name to share dir, used to derive name from
|
||||||
// TODO: find shared parent, and archive from there instead
|
let name = diff_paths(&path, &shared_dir)
|
||||||
if !matcher_main.force() && name.contains("..") {
|
.expect("failed to determine relative path of file to archive");
|
||||||
quit_error_msg(
|
let name = name.to_str().expect("failed to get file path");
|
||||||
"when archiving, all files must be within the current working directory",
|
|
||||||
ErrorHintsBuilder::default()
|
|
||||||
.force(true)
|
|
||||||
.verbose(false)
|
|
||||||
.build()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add file to archiver
|
// Add file to archiver
|
||||||
archiver
|
archiver
|
||||||
|
@ -231,8 +245,6 @@ impl<'a> Upload<'a> {
|
||||||
select_api_version(&client, host.clone(), &mut desired_version)?;
|
select_api_version(&client, host.clone(), &mut desired_version)?;
|
||||||
let api_version = desired_version.version().unwrap();
|
let api_version = desired_version.version().unwrap();
|
||||||
|
|
||||||
// TODO: ensure the file exists and is accessible
|
|
||||||
|
|
||||||
// We do not authenticate for now
|
// We do not authenticate for now
|
||||||
let auth = false;
|
let auth = false;
|
||||||
|
|
||||||
|
@ -465,6 +477,74 @@ impl<'a> Upload<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find the deepest directory all given paths share.
|
||||||
|
///
|
||||||
|
/// This function canonicalizes the paths, make sure the paths exist.
|
||||||
|
///
|
||||||
|
/// Returns `None` if paths are using a different root.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// If the following paths are given:
|
||||||
|
///
|
||||||
|
/// - `/home/user/git/ffsend/src`
|
||||||
|
/// - `/home/user/git/ffsend/src/main.rs`
|
||||||
|
/// - `/home/user/git/ffsend/Cargo.toml`
|
||||||
|
///
|
||||||
|
/// The following is returned:
|
||||||
|
///
|
||||||
|
/// `/home/user/git/ffsend`
|
||||||
|
#[cfg(feature = "archive")]
|
||||||
|
fn shared_dir(paths: Vec<PathBuf>) -> Option<PathBuf> {
|
||||||
|
// Any path must be given
|
||||||
|
if paths.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build vector
|
||||||
|
let c: Vec<Vec<PathBuf>> = paths
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| p.canonicalize().expect("failed to canonicalize path"))
|
||||||
|
.map(|mut p| {
|
||||||
|
// Start with parent if current path is file
|
||||||
|
if p.is_file() {
|
||||||
|
p = match p.parent() {
|
||||||
|
Some(p) => p.to_path_buf(),
|
||||||
|
None => return vec![],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build list of path buffers for each path component
|
||||||
|
let mut items = vec![p];
|
||||||
|
#[allow(mutable_borrow_reservation_conflict)]
|
||||||
|
while let Some(item) = items.last().unwrap().parent() {
|
||||||
|
items.push(item.to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse as we built it in the wrong order
|
||||||
|
items.reverse();
|
||||||
|
items
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Find the index at which the paths are last shared at by walking through indices
|
||||||
|
let i = (0..)
|
||||||
|
.take_while(|i| {
|
||||||
|
// Get path for first item, stop if none
|
||||||
|
let base = &c[0].get(*i);
|
||||||
|
if base.is_none() {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// All other paths must equal at this index
|
||||||
|
c.iter().skip(1).all(|p| &p.get(*i) == base)
|
||||||
|
})
|
||||||
|
.last();
|
||||||
|
|
||||||
|
// Find the shared path
|
||||||
|
i.map(|i| c[0][i].to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Fail)]
|
#[derive(Debug, Fail)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Selecting the API version to use failed.
|
/// Selecting the API version to use failed.
|
||||||
|
|
Loading…
Reference in a new issue