Skip to content

Commit

Permalink
Add support for downloading file to stdout
Browse files Browse the repository at this point in the history
  • Loading branch information
prasmussen committed Mar 11, 2023
1 parent 77e1e1b commit 27dbaa6
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 29 deletions.
112 changes: 84 additions & 28 deletions src/files/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,46 @@ pub struct Config {
pub existing_file_action: ExistingFileAction,
pub follow_shortcuts: bool,
pub download_directories: bool,
pub destination_root: Option<PathBuf>,
pub destination: Destination,
}

impl Config {
fn canonical_destination_root(&self) -> Result<PathBuf, Error> {
if let Some(path) = &self.destination_root {
if !path.exists() {
Err(Error::DestinationPathDoesNotExist(path.clone()))
} else if !path.is_dir() {
Err(Error::DestinationPathNotADirectory(path.clone()))
} else {
path.canonicalize()
.map_err(|err| Error::CanonicalizeDestinationPath(path.clone(), err))
match &self.destination {
Destination::CurrentDir => {
let current_path = PathBuf::from(".");
let canonical_current_path = current_path
.canonicalize()
.map_err(|err| Error::CanonicalizeDestinationPath(current_path.clone(), err))?;
Ok(canonical_current_path)
}

Destination::Path(path) => {
if !path.exists() {
Err(Error::DestinationPathDoesNotExist(path.clone()))
} else if !path.is_dir() {
Err(Error::DestinationPathNotADirectory(path.clone()))
} else {
path.canonicalize()
.map_err(|err| Error::CanonicalizeDestinationPath(path.clone(), err))
}
}

Destination::Stdout => {
// fmt
Err(Error::StdoutNotValidDestination)
}
} else {
let current_path = PathBuf::from(".");
let canonical_current_path = current_path
.canonicalize()
.map_err(|err| Error::CanonicalizeDestinationPath(current_path.clone(), err))?;
Ok(canonical_current_path)
}
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Destination {
CurrentDir,
Path(PathBuf),
Stdout,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ExistingFileAction {
Abort,
Expand Down Expand Up @@ -91,17 +107,26 @@ pub async fn download_regular(
file: &google_drive3::api::File,
config: &Config,
) -> Result<(), Error> {
let file_name = file.name.clone().ok_or(Error::MissingFileName)?;
let root_path = config.canonical_destination_root()?;
let abs_file_path = root_path.join(&file_name);

let body = download_file(&hub, &config.file_id)
.await
.map_err(Error::DownloadFile)?;

println!("Downloading {}", file_name);
save_body_to_file(body, &abs_file_path, file.md5_checksum.clone()).await?;
println!("Successfully downloaded {}", file_name);
match &config.destination {
Destination::Stdout => {
// fmt
save_body_to_stdout(body).await?;
}

_ => {
let file_name = file.name.clone().ok_or(Error::MissingFileName)?;
let root_path = config.canonical_destination_root()?;
let abs_file_path = root_path.join(&file_name);

println!("Downloading {}", file_name);
save_body_to_file(body, &abs_file_path, file.md5_checksum.clone()).await?;
println!("Successfully downloaded {}", file_name);
}
}

Ok(())
}
Expand Down Expand Up @@ -195,6 +220,7 @@ pub enum Error {
CanonicalizeDestinationPath(PathBuf, io::Error),
MissingShortcutTarget,
IsShortcut(String),
StdoutNotValidDestination,
}

impl error::Error for Error {}
Expand Down Expand Up @@ -258,6 +284,10 @@ impl Display for Error {
"'{}' is a shortcut, use --follow-shortcuts to download the file it points to",
name
),
Error::StdoutNotValidDestination => write!(
f,
"Stdout is not a valid destination for this combination of options"
),
}
}
}
Expand Down Expand Up @@ -288,15 +318,41 @@ pub async fn save_body_to_file(
fs::rename(&tmp_file_path, &file_path).map_err(Error::RenameFile)
}

// TODO: move to common
pub async fn save_body_to_stdout(mut body: hyper::Body) -> Result<(), Error> {
let mut stdout = io::stdout();

// Read chunks from stream and write to stdout
while let Some(chunk_result) = body.next().await {
let chunk = chunk_result.map_err(Error::ReadChunk)?;
stdout.write_all(&chunk).map_err(Error::WriteChunk)?;
}

Ok(())
}

fn err_if_file_exists(file: &google_drive3::api::File, config: &Config) -> Result<(), Error> {
let file_name = file.name.clone().ok_or(Error::MissingFileName)?;
let root_path = config.canonical_destination_root()?;
let file_path = root_path.join(&file_name);

if file_path.exists() && config.existing_file_action == ExistingFileAction::Abort {
Err(Error::FileExists(file_path.clone()))
} else {
Ok(())
let file_path = match &config.destination {
Destination::CurrentDir => Some(PathBuf::from(".").join(file_name)),
Destination::Path(path) => Some(path.join(file_name)),
Destination::Stdout => None,
};

match file_path {
Some(path) => {
if path.exists() && config.existing_file_action == ExistingFileAction::Abort {
Err(Error::FileExists(path.clone()))
} else {
Ok(())
}
}

None => {
// fmt
Ok(())
}
}
}

Expand Down
15 changes: 14 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ enum FileCommand {
/// Path where the file/directory should be downloaded to
#[arg(long, value_name = "PATH")]
destination: Option<PathBuf>,

/// Write file to stdout
#[arg(long)]
stdout: bool,
},

/// Upload file
Expand Down Expand Up @@ -493,19 +497,28 @@ async fn main() {
follow_shortcuts,
recursive,
destination,
stdout,
} => {
let existing_file_action = if overwrite {
files::download::ExistingFileAction::Overwrite
} else {
files::download::ExistingFileAction::Abort
};

let dst = if stdout {
files::download::Destination::Stdout
} else if let Some(path) = destination {
files::download::Destination::Path(path)
} else {
files::download::Destination::CurrentDir
};

files::download(files::download::Config {
file_id,
existing_file_action,
follow_shortcuts,
download_directories: recursive,
destination_root: destination,
destination: dst,
})
.await
.unwrap_or_else(handle_error)
Expand Down

0 comments on commit 27dbaa6

Please sign in to comment.