diff --git a/src/Program.cs b/src/Program.cs index 754b8fd..241e2fb 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -33,7 +33,7 @@ app.UseStaticFiles(new StaticFileOptions app.UseStatusCodePages(); -app.MapPost("/Files/{fileId}", (string fileId, FileService fileService) => +app.MapPost("/Files/{fileId}", (string fileId, CreateFileRequest request, FileService fileService) => { if (fileService.IsValidFileId(fileId) is false) { @@ -49,6 +49,10 @@ app.MapPost("/Files/{fileId}", (string fileId, FileService fileService) => Directory.CreateDirectory(fileDirectory); + var partFilePath = Path.Combine(fileDirectory, "upload.part"); + using var handle = File.OpenHandle(partFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite); + RandomAccess.SetLength(handle, request.Size); + return Results.Ok(new SessionResponse(fileId, "New upload session created.")); }); @@ -117,7 +121,10 @@ app.MapPost("/Files/{fileId}/upload", async ( } using var formStream = chunk.OpenReadStream(); - var hashBytes = await SHA256.HashDataAsync(formStream); + var buffer = new byte[chunk.Length]; + await formStream.ReadExactlyAsync(buffer); + + var hashBytes = SHA256.HashData(buffer); var calculatedHash = Convert.ToHexString(hashBytes).ToLowerInvariant(); if (CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(calculatedHash), Encoding.UTF8.GetBytes(hash.ToLowerInvariant())) is false) @@ -128,20 +135,15 @@ app.MapPost("/Files/{fileId}/upload", async ( var partFilePath = Path.Combine(fileDirectory, "upload.part"); long byteOffset = index * chunkSize; - var fileStreamOptions = new FileStreamOptions - { - Mode = FileMode.OpenOrCreate, - Access = FileAccess.Write, - Share = FileShare.ReadWrite, - BufferSize = 81920, - Options = FileOptions.Asynchronous - }; + using var handle = File.OpenHandle( + partFilePath, + FileMode.Open, + FileAccess.Write, + FileShare.ReadWrite, + FileOptions.Asynchronous + ); - formStream.Position = 0; - - using var targetStream = new FileStream(partFilePath, fileStreamOptions); - targetStream.Seek(byteOffset, SeekOrigin.Begin); - await formStream.CopyToAsync(targetStream); + await RandomAccess.WriteAsync(handle, buffer, byteOffset); await File.WriteAllTextAsync(markerFilePath, string.Empty); @@ -256,6 +258,7 @@ app.MapDelete("/Files/{fileId}", (string fileId, FileService fileService) => app.Run(); +record CreateFileRequest(long Size); record FileCompleteRequest(string Filename, string CompositeHash); record SessionResponse(string FileId, string Message); record ChunkStatusInfo(int Index, string Hash); @@ -263,6 +266,7 @@ record SessionStatusResponse(string FileId, List UploadedChunks record ChunkUploadResponse(string FileId, int Index, string Status); record CompleteResponse(string Status, string Path); +[JsonSerializable(typeof(CreateFileRequest))] [JsonSerializable(typeof(FileCompleteRequest))] [JsonSerializable(typeof(SessionResponse))] [JsonSerializable(typeof(ChunkStatusInfo))] diff --git a/src/wwwroot/index.js b/src/wwwroot/index.js index 1ca26a6..367337b 100644 --- a/src/wwwroot/index.js +++ b/src/wwwroot/index.js @@ -56,7 +56,12 @@ document.addEventListener("DOMContentLoaded", () => { const createFileResponse = await fetch(`/files/${fileId}`, { method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ size: file.size }), }); + let existingChunksMap = new Map(); if (createFileResponse.status === 409 || createFileResponse.ok) {