Skip to content
49 changes: 49 additions & 0 deletions Storage/FileObjectV2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Supabase.Storage
{
public class FileObjectV2
{

[JsonProperty("id")]
public string Id { get; set; }

Check warning on line 11 in Storage/FileObjectV2.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Id' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 11 in Storage/FileObjectV2.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Id' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

[JsonProperty("version")]
public string Version { get; set; }

Check warning on line 14 in Storage/FileObjectV2.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Version' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 14 in Storage/FileObjectV2.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Version' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

[JsonProperty("name")]
public string? Name { get; set; }

[JsonProperty("bucket_id")]
public string? BucketId { get; set; }

[JsonProperty("updated_at")]
public DateTime? UpdatedAt { get; set; }

[JsonProperty("created_at")]
public DateTime? CreatedAt { get; set; }

[JsonProperty("last_accessed_at")]
public DateTime? LastAccessedAt { get; set; }

[JsonProperty("size")]
public int? Size { get; set; }

[JsonProperty("cache_control")]
public string? CacheControl { get; set; }

[JsonProperty("content_type")]
public string? ContentType { get; set; }

[JsonProperty("etag")]
public string? Etag { get; set; }

[JsonProperty("last_modified")]
public DateTime? LastModified { get; set; }

[JsonProperty("metadata")]
public Dictionary<string, string>? Metadata { get; set; }
}
}
12 changes: 11 additions & 1 deletion Storage/FileOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Supabase.Storage
{
Expand All @@ -12,5 +13,14 @@ public class FileOptions

[JsonProperty("upsert")]
public bool Upsert { get; set; }

[JsonProperty("duplex")]
public string? Duplex { get; set; }

[JsonProperty("metadata")]
public Dictionary<string, string>? Metadata { get; set; }

[JsonProperty("headers")]
public Dictionary<string, string>? Headers { get; set; }
}
}
1 change: 1 addition & 0 deletions Storage/Interfaces/IStorageFileApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public interface IStorageFileApi<TFileObject>
Task<string> DownloadPublicFile(string supabasePath, string localPath, TransformOptions? transformOptions = null, EventHandler<float>? onProgress = null);
string GetPublicUrl(string path, TransformOptions? transformOptions = null);
Task<List<TFileObject>?> List(string path = "", SearchOptions? options = null);
Task<FileObjectV2?> Info(string path);
Task<bool> Move(string fromPath, string toPath, DestinationOptions? options = null);
Task<bool> Copy(string fromPath, string toPath, DestinationOptions? options = null);
Task<TFileObject?> Remove(string path);
Expand Down
37 changes: 37 additions & 0 deletions Storage/StorageFileApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,19 @@ await Helpers.MakeRequest<List<FileObject>>(HttpMethod.Post, $"{Url}/object/list

return response;
}

/// <summary>
/// Retrieves the details of an existing file.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public async Task<FileObjectV2?> Info(string path)
{
var response =
await Helpers.MakeRequest<FileObjectV2>(HttpMethod.Get, $"{Url}/object/info/{BucketId}/{path}", null, Headers);

return response;
}

/// <summary>
/// Uploads a file to an existing bucket.
Expand Down Expand Up @@ -464,6 +477,14 @@ private async Task<string> UploadOrUpdate(string localPath, string supabasePath,
if (options.Upsert)
headers.Add("x-upsert", options.Upsert.ToString().ToLower());

if (options.Metadata != null)
headers.Add("x-metadata", ParseMetadata(options.Metadata));

options.Headers?.ToList().ForEach(x => headers.Add(x.Key, x.Value));

if (options.Duplex != null)
headers.Add("x-duplex", options.Duplex.ToLower());

var progress = new Progress<float>();

if (onProgress != null)
Expand All @@ -474,6 +495,14 @@ private async Task<string> UploadOrUpdate(string localPath, string supabasePath,
return GetFinalPath(supabasePath);
}

private static string ParseMetadata(Dictionary<string, string> metadata)
{
var json = JsonConvert.SerializeObject(metadata);
var base64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(json));

return base64;
}

private async Task<string> UploadOrUpdate(byte[] data, string supabasePath, FileOptions options,
EventHandler<float>? onProgress = null)
{
Expand All @@ -488,6 +517,14 @@ private async Task<string> UploadOrUpdate(byte[] data, string supabasePath, File
if (options.Upsert)
headers.Add("x-upsert", options.Upsert.ToString().ToLower());

if (options.Metadata != null)
headers.Add("x-metadata", ParseMetadata(options.Metadata));

options.Headers?.ToList().ForEach(x => headers.Add(x.Key, x.Value));

if (options.Duplex != null)
headers.Add("x-duplex", options.Duplex.ToLower());

var progress = new Progress<float>();

if (onProgress != null)
Expand Down
48 changes: 47 additions & 1 deletion StorageTests/StorageFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Supabase.Storage;
using Supabase.Storage.Interfaces;
using FileOptions = Supabase.Storage.FileOptions;

namespace StorageTests;

Expand Down Expand Up @@ -75,6 +76,51 @@ public async Task UploadFile()

await _bucket.Remove(new List<string> { name });
}

[TestMethod("File: Upload File With FileOptions")]
public async Task UploadFileWithFileOptions()
{
var didTriggerProgress = new TaskCompletionSource<bool>();

var asset = "supabase-csharp.png";
var name = $"{Guid.NewGuid()}.png";
var basePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)?.Replace("file:", "");

Assert.IsNotNull(basePath);

var imagePath = Path.Combine(basePath, "Assets", asset);

var metadata = new Dictionary<string, string>
{
["custom"] = "metadata",
["local_file"] = "local_file"
};

var headers = new Dictionary<string, string>
{
["x-version"] = "123"
};

var options = new FileOptions
{
Duplex = "duplex",
Metadata = metadata,
Headers = headers,
};
await _bucket.Upload(imagePath, name, options, (_, _) => { didTriggerProgress.TrySetResult(true); });

var item = await _bucket.Info(name);

Assert.IsNotNull(item);
Assert.IsNotNull(item.Metadata);
Assert.AreEqual(metadata["custom"], item.Metadata["custom"]);
Assert.AreEqual(metadata["local_file"], item.Metadata["local_file"]);

var sentProgressEvent = await didTriggerProgress.Task;
Assert.IsTrue(sentProgressEvent);

await _bucket.Remove([name]);
}

[TestMethod("File: Upload Arbitrary Byte Array")]
public async Task UploadArbitraryByteArray()
Expand Down Expand Up @@ -192,7 +238,7 @@ public async Task CopyToAnotherBucket()
foreach (var file in copied)
{
if (file.Name is not null)
await localBucket.Remove(new List<string> { file.Name });
await localBucket.Remove([file.Name]);
}

await Storage.DeleteBucket("copyfile");
Expand Down
Loading