├── global.json ├── Uploadcare ├── icon.png ├── DTO │ ├── FilePageData.cs │ ├── GroupPageData.cs │ ├── UploadBaseData.cs │ ├── UploadFromUrlData.cs │ ├── UploadFromUrlStatusData.cs │ ├── MassOperationsData.cs │ ├── CopyFileData.cs │ └── BasePageData.cs ├── UploadcareAuthType.cs ├── Utils │ ├── UrlParameters │ │ ├── IUrlParameter.cs │ │ ├── EOrderBy.cs │ │ ├── LimitParameter.cs │ │ ├── FilesStoredParameter.cs │ │ ├── FilesRemovedParameter.cs │ │ ├── OrderingFromParameter.cs │ │ └── OrderingParameter.cs │ ├── IPageData.cs │ ├── AuthHelper.cs │ ├── CryptoHelper.cs │ ├── Urls.cs │ ├── PagedDataFilesEnumerator.cs │ └── RequestHelper.cs ├── IUploadcareConnection.cs ├── Clients │ ├── IProjectsClient.cs │ ├── IFaceDetectionClient.cs │ ├── ProjectsClient.cs │ ├── FaceDetectionClient.cs │ ├── IUploadcareClient.cs │ ├── IGroupsClient.cs │ ├── IWebhooksClient.cs │ ├── GroupsClient.cs │ ├── WebhookClient.cs │ ├── IFilesClient.cs │ └── FilesClient.cs ├── Exceptions │ ├── UploadFailureException.cs │ └── UploadcareApiExceptions.cs ├── IPaginatedQueryBuilder.cs ├── UploadcareConnection.cs ├── Models │ ├── UploadcareWebhook.cs │ ├── UploadcareProject.cs │ ├── UploadcareGroup.cs │ ├── UploadcareFile.cs │ └── UploadcareFaceDetection.cs ├── Uploadcare.csproj ├── Upload │ ├── SignedFileUploader.cs │ ├── UrlUploader.cs │ └── FileUploader.cs ├── GroupsQueryBuilder.cs ├── Client.cs ├── FileQueryBuilder.cs └── CdnPathBuilder.cs ├── Uploadcare.Tests ├── 0.jpg ├── 1.jpg ├── 2.jpg ├── Lenna.png ├── xunit.runner.json ├── detectedfaces.json ├── Clients │ ├── ProjectsClientTest.cs │ ├── WebhooksTests.cs │ ├── GroupsClientTest.cs │ └── FileClientTest.cs ├── Uploaders │ ├── UrlUploaderTest.cs │ ├── FileUploaderTest.cs │ └── SignedFileUploaderTest.cs ├── Helpers │ └── HashHelper.cs ├── Utils │ ├── UploadcareFaceConverterTests.cs │ └── CdnPathBuilderTest.cs └── Uploadcare.Tests.csproj ├── .travis.yml ├── .github └── workflows │ └── build-and-test ├── LICENSE ├── Uploadcare.sln ├── azure-pipelines.yml ├── .gitattributes ├── README.md └── .gitignore /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ "UploadcareCSharp", "UploadcareCSharp.Test" ] 3 | } -------------------------------------------------------------------------------- /Uploadcare/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okolobaxa/uploadcare-dotnet/HEAD/Uploadcare/icon.png -------------------------------------------------------------------------------- /Uploadcare.Tests/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okolobaxa/uploadcare-dotnet/HEAD/Uploadcare.Tests/0.jpg -------------------------------------------------------------------------------- /Uploadcare.Tests/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okolobaxa/uploadcare-dotnet/HEAD/Uploadcare.Tests/1.jpg -------------------------------------------------------------------------------- /Uploadcare.Tests/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okolobaxa/uploadcare-dotnet/HEAD/Uploadcare.Tests/2.jpg -------------------------------------------------------------------------------- /Uploadcare.Tests/Lenna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okolobaxa/uploadcare-dotnet/HEAD/Uploadcare.Tests/Lenna.png -------------------------------------------------------------------------------- /Uploadcare.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "parallelizeTestCollections": true, 3 | "maxParallelThreads": -1 4 | } -------------------------------------------------------------------------------- /Uploadcare/DTO/FilePageData.cs: -------------------------------------------------------------------------------- 1 | using Uploadcare.Models; 2 | 3 | namespace Uploadcare.DTO 4 | { 5 | internal class FilePageData : BasePageData 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Uploadcare/DTO/GroupPageData.cs: -------------------------------------------------------------------------------- 1 | using Uploadcare.Models; 2 | 3 | namespace Uploadcare.DTO 4 | { 5 | internal class GroupPageData : BasePageData 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Uploadcare/UploadcareAuthType.cs: -------------------------------------------------------------------------------- 1 | namespace Uploadcare 2 | { 3 | public enum UploadcareAuthType 4 | { 5 | NoAuth = 0, 6 | Simple = 1, 7 | Signed = 2 8 | } 9 | } -------------------------------------------------------------------------------- /Uploadcare/Utils/UrlParameters/IUrlParameter.cs: -------------------------------------------------------------------------------- 1 | namespace Uploadcare.Utils.UrlParameters 2 | { 3 | internal interface IUrlParameter 4 | { 5 | string GetParam(); 6 | string GetValue(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Uploadcare/DTO/UploadBaseData.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Uploadcare.DTO 4 | { 5 | internal class UploadcareFileBaseData 6 | { 7 | [JsonPropertyName("file")] 8 | public string File { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /Uploadcare/DTO/UploadFromUrlData.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Uploadcare.DTO 4 | { 5 | internal class UploadFromUrlData 6 | { 7 | [JsonPropertyName("token")] 8 | public string Token { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Uploadcare/IUploadcareConnection.cs: -------------------------------------------------------------------------------- 1 | namespace Uploadcare 2 | { 3 | public interface IUploadcareConnection 4 | { 5 | UploadcareAuthType AuthType { get; } 6 | 7 | string PublicKey { get; } 8 | 9 | string PrivateKey { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /Uploadcare/Utils/IPageData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Uploadcare.Utils 4 | { 5 | internal interface IPageData 6 | { 7 | List GetResults(); 8 | bool HasMore(); 9 | 10 | int Total { get; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Uploadcare/Utils/UrlParameters/EOrderBy.cs: -------------------------------------------------------------------------------- 1 | namespace Uploadcare.Utils.UrlParameters 2 | { 3 | internal enum EFileOrderBy 4 | { 5 | DatetimeUploaded = 0, 6 | DatetimeUploadedDesc = 1, 7 | Size = 2, 8 | SizeDesc = 3 9 | } 10 | 11 | internal enum EGroupOrderBy 12 | { 13 | DatetimeCreated = 0, 14 | DatetimeCreatedDesc = 1 15 | } 16 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | sudo: required 5 | dist: bionic 6 | mono: none 7 | addons: 8 | snaps: 9 | - name: dotnet-sdk 10 | classic: true 11 | channel: latest/stable 12 | install: 13 | - sudo snap alias dotnet-sdk.dotnet dotnet 14 | - dotnet --version 15 | - dotnet restore 16 | script: 17 | - dotnet build 18 | - dotnet test Uploadcare.Tests/Uploadcare.Tests.csproj 19 | -------------------------------------------------------------------------------- /Uploadcare/Clients/IProjectsClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Uploadcare.Models; 3 | 4 | namespace Uploadcare.Clients 5 | { 6 | public interface IProjectsClient 7 | { 8 | /// 9 | /// This provides basic information about the project you connect to. 10 | /// 11 | /// Project resource 12 | Task GetAsync(); 13 | } 14 | } -------------------------------------------------------------------------------- /Uploadcare/DTO/UploadFromUrlStatusData.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Uploadcare.DTO 4 | { 5 | internal class UploadFromUrlStatusData 6 | { 7 | [JsonPropertyName("status")] 8 | public string Status { get; set; } 9 | 10 | [JsonPropertyName("file_id")] 11 | public string FileId { get; set; } 12 | 13 | [JsonPropertyName("error")] 14 | public string Error { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Uploadcare/Exceptions/UploadFailureException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Uploadcare.Exceptions 4 | { 5 | public class UploadFailureException : Exception 6 | { 7 | public UploadFailureException() 8 | { 9 | 10 | } 11 | 12 | public UploadFailureException(Exception e) : base("Upload failed", e) 13 | { 14 | 15 | } 16 | 17 | public UploadFailureException(string message) : base(message) 18 | { 19 | 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Uploadcare/IPaginatedQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Uploadcare 4 | { 5 | internal interface IPaginatedQueryBuilder 6 | { 7 | /// 8 | /// Returns a resource iterable for lazy loading. 9 | /// 10 | IEnumerable AsEnumerable(); 11 | 12 | /// 13 | /// Iterates through all resources and returns a complete list. 14 | /// 15 | List AsList(); 16 | } 17 | } -------------------------------------------------------------------------------- /Uploadcare.Tests/detectedfaces.json: -------------------------------------------------------------------------------- 1 | { 2 | "id":"e7610aef-aa44-4959-838f-ebcb91339544", 3 | "color_mode":"RGB", 4 | "orientation":null, 5 | "format":"PNG", 6 | "sequence":false, 7 | "height":512, 8 | "width":512, 9 | "geo_location":null, 10 | "datetime_original":null, 11 | "dpi":null, 12 | "faces":[ 13 | [ 14 | 208, 15 | 183, 16 | 146, 17 | 207 18 | ], 19 | [ 20 | 463, 21 | 325, 22 | 654, 23 | 256 24 | ] 25 | ], 26 | "test": "" 27 | } -------------------------------------------------------------------------------- /Uploadcare/Clients/IFaceDetectionClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Uploadcare.Models; 4 | 5 | namespace Uploadcare.Clients 6 | { 7 | public interface IFaceDetectionClient 8 | { 9 | /// 10 | /// Detect faces in image. 11 | /// 12 | /// File UUID 13 | /// List of detected faces coordinates 14 | Task> DetectFaces(string fileId); 15 | } 16 | } -------------------------------------------------------------------------------- /Uploadcare/Utils/UrlParameters/LimitParameter.cs: -------------------------------------------------------------------------------- 1 | namespace Uploadcare.Utils.UrlParameters 2 | { 3 | internal class LimitParameter : IUrlParameter 4 | { 5 | private readonly int _limit; 6 | 7 | public LimitParameter(int limit) 8 | { 9 | _limit = limit; 10 | } 11 | 12 | public string GetParam() 13 | { 14 | return "limit"; 15 | } 16 | 17 | public string GetValue() 18 | { 19 | return _limit.ToString(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Uploadcare/UploadcareConnection.cs: -------------------------------------------------------------------------------- 1 | namespace Uploadcare 2 | { 3 | public class UploadcareConnection : IUploadcareConnection 4 | { 5 | public UploadcareConnection(string publicKey, string privateKey, UploadcareAuthType authType) 6 | { 7 | PublicKey = publicKey; 8 | PrivateKey = privateKey; 9 | AuthType = authType; 10 | } 11 | 12 | public UploadcareAuthType AuthType { get; } 13 | public string PublicKey { get; } 14 | public string PrivateKey { get; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Uploadcare/DTO/MassOperationsData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | using Uploadcare.Models; 4 | 5 | namespace Uploadcare.DTO 6 | { 7 | internal class MassOperationsData 8 | { 9 | [JsonPropertyName("status")] 10 | public string Status { get; set; } 11 | 12 | [JsonPropertyName("problems")] 13 | public Dictionary Problems { get; set; } 14 | 15 | [JsonPropertyName("result")] 16 | public List Files { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Uploadcare/Utils/UrlParameters/FilesStoredParameter.cs: -------------------------------------------------------------------------------- 1 | namespace Uploadcare.Utils.UrlParameters 2 | { 3 | internal class FilesStoredParameter : IUrlParameter 4 | { 5 | private readonly bool _stored; 6 | 7 | public FilesStoredParameter(bool stored) 8 | { 9 | _stored = stored; 10 | } 11 | 12 | public string GetParam() 13 | { 14 | return "stored"; 15 | } 16 | 17 | public string GetValue() 18 | { 19 | return _stored ? "true" : "false"; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Uploadcare/Utils/UrlParameters/FilesRemovedParameter.cs: -------------------------------------------------------------------------------- 1 | namespace Uploadcare.Utils.UrlParameters 2 | { 3 | internal class FilesRemovedParameter: IUrlParameter 4 | { 5 | private readonly bool _removed; 6 | 7 | public FilesRemovedParameter(bool removed) 8 | { 9 | _removed = removed; 10 | } 11 | 12 | public string GetParam() 13 | { 14 | return "removed"; 15 | } 16 | 17 | public string GetValue() 18 | { 19 | return _removed ? "true" : "false"; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Uploadcare.Tests/Clients/ProjectsClientTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace Uploadcare.Tests.Clients 5 | { 6 | public class ProjectsClientTest 7 | { 8 | [Fact] 9 | public async Task client_project_assert() 10 | { 11 | var client = UploadcareClient.DemoClientWithSignedAuth(); 12 | var result = await client.Projects.GetAsync(); 13 | 14 | Assert.NotNull(result); 15 | Assert.NotNull(result.Name); 16 | Assert.NotEmpty(result.Name); 17 | Assert.NotNull(result.Owner); 18 | Assert.NotEmpty(result.Collaborators); 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /Uploadcare.Tests/Uploaders/UrlUploaderTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Uploadcare.Upload; 3 | using Xunit; 4 | 5 | namespace Uploadcare.Tests.Uploaders 6 | { 7 | public class UrlUploaderTest 8 | { 9 | private const string URL = "https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png"; 10 | 11 | [Fact] 12 | public async Task urluploader_upload_assert() 13 | { 14 | var client = UploadcareClient.DemoClient(); 15 | 16 | var uploader = new UrlUploader(client); 17 | var result = await uploader.Upload(URL); 18 | 19 | Assert.NotNull(result.Uuid); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Uploadcare/Clients/ProjectsClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Uploadcare.Models; 3 | using Uploadcare.Utils; 4 | 5 | namespace Uploadcare.Clients 6 | { 7 | internal class ProjectsClient : IProjectsClient 8 | { 9 | private readonly RequestHelper _requestHelper; 10 | 11 | public ProjectsClient(RequestHelper requestHelper) 12 | { 13 | _requestHelper = requestHelper; 14 | } 15 | 16 | public async Task GetAsync() 17 | { 18 | var url = Urls.ApiProject; 19 | 20 | var result = await _requestHelper.Get(url, default); 21 | 22 | return result; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Uploadcare/DTO/CopyFileData.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Uploadcare.DTO 4 | { 5 | internal class BaseCopyFileData 6 | { 7 | [JsonPropertyName("type")] 8 | public string Type { get; set; } 9 | } 10 | 11 | internal class CopyFileData : BaseCopyFileData 12 | { 13 | [JsonPropertyName("result")] 14 | public FileDataBase File { get; set; } 15 | } 16 | 17 | internal class CopyUrlData : BaseCopyFileData 18 | { 19 | [JsonPropertyName("result")] 20 | public string Url { get; set; } 21 | } 22 | 23 | internal class FileDataBase 24 | { 25 | [JsonPropertyName("uuid")] 26 | public string Uuid { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Uploadcare.Tests/Helpers/HashHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | 5 | namespace Uploadcare.Tests.Helpers 6 | { 7 | internal static class HashHelper 8 | { 9 | public static string GetFileHash(string filename) 10 | { 11 | var hash = new SHA1Managed(); 12 | var clearBytes = File.ReadAllBytes(filename); 13 | var hashedBytes = hash.ComputeHash(clearBytes); 14 | return ConvertBytesToHex(hashedBytes); 15 | } 16 | 17 | private static string ConvertBytesToHex(byte[] bytes) 18 | { 19 | var sb = new StringBuilder(); 20 | 21 | foreach (var t in bytes) 22 | { 23 | sb.Append(t.ToString("x")); 24 | } 25 | 26 | return sb.ToString(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Uploadcare/Models/UploadcareWebhook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Uploadcare.Models 5 | { 6 | public class UploadcareWebhook 7 | { 8 | [JsonPropertyName("id")] 9 | public int Id { get; set; } 10 | 11 | [JsonPropertyName("created")] 12 | public DateTime Created { get; set; } 13 | 14 | [JsonPropertyName("updated")] 15 | public DateTime? Updated { get; set; } 16 | 17 | [JsonPropertyName("event")] 18 | public string EventType { get; set; } 19 | 20 | [JsonPropertyName("target_url")] 21 | public string TargetUrl { get; set; } 22 | 23 | [JsonPropertyName("project")] 24 | public int Project { get; set; } 25 | 26 | [JsonPropertyName("is_active")] 27 | public bool IsActive { get; set; } 28 | } 29 | } -------------------------------------------------------------------------------- /Uploadcare/Models/UploadcareProject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace Uploadcare.Models 6 | { 7 | public class UploadcareProject 8 | { 9 | [JsonPropertyName("name")] 10 | public string Name { get; set; } 11 | 12 | [JsonPropertyName("pub_key")] 13 | public string PubKey { get; set; } 14 | 15 | [JsonPropertyName("collaborators")] 16 | public IList Collaborators { get; set; } 17 | 18 | 19 | public UploadcareCollaborator Owner => Collaborators?.FirstOrDefault(); 20 | } 21 | 22 | public class UploadcareCollaborator 23 | { 24 | [JsonPropertyName("name")] 25 | public string Name { get; set; } 26 | 27 | [JsonPropertyName("email")] 28 | public string Email { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /Uploadcare/DTO/BasePageData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | using Uploadcare.Utils; 4 | 5 | namespace Uploadcare.DTO 6 | { 7 | internal class BasePageData : IPageData 8 | { 9 | [JsonPropertyName("per_page")] 10 | public int PerPage { get; set; } 11 | 12 | [JsonPropertyName("total")] 13 | public int Total { get; set; } 14 | 15 | [JsonPropertyName("previous")] 16 | public string Previous { get; set; } 17 | 18 | [JsonPropertyName("next")] 19 | public string Next { get; set; } 20 | 21 | [JsonPropertyName("results")] 22 | public List Results { get; set; } 23 | 24 | public List GetResults() 25 | { 26 | return Results; 27 | } 28 | 29 | public bool HasMore() 30 | { 31 | return Next != null; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Uploadcare/Models/UploadcareGroup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace Uploadcare.Models 6 | { 7 | public class UploadcareGroup 8 | { 9 | [JsonPropertyName("id")] 10 | public string Id { get; set; } 11 | 12 | [JsonPropertyName("datetime_created")] 13 | public DateTime DatetimeCreated { get; set; } 14 | 15 | [JsonPropertyName("datetime_stored")] 16 | public DateTime? DatetimeStored { get; set; } 17 | 18 | [JsonPropertyName("files_count")] 19 | public long FilesCount { get; set; } 20 | 21 | [JsonPropertyName("cdn_url")] 22 | public string CdnUrl { get; set; } 23 | 24 | [JsonPropertyName("files")] 25 | public List Files { get; set; } 26 | 27 | [JsonPropertyName("url")] 28 | public string Url { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /.github/workflows/build-and-test: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1.301 20 | - name: Install dependencies 21 | run: dotnet restore ./ 22 | - name: Install tools 23 | run: dotnet tool restore 24 | - name: Build 25 | run: dotnet build ./ --configuration Release --no-restore 26 | - name: Test 27 | run: dotnet test ./ --no-restore --verbosity normal /p:AltCover=true /p:AltCoverAssemblyExcludeFilter=?Strings /p:AltCoverAttributeFilter=TestSDKAutoGeneratedCode 28 | - name: Generate HTML report 29 | run: dotnet reportgenerator "-reports:./Uploadcare.Tests/coverage.xml" -reporttypes:Html "-targetdir:./report" 30 | -------------------------------------------------------------------------------- /Uploadcare/Uploadcare.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Anton Kheistver and contributors 6 | C# library for accessing Uploadcare API 7 | UploadcareCSharp 8 | https://github.com/okolobaxa/uploadcare-dotnet 9 | MIT 10 | icon.png 11 | uploadcare;upload;file 12 | 1.1.3.1 13 | 1.1.3.1 14 | Anton Kheistver and contributors 15 | 1.1.3.0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Uploadcare/Utils/AuthHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Uploadcare.Utils 4 | { 5 | internal static class AuthHeaderHelper 6 | { 7 | public static string GetSimple(string publicKey, string privateKey) 8 | { 9 | return $"Uploadcare.Simple {publicKey}:{privateKey}"; 10 | } 11 | 12 | public static string GetSigned(string publicKey, string signature) 13 | { 14 | return $"Uploadcare {publicKey}:{signature}"; 15 | } 16 | 17 | public static string CombineDataForSignature(string httpMethod, string requestBodyHash, string contentTypeHeader, string dateHeader, string uri) 18 | { 19 | var sb = new StringBuilder(); 20 | 21 | sb.Append(httpMethod).Append('\n'); 22 | sb.Append(requestBodyHash).Append('\n'); 23 | sb.Append(contentTypeHeader).Append('\n'); 24 | sb.Append(dateHeader).Append('\n'); 25 | sb.Append(uri); 26 | 27 | return sb.ToString(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Uploadcare.Tests/Utils/UploadcareFaceConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.Json; 3 | using Uploadcare.Models; 4 | using Xunit; 5 | 6 | namespace Uploadcare.Tests.Utils 7 | { 8 | public class UploadcareFaceConverterTests 9 | { 10 | [Fact] 11 | public void Deserialize() 12 | { 13 | var json = File.ReadAllText("detectedfaces.json"); 14 | 15 | var data = JsonSerializer.Deserialize(json); 16 | 17 | Assert.NotNull(data); 18 | Assert.NotNull(data.Faces); 19 | Assert.Equal(2, data.Faces.Count); 20 | 21 | foreach (var face in data.Faces) 22 | { 23 | Assert.NotNull(face.FaceCoordinates); 24 | Assert.Equal(4, face.FaceCoordinates.Length); 25 | Assert.NotEqual(0, face.Height); 26 | Assert.NotEqual(0, face.Width); 27 | Assert.NotEqual(0, face.Top); 28 | Assert.NotEqual(0, face.Left); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Uploadcare/Utils/UrlParameters/OrderingFromParameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Uploadcare.Utils.UrlParameters 4 | { 5 | internal class OrderingFromSizeParameter : IUrlParameter 6 | { 7 | private readonly long _size; 8 | 9 | public OrderingFromSizeParameter(long size) 10 | { 11 | _size = size; 12 | } 13 | 14 | public string GetParam() 15 | { 16 | return "from"; 17 | } 18 | 19 | public string GetValue() 20 | { 21 | return _size.ToString(); 22 | } 23 | } 24 | 25 | internal class OrderingFromDateParameter : IUrlParameter 26 | { 27 | private readonly DateTime _timestamp; 28 | 29 | public OrderingFromDateParameter(DateTime timestamp) 30 | { 31 | _timestamp = timestamp; 32 | } 33 | 34 | public string GetParam() 35 | { 36 | return "from"; 37 | } 38 | 39 | public string GetValue() 40 | { 41 | return _timestamp.ToString("o"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Uploadcare.Tests/Uploaders/FileUploaderTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Uploadcare.Upload; 4 | using Xunit; 5 | 6 | namespace Uploadcare.Tests.Uploaders 7 | { 8 | public class FileUploaderTest 9 | { 10 | [Fact] 11 | public async Task fileuploader_upload_assert() 12 | { 13 | var client = UploadcareClient.DemoClient(); 14 | var file = new FileInfo("1.jpg"); 15 | 16 | var uploader = new FileUploader(client); 17 | var result = await uploader.Upload(file); 18 | 19 | Assert.NotNull(result.Uuid); 20 | } 21 | 22 | [Fact] 23 | public async Task fileuploader_upload_bytes() 24 | { 25 | var client = UploadcareClient.DemoClient(); 26 | var file = new FileInfo("Lenna.png"); 27 | var bytes = File.ReadAllBytes(file.FullName); 28 | 29 | var uploader = new FileUploader(client); 30 | var result = await uploader.Upload(bytes, file.Name); 31 | 32 | Assert.NotNull(result.Uuid); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Anton Kheystver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Uploadcare/Clients/FaceDetectionClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Uploadcare.Models; 5 | using Uploadcare.Utils; 6 | 7 | namespace Uploadcare.Clients 8 | { 9 | internal class FaceDetectionClient : IFaceDetectionClient 10 | { 11 | private readonly RequestHelper _requestHelper; 12 | 13 | public FaceDetectionClient() 14 | { 15 | var connection = new UploadcareConnection(null, null, UploadcareAuthType.NoAuth); 16 | 17 | _requestHelper = new RequestHelper(connection); 18 | } 19 | 20 | public async Task> DetectFaces(string fileId) 21 | { 22 | if (string.IsNullOrEmpty(fileId)) 23 | { 24 | throw new ArgumentNullException(nameof(fileId)); 25 | } 26 | 27 | var url = Urls.CdnFileDetectFace(fileId); 28 | 29 | var result = await _requestHelper.Get(url, default); 30 | 31 | return result.Faces != null ? result.Faces.ToArray() : Array.Empty(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Uploadcare.Tests/Uploaders/SignedFileUploaderTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Uploadcare.Upload; 5 | using Xunit; 6 | 7 | namespace Uploadcare.Tests.Uploaders 8 | { 9 | public class SignedFileUploaderTest 10 | { 11 | [Fact] 12 | public async Task signedfileuploader_upload_assert() 13 | { 14 | var client = UploadcareClient.DemoClient(); 15 | var file = new FileInfo("Lenna.png"); 16 | 17 | var uploader = new SignedFileUploader(client, new TimeSpan(0, 0, 30)); 18 | var result = await uploader.Upload(file); 19 | 20 | Assert.NotNull(result.Uuid); 21 | } 22 | 23 | [Fact] 24 | public async Task signedfileuploader_upload_bytes() 25 | { 26 | var client = UploadcareClient.DemoClient(); 27 | var file = new FileInfo("Lenna.png"); 28 | var bytes = File.ReadAllBytes(file.FullName); 29 | 30 | var uploader = new SignedFileUploader(client, new TimeSpan(0, 0, 30)); 31 | var result = await uploader.Upload(bytes, file.Name); 32 | 33 | Assert.NotNull(result.Uuid); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Uploadcare/Clients/IUploadcareClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Uploadcare.Clients 4 | { 5 | /// 6 | /// Uploadcare API client. 7 | /// 8 | /// Provides simple access to UploadcareFile and Project resources. 9 | /// 10 | public interface IUploadcareClient : IDisposable 11 | { 12 | IProjectsClient Projects { get; } 13 | 14 | IFilesClient Files { get; } 15 | 16 | IGroupsClient Groups { get; } 17 | 18 | IWebhooksClient Webhooks { get; } 19 | 20 | IFaceDetectionClient FaceDetection { get; } 21 | 22 | /// 23 | /// Returns the public key. 24 | /// 25 | /// Public key 26 | string PublicKey { get; } 27 | 28 | /// 29 | /// Returns the private key. 30 | /// 31 | /// Private key 32 | string PrivateKey { get; } 33 | 34 | /// 35 | /// Returns true, if simple authentication is used. 36 | /// 37 | /// true, if simple authentication is used, false otherwise 38 | UploadcareAuthType AuthType { get; } 39 | 40 | IUploadcareConnection Connection { get; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Uploadcare/Clients/IGroupsClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Uploadcare.Models; 4 | 5 | namespace Uploadcare.Clients 6 | { 7 | public interface IGroupsClient 8 | { 9 | /// 10 | /// Get a file group by UUID. 11 | /// 12 | /// Group UUID 13 | /// Group resource 14 | Task GetAsync(string groupId); 15 | 16 | /// 17 | /// Mark all files in a group as stored. 18 | /// 19 | /// Group UUID 20 | /// Group resource 21 | Task StoreAsync(string groupId); 22 | 23 | /// 24 | /// Create group from a set of files by using their UUIDs. 25 | /// 26 | /// That parameter defines a set of files you want to join in a group. 27 | /// Each parameter can be a file UUID or a CDN URL, with or without applied Image Transformations operations. 28 | /// 29 | /// Group resource 30 | Task CreateAsync(IReadOnlyCollection fileIds); 31 | 32 | GroupsQueryBuilder GetGroups(); 33 | } 34 | } -------------------------------------------------------------------------------- /Uploadcare/Upload/SignedFileUploader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Net.Http; 4 | using Uploadcare.Clients; 5 | using Uploadcare.Utils; 6 | 7 | namespace Uploadcare.Upload 8 | { 9 | /// 10 | /// Uploadcare signed uploader for files and binary data. 11 | /// 12 | public sealed class SignedFileUploader : FileUploader 13 | { 14 | private readonly TimeSpan _expireTime; 15 | 16 | /// 17 | /// Creates a new uploader from binary data. 18 | /// 19 | /// Uploadcare client 20 | /// Expiration time for signature 21 | public SignedFileUploader(IUploadcareClient client, TimeSpan expireTime) : base(client) 22 | { 23 | _expireTime = expireTime; 24 | } 25 | 26 | protected override void AddAdditionalContent(MultipartFormDataContent content) 27 | { 28 | var expire = (DateTimeOffset.UtcNow.ToUnixTimeSeconds() + _expireTime.TotalSeconds).ToString(CultureInfo.InvariantCulture); 29 | var signature = CryptoHelper.StringToMD5(Client.PrivateKey + expire); 30 | 31 | content.Add(new StringContent(expire), "expire"); 32 | content.Add(new StringContent(signature), "signature"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Uploadcare.Tests/Uploadcare.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Always 24 | 25 | 26 | Always 27 | 28 | 29 | Always 30 | 31 | 32 | Always 33 | 34 | 35 | Always 36 | 37 | 38 | 39 | 40 | Always 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Uploadcare/Utils/CryptoHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | 5 | namespace Uploadcare.Utils 6 | { 7 | internal static class CryptoHelper 8 | { 9 | public static string StringToMD5(string s) 10 | { 11 | using (var md5 = MD5.Create()) 12 | { 13 | var bytes = Encoding.UTF8.GetBytes(s); 14 | var hashBytes = md5.ComputeHash(bytes); 15 | 16 | return HexStringFromBytes(hashBytes); 17 | } 18 | } 19 | 20 | public static string BytesToMD5(byte[] bytes) 21 | { 22 | using (var md5 = MD5.Create()) 23 | { 24 | var hashBytes = md5.ComputeHash(bytes); 25 | 26 | return HexStringFromBytes(hashBytes); 27 | } 28 | } 29 | 30 | public static string Sign(string stringToBeSigned, string privateKey) 31 | { 32 | var keyBytes = Encoding.UTF8.GetBytes(privateKey); 33 | 34 | using (var hmac = new HMACSHA1(keyBytes)) 35 | { 36 | hmac.Initialize(); 37 | 38 | var dataBytes = Encoding.UTF8.GetBytes(stringToBeSigned); 39 | 40 | var hashBytes = hmac.ComputeHash(dataBytes); 41 | 42 | return HexStringFromBytes(hashBytes); 43 | } 44 | } 45 | 46 | private static string HexStringFromBytes(byte[] bytes) 47 | { 48 | return BitConverter.ToString(bytes).Replace("-", "").ToLower(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Uploadcare.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29318.209 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Uploadcare", "Uploadcare\Uploadcare.csproj", "{58F024C2-EB19-4FBE-A4DC-96A990DEAC3B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Uploadcare.Tests", "Uploadcare.Tests\Uploadcare.Tests.csproj", "{CF0C571C-F8DC-4623-A926-4E8178D4EA77}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {58F024C2-EB19-4FBE-A4DC-96A990DEAC3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {58F024C2-EB19-4FBE-A4DC-96A990DEAC3B}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {58F024C2-EB19-4FBE-A4DC-96A990DEAC3B}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {58F024C2-EB19-4FBE-A4DC-96A990DEAC3B}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {CF0C571C-F8DC-4623-A926-4E8178D4EA77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {CF0C571C-F8DC-4623-A926-4E8178D4EA77}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {CF0C571C-F8DC-4623-A926-4E8178D4EA77}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {CF0C571C-F8DC-4623-A926-4E8178D4EA77}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {7301E0F1-146D-491C-9594-D501E181D2FB} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Uploadcare/Utils/UrlParameters/OrderingParameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Uploadcare.Utils.UrlParameters 4 | { 5 | internal abstract class BaseOrderingParameter : IUrlParameter 6 | { 7 | public string GetParam() 8 | { 9 | return "ordering"; 10 | } 11 | 12 | public abstract string GetValue(); 13 | } 14 | 15 | internal class FileOrderingParameter : BaseOrderingParameter 16 | { 17 | private readonly EFileOrderBy _orderBy; 18 | 19 | public FileOrderingParameter(EFileOrderBy orderBy) 20 | { 21 | _orderBy = orderBy; 22 | } 23 | 24 | 25 | public override string GetValue() 26 | { 27 | switch (_orderBy) 28 | { 29 | case EFileOrderBy.DatetimeUploaded: return "datetime_uploaded"; 30 | case EFileOrderBy.DatetimeUploadedDesc: return "-datetime_uploaded"; 31 | case EFileOrderBy.Size: return "size"; 32 | case EFileOrderBy.SizeDesc: return "-size"; 33 | default: throw new ArgumentOutOfRangeException(); 34 | } 35 | } 36 | } 37 | 38 | internal class GroupOrderingParameter : BaseOrderingParameter 39 | { 40 | private readonly EGroupOrderBy _orderBy; 41 | 42 | public GroupOrderingParameter(EGroupOrderBy orderBy) 43 | { 44 | _orderBy = orderBy; 45 | } 46 | 47 | public override string GetValue() 48 | { 49 | switch (_orderBy) 50 | { 51 | case EGroupOrderBy.DatetimeCreated: return "datetime_created"; 52 | case EGroupOrderBy.DatetimeCreatedDesc: return "-datetime_created"; 53 | default: throw new ArgumentOutOfRangeException(); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Uploadcare/Exceptions/UploadcareApiExceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Uploadcare.Exceptions 4 | { 5 | /// 6 | /// A generic error of the uploadcare API. 7 | /// 8 | public class UploadcareApiException : Exception 9 | { 10 | public UploadcareApiException(string message) : base(message) { } 11 | 12 | protected UploadcareApiException(string message, Exception cause) : base(message, cause) { } 13 | } 14 | 15 | /// 16 | /// An exception thrown in case of authentication error returned by the uploadcare API 17 | /// 18 | public class UploadcareAuthenticationException : UploadcareApiException 19 | { 20 | public UploadcareAuthenticationException(string message) : base(message) { } 21 | } 22 | 23 | /// 24 | /// An exception thrown in case the http request sent to the Uploadcare API was invalid. 25 | /// 26 | public class UploadcareInvalidRequestException : UploadcareApiException 27 | { 28 | public UploadcareInvalidRequestException(string message) : base(message) { } 29 | } 30 | 31 | /// 32 | /// An exception thrown in case the http response received from Uploadcare API was invalid. 33 | /// 34 | public class UploadcareInvalidResponseException : UploadcareApiException 35 | { 36 | public UploadcareInvalidResponseException(string message) : base(message) { } 37 | } 38 | 39 | /// 40 | /// An exception thrown in cases of network failure. 41 | /// 42 | public class UploadcareNetworkException : UploadcareApiException 43 | { 44 | public UploadcareNetworkException(Exception cause) : base("Network failure", cause) { } 45 | 46 | public UploadcareNetworkException(string message) : base(message) { } 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /Uploadcare/Clients/IWebhooksClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Uploadcare.Models; 4 | 5 | namespace Uploadcare.Clients 6 | { 7 | public interface IWebhooksClient 8 | { 9 | /// 10 | /// Get a list of active subscriptions. 11 | /// 12 | /// Webhook resources 13 | Task> GetAsync(); 14 | 15 | /// 16 | /// Create a new subscription. 17 | /// 18 | /// A URL that is triggered by an event, for example, a file upload 19 | /// An event you subscribe to. 20 | /// Marks a subscription as either active or not, defaults to true. 21 | /// Webhook resource 22 | Task SubscribeAsync(string eventType, string targetUrl, bool isActive = true); 23 | 24 | /// 25 | /// Update subscription parameters. 26 | /// 27 | /// Subscription Id 28 | /// A URL that is triggered by an event, for example, a file upload 29 | /// An event you subscribe to. 30 | /// Marks a subscription as either active or not, defaults to true. 31 | Task UpdateAsync(long subscriptionId, string eventType = null, string targetUrl = null, bool? isActive = null); 32 | 33 | /// 34 | /// Delete a subscription. 35 | /// 36 | /// A URL that is triggered by an event, for example, a file upload 37 | /// An event you unsubscribe from. 38 | Task UnsubscribeAsync(string eventType, string targetUrl); 39 | } 40 | } -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | stages: 5 | 6 | - stage: 'Build' 7 | variables: 8 | buildConfiguration: 'Release' 9 | 10 | jobs: 11 | - job: 12 | pool: 13 | vmImage: 'ubuntu-latest' 14 | 15 | workspace: 16 | clean: all 17 | 18 | steps: 19 | - task: DotNetCoreCLI@2 20 | displayName: "NuGet Restore" 21 | inputs: 22 | command: restore 23 | projects: '**/*.csproj' 24 | 25 | - task: DotNetCoreCLI@2 26 | displayName: "Build Solution" 27 | inputs: 28 | command: build 29 | projects: '**/*.csproj' 30 | arguments: '--configuration $(buildConfiguration)' 31 | 32 | - task: DotNetCoreCLI@2 33 | displayName: "Run Tests" 34 | inputs: 35 | command: 'test' 36 | projects: 'Uploadcare.Tests/Uploadcare.Tests.csproj' 37 | 38 | - task: DotNetCoreCLI@2 39 | displayName: 'Create NuGet Package' 40 | inputs: 41 | command: pack 42 | packDirectory: '$(Build.ArtifactStagingDirectory)/package' 43 | arguments: '--configuration $(buildConfiguration)' 44 | nobuild: true 45 | 46 | - publish: '$(Build.ArtifactStagingDirectory)/package' 47 | artifact: 'package' 48 | 49 | - task: PublishTestResults@2 50 | inputs: 51 | testResultsFormat: 'XUnit' 52 | testResultsFiles: '**/*.trx' 53 | searchFolder: '$(Agent.TempDirectory)' 54 | 55 | - stage: 'PublishReleaseNuGetPackage' 56 | displayName: 'Publish NuGet Package' 57 | dependsOn: 'Build' 58 | condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 59 | 60 | jobs: 61 | - deployment: 62 | pool: 63 | vmImage: 'ubuntu-latest' 64 | environment: 'nugetorg' 65 | strategy: 66 | runOnce: 67 | deploy: 68 | steps: 69 | 70 | - task: NuGetToolInstaller@1 71 | inputs: 72 | versionSpec: '5.1' 73 | 74 | - task: NuGetCommand@2 75 | inputs: 76 | command: 'custom' 77 | arguments: 'push $(Pipeline.Workspace)/package/*.nupkg -NonInteractive -Source https://api.nuget.org/v3/index.json -ApiKey $(nuget-uploadcare) -SkipDuplicate -NoSymbols' 78 | -------------------------------------------------------------------------------- /Uploadcare.Tests/Clients/WebhooksTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace Uploadcare.Tests.Clients 5 | { 6 | public class WebhooksTests 7 | { 8 | [Fact(Skip = "You need to have a special account to use webhooks")] 9 | public async Task client_getwebhooks_assert() 10 | { 11 | var client = UploadcareClient.DemoClientWithSignedAuth(); 12 | 13 | var webhook = await client.Webhooks.GetAsync(); 14 | 15 | Assert.NotNull(webhook); 16 | } 17 | 18 | [Fact(Skip = "You need to have a special account to use webhooks")] 19 | public async Task client_webhook_subscribe_assert() 20 | { 21 | var client = UploadcareClient.DemoClientWithSignedAuth(); 22 | 23 | var webhook = await client.Webhooks.SubscribeAsync("file.uploaded", "https://google.com", true); 24 | 25 | Assert.NotNull(webhook); 26 | 27 | //clean-up 28 | await client.Webhooks.UnsubscribeAsync("file.uploaded", "https://google.com"); 29 | } 30 | 31 | [Fact(Skip = "You need to have a special account to use webhooks")] 32 | public async Task client_webhook_unsubscribe_assert() 33 | { 34 | var client = UploadcareClient.DemoClientWithSignedAuth(); 35 | 36 | var webhook = await client.Webhooks.SubscribeAsync("file.uploaded", "https://google.com", true); 37 | 38 | await client.Webhooks.UnsubscribeAsync("file.uploaded", "https://google.com"); 39 | 40 | Assert.NotNull(webhook); 41 | 42 | //clean-up 43 | await client.Webhooks.UnsubscribeAsync("file.uploaded", "https://google.com"); 44 | } 45 | 46 | [Fact(Skip = "You need to have a special account to use webhooks")] 47 | public async Task client_webhook_update_assert() 48 | { 49 | var client = UploadcareClient.DemoClientWithSignedAuth(); 50 | 51 | var webhook = await client.Webhooks.SubscribeAsync("file.uploaded", "https://google.com", true); 52 | var updated = await client.Webhooks.UpdateAsync(webhook.Id, "file.uploaded", "https://apple.com", false); 53 | 54 | Assert.NotNull(webhook); 55 | Assert.Equal("https://apple.com", updated.TargetUrl); 56 | 57 | //clean-up 58 | await client.Webhooks.UnsubscribeAsync("file.uploaded", "https://apple.com"); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Uploadcare/GroupsQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Uploadcare.DTO; 5 | using Uploadcare.Models; 6 | using Uploadcare.Utils; 7 | using Uploadcare.Utils.UrlParameters; 8 | 9 | namespace Uploadcare 10 | { 11 | public class GroupsQueryBuilder : IPaginatedQueryBuilder 12 | { 13 | private readonly RequestHelper _requestHelper; 14 | private readonly List _parameters = new List(5); 15 | 16 | 17 | /// 18 | /// Initializes a new builder. 19 | /// 20 | internal GroupsQueryBuilder(RequestHelper requestHelper) 21 | { 22 | _requestHelper = requestHelper; 23 | } 24 | 25 | /// 26 | /// Set a preferred amount of files in a list for a single response 27 | /// 28 | /// A preferred amount of files in a list for a single response. Defaults to 100, while the maximum is 1000. 29 | public GroupsQueryBuilder Limit(int limit) 30 | { 31 | if (limit > 1000) 32 | { 33 | limit = 1000; 34 | } 35 | 36 | _parameters.Add(new LimitParameter(limit)); 37 | 38 | return this; 39 | } 40 | 41 | public GroupsQueryBuilder OrderByCreateDate(DateTime? from = null) 42 | { 43 | _parameters.Add(new GroupOrderingParameter(EGroupOrderBy.DatetimeCreated)); 44 | 45 | if (from.HasValue) 46 | { 47 | _parameters.Add(new OrderingFromDateParameter(from.Value)); 48 | } 49 | 50 | return this; 51 | } 52 | 53 | public GroupsQueryBuilder OrderByCreateDateDesc(DateTime? from = null) 54 | { 55 | _parameters.Add(new GroupOrderingParameter(EGroupOrderBy.DatetimeCreatedDesc)); 56 | 57 | if (from.HasValue) 58 | { 59 | _parameters.Add(new OrderingFromDateParameter(from.Value)); 60 | } 61 | 62 | return this; 63 | } 64 | 65 | public IEnumerable AsEnumerable() 66 | { 67 | var url = Urls.ApiGroups; 68 | 69 | var result = _requestHelper.ExecutePaginatedQuery(url, _parameters, new GroupPageData()); 70 | 71 | return result; 72 | } 73 | 74 | public List AsList() 75 | { 76 | return AsEnumerable().ToList(); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /Uploadcare/Clients/GroupsClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Uploadcare.Models; 6 | using Uploadcare.Utils; 7 | 8 | namespace Uploadcare.Clients 9 | { 10 | internal class GroupsClient : IGroupsClient 11 | { 12 | private readonly RequestHelper _requestHelper; 13 | 14 | public GroupsClient(RequestHelper requestHelper) 15 | { 16 | _requestHelper = requestHelper; 17 | } 18 | 19 | public async Task GetAsync(string groupId) 20 | { 21 | if (string.IsNullOrEmpty(groupId)) 22 | { 23 | throw new ArgumentNullException(nameof(groupId)); 24 | } 25 | 26 | var url = Urls.ApiGroup(groupId); 27 | 28 | var result = await _requestHelper.Get(url, default); 29 | 30 | return result; 31 | } 32 | 33 | public async Task StoreAsync(string groupId) 34 | { 35 | if (string.IsNullOrEmpty(groupId)) 36 | { 37 | throw new ArgumentNullException(nameof(groupId)); 38 | } 39 | 40 | var url = Urls.ApiGroupsStorage(groupId); 41 | 42 | var result = await _requestHelper.Post(url); 43 | 44 | return result; 45 | } 46 | 47 | public async Task CreateAsync(IReadOnlyCollection fileIds) 48 | { 49 | if (fileIds == null) 50 | { 51 | throw new ArgumentNullException(nameof(fileIds)); 52 | } 53 | 54 | if (!fileIds.Any()) 55 | { 56 | throw new ArgumentException(nameof(fileIds)); 57 | } 58 | 59 | var url = Urls.ApiCreateGroup; 60 | 61 | var publicKey = _requestHelper.GetConnection().PublicKey; 62 | 63 | var formData = new Dictionary(fileIds.Count + 1) 64 | { 65 | {"pub_key", publicKey} 66 | }; 67 | 68 | int i = 0; 69 | foreach (var fileId in fileIds) 70 | { 71 | formData.Add($"files[{i}]", fileId); 72 | i++; 73 | } 74 | 75 | var result = await _requestHelper.PostFormData(url, formData); 76 | 77 | return result; 78 | } 79 | 80 | public GroupsQueryBuilder GetGroups() 81 | { 82 | return new GroupsQueryBuilder(_requestHelper); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Uploadcare/Models/UploadcareFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Uploadcare.Models 5 | { 6 | public class UploadcareFile 7 | { 8 | [JsonPropertyName("uuid")] 9 | public virtual string Uuid { get; set; } 10 | 11 | [JsonPropertyName("datetime_removed")] 12 | public DateTime? DatetimeRemoved { get; set; } 13 | 14 | [JsonPropertyName("datetime_stored")] 15 | public DateTime? DatetimeStored { get; set; } 16 | 17 | [JsonPropertyName("datetime_uploaded")] 18 | public DateTime DatetimeUploaded { get; set; } 19 | 20 | [JsonPropertyName("is_image")] 21 | public bool IsImage { get; set; } 22 | 23 | [JsonPropertyName("is_ready")] 24 | public bool IsReady { get; set; } 25 | 26 | [JsonPropertyName("mime_type")] 27 | public string MimeType { get; set; } 28 | 29 | [JsonPropertyName("original_file_url")] 30 | public string OriginalFileUrl { get; set; } 31 | 32 | [JsonPropertyName("original_filename")] 33 | public string OriginalFilename { get; set; } 34 | 35 | [JsonPropertyName("size")] 36 | public long Size { get; set; } 37 | 38 | [JsonPropertyName("url")] 39 | public string Url { get; set; } 40 | 41 | [JsonPropertyName("image_info")] 42 | public ImageInfo ImageInfoData { get; set; } 43 | 44 | public bool Stored => DatetimeStored.HasValue && DatetimeStored != DateTime.MinValue; 45 | 46 | public bool Removed => DatetimeRemoved.HasValue && DatetimeRemoved != DateTime.MinValue; 47 | 48 | /// 49 | /// Creates a CDN path builder for this file. 50 | /// 51 | /// CDN path builder 52 | public CdnPathBuilder CdnPath() 53 | { 54 | return new CdnPathBuilder(this); 55 | } 56 | } 57 | 58 | public class ImageInfo 59 | { 60 | [JsonPropertyName("height")] 61 | public int Height { get; set; } 62 | 63 | [JsonPropertyName("width")] 64 | public int Width { get; set; } 65 | 66 | [JsonPropertyName("geo_location")] 67 | public GeoLocationInfo GeoLocation { get; set; } 68 | 69 | [JsonPropertyName("datetime_original")] 70 | public DateTime? DatetimeOriginal { get; set; } 71 | 72 | [JsonPropertyName("format")] 73 | public string Format { get; set; } 74 | } 75 | 76 | public class GeoLocationInfo 77 | { 78 | [JsonPropertyName("latitude")] 79 | public decimal Latitude { get; set; } 80 | 81 | [JsonPropertyName("longitude")] 82 | public decimal Longitude { get; set; } 83 | } 84 | } -------------------------------------------------------------------------------- /Uploadcare/Utils/Urls.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Uploadcare.Utils 4 | { 5 | internal static class Urls 6 | { 7 | public const string API_BASE = "https://api.uploadcare.com"; 8 | public const string CDN_BASE = "https://ucarecdn.com"; 9 | public const string UPLOAD_BASE = "https://upload.uploadcare.com"; 10 | 11 | 12 | public static Uri CdnFile(string fileId) => new Uri($"{CDN_BASE}/{fileId}/"); 13 | 14 | public static Uri ApiProject => new Uri($"{API_BASE}/project/"); 15 | 16 | public static Uri ApiFile(string fileId) => new Uri($"{API_BASE}/files/{fileId}/"); 17 | 18 | public static Uri CdnFileDetectFace(string fileId) => new Uri($"{CDN_BASE}/{fileId}/detect_faces/"); 19 | 20 | public static Uri ApiFiles => new Uri($"{API_BASE}/files/"); 21 | 22 | public static Uri ApiFilesStorage => new Uri($"{API_BASE}/files/storage/"); 23 | 24 | public static Uri ApiGroup(string groupId) => new Uri($"{API_BASE}/groups/{groupId}/"); 25 | 26 | public static Uri ApiGroups => new Uri($"{API_BASE}/groups/"); 27 | 28 | public static Uri ApiCreateGroup => new Uri($"{UPLOAD_BASE}/group/"); 29 | 30 | public static Uri ApiFileStorage(string fileId) => new Uri($"{API_BASE}/files/{fileId}/storage/"); 31 | 32 | public static Uri ApiGroupsStorage(string groupId) => new Uri($"{API_BASE}/groups/{groupId}/storage/"); 33 | 34 | public static Uri ApiWebhook(long subscriptionId) => new Uri($"{API_BASE}/webhooks/{subscriptionId}/"); 35 | 36 | public static Uri ApiWebhooks => new Uri($"{API_BASE}/webhooks/"); 37 | 38 | public static Uri ApiWebhooksUnsubscribe => new Uri($"{API_BASE}/webhooks/unsubscribe/"); 39 | 40 | public static Uri UploadBase => new Uri($"{UPLOAD_BASE}/base/"); 41 | 42 | public static Uri UploadFromUrl(string sourceUrl, string pubKey, bool? store = null) 43 | { 44 | var path = $"{UPLOAD_BASE}/from_url/?source_url={ sourceUrl}&pub_key={pubKey}"; 45 | if (store != null) 46 | { 47 | if (store.Value) 48 | { 49 | path += "&store=1"; 50 | } 51 | else 52 | { 53 | path += "&store=0"; 54 | } 55 | } 56 | 57 | var builder = new UriBuilder(new Uri(path)); 58 | 59 | return builder.Uri; 60 | } 61 | 62 | public static Uri UploadFromUrlStatus(string token) 63 | { 64 | var path = $"{UPLOAD_BASE}/from_url/status/?token={token}"; 65 | 66 | var builder = new UriBuilder(new Uri(path)); 67 | 68 | return builder.Uri; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Uploadcare.Tests/Clients/GroupsClientTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Uploadcare.Upload; 6 | using Xunit; 7 | 8 | namespace Uploadcare.Tests.Clients 9 | { 10 | public class GroupsClientTest 11 | { 12 | [Fact] 13 | public async Task client_getgroup_assert() 14 | { 15 | var client = UploadcareClient.DemoClientWithSignedAuth(); 16 | 17 | var group = client.Groups.GetGroups().AsEnumerable().FirstOrDefault(); 18 | 19 | var result = await client.Groups.GetAsync(group.Id); 20 | 21 | Assert.NotNull(result); 22 | } 23 | 24 | [Fact] 25 | public async Task client_storegroup_assert() 26 | { 27 | var client = UploadcareClient.DemoClientWithSignedAuth(); 28 | 29 | var group = client.Groups.GetGroups().AsEnumerable().FirstOrDefault(); 30 | 31 | var result = await client.Groups.StoreAsync(group.Id); 32 | 33 | Assert.NotNull(result); 34 | Assert.NotNull(result.DatetimeStored); 35 | } 36 | 37 | 38 | [Fact] 39 | public void client_getgroups_iterable() 40 | { 41 | var client = UploadcareClient.DemoClientWithSignedAuth(); 42 | var count = 0; 43 | 44 | foreach (var file in client.Files.GetFiles().AsEnumerable()) 45 | { 46 | Assert.NotNull(file); 47 | count++; 48 | if (count == 50) 49 | break; 50 | } 51 | 52 | Assert.True(count == 50); 53 | } 54 | 55 | [Fact] 56 | public void client_getgroups_iterable_orderby_date() 57 | { 58 | var client = UploadcareClient.DemoClientWithSignedAuth(); 59 | var count = 0; 60 | 61 | foreach (var file in client.Groups.GetGroups().OrderByCreateDate(DateTime.UtcNow.AddDays(-1)).AsEnumerable()) 62 | { 63 | Assert.NotNull(file); 64 | count++; 65 | if (count == 150) 66 | break; 67 | } 68 | 69 | Assert.True(count == 150); 70 | } 71 | 72 | [Fact] 73 | public async Task client_creategroup_assert() 74 | { 75 | var client = UploadcareClient.DemoClientWithSignedAuth(); 76 | 77 | var file = new FileInfo("Lenna.png"); 78 | 79 | var uploader = new FileUploader(client); 80 | var result1 = await uploader.Upload(file); 81 | var result2 = await uploader.Upload(file); 82 | 83 | var group = await client.Groups.CreateAsync(new[] { result1.Uuid, result2.Uuid }); 84 | 85 | Assert.True(group.FilesCount == 2); 86 | 87 | Assert.Contains(group.Files, x => x.Uuid == result1.Uuid); 88 | Assert.Contains(group.Files, x => x.Uuid == result2.Uuid); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /Uploadcare/Client.cs: -------------------------------------------------------------------------------- 1 | using Uploadcare.Clients; 2 | using Uploadcare.Utils; 3 | 4 | namespace Uploadcare 5 | { 6 | public sealed class UploadcareClient : IUploadcareClient 7 | { 8 | private readonly RequestHelper _requestHelper; 9 | 10 | /// 11 | /// Initializes a client with custom access keys. 12 | /// Can use simple or secure authentication. 13 | /// 14 | /// API public key 15 | /// API private key 16 | /// Type of auth: simple or signed 17 | public UploadcareClient(string publicKey, string privateKey, UploadcareAuthType authType = UploadcareAuthType.Simple) 18 | { 19 | Connection = new UploadcareConnection(publicKey, privateKey, authType); 20 | 21 | _requestHelper = new RequestHelper(Connection); 22 | 23 | Projects = new ProjectsClient(_requestHelper); 24 | Files = new FilesClient(_requestHelper); 25 | Groups = new GroupsClient(_requestHelper); 26 | Webhooks = new WebhookClient(_requestHelper); 27 | FaceDetection = new FaceDetectionClient(); 28 | } 29 | 30 | public UploadcareClient(IUploadcareConnection connection) 31 | { 32 | Connection = connection; 33 | 34 | _requestHelper = new RequestHelper(Connection); 35 | 36 | Projects = new ProjectsClient(_requestHelper); 37 | Files = new FilesClient(_requestHelper); 38 | Groups = new GroupsClient(_requestHelper); 39 | Webhooks = new WebhookClient(_requestHelper); 40 | FaceDetection = new FaceDetectionClient(); 41 | } 42 | 43 | /// 44 | /// Creates a client with demo credentials. Useful for tests and anonymous access. 45 | /// Warning! Do not use in production. All demo account files are eventually purged. 46 | /// 47 | /// A demo client 48 | public static IUploadcareClient DemoClient() 49 | { 50 | return new UploadcareClient("demopublickey", "demoprivatekey"); 51 | } 52 | 53 | public static IUploadcareClient DemoClientWithSignedAuth() 54 | { 55 | return new UploadcareClient("demopublickey", "demoprivatekey", UploadcareAuthType.Signed); 56 | } 57 | 58 | public IProjectsClient Projects { get; } 59 | 60 | public IFilesClient Files { get; } 61 | 62 | public IGroupsClient Groups { get; } 63 | 64 | public IWebhooksClient Webhooks { get; } 65 | 66 | public IFaceDetectionClient FaceDetection { get; } 67 | 68 | public string PublicKey => Connection.PublicKey; 69 | 70 | public string PrivateKey => Connection.PrivateKey; 71 | 72 | public UploadcareAuthType AuthType => Connection.AuthType; 73 | 74 | public IUploadcareConnection Connection { get; } 75 | 76 | internal RequestHelper GetRequestHelper() 77 | { 78 | return _requestHelper; 79 | } 80 | 81 | public void Dispose() 82 | { 83 | _requestHelper?.Dispose(); 84 | } 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | uploadcare-dotnet 2 | =============== 3 | 4 | [![Build Status](https://okolobaxa.visualstudio.com/uploadcare-dotnet/_apis/build/status/okolobaxa.uploadcare-dotnet?branchName=master)](https://okolobaxa.visualstudio.com/uploadcare-dotnet/_build/latest?definitionId=1&branchName=master) 5 | [![NuGet version](http://img.shields.io/nuget/v/UploadcareCSharp.svg)](https://www.nuget.org/packages/UploadcareCSharp/)  6 | 7 | C# library for Uploadcare. Uploadcare is a content delivery platform that optimizes web performance for developers, startups and large enterprises. 8 | 9 | Supported features: 10 | 11 | - Full Uploadcare API v0.5 (file, group, project and webhook, face detection) 12 | - CDN path builder 13 | - File uploads from disk, byteArray, and URL 14 | - Signed uploads 15 | - Simple and signed auth 16 | 17 | TODO: 18 | - Multi-part uploads 19 | - Throttling 20 | 21 | ## Nuget 22 | Latest stable version is available from [NuGet Gallery](https://www.nuget.org/packages/UploadcareCSharp/) 23 | 24 | ## Installation 25 | 26 | Using the [.NET Core command-line interface (CLI) tools][dotnet-core-cli-tools]: 27 | 28 | ```sh 29 | dotnet add package UploadcareCSharp 30 | ``` 31 | 32 | Using the [NuGet Command Line Interface (CLI)][nuget-cli]: 33 | 34 | ```sh 35 | nuget install UploadcareCSharp 36 | ``` 37 | 38 | Using the [Package Manager Console][package-manager-console]: 39 | 40 | ```powershell 41 | Install-Package UploadcareCSharp 42 | ``` 43 | 44 | From within Visual Studio: 45 | 46 | 1. Open the Solution Explorer. 47 | 2. Right-click on a project within your solution. 48 | 3. Click on *Manage NuGet Packages...* 49 | 4. Click on the *Browse* tab and search for "UploadcareCSharp". 50 | 5. Click on the UploadcareCSharp package, select the appropriate version in the 51 | right-tab and click *Install*. 52 | 53 | 54 | ## Examples 55 | ### Basic API Usage 56 | 57 | ```csharp 58 | var client = UploadcareClient.DemoClientWithSignedAuth(); 59 | 60 | var project = await client.Projects.GetAsync(); 61 | var file = await client.Files.GetAsync("85b5644f-e692-4855-9db0-8c5a83096e25"); 62 | ``` 63 | 64 | ### Building CDN URLs 65 | 66 | ```csharp 67 | var file = await client.Files.GetAsync("85b5644f-e692-4855-9db0-8c5a83096e25"); 68 | var builder = new CdnPathBuilder(file) 69 | .ResizeWidth(200) 70 | .CropCenter(200, 200) 71 | .Grayscale(); 72 | 73 | var url = builder.Build(); 74 | ``` 75 | ### File uploads 76 | 77 | ```csharp 78 | var file = new FileInfo("Lenna.png"); 79 | 80 | try 81 | { 82 | var uploader = new FileUploader(client); 83 | var result = await uploader.Upload(file); 84 | 85 | Console.Writeline(result.FileId); 86 | } 87 | catch (UploadFailureException ex) 88 | { 89 | Console.Writeline("Upload failed :("); 90 | } 91 | ``` 92 | For any requests, bug or comments, please [open an issue][issues] or [submit a pull request][pulls]. 93 | 94 | [issues]: https://github.com/okolobaxa/uploadcare-dotnet/issues/new 95 | [nuget-cli]: https://docs.microsoft.com/en-us/nuget/tools/nuget-exe-cli-reference 96 | [package-manager-console]: https://docs.microsoft.com/en-us/nuget/tools/package-manager-console 97 | [pulls]: https://github.com/okolobaxa/uploadcare-dotnet/pulls 98 | [dotnet-core-cli-tools]: https://docs.microsoft.com/en-us/dotnet/core/tools/ 99 | -------------------------------------------------------------------------------- /Uploadcare.Tests/Utils/CdnPathBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using Moq; 4 | using Uploadcare.Models; 5 | using Xunit; 6 | 7 | namespace Uploadcare.Tests.Utils 8 | { 9 | public class CdnPathBuilderTest 10 | { 11 | private const string FILE_ID = "27c7846b-a019-4516-a5e4-de635f822161"; 12 | private CdnPathBuilder _builder; 13 | 14 | 15 | [Fact] 16 | public void cdnpathbuilder_fileUrl_assert() 17 | { 18 | var fileMoq = new Mock(); 19 | fileMoq.Setup(x => x.Uuid).Returns(FILE_ID); 20 | 21 | _builder = new CdnPathBuilder(fileMoq.Object); 22 | var path = _builder.Build(); 23 | Assert.Equal("https://ucarecdn.com/" + FILE_ID + "/", path); 24 | } 25 | 26 | [Fact] 27 | public void cdnpathbuilder_fileUrl__static_assert() 28 | { 29 | var path = CdnPathBuilder.Build(FILE_ID).ToString(); 30 | 31 | Assert.Equal("https://ucarecdn.com/" + FILE_ID + "/", path); 32 | } 33 | 34 | [Fact] 35 | public void cdnpathbuilder_allOperations_assert() 36 | { 37 | var fileMoq = new Mock(); 38 | fileMoq.Setup(x => x.Uuid).Returns(FILE_ID); 39 | 40 | _builder = new CdnPathBuilder(fileMoq.Object); 41 | 42 | var path = _builder.Crop(100, 110).CropColor(120, 130, Color.Black).CropCenter(140, 150).CropCenterColor(160, 170, Color.Red).Resize(100, 110).ResizeWidth(120).ResizeHeight(130).ScaleCrop(100, 110).ScaleCropCenter(120, 130).Flip().Grayscale().Invert().Mirror().Build(); 43 | Assert.Equal("https://ucarecdn.com/" + FILE_ID + "/-/crop/100x110" + "/-/crop/120x130/000000" + "/-/crop/140x150/center" + "/-/crop/160x170/center/ff0000" + "/-/resize/100x110" + "/-/resize/120x" + "/-/resize/x130" + "/-/scale_crop/100x110" + "/-/scale_crop/120x130/center" + "/-/effect/flip" + "/-/effect/grayscale" + "/-/effect/invert" + "/-/effect/mirror" + "/", path); 44 | } 45 | 46 | [Fact] 47 | public void cdnpathbuilder_dimensionGuard_fail() 48 | { 49 | var fileMoq = new Mock(); 50 | fileMoq.Setup(x => x.Uuid).Returns(FILE_ID); 51 | 52 | _builder = new CdnPathBuilder(fileMoq.Object); 53 | 54 | _builder.ResizeWidth(1); 55 | _builder.ResizeWidth(1024); 56 | 57 | try 58 | { 59 | _builder.ResizeWidth(0); 60 | } 61 | catch (ArgumentException) 62 | { 63 | try 64 | { 65 | _builder.ResizeWidth(1025); 66 | } 67 | catch (ArgumentException) 68 | { 69 | Assert.True(true); 70 | } 71 | } 72 | } 73 | 74 | [Fact] 75 | public void cdnpathbuilder_dimensionsGuard_fail() 76 | { 77 | var fileMoq = new Mock(); 78 | fileMoq.Setup(x => x.Uuid).Returns(FILE_ID); 79 | 80 | _builder = new CdnPathBuilder(fileMoq.Object); 81 | 82 | _builder.Resize(1024, 634); 83 | _builder.Resize(634, 1024); 84 | try 85 | { 86 | _builder.Resize(1024, 635); 87 | } 88 | catch (ArgumentException) 89 | { 90 | try 91 | { 92 | _builder.Resize(635, 1024); 93 | } 94 | catch (ArgumentException) 95 | { 96 | Assert.True(true); 97 | } 98 | } 99 | } 100 | 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /Uploadcare/Upload/UrlUploader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Uploadcare.Clients; 5 | using Uploadcare.DTO; 6 | using Uploadcare.Exceptions; 7 | using Uploadcare.Models; 8 | using Uploadcare.Utils; 9 | 10 | namespace Uploadcare.Upload 11 | { 12 | /// 13 | /// Uploadcare uploader for URLs. 14 | /// 15 | public sealed class UrlUploader 16 | { 17 | private readonly UploadcareClient _client; 18 | private readonly int _pollingIntervalMsec; 19 | 20 | private CancellationTokenSource cts; 21 | 22 | /// 23 | /// Create a new uploader from a URL. 24 | /// 25 | /// Uploadcare client 26 | /// Time interval for polling upload progress 27 | public UrlUploader(IUploadcareClient client, int pollingIntervalMsec = 500) 28 | { 29 | _client = (UploadcareClient)client; 30 | _pollingIntervalMsec = pollingIntervalMsec; 31 | } 32 | 33 | /// 34 | /// Uploads the file to Uploadcare from URL. 35 | /// 36 | /// Defines your source file URL, which should be a public HTTP or HTTPS link 37 | /// Sets the file storing behavior. Once stored, files are not deleted after a 24-hour period 38 | /// UploadcareFile resource 39 | /// 40 | public async Task Upload(string sourceUrl, bool? store = null) 41 | { 42 | if (string.IsNullOrEmpty(sourceUrl)) 43 | { 44 | throw new ArgumentNullException(nameof(sourceUrl)); 45 | } 46 | 47 | var requestHelper = _client.GetRequestHelper(); 48 | var uploadUrl = Urls.UploadFromUrl(sourceUrl, _client.PublicKey, store); 49 | 50 | var uploadData = await requestHelper.Get(uploadUrl, default); 51 | 52 | using (cts = new CancellationTokenSource()) 53 | { 54 | var task = Task.Run(() => PoolStatus(requestHelper, uploadData.Token, cts.Token), cts.Token); 55 | 56 | var statusData = task.Result; 57 | 58 | return await _client.Files.GetAsync(statusData.FileId); 59 | } 60 | } 61 | 62 | private async Task PoolStatus(RequestHelper requestHelper, string fileToken, CancellationToken cancellationToken) 63 | { 64 | var statusUrl = Urls.UploadFromUrlStatus(fileToken); 65 | 66 | while (true) 67 | { 68 | await Task.Delay(_pollingIntervalMsec, cancellationToken); 69 | 70 | var data = await requestHelper.Get(statusUrl, default); 71 | 72 | if (data.Status.Equals("success")) 73 | { 74 | return data; 75 | } 76 | 77 | if (data.Status.Equals("error") || data.Status.Equals("failed")) 78 | { 79 | throw new UploadFailureException(data.Error); 80 | } 81 | 82 | cancellationToken.ThrowIfCancellationRequested(); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Uploadcare/Utils/PagedDataFilesEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.Specialized; 5 | using System.Linq; 6 | using Uploadcare.Utils.UrlParameters; 7 | 8 | namespace Uploadcare.Utils 9 | { 10 | internal class PagedDataFilesEnumerator : IEnumerator, IEnumerable 11 | { 12 | private readonly RequestHelper _requestHelper; 13 | private readonly Uri _url; 14 | private readonly IReadOnlyCollection _urlParameters; 15 | private readonly TK _dataClass; 16 | private int _page; 17 | private int _total; 18 | private bool _more; 19 | private IEnumerator _pageIterator; 20 | 21 | public PagedDataFilesEnumerator(RequestHelper requestHelper, Uri url, IReadOnlyCollection urlParameters, TK dataClass) 22 | { 23 | _requestHelper = requestHelper; 24 | _url = url; 25 | _urlParameters = urlParameters; 26 | _dataClass = dataClass; 27 | 28 | GetNext(); 29 | } 30 | 31 | private void GetNext() 32 | { 33 | var builder = new UriBuilder(_url); 34 | 35 | var queryParameters = new NameValueCollection(); 36 | SetQueryParameters(queryParameters, _urlParameters); 37 | queryParameters.Add("page", (++_page).ToString()); 38 | 39 | builder.Query = ToQueryString(queryParameters); 40 | 41 | var rawPageData = _requestHelper.Get(builder.Uri, _dataClass).GetAwaiter().GetResult(); 42 | var pageData = (IPageData)rawPageData; 43 | var results = pageData.GetResults(); 44 | 45 | _total += results.Count; 46 | _more = _total + 1 < pageData.Total; 47 | 48 | _pageIterator = results.GetEnumerator(); 49 | _pageIterator.MoveNext(); 50 | } 51 | 52 | public bool MoveNext() 53 | { 54 | if (_pageIterator.MoveNext()) 55 | { 56 | return true; 57 | } 58 | 59 | if (_more) 60 | { 61 | GetNext(); 62 | return true; 63 | } 64 | 65 | return false; 66 | } 67 | 68 | public void Reset() => throw new NotImplementedException(); 69 | 70 | public void Dispose() 71 | { 72 | } 73 | 74 | public T Current => _pageIterator.Current; 75 | 76 | object IEnumerator.Current => Current; 77 | 78 | public IEnumerator GetEnumerator() => this; 79 | 80 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 81 | 82 | private static void SetQueryParameters(NameValueCollection queryParameters, IEnumerable parameters) 83 | { 84 | foreach (var parameter in parameters) 85 | { 86 | queryParameters.Add(parameter.GetParam(), parameter.GetValue()); 87 | } 88 | } 89 | 90 | private static string ToQueryString(NameValueCollection nvc) 91 | { 92 | var array = (from key in nvc.AllKeys 93 | from value in nvc.GetValues(key) 94 | select $"{key}={value}").ToArray(); 95 | 96 | return string.Join("&", array); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Uploadcare/Clients/WebhookClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Uploadcare.Models; 5 | using Uploadcare.Utils; 6 | 7 | namespace Uploadcare.Clients 8 | { 9 | internal class WebhookClient : IWebhooksClient 10 | { 11 | private readonly RequestHelper _requestHelper; 12 | 13 | public WebhookClient(RequestHelper requestHelper) 14 | { 15 | _requestHelper = requestHelper; 16 | } 17 | 18 | public async Task> GetAsync() 19 | { 20 | var url = Urls.ApiWebhooks; 21 | 22 | var result = await _requestHelper.Get>(url, default); 23 | 24 | return result; 25 | } 26 | 27 | public async Task SubscribeAsync(string eventType, string targetUrl, bool isActive = true) 28 | { 29 | if (string.IsNullOrEmpty(eventType)) 30 | { 31 | throw new ArgumentNullException(nameof(eventType)); 32 | } 33 | 34 | if (string.IsNullOrEmpty(targetUrl)) 35 | { 36 | throw new ArgumentNullException(nameof(targetUrl)); 37 | } 38 | 39 | var url = Urls.ApiWebhooks; 40 | 41 | var formData = new Dictionary(3) 42 | { 43 | {"event", eventType}, 44 | {"target_url", targetUrl}, 45 | {"is_active", isActive.ToString()} 46 | }; 47 | 48 | var result = await _requestHelper.PostFormData(url, formData); 49 | 50 | return result; 51 | } 52 | 53 | public async Task UnsubscribeAsync(string eventType, string targetUrl) 54 | { 55 | if (string.IsNullOrEmpty(eventType)) 56 | { 57 | throw new ArgumentNullException(nameof(eventType)); 58 | } 59 | 60 | if (string.IsNullOrEmpty(targetUrl)) 61 | { 62 | throw new ArgumentNullException(nameof(targetUrl)); 63 | } 64 | 65 | var url = Urls.ApiWebhooksUnsubscribe; 66 | 67 | var formData = new Dictionary(2) 68 | { 69 | {"event", eventType}, 70 | {"target_url", targetUrl} 71 | }; 72 | 73 | await _requestHelper.PostFormData(url, formData); 74 | } 75 | 76 | public async Task UpdateAsync(long subscriptionId, string eventType = null, string targetUrl = null, bool? isActive = null) 77 | { 78 | if (subscriptionId <=0) 79 | { 80 | throw new ArgumentException(nameof(subscriptionId)); 81 | } 82 | 83 | var url = Urls.ApiWebhook(subscriptionId); 84 | 85 | var formData = new Dictionary(3); 86 | 87 | if (eventType != null) 88 | formData.Add("event", eventType); 89 | 90 | if (targetUrl != null) 91 | formData.Add("target_url", targetUrl); 92 | 93 | if (isActive != null) 94 | formData.Add("is_active", isActive.Value.ToString()); 95 | 96 | var result = await _requestHelper.PostFormData(url, formData); 97 | 98 | return result; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Uploadcare/Models/UploadcareFaceDetection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace Uploadcare.Models 7 | { 8 | public class UploadcareFaceDetection 9 | { 10 | [JsonPropertyName("id")] 11 | public string Id { get; set; } 12 | 13 | [JsonPropertyName("orientation")] 14 | public int? Orientation { get; set; } 15 | 16 | [JsonPropertyName("format")] 17 | public string Format { get; set; } 18 | 19 | [JsonPropertyName("height")] 20 | public int Height { get; set; } 21 | 22 | [JsonPropertyName("width")] 23 | public int Width { get; set; } 24 | 25 | [JsonPropertyName("geo_location")] 26 | public GeoLocationInfo GeoLocation { get; set; } 27 | 28 | [JsonPropertyName("datetime_original")] 29 | public DateTime? DatetimeOriginal { get; set; } 30 | 31 | [JsonPropertyName("dpi")] 32 | public List DPI { get; set; } 33 | 34 | [JsonPropertyName("faces"), JsonConverter(typeof(UploadcareFaceConverter))] 35 | public List Faces { get; set; } 36 | } 37 | 38 | public class UploadcareFace 39 | { 40 | public UploadcareFace(int[] numbers) 41 | { 42 | FaceCoordinates = numbers; 43 | } 44 | 45 | public int[] FaceCoordinates { get; set; } 46 | 47 | /// 48 | /// Coordinates of the upper-left corner of an area where a face was found. 49 | /// 50 | public int Left => FaceCoordinates[0]; 51 | 52 | /// 53 | /// Coordinates of the upper-left corner of an area where a face was found. 54 | /// 55 | public int Top => FaceCoordinates[1]; 56 | 57 | /// 58 | /// Dimensions of that area 59 | /// 60 | public int Width => FaceCoordinates[2]; 61 | 62 | /// 63 | /// Dimensions of that area 64 | /// 65 | public int Height => FaceCoordinates[3]; 66 | } 67 | 68 | public class UploadcareFaceConverter : JsonConverter> 69 | { 70 | public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 71 | { 72 | if (reader.TokenType != JsonTokenType.StartArray) 73 | { 74 | throw new JsonException(); 75 | } 76 | 77 | var result = new List(); 78 | 79 | var startDepth = reader.CurrentDepth; 80 | 81 | while (reader.Read()) 82 | { 83 | switch (reader.TokenType) 84 | { 85 | case JsonTokenType.EndArray when reader.CurrentDepth == startDepth: 86 | return result; 87 | case JsonTokenType.EndArray: 88 | continue; 89 | } 90 | 91 | var numbers = new int[4]; 92 | reader.Read(); 93 | numbers[0] = reader.GetInt32(); 94 | 95 | reader.Read(); 96 | numbers[1] = reader.GetInt32(); 97 | 98 | reader.Read(); 99 | numbers[2] = reader.GetInt32(); 100 | 101 | reader.Read(); 102 | numbers[3] = reader.GetInt32(); 103 | 104 | var face = new UploadcareFace(numbers); 105 | result.Add(face); 106 | } 107 | 108 | throw new JsonException(); 109 | } 110 | 111 | public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) 112 | { 113 | throw new NotImplementedException(); 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | 198 | #Visual Studio Code files 199 | .vscode 200 | /.idea 201 | -------------------------------------------------------------------------------- /Uploadcare/Clients/IFilesClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using Uploadcare.Models; 6 | 7 | namespace Uploadcare.Clients 8 | { 9 | public interface IFilesClient 10 | { 11 | /// 12 | /// Requests file data. 13 | /// 14 | /// File UUID 15 | /// File resource 16 | Task GetAsync(string fileId); 17 | 18 | /// 19 | /// Requests file data as Stream 20 | /// 21 | /// File UUID 22 | /// File resource 23 | Task GetStreamAsync(string fileId); 24 | 25 | /// 26 | /// Marks a file as deleted. 27 | /// 28 | /// File UUID 29 | Task DeleteAsync(string fileId); 30 | 31 | /// 32 | /// Marks a file as saved. 33 | /// 34 | /// This has to be done for all files you want to keep. Unsaved files are eventually purged. 35 | /// 36 | /// File UUID 37 | /// File resource 38 | Task StoreAsync(string fileId); 39 | 40 | /// 41 | /// Copying uploaded files to a specified storage 42 | /// 43 | /// A CDN URL or just UUID of a file subjected to copy 44 | /// true to store files while copying. If stored, files won’t be automatically deleted after a 24-hour period. false to not store files, default. 45 | /// Applicable to custom storage only. true to make copied files available via public links, false to reverse the behavior. 46 | /// UUID of new File resource 47 | Task CopyAsync(string source, bool store = false, bool makePublic = false); 48 | 49 | /// 50 | /// Copying uploaded files to a specified storage 51 | /// 52 | /// A CDN URL or just UUID of a file subjected to copy 53 | /// Identifies a custom storage name related to your project. Implies you are copying a file to a specified custom storage 54 | /// true to store files while copying. If stored, files won’t be automatically deleted after a 24-hour period. false to not store files, default. 55 | /// Applicable to custom storage only. true to make copied files available via public links, false to reverse the behavior. 56 | /// Applies to custom storage usage scenario only. The parameter is used to specify file names Uploadcare passes to a custom storage. 57 | /// UUID of new File resource 58 | Task CopyAsync(string source, string target, bool store = false, bool makePublic = false, string pattern = null); 59 | 60 | /// 61 | /// Batch file storing 62 | /// 63 | /// Up to 100 UUIDs are supported per request. 64 | /// 65 | /// Array of file UUIDs 66 | /// Tuple of successfully stored files and errors 67 | Task, IReadOnlyDictionary>> StoreAsync(IReadOnlyCollection fileIds); 68 | 69 | /// 70 | /// Batch file delete 71 | /// 72 | /// Up to 100 UUIDs are supported per request. 73 | /// 74 | /// Array of file UUIDs 75 | /// Tuple of successfully deleted files and errors 76 | Task, IReadOnlyDictionary>> DeleteAsync(IReadOnlyCollection fileIds); 77 | 78 | /// 79 | /// Begins to build a request for uploaded files for the current account. 80 | /// 81 | /// File resource request builder 82 | FilesQueryBuilder GetFiles(); 83 | } 84 | } -------------------------------------------------------------------------------- /Uploadcare/Upload/FileUploader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using Uploadcare.Clients; 7 | using Uploadcare.DTO; 8 | using Uploadcare.Exceptions; 9 | using Uploadcare.Models; 10 | using Uploadcare.Utils; 11 | 12 | namespace Uploadcare.Upload 13 | { 14 | /// 15 | /// Uploadcare uploader for files and binary data. 16 | /// 17 | public class FileUploader 18 | { 19 | protected readonly UploadcareClient Client; 20 | 21 | private const string PublicKeyContent = "UPLOADCARE_PUB_KEY"; 22 | private const string StoreContent = "UPLOADCARE_STORE"; 23 | 24 | 25 | /// 26 | /// Creates a new uploader from binary data. 27 | /// 28 | /// Uploadcare client 29 | public FileUploader(IUploadcareClient client) 30 | { 31 | Client = (UploadcareClient)client; 32 | } 33 | 34 | /// 35 | /// Uploads the file to Uploadcare. 36 | /// 37 | /// File binary data 38 | /// File name 39 | /// Sets the file storing behavior. In this context, storing a file means making it permanently available 40 | /// An Uploadcare file 41 | /// 42 | public Task Upload(byte[] bytes, string filename, bool? store = null) 43 | { 44 | if (string.IsNullOrEmpty(filename)) 45 | { 46 | throw new ArgumentNullException(nameof(filename)); 47 | } 48 | 49 | var content = new ByteArrayContent(bytes); 50 | 51 | return UploadInternal(content, filename, store); 52 | } 53 | 54 | /// 55 | /// Uploads the file to Uploadcare. 56 | /// 57 | /// File information 58 | /// Sets the file storing behavior. In this context, storing a file means making it permanently available 59 | /// An Uploadcare file 60 | /// 61 | public Task Upload(FileInfo fileInfo, bool? store = null) 62 | { 63 | if (fileInfo == null) 64 | { 65 | throw new ArgumentNullException(nameof(fileInfo)); 66 | } 67 | 68 | var content = new StreamContent(File.OpenRead(fileInfo.FullName)); 69 | 70 | return UploadInternal(content, fileInfo.FullName, store); 71 | } 72 | 73 | private async Task UploadInternal(HttpContent binaryContent, string filename, bool? store = null) 74 | { 75 | var url = Urls.UploadBase; 76 | 77 | try 78 | { 79 | var boundary = DateTime.Now.Ticks.ToString("x", NumberFormatInfo.InvariantInfo); 80 | 81 | using (var content = new MultipartFormDataContent(boundary)) 82 | { 83 | content.Add(new StringContent(Client.PublicKey), PublicKeyContent); 84 | 85 | if (store != null) 86 | { 87 | content.Add(store.Value ? new StringContent("1") : new StringContent("0"), StoreContent); 88 | } 89 | else 90 | { 91 | content.Add(new StringContent("auto"), StoreContent); 92 | } 93 | 94 | AddAdditionalContent(content); 95 | 96 | content.Add(binaryContent, "file", filename); 97 | 98 | var file = await Client.GetRequestHelper().PostContent(url, content); 99 | 100 | return await Client.Files.GetAsync(file.File); 101 | } 102 | } 103 | catch (Exception e) 104 | { 105 | throw new UploadFailureException(e); 106 | } 107 | } 108 | 109 | protected virtual void AddAdditionalContent(MultipartFormDataContent content) { } 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /Uploadcare/FileQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Uploadcare.DTO; 5 | using Uploadcare.Models; 6 | using Uploadcare.Utils; 7 | using Uploadcare.Utils.UrlParameters; 8 | 9 | namespace Uploadcare 10 | { 11 | public class FilesQueryBuilder : IPaginatedQueryBuilder 12 | { 13 | private readonly RequestHelper _requestHelper; 14 | private readonly List _parameters = new List(12); 15 | 16 | 17 | /// 18 | /// Initializes a new builder. 19 | /// 20 | internal FilesQueryBuilder(RequestHelper requestHelper) 21 | { 22 | _requestHelper = requestHelper; 23 | } 24 | 25 | /// 26 | /// Adds a filter for removed files. 27 | /// 28 | /// true to only include removed files in the response, false to include existing files. Defaults to false. 29 | public FilesQueryBuilder Removed(bool removed) 30 | { 31 | _parameters.Add(new FilesRemovedParameter(removed)); 32 | 33 | return this; 34 | } 35 | 36 | /// 37 | /// Adds a filter for removed files. 38 | /// 39 | public FilesQueryBuilder OnlyRemoved() 40 | { 41 | _parameters.Add(new FilesRemovedParameter(removed: true)); 42 | 43 | return this; 44 | } 45 | 46 | /// 47 | /// Adds a filter for stored files. 48 | /// 49 | /// true to only include files that were stored, false to include temporary ones. The default is unset: both stored and not stored files are returned. 50 | public FilesQueryBuilder Stored(bool stored) 51 | { 52 | _parameters.Add(new FilesStoredParameter(stored)); 53 | 54 | return this; 55 | } 56 | 57 | /// 58 | /// Adds a filter for stored files. 59 | /// 60 | public FilesQueryBuilder OnlyStored() 61 | { 62 | _parameters.Add(new FilesStoredParameter(stored: true)); 63 | 64 | return this; 65 | } 66 | 67 | /// 68 | /// Set a preferred amount of files in a list for a single response 69 | /// 70 | /// A preferred amount of files in a list for a single response. Defaults to 100, while the maximum is 1000. 71 | public FilesQueryBuilder Limit(int limit) 72 | { 73 | if (limit > 1000) 74 | { 75 | limit = 1000; 76 | } 77 | 78 | _parameters.Add(new LimitParameter(limit)); 79 | 80 | return this; 81 | } 82 | 83 | public FilesQueryBuilder OrderByUploadDate(DateTime? from = null) 84 | { 85 | _parameters.Add(new FileOrderingParameter(EFileOrderBy.DatetimeUploaded)); 86 | 87 | if (from.HasValue) 88 | { 89 | _parameters.Add(new OrderingFromDateParameter(from.Value)); 90 | } 91 | 92 | return this; 93 | } 94 | 95 | public FilesQueryBuilder OrderByUploadDateDesc(DateTime? from = null) 96 | { 97 | _parameters.Add(new FileOrderingParameter(EFileOrderBy.DatetimeUploadedDesc)); 98 | 99 | if (from.HasValue) 100 | { 101 | _parameters.Add(new OrderingFromDateParameter(from.Value)); 102 | } 103 | 104 | return this; 105 | } 106 | 107 | public FilesQueryBuilder OrderBySize(long? sizeFromBytes = null) 108 | { 109 | _parameters.Add(new FileOrderingParameter(EFileOrderBy.Size)); 110 | 111 | if (sizeFromBytes.HasValue) 112 | { 113 | _parameters.Add(new OrderingFromSizeParameter(sizeFromBytes.Value)); 114 | } 115 | 116 | return this; 117 | } 118 | 119 | public FilesQueryBuilder OrderBySizeDesc(long? sizeFromBytes = null) 120 | { 121 | _parameters.Add(new FileOrderingParameter(EFileOrderBy.SizeDesc)); 122 | 123 | if (sizeFromBytes.HasValue) 124 | { 125 | _parameters.Add(new OrderingFromSizeParameter(sizeFromBytes.Value)); 126 | } 127 | 128 | return this; 129 | } 130 | 131 | public IEnumerable AsEnumerable() 132 | { 133 | var url = Urls.ApiFiles; 134 | 135 | var result = _requestHelper.ExecutePaginatedQuery(url, _parameters, new FilePageData()); 136 | 137 | return result; 138 | } 139 | 140 | public List AsList() 141 | { 142 | return AsEnumerable().ToList(); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /Uploadcare/Clients/FilesClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Http.Headers; 6 | using System.Threading.Tasks; 7 | using Uploadcare.DTO; 8 | using Uploadcare.Models; 9 | using Uploadcare.Utils; 10 | 11 | namespace Uploadcare.Clients 12 | { 13 | internal class FilesClient : IFilesClient 14 | { 15 | private readonly RequestHelper _requestHelper; 16 | 17 | public FilesClient(RequestHelper requestHelper) 18 | { 19 | _requestHelper = requestHelper; 20 | } 21 | 22 | public async Task GetAsync(string fileId) 23 | { 24 | if (string.IsNullOrEmpty(fileId)) 25 | { 26 | throw new ArgumentNullException(nameof(fileId)); 27 | } 28 | 29 | var url = Urls.ApiFile(fileId); 30 | 31 | var result = await _requestHelper.Get(url, default); 32 | 33 | return result; 34 | } 35 | 36 | public async Task GetStreamAsync(string fileId) 37 | { 38 | var result = await GetAsync(fileId); 39 | 40 | var client = _requestHelper.HttpClient; 41 | 42 | //We want to utilize same instance of HttpClient, but for cdn operations (like download) we should not pass any auth headers 43 | 44 | var authHeaderValue = client.DefaultRequestHeaders.Authorization; 45 | client.DefaultRequestHeaders.Remove("Authorization"); 46 | 47 | var stream = await client.GetStreamAsync(result.OriginalFileUrl); 48 | 49 | client.DefaultRequestHeaders.Authorization = authHeaderValue; 50 | 51 | return stream; 52 | } 53 | 54 | public async Task DeleteAsync(string fileId) 55 | { 56 | if (string.IsNullOrEmpty(fileId)) 57 | { 58 | throw new ArgumentNullException(nameof(fileId)); 59 | } 60 | 61 | var url = Urls.ApiFile(fileId); 62 | 63 | await _requestHelper.Delete(url, string.Empty); 64 | } 65 | 66 | public async Task StoreAsync(string fileId) 67 | { 68 | if (string.IsNullOrEmpty(fileId)) 69 | { 70 | throw new ArgumentNullException(nameof(fileId)); 71 | } 72 | 73 | var url = Urls.ApiFileStorage(fileId); 74 | 75 | var result = await _requestHelper.Post(url); 76 | 77 | return result; 78 | } 79 | 80 | public async Task CopyAsync(string source, bool store = false, bool makePublic = false) 81 | { 82 | if (string.IsNullOrEmpty(source)) 83 | { 84 | throw new ArgumentNullException(nameof(source)); 85 | } 86 | 87 | var url = Urls.ApiFiles; 88 | 89 | var formData = new Dictionary 90 | { 91 | {"source", source}, 92 | {"store", store.ToString()}, 93 | {"make_public", makePublic.ToString()} 94 | }; 95 | 96 | var result = await _requestHelper.PostFormData(url, formData); 97 | 98 | return result.File.Uuid; 99 | } 100 | 101 | public async Task CopyAsync(string source, string target, 102 | bool store = false, bool makePublic = false, string pattern = null) 103 | { 104 | if (string.IsNullOrEmpty(source)) 105 | { 106 | throw new ArgumentNullException(nameof(source)); 107 | } 108 | 109 | if (string.IsNullOrEmpty(target)) 110 | { 111 | throw new ArgumentNullException(nameof(target)); 112 | } 113 | 114 | var url = Urls.ApiFiles; 115 | 116 | var formData = new Dictionary 117 | { 118 | {"source", source}, 119 | {"target", target}, 120 | {"pattern", pattern}, 121 | {"store", store.ToString()}, 122 | {"make_public", makePublic.ToString()} 123 | }; 124 | 125 | var result = await _requestHelper.PostFormData(url, formData); 126 | 127 | return result.Url; 128 | } 129 | 130 | public async Task, IReadOnlyDictionary>> StoreAsync(IReadOnlyCollection fileIds) 131 | { 132 | if (fileIds == null) 133 | { 134 | throw new ArgumentNullException(nameof(fileIds)); 135 | } 136 | 137 | if (!fileIds.Any()) 138 | { 139 | return new Tuple, IReadOnlyDictionary>(Array.Empty(), new Dictionary()); 140 | } 141 | 142 | var url = Urls.ApiFilesStorage; 143 | 144 | var result = await _requestHelper.Put, MassOperationsData>(url, fileIds); 145 | 146 | return new Tuple, IReadOnlyDictionary>(result.Files, result.Problems); 147 | } 148 | 149 | public async Task, IReadOnlyDictionary>> DeleteAsync(IReadOnlyCollection fileIds) 150 | { 151 | if (fileIds == null) 152 | { 153 | throw new ArgumentNullException(nameof(fileIds)); 154 | } 155 | 156 | if (!fileIds.Any()) 157 | { 158 | return new Tuple, IReadOnlyDictionary>(Array.Empty(), new Dictionary()); 159 | } 160 | 161 | var url = Urls.ApiFilesStorage; 162 | 163 | var result = await _requestHelper.Delete, MassOperationsData>(url, fileIds); 164 | 165 | return new Tuple, IReadOnlyDictionary>(result.Files, result.Problems); 166 | } 167 | 168 | public FilesQueryBuilder GetFiles() 169 | { 170 | return new FilesQueryBuilder(_requestHelper); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Uploadcare/Utils/RequestHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Threading.Tasks; 8 | using Uploadcare.Exceptions; 9 | using Uploadcare.Utils.UrlParameters; 10 | 11 | namespace Uploadcare.Utils 12 | { 13 | internal class RequestHelper : IDisposable 14 | { 15 | private readonly IUploadcareConnection _connection; 16 | internal readonly HttpClient HttpClient; 17 | 18 | internal RequestHelper(IUploadcareConnection connection) 19 | { 20 | _connection = connection; 21 | HttpClient = new HttpClient(); 22 | 23 | HttpClient.DefaultRequestHeaders.Add("Accept", new[] { "application/vnd.uploadcare-v0.5+json" }); 24 | 25 | if (connection.AuthType == UploadcareAuthType.Simple) 26 | { 27 | HttpClient.DefaultRequestHeaders.Add("Authorization", AuthHeaderHelper.GetSimple(_connection.PublicKey, _connection.PrivateKey)); 28 | } 29 | } 30 | 31 | public IUploadcareConnection GetConnection() => _connection; 32 | 33 | public IEnumerable ExecutePaginatedQuery(Uri url, IReadOnlyCollection urlParameters, TK pageData) 34 | { 35 | return new PagedDataFilesEnumerator(this, url, urlParameters, pageData); 36 | } 37 | 38 | public async Task PostContent(Uri uri, HttpContent requestContent) 39 | { 40 | var httpRequestMessage = new HttpRequestMessage 41 | { 42 | Method = HttpMethod.Post, 43 | RequestUri = uri, 44 | Headers = 45 | { 46 | { HttpRequestHeader.Date.ToString(), DateTime.Now.ToString("R") } 47 | }, 48 | Content = requestContent 49 | }; 50 | 51 | var responseMessage = await HttpClient.SendAsync(httpRequestMessage); 52 | 53 | await CheckResponseStatus(responseMessage); 54 | 55 | return await GetResponse(responseMessage); 56 | } 57 | 58 | public async Task Get(Uri uri, TOut dataClass) 59 | { 60 | var responseMessage = await Send(uri, HttpMethod.Get, new StringContent(string.Empty, Encoding.UTF8, "application/json")); 61 | 62 | await CheckResponseStatus(responseMessage); 63 | 64 | return await GetResponse(responseMessage); 65 | } 66 | 67 | public async Task Post(Uri uri) 68 | { 69 | var responseMessage = await Send(uri, HttpMethod.Post, new StringContent(string.Empty, Encoding.UTF8, "application/json")); 70 | 71 | await CheckResponseStatus(responseMessage); 72 | 73 | return await GetResponse(responseMessage); 74 | } 75 | 76 | public async Task PostFormData(Uri uri, Dictionary formData) 77 | { 78 | using (var content = new FormUrlEncodedContent(formData)) 79 | { 80 | var responseMessage = await Send(uri, HttpMethod.Post, content); 81 | 82 | await CheckResponseStatus(responseMessage); 83 | 84 | return await GetResponse(responseMessage); 85 | } 86 | } 87 | 88 | public async Task PostFormData(Uri uri, Dictionary formData) 89 | { 90 | using (var content = new FormUrlEncodedContent(formData)) 91 | { 92 | var responseMessage = await Send(uri, HttpMethod.Post, content); 93 | 94 | await CheckResponseStatus(responseMessage); 95 | } 96 | } 97 | 98 | public async Task Put(Uri uri, TIn data) 99 | { 100 | var stringData = JsonSerializer.Serialize(data); 101 | var content = new StringContent(stringData, Encoding.UTF8, "application/json"); 102 | 103 | var responseMessage = await Send(uri, HttpMethod.Put, content); 104 | 105 | await CheckResponseStatus(responseMessage); 106 | 107 | return await GetResponse(responseMessage); 108 | } 109 | 110 | public async Task Delete(Uri uri, TIn data) 111 | { 112 | var stringData = JsonSerializer.Serialize(data); 113 | var content = new StringContent(stringData, Encoding.UTF8, "application/json"); 114 | 115 | var responseMessage = await Send(uri, HttpMethod.Delete, content); 116 | 117 | await CheckResponseStatus(responseMessage); 118 | 119 | return await GetResponse(responseMessage); 120 | } 121 | 122 | private async Task Send(Uri uri, HttpMethod method, HttpContent content) 123 | { 124 | try 125 | { 126 | var dateHeader = DateTime.UtcNow.ToString("R"); 127 | 128 | var httpRequestMessage = new HttpRequestMessage 129 | { 130 | Method = method, 131 | RequestUri = uri, 132 | Headers = 133 | { 134 | {HttpRequestHeader.Date.ToString(), dateHeader} 135 | }, 136 | Content = content 137 | }; 138 | 139 | if (_connection.AuthType == UploadcareAuthType.Signed) 140 | { 141 | var contentTypeHeader = content.Headers.ContentType.ToString(); 142 | var contentBytes = await content.ReadAsByteArrayAsync(); 143 | var stringContentHash = CryptoHelper.BytesToMD5(contentBytes); 144 | 145 | var dataForSign = AuthHeaderHelper.CombineDataForSignature(method.Method, stringContentHash, contentTypeHeader, dateHeader, uri.PathAndQuery); 146 | var signature = CryptoHelper.Sign(dataForSign, _connection.PrivateKey); 147 | 148 | httpRequestMessage.Headers.Add("Authorization", AuthHeaderHelper.GetSigned(_connection.PublicKey, signature)); 149 | } 150 | 151 | var responseMessage = await HttpClient.SendAsync(httpRequestMessage); 152 | 153 | await CheckResponseStatus(responseMessage); 154 | 155 | return responseMessage; 156 | } 157 | catch (HttpRequestException e) 158 | { 159 | throw new UploadcareNetworkException(e); 160 | } 161 | } 162 | 163 | private static async Task CheckResponseStatus(HttpResponseMessage response) 164 | { 165 | var statusCode = response.StatusCode; 166 | 167 | if (statusCode >= HttpStatusCode.OK && statusCode < HttpStatusCode.Ambiguous) 168 | { 169 | return; 170 | } 171 | 172 | var body = await response.Content.ReadAsStringAsync(); 173 | 174 | JsonDocument jObject; 175 | 176 | try 177 | { 178 | jObject = JsonDocument.Parse(body); 179 | } 180 | catch (JsonException) 181 | { 182 | throw new UploadcareInvalidResponseException(body); 183 | } 184 | 185 | var errorToken = jObject.RootElement.GetProperty("detail"); 186 | if (errorToken.ValueKind == JsonValueKind.Null) 187 | { 188 | throw new UploadcareInvalidResponseException(body); 189 | } 190 | 191 | switch (statusCode) 192 | { 193 | case HttpStatusCode.Unauthorized: 194 | case HttpStatusCode.Forbidden: 195 | throw new UploadcareAuthenticationException(errorToken.GetString()); 196 | case HttpStatusCode.BadRequest: 197 | case HttpStatusCode.NotFound: 198 | throw new UploadcareInvalidRequestException(errorToken.GetString()); 199 | } 200 | 201 | throw new UploadcareApiException("Unknown exception during an API call, response:" + body); 202 | } 203 | 204 | private static async Task GetResponse(HttpResponseMessage responseMessage) 205 | { 206 | using (var responseContent = responseMessage.Content) 207 | { 208 | if (responseContent == null) throw new UploadcareInvalidResponseException("Received empty response"); 209 | 210 | var responseBody = await responseContent.ReadAsStreamAsync(); 211 | 212 | return await JsonSerializer.DeserializeAsync(responseBody); 213 | } 214 | } 215 | 216 | public void Dispose() 217 | { 218 | HttpClient?.Dispose(); 219 | } 220 | } 221 | } -------------------------------------------------------------------------------- /Uploadcare.Tests/Clients/FileClientTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Uploadcare.Tests.Helpers; 8 | using Uploadcare.Upload; 9 | using Xunit; 10 | 11 | namespace Uploadcare.Tests.Clients 12 | { 13 | public class FileClientTest 14 | { 15 | [Fact] 16 | public async Task client_getfile_assert() 17 | { 18 | var client = UploadcareClient.DemoClientWithSignedAuth(); 19 | var file = new FileInfo("Lenna.png"); 20 | 21 | var uploader = new FileUploader(client); 22 | var uploadedFileInfo = await uploader.Upload(file); 23 | 24 | await Task.Delay(5000); 25 | 26 | var result = await client.Files.GetAsync(uploadedFileInfo.Uuid); 27 | 28 | Assert.NotNull(result.Uuid); 29 | } 30 | 31 | [Fact] 32 | public async Task client_getfileasstream_assert() 33 | { 34 | var client = UploadcareClient.DemoClientWithSignedAuth(); 35 | var file = new FileInfo("Lenna.png"); 36 | 37 | var uploader = new FileUploader(client); 38 | var uploadedFileInfo = await uploader.Upload(file); 39 | 40 | await Task.Delay(5000); 41 | 42 | var temp = Path.GetTempFileName(); 43 | 44 | using (var stream = await client.Files.GetStreamAsync(uploadedFileInfo.Uuid)) 45 | using (var fileStream = File.Create(temp)) 46 | { 47 | await stream.CopyToAsync(fileStream); 48 | } 49 | 50 | var originalHash = HashHelper.GetFileHash("Lenna.png"); 51 | var copiedHash = HashHelper.GetFileHash(temp); 52 | 53 | Assert.Equal(copiedHash, originalHash); 54 | 55 | File.Delete(temp); 56 | } 57 | 58 | [Fact] 59 | public async Task client_savefile_assert() 60 | { 61 | var client = UploadcareClient.DemoClientWithSignedAuth(); 62 | var file = new FileInfo("Lenna.png"); 63 | 64 | var uploader = new FileUploader(client); 65 | var uploadedFileInfo = await uploader.Upload(file); 66 | 67 | await Task.Delay(5000); 68 | 69 | var result = await client.Files.StoreAsync(uploadedFileInfo.Uuid); 70 | 71 | Assert.NotNull(result.Uuid); 72 | Assert.True(result.Stored); 73 | } 74 | 75 | [Fact] 76 | public async Task client_deletefile_assert() 77 | { 78 | var client = UploadcareClient.DemoClientWithSignedAuth(); 79 | var file = new FileInfo("Lenna.png"); 80 | 81 | var uploader = new FileUploader(client); 82 | var uploadedFileInfo = await uploader.Upload(file); 83 | 84 | await Task.Delay(5000); 85 | 86 | await client.Files.DeleteAsync(uploadedFileInfo.Uuid); 87 | var result = await client.Files.GetAsync(uploadedFileInfo.Uuid); 88 | 89 | Assert.NotNull(result.Uuid); 90 | Assert.True(result.Removed); 91 | } 92 | 93 | [Fact] 94 | public void client_getfiles_iterable() 95 | { 96 | var client = UploadcareClient.DemoClientWithSignedAuth(); 97 | var count = 0; 98 | 99 | foreach (var file in client.Files.GetFiles().AsEnumerable()) 100 | { 101 | Assert.NotNull(file); 102 | count++; 103 | if (count == 50) 104 | break; 105 | } 106 | 107 | Assert.True(count == 50); 108 | } 109 | 110 | [Fact] 111 | public void client_getfiles_iterable_stored() 112 | { 113 | var client = UploadcareClient.DemoClientWithSignedAuth(); 114 | var count = 0; 115 | 116 | foreach (var file in client.Files.GetFiles().Stored(true).AsEnumerable()) 117 | { 118 | Assert.NotNull(file); 119 | count++; 120 | if (count == 150) 121 | break; 122 | } 123 | 124 | Assert.True(count == 150); 125 | } 126 | 127 | [Fact] 128 | public void client_getfiles_iterable_orderby_size() 129 | { 130 | var client = UploadcareClient.DemoClientWithSignedAuth(); 131 | var count = 0; 132 | 133 | foreach (var file in client.Files.GetFiles().OrderBySize(100000).AsEnumerable()) 134 | { 135 | Assert.NotNull(file); 136 | count++; 137 | if (count == 150) 138 | break; 139 | } 140 | 141 | Assert.True(count == 150); 142 | } 143 | 144 | [Fact] 145 | public void client_getfiles_iterable_orderby_date() 146 | { 147 | var client = UploadcareClient.DemoClientWithSignedAuth(); 148 | var count = 0; 149 | 150 | foreach (var file in client.Files.GetFiles().OrderByUploadDate(DateTime.UtcNow.AddDays(-1)).AsEnumerable()) 151 | { 152 | Assert.NotNull(file); 153 | count++; 154 | if (count == 150) 155 | break; 156 | } 157 | 158 | Assert.True(count == 150); 159 | } 160 | 161 | [Fact] 162 | public async Task client_copyfile_assert() 163 | { 164 | var client = UploadcareClient.DemoClientWithSignedAuth(); 165 | var file = new FileInfo("Lenna.png"); 166 | 167 | var uploader = new FileUploader(client); 168 | 169 | var uploadedFileInfo = await uploader.Upload(file); 170 | 171 | //We have to wait end of file processing in Uploadcare backend for copy it 172 | if (!uploadedFileInfo.IsReady) 173 | { 174 | await Task.Delay(1000); 175 | 176 | while (!uploadedFileInfo.IsReady) 177 | { 178 | uploadedFileInfo = await client.Files.GetAsync(uploadedFileInfo.Uuid); 179 | } 180 | } 181 | 182 | var newFileId = client.Files.CopyAsync(uploadedFileInfo.Uuid).GetAwaiter().GetResult(); 183 | 184 | Assert.NotNull(newFileId); 185 | } 186 | 187 | [Fact] 188 | public async Task client_massstore_assert() 189 | { 190 | var client = UploadcareClient.DemoClientWithSignedAuth(); 191 | var file = new FileInfo("Lenna.png"); 192 | 193 | var uploader = new FileUploader(client); 194 | var uploadedFileInfo1 = await uploader.Upload(file, false); 195 | var uploadedFileInfo2 = await uploader.Upload(file, false); 196 | var badFileId = "4j334o01-8bs3"; 197 | 198 | await Task.Delay(5000); 199 | 200 | var fileIds = new[] { uploadedFileInfo1.Uuid, uploadedFileInfo2.Uuid, badFileId }; 201 | 202 | var (files, problems) = await client.Files.StoreAsync(fileIds.ToList()); 203 | 204 | Assert.Contains(files, x => x.Uuid == uploadedFileInfo1.Uuid); 205 | Assert.Contains(files, x => x.Uuid == uploadedFileInfo2.Uuid); 206 | Assert.Contains(problems, x => x.Key == badFileId); 207 | } 208 | 209 | [Fact] 210 | public async Task client_massdelete_assert() 211 | { 212 | var client = UploadcareClient.DemoClientWithSignedAuth(); 213 | var file = new FileInfo("Lenna.png"); 214 | 215 | var uploader = new FileUploader(client); 216 | var uploadedFileInfo1 = await uploader.Upload(file, false); 217 | var uploadedFileInfo2 = await uploader.Upload(file, false); 218 | var badFileId = "4j334o01-8bs3"; 219 | 220 | await Task.Delay(5000); 221 | 222 | var fileIds = new[] { uploadedFileInfo1.Uuid, uploadedFileInfo2.Uuid, badFileId }; 223 | 224 | var (files, problems) = await client.Files.DeleteAsync(fileIds.ToList()); 225 | 226 | Assert.Contains(files, x => x.Uuid == uploadedFileInfo1.Uuid); 227 | Assert.Contains(files, x => x.Uuid == uploadedFileInfo2.Uuid); 228 | Assert.Contains(problems, x => x.Key == badFileId); 229 | } 230 | 231 | [Fact] 232 | public async Task client_detect_faces() 233 | { 234 | var client = new UploadcareClient("e8e49b932d98e53748a3", "899a21a3aee6a7800859"); 235 | var file = new FileInfo("2.jpg"); 236 | 237 | var uploader = new FileUploader(client); 238 | var uploadedFileInfo = await uploader.Upload(file, false); 239 | 240 | await Task.Delay(5000); 241 | 242 | var faces = await client.FaceDetection.DetectFaces(uploadedFileInfo.Uuid); 243 | 244 | Assert.NotEmpty(faces); 245 | } 246 | 247 | [Fact] 248 | public async Task client_detect_no_faces() 249 | { 250 | var client = new UploadcareClient("e8e49b932d98e53748a3", "899a21a3aee6a7800859"); 251 | var file = new FileInfo("0.jpg"); 252 | 253 | var uploader = new FileUploader(client); 254 | var uploadedFileInfo = await uploader.Upload(file, false); 255 | 256 | await Task.Delay(5000); 257 | 258 | var faces = await client.FaceDetection.DetectFaces(uploadedFileInfo.Uuid); 259 | 260 | Assert.Empty(faces); 261 | } 262 | } 263 | } -------------------------------------------------------------------------------- /Uploadcare/CdnPathBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Text; 4 | using Uploadcare.Models; 5 | using Uploadcare.Utils; 6 | 7 | namespace Uploadcare 8 | { 9 | public sealed class CdnPathBuilder 10 | { 11 | private readonly StringBuilder _sb = new StringBuilder(); 12 | 13 | /// 14 | /// Creates a new CDN path builder for some image UploadcareFile. 15 | /// 16 | /// UploadcareFile to be used for the path 17 | /// 18 | public CdnPathBuilder(UploadcareFile uploadcareFile) 19 | { 20 | _sb.Append(Urls.CdnFile(uploadcareFile.Uuid)); 21 | } 22 | 23 | /// 24 | /// Creates a new CDN path builder for some image id. 25 | /// 26 | /// Image id to be used for the path 27 | public CdnPathBuilder(string fileId) 28 | { 29 | _sb.Append(Urls.CdnFile(fileId)); 30 | } 31 | 32 | /// 33 | /// Creates a new CDN path builder for some image id. 34 | /// 35 | /// CDN path of file 36 | public CdnPathBuilder(Uri cdnUri) 37 | { 38 | _sb.Append(cdnUri); 39 | } 40 | 41 | private static void DimensionGuard(int dim) 42 | { 43 | if (dim < 1 || dim > 1024) 44 | { 45 | throw new ArgumentException("Dimensions must be in the range 1-1024"); 46 | } 47 | } 48 | 49 | private static void DimensionsGuard(int width, int height) 50 | { 51 | DimensionGuard(width); 52 | DimensionGuard(height); 53 | 54 | if (width > 634 && height > 634) 55 | { 56 | throw new ArgumentException("At least one dimension must be less than 634"); 57 | } 58 | } 59 | 60 | private static string ColorToHex(Color color) 61 | { 62 | return (color.R.ToString("X2") + color.G.ToString("X2") + color.B.ToString("X2")).ToLower(); 63 | } 64 | 65 | /// 66 | /// Adds top-left-aligned crop. 67 | /// 68 | /// Crop width 69 | /// Crop height 70 | public CdnPathBuilder Crop(int width, int height) 71 | { 72 | DimensionsGuard(width, height); 73 | AppendTrailingSlash(); 74 | 75 | _sb.Append("-/crop/").Append(width).Append("x").Append(height); 76 | 77 | return this; 78 | } 79 | 80 | /// 81 | /// Adds center-aligned crop. 82 | /// 83 | /// Crop width 84 | /// Crop height 85 | public CdnPathBuilder CropCenter(int width, int height) 86 | { 87 | DimensionsGuard(width, height); 88 | AppendTrailingSlash(); 89 | 90 | _sb.Append("-/crop/").Append(width).Append("x").Append(height).Append("/center"); 91 | 92 | return this; 93 | } 94 | 95 | /// 96 | /// Adds top-left-aligned crop with a filled background. 97 | /// 98 | /// Crop width 99 | /// Crop height 100 | /// Background color 101 | public CdnPathBuilder CropColor(int width, int height, Color color) 102 | { 103 | DimensionsGuard(width, height); 104 | AppendTrailingSlash(); 105 | 106 | _sb.Append("-/crop/").Append(width).Append("x").Append(height).Append("/").Append(ColorToHex(color)); 107 | 108 | return this; 109 | } 110 | 111 | /// 112 | /// Adds center-aligned crop with a filled background. 113 | /// 114 | /// Crop width 115 | /// Crop height 116 | /// Background color 117 | public CdnPathBuilder CropCenterColor(int width, int height, Color color) 118 | { 119 | DimensionsGuard(width, height); 120 | AppendTrailingSlash(); 121 | 122 | _sb.Append("-/crop/").Append(width).Append("x").Append(height).Append("/center/").Append(ColorToHex(color)); 123 | 124 | return this; 125 | } 126 | 127 | /// 128 | /// Resizes width, keeping the aspect ratio. 129 | /// 130 | /// New width 131 | public CdnPathBuilder ResizeWidth(int width) 132 | { 133 | DimensionGuard(width); 134 | AppendTrailingSlash(); 135 | 136 | _sb.Append("-/resize/").Append(width).Append("x"); 137 | 138 | return this; 139 | } 140 | 141 | /// 142 | /// Resizes height, keeping the aspect ratio. 143 | /// 144 | /// New height 145 | public CdnPathBuilder ResizeHeight(int height) 146 | { 147 | DimensionGuard(height); 148 | AppendTrailingSlash(); 149 | 150 | _sb.Append("-/resize/x").Append(height); 151 | 152 | return this; 153 | } 154 | 155 | /// 156 | /// Resizes width and height 157 | /// 158 | /// New width 159 | /// New height 160 | public CdnPathBuilder Resize(int width, int height) 161 | { 162 | DimensionsGuard(width, height); 163 | AppendTrailingSlash(); 164 | 165 | _sb.Append("-/resize/").Append(width).Append("x").Append(height); 166 | 167 | return this; 168 | } 169 | 170 | /// 171 | /// Scales the image until one of the dimensions fits, 172 | /// then crops the bottom or right side. 173 | /// 174 | /// New width 175 | /// New height 176 | /// Use AI to find crop position 177 | public CdnPathBuilder ScaleCrop(int width, int height, bool smart = false) 178 | { 179 | DimensionsGuard(width, height); 180 | AppendTrailingSlash(); 181 | 182 | _sb.Append("-/scale_crop/").Append(width).Append("x").Append(height); 183 | 184 | if (smart) 185 | { 186 | _sb.Append("/smart/"); 187 | } 188 | 189 | return this; 190 | } 191 | 192 | /// 193 | /// Scales the image until one of the dimensions fits, 194 | /// centers it, then crops the rest. 195 | /// 196 | /// New width 197 | /// New height 198 | public CdnPathBuilder ScaleCropCenter(int width, int height) 199 | { 200 | DimensionsGuard(width, height); 201 | AppendTrailingSlash(); 202 | 203 | _sb.Append("-/scale_crop/").Append(width).Append("x").Append(height).Append("/center"); 204 | 205 | return this; 206 | } 207 | 208 | /// 209 | /// Flips the image. 210 | /// 211 | public CdnPathBuilder Flip() 212 | { 213 | AppendTrailingSlash(); 214 | 215 | _sb.Append("-/effect/flip"); 216 | 217 | return this; 218 | } 219 | 220 | /// 221 | /// Adds a grayscale effect. 222 | /// 223 | public CdnPathBuilder Grayscale() 224 | { 225 | AppendTrailingSlash(); 226 | 227 | _sb.Append("-/effect/grayscale"); 228 | 229 | return this; 230 | } 231 | 232 | /// 233 | /// Inverts colors. 234 | /// 235 | public CdnPathBuilder Invert() 236 | { 237 | AppendTrailingSlash(); 238 | 239 | _sb.Append("-/effect/invert"); 240 | 241 | return this; 242 | } 243 | 244 | /// 245 | /// Horizontally mirror image. 246 | /// 247 | public CdnPathBuilder Mirror() 248 | { 249 | AppendTrailingSlash(); 250 | 251 | _sb.Append("-/effect/mirror"); 252 | 253 | return this; 254 | } 255 | 256 | /// 257 | /// EXIF-based autorotate. 258 | /// 259 | public CdnPathBuilder Autorotate(bool yes = true) 260 | { 261 | AppendTrailingSlash(); 262 | 263 | _sb.Append(yes ? "-/autorotate/yes" : "-/autorotate/no"); 264 | 265 | return this; 266 | } 267 | 268 | /// 269 | /// Returns the current CDN path as a string. 270 | /// 271 | /// Avoid using directly. 272 | /// Instead, pass the configured builder to a URL factory. 273 | /// 274 | /// CDN path 275 | /// 276 | public string Build() 277 | { 278 | AppendTrailingSlash(); 279 | 280 | return _sb.ToString(); 281 | } 282 | 283 | private void AppendTrailingSlash() 284 | { 285 | if (_sb[_sb.Length - 1] != '/') 286 | { 287 | _sb.Append("/"); 288 | } 289 | } 290 | 291 | public static Uri Build(string fileId) 292 | { 293 | return Urls.CdnFile(fileId); 294 | } 295 | } 296 | } --------------------------------------------------------------------------------