diff --git a/DriveClient/Helpers/KDriveJsonContext.cs b/DriveClient/Helpers/KDriveJsonContext.cs
index aaebc90..1892454 100644
--- a/DriveClient/Helpers/KDriveJsonContext.cs
+++ b/DriveClient/Helpers/KDriveJsonContext.cs
@@ -11,6 +11,7 @@ namespace kDriveClient.Helpers
[JsonSerializable(typeof(KDriveFile))]
[JsonSerializable(typeof(KDriveChunk))]
[JsonSerializable(typeof(KDriveUploadResponseWraper))]
+ [JsonSerializable(typeof(KDriveFinishRequest))]
[JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)]
public partial class KDriveJsonContext : JsonSerializerContext
{
diff --git a/DriveClient/Helpers/KDriveRequestFactory.cs b/DriveClient/Helpers/KDriveRequestFactory.cs
index 5b62359..ce81a4b 100644
--- a/DriveClient/Helpers/KDriveRequestFactory.cs
+++ b/DriveClient/Helpers/KDriveRequestFactory.cs
@@ -74,11 +74,8 @@ public static HttpRequestMessage CreateChunkUploadRequest(string baseUrl, string
/// An HttpRequestMessage configured to finish the upload session.
public static HttpRequestMessage CreateFinishSessionRequest(long driveId, string sessionToken, string totalChunkHash)
{
- var content = new StringContent(JsonSerializer.Serialize(new
- {
- total_chunk_hash = $"sha256:{totalChunkHash.ToLowerInvariant()}"
- }, KDriveJsonContext.Default.Object));
-
+ // Send empty JSON body - kDrive validates file integrity via individual chunk hashes
+ var content = new StringContent("{}");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return new HttpRequestMessage(HttpMethod.Post, $"/3/drive/{driveId}/upload/session/{sessionToken}/finish")
diff --git a/DriveClient/Models/KDriveFile.cs b/DriveClient/Models/KDriveFile.cs
index 848447a..7fd2b91 100644
--- a/DriveClient/Models/KDriveFile.cs
+++ b/DriveClient/Models/KDriveFile.cs
@@ -1,4 +1,6 @@
-namespace kDriveClient.Models
+using System.Text;
+
+namespace kDriveClient.Models
{
///
/// KDriveFile represents a file in the kDrive system.
@@ -36,14 +38,17 @@ public class KDriveFile
public string? SymbolicLink { get; set; }
///
- /// TotalChunkHash is the SHA-256 hash of the entire file content, computed from all chunks.
+ /// TotalChunkHash is the SHA-256 hash of the entire file content.
///
public string TotalChunkHash
{
get
{
- return Convert.ToHexString(
- SHA256.HashData(this.Content));
+ var originalPosition = this.Content.Position;
+ this.Content.Position = 0;
+ var hash = Convert.ToHexString(SHA256.HashData(this.Content));
+ this.Content.Position = originalPosition;
+ return hash;
}
}
@@ -82,7 +87,8 @@ public void SplitIntoChunks(int chunkSize)
while ((bytesRead = this.Content.Read(buffer, 0, chunkSize)) > 0)
{
byte[] content = [.. buffer.Take(bytesRead)];
- this.Chunks.Add(new KDriveChunk(content, chunkNumber++, SHA256.HashData(content)));
+ var chunkHash = SHA256.HashData(content);
+ this.Chunks.Add(new KDriveChunk(content, chunkNumber++, chunkHash));
}
this.Content.Position = 0;
diff --git a/DriveClient/Models/KDriveFinishRequest.cs b/DriveClient/Models/KDriveFinishRequest.cs
new file mode 100644
index 0000000..b02e2f9
--- /dev/null
+++ b/DriveClient/Models/KDriveFinishRequest.cs
@@ -0,0 +1,16 @@
+using System.Text.Json.Serialization;
+
+namespace kDriveClient.Models
+{
+ ///
+ /// Represents the request body for finishing an upload session.
+ ///
+ public class KDriveFinishRequest
+ {
+ ///
+ /// The total chunk hash of the uploaded file.
+ ///
+ [JsonPropertyName("total_chunk_hash")]
+ public string TotalChunkHash { get; set; } = string.Empty;
+ }
+}
diff --git a/DriveClient/kDriveClient/KDriveClient.cs b/DriveClient/kDriveClient/KDriveClient.cs
index b3d0b06..c5666b0 100644
--- a/DriveClient/kDriveClient/KDriveClient.cs
+++ b/DriveClient/kDriveClient/KDriveClient.cs
@@ -81,7 +81,7 @@ public KDriveClient(string token, long driveId, ILogger? logger) :
/// Choose if we should make a speed test to optimize chunks
/// Number of parrallels threads
/// Logger
- public KDriveClient(string token, long driveId, bool autoChunk, int parallelism, ILogger? logger) : this(token, driveId, autoChunk, parallelism, logger, null)
+ public KDriveClient(string token, long driveId, bool autoChunk, int parallelism, ILogger? logger) : this(token, driveId, autoChunk, parallelism, logger, null, null)
{ }
///
@@ -93,7 +93,8 @@ public KDriveClient(string token, long driveId, bool autoChunk, int parallelism,
/// Number of parrallels threads
/// Logger
/// Custome HttpClient
- public KDriveClient(string token, long driveId, bool autoChunk, int parallelism, ILogger? logger, HttpClient? httpClient = null)
+ /// Optional custom chunk size in bytes. If not specified, will be calculated dynamically based on speed test when autoChunk is true.
+ public KDriveClient(string token, long driveId, bool autoChunk, int parallelism, ILogger? logger, HttpClient? httpClient = null, int? chunkSize = null)
{
DriveId = driveId;
Parallelism = parallelism;
@@ -103,12 +104,25 @@ public KDriveClient(string token, long driveId, bool autoChunk, int parallelism,
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("kDriveClient.NET/version");
this.Logger?.LogInformation("KDriveClient initialized with Drive ID: {DriveId}", DriveId);
+
+ if (chunkSize.HasValue)
+ {
+ DynamicChunkSizeBytes = chunkSize.Value;
+ this.Logger?.LogInformation("Using custom chunk size: {ChunkSize} bytes", DynamicChunkSizeBytes);
+ }
+
if (autoChunk)
{
this.Logger?.LogInformation("Auto chunking enabled, initializing upload strategy...");
- InitializeUploadStrategyAsync().GetAwaiter().GetResult();
+ InitializeUploadStrategyAsync(chunkSize).GetAwaiter().GetResult();
this.Logger?.LogInformation("Upload strategy initialized with direct upload threshold: {Threshold} bytes and dynamic chunk size: {ChunkSize} bytes", DirectUploadThresholdBytes, DynamicChunkSizeBytes);
}
+ else if (!chunkSize.HasValue)
+ {
+ // If autoChunk is disabled and no custom chunk size provided, use a default
+ DynamicChunkSizeBytes = 1024 * 1024; // Default to 1MB chunks
+ this.Logger?.LogInformation("Using default chunk size: {ChunkSize} bytes", DynamicChunkSizeBytes);
+ }
}
///
diff --git a/DriveClient/kDriveClient/SpeedTest.cs b/DriveClient/kDriveClient/SpeedTest.cs
index 753c025..97fb243 100644
--- a/DriveClient/kDriveClient/SpeedTest.cs
+++ b/DriveClient/kDriveClient/SpeedTest.cs
@@ -11,9 +11,10 @@ public partial class KDriveClient
///
/// Initializes the upload strategy by performing a speed test.
///
+ /// Optional custom chunk size in bytes. If specified, only DirectUploadThresholdBytes will be calculated from speed test.
/// Cancellation token to cancel the operation.
/// A task that represents the asynchronous operation.
- private async Task InitializeUploadStrategyAsync(CancellationToken ct = default)
+ private async Task InitializeUploadStrategyAsync(int? customChunkSize = null, CancellationToken ct = default)
{
this.Logger?.LogInformation("Starting upload strategy initialization...");
var buffer = new byte[1024 * 1024];
@@ -65,9 +66,18 @@ private async Task InitializeUploadStrategyAsync(CancellationToken ct = default)
var speedBytesPerSec = buffer.Length / (sw.ElapsedMilliseconds / 1000.0);
DirectUploadThresholdBytes = (long)speedBytesPerSec;
- DynamicChunkSizeBytes = (int)(speedBytesPerSec * 0.9);
- this.Logger?.LogInformation("Upload strategy initialized: DirectUploadThresholdBytes = {DirectUploadThresholdBytes}, DynamicChunkSizeBytes = {DynamicChunkSizeBytes}",
- DirectUploadThresholdBytes, DynamicChunkSizeBytes);
+
+ if (!customChunkSize.HasValue)
+ {
+ DynamicChunkSizeBytes = (int)(speedBytesPerSec * 0.9);
+ this.Logger?.LogInformation("Upload strategy initialized: DirectUploadThresholdBytes = {DirectUploadThresholdBytes}, DynamicChunkSizeBytes = {DynamicChunkSizeBytes} (calculated from speed test)",
+ DirectUploadThresholdBytes, DynamicChunkSizeBytes);
+ }
+ else
+ {
+ this.Logger?.LogInformation("Upload strategy initialized: DirectUploadThresholdBytes = {DirectUploadThresholdBytes}, DynamicChunkSizeBytes = {DynamicChunkSizeBytes} (custom)",
+ DirectUploadThresholdBytes, DynamicChunkSizeBytes);
+ }
}
}
}
\ No newline at end of file