From 24b49ddb746bd99aa6c1a7b6bc19d2bcb1d469ae Mon Sep 17 00:00:00 2001 From: Diego Santo Date: Sun, 7 Sep 2025 15:59:30 -0300 Subject: [PATCH 1/2] feat: implement cancelation token on some of upload method --- Storage/Extensions/HttpClientProgress.cs | 12 +++++----- Storage/Interfaces/IStorageFileApi.cs | 9 ++++---- Storage/StorageFileApi.cs | 25 +++++++++++---------- StorageTests/StorageFileTests.cs | 28 ++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 22 deletions(-) diff --git a/Storage/Extensions/HttpClientProgress.cs b/Storage/Extensions/HttpClientProgress.cs index 6410bbc..9e2c0c6 100644 --- a/Storage/Extensions/HttpClientProgress.cs +++ b/Storage/Extensions/HttpClientProgress.cs @@ -90,19 +90,19 @@ internal static class HttpClientProgress } } - public static Task UploadFileAsync(this HttpClient client, Uri uri, string filePath, Dictionary? headers = null, Progress? progress = null) + public static Task UploadFileAsync(this HttpClient client, Uri uri, string filePath, Dictionary? headers = null, Progress? progress = null, CancellationToken cancellationToken = default) { var fileStream = new FileStream(filePath, mode: FileMode.Open, FileAccess.Read); - return UploadAsync(client, uri, fileStream, headers, progress); + return UploadAsync(client, uri, fileStream, headers, progress, cancellationToken); } - public static Task UploadBytesAsync(this HttpClient client, Uri uri, byte[] data, Dictionary? headers = null, Progress? progress = null) + public static Task UploadBytesAsync(this HttpClient client, Uri uri, byte[] data, Dictionary? headers = null, Progress? progress = null, CancellationToken cancellationToken = default) { var stream = new MemoryStream(data); - return UploadAsync(client, uri, stream, headers, progress); + return UploadAsync(client, uri, stream, headers, progress, cancellationToken); } - public static async Task UploadAsync(this HttpClient client, Uri uri, Stream stream, Dictionary? headers = null, Progress? progress = null) + public static async Task UploadAsync(this HttpClient client, Uri uri, Stream stream, Dictionary? headers = null, Progress? progress = null, CancellationToken cancellationToken = default) { var content = new ProgressableStreamContent(stream, 4096, progress); @@ -119,7 +119,7 @@ public static async Task UploadAsync(this HttpClient client } } - var response = await client.PostAsync(uri, content); + var response = await client.PostAsync(uri, content, cancellationToken); if (!response.IsSuccessStatusCode) { diff --git a/Storage/Interfaces/IStorageFileApi.cs b/Storage/Interfaces/IStorageFileApi.cs index 4af9dcc..5941976 100644 --- a/Storage/Interfaces/IStorageFileApi.cs +++ b/Storage/Interfaces/IStorageFileApi.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Supabase.Storage.Interfaces @@ -23,10 +24,10 @@ public interface IStorageFileApi Task Copy(string fromPath, string toPath, DestinationOptions? options = null); Task Remove(string path); Task?> Remove(List paths); - Task Update(byte[] data, string supabasePath, FileOptions? options = null, EventHandler? onProgress = null); - Task Update(string localFilePath, string supabasePath, FileOptions? options = null, EventHandler? onProgress = null); - Task Upload(byte[] data, string supabasePath, FileOptions? options = null, EventHandler? onProgress = null, bool inferContentType = true); - Task Upload(string localFilePath, string supabasePath, FileOptions? options = null, EventHandler? onProgress = null, bool inferContentType = true); + Task Update(byte[] data, string supabasePath, FileOptions? options = null, EventHandler? onProgress = null, CancellationToken cancellationToken = default); + Task Update(string localFilePath, string supabasePath, FileOptions? options = null, EventHandler? onProgress = null, CancellationToken cancellationToken = default); + Task Upload(byte[] data, string supabasePath, FileOptions? options = null, EventHandler? onProgress = null, bool inferContentType = true, CancellationToken cancellationToken = default); + Task Upload(string localFilePath, string supabasePath, FileOptions? options = null, EventHandler? onProgress = null, bool inferContentType = true, CancellationToken cancellationToken = default); Task UploadToSignedUrl(byte[] data, UploadSignedUrl url, FileOptions? options = null, EventHandler? onProgress = null, bool inferContentType = true); Task UploadToSignedUrl(string localFilePath, UploadSignedUrl url, FileOptions? options = null, EventHandler? onProgress = null, bool inferContentType = true); Task CreateUploadSignedUrl(string supabasePath); diff --git a/Storage/StorageFileApi.cs b/Storage/StorageFileApi.cs index 2ab8091..068e6d9 100644 --- a/Storage/StorageFileApi.cs +++ b/Storage/StorageFileApi.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using System.Web; using Newtonsoft.Json; @@ -172,14 +173,14 @@ await Helpers.MakeRequest>(HttpMethod.Post, $"{Url}/object/list /// /// public async Task Upload(string localFilePath, string supabasePath, FileOptions? options = null, - EventHandler? onProgress = null, bool inferContentType = true) + EventHandler? onProgress = null, bool inferContentType = true, CancellationToken cancellationToken = default) { options ??= new FileOptions(); if (inferContentType) options.ContentType = MimeMapping.MimeUtility.GetMimeMapping(localFilePath); - var result = await UploadOrUpdate(localFilePath, supabasePath, options, onProgress); + var result = await UploadOrUpdate(localFilePath, supabasePath, options, onProgress, cancellationToken); return result; } @@ -193,14 +194,14 @@ public async Task Upload(string localFilePath, string supabasePath, File /// /// public async Task Upload(byte[] data, string supabasePath, FileOptions? options = null, - EventHandler? onProgress = null, bool inferContentType = true) + EventHandler? onProgress = null, bool inferContentType = true, CancellationToken cancellationToken = default) { options ??= new FileOptions(); if (inferContentType) options.ContentType = MimeMapping.MimeUtility.GetMimeMapping(supabasePath); - var result = await UploadOrUpdate(data, supabasePath, options, onProgress); + var result = await UploadOrUpdate(data, supabasePath, options, onProgress, cancellationToken); return result; } @@ -288,10 +289,10 @@ public async Task UploadToSignedUrl(byte[] data, UploadSignedUrl signedU /// /// public Task Update(string localFilePath, string supabasePath, FileOptions? options = null, - EventHandler? onProgress = null) + EventHandler? onProgress = null, CancellationToken cancellationToken = default) { options ??= new FileOptions(); - return UploadOrUpdate(localFilePath, supabasePath, options, onProgress); + return UploadOrUpdate(localFilePath, supabasePath, options, onProgress, cancellationToken); } /// @@ -303,10 +304,10 @@ public Task Update(string localFilePath, string supabasePath, FileOption /// /// public Task Update(byte[] data, string supabasePath, FileOptions? options = null, - EventHandler? onProgress = null) + EventHandler? onProgress = null, CancellationToken cancellationToken = default) { options ??= new FileOptions(); - return UploadOrUpdate(data, supabasePath, options, onProgress); + return UploadOrUpdate(data, supabasePath, options, onProgress, cancellationToken); } /// @@ -480,7 +481,7 @@ public async Task CreateUploadSignedUrl(string supabasePath) } private async Task UploadOrUpdate(string localPath, string supabasePath, FileOptions options, - EventHandler? onProgress = null) + EventHandler? onProgress = null, CancellationToken cancellationToken = default) { Uri uri = new Uri($"{Url}/object/{GetFinalPath(supabasePath)}"); @@ -506,7 +507,7 @@ private async Task UploadOrUpdate(string localPath, string supabasePath, if (onProgress != null) progress.ProgressChanged += onProgress; - await Helpers.HttpUploadClient!.UploadFileAsync(uri, localPath, headers, progress); + await Helpers.HttpUploadClient!.UploadFileAsync(uri, localPath, headers, progress, cancellationToken); return GetFinalPath(supabasePath); } @@ -520,7 +521,7 @@ private static string ParseMetadata(Dictionary metadata) } private async Task UploadOrUpdate(byte[] data, string supabasePath, FileOptions options, - EventHandler? onProgress = null) + EventHandler? onProgress = null, CancellationToken cancellationToken = default) { Uri uri = new Uri($"{Url}/object/{GetFinalPath(supabasePath)}"); @@ -546,7 +547,7 @@ private async Task UploadOrUpdate(byte[] data, string supabasePath, File if (onProgress != null) progress.ProgressChanged += onProgress; - await Helpers.HttpUploadClient!.UploadBytesAsync(uri, data, headers, progress); + await Helpers.HttpUploadClient!.UploadBytesAsync(uri, data, headers, progress, cancellationToken); return GetFinalPath(supabasePath); } diff --git a/StorageTests/StorageFileTests.cs b/StorageTests/StorageFileTests.cs index 4182e4a..419c1c8 100644 --- a/StorageTests/StorageFileTests.cs +++ b/StorageTests/StorageFileTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Supabase.Storage; @@ -142,6 +143,33 @@ public async Task UploadArbitraryByteArray() await _bucket.Remove(new List { name }); } + + [TestMethod("File: Cancel Upload Arbitrary Byte Array")] + public async Task UploadArbitraryByteArrayCanceled() + { + var tsc = new TaskCompletionSource(); + using var ctk = new CancellationTokenSource(TimeSpan.FromMilliseconds(1)); + + var data = new byte[20 * 1024 * 1024]; + var rng = new Random(); + rng.NextBytes(data); + var name = $"{Guid.NewGuid()}.bin"; + + var action = async () => + { + await _bucket.Upload(data, name, null, (_, _) => tsc.TrySetResult(true), true, ctk.Token); + }; + + await Assert.ThrowsExceptionAsync(action); + + var list = await _bucket.List(); + Assert.IsNotNull(list); + + var existing = list.Find(item => item.Name == name); + Assert.IsNull(existing); + + await _bucket.Remove([name]); + } [TestMethod("File: Download")] public async Task DownloadFile() From dba16f47426d64720c58338851b1d55af1c4230d Mon Sep 17 00:00:00 2001 From: Diego Santo Date: Tue, 9 Sep 2025 20:10:15 -0300 Subject: [PATCH 2/2] fix: change the upload cancelation time --- StorageTests/StorageFileTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StorageTests/StorageFileTests.cs b/StorageTests/StorageFileTests.cs index 3047a5d..f2d2f82 100644 --- a/StorageTests/StorageFileTests.cs +++ b/StorageTests/StorageFileTests.cs @@ -270,7 +270,7 @@ public async Task UploadOrResumeByteWithInterruptionAndResume() var options = new FileOptions { Duplex = "duplex", Metadata = metadata }; - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)); + using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(300)); try {