Skip to content

Commit e4f852e

Browse files
chore: release 2.2.0
Release-As 2.2.0
1 parent e695506 commit e4f852e

File tree

5 files changed

+112
-12
lines changed

5 files changed

+112
-12
lines changed

Storage/DownloadOptions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Supabase.Storage
4+
{
5+
public class DownloadOptions
6+
{
7+
/// <summary>
8+
/// <p>Use the original file name when downloading</p>
9+
/// </summary>
10+
public static readonly DownloadOptions UseOriginalFileName = new DownloadOptions { FileName = "" };
11+
12+
/// <summary>
13+
/// <p>The name of the file to be downloaded</p>
14+
/// <p>When field is null, no download attribute will be added.</p>
15+
/// <p>When field is empty, the original file name will be used. Use <see cref="UseOriginalFileName"/> for quick initialized with original file names.</p>
16+
/// </summary>
17+
public string? FileName { get; set; }
18+
}
19+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Collections.Specialized;
2+
using System.Web;
3+
4+
namespace Supabase.Storage.Extensions
5+
{
6+
public static class DownloadOptionsExtension
7+
{
8+
/// <summary>
9+
/// Transforms options into a NameValueCollection to be used with a <see cref="UriBuilder"/>
10+
/// </summary>
11+
/// <param name="download"></param>
12+
/// <returns></returns>
13+
public static NameValueCollection ToQueryCollection(this DownloadOptions download)
14+
{
15+
var query = HttpUtility.ParseQueryString(string.Empty);
16+
17+
if (download.FileName == null)
18+
{
19+
return query;
20+
}
21+
22+
query.Add("download", string.IsNullOrEmpty(download.FileName) ? "true" : download.FileName);
23+
24+
return query;
25+
}
26+
}
27+
}

Storage/Interfaces/IStorageFileApi.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ public interface IStorageFileApi<TFileObject>
88
where TFileObject : FileObject
99
{
1010
ClientOptions Options { get; }
11-
Task<string> CreateSignedUrl(string path, int expiresIn, TransformOptions? transformOptions = null);
12-
Task<List<CreateSignedUrlsResponse>?> CreateSignedUrls(List<string> paths, int expiresIn);
11+
Task<string> CreateSignedUrl(string path, int expiresIn, TransformOptions? transformOptions = null, DownloadOptions? options = null);
12+
Task<List<CreateSignedUrlsResponse>?> CreateSignedUrls(List<string> paths, int expiresIn, DownloadOptions? options = null);
1313
Task<byte[]> Download(string supabasePath, EventHandler<float>? onProgress = null);
1414
Task<byte[]> Download(string supabasePath, TransformOptions? transformOptions = null, EventHandler<float>? onProgress = null);
1515
Task<string> Download(string supabasePath, string localPath, EventHandler<float>? onProgress = null);
1616
Task<string> Download(string supabasePath, string localPath, TransformOptions? transformOptions = null, EventHandler<float>? onProgress = null);
1717
Task<byte[]> DownloadPublicFile(string supabasePath, TransformOptions? transformOptions = null, EventHandler<float>? onProgress = null);
1818
Task<string> DownloadPublicFile(string supabasePath, string localPath, TransformOptions? transformOptions = null, EventHandler<float>? onProgress = null);
19-
string GetPublicUrl(string path, TransformOptions? transformOptions = null);
19+
string GetPublicUrl(string path, TransformOptions? transformOptions = null, DownloadOptions? options = null);
2020
Task<List<TFileObject>?> List(string path = "", SearchOptions? options = null);
2121
Task<bool> Move(string fromPath, string toPath, DestinationOptions? options = null);
2222
Task<bool> Copy(string fromPath, string toPath, DestinationOptions? options = null);

Storage/StorageFileApi.cs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Specialized;
34
using System.IO;
45
using System.Linq;
56
using System.Net.Http;
@@ -41,15 +42,25 @@ public StorageFileApi(string url, Dictionary<string, string>? headers = null, st
4142
/// </summary>
4243
/// <param name="path"></param>
4344
/// <param name="transformOptions"></param>
45+
/// <param name="downloadOptions"></param>
4446
/// <returns></returns>
45-
public string GetPublicUrl(string path, TransformOptions? transformOptions)
47+
public string GetPublicUrl(string path, TransformOptions? transformOptions, DownloadOptions? downloadOptions = null)
4648
{
49+
var queryParams = HttpUtility.ParseQueryString(string.Empty);
50+
51+
if (downloadOptions != null)
52+
queryParams.Add(downloadOptions.ToQueryCollection());
53+
4754
if (transformOptions == null)
48-
return $"{Url}/object/public/{GetFinalPath(path)}";
55+
{
56+
var queryParamsString = queryParams.ToString();
57+
return $"{Url}/object/public/{GetFinalPath(path)}?{queryParamsString}";
58+
}
4959

60+
queryParams.Add(transformOptions.ToQueryCollection());
5061
var builder = new UriBuilder($"{Url}/render/image/public/{GetFinalPath(path)}")
5162
{
52-
Query = transformOptions.ToQueryCollection().ToString()
63+
Query = queryParams.ToString()
5364
};
5465

5566
return builder.ToString();
@@ -61,8 +72,9 @@ public string GetPublicUrl(string path, TransformOptions? transformOptions)
6172
/// <param name="path">The file path to be downloaded, including the current file name. For example `folder/image.png`.</param>
6273
/// <param name="expiresIn">The number of seconds until the signed URL expires. For example, `60` for a URL which is valid for one minute.</param>
6374
/// <param name="transformOptions"></param>
75+
/// <param name="downloadOptions"></param>
6476
/// <returns></returns>
65-
public async Task<string> CreateSignedUrl(string path, int expiresIn, TransformOptions? transformOptions = null)
77+
public async Task<string> CreateSignedUrl(string path, int expiresIn, TransformOptions? transformOptions = null, DownloadOptions? downloadOptions = null)
6678
{
6779
var body = new Dictionary<string, object?> { { "expiresIn", expiresIn } };
6880
var url = $"{Url}/object/sign/{GetFinalPath(path)}";
@@ -79,22 +91,26 @@ public async Task<string> CreateSignedUrl(string path, int expiresIn, TransformO
7991
if (response == null || string.IsNullOrEmpty(response.SignedUrl))
8092
throw new SupabaseStorageException(
8193
$"Signed Url for {path} returned empty, do you have permission?");
94+
95+
var downloadQueryParams = downloadOptions?.ToQueryCollection().ToString();
8296

83-
return $"{Url}{response?.SignedUrl}";
97+
return $"{Url}{response.SignedUrl}?{downloadQueryParams}";
8498
}
8599

86100
/// <summary>
87101
/// Create signed URLs to download files without requiring permissions. These URLs can be valid for a set number of seconds.
88102
/// </summary>
89103
/// <param name="paths">paths The file paths to be downloaded, including the current file names. For example [`folder/image.png`, 'folder2/image2.png'].</param>
90104
/// <param name="expiresIn">The number of seconds until the signed URLs expire. For example, `60` for URLs which are valid for one minute.</param>
105+
/// <param name="downloadOptions"></param>
91106
/// <returns></returns>
92-
public async Task<List<CreateSignedUrlsResponse>?> CreateSignedUrls(List<string> paths, int expiresIn)
107+
public async Task<List<CreateSignedUrlsResponse>?> CreateSignedUrls(List<string> paths, int expiresIn, DownloadOptions? downloadOptions = null)
93108
{
94109
var body = new Dictionary<string, object> { { "expiresIn", expiresIn }, { "paths", paths } };
95110
var response = await Helpers.MakeRequest<List<CreateSignedUrlsResponse>>(HttpMethod.Post,
96111
$"{Url}/object/sign/{BucketId}", body, Headers);
97112

113+
var downloadQueryParams = downloadOptions?.ToQueryCollection().ToString();
98114
if (response != null)
99115
{
100116
foreach (var item in response)
@@ -103,7 +119,7 @@ public async Task<string> CreateSignedUrl(string path, int expiresIn, TransformO
103119
throw new SupabaseStorageException(
104120
$"Signed Url for {item.Path} returned empty, do you have permission?");
105121

106-
item.SignedUrl = $"{Url}{item.SignedUrl}";
122+
item.SignedUrl = $"{Url}{item.SignedUrl}?{downloadQueryParams}";
107123
}
108124
}
109125

StorageTests/StorageFileTests.cs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,30 @@ public async Task GetPublicLink()
208208

209209
Assert.IsNotNull(url);
210210
}
211+
212+
[TestMethod("File: Get Public Link with download options")]
213+
public async Task GetPublicLinkWithDownloadOptions()
214+
{
215+
var name = $"{Guid.NewGuid()}.bin";
216+
await _bucket.Upload(new Byte[] { 0x0, 0x1 }, name);
217+
var url = _bucket.GetPublicUrl(name, null, new DownloadOptions { FileName = "custom-file.png"});
218+
await _bucket.Remove(new List<string> { name });
219+
220+
Assert.IsNotNull(url);
221+
StringAssert.Contains(url, "download=custom-file.png");
222+
}
223+
224+
[TestMethod("File: Get Public Link with download and transform options")]
225+
public async Task GetPublicLinkWithDownloadAndTransformOptions()
226+
{
227+
var name = $"{Guid.NewGuid()}.bin";
228+
await _bucket.Upload(new Byte[] { 0x0, 0x1 }, name);
229+
var url = _bucket.GetPublicUrl(name, new TransformOptions { Height = 100, Width = 100}, DownloadOptions.UseOriginalFileName);
230+
await _bucket.Remove(new List<string> { name });
231+
232+
Assert.IsNotNull(url);
233+
StringAssert.Contains(url, "download=true");
234+
}
211235

212236
[TestMethod("File: Get Signed Link")]
213237
public async Task GetSignedLink()
@@ -232,6 +256,19 @@ public async Task GetSignedLinkWithTransformOptions()
232256

233257
await _bucket.Remove(new List<string> { name });
234258
}
259+
260+
[TestMethod("File: Get Signed Link with download options")]
261+
public async Task GetSignedLinkWithDownloadOptions()
262+
{
263+
var name = $"{Guid.NewGuid()}.bin";
264+
await _bucket.Upload(new Byte[] { 0x0, 0x1 }, name);
265+
266+
var url = await _bucket.CreateSignedUrl(name, 3600, null, new DownloadOptions { FileName = "custom-file.png"});
267+
Assert.IsTrue(Uri.IsWellFormedUriString(url, UriKind.Absolute));
268+
StringAssert.Contains(url, "download=custom-file.png");
269+
270+
await _bucket.Remove(new List<string> { name });
271+
}
235272

236273
[TestMethod("File: Get Multiple Signed Links")]
237274
public async Task GetMultipleSignedLinks()
@@ -242,13 +279,14 @@ public async Task GetMultipleSignedLinks()
242279
var name2 = $"{Guid.NewGuid()}.bin";
243280
await _bucket.Upload(new Byte[] { 0x0, 0x1 }, name2);
244281

245-
var urls = await _bucket.CreateSignedUrls(new List<string> { name1, name2 }, 3600);
282+
var urls = await _bucket.CreateSignedUrls(new List<string> { name1, name2 }, 3600, DownloadOptions.UseOriginalFileName);
246283

247284
Assert.IsNotNull(urls);
248285

249286
foreach (var response in urls)
250287
{
251-
Assert.IsTrue(Uri.IsWellFormedUriString(response.SignedUrl, UriKind.Absolute));
288+
Assert.IsTrue(Uri.IsWellFormedUriString($"{response.SignedUrl}", UriKind.Absolute));
289+
StringAssert.Contains(response.SignedUrl, "download=true");
252290
}
253291

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

0 commit comments

Comments
 (0)