fix: resolve all clippy warnings and enforce cargo fmt

- display_helpers: convert module doc-comments to regular comments,
  merge identical text/markdown + text/ branches
- search_service: replace needless range loops with slice-based pagination
- folder_repository, folder_db_repository: collapse nested if statements
- favorites_pg_repository: remove unnecessary borrow on generic arg
- file_blob_read_repository: collapse 6 nested if-let blocks
- file_blob_write_repository: collapse nested if for dedup ref decrement
- chunked_upload_service: use div_ceil(), collapse 2 nested if blocks
- folder_handler: collapse nested if-let for owner check
- webdav_handler: replace 7x io::Error::new(ErrorKind::Other, ..) with
  io::Error::other(..)
- cargo fmt applied to all files

Passes: cargo clippy --all-targets --all-features -- -D warnings
This commit is contained in:
Dionisio
2026-02-25 10:28:34 +01:00
parent 093400ce72
commit 97cf6402e2
34 changed files with 769 additions and 761 deletions
@@ -130,7 +130,7 @@ impl UploadSession {
/// Build the completed-chunks bitmask (1 bit per chunk).
fn build_progress_bitmask(&self) -> Vec<u8> {
let len = (self.chunks.len() + 7) / 8;
let len = self.chunks.len().div_ceil(8);
let mut bitmask = vec![0u8; len];
for chunk in &self.chunks {
if chunk.status == ChunkStatus::Complete {
@@ -157,8 +157,8 @@ impl UploadSession {
/// Persist the full session metadata once (on create).
async fn persist_metadata(&self) -> Result<(), String> {
let path = self.temp_dir.join(SESSION_META_FILE);
let json = serde_json::to_vec(self)
.map_err(|e| format!("Failed to serialise session: {e}"))?;
let json =
serde_json::to_vec(self).map_err(|e| format!("Failed to serialise session: {e}"))?;
// Atomic write: write to .tmp then rename
let tmp = self.temp_dir.join("session.json.tmp");
fs::write(&tmp, &json)
@@ -208,9 +208,7 @@ impl ChunkedUploadService {
};
if recovered_count > 0 {
tracing::info!(
"♻️ Recovered {recovered_count} chunked-upload session(s) from disk"
);
tracing::info!("♻️ Recovered {recovered_count} chunked-upload session(s) from disk");
}
// Start cleanup task
@@ -357,14 +355,13 @@ impl ChunkedUploadService {
let dir_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
let sessions = sessions.read().await;
if !sessions.contains_key(dir_name) {
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);
}
if !sessions.contains_key(dir_name)
&& 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);
}
}
}
@@ -504,11 +501,10 @@ impl ChunkedUploadService {
// worker free for other connections.
if let Some(ref expected_checksum) = checksum {
let data_clone = data.clone(); // Bytes::clone is O(1) — just an Arc increment
let actual_checksum = tokio::task::spawn_blocking(move || {
format!("{:x}", md5::compute(&data_clone))
})
.await
.map_err(|e| format!("MD5 checksum task failed: {e}"))?;
let actual_checksum =
tokio::task::spawn_blocking(move || format!("{:x}", md5::compute(&data_clone)))
.await
.map_err(|e| format!("MD5 checksum task failed: {e}"))?;
if actual_checksum != *expected_checksum {
return Err(format!(
@@ -576,10 +572,7 @@ impl ChunkedUploadService {
}
/// Get upload status
async fn get_status_inner(
&self,
upload_id: &str,
) -> Result<UploadStatusResponseDto, String> {
async fn get_status_inner(&self, upload_id: &str) -> Result<UploadStatusResponseDto, String> {
let sessions = self.sessions.read().await;
let session = sessions
.get(upload_id)
@@ -645,12 +638,17 @@ impl ChunkedUploadService {
let chunks_meta: Vec<(usize, PathBuf)> = session
.chunks
.iter()
.map(|c| (c.index, session.temp_dir.join(format!("chunk_{:06}", c.index))))
.map(|c| {
(
c.index,
session.temp_dir.join(format!("chunk_{:06}", c.index)),
)
})
.collect();
let total_size = session.total_size;
let hash = tokio::task::spawn_blocking(move || -> Result<String, String> {
use std::io::{Read, Write, BufWriter as StdBufWriter};
use std::io::{BufWriter as StdBufWriter, Read, Write};
let raw_output = std::fs::OpenOptions::new()
.create(true)
@@ -721,10 +719,10 @@ impl ChunkedUploadService {
/// Finalize upload: remove session from RAM and clean up temp directory
async fn finalize_upload_inner(&self, upload_id: &str) -> Result<(), String> {
let mut sessions = self.sessions.write().await;
if let Some(session) = sessions.remove(upload_id) {
if let Err(e) = fs::remove_dir_all(&session.temp_dir).await {
tracing::warn!("Failed to cleanup upload {}: {}", upload_id, e);
}
if let Some(session) = sessions.remove(upload_id)
&& let Err(e) = fs::remove_dir_all(&session.temp_dir).await
{
tracing::warn!("Failed to cleanup upload {}: {}", upload_id, e);
}
Ok(())
}
@@ -919,8 +917,7 @@ mod tests {
};
let json = serde_json::to_vec(&session).expect("serialise");
let restored: UploadSession =
serde_json::from_slice(&json).expect("deserialise");
let restored: UploadSession = serde_json::from_slice(&json).expect("deserialise");
assert_eq!(restored.id, session.id);
assert_eq!(restored.filename, session.filename);
@@ -995,7 +992,9 @@ mod tests {
let recovered = ChunkedUploadService::recover_sessions(&base).await;
assert_eq!(recovered.len(), 1);
let session = recovered.get(&upload_id).expect("session must be recovered");
let session = recovered
.get(&upload_id)
.expect("session must be recovered");
assert_eq!(session.filename, "bigfile.bin");
assert_eq!(session.folder_id, Some("folder-x".into()));
assert_eq!(session.chunks[0].status, ChunkStatus::Complete);
@@ -1050,10 +1049,8 @@ mod tests {
assert!(status.pending_chunks.is_empty());
// 4. Complete (assemble)
let (path, filename, _folder, _ct, size, hash) = service
.complete_upload_inner(&id)
.await
.expect("complete");
let (path, filename, _folder, _ct, size, hash) =
service.complete_upload_inner(&id).await.expect("complete");
assert_eq!(filename, "test.txt");
assert_eq!(size, 1024);
assert!(!hash.is_empty());
@@ -1077,7 +1074,13 @@ mod tests {
let service = ChunkedUploadService::new(base.clone()).await;
let resp = service
.create_session_inner("x.bin".into(), None, "application/octet-stream".into(), 512, Some(512))
.create_session_inner(
"x.bin".into(),
None,
"application/octet-stream".into(),
512,
Some(512),
)
.await
.expect("create");
@@ -1155,12 +1158,18 @@ mod tests {
chunk_size: 512,
chunks: vec![
ChunkInfo {
index: 0, offset: 0, size: 512,
status: ChunkStatus::Pending, checksum: None,
index: 0,
offset: 0,
size: 512,
status: ChunkStatus::Pending,
checksum: None,
},
ChunkInfo {
index: 1, offset: 512, size: 512,
status: ChunkStatus::Pending, checksum: None,
index: 1,
offset: 512,
size: 512,
status: ChunkStatus::Pending,
checksum: None,
},
],
created_at: Utc::now(),
@@ -1171,14 +1180,20 @@ mod tests {
// Write metadata
let json = serde_json::to_vec(&session).unwrap();
fs::write(session_dir.join(SESSION_META_FILE), &json).await.unwrap();
fs::write(session_dir.join(SESSION_META_FILE), &json)
.await
.unwrap();
// Write progress marking both chunks complete
let bitmask = vec![0b00000011u8]; // bits 0 and 1
fs::write(session_dir.join(PROGRESS_FILE), &bitmask).await.unwrap();
fs::write(session_dir.join(PROGRESS_FILE), &bitmask)
.await
.unwrap();
// But only create chunk_000000 on disk — chunk_000001 is "missing"
fs::write(session_dir.join("chunk_000000"), &[0u8; 512]).await.unwrap();
fs::write(session_dir.join("chunk_000000"), &[0u8; 512])
.await
.unwrap();
let recovered = ChunkedUploadService::recover_sessions(&base).await;
let s = recovered.get("partial-session").expect("must be recovered");
+1 -2
View File
@@ -712,8 +712,7 @@ impl DedupService {
let blob_root = blob_root.clone();
async move {
let prefix = &hash[0..2];
let blob_path =
blob_root.join(prefix).join(format!("{}.blob", hash));
let blob_path = blob_root.join(prefix).join(format!("{}.blob", hash));
let mut issues = Vec::new();
@@ -124,7 +124,13 @@ impl FileContentCache {
/// Put file content into cache
///
/// Moka handles eviction automatically based on weight (content size).
pub async fn put(&self, file_id: String, content: Bytes, etag: Arc<str>, content_type: Arc<str>) {
pub async fn put(
&self,
file_id: String,
content: Bytes,
etag: Arc<str>,
content_type: Arc<str>,
) {
let size = content.len();
// Don't cache if too large
@@ -258,24 +264,14 @@ mod tests {
// A file within the limit should be cached
let small = Bytes::from(vec![0u8; 50]);
cache
.put(
"small".to_string(),
small,
"e1".into(),
"app/bin".into(),
)
.put("small".to_string(), small, "e1".into(), "app/bin".into())
.await;
assert!(cache.get("small").await.is_some());
// A file exceeding max_file_size is rejected by our own logic
let big = Bytes::from(vec![1u8; 51]);
cache
.put(
"big".to_string(),
big,
"e2".into(),
"app/bin".into(),
)
.put("big".to_string(), big, "e2".into(), "app/bin".into())
.await;
assert!(
cache.get("big").await.is_none(),
@@ -296,12 +292,7 @@ mod tests {
let content = Bytes::from("test");
cache
.put(
"file1".to_string(),
content,
"e".into(),
"t".into(),
)
.put("file1".to_string(), content, "e".into(), "t".into())
.await;
assert!(cache.get("file1").await.is_some());
@@ -258,11 +258,7 @@ impl ImageTranscodeService {
original_size,
transcoded_size
);
return Ok((
original_content,
original_mime.to_string(),
false,
));
return Ok((original_content, original_mime.to_string(), false));
}
let saved = original_size - transcoded_size;
@@ -39,13 +39,12 @@ impl Argon2PasswordHasher {
///
/// Panics at startup if the parameters are invalid (caught immediately).
pub fn new(memory_cost: u32, time_cost: u32, parallelism: u32) -> Self {
let params = Params::new(memory_cost, time_cost, parallelism, None)
.unwrap_or_else(|e| {
panic!(
"Invalid Argon2 parameters (m={}, t={}, p={}): {}",
memory_cost, time_cost, parallelism, e
)
});
let params = Params::new(memory_cost, time_cost, parallelism, None).unwrap_or_else(|e| {
panic!(
"Invalid Argon2 parameters (m={}, t={}, p={}): {}",
memory_cost, time_cost, parallelism, e
)
});
tracing::info!(
"Argon2PasswordHasher initialized: m_cost={} KiB, t_cost={}, p_cost={}",
@@ -192,11 +192,7 @@ impl ThumbnailService {
}
// 2. Generate thumbnail (CPU-bound, runs in spawn_blocking)
tracing::info!(
"🎨 Generating thumbnail: {} {:?}",
file_id_owned,
size
);
tracing::info!("🎨 Generating thumbnail: {} {:?}", file_id_owned, size);
match self.generate_thumbnail(&original_owned, size).await {
Ok(bytes) => {
// Save to disk (best-effort — don't fail the request)
@@ -245,7 +241,10 @@ impl ThumbnailService {
let max_dim = size.max_dimension();
// Acquire semaphore permit — bounds peak RAM from concurrent decodes
let _permit = self.decode_semaphore.acquire().await
let _permit = self
.decode_semaphore
.acquire()
.await
.map_err(|_| ThumbnailError::TaskError("Decode semaphore closed".into()))?;
// Run image processing in blocking thread pool
@@ -278,9 +277,9 @@ impl ThumbnailService {
// Adaptive filter: faster filters for smaller sizes where
// quality difference vs Lanczos3 is imperceptible
let filter = match size {
ThumbnailSize::Icon => FilterType::Triangle, // 150px — max speed
ThumbnailSize::Icon => FilterType::Triangle, // 150px — max speed
ThumbnailSize::Preview => FilterType::CatmullRom, // 400px — good balance
ThumbnailSize::Large => FilterType::CatmullRom, // 800px — sufficient quality
ThumbnailSize::Large => FilterType::CatmullRom, // 800px — sufficient quality
};
let thumbnail = img.resize(new_width, new_height, filter);
@@ -312,7 +311,10 @@ impl ThumbnailService {
let _permit = match self.decode_semaphore.acquire().await {
Ok(p) => p,
Err(_) => {
tracing::warn!("Decode semaphore closed, skipping thumbnails for {}", file_id);
tracing::warn!(
"Decode semaphore closed, skipping thumbnails for {}",
file_id
);
return;
}
};
@@ -333,8 +335,8 @@ impl ThumbnailService {
)));
}
let img = image::open(&path)
.map_err(|e| ThumbnailError::ImageError(e.to_string()))?;
let img =
image::open(&path).map_err(|e| ThumbnailError::ImageError(e.to_string()))?;
let (orig_w, orig_h) = (img.width(), img.height());
@@ -352,18 +354,15 @@ impl ThumbnailService {
};
let filter = match size {
ThumbnailSize::Icon => FilterType::Triangle,
ThumbnailSize::Icon => FilterType::Triangle,
ThumbnailSize::Preview => FilterType::CatmullRom,
ThumbnailSize::Large => FilterType::CatmullRom,
ThumbnailSize::Large => FilterType::CatmullRom,
};
let thumb = img.resize(new_w, new_h, filter);
let mut buf = Vec::new();
thumb
.write_to(
&mut std::io::Cursor::new(&mut buf),
ImageFormat::WebP,
)
.write_to(&mut std::io::Cursor::new(&mut buf), ImageFormat::WebP)
.map_err(|e| ThumbnailError::ImageError(e.to_string()))?;
Ok((size, Bytes::from(buf)))
@@ -376,15 +375,11 @@ impl ThumbnailService {
let thumbnails = match results {
Ok(Ok(t)) => t,
Ok(Err(e)) => {
tracing::warn!(
"Thumbnail generation failed for {}: {}", file_id, e
);
tracing::warn!("Thumbnail generation failed for {}: {}", file_id, e);
return;
}
Err(e) => {
tracing::warn!(
"Thumbnail task panicked for {}: {}", file_id, e
);
tracing::warn!("Thumbnail task panicked for {}: {}", file_id, e);
return;
}
};
+4 -9
View File
@@ -8,8 +8,8 @@ use crate::{
use async_trait::async_trait;
use async_zip::base::write::ZipFileWriter;
use async_zip::{Compression, ZipEntryBuilder};
use futures::io::AsyncWriteExt as FuturesWriteExt;
use futures::StreamExt;
use futures::io::AsyncWriteExt as FuturesWriteExt;
use std::collections::HashMap;
use std::sync::Arc;
use tempfile::NamedTempFile;
@@ -99,17 +99,13 @@ impl ZipService {
.folder_service
.list_subtree_folders(folder_id)
.await
.map_err(|e| {
ZipError::FolderContentsError(format!("subtree folders: {}", e))
})?;
.map_err(|e| ZipError::FolderContentsError(format!("subtree folders: {}", e)))?;
let all_files = self
.file_service
.list_files_in_subtree(folder_id)
.await
.map_err(|e| {
ZipError::FolderContentsError(format!("subtree files: {}", e))
})?;
.map_err(|e| ZipError::FolderContentsError(format!("subtree files: {}", e)))?;
info!(
"ZIP subtree: {} folders, {} files",
@@ -157,8 +153,7 @@ impl ZipService {
let zip_dir = format!("{}/", folder_zip_path(&folder.path));
// Directory entry (Stored, zero-length body)
let dir_entry =
ZipEntryBuilder::new(zip_dir.clone().into(), Compression::Stored);
let dir_entry = ZipEntryBuilder::new(zip_dir.clone().into(), Compression::Stored);
match zip.write_entry_whole(dir_entry, &[]).await {
Ok(()) => debug!("Folder added to ZIP: {}", zip_dir),
Err(e) => {