From f86c5f1dd08fa0af892a9c27879bf28fdaa19a27 Mon Sep 17 00:00:00 2001 From: Mark Saroufim Date: Tue, 10 Feb 2026 15:51:38 -0800 Subject: [PATCH] Support archive (tarball/zip) submissions for model competitions - Read submission files as bytes (Vec) instead of String to handle binary archives without crashing on invalid UTF-8 - Change submit_solution signature from &str to &[u8] for file content - Add is_archive_file() helper to detect .tar.gz, .tgz, .zip files - Skip popcorn directive parsing for archive files (return empty directives) --- src/cmd/submit.rs | 12 ++++++------ src/service/mod.rs | 6 +++--- src/utils/mod.rs | 21 +++++++++++++++++++++ 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/cmd/submit.rs b/src/cmd/submit.rs index a508b12..ec8e6fb 100644 --- a/src/cmd/submit.rs +++ b/src/cmd/submit.rs @@ -295,10 +295,10 @@ impl App { .clone() .ok_or_else(|| anyhow!("Submission mode not selected"))?; - // Read file content + // Read file content as bytes (supports both text and archive files) let mut file = File::open(&filepath)?; - let mut file_content = String::new(); - file.read_to_string(&mut file_content)?; + let mut file_content = Vec::new(); + file.read_to_end(&mut file_content)?; self.submission_task = Some(tokio::spawn(async move { service::submit_solution( @@ -734,10 +734,10 @@ pub async fn run_submit_plain( anyhow!("Submission mode not specified. Use --mode flag (test, benchmark, leaderboard, profile)") })?; - // Read file content + // Read file content as bytes (supports both text and archive files) let mut file = File::open(&file_to_submit)?; - let mut file_content = String::new(); - file.read_to_string(&mut file_content)?; + let mut file_content = Vec::new(); + file.read_to_end(&mut file_content)?; eprintln!("Submitting to leaderboard: {}", final_leaderboard); eprintln!("GPU: {}", final_gpu); diff --git a/src/service/mod.rs b/src/service/mod.rs index 6293696..906877a 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -450,7 +450,7 @@ pub async fn delete_user_submission(client: &Client, submission_id: i64) -> Resu pub async fn submit_solution>( client: &Client, filepath: P, - file_content: &str, + file_content: &[u8], leaderboard: &str, gpu: &str, submission_mode: &str, @@ -465,7 +465,7 @@ pub async fn submit_solution>( .ok_or_else(|| anyhow!("Invalid filepath"))? .to_string_lossy(); - let part = Part::bytes(file_content.as_bytes().to_vec()).file_name(filename.to_string()); + let part = Part::bytes(file_content.to_vec()).file_name(filename.to_string()); let form = Form::new().part("file", part); @@ -824,7 +824,7 @@ mod tests { let result = submit_solution( &client, "test.py", - "print('hello')", + b"print('hello')", "test-leaderboard", "H100", "test", diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 383a171..7bf7b7a 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -7,7 +7,28 @@ pub struct PopcornDirectives { pub gpus: Vec, } +pub fn is_archive_file>(filepath: P) -> bool { + let path = filepath.as_ref(); + let name = path + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_lowercase(); + name.ends_with(".tar.gz") || name.ends_with(".tgz") || name.ends_with(".zip") +} + pub fn get_popcorn_directives>(filepath: P) -> Result<(PopcornDirectives, bool)> { + // Archive files (tarballs, zips) are binary and cannot contain directives + if is_archive_file(&filepath) { + return Ok(( + PopcornDirectives { + leaderboard_name: String::new(), + gpus: Vec::new(), + }, + false, + )); + } + let content = fs::read_to_string(filepath)?; let mut gpus: Vec = Vec::new();