diff --git a/migrations/sql/20250118_add_reset_monthly_quota_procedure.sql b/migrations/sql/20250118_add_reset_monthly_quota_procedure.sql deleted file mode 100644 index 9bb50b8..0000000 --- a/migrations/sql/20250118_add_reset_monthly_quota_procedure.sql +++ /dev/null @@ -1,50 +0,0 @@ --- Add reset_monthly_quota stored procedure for monthly quota reset --- This procedure is called by the quota maintenance background task - -DROP PROCEDURE IF EXISTS reset_monthly_quota; - -DELIMITER $$ - -CREATE PROCEDURE reset_monthly_quota(IN p_account_hash VARCHAR(255)) -BEGIN - DECLARE v_current_month VARCHAR(7); - DECLARE v_next_reset_date DATE; - - SET v_current_month = DATE_FORMAT(CURDATE(), '%Y-%m'); - SET v_next_reset_date = DATE_ADD(LAST_DAY(CURDATE()), INTERVAL 1 DAY); - - -- Archive current month data (already in usage_bandwidth_monthly) - -- Just need to reset for next month - - -- Reset API call counter - UPDATE usage_storage - SET api_calls_count = 0, - quota_reset_date = v_next_reset_date, - updated_at = NOW() - WHERE account_hash = p_account_hash; - - -- Initialize next month bandwidth record - INSERT INTO usage_bandwidth_monthly ( - account_hash, usage_month, upload_bytes, download_bytes, - upload_count, download_count - ) - SELECT - account_hash, - DATE_FORMAT(v_next_reset_date, '%Y-%m'), - 0, 0, 0, 0 - FROM usage_storage - WHERE account_hash = p_account_hash - ON DUPLICATE KEY UPDATE updated_at = NOW(); - - -- Log quota reset event - INSERT INTO quota_events ( - account_hash, event_type, current_value, limit_value, - severity, message - ) VALUES ( - p_account_hash, 'QUOTA_RESET', 0, 0, - 'INFO', 'Monthly quota has been reset' - ); -END$$ - -DELIMITER ; - diff --git a/migrations/sql/20250118_add_reset_monthly_quota_procedure_down.sql b/migrations/sql/20250118_add_reset_monthly_quota_procedure_down.sql deleted file mode 100644 index 0796505..0000000 --- a/migrations/sql/20250118_add_reset_monthly_quota_procedure_down.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Rollback: Remove reset_monthly_quota stored procedure - -DROP PROCEDURE IF EXISTS reset_monthly_quota; - diff --git a/src/services/file_service.rs b/src/services/file_service.rs index 3b4b98f..b60851e 100644 --- a/src/services/file_service.rs +++ b/src/services/file_service.rs @@ -599,9 +599,78 @@ impl FileService { ); if is_deleted { - info!("File is already deleted: file_id={}", file_id); - // Treat as success if already deleted - return Ok(()); + warn!( + "⚠️ Client requested deletion of already-deleted file_id={}, checking for actual active file", + file_id + ); + + // Try to find actual active file by path/name + // This handles the case where client has wrong file_id (from previous deleted file) + // revision=0 means get the latest active file + match self + .storage + .find_file_by_path_and_name( + &file_info.account_hash, + &file_info.file_path, + &file_info.filename, + 0, // revision=0: get latest active file (highest revision, not deleted) + ) + .await + { + Ok(Some(active_file)) => { + warn!( + "🔄 Found actual active file: old_file_id={}, new_file_id={}, path={}", + file_id, active_file.file_id, file_info.file_path + ); + // Delete the actual active file instead + match self + .storage + .delete_file(&active_file.account_hash, active_file.file_id) + .await + { + Ok(_) => { + self.files.lock().await.remove(&active_file.file_id); + info!( + "✅ Successfully deleted actual active file: file_id={}, path={}", + active_file.file_id, file_info.file_path + ); + + // Send notification for the actual deleted file + if let Some(nm) = &self.notification_manager { + let notification = FileUpdateNotification { + account_hash: active_file.account_hash.clone(), + device_hash: active_file.device_hash.clone(), + file_info: Some(active_file.to_sync_file()), + update_type: crate::sync::file_update_notification::UpdateType::Deleted as i32, + timestamp: chrono::Utc::now().timestamp(), + rename_info: None, + }; + let _ = nm.broadcast_file_update(notification).await; + } + + return Ok(()); + } + Err(e) => { + error!( + "Failed to delete actual active file: file_id={}, error={}", + active_file.file_id, e + ); + return Err(e); + } + } + } + Ok(None) => { + info!("No active file found with same path, file is truly deleted: file_id={}", file_id); + return Ok(()); + } + Err(e) => { + warn!( + "Error searching for active file: {}, treating as already deleted", + e + ); + return Ok(()); + } + } } // Handle file deletion diff --git a/src/storage/mysql_file.rs b/src/storage/mysql_file.rs index 95025ec..8c2ed42 100644 --- a/src/storage/mysql_file.rs +++ b/src/storage/mysql_file.rs @@ -767,7 +767,7 @@ impl MySqlFileExt for MySqlStorage { // Check if file exists and belongs to the user // Use Vec for VARBINARY columns (file_path, filename) let row_opt = sqlx::query( - r#"SELECT file_id, revision, file_path, filename, device_hash, group_id, watcher_id + r#"SELECT file_id, revision, file_path, filename, device_hash, group_id, watcher_id, server_group_id, server_watcher_id FROM files WHERE file_id = ? AND account_hash = ?"#, ) .bind(file_id) @@ -800,6 +800,8 @@ impl MySqlFileExt for MySqlStorage { let device_hash: String = row.try_get("device_hash").unwrap_or_default(); let group_id: i32 = row.try_get("group_id").unwrap_or(0); let watcher_id: i32 = row.try_get("watcher_id").unwrap_or(0); + let server_group_id: i32 = row.try_get("server_group_id").unwrap_or(0); + let server_watcher_id: i32 = row.try_get("server_watcher_id").unwrap_or(0); let new_revision = current_revision + 1; @@ -859,8 +861,8 @@ impl MySqlFileExt for MySqlStorage { // Use Vec for VARBINARY columns to avoid type mismatch sqlx::query( r#"INSERT INTO files - (file_id, account_hash, device_hash, file_path, filename, file_hash, size, unix_permissions) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)"#, + (file_id, account_hash, device_hash, file_path, filename, file_hash, size, unix_permissions, server_group_id, server_watcher_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"#, ) .bind(new_file_id) .bind(account_hash) @@ -870,6 +872,8 @@ impl MySqlFileExt for MySqlStorage { .bind(&file_path_bytes) // file_hash uses file_path_bytes .bind(0i64) .bind(None::) + .bind(server_group_id) + .bind(server_watcher_id) .execute(&mut *tx) .await .map_err(|e| StorageError::Database(format!("Deletion history addition failed (step 1, schema/VARBINARY type mismatch): {}", e)))?;