|
4 | 4 | using System.IO; |
5 | 5 | using System.Linq; |
6 | 6 | using System.Net.Http; |
| 7 | +using System.Threading; |
7 | 8 | using System.Threading.Tasks; |
8 | 9 | using System.Web; |
| 10 | +using BirdMessenger.Collections; |
9 | 11 | using Newtonsoft.Json; |
10 | 12 | using Newtonsoft.Json.Converters; |
11 | 13 | using Supabase.Storage.Exceptions; |
@@ -309,6 +311,47 @@ public Task<string> Update(byte[] data, string supabasePath, FileOptions? option |
309 | 311 | return UploadOrUpdate(data, supabasePath, options, onProgress); |
310 | 312 | } |
311 | 313 |
|
| 314 | + /// <summary> |
| 315 | + /// Attempts to upload a file to Supabase storage. If the upload process is interrupted or incomplete, it will attempt to resume the upload. |
| 316 | + /// </summary> |
| 317 | + /// <param name="localPath">The local file path of the file to be uploaded.</param> |
| 318 | + /// <param name="supabasePath">The destination path in Supabase Storage where the file will be stored.</param> |
| 319 | + /// <param name="options">Optional file options to specify metadata or other upload configurations.</param> |
| 320 | + /// <param name="onProgress">An optional event handler for tracking and reporting upload progress as a percentage.</param> |
| 321 | + /// <returns>Returns a task that resolves to a string representing the URL or path of the uploaded file in the storage.</returns> |
| 322 | + public Task UploadOrResume( |
| 323 | + string localPath, |
| 324 | + string supabasePath, |
| 325 | + FileOptions? options, |
| 326 | + EventHandler<float>? onProgress = null, |
| 327 | + CancellationToken cancellationToken = default |
| 328 | + ) |
| 329 | + { |
| 330 | + options ??= new FileOptions(); |
| 331 | + return UploadOrContinue(localPath, supabasePath, options, onProgress, cancellationToken); |
| 332 | + } |
| 333 | + |
| 334 | + /// <summary> |
| 335 | + /// Uploads a file to the provided Supabase path or resumes an interrupted upload. |
| 336 | + /// Uses provided options and allows for a progress event handler to be specified. |
| 337 | + /// </summary> |
| 338 | + /// <param name="data">The byte array representing the file data to be uploaded.</param> |
| 339 | + /// <param name="supabasePath">The path in Supabase where the file should be stored.</param> |
| 340 | + /// <param name="options">The file upload options determining any specific behaviors or settings for the upload.</param> |
| 341 | + /// <param name="onProgress">An optional event handler to monitor and report upload progress as a float percentage.</param> |
| 342 | + /// <returns>A task that resolves to the file's path once the upload is complete.</returns> |
| 343 | + public Task UploadOrResume( |
| 344 | + byte[] data, |
| 345 | + string supabasePath, |
| 346 | + FileOptions? options, |
| 347 | + EventHandler<float>? onProgress = null, |
| 348 | + CancellationToken cancellationToken = default |
| 349 | + ) |
| 350 | + { |
| 351 | + options ??= new FileOptions(); |
| 352 | + return UploadOrContinue(data, supabasePath, options, onProgress, cancellationToken); |
| 353 | + } |
| 354 | + |
312 | 355 | /// <summary> |
313 | 356 | /// Moves an existing file to a new location, optionally allowing renaming. |
314 | 357 | /// </summary> |
@@ -511,6 +554,103 @@ private async Task<string> UploadOrUpdate(string localPath, string supabasePath, |
511 | 554 | return GetFinalPath(supabasePath); |
512 | 555 | } |
513 | 556 |
|
| 557 | + private async Task UploadOrContinue( |
| 558 | + string localPath, |
| 559 | + string supabasePath, |
| 560 | + FileOptions options, |
| 561 | + EventHandler<float>? onProgress = null, |
| 562 | + CancellationToken cancellationToken = default |
| 563 | + ) |
| 564 | + { |
| 565 | + |
| 566 | + var uri = new Uri($"{Url}/upload/resumable"); |
| 567 | + |
| 568 | + var headers = new Dictionary<string, string>(Headers) |
| 569 | + { |
| 570 | + { "cache-control", $"max-age={options.CacheControl}" }, |
| 571 | + }; |
| 572 | + |
| 573 | + var metadata = new MetadataCollection |
| 574 | + { |
| 575 | + ["bucketName"] = BucketId, |
| 576 | + ["objectName"] = supabasePath, |
| 577 | + ["contentType"] = options.ContentType |
| 578 | + }; |
| 579 | + |
| 580 | + if (options.Upsert) |
| 581 | + headers.Add("x-upsert", options.Upsert.ToString().ToLower()); |
| 582 | + |
| 583 | + if (options.Metadata != null) |
| 584 | + headers.Add("x-metadata", ParseMetadata(options.Metadata)); |
| 585 | + |
| 586 | + options.Headers?.ToList().ForEach(x => headers.Add(x.Key, x.Value)); |
| 587 | + |
| 588 | + if (options.Duplex != null) |
| 589 | + headers.Add("x-duplex", options.Duplex.ToLower()); |
| 590 | + |
| 591 | + var progress = new Progress<float>(); |
| 592 | + |
| 593 | + if (onProgress != null) |
| 594 | + progress.ProgressChanged += onProgress; |
| 595 | + |
| 596 | + await Helpers.HttpUploadClient!.UploadOrContinueFileAsync( |
| 597 | + uri, |
| 598 | + localPath, |
| 599 | + headers, |
| 600 | + metadata, |
| 601 | + progress, |
| 602 | + cancellationToken |
| 603 | + ); |
| 604 | + } |
| 605 | + |
| 606 | + private async Task UploadOrContinue( |
| 607 | + byte[] data, |
| 608 | + string supabasePath, |
| 609 | + FileOptions options, |
| 610 | + EventHandler<float>? onProgress = null, |
| 611 | + CancellationToken cancellationToken = default |
| 612 | + ) |
| 613 | + { |
| 614 | + var uri = new Uri($"{Url}/upload/resumable"); |
| 615 | + |
| 616 | + var headers = new Dictionary<string, string>(Headers) |
| 617 | + { |
| 618 | + { "cache-control", $"max-age={options.CacheControl}" }, |
| 619 | + }; |
| 620 | + |
| 621 | + var metadata = new MetadataCollection |
| 622 | + { |
| 623 | + ["bucketName"] = BucketId, |
| 624 | + ["objectName"] = supabasePath, |
| 625 | + ["contentType"] = options.ContentType, |
| 626 | + }; |
| 627 | + |
| 628 | + if (options.Upsert) |
| 629 | + headers.Add("x-upsert", options.Upsert.ToString().ToLower()); |
| 630 | + |
| 631 | + if (options.Metadata != null) |
| 632 | + metadata["metadata"] = JsonConvert.SerializeObject(options.Metadata); |
| 633 | + |
| 634 | + options.Headers?.ToList().ForEach(x => headers.Add(x.Key, x.Value)); |
| 635 | + |
| 636 | + if (options.Duplex != null) |
| 637 | + headers.Add("x-duplex", options.Duplex.ToLower()); |
| 638 | + |
| 639 | + var progress = new Progress<float>(); |
| 640 | + |
| 641 | + if (onProgress != null) |
| 642 | + progress.ProgressChanged += onProgress; |
| 643 | + |
| 644 | + await Helpers.HttpUploadClient!.UploadOrContinueByteAsync( |
| 645 | + uri, |
| 646 | + data, |
| 647 | + headers, |
| 648 | + metadata, |
| 649 | + progress, |
| 650 | + cancellationToken |
| 651 | + ); |
| 652 | + } |
| 653 | + |
514 | 654 | private static string ParseMetadata(Dictionary<string, string> metadata) |
515 | 655 | { |
516 | 656 | var json = JsonConvert.SerializeObject(metadata); |
|
0 commit comments