Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 59 additions & 9 deletions crates/cli/src/commands/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,8 @@ async fn copy_local_to_s3(
}
}

/// Multipart upload threshold: files at least this size use multipart upload (64 MiB)
const MULTIPART_THRESHOLD: u64 = 64 * 1024 * 1024;
/// Multipart upload threshold: files larger than this size use multipart upload.
const MULTIPART_THRESHOLD: u64 = rc_s3::multipart::DEFAULT_PART_SIZE;
/// Download progress threshold: avoid flicker for tiny downloads while surfacing meaningful waits.
const DOWNLOAD_PROGRESS_THRESHOLD: u64 = 4 * 1024 * 1024;

Expand Down Expand Up @@ -277,12 +277,6 @@ async fn upload_file(
return ExitCode::Success;
}

// Determine content type
let guessed_type: Option<String> = mime_guess::from_path(src)
.first()
.map(|m| m.essence_str().to_string());
let content_type = args.content_type.as_deref().or(guessed_type.as_deref());

// Get file size for progress bar decision
let file_size = match std::fs::metadata(src) {
Ok(m) => m.len(),
Expand All @@ -294,8 +288,18 @@ async fn upload_file(
}
};

// Determine content type
let guessed_type: Option<String> = mime_guess::from_path(src)
.first()
.map(|m| m.essence_str().to_string());
let content_type = select_upload_content_type(
args.content_type.as_deref(),
guessed_type.as_deref(),
file_size,
);

Comment thread
overtrue marked this conversation as resolved.
// Show progress bar for large files
let progress = if file_size >= MULTIPART_THRESHOLD {
let progress = if file_size > MULTIPART_THRESHOLD {
tracing::debug!(
file_size,
threshold = MULTIPART_THRESHOLD,
Expand Down Expand Up @@ -335,6 +339,18 @@ async fn upload_file(
}
}

fn select_upload_content_type<'a>(
explicit_type: Option<&'a str>,
guessed_type: Option<&'a str>,
file_size: u64,
) -> Option<&'a str> {
if file_size > MULTIPART_THRESHOLD {
explicit_type
} else {
explicit_type.or(guessed_type)
}
}

async fn upload_directory(
client: &S3Client,
src: &Path,
Expand Down Expand Up @@ -862,6 +878,40 @@ mod tests {
assert!(!progress.is_visible());
}

#[test]
fn test_select_upload_content_type_uses_guess_for_small_files() {
let selected =
select_upload_content_type(None, Some("text/plain"), MULTIPART_THRESHOLD - 1);

assert_eq!(selected, Some("text/plain"));
}

#[test]
fn test_select_upload_content_type_skips_guess_for_multipart_files() {
let selected =
select_upload_content_type(None, Some("text/plain"), MULTIPART_THRESHOLD + 1);

assert_eq!(selected, None);
}

#[test]
fn test_select_upload_content_type_uses_guess_at_multipart_boundary() {
let selected = select_upload_content_type(None, Some("text/plain"), MULTIPART_THRESHOLD);

assert_eq!(selected, Some("text/plain"));
}

#[test]
fn test_select_upload_content_type_keeps_explicit_type_for_multipart_files() {
let selected = select_upload_content_type(
Some("application/octet-stream"),
Some("text/plain"),
MULTIPART_THRESHOLD + 1,
);

assert_eq!(selected, Some("application/octet-stream"));
}

#[test]
fn test_parse_cp_path_prefers_existing_local_path_when_alias_missing() {
let (alias_manager, temp_dir) = temp_alias_manager();
Expand Down
Loading