style: fix clippy collapsible_if + cargo fmt
This commit is contained in:
@@ -150,8 +150,7 @@ impl CalDavAdapter {
|
|||||||
} else if name_str == "time-range" || name_str.ends_with(":time-range") {
|
} else if name_str == "time-range" || name_str.ends_with(":time-range") {
|
||||||
// Parse time-range attributes
|
// Parse time-range attributes
|
||||||
for attr in e.attributes().flatten() {
|
for attr in e.attributes().flatten() {
|
||||||
let attr_name =
|
let attr_name = std::str::from_utf8(attr.key.as_ref()).unwrap_or("");
|
||||||
std::str::from_utf8(attr.key.as_ref()).unwrap_or("");
|
|
||||||
let attr_value = attr.unescape_value().unwrap_or_default();
|
let attr_value = attr.unescape_value().unwrap_or_default();
|
||||||
|
|
||||||
if attr_name == "start" {
|
if attr_name == "start" {
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ impl QualifiedName {
|
|||||||
name: name.into(),
|
name: name.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for QualifiedName {
|
impl std::fmt::Display for QualifiedName {
|
||||||
|
|||||||
@@ -118,7 +118,14 @@ impl FileUploadUseCase for FileUploadService {
|
|||||||
) -> Result<FileDto, DomainError> {
|
) -> Result<FileDto, DomainError> {
|
||||||
let file = self
|
let file = self
|
||||||
.file_write
|
.file_write
|
||||||
.save_file_from_temp(name.clone(), folder_id, content_type, temp_path, size, pre_computed_hash)
|
.save_file_from_temp(
|
||||||
|
name.clone(),
|
||||||
|
folder_id,
|
||||||
|
content_type,
|
||||||
|
temp_path,
|
||||||
|
size,
|
||||||
|
pre_computed_hash,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let dto = FileDto::from(file);
|
let dto = FileDto::from(file);
|
||||||
info!(
|
info!(
|
||||||
@@ -165,8 +172,15 @@ impl FileUploadUseCase for FileUploadService {
|
|||||||
})?
|
})?
|
||||||
.len();
|
.len();
|
||||||
|
|
||||||
self.upload_file_streaming(name, folder_id, content_type, file_path, size, pre_computed_hash)
|
self.upload_file_streaming(
|
||||||
.await
|
name,
|
||||||
|
folder_id,
|
||||||
|
content_type,
|
||||||
|
file_path,
|
||||||
|
size,
|
||||||
|
pre_computed_hash,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a file at a specific path (for WebDAV PUT on new resource).
|
/// Creates a file at a specific path (for WebDAV PUT on new resource).
|
||||||
@@ -205,12 +219,13 @@ impl FileUploadUseCase for FileUploadService {
|
|||||||
async fn update_file(&self, path: &str, content: &[u8]) -> Result<(), DomainError> {
|
async fn update_file(&self, path: &str, content: &[u8]) -> Result<(), DomainError> {
|
||||||
// Direct SQL lookup — O(folder_depth) instead of O(total_files)
|
// Direct SQL lookup — O(folder_depth) instead of O(total_files)
|
||||||
if let Some(file_read) = &self.file_read
|
if let Some(file_read) = &self.file_read
|
||||||
&& let Some(file) = file_read.find_file_by_path(path).await? {
|
&& let Some(file) = file_read.find_file_by_path(path).await?
|
||||||
self.file_write
|
{
|
||||||
.update_file_content(file.id(), content.to_vec())
|
self.file_write
|
||||||
.await?;
|
.update_file_content(file.id(), content.to_vec())
|
||||||
return Ok(());
|
.await?;
|
||||||
}
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let path_normalized = path.trim_start_matches('/').trim_end_matches('/');
|
let path_normalized = path.trim_start_matches('/').trim_end_matches('/');
|
||||||
let (parent_path, filename) = if let Some(idx) = path_normalized.rfind('/') {
|
let (parent_path, filename) = if let Some(idx) = path_normalized.rfind('/') {
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ impl StoragePath {
|
|||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.segments.is_empty()
|
self.segments.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for StoragePath {
|
impl std::fmt::Display for StoragePath {
|
||||||
|
|||||||
@@ -106,7 +106,19 @@ impl FileBlobReadRepository {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl FileReadPort for FileBlobReadRepository {
|
impl FileReadPort for FileBlobReadRepository {
|
||||||
async fn get_file(&self, id: &str) -> Result<File, DomainError> {
|
async fn get_file(&self, id: &str) -> Result<File, DomainError> {
|
||||||
let row = sqlx::query_as::<_, (String, String, Option<String>, i64, String, i64, i64, String)>(
|
let row = sqlx::query_as::<
|
||||||
|
_,
|
||||||
|
(
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
Option<String>,
|
||||||
|
i64,
|
||||||
|
String,
|
||||||
|
i64,
|
||||||
|
i64,
|
||||||
|
String,
|
||||||
|
),
|
||||||
|
>(
|
||||||
r#"
|
r#"
|
||||||
SELECT id::text, name, folder_id::text, size, mime_type,
|
SELECT id::text, name, folder_id::text, size, mime_type,
|
||||||
EXTRACT(EPOCH FROM created_at)::bigint,
|
EXTRACT(EPOCH FROM created_at)::bigint,
|
||||||
@@ -374,7 +386,9 @@ impl FileReadPort for FileBlobReadRepository {
|
|||||||
.map_err(|e| DomainError::internal_error("FileBlobRead", format!("find file: {e}")))?;
|
.map_err(|e| DomainError::internal_error("FileBlobRead", format!("find file: {e}")))?;
|
||||||
|
|
||||||
match row {
|
match row {
|
||||||
Some(r) => Ok(Some(self.row_to_file(r.0, r.1, r.2, r.3, r.4, r.5, r.6).await?)),
|
Some(r) => Ok(Some(
|
||||||
|
self.row_to_file(r.0, r.1, r.2, r.3, r.4, r.5, r.6).await?,
|
||||||
|
)),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,12 +138,13 @@ impl FileWritePort for FileBlobWriteRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let sqlx::Error::Database(ref db_err) = e
|
if let sqlx::Error::Database(ref db_err) = e
|
||||||
&& db_err.code().as_deref() == Some("23505") {
|
&& db_err.code().as_deref() == Some("23505")
|
||||||
return Err(DomainError::already_exists(
|
{
|
||||||
"File",
|
return Err(DomainError::already_exists(
|
||||||
format!("{name} already exists in folder"),
|
"File",
|
||||||
));
|
format!("{name} already exists in folder"),
|
||||||
}
|
));
|
||||||
|
}
|
||||||
return Err(DomainError::internal_error(
|
return Err(DomainError::internal_error(
|
||||||
"FileBlobWrite",
|
"FileBlobWrite",
|
||||||
format!("insert: {e}"),
|
format!("insert: {e}"),
|
||||||
@@ -210,12 +211,13 @@ impl FileWritePort for FileBlobWriteRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let sqlx::Error::Database(ref db_err) = e
|
if let sqlx::Error::Database(ref db_err) = e
|
||||||
&& db_err.code().as_deref() == Some("23505") {
|
&& db_err.code().as_deref() == Some("23505")
|
||||||
return Err(DomainError::already_exists(
|
{
|
||||||
"File",
|
return Err(DomainError::already_exists(
|
||||||
format!("{name} already exists in folder"),
|
"File",
|
||||||
));
|
format!("{name} already exists in folder"),
|
||||||
}
|
));
|
||||||
|
}
|
||||||
return Err(DomainError::internal_error(
|
return Err(DomainError::internal_error(
|
||||||
"FileBlobWrite",
|
"FileBlobWrite",
|
||||||
format!("insert: {e}"),
|
format!("insert: {e}"),
|
||||||
@@ -230,8 +232,16 @@ impl FileWritePort for FileBlobWriteRepository {
|
|||||||
&blob_hash[..12]
|
&blob_hash[..12]
|
||||||
);
|
);
|
||||||
|
|
||||||
self.row_to_file(row.0, name, folder_id, size as i64, content_type, row.1, row.2)
|
self.row_to_file(
|
||||||
.await
|
row.0,
|
||||||
|
name,
|
||||||
|
folder_id,
|
||||||
|
size as i64,
|
||||||
|
content_type,
|
||||||
|
row.1,
|
||||||
|
row.2,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn move_file(
|
async fn move_file(
|
||||||
@@ -312,12 +322,13 @@ impl FileWritePort for FileBlobWriteRepository {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
if let sqlx::Error::Database(ref db_err) = e
|
if let sqlx::Error::Database(ref db_err) = e
|
||||||
&& db_err.code().as_deref() == Some("23505") {
|
&& db_err.code().as_deref() == Some("23505")
|
||||||
return DomainError::already_exists(
|
{
|
||||||
"File",
|
return DomainError::already_exists(
|
||||||
"File with that name already exists in target folder".to_string(),
|
"File",
|
||||||
);
|
"File with that name already exists in target folder".to_string(),
|
||||||
}
|
);
|
||||||
|
}
|
||||||
DomainError::internal_error("FileBlobWrite", format!("copy: {e}"))
|
DomainError::internal_error("FileBlobWrite", format!("copy: {e}"))
|
||||||
})?
|
})?
|
||||||
.ok_or_else(|| DomainError::not_found("File", file_id))?;
|
.ok_or_else(|| DomainError::not_found("File", file_id))?;
|
||||||
@@ -360,12 +371,10 @@ impl FileWritePort for FileBlobWriteRepository {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
if let sqlx::Error::Database(ref db_err) = e
|
if let sqlx::Error::Database(ref db_err) = e
|
||||||
&& db_err.code().as_deref() == Some("23505") {
|
&& db_err.code().as_deref() == Some("23505")
|
||||||
return DomainError::already_exists(
|
{
|
||||||
"File",
|
return DomainError::already_exists("File", format!("{new_name} already exists"));
|
||||||
format!("{new_name} already exists"),
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
DomainError::internal_error("FileBlobWrite", format!("rename: {e}"))
|
DomainError::internal_error("FileBlobWrite", format!("rename: {e}"))
|
||||||
})?
|
})?
|
||||||
.ok_or_else(|| DomainError::not_found("File", file_id))?;
|
.ok_or_else(|| DomainError::not_found("File", file_id))?;
|
||||||
@@ -449,13 +458,14 @@ impl FileWritePort for FileBlobWriteRepository {
|
|||||||
|
|
||||||
// Decrement old blob ref (only if hash changed, best-effort)
|
// Decrement old blob ref (only if hash changed, best-effort)
|
||||||
if old_hash != new_hash
|
if old_hash != new_hash
|
||||||
&& let Err(e) = self.dedup.remove_reference(&old_hash).await {
|
&& let Err(e) = self.dedup.remove_reference(&old_hash).await
|
||||||
tracing::warn!(
|
{
|
||||||
"Failed to decrement old blob ref {}: {}",
|
tracing::warn!(
|
||||||
&old_hash[..12],
|
"Failed to decrement old blob ref {}: {}",
|
||||||
e
|
&old_hash[..12],
|
||||||
);
|
e
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,12 +131,13 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
if let sqlx::Error::Database(ref db_err) = e
|
if let sqlx::Error::Database(ref db_err) = e
|
||||||
&& db_err.code().as_deref() == Some("23505") {
|
&& db_err.code().as_deref() == Some("23505")
|
||||||
return DomainError::already_exists(
|
{
|
||||||
"Folder",
|
return DomainError::already_exists(
|
||||||
format!("{name} already exists in parent"),
|
"Folder",
|
||||||
);
|
format!("{name} already exists in parent"),
|
||||||
}
|
);
|
||||||
|
}
|
||||||
DomainError::internal_error("FolderDb", format!("insert: {e}"))
|
DomainError::internal_error("FolderDb", format!("insert: {e}"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -333,12 +334,10 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
if let sqlx::Error::Database(ref db_err) = e
|
if let sqlx::Error::Database(ref db_err) = e
|
||||||
&& db_err.code().as_deref() == Some("23505") {
|
&& db_err.code().as_deref() == Some("23505")
|
||||||
return DomainError::already_exists(
|
{
|
||||||
"Folder",
|
return DomainError::already_exists("Folder", format!("{new_name} already exists"));
|
||||||
format!("{new_name} already exists"),
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
DomainError::internal_error("FolderDb", format!("rename: {e}"))
|
DomainError::internal_error("FolderDb", format!("rename: {e}"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|||||||
@@ -215,9 +215,7 @@ impl DedupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Atomic write: temp file → rename
|
// Atomic write: temp file → rename
|
||||||
let temp_path = self
|
let temp_path = self.temp_root.join(format!("{}.tmp", uuid::Uuid::new_v4()));
|
||||||
.temp_root
|
|
||||||
.join(format!("{}.tmp", uuid::Uuid::new_v4()));
|
|
||||||
fs::write(&temp_path, content).await.map_err(|e| {
|
fs::write(&temp_path, content).await.map_err(|e| {
|
||||||
DomainError::internal_error("Dedup", format!("Failed to write temp blob: {}", e))
|
DomainError::internal_error("Dedup", format!("Failed to write temp blob: {}", e))
|
||||||
})?;
|
})?;
|
||||||
@@ -269,10 +267,7 @@ impl DedupService {
|
|||||||
let file_size = fs::metadata(source_path)
|
let file_size = fs::metadata(source_path)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
DomainError::internal_error(
|
DomainError::internal_error("Dedup", format!("Failed to get file metadata: {}", e))
|
||||||
"Dedup",
|
|
||||||
format!("Failed to get file metadata: {}", e),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.len();
|
.len();
|
||||||
|
|
||||||
@@ -555,7 +550,10 @@ impl DedupService {
|
|||||||
format!("Failed to open blob {}: {}", hash, e),
|
format!("Failed to open blob {}: {}", hash, e),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
Ok(Box::pin(ReaderStream::with_capacity(file, STREAM_CHUNK_SIZE)))
|
Ok(Box::pin(ReaderStream::with_capacity(
|
||||||
|
file,
|
||||||
|
STREAM_CHUNK_SIZE,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stream a byte range of a blob — only reads the requested portion.
|
/// Stream a byte range of a blob — only reads the requested portion.
|
||||||
@@ -588,9 +586,15 @@ impl DedupService {
|
|||||||
if let Some(end_pos) = end {
|
if let Some(end_pos) = end {
|
||||||
let limit = end_pos.saturating_sub(start);
|
let limit = end_pos.saturating_sub(start);
|
||||||
let limited = file.take(limit);
|
let limited = file.take(limit);
|
||||||
Ok(Box::pin(ReaderStream::with_capacity(limited, STREAM_CHUNK_SIZE)))
|
Ok(Box::pin(ReaderStream::with_capacity(
|
||||||
|
limited,
|
||||||
|
STREAM_CHUNK_SIZE,
|
||||||
|
)))
|
||||||
} else {
|
} else {
|
||||||
Ok(Box::pin(ReaderStream::with_capacity(file, STREAM_CHUNK_SIZE)))
|
Ok(Box::pin(ReaderStream::with_capacity(
|
||||||
|
file,
|
||||||
|
STREAM_CHUNK_SIZE,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,15 +684,15 @@ impl DedupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check size
|
// Check size
|
||||||
if let Ok(file_meta) = fs::metadata(&blob_path).await {
|
if let Ok(file_meta) = fs::metadata(&blob_path).await
|
||||||
if file_meta.len() != *expected_size as u64 {
|
&& file_meta.len() != *expected_size as u64
|
||||||
corrupted.push(format!(
|
{
|
||||||
"{}: size mismatch (expected: {}, actual: {})",
|
corrupted.push(format!(
|
||||||
hash,
|
"{}: size mismatch (expected: {}, actual: {})",
|
||||||
expected_size,
|
hash,
|
||||||
file_meta.len()
|
expected_size,
|
||||||
));
|
file_meta.len()
|
||||||
}
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,7 +760,8 @@ impl DedupPort for DedupService {
|
|||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
pre_computed_hash: Option<String>,
|
pre_computed_hash: Option<String>,
|
||||||
) -> Result<DedupResultDto, DomainError> {
|
) -> Result<DedupResultDto, DomainError> {
|
||||||
self.store_from_file(source_path, content_type, pre_computed_hash).await
|
self.store_from_file(source_path, content_type, pre_computed_hash)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn blob_exists(&self, hash: &str) -> bool {
|
async fn blob_exists(&self, hash: &str) -> bool {
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ impl FileContentCache {
|
|||||||
misses: AtomicUsize::new(0),
|
misses: AtomicUsize::new(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FileContentCache {
|
impl Default for FileContentCache {
|
||||||
|
|||||||
@@ -301,7 +301,13 @@ impl ChunkedUploadHandler {
|
|||||||
|
|
||||||
// Upload from assembled file on disk — zero extra RAM copies, hash pre-computed
|
// Upload from assembled file on disk — zero extra RAM copies, hash pre-computed
|
||||||
match upload_service
|
match upload_service
|
||||||
.upload_file_from_path(filename.clone(), folder_id.clone(), content_type, &assembled_path, Some(hash))
|
.upload_file_from_path(
|
||||||
|
filename.clone(),
|
||||||
|
folder_id.clone(),
|
||||||
|
content_type,
|
||||||
|
&assembled_path,
|
||||||
|
Some(hash),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ impl FileHandler {
|
|||||||
.and_then(|v| v.to_str().ok())
|
.and_then(|v| v.to_str().ok())
|
||||||
.and_then(|s| s.parse::<u64>().ok());
|
.and_then(|s| s.parse::<u64>().ok());
|
||||||
if let Some(len) = hint {
|
if let Some(len) = hint {
|
||||||
let _ = file.set_len(len).await; // best-effort
|
let _ = file.set_len(len).await; // best-effort
|
||||||
}
|
}
|
||||||
|
|
||||||
// 512 KB buffer — 8× fewer write syscalls than 64 KB
|
// 512 KB buffer — 8× fewer write syscalls than 64 KB
|
||||||
@@ -164,15 +164,15 @@ impl FileHandler {
|
|||||||
.check_storage_quota(&auth_user.id, total_size)
|
.check_storage_quota(&auth_user.id, total_size)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
let _ = tokio::fs::remove_file(&temp_path).await;
|
let _ = tokio::fs::remove_file(&temp_path).await;
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"⛔ UPLOAD REJECTED (quota): user={}, file={}, size={}",
|
"⛔ UPLOAD REJECTED (quota): user={}, file={}, size={}",
|
||||||
auth_user.username,
|
auth_user.username,
|
||||||
filename,
|
filename,
|
||||||
total_size
|
total_size
|
||||||
);
|
);
|
||||||
return Self::quota_error_response(err).into_response();
|
return Self::quota_error_response(err).into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Streaming upload (temp file → blob store, hash pre-computed) ─
|
// ── Streaming upload (temp file → blob store, hash pre-computed) ─
|
||||||
match upload_service
|
match upload_service
|
||||||
@@ -543,8 +543,7 @@ impl FileHandler {
|
|||||||
multipart: Multipart,
|
multipart: Multipart,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Use the streaming upload handler
|
// Use the streaming upload handler
|
||||||
let response =
|
let response = Self::upload_file(State(state.clone()), auth_user, multipart).await;
|
||||||
Self::upload_file(State(state.clone()), auth_user, multipart).await;
|
|
||||||
|
|
||||||
// Try to extract file info for thumbnail generation
|
// Try to extract file info for thumbnail generation
|
||||||
if let Ok(body_bytes) =
|
if let Ok(body_bytes) =
|
||||||
|
|||||||
Reference in New Issue
Block a user