diff --git a/dragonfly-client/src/bin/dfget/main.rs b/dragonfly-client/src/bin/dfget/main.rs index 7ce3ceda..f1b6f69a 100644 --- a/dragonfly-client/src/bin/dfget/main.rs +++ b/dragonfly-client/src/bin/dfget/main.rs @@ -138,7 +138,7 @@ struct Args { long = "overwrite", default_value_t = false, env = "DFGET_OVERWRITE", - help = "Specify whether to overwrite the output file if it already exists. If it is true, dfget will overwrite the output file. If it is false, dfget will return an error if the output file already exists. Cannot be used with `--force-hard-link=true`" + help = "Specify whether to overwrite the output file if it already exists. If it is true, dfget will overwrite the output file. If it is false, dfget will skip the download and return success when the output file already exists. Cannot be used with `--force-hard-link=true`" )] overwrite: bool, @@ -991,6 +991,15 @@ async fn download( progress_bar: ProgressBar, download_client: DfdaemonDownloadClient, ) -> Result<()> { + if should_skip_existing_output(&args) { + info!( + "output path {} already exists, skip download", + args.output.display() + ); + progress_bar.finish_with_message(format!("{} (skipped)", args.output.display())); + return Ok(()); + } + let url = Url::parse(args.url.as_str()).or_err(ErrorType::ParseError)?; let object_storage = if object_storage::Scheme::is_supported(url.scheme()) { Some(ObjectStorage { @@ -1395,13 +1404,6 @@ fn validate_args(args: &Args) -> Result<()> { ))); } } - - if !args.overwrite && absolute_path.exists() { - return Err(Error::ValidationError(format!( - "output path {} is already exist", - args.output.to_string_lossy() - ))); - } } if let Some(piece_length) = args.piece_length { @@ -1435,6 +1437,10 @@ fn validate_args(args: &Args) -> Result<()> { Ok(()) } +fn should_skip_existing_output(args: &Args) -> bool { + !args.url.path().ends_with('/') && !args.overwrite && args.output.exists() +} + /// Validates that a path string is a normal relative path without unsafe components. /// /// This function ensures that a given path is both relative (doesn't start with '/') @@ -1541,6 +1547,19 @@ mod tests { let result = validate_args(&args); assert!(result.is_ok()); + + // Existing file is allowed and will be skipped at runtime by default. + let existing_file_path = tempdir.path().join("existing.txt"); + std::fs::File::create(&existing_file_path).unwrap(); + let args = Args::parse_from(vec![ + "dfget", + "http://test.local/existing.txt", + "--output", + existing_file_path.as_os_str().to_str().unwrap(), + ]); + + let result = validate_args(&args); + assert!(result.is_ok()); } #[test] @@ -1574,15 +1593,6 @@ mod tests { non_exist_dir_path.display() ), ), - ( - Args::parse_from(vec![ - "dfget", - "http://test.local/test.txt", - "--output", - file_path.as_os_str().to_str().unwrap(), - ]), - format!("output path {} is already exist", file_path.display()), - ), ( Args::parse_from(vec![ "dfget", @@ -1611,6 +1621,30 @@ mod tests { } } + #[test] + fn should_skip_existing_output_file() { + let tempdir = tempfile::tempdir().unwrap(); + let existing_file_path = tempdir.path().join("test.txt"); + std::fs::File::create(&existing_file_path).unwrap(); + + let args = Args::parse_from(vec![ + "dfget", + "http://test.local/test.txt", + "--output", + existing_file_path.as_os_str().to_str().unwrap(), + ]); + assert!(should_skip_existing_output(&args)); + + let args = Args::parse_from(vec![ + "dfget", + "http://test.local/test.txt", + "--output", + existing_file_path.as_os_str().to_str().unwrap(), + "--overwrite", + ]); + assert!(!should_skip_existing_output(&args)); + } + #[test] fn should_make_output_by_entry() { let url = Url::parse("http://example.com/root/").unwrap();