diff --git a/src/application/adapters/caldav_adapter.rs b/src/application/adapters/caldav_adapter.rs index ea14e3c3..d5183743 100644 --- a/src/application/adapters/caldav_adapter.rs +++ b/src/application/adapters/caldav_adapter.rs @@ -360,9 +360,9 @@ impl CalDavAdapter { for (name, value) in &calendar.custom_properties { // Skip properties that start with _ - they're internal if !name.starts_with('_') { - xml_writer.write_event(Event::Start(BytesStart::new(&format!("CS:{}", name))))?; + xml_writer.write_event(Event::Start(BytesStart::new(format!("CS:{}", name))))?; xml_writer.write_event(Event::Text(BytesText::new(value)))?; - xml_writer.write_event(Event::End(BytesEnd::new(&format!("CS:{}", name))))?; + xml_writer.write_event(Event::End(BytesEnd::new(format!("CS:{}", name))))?; } } diff --git a/src/application/adapters/webdav_adapter.rs b/src/application/adapters/webdav_adapter.rs index b7d7c21a..469c5232 100644 --- a/src/application/adapters/webdav_adapter.rs +++ b/src/application/adapters/webdav_adapter.rs @@ -222,7 +222,7 @@ impl WebDavAdapter { // Add response for current folder if provided if let Some(folder) = folder { - Self::write_folder_response(&mut xml_writer, folder, request, &format!("{}", base_href))?; + Self::write_folder_response(&mut xml_writer, folder, request, base_href)?; } // If depth allows, add responses for files and subfolders @@ -396,7 +396,7 @@ impl WebDavAdapter { // Convert u64 timestamp to DateTime let created_at = chrono::DateTime::::from_timestamp(folder.created_at as i64, 0) - .unwrap_or_else(|| Utc::now()); + .unwrap_or_else(Utc::now); xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?; xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?; @@ -406,7 +406,7 @@ impl WebDavAdapter { // Convert u64 timestamp to DateTime let modified_at = chrono::DateTime::::from_timestamp(folder.modified_at as i64, 0) - .unwrap_or_else(|| Utc::now()); + .unwrap_or_else(Utc::now); xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?; xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?; @@ -457,7 +457,7 @@ impl WebDavAdapter { // Convert u64 timestamp to DateTime let created_at = chrono::DateTime::::from_timestamp(file.created_at as i64, 0) - .unwrap_or_else(|| Utc::now()); + .unwrap_or_else(Utc::now); xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?; xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?; @@ -467,7 +467,7 @@ impl WebDavAdapter { // Convert u64 timestamp to DateTime let modified_at = chrono::DateTime::::from_timestamp(file.modified_at as i64, 0) - .unwrap_or_else(|| Utc::now()); + .unwrap_or_else(Utc::now); xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?; xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?; @@ -536,7 +536,7 @@ impl WebDavAdapter { // Convert u64 timestamp to DateTime let created_at = chrono::DateTime::::from_timestamp(folder.created_at as i64, 0) - .unwrap_or_else(|| Utc::now()); + .unwrap_or_else(Utc::now); xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?; xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?; @@ -546,7 +546,7 @@ impl WebDavAdapter { // Convert u64 timestamp to DateTime let modified_at = chrono::DateTime::::from_timestamp(folder.modified_at as i64, 0) - .unwrap_or_else(|| Utc::now()); + .unwrap_or_else(Utc::now); xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?; xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?; @@ -568,12 +568,12 @@ impl WebDavAdapter { }, _ => { // Property not supported - write empty element - xml_writer.write_event(Event::Empty(BytesStart::new(&format!("D:{}", prop.name))))?; + xml_writer.write_event(Event::Empty(BytesStart::new(format!("D:{}", prop.name))))?; } } } else { // Non-DAV namespace, not supported - xml_writer.write_event(Event::Empty(BytesStart::new(&format!("{}:{}", prop.namespace, prop.name))))?; + xml_writer.write_event(Event::Empty(BytesStart::new(format!("{}:{}", prop.namespace, prop.name))))?; } } @@ -612,7 +612,7 @@ impl WebDavAdapter { // Convert u64 timestamp to DateTime let created_at = chrono::DateTime::::from_timestamp(file.created_at as i64, 0) - .unwrap_or_else(|| Utc::now()); + .unwrap_or_else(Utc::now); xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?; xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?; @@ -622,7 +622,7 @@ impl WebDavAdapter { // Convert u64 timestamp to DateTime let modified_at = chrono::DateTime::::from_timestamp(file.modified_at as i64, 0) - .unwrap_or_else(|| Utc::now()); + .unwrap_or_else(Utc::now); xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?; xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?; @@ -634,12 +634,12 @@ impl WebDavAdapter { }, _ => { // Property not supported - write empty element - xml_writer.write_event(Event::Empty(BytesStart::new(&format!("D:{}", prop.name))))?; + xml_writer.write_event(Event::Empty(BytesStart::new(format!("D:{}", prop.name))))?; } } } else { // Non-DAV namespace, not supported - xml_writer.write_event(Event::Empty(BytesStart::new(&format!("{}:{}", prop.namespace, prop.name))))?; + xml_writer.write_event(Event::Empty(BytesStart::new(format!("{}:{}", prop.namespace, prop.name))))?; } } @@ -997,22 +997,20 @@ impl WebDavAdapter { /// Helper method to extract namespace from tag name pub fn extract_namespace(name: &str) -> String { - if let Some(idx) = name.rfind(':') { - if idx > 0 { + if let Some(idx) = name.rfind(':') + && idx > 0 { return name[..idx].to_string(); } - } // Default namespace for WebDAV "DAV:".to_string() } /// Helper method to extract local name from tag name pub fn extract_local_name(name: &str) -> String { - if let Some(idx) = name.rfind(':') { - if idx > 0 && idx < name.len() - 1 { + if let Some(idx) = name.rfind(':') + && idx > 0 && idx < name.len() - 1 { return name[idx+1..].to_string(); } - } name.to_string() } } \ No newline at end of file diff --git a/src/application/dtos/pagination.rs b/src/application/dtos/pagination.rs index 37bc27dd..b8882451 100644 --- a/src/application/dtos/pagination.rs +++ b/src/application/dtos/pagination.rs @@ -97,7 +97,7 @@ impl PaginatedResponseDto { let total_pages = if total_items == 0 { 0 } else { - (total_items + page_size - 1) / page_size + total_items.div_ceil(page_size) }; let pagination = PaginationDto { diff --git a/src/application/ports/outbound.rs b/src/application/ports/outbound.rs index fb23eea7..b63e398a 100644 --- a/src/application/ports/outbound.rs +++ b/src/application/ports/outbound.rs @@ -75,7 +75,7 @@ pub trait IdMappingPort: Send + Sync + 'static { /// Updates a file's path async fn update_file_path(&self, file_id: &str, new_path: &PathBuf) -> Result<(), DomainError> { - let storage_path = StoragePath::from_string(&new_path.to_string_lossy().to_string()); + let storage_path = StoragePath::from_string(new_path.to_string_lossy().as_ref()); self.update_path(file_id, &storage_path).await } } \ No newline at end of file diff --git a/src/application/services/admin_settings_service.rs b/src/application/services/admin_settings_service.rs index a451aa54..5aa7a9bb 100644 --- a/src/application/services/admin_settings_service.rs +++ b/src/application/services/admin_settings_service.rs @@ -139,11 +139,10 @@ impl AdminSettingsService { self.settings_repo.set("oidc.issuer_url", &dto.issuer_url, cat, false, by).await?; self.settings_repo.set("oidc.client_id", &dto.client_id, cat, false, by).await?; - if let Some(ref secret) = dto.client_secret { - if !secret.is_empty() { + if let Some(ref secret) = dto.client_secret + && !secret.is_empty() { self.settings_repo.set("oidc.client_secret", secret, cat, true, by).await?; } - } if let Some(ref v) = dto.scopes { self.settings_repo.set("oidc.scopes", v, cat, false, by).await?; } diff --git a/src/application/services/auth_application_service.rs b/src/application/services/auth_application_service.rs index 87ea763a..f8378713 100644 --- a/src/application/services/auth_application_service.rs +++ b/src/application/services/auth_application_service.rs @@ -152,13 +152,13 @@ impl AuthApplicationService { /// Returns whether OIDC is configured and enabled pub fn oidc_enabled(&self) -> bool { let state = self.oidc.read().unwrap(); - state.service.is_some() && state.config.as_ref().map_or(false, |c| c.enabled) + state.service.is_some() && state.config.as_ref().is_some_and(|c| c.enabled) } /// Returns whether password login is disabled (OIDC-only mode) pub fn password_login_disabled(&self) -> bool { let state = self.oidc.read().unwrap(); - state.config.as_ref().map_or(false, |c| c.disable_password_login) + state.config.as_ref().is_some_and(|c| c.disable_password_login) } /// Returns a clone of the OIDC config if available diff --git a/src/application/services/file_retrieval_service.rs b/src/application/services/file_retrieval_service.rs index 671c025f..2ad493ba 100644 --- a/src/application/services/file_retrieval_service.rs +++ b/src/application/services/file_retrieval_service.rs @@ -145,12 +145,12 @@ impl FileRetrievalUseCase for FileRetrievalService { let mime_type = dto.mime_type.clone(); let file_size = dto.size; let file_name = dto.name.clone(); - let modified_at = dto.modified_at.clone(); + let modified_at = dto.modified_at; let do_transcode = accept_webp && !prefer_original; // ── Tier 0: Write-behind cache ─────────────────────── - if let Some(wb) = &self.write_behind { - if let Some(pending) = wb.get_pending(id).await { + if let Some(wb) = &self.write_behind + && let Some(pending) = wb.get_pending(id).await { debug!("⚡ TIER 0 Write-Behind HIT: {} ({} bytes)", file_name, pending.len()); let (data, mime) = if do_transcode { if let Some((t, m)) = self.try_transcode(id, &pending, &mime_type, file_size, true).await { @@ -167,30 +167,27 @@ impl FileRetrievalUseCase for FileRetrievalService { was_transcoded: do_transcode, })); } - } // ── Tier 1: Hot cache + transcode (<10 MB) ────────── if file_size < CACHE_THRESHOLD { // Check content cache first - if let Some(cache) = &self.content_cache { - if let Some((cached, _etag, _ct)) = cache.get(id).await { + if let Some(cache) = &self.content_cache + && let Some((cached, _etag, _ct)) = cache.get(id).await { debug!("🔥 TIER 1 Cache HIT: {} ({} bytes)", file_name, cached.len()); - if do_transcode { - if let Some((t, m)) = self.try_transcode(id, &cached, &mime_type, file_size, true).await { + if do_transcode + && let Some((t, m)) = self.try_transcode(id, &cached, &mime_type, file_size, true).await { return Ok((dto, OptimizedFileContent::Bytes { data: t, mime_type: m, was_transcoded: true, })); } - } return Ok((dto, OptimizedFileContent::Bytes { data: cached, mime_type: mime_type.clone(), was_transcoded: false, })); } - } // Cache miss – load from disk debug!("💾 TIER 1 Cache MISS: {} – loading from disk", file_name); @@ -203,15 +200,14 @@ impl FileRetrievalUseCase for FileRetrievalService { cache.put(id.to_string(), content_bytes.clone(), etag, mime_type.clone()).await; } - if do_transcode { - if let Some((t, m)) = self.try_transcode(id, &content_bytes, &mime_type, file_size, true).await { + if do_transcode + && let Some((t, m)) = self.try_transcode(id, &content_bytes, &mime_type, file_size, true).await { return Ok((dto, OptimizedFileContent::Bytes { data: t, mime_type: m, was_transcoded: true, })); } - } return Ok((dto, OptimizedFileContent::Bytes { data: content_bytes, mime_type: mime_type.clone(), diff --git a/src/application/services/file_upload_service.rs b/src/application/services/file_upload_service.rs index c391ba96..1ce676e0 100644 --- a/src/application/services/file_upload_service.rs +++ b/src/application/services/file_upload_service.rs @@ -13,7 +13,7 @@ use crate::common::errors::DomainError; use tracing::{debug, info, warn}; /// Threshold for using streaming upload (files >= 1MB use streaming) -const STREAMING_UPLOAD_THRESHOLD: usize = 1 * 1024 * 1024; +const STREAMING_UPLOAD_THRESHOLD: usize = 1024 * 1024; /// Threshold for write-behind cache (files < 256KB get instant response) const WRITE_BEHIND_THRESHOLD: usize = 256 * 1024; @@ -168,9 +168,9 @@ impl FileUploadUseCase for FileUploadService { } // ─── TIER 1: Write-Behind (<256 KB) ────────────────── - if total_size < WRITE_BEHIND_THRESHOLD { - if let Some(wb) = &self.write_behind { - if wb.is_eligible_size(total_size) { + if total_size < WRITE_BEHIND_THRESHOLD + && let Some(wb) = &self.write_behind + && wb.is_eligible_size(total_size) { let data: Bytes = if chunks.len() == 1 { chunks.into_iter().next().unwrap() } else { @@ -198,12 +198,10 @@ impl FileUploadUseCase for FileUploadService { self.maybe_update_storage_usage(&dto); return Ok((dto, UploadStrategy::WriteBehind)); } - } - } // ─── TIER 2: Streaming (≥1 MB) ────────────────────── if total_size >= STREAMING_UPLOAD_THRESHOLD { - let chunk_stream = stream::iter(chunks.into_iter().map(|c| Ok::<_, std::io::Error>(c))); + let chunk_stream = stream::iter(chunks.into_iter().map(Ok::<_, std::io::Error>)); let pinned_stream: Pin> + Send>> = Box::pin(chunk_stream); diff --git a/src/application/services/search_service.rs b/src/application/services/search_service.rs index 5c639fd7..340bec35 100644 --- a/src/application/services/search_service.rs +++ b/src/application/services/search_service.rs @@ -154,8 +154,8 @@ impl SearchService { return None; } - if let Ok(cache) = self.search_cache.lock() { - if let Some(cached_result) = cache.get(key) { + if let Ok(cache) = self.search_cache.lock() + && let Some(cached_result) = cache.get(key) { let now = Instant::now(); let ttl = Duration::from_secs(self.cache_ttl); @@ -164,7 +164,6 @@ impl SearchService { return Some(cached_result.results.clone()); } } - } None } @@ -183,14 +182,13 @@ impl SearchService { if let Ok(mut cache) = self.search_cache.lock() { // If the cache is full, remove the oldest entry - if cache.len() >= self.max_cache_size { - if let Some((oldest_key, _)) = cache + if cache.len() >= self.max_cache_size + && let Some((oldest_key, _)) = cache .iter() .min_by_key(|(_, result)| result.timestamp) { let key_to_remove = oldest_key.clone(); cache.remove(&key_to_remove); } - } // Store the new result cache.insert(key, CachedSearchResult { @@ -211,15 +209,14 @@ impl SearchService { files.into_iter() .filter(|file| { // Filter by name - if let Some(name_query) = &criteria.name_contains { - if !file.name.to_lowercase().contains(&name_query.to_lowercase()) { + if let Some(name_query) = &criteria.name_contains + && !file.name.to_lowercase().contains(&name_query.to_lowercase()) { return false; } - } // Filter by file type (extension) if let Some(file_types) = &criteria.file_types { - if let Some(extension) = file.name.split('.').last() { + if let Some(extension) = file.name.split('.').next_back() { if !file_types.iter().any(|ext| ext.eq_ignore_ascii_case(extension)) { return false; } @@ -230,43 +227,37 @@ impl SearchService { } // Filter by creation date - if let Some(created_after) = criteria.created_after { - if file.created_at < created_after { + if let Some(created_after) = criteria.created_after + && file.created_at < created_after { return false; } - } - if let Some(created_before) = criteria.created_before { - if file.created_at > created_before { + if let Some(created_before) = criteria.created_before + && file.created_at > created_before { return false; } - } // Filter by modification date - if let Some(modified_after) = criteria.modified_after { - if file.modified_at < modified_after { + if let Some(modified_after) = criteria.modified_after + && file.modified_at < modified_after { return false; } - } - if let Some(modified_before) = criteria.modified_before { - if file.modified_at > modified_before { + if let Some(modified_before) = criteria.modified_before + && file.modified_at > modified_before { return false; } - } // Filter by size - if let Some(min_size) = criteria.min_size { - if file.size < min_size { + if let Some(min_size) = criteria.min_size + && file.size < min_size { return false; } - } - if let Some(max_size) = criteria.max_size { - if file.size > max_size { + if let Some(max_size) = criteria.max_size + && file.size > max_size { return false; } - } true }) @@ -284,37 +275,32 @@ impl SearchService { folders.into_iter() .filter(|folder| { // Filter by name - if let Some(name_query) = &criteria.name_contains { - if !folder.name.to_lowercase().contains(&name_query.to_lowercase()) { + if let Some(name_query) = &criteria.name_contains + && !folder.name.to_lowercase().contains(&name_query.to_lowercase()) { return false; } - } // Filter by creation date - if let Some(created_after) = criteria.created_after { - if folder.created_at < created_after { + if let Some(created_after) = criteria.created_after + && folder.created_at < created_after { return false; } - } - if let Some(created_before) = criteria.created_before { - if folder.created_at > created_before { + if let Some(created_before) = criteria.created_before + && folder.created_at > created_before { return false; } - } // Filter by modification date - if let Some(modified_after) = criteria.modified_after { - if folder.modified_at < modified_after { + if let Some(modified_after) = criteria.modified_after + && folder.modified_at < modified_after { return false; } - } - if let Some(modified_before) = criteria.modified_before { - if folder.modified_at > modified_before { + if let Some(modified_before) = criteria.modified_before + && folder.modified_at > modified_before { return false; } - } true }) @@ -438,10 +424,8 @@ impl SearchUseCase for SearchService { if idx < found_folders.len() { paginated_folders.push(found_folders[idx].clone()); } - } else { - if idx < found_files.len() { - paginated_files.push(found_files[idx].clone()); - } + } else if idx < found_files.len() { + paginated_files.push(found_files[idx].clone()); } } diff --git a/src/application/services/trash_service_test.rs b/src/application/services/trash_service_test.rs index 0363c8b6..e006e256 100644 --- a/src/application/services/trash_service_test.rs +++ b/src/application/services/trash_service_test.rs @@ -57,21 +57,19 @@ impl TrashRepository for MockTrashRepository { async fn restore_from_trash(&self, id: &Uuid, user_id: &Uuid) -> Result<()> { let mut items = self.trash_items.lock().unwrap(); - if let Some(item) = items.get(id) { - if item.user_id() == *user_id { + if let Some(item) = items.get(id) + && item.user_id() == *user_id { items.remove(id); } - } Ok(()) } async fn delete_permanently(&self, id: &Uuid, user_id: &Uuid) -> Result<()> { let mut items = self.trash_items.lock().unwrap(); - if let Some(item) = items.get(id) { - if item.user_id() == *user_id { + if let Some(item) = items.get(id) + && item.user_id() == *user_id { items.remove(id); } - } Ok(()) } diff --git a/src/common/config.rs b/src/common/config.rs index 3e416a04..be4e0699 100644 --- a/src/common/config.rs +++ b/src/common/config.rs @@ -142,8 +142,7 @@ impl ResourceConfig { } // Calculate the number of chunks based on size - let chunk_count = (size_bytes as usize + config.parallel_chunk_size_bytes - 1) - / config.parallel_chunk_size_bytes; + let chunk_count = (size_bytes as usize).div_ceil(config.parallel_chunk_size_bytes); // Limit to the maximum number of parallel chunks chunk_count.min(config.max_parallel_chunks) @@ -156,7 +155,7 @@ impl ResourceConfig { } // Distribute the size evenly among the chunks - ((file_size as usize) + chunk_count - 1) / chunk_count + (file_size as usize).div_ceil(chunk_count) } } @@ -421,11 +420,10 @@ impl AppConfig { config.static_path = PathBuf::from(static_path); } - if let Ok(server_port) = env::var("OXICLOUD_SERVER_PORT") { - if let Ok(port) = server_port.parse::() { + if let Ok(server_port) = env::var("OXICLOUD_SERVER_PORT") + && let Ok(port) = server_port.parse::() { config.server_port = port; } - } if let Ok(server_host) = env::var("OXICLOUD_SERVER_HOST") { config.server_host = server_host; @@ -437,18 +435,16 @@ impl AppConfig { } if let Ok(max_connections) = env::var("OXICLOUD_DB_MAX_CONNECTIONS") - .map(|v| v.parse::()) { - if let Ok(val) = max_connections { + .map(|v| v.parse::()) + && let Ok(val) = max_connections { config.database.max_connections = val; } - } if let Ok(min_connections) = env::var("OXICLOUD_DB_MIN_CONNECTIONS") - .map(|v| v.parse::()) { - if let Ok(val) = min_connections { + .map(|v| v.parse::()) + && let Ok(val) = min_connections { config.database.min_connections = val; } - } // Auth configuration if let Ok(jwt_secret) = env::var("OXICLOUD_JWT_SECRET") { @@ -473,54 +469,47 @@ impl AppConfig { } if let Ok(access_token_expiry) = env::var("OXICLOUD_ACCESS_TOKEN_EXPIRY_SECS") - .map(|v| v.parse::()) { - if let Ok(val) = access_token_expiry { + .map(|v| v.parse::()) + && let Ok(val) = access_token_expiry { config.auth.access_token_expiry_secs = val; } - } if let Ok(refresh_token_expiry) = env::var("OXICLOUD_REFRESH_TOKEN_EXPIRY_SECS") - .map(|v| v.parse::()) { - if let Ok(val) = refresh_token_expiry { + .map(|v| v.parse::()) + && let Ok(val) = refresh_token_expiry { config.auth.refresh_token_expiry_secs = val; } - } // Feature flags if let Ok(enable_auth) = env::var("OXICLOUD_ENABLE_AUTH") - .map(|v| v.parse::()) { - if let Ok(val) = enable_auth { + .map(|v| v.parse::()) + && let Ok(val) = enable_auth { config.features.enable_auth = val; } - } if let Ok(enable_user_storage_quotas) = env::var("OXICLOUD_ENABLE_USER_STORAGE_QUOTAS") - .map(|v| v.parse::()) { - if let Ok(val) = enable_user_storage_quotas { + .map(|v| v.parse::()) + && let Ok(val) = enable_user_storage_quotas { config.features.enable_user_storage_quotas = val; } - } if let Ok(enable_file_sharing) = env::var("OXICLOUD_ENABLE_FILE_SHARING") - .map(|v| v.parse::()) { - if let Ok(val) = enable_file_sharing { + .map(|v| v.parse::()) + && let Ok(val) = enable_file_sharing { config.features.enable_file_sharing = val; } - } if let Ok(enable_trash) = env::var("OXICLOUD_ENABLE_TRASH") - .map(|v| v.parse::()) { - if let Ok(val) = enable_trash { + .map(|v| v.parse::()) + && let Ok(val) = enable_trash { config.features.enable_trash = val; } - } if let Ok(enable_search) = env::var("OXICLOUD_ENABLE_SEARCH") - .map(|v| v.parse::()) { - if let Ok(val) = enable_search { + .map(|v| v.parse::()) + && let Ok(val) = enable_search { config.features.enable_search = val; } - } // OIDC configuration if let Ok(v) = env::var("OXICLOUD_OIDC_ENABLED") { @@ -558,12 +547,11 @@ impl AppConfig { } // Validate OIDC config when enabled - if config.oidc.enabled { - if config.oidc.issuer_url.is_empty() || config.oidc.client_id.is_empty() || config.oidc.client_secret.is_empty() { + if config.oidc.enabled + && (config.oidc.issuer_url.is_empty() || config.oidc.client_id.is_empty() || config.oidc.client_secret.is_empty()) { tracing::error!("OIDC is enabled but OXICLOUD_OIDC_ISSUER_URL, OXICLOUD_OIDC_CLIENT_ID, or OXICLOUD_OIDC_CLIENT_SECRET are not set"); config.oidc.enabled = false; } - } config } diff --git a/src/domain/entities/calendar_event.rs b/src/domain/entities/calendar_event.rs index 4384591a..24977441 100644 --- a/src/domain/entities/calendar_event.rs +++ b/src/domain/entities/calendar_event.rs @@ -109,15 +109,14 @@ impl CalendarEvent { } // Validate RRULE if provided (basic validation) - if let Some(ref rule) = rrule { - if !rule.starts_with("FREQ=") { + if let Some(ref rule) = rrule + && !rule.starts_with("FREQ=") { return Err(DomainError::new( ErrorKind::InvalidInput, "CalendarEvent", "Recurrence rule must start with FREQ=", )); } - } // Validate iCalendar data (basic validation) if !ical_data.contains("BEGIN:VEVENT") || !ical_data.contains("END:VEVENT") { @@ -500,15 +499,14 @@ impl CalendarEvent { */ pub fn update_rrule(&mut self, rrule: Option) -> Result<()> { // Validate RRULE if provided (basic validation) - if let Some(ref rule) = rrule { - if !rule.starts_with("FREQ=") { + if let Some(ref rule) = rrule + && !rule.starts_with("FREQ=") { return Err(DomainError::new( ErrorKind::InvalidInput, "CalendarEvent", "Recurrence rule must start with FREQ=", )); } - } self.rrule = rrule.clone(); self.updated_at = Utc::now(); @@ -547,17 +545,15 @@ impl CalendarEvent { self.description = Self::extract_ical_property(&ical_data, "DESCRIPTION"); self.location = Self::extract_ical_property(&ical_data, "LOCATION"); - if let Some(dtstart) = Self::extract_ical_property(&ical_data, "DTSTART") { - if let Ok(start_time) = Self::parse_ical_datetime(&dtstart) { + if let Some(dtstart) = Self::extract_ical_property(&ical_data, "DTSTART") + && let Ok(start_time) = Self::parse_ical_datetime(&dtstart) { self.start_time = start_time; } - } - if let Some(dtend) = Self::extract_ical_property(&ical_data, "DTEND") { - if let Ok(end_time) = Self::parse_ical_datetime(&dtend) { + if let Some(dtend) = Self::extract_ical_property(&ical_data, "DTEND") + && let Ok(end_time) = Self::parse_ical_datetime(&dtend) { self.end_time = end_time; } - } // Update all-day status based on DTSTART if let Some(dtstart) = Self::extract_ical_property(&ical_data, "DTSTART") { @@ -612,13 +608,13 @@ impl CalendarEvent { let until_start = until_pos + 6; // "UNTIL=" is 6 chars if let Some(until_end) = rrule[until_start..].find(';') { let until_str = &rrule[until_start..until_start+until_end]; - if let Ok(until_date) = Self::parse_ical_datetime(&until_str) { + if let Ok(until_date) = Self::parse_ical_datetime(until_str) { return until_date >= *start; } } else { // UNTIL is the last part of the rule let until_str = &rrule[until_start..]; - if let Ok(until_date) = Self::parse_ical_datetime(&until_str) { + if let Ok(until_date) = Self::parse_ical_datetime(until_str) { return until_date >= *start; } } @@ -677,7 +673,7 @@ impl CalendarEvent { fn parse_ical_datetime(datetime: &str) -> std::result::Result, String> { // Handle VALUE=DATE format if datetime.contains("VALUE=DATE") { - let date_str = datetime.split(':').last().unwrap_or(""); + let date_str = datetime.split(':').next_back().unwrap_or(""); if date_str.len() != 8 { return Err("Invalid date format".to_string()); } @@ -696,7 +692,7 @@ impl CalendarEvent { } // Handle standard UTC format (20230101T120000Z) - let datetime_str = datetime.split(':').last().unwrap_or(datetime); + let datetime_str = datetime.split(':').next_back().unwrap_or(datetime); if datetime_str.len() < 15 || !datetime_str.ends_with('Z') { return Err("Invalid datetime format".to_string()); } diff --git a/src/domain/entities/share.rs b/src/domain/entities/share.rs index 7d89b4e2..d009c4d3 100644 --- a/src/domain/entities/share.rs +++ b/src/domain/entities/share.rs @@ -262,9 +262,9 @@ mod tests { assert_eq!(share.item_id(), "test_file_id"); assert_eq!(*share.item_type(), ShareItemType::File); assert_eq!(share.created_by(), "user123"); - assert_eq!(share.permissions().read(), true); - assert_eq!(share.permissions().write(), false); - assert_eq!(share.permissions().reshare(), false); + assert!(share.permissions().read()); + assert!(!share.permissions().write()); + assert!(!share.permissions().reshare()); assert!(!share.has_password()); assert!(share.expires_at().is_none()); assert_eq!(share.access_count(), 0); diff --git a/src/domain/entities/user.rs b/src/domain/entities/user.rs index 71f5821c..9927d590 100644 --- a/src/domain/entities/user.rs +++ b/src/domain/entities/user.rs @@ -59,21 +59,15 @@ impl User { ) -> UserResult { // Validations if username.is_empty() || username.len() < 3 || username.len() > 32 { - return Err(UserError::InvalidUsername(format!( - "Username must be between 3 and 32 characters" - ))); + return Err(UserError::InvalidUsername("Username must be between 3 and 32 characters".to_string())); } if !email.contains('@') || email.len() < 5 { - return Err(UserError::ValidationError(format!( - "Invalid email" - ))); + return Err(UserError::ValidationError("Invalid email".to_string())); } if password_hash.is_empty() { - return Err(UserError::InvalidPassword(format!( - "Password hash cannot be empty" - ))); + return Err(UserError::InvalidPassword("Password hash cannot be empty".to_string())); } let now = Utc::now(); diff --git a/src/infrastructure/adapters/contact_storage_adapter.rs b/src/infrastructure/adapters/contact_storage_adapter.rs index dfefcace..a504a9e1 100644 --- a/src/infrastructure/adapters/contact_storage_adapter.rs +++ b/src/infrastructure/adapters/contact_storage_adapter.rs @@ -380,8 +380,8 @@ impl ContactUseCase for ContactStorageAdapter { } else if trimmed.starts_with("NOTE:") { notes = Some(trimmed[5..].trim().to_string()); } else if trimmed.starts_with("EMAIL") { - if let Some(value) = trimmed.split(':').nth(1) { - if !value.is_empty() { + if let Some(value) = trimmed.split(':').nth(1) + && !value.is_empty() { let email_type = if trimmed.contains("TYPE=HOME") { "home" } else if trimmed.contains("TYPE=WORK") { "work" } else { "other" }; @@ -391,10 +391,9 @@ impl ContactUseCase for ContactStorageAdapter { is_primary: emails.is_empty(), }); } - } - } else if trimmed.starts_with("TEL") { - if let Some(value) = trimmed.split(':').nth(1) { - if !value.is_empty() { + } else if trimmed.starts_with("TEL") + && let Some(value) = trimmed.split(':').nth(1) + && !value.is_empty() { let phone_type = if trimmed.contains("TYPE=CELL") || trimmed.contains("TYPE=MOBILE") { "mobile" } else if trimmed.contains("TYPE=HOME") { "home" } else if trimmed.contains("TYPE=WORK") { "work" } @@ -405,8 +404,6 @@ impl ContactUseCase for ContactStorageAdapter { is_primary: phones.is_empty(), }); } - } - } } let contact_uid = uid.unwrap_or_else(|| format!("{}@oxicloud", Uuid::new_v4())); diff --git a/src/infrastructure/adapters/error_adapters.rs b/src/infrastructure/adapters/error_adapters.rs index 554f9870..0cde95e1 100644 --- a/src/infrastructure/adapters/error_adapters.rs +++ b/src/infrastructure/adapters/error_adapters.rs @@ -24,10 +24,10 @@ use crate::domain::errors::{DomainError, ErrorKind}; #[macro_export] macro_rules! impl_infra_error_to_domain { ($error_type:ty, $entity_type:expr) => { - impl From<$error_type> for crate::domain::errors::DomainError { + impl From<$error_type> for $crate::domain::errors::DomainError { fn from(err: $error_type) -> Self { - crate::domain::errors::DomainError { - kind: crate::domain::errors::ErrorKind::InternalError, + $crate::domain::errors::DomainError { + kind: $crate::domain::errors::ErrorKind::InternalError, entity_type: $entity_type, entity_id: None, message: format!("{}", err), @@ -81,7 +81,7 @@ impl IntoDomainError for sqlx::Error { } sqlx::Error::Database(db_err) => { // Handle specific PostgreSQL error codes - if db_err.code().map_or(false, |c| c == "23505") { + if db_err.code().is_some_and(|c| c == "23505") { DomainError::already_exists(entity_type, "Record already exists") } else { DomainError::new( diff --git a/src/infrastructure/db.rs b/src/infrastructure/db.rs index 4e088570..69a8deb2 100644 --- a/src/infrastructure/db.rs +++ b/src/infrastructure/db.rs @@ -73,8 +73,7 @@ pub async fn create_database_pool(config: &AppConfig) -> Result { async fn tables_exist(pool: &PgPool) -> bool { sqlx::query("SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'auth' AND tablename = 'users')") .fetch_one(pool) - .await - .and_then(|row| Ok(row.get::(0))) + .await.map(|row| row.get::(0)) .unwrap_or(false) } diff --git a/src/infrastructure/repositories/file_fs_read_repository.rs b/src/infrastructure/repositories/file_fs_read_repository.rs index 0c09a78b..bf00031d 100644 --- a/src/infrastructure/repositories/file_fs_read_repository.rs +++ b/src/infrastructure/repositories/file_fs_read_repository.rs @@ -83,11 +83,10 @@ impl FileFsReadRepository { async fn get_file_metadata_raw(&self, abs_path: &PathBuf) -> FileRepositoryResult<(u64, u64, u64)> { // Cache first - if let Some(cached) = self.metadata_cache.get_metadata(abs_path).await { - if let (Some(s), Some(c), Some(m)) = (cached.size, cached.created_at, cached.modified_at) { + if let Some(cached) = self.metadata_cache.get_metadata(abs_path).await + && let (Some(s), Some(c), Some(m)) = (cached.size, cached.created_at, cached.modified_at) { return Ok((s, c, m)); } - } let metadata = time::timeout(self.config.timeouts.file_timeout(), fs::metadata(abs_path)) .await .map_err(|_| FileRepositoryError::StorageError(format!("Timeout metadata: {}", abs_path.display())))? diff --git a/src/infrastructure/repositories/file_fs_write_repository.rs b/src/infrastructure/repositories/file_fs_write_repository.rs index 45f09bfb..127f2475 100644 --- a/src/infrastructure/repositories/file_fs_write_repository.rs +++ b/src/infrastructure/repositories/file_fs_write_repository.rs @@ -101,11 +101,10 @@ impl FileFsWriteRepository { } async fn get_file_metadata_raw(&self, abs_path: &PathBuf) -> FileRepositoryResult<(u64, u64, u64)> { - if let Some(cached) = self.metadata_cache.get_metadata(abs_path).await { - if let (Some(s), Some(c), Some(m)) = (cached.size, cached.created_at, cached.modified_at) { + if let Some(cached) = self.metadata_cache.get_metadata(abs_path).await + && let (Some(s), Some(c), Some(m)) = (cached.size, cached.created_at, cached.modified_at) { return Ok((s, c, m)); } - } let meta = time::timeout(self.config.timeouts.file_timeout(), fs::metadata(abs_path)) .await .map_err(|_| FileRepositoryError::StorageError(format!("Timeout: {}", abs_path.display())))? @@ -177,11 +176,10 @@ impl FileFsWriteRepository { for attempt in 1..=3 { match self.id_mapping_service.save_changes().await { Ok(_) => { - if let Ok(verified) = self.id_mapping_service.get_path_by_id(id).await { - if verified.to_string() == expected_path { + if let Ok(verified) = self.id_mapping_service.get_path_by_id(id).await + && verified.to_string() == expected_path { return Ok(()); } - } if attempt == 3 { return Err(FileRepositoryError::Other("Failed to verify ID mapping after 3 attempts".into())); } diff --git a/src/infrastructure/repositories/folder_fs_repository.rs b/src/infrastructure/repositories/folder_fs_repository.rs index a272accd..dd892560 100644 --- a/src/infrastructure/repositories/folder_fs_repository.rs +++ b/src/infrastructure/repositories/folder_fs_repository.rs @@ -135,7 +135,7 @@ impl FolderFsRepository { /// Updates a folder path in the ID mapping service pub async fn update_mapped_folder_path(&self, folder_id: &str, new_path: &PathBuf) -> FolderRepositoryResult<()> { - let storage_path = StoragePath::from_string(&new_path.to_string_lossy().to_string()); + let storage_path = StoragePath::from_string(new_path.to_string_lossy().as_ref()); self.id_mapping_service.update_path(folder_id, &storage_path).await .map_err(|e| FolderRepositoryError::StorageError(format!("Failed to update folder path: {}", e))) } @@ -338,10 +338,7 @@ impl FolderRepository for FolderFsRepository { let parent_id: Option = if parent.is_none() || parent.as_ref().unwrap().is_empty() { None } else { - match self.id_mapping_service.get_or_create_id(parent.as_ref().unwrap()).await { - Ok(pid) => Some(pid), - Err(_) => None, - } + self.id_mapping_service.get_or_create_id(parent.as_ref().unwrap()).await.ok() }; // Create folder entity @@ -377,10 +374,7 @@ impl FolderRepository for FolderFsRepository { let parent_id: Option = if parent.is_none() || parent.as_ref().unwrap().is_empty() { None } else { - match self.id_mapping_service.get_or_create_id(parent.as_ref().unwrap()).await { - Ok(pid) => Some(pid), - Err(_) => None, - } + self.id_mapping_service.get_or_create_id(parent.as_ref().unwrap()).await.ok() }; // Get folder metadata @@ -651,11 +645,10 @@ impl FolderRepository for FolderFsRepository { current_idx += 1; } - if !folders.is_empty() { - if let Err(e) = self.id_mapping_service.save_changes().await { + if !folders.is_empty() + && let Err(e) = self.id_mapping_service.save_changes().await { tracing::error!("Error saving ID mappings: {}", e); } - } Ok((folders, total_count)) } @@ -771,15 +764,13 @@ impl FolderRepository for FolderFsRepository { tokio::task::spawn_blocking(move || { if let Err(e) = std::fs::remove_dir_all(&path_for_large_removal) { tracing::error!("Error removing large directory: {}", e); - return Err(std::io::Error::new( - std::io::ErrorKind::Other, + return Err(std::io::Error::other( format!("Failed to remove large directory: {}", e) )); } Ok(()) }).await.unwrap_or_else(|e| { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(std::io::Error::other( format!("Task panicked during directory removal: {}", e) )) }) diff --git a/src/infrastructure/repositories/folder_fs_repository_trash.rs b/src/infrastructure/repositories/folder_fs_repository_trash.rs index 3835beda..6a925083 100644 --- a/src/infrastructure/repositories/folder_fs_repository_trash.rs +++ b/src/infrastructure/repositories/folder_fs_repository_trash.rs @@ -94,15 +94,14 @@ impl FolderFsRepository { let original_path_buf = PathBuf::from(original_path); // Ensure the destination parent directory exists - if let Some(parent) = original_path_buf.parent() { - if !parent.exists() { + if let Some(parent) = original_path_buf.parent() + && !parent.exists() { fs::create_dir_all(parent).await .map_err(|e| { error!("Error creating parent directory for restoration: {}", e); FolderRepositoryError::StorageError(e.to_string()) })?; } - } // Move the folder from the trash to its original location match fs::rename(¤t_path, &original_path_buf).await { diff --git a/src/infrastructure/repositories/pg/user_pg_repository.rs b/src/infrastructure/repositories/pg/user_pg_repository.rs index d2b2c137..e7492734 100644 --- a/src/infrastructure/repositories/pg/user_pg_repository.rs +++ b/src/infrastructure/repositories/pg/user_pg_repository.rs @@ -32,7 +32,7 @@ impl UserPgRepository { UserRepositoryError::NotFound("User not found".to_string()) }, sqlx::Error::Database(db_err) => { - if db_err.code().map_or(false, |code| code == "23505") { + if db_err.code().is_some_and(|code| code == "23505") { // PostgreSQL uniqueness violation code UserRepositoryError::AlreadyExists( "User or email already exists".to_string() @@ -268,7 +268,7 @@ impl UserRepository for UserPgRepository { .bind(user_clone.username()) .bind(user_clone.email()) .bind(user_clone.password_hash()) - .bind(&user_clone.role().to_string()) + .bind(user_clone.role().to_string()) .bind(user_clone.storage_quota_bytes()) .bind(user_clone.storage_used_bytes()) .bind(user_clone.updated_at()) diff --git a/src/infrastructure/services/chunked_upload_service.rs b/src/infrastructure/services/chunked_upload_service.rs index 2fdf0e9b..3c6228e8 100644 --- a/src/infrastructure/services/chunked_upload_service.rs +++ b/src/infrastructure/services/chunked_upload_service.rs @@ -80,7 +80,7 @@ pub struct UploadSession { impl UploadSession { /// Calculate number of chunks needed pub fn calculate_chunk_count(total_size: u64, chunk_size: usize) -> usize { - ((total_size as usize + chunk_size - 1) / chunk_size).max(1) + (total_size as usize).div_ceil(chunk_size).max(1) } /// Get upload progress (0.0 - 1.0) @@ -212,14 +212,12 @@ impl ChunkedUploadService { let sessions = sessions.read().await; if !sessions.contains_key(dir_name) { // Check if directory is old (>24h) - if let Ok(metadata) = fs::metadata(&path).await { - if let Ok(modified) = metadata.modified() { - if modified.elapsed().unwrap_or_default() > SESSION_EXPIRATION { + if let Ok(metadata) = fs::metadata(&path).await + && let Ok(modified) = metadata.modified() + && modified.elapsed().unwrap_or_default() > SESSION_EXPIRATION { let _ = fs::remove_dir_all(&path).await; tracing::info!("🧹 Cleaned orphaned upload dir: {:?}", path); } - } - } } } } @@ -283,7 +281,7 @@ impl ChunkedUploadService { bytes_received: 0, }; - let expires_at = (SESSION_EXPIRATION.as_secs()) as u64; + let expires_at = SESSION_EXPIRATION.as_secs(); { let mut sessions = self.sessions.write().await; diff --git a/src/infrastructure/services/compression_service.rs b/src/infrastructure/services/compression_service.rs index 9b822b5c..2503cfd8 100644 --- a/src/infrastructure/services/compression_service.rs +++ b/src/infrastructure/services/compression_service.rs @@ -159,7 +159,7 @@ impl CompressionService for GzipCompressionService { Ok(compressed) }).await.unwrap_or_else(|e| { error!("Compression task error: {}", e); - Err(io::Error::new(io::ErrorKind::Other, e.to_string())) + Err(io::Error::other(e.to_string())) }) } @@ -226,7 +226,7 @@ impl CompressionService for GzipCompressionService { Ok(decompressed) }).await.unwrap_or_else(|e| { error!("Decompression task error: {}", e); - Err(io::Error::new(io::ErrorKind::Other, e.to_string())) + Err(io::Error::other(e.to_string())) }) } diff --git a/src/infrastructure/services/dedup_service.rs b/src/infrastructure/services/dedup_service.rs index 7267b916..964c45c4 100644 --- a/src/infrastructure/services/dedup_service.rs +++ b/src/infrastructure/services/dedup_service.rs @@ -622,14 +622,13 @@ impl DedupService { } // Check size - if let Ok(file_meta) = fs::metadata(&blob_path).await { - if file_meta.len() != metadata.size { + if let Ok(file_meta) = fs::metadata(&blob_path).await + && file_meta.len() != metadata.size { corrupted.push(format!( "{}: size mismatch (expected: {}, actual: {})", hash, metadata.size, file_meta.len() )); } - } } if corrupted.is_empty() { diff --git a/src/infrastructure/services/file_system_i18n_service.rs b/src/infrastructure/services/file_system_i18n_service.rs index 751123c9..c7933356 100644 --- a/src/infrastructure/services/file_system_i18n_service.rs +++ b/src/infrastructure/services/file_system_i18n_service.rs @@ -51,13 +51,11 @@ impl FileSystemI18nService { } } - if let Some(last_part) = parts.last() { - if let Some(value) = current.get(last_part) { - if value.is_string() { + if let Some(last_part) = parts.last() + && let Some(value) = current.get(last_part) + && value.is_string() { return value.as_str().map(|s| s.to_string()); } - } - } None } @@ -75,13 +73,11 @@ impl I18nService for FileSystemI18nService { } // Try to use English as fallback if we couldn't find the key - if locale != Locale::English { - if let Some(english_translations) = cache.get(&Locale::English) { - if let Some(value) = self.get_nested_value(english_translations, key) { + if locale != Locale::English + && let Some(english_translations) = cache.get(&Locale::English) + && let Some(value) = self.get_nested_value(english_translations, key) { return Ok(value); } - } - } return Err(I18nError::KeyNotFound(key.to_string())); } @@ -98,13 +94,11 @@ impl I18nService for FileSystemI18nService { } // Try to use English as fallback - if locale != Locale::English { - if let Some(english_translations) = cache.get(&Locale::English) { - if let Some(value) = self.get_nested_value(english_translations, key) { + if locale != Locale::English + && let Some(english_translations) = cache.get(&Locale::English) + && let Some(value) = self.get_nested_value(english_translations, key) { return Ok(value); } - } - } } } diff --git a/src/infrastructure/services/file_system_utils.rs b/src/infrastructure/services/file_system_utils.rs index 736e7313..28d79b8e 100644 --- a/src/infrastructure/services/file_system_utils.rs +++ b/src/infrastructure/services/file_system_utils.rs @@ -25,8 +25,7 @@ impl FileSystemUtils { Ok(file) => file, Err(e) => { error!("Failed to create temporary file in {}: {}", dir.display(), e); - return Err(IoError::new(std::io::ErrorKind::Other, - format!("Failed to create temporary file: {}", e))); + return Err(IoError::other(format!("Failed to create temporary file: {}", e))); } }; diff --git a/src/infrastructure/services/id_mapping_optimizer.rs b/src/infrastructure/services/id_mapping_optimizer.rs index cecea106..e896c74e 100644 --- a/src/infrastructure/services/id_mapping_optimizer.rs +++ b/src/infrastructure/services/id_mapping_optimizer.rs @@ -60,6 +60,7 @@ pub struct OptimizerStats { } /// Queue for batch operations +#[derive(Default)] struct BatchQueue { /// Pending paths to get/create ID path_to_id_requests: HashSet, @@ -67,14 +68,6 @@ struct BatchQueue { id_to_path_requests: HashSet, } -impl Default for BatchQueue { - fn default() -> Self { - Self { - path_to_id_requests: HashSet::new(), - id_to_path_requests: HashSet::new(), - } - } -} /// Result of a batch operation struct BatchResult { diff --git a/src/infrastructure/services/id_mapping_service.rs b/src/infrastructure/services/id_mapping_service.rs index 0f921a8f..367919a0 100644 --- a/src/infrastructure/services/id_mapping_service.rs +++ b/src/infrastructure/services/id_mapping_service.rs @@ -173,13 +173,11 @@ impl IdMappingService { }; // Ensure directory exists - if let Some(parent) = map_path.parent() { - if !parent.exists() { - if let Err(e) = fs::create_dir_all(parent).await { + if let Some(parent) = map_path.parent() + && !parent.exists() + && let Err(e) = fs::create_dir_all(parent).await { tracing::error!("Failed to create directory for ID map: {}", e); } - } - } // Write empty map to file (best-effort: the in-memory map is valid even if disk write fails) match serde_json::to_string_pretty(&empty_map) { @@ -413,8 +411,7 @@ impl IdMappingService { // Try a second save if verification fails if let Err(retry_err) = self.save_id_map().await { tracing::error!("Second save attempt also failed: {}", retry_err); - return Err(IdMappingError::IoError(std::io::Error::new( - std::io::ErrorKind::Other, + return Err(IdMappingError::IoError(std::io::Error::other( format!("Failed to verify and retry save: {}", retry_err) ))); } @@ -435,8 +432,7 @@ impl IdMappingService { }, Err(retry_e) => { tracing::error!("Second save attempt also failed: {}", retry_e); - Err(IdMappingError::IoError(std::io::Error::new( - std::io::ErrorKind::Other, + Err(IdMappingError::IoError(std::io::Error::other( format!("Failed to save ID mappings after retry: {}", retry_e) ))) } diff --git a/src/infrastructure/services/image_transcode_service.rs b/src/infrastructure/services/image_transcode_service.rs index cdfe6837..2c69f505 100644 --- a/src/infrastructure/services/image_transcode_service.rs +++ b/src/infrastructure/services/image_transcode_service.rs @@ -200,11 +200,7 @@ impl ImageTranscodeService { // Calculate savings let original_size = original_content.len(); let transcoded_size = transcoded_bytes.len(); - let saved = if transcoded_size < original_size { - original_size - transcoded_size - } else { - 0 - }; + let saved = original_size.saturating_sub(transcoded_size); // Only use transcoded if it's actually smaller if transcoded_size >= original_size { diff --git a/src/infrastructure/services/path_service.rs b/src/infrastructure/services/path_service.rs index 0f413fca..1149fef8 100644 --- a/src/infrastructure/services/path_service.rs +++ b/src/infrastructure/services/path_service.rs @@ -65,7 +65,7 @@ impl PathService { /// Checks if a path is at the root pub fn is_in_root(&self, path: &StoragePath) -> bool { - path.parent().map_or(true, |p| p.is_empty()) + path.parent().is_none_or(|p| p.is_empty()) } /// Gets the root path used by this service diff --git a/src/infrastructure/services/write_behind_cache.rs b/src/infrastructure/services/write_behind_cache.rs index 99cac4df..40afc1e8 100644 --- a/src/infrastructure/services/write_behind_cache.rs +++ b/src/infrastructure/services/write_behind_cache.rs @@ -256,13 +256,12 @@ impl WriteBehindCache { pending.get(&file_id).cloned() }; - if let Some(entry) = entry { - if let Err(e) = self.flush_single(&file_id, &entry).await { + if let Some(entry) = entry + && let Err(e) = self.flush_single(&file_id, &entry).await { tracing::error!("Failed to flush {}: {}", file_id, e); // Keep in cache for retry continue; } - } } FlushCommand::FlushAll => { let entries: Vec<_> = { diff --git a/src/interfaces/api/handlers/admin_handler.rs b/src/interfaces/api/handlers/admin_handler.rs index f2bb4e0f..c86cf1a5 100644 --- a/src/interfaces/api/handlers/admin_handler.rs +++ b/src/interfaces/api/handlers/admin_handler.rs @@ -51,7 +51,7 @@ async fn admin_guard(state: &AppState, headers: &HeaderMap) -> Result<(String, S .ok_or_else(|| AppError::unauthorized("Authorization token required"))?; let claims = auth.token_service.validate_token(token) - .map_err(|e| AppError::unauthorized(&format!("Invalid token: {}", e)))?; + .map_err(|e| AppError::unauthorized(format!("Invalid token: {}", e)))?; if claims.role != "admin" { return Err(AppError::new( @@ -75,7 +75,7 @@ async fn get_oidc_settings( .ok_or_else(|| AppError::internal_error("Admin settings service not available"))?; let settings = svc.get_oidc_settings().await - .map_err(|e| AppError::internal_error(&format!("Failed to load settings: {}", e)))?; + .map_err(|e| AppError::internal_error(format!("Failed to load settings: {}", e)))?; Ok(Json(settings)) } @@ -92,7 +92,7 @@ async fn save_oidc_settings( .ok_or_else(|| AppError::internal_error("Admin settings service not available"))?; svc.save_oidc_settings(dto, &user_id).await - .map_err(|e| AppError::internal_error(&format!("Failed to save settings: {}", e)))?; + .map_err(|e| AppError::internal_error(format!("Failed to save settings: {}", e)))?; Ok((StatusCode::OK, Json(serde_json::json!({ "message": "OIDC settings saved and applied successfully" @@ -111,7 +111,7 @@ async fn test_oidc_connection( .ok_or_else(|| AppError::internal_error("Admin settings service not available"))?; let result = svc.test_oidc_connection(dto).await - .map_err(|e| AppError::internal_error(&format!("Connection test failed: {}", e)))?; + .map_err(|e| AppError::internal_error(format!("Connection test failed: {}", e)))?; Ok(Json(result)) } @@ -173,7 +173,7 @@ async fn get_dashboard_stats( ) .fetch_one(db_pool.as_ref()) .await - .map_err(|e| AppError::internal_error(&format!("Database query failed: {}", e)))?; + .map_err(|e| AppError::internal_error(format!("Database query failed: {}", e)))?; use sqlx::Row; let total_quota: i64 = stats_row.get("total_quota_bytes"); @@ -228,7 +228,7 @@ async fn list_users( let offset = query.offset.unwrap_or(0); let users = auth.auth_application_service.list_users(limit, offset).await - .map_err(|e| AppError::internal_error(&format!("Failed to list users: {}", e)))?; + .map_err(|e| AppError::internal_error(format!("Failed to list users: {}", e)))?; let total = auth.auth_application_service.count_users_efficient().await.unwrap_or(0); @@ -252,7 +252,7 @@ async fn get_user( .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; let user = auth.auth_application_service.get_user_admin(&id).await - .map_err(|e| AppError::not_found(&format!("User not found: {}", e)))?; + .map_err(|e| AppError::not_found(format!("User not found: {}", e)))?; Ok(Json(user)) } @@ -278,7 +278,7 @@ async fn delete_user( .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; auth.auth_application_service.delete_user_admin(&id).await - .map_err(|e| AppError::internal_error(&format!("Failed to delete user: {}", e)))?; + .map_err(|e| AppError::internal_error(format!("Failed to delete user: {}", e)))?; Ok((StatusCode::OK, Json(serde_json::json!({ "message": "User deleted successfully" @@ -307,7 +307,7 @@ async fn update_user_role( .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; auth.auth_application_service.change_user_role(&id, &dto.role).await - .map_err(|e| AppError::internal_error(&format!("Failed to change role: {}", e)))?; + .map_err(|e| AppError::internal_error(format!("Failed to change role: {}", e)))?; Ok((StatusCode::OK, Json(serde_json::json!({ "message": format!("User role updated to '{}'", dto.role) @@ -336,7 +336,7 @@ async fn update_user_active( .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; auth.auth_application_service.set_user_active(&id, dto.active).await - .map_err(|e| AppError::internal_error(&format!("Failed to update user status: {}", e)))?; + .map_err(|e| AppError::internal_error(format!("Failed to update user status: {}", e)))?; let status = if dto.active { "activated" } else { "deactivated" }; Ok((StatusCode::OK, Json(serde_json::json!({ @@ -357,7 +357,7 @@ async fn update_user_quota( .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; auth.auth_application_service.update_user_quota(&id, dto.quota_bytes).await - .map_err(|e| AppError::internal_error(&format!("Failed to update quota: {}", e)))?; + .map_err(|e| AppError::internal_error(format!("Failed to update quota: {}", e)))?; Ok((StatusCode::OK, Json(serde_json::json!({ "message": "User quota updated", @@ -383,7 +383,7 @@ async fn create_user( let user = auth.auth_application_service.admin_create_user(dto).await .map_err(|e| AppError::new( StatusCode::BAD_REQUEST, - &format!("Failed to create user: {}", e), + format!("Failed to create user: {}", e), "CreateUserFailed", ))?; @@ -405,7 +405,7 @@ async fn reset_user_password( auth.auth_application_service.admin_reset_password(&id, &dto.new_password).await .map_err(|e| AppError::new( StatusCode::BAD_REQUEST, - &format!("Failed to reset password: {}", e), + format!("Failed to reset password: {}", e), "ResetPasswordFailed", ))?; @@ -455,7 +455,7 @@ async fn set_registration_setting( .ok_or_else(|| AppError::internal_error("Admin settings service not available"))?; svc.set_registration_enabled(enabled, &admin_id).await - .map_err(|e| AppError::internal_error(&format!("Failed to save setting: {}", e)))?; + .map_err(|e| AppError::internal_error(format!("Failed to save setting: {}", e)))?; Ok((StatusCode::OK, Json(serde_json::json!({ "message": format!("Public registration {}", if enabled { "enabled" } else { "disabled" }), diff --git a/src/interfaces/api/handlers/auth_handler.rs b/src/interfaces/api/handlers/auth_handler.rs index 70c1c0bc..6498de44 100644 --- a/src/interfaces/api/handlers/auth_handler.rs +++ b/src/interfaces/api/handlers/auth_handler.rs @@ -67,15 +67,14 @@ async fn register( } // Check if public registration has been disabled by the admin - if let Some(admin_svc) = state.admin_settings_service.as_ref() { - if !admin_svc.get_registration_enabled().await { + if let Some(admin_svc) = state.admin_settings_service.as_ref() + && !admin_svc.get_registration_enabled().await { return Err(AppError::new( StatusCode::FORBIDDEN, "Public registration has been disabled by the administrator.", "RegistrationDisabled", )); } - } // Registration logic (admin detection, fresh-install handling, duplicate // checks) is all inside the service layer. Call it directly. @@ -178,7 +177,7 @@ async fn get_current_user( // Validate the token and get claims let claims = auth_service.token_service.validate_token(token) - .map_err(|e| AppError::unauthorized(&format!("Invalid token: {}", e)))?; + .map_err(|e| AppError::unauthorized(format!("Invalid token: {}", e)))?; let user_id = claims.sub; @@ -220,7 +219,7 @@ async fn change_password( // Validate the token and get claims let claims = auth_service.token_service.validate_token(token) - .map_err(|e| AppError::unauthorized(&format!("Invalid token: {}", e)))?; + .map_err(|e| AppError::unauthorized(format!("Invalid token: {}", e)))?; auth_service.auth_application_service.change_password(&claims.sub, dto).await?; @@ -243,7 +242,7 @@ async fn logout( // Validate the token and get claims let claims = auth_service.token_service.validate_token(token) - .map_err(|e| AppError::unauthorized(&format!("Invalid token: {}", e)))?; + .map_err(|e| AppError::unauthorized(format!("Invalid token: {}", e)))?; // Use access token for logout (we don't have refresh token in headers) auth_service.auth_application_service.logout(&claims.sub, token).await?; diff --git a/src/interfaces/api/handlers/caldav_handler.rs b/src/interfaces/api/handlers/caldav_handler.rs index 8afb7455..9417646d 100644 --- a/src/interfaces/api/handlers/caldav_handler.rs +++ b/src/interfaces/api/handlers/caldav_handler.rs @@ -320,7 +320,7 @@ async fn handle_mkcalendar( .map_err(|e| AppError::bad_request(format!("Failed to read request body: {}", e)))?; let (name, description, color) = if body_bytes.is_empty() { - let name = path.split('/').last().unwrap_or("New Calendar").to_string(); + let name = path.split('/').next_back().unwrap_or("New Calendar").to_string(); (name, None, None) } else { CalDavAdapter::parse_mkcalendar(body_bytes.reader()) diff --git a/src/interfaces/api/handlers/carddav_handler.rs b/src/interfaces/api/handlers/carddav_handler.rs index ea29623e..9359c8a3 100644 --- a/src/interfaces/api/handlers/carddav_handler.rs +++ b/src/interfaces/api/handlers/carddav_handler.rs @@ -335,7 +335,7 @@ async fn handle_mkcol( .map_err(|e| AppError::bad_request(format!("Failed to read request body: {}", e)))?; let (name, description, color) = if body_bytes.is_empty() { - let name = path.split('/').last().unwrap_or("New Address Book").to_string(); + let name = path.split('/').next_back().unwrap_or("New Address Book").to_string(); (name, None, None) } else { CardDavAdapter::parse_mkaddressbook(body_bytes.reader()) diff --git a/src/interfaces/api/handlers/file_handler.rs b/src/interfaces/api/handlers/file_handler.rs index e545ad7e..80eb5054 100644 --- a/src/interfaces/api/handlers/file_handler.rs +++ b/src/interfaces/api/handlers/file_handler.rs @@ -271,7 +271,7 @@ impl FileHandler { }; // ── Metadata-only request ──────────────────────────────────── - if params.get("metadata").map_or(false, |v| v == "true" || v == "1") { + if params.get("metadata").is_some_and(|v| v == "true" || v == "1") { return (StatusCode::OK, Json(serde_json::json!({ "id": file_dto.id, "name": file_dto.name, @@ -287,9 +287,9 @@ impl FileHandler { let etag = format!("\"{}-{}\"", id, file_dto.modified_at); // ── ETag (304 Not Modified) ────────────────────────────────── - if let Some(inm) = headers.get(header::IF_NONE_MATCH) { - if let Ok(client_etag) = inm.to_str() { - if client_etag == etag || client_etag == "*" { + if let Some(inm) = headers.get(header::IF_NONE_MATCH) + && let Ok(client_etag) = inm.to_str() + && (client_etag == etag || client_etag == "*") { return Response::builder() .status(StatusCode::NOT_MODIFIED) .header(header::ETAG, &etag) @@ -297,13 +297,11 @@ impl FileHandler { .unwrap() .into_response(); } - } - } // ── Range Requests ─────────────────────────────────────────── - if let Some(range_header) = headers.get(header::RANGE) { - if let Ok(range_str) = range_header.to_str() { - if let Ok(ranges) = parse_range_header(range_str) { + if let Some(range_header) = headers.get(header::RANGE) + && let Ok(range_str) = range_header.to_str() + && let Ok(ranges) = parse_range_header(range_str) { let validated = ranges.validate(file_dto.size); if let Ok(valid_ranges) = validated { if let Some(range) = valid_ranges.first() { @@ -342,16 +340,14 @@ impl FileHandler { .into_response(); } } - } - } // ── Normal download (delegated to service) ─────────────────── let disposition = Self::content_disposition(&file_dto.name, &file_dto.mime_type, ¶ms); let accept_webp = headers.get(header::ACCEPT) .and_then(|v| v.to_str().ok()) - .map_or(false, |a| a.contains("image/webp")); - let prefer_original = params.get("original").map_or(false, |v| v == "true" || v == "1"); + .is_some_and(|a| a.contains("image/webp")); + let prefer_original = params.get("original").is_some_and(|v| v == "true" || v == "1"); match retrieval.get_file_optimized(&id, accept_webp, prefer_original).await { Ok((_file, content)) => match content { @@ -447,9 +443,9 @@ impl FileHandler { if let Ok(body_bytes) = axum::body::to_bytes( response.into_response().into_body(), 10 * 1024, - ).await { - if let Ok(file_info) = serde_json::from_slice::(&body_bytes) { - if let (Some(file_id), Some(mime_type), Some(file_path_str)) = ( + ).await + && let Ok(file_info) = serde_json::from_slice::(&body_bytes) + && let (Some(file_id), Some(mime_type), Some(file_path_str)) = ( file_info.get("id").and_then(|v| v.as_str()), file_info.get("mime_type").and_then(|v| v.as_str()), file_info.get("path").and_then(|v| v.as_str()), @@ -477,8 +473,6 @@ impl FileHandler { .unwrap() .into_response(); } - } - } // Fallback for errors (StatusCode::INTERNAL_SERVER_ERROR, "Upload processing error").into_response() @@ -676,7 +670,7 @@ impl FileHandler { /// Build a Content-Disposition header value. fn content_disposition(name: &str, mime: &str, params: &HashMap) -> String { - let force_inline = params.get("inline").map_or(false, |v| v == "true" || v == "1"); + let force_inline = params.get("inline").is_some_and(|v| v == "true" || v == "1"); if force_inline || mime.starts_with("image/") || mime == "application/pdf" @@ -750,7 +744,7 @@ impl FileHandler { .header(header::VARY, "Accept-Encoding"); if should_compress { - match compression_service.compress_data(&content.to_vec(), compression_level).await { + match compression_service.compress_data(&content, compression_level).await { Ok(compressed) => { builder .header(header::CONTENT_TYPE, mime_type) diff --git a/src/interfaces/api/handlers/webdav_handler.rs b/src/interfaces/api/handlers/webdav_handler.rs index 2c6dda7c..e87c8463 100644 --- a/src/interfaces/api/handlers/webdav_handler.rs +++ b/src/interfaces/api/handlers/webdav_handler.rs @@ -409,7 +409,7 @@ async fn handle_get( .header(header::CONTENT_LENGTH, content.len()) .header(header::ETAG, format!("\"{}\"", file.id)) .header(header::LAST_MODIFIED, chrono::DateTime::::from_timestamp(file.created_at as i64, 0) - .unwrap_or_else(|| Utc::now()) + .unwrap_or_else(Utc::now) .to_rfc2822()) .body(Body::from(content)) .unwrap()) @@ -463,7 +463,7 @@ async fn handle_head( .header(header::CONTENT_LENGTH, content.len()) .header(header::ETAG, format!("\"{}\"", file.id)) .header(header::LAST_MODIFIED, chrono::DateTime::::from_timestamp(file.created_at as i64, 0) - .unwrap_or_else(|| Utc::now()) + .unwrap_or_else(Utc::now) .to_rfc2822()) .body(Body::empty()) .unwrap()) @@ -573,7 +573,7 @@ async fn handle_mkcol( } // Extract folder name from path - let folder_name = path.split('/').last().unwrap_or("unnamed"); + let folder_name = path.split('/').next_back().unwrap_or("unnamed"); // Get parent folder path let parent_path = if let Some(idx) = path.rfind('/') { @@ -715,7 +715,7 @@ async fn handle_move( if let Ok(folder) = folder_result { // Move folder - let dest_folder_name = destination_path.split('/').last().unwrap_or(&destination_path); + let dest_folder_name = destination_path.split('/').next_back().unwrap_or(&destination_path); let dest_parent_path = if let Some(idx) = destination_path.rfind('/') { &destination_path[..idx] } else { @@ -753,7 +753,7 @@ async fn handle_move( AppError::not_found(format!("Resource not found: {}", source_path)) })?; - let dest_filename = destination_path.split('/').last().unwrap_or(&destination_path); + let dest_filename = destination_path.split('/').next_back().unwrap_or(&destination_path); let dest_parent_path = if let Some(idx) = destination_path.rfind('/') { &destination_path[..idx] } else { @@ -854,7 +854,7 @@ async fn handle_copy( // Copy folder let recursive = depth != "0"; - let dest_folder_name = destination_path.split('/').last().unwrap_or(&destination_path); + let dest_folder_name = destination_path.split('/').next_back().unwrap_or(&destination_path); let dest_parent_path = if let Some(idx) = destination_path.rfind('/') { &destination_path[..idx] } else { @@ -908,7 +908,7 @@ async fn handle_copy( })?; // Get destination parent path and filename - let dest_filename = destination_path.split('/').last().unwrap_or(&destination_path); + let dest_filename = destination_path.split('/').next_back().unwrap_or(&destination_path); let dest_parent_path = if let Some(idx) = destination_path.rfind('/') { &destination_path[..idx] } else { diff --git a/src/interfaces/middleware/cache.rs b/src/interfaces/middleware/cache.rs index d983e642..50c63504 100644 --- a/src/interfaces/middleware/cache.rs +++ b/src/interfaces/middleware/cache.rs @@ -197,13 +197,12 @@ where // If there is a cache entry if let Some(cache_entry) = cache.get(cache_key) { // Check if the client already has the updated version - if let Some(client_etag) = if_none_match { - if client_etag == cache_entry.etag { + if let Some(client_etag) = if_none_match + && client_etag == cache_entry.etag { // The client has the most recent version, send 304 Not Modified debug!("Cache hit (304) for key: {}", cache_key); return Ok(create_not_modified_response(&cache_entry)); } - } // The client needs the updated version if let Some(data) = &cache_entry.data { @@ -392,7 +391,7 @@ where // The client has the correct version, send 304 debug!("Cache HIT (304): {}", cache_key); let response = create_not_modified_response(&cache_entry); - return Box::pin(async move { Ok(response) }); + Box::pin(async move { Ok(response) }) }, Some(cache_entry) if cache_entry.data.is_some() => { // The client needs the updated version @@ -409,7 +408,7 @@ where // Add cache headers set_cache_headers(&mut response, &cache_entry.etag, max_age.unwrap_or(cache_entry.max_age)); - return Box::pin(async move { Ok(response) }); + Box::pin(async move { Ok(response) }) }, _ => { // Not cached or expired @@ -419,7 +418,7 @@ where let max_age = self.max_age; let cache_key = cache_key.clone(); - return Box::pin(async move { + Box::pin(async move { let response = future.await.map_err(|e| e.into())?; let response = response_map_body(response).await; @@ -449,7 +448,7 @@ where set_cache_headers(&mut response, &etag, max_age.unwrap_or(cache_clone.default_max_age)); Ok(response) - }); + }) } } }