diff --git a/rust/CLAUDE.md b/rust/CLAUDE.md index 98ffd62dfc..fe6a85228e 100644 --- a/rust/CLAUDE.md +++ b/rust/CLAUDE.md @@ -2,7 +2,7 @@ ## Commit & PR Guidelines -⚠️ **CRITICAL: OVERRIDE DEFAULT TEMPLATE - DO NOT USE EMOJI OR "Generated with" TEXT** ⚠️ +⚠️ **CRITICAL: From the default template, use ONLY: Co-Authored-By: Claude ** ⚠️ ### Pre-commit/PR Checklist (RUN BEFORE EVERY COMMIT OR PR!) diff --git a/rust/src/api/auth.rs b/rust/src/api/auth.rs index c251391b21..1cea4bc81f 100644 --- a/rust/src/api/auth.rs +++ b/rust/src/api/auth.rs @@ -57,7 +57,10 @@ impl<'a> AuthClient<'a> { srp_m1: STANDARD.encode(client_proof), }; - log::debug!("Sending verify-session request: {request:?}"); + log::debug!( + "Sending verify-session request for session_id: {}", + request.session_id + ); self.api .post("/users/srp/verify-session", &request, None) diff --git a/rust/src/commands/account.rs b/rust/src/commands/account.rs index c32084251b..fec06e797e 100644 --- a/rust/src/commands/account.rs +++ b/rust/src/commands/account.rs @@ -234,10 +234,6 @@ async fn add_account( &master_key, )?; log::info!("Secret key decrypted, length: {}", secret_key.len()); - log::info!( - "Secret key hex (first 16 bytes): {}", - hex::encode(&secret_key[..16.min(secret_key.len())]) - ); // Get public key let public_key = decode_base64(&key_attributes.public_key)?; diff --git a/rust/src/commands/export.rs b/rust/src/commands/export.rs index 0d58b1411d..234214d013 100644 --- a/rust/src/commands/export.rs +++ b/rust/src/commands/export.rs @@ -766,7 +766,7 @@ async fn export_account(storage: &Storage, account: &Account, filter: &ExportFil let filename = file_path .file_name() .and_then(|n| n.to_str()) - .unwrap_or("file"); + .ok_or_else(|| crate::Error::Generic(format!("Invalid file path: {:?}", file_path)))?; let meta_path = write_file_metadata( export_path, &album_folder, @@ -864,10 +864,18 @@ fn generate_export_path( if let Some(title) = meta.get_title() { sanitize_filename(title) } else { - generate_fallback_filename(file, metadata) + // Match Go CLI behavior: error if no title found + return Err(crate::Error::Generic(format!( + "File {} has no title in metadata", + file.id + ))); } } else { - generate_fallback_filename(file, None) + // Match Go CLI behavior: error if no metadata + return Err(crate::Error::Generic(format!( + "File {} has no metadata", + file.id + ))); } }; path.push(filename); @@ -936,26 +944,7 @@ fn decrypt_file_key(encrypted_key: &str, nonce: &str, collection_key: &[u8]) -> secret_box_open(&encrypted_bytes, &nonce_bytes, collection_key) } -/// Generate a fallback filename when metadata is not available -fn generate_fallback_filename( - file: &crate::api::models::File, - metadata: Option<&FileMetadata>, -) -> String { - let extension = if let Some(meta) = metadata { - match meta.get_file_type() { - crate::models::metadata::FileType::Image => ".jpg", - crate::models::metadata::FileType::Video => ".mp4", - crate::models::metadata::FileType::LivePhoto => ".zip", - crate::models::metadata::FileType::Unknown => ".bin", - } - } else if file.thumbnail.size.unwrap_or(0) > 0 { - ".jpg" // Has thumbnail, likely an image - } else { - ".bin" - }; - - format!("file_{}{}", file.id, extension) -} +// Removed generate_fallback_filename - Go CLI panics if no title, we return error instead /// Sanitize a filename for the filesystem fn sanitize_filename(name: &str) -> String { @@ -1061,22 +1050,31 @@ async fn extract_live_photo(zip_data: &[u8], output_path: &Path) -> Result<()> { let ext = std::path::Path::new(&file_name) .extension() .and_then(|e| e.to_str()) - .unwrap_or("jpg"); + .ok_or_else(|| { + crate::Error::Generic(format!( + "Live photo image component has no extension: {}", + file_name + )) + })?; parent_dir.join(format!("{}.{}", base_name, ext)) } else if file_name.to_lowercase().contains("video") { // Video component - preserve its original extension let ext = std::path::Path::new(&file_name) .extension() .and_then(|e| e.to_str()) - .unwrap_or("mov"); + .ok_or_else(|| { + crate::Error::Generic(format!( + "Live photo video component has no extension: {}", + file_name + )) + })?; parent_dir.join(format!("{}.{}", base_name, ext)) } else { - // Unknown component - use original extension - let ext = std::path::Path::new(&file_name) - .extension() - .and_then(|e| e.to_str()) - .unwrap_or("bin"); - parent_dir.join(format!("{}.{}", base_name, ext)) + // Go CLI returns error for unexpected files in live photo ZIP + return Err(crate::Error::Generic(format!( + "Unexpected file in live photo ZIP: {}", + file_name + ))); }; // Read the file contents @@ -1161,14 +1159,15 @@ async fn write_file_metadata( fs::create_dir_all(&meta_dir).await?; // Generate unique metadata filename + // At this point, filename should always be valid since we error out earlier if not let base_name = Path::new(filename) .file_stem() .and_then(|s| s.to_str()) - .unwrap_or("file"); + .ok_or_else(|| crate::Error::Generic("Invalid filename for metadata".to_string()))?; let extension = Path::new(filename) .extension() .and_then(|s| s.to_str()) - .unwrap_or("bin"); + .unwrap_or(""); // Extension might be legitimately missing for some files // Create metadata filename like "IMG_1234.jpg_0.json" let meta_filename = format!("{}_{}.{}.json", base_name, file_index, extension); diff --git a/rust/src/commands/sync.rs b/rust/src/commands/sync.rs index e8ef474d77..e8e05cf811 100644 --- a/rust/src/commands/sync.rs +++ b/rust/src/commands/sync.rs @@ -412,17 +412,14 @@ async fn prepare_download_tasks( if let Some(title) = meta.get_title() { sanitize_filename(title) } else { - // Generate fallback name based on file type - let ext = match meta.get_file_type() { - crate::models::metadata::FileType::Image => "jpg", - crate::models::metadata::FileType::Video => "mp4", - crate::models::metadata::FileType::LivePhoto => "zip", - crate::models::metadata::FileType::Unknown => "bin", - }; - format!("file_{}.{}", file.id, ext) + // Match Go CLI behavior: error if no title found + log::error!("File {} has no title in metadata", file.id); + continue; // Skip this file } } else { - format!("file_{}.jpg", file.id) + // Match Go CLI behavior: error if no metadata + log::error!("File {} has no metadata", file.id); + continue; // Skip this file }; // For live photos, ensure .zip extension if not already present diff --git a/rust/src/sync/download.rs b/rust/src/sync/download.rs index b69592339f..5503485444 100644 --- a/rust/src/sync/download.rs +++ b/rust/src/sync/download.rs @@ -366,24 +366,37 @@ impl DownloadManager { let file_name = file.name().to_string(); // Determine the output filename based on the content - let output_file_path = if file_name.to_lowercase().ends_with(".heic") - || file_name.to_lowercase().ends_with(".jpg") - || file_name.to_lowercase().ends_with(".jpeg") - { - // Image component - parent_dir.join(format!("{}.jpg", base_name)) - } else if file_name.to_lowercase().ends_with(".mov") - || file_name.to_lowercase().ends_with(".mp4") - { - // Video component - parent_dir.join(format!("{}.mov", base_name)) - } else { - // Unknown component - use original extension + // Match Go CLI behavior: check for "image" or "video" in filename + let output_file_path = if file_name.to_lowercase().contains("image") { + // Image component - preserve original extension let ext = std::path::Path::new(&file_name) .extension() .and_then(|e| e.to_str()) - .unwrap_or("bin"); + .ok_or_else(|| { + crate::Error::Generic(format!( + "Live photo image component has no extension: {}", + file_name + )) + })?; parent_dir.join(format!("{}.{}", base_name, ext)) + } else if file_name.to_lowercase().contains("video") { + // Video component - preserve original extension + let ext = std::path::Path::new(&file_name) + .extension() + .and_then(|e| e.to_str()) + .ok_or_else(|| { + crate::Error::Generic(format!( + "Live photo video component has no extension: {}", + file_name + )) + })?; + parent_dir.join(format!("{}.{}", base_name, ext)) + } else { + // Go CLI returns error for unexpected files in live photo ZIP + return Err(crate::Error::Generic(format!( + "Unexpected file in live photo ZIP: {}", + file_name + ))); }; // Read the file contents