From ffca94f78990231c8ddd7752f4dd7dd12513c400 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 13 Nov 2025 01:12:13 +0800 Subject: [PATCH] :bug: Fix some issues when creating duplicate indexes and instant upload triggered won't create index --- DysonNetwork.Drive/Index/FileIndexService.cs | 22 +++- .../Storage/FileUploadController.cs | 116 +++++++++++------- 2 files changed, 90 insertions(+), 48 deletions(-) diff --git a/DysonNetwork.Drive/Index/FileIndexService.cs b/DysonNetwork.Drive/Index/FileIndexService.cs index 0707e92..174d82d 100644 --- a/DysonNetwork.Drive/Index/FileIndexService.cs +++ b/DysonNetwork.Drive/Index/FileIndexService.cs @@ -8,15 +8,25 @@ public class FileIndexService(AppDatabase db) /// /// Creates a new file index entry /// - /// The parent folder path with trailing slash + /// The parent folder path with a trailing slash /// The file ID /// The account ID /// The created file index public async Task CreateAsync(string path, string fileId, Guid accountId) { - // Ensure a path has trailing slash and is query-safe + // Ensure a path has a trailing slash and is query-safe var normalizedPath = NormalizePath(path); + // Check if a file with the same name already exists in the same path for this account + var existingFileIndex = await db.FileIndexes + .FirstOrDefaultAsync(fi => fi.AccountId == accountId && fi.Path == normalizedPath && fi.FileId == fileId); + + if (existingFileIndex != null) + { + throw new InvalidOperationException( + $"A file with ID '{fileId}' already exists in path '{normalizedPath}' for account '{accountId}'"); + } + var fileIndex = new SnCloudFileIndex { Path = normalizedPath, @@ -44,7 +54,7 @@ public class FileIndexService(AppDatabase db) // Since properties are init-only, we need to remove the old index and create a new one db.FileIndexes.Remove(fileIndex); - + var newFileIndex = new SnCloudFileIndex { Path = NormalizePath(newPath), @@ -104,7 +114,7 @@ public class FileIndexService(AppDatabase db) public async Task RemoveByPathAsync(Guid accountId, string path) { var normalizedPath = NormalizePath(path); - + var indexes = await db.FileIndexes .Where(fi => fi.AccountId == accountId && fi.Path == normalizedPath) .ToListAsync(); @@ -127,7 +137,7 @@ public class FileIndexService(AppDatabase db) public async Task> GetByPathAsync(Guid accountId, string path) { var normalizedPath = NormalizePath(path); - + return await db.FileIndexes .Where(fi => fi.AccountId == accountId && fi.Path == normalizedPath) .Include(fi => fi.File) @@ -184,4 +194,4 @@ public class FileIndexService(AppDatabase db) return path; } -} +} \ No newline at end of file diff --git a/DysonNetwork.Drive/Storage/FileUploadController.cs b/DysonNetwork.Drive/Storage/FileUploadController.cs index 2d6738f..6d8c79d 100644 --- a/DysonNetwork.Drive/Storage/FileUploadController.cs +++ b/DysonNetwork.Drive/Storage/FileUploadController.cs @@ -61,10 +61,32 @@ public class FileUploadController( EnsureTempDirectoryExists(); + var accountId = Guid.Parse(currentUser.Id); + // Check if a file with the same hash already exists var existingFile = await db.Files.FirstOrDefaultAsync(f => f.Hash == request.Hash); if (existingFile != null) { + // Create the file index if a path is provided, even for existing files + if (string.IsNullOrEmpty(request.Path)) + return Ok(new CreateUploadTaskResponse + { + FileExists = true, + File = existingFile + }); + try + { + await fileIndexService.CreateAsync(request.Path, existingFile.Id, accountId); + logger.LogInformation("Created file index for existing file {FileId} at path {Path}", + existingFile.Id, request.Path); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to create file index for existing file {FileId} at path {Path}", + existingFile.Id, request.Path); + // Don't fail the request if index creation fails, just log it + } + return Ok(new CreateUploadTaskResponse { FileExists = true, @@ -72,7 +94,6 @@ public class FileUploadController( }); } - var accountId = Guid.Parse(currentUser.Id); var taskId = await Nanoid.GenerateAsync(); // Create persistent upload task @@ -92,18 +113,20 @@ public class FileUploadController( if (currentUser.IsSuperuser) return null; var allowed = await permission.HasPermissionAsync(new HasPermissionRequest - { Actor = $"user:{currentUser.Id}", Area = "global", Key = "files.create" }); + { Actor = $"user:{currentUser.Id}", Area = "global", Key = "files.create" }); - return allowed.HasPermission ? null : - new ObjectResult(ApiError.Unauthorized(forbidden: true)) { StatusCode = 403 }; + return allowed.HasPermission + ? null + : new ObjectResult(ApiError.Unauthorized(forbidden: true)) { StatusCode = 403 }; } private Task ValidatePoolAccess(Account currentUser, FilePool pool, CreateUploadTaskRequest request) { if (pool.PolicyConfig.RequirePrivilege <= 0) return Task.FromResult(null); - var privilege = currentUser.PerkSubscription is null ? 0 : - PerkSubscriptionPrivilege.GetPrivilegeFromIdentifier(currentUser.PerkSubscription.Identifier); + var privilege = currentUser.PerkSubscription is null + ? 0 + : PerkSubscriptionPrivilege.GetPrivilegeFromIdentifier(currentUser.PerkSubscription.Identifier); if (privilege < pool.PolicyConfig.RequirePrivilege) { @@ -121,7 +144,7 @@ public class FileUploadController( if (!policy.AllowEncryption && !string.IsNullOrEmpty(request.EncryptPassword)) { return new ObjectResult(ApiError.Unauthorized("File encryption is not allowed in this pool", true)) - { StatusCode = 403 }; + { StatusCode = 403 }; } if (policy.AcceptTypes is { Count: > 0 }) @@ -129,10 +152,10 @@ public class FileUploadController( if (string.IsNullOrEmpty(request.ContentType)) { return new ObjectResult(ApiError.Validation(new Dictionary - { - { "contentType", new[] { "Content type is required by the pool's policy" } } - })) - { StatusCode = 400 }; + { + { "contentType", new[] { "Content type is required by the pool's policy" } } + })) + { StatusCode = 400 }; } var foundMatch = policy.AcceptTypes.Any(acceptType => @@ -141,22 +164,23 @@ public class FileUploadController( return acceptType.Equals(request.ContentType, StringComparison.OrdinalIgnoreCase); var type = acceptType[..^2]; return request.ContentType.StartsWith($"{type}/", StringComparison.OrdinalIgnoreCase); - }); if (!foundMatch) { return new ObjectResult( - ApiError.Unauthorized($"Content type {request.ContentType} is not allowed by the pool's policy", true)) - { StatusCode = 403 }; + ApiError.Unauthorized($"Content type {request.ContentType} is not allowed by the pool's policy", + true)) + { StatusCode = 403 }; } } if (policy.MaxFileSize is not null && request.FileSize > policy.MaxFileSize) { return new ObjectResult(ApiError.Unauthorized( - $"File size {request.FileSize} is larger than the pool's maximum file size {policy.MaxFileSize}", true)) - { StatusCode = 403 }; + $"File size {request.FileSize} is larger than the pool's maximum file size {policy.MaxFileSize}", + true)) + { StatusCode = 403 }; } return null; @@ -173,8 +197,9 @@ public class FileUploadController( if (!ok) { return new ObjectResult( - ApiError.Unauthorized($"File size {billableUnit} MiB is exceeded the user's quota {quota} MiB", true)) - { StatusCode = 403 }; + ApiError.Unauthorized($"File size {billableUnit} MiB is exceeded the user's quota {quota} MiB", + true)) + { StatusCode = 403 }; } return null; @@ -190,8 +215,7 @@ public class FileUploadController( public class UploadChunkRequest { - [Required] - public IFormFile Chunk { get; set; } = null!; + [Required] public IFormFile Chunk { get; set; } = null!; } [HttpPost("chunk/{taskId}/{chunkIndex:int}")] @@ -269,11 +293,13 @@ public class FileUploadController( { var accountId = Guid.Parse(currentUser.Id); await fileIndexService.CreateAsync(persistentTask.Path, fileId, accountId); - logger.LogInformation("Created file index for file {FileId} at path {Path}", fileId, persistentTask.Path); + logger.LogInformation("Created file index for file {FileId} at path {Path}", fileId, + persistentTask.Path); } catch (Exception ex) { - logger.LogWarning(ex, "Failed to create file index for file {FileId} at path {Path}", fileId, persistentTask.Path); + logger.LogWarning(ex, "Failed to create file index for file {FileId} at path {Path}", fileId, + persistentTask.Path); // Don't fail the upload if index creation fails, just log it } } @@ -289,7 +315,8 @@ public class FileUploadController( catch (Exception ex) { // Log the actual exception for debugging - logger.LogError(ex, "Failed to complete upload for task {TaskId}. Error: {ErrorMessage}", taskId, ex.Message); + logger.LogError(ex, "Failed to complete upload for task {TaskId}. Error: {ErrorMessage}", taskId, + ex.Message); // Mark task as failed await persistentTaskService.MarkTaskFailedAsync(taskId); @@ -314,9 +341,9 @@ public class FileUploadController( } private static async Task MergeChunks( - string taskId, - string taskPath, - string mergedFilePath, + string taskId, + string taskPath, + string mergedFilePath, int chunksCount, PersistentTaskService persistentTaskService) { @@ -338,8 +365,8 @@ public class FileUploadController( // Update progress after each chunk is merged var currentProgress = baseProgress + progressPerChunk * (i + 1); await persistentTaskService.UpdateTaskProgressAsync( - taskId, - currentProgress, + taskId, + currentProgress, "Merging chunks... (" + (i + 1) + "/" + chunksCount + ")" ); } @@ -379,7 +406,8 @@ public class FileUploadController( return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 }; var accountId = Guid.Parse(currentUser.Id); - var tasks = await persistentTaskService.GetUserUploadTasksAsync(accountId, status, sortBy, sortDescending, offset, limit); + var tasks = await persistentTaskService.GetUserUploadTasksAsync(accountId, status, sortBy, sortDescending, + offset, limit); Response.Headers.Append("X-Total", tasks.TotalCount.ToString()); @@ -595,18 +623,22 @@ public class FileUploadController( task.Hash, task.UploadedChunks }, - Pool = pool != null ? new - { - pool.Id, - pool.Name, - pool.Description - } : null, - Bundle = bundle != null ? new - { - bundle.Id, - bundle.Name, - bundle.Description - } : null, + Pool = pool != null + ? new + { + pool.Id, + pool.Name, + pool.Description + } + : null, + Bundle = bundle != null + ? new + { + bundle.Id, + bundle.Name, + bundle.Description + } + : null, EstimatedTimeRemaining = CalculateEstimatedTime(task), UploadSpeed = CalculateUploadSpeed(task) }); @@ -652,4 +684,4 @@ public class FileUploadController( _ => $"{bytesPerSecond / (1024 * 1024):F1} MB/s" }; } -} +} \ No newline at end of file