diff --git a/README.md b/README.md index 1cde6c5..5945b99 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Install via Nuget ```csharp using (var png = new TinyPngClient("yourSecretApiKey")) { - await (await png.Compress("cat.jpg")).SaveImageToDisk("compressedCat.jpg"); + await png.Compress("cat.jpg"); } ``` @@ -39,31 +39,36 @@ using (var png = new TinyPngClient("yourSecretApiKey")) using (var png = new TinyPngClient("yourSecretApiKey")) { //compress an image - var compressResult = await png.Compress("pathToFile or byte array or stream"); + var result = await png.Compress("pathToFile or byte array or stream"); + + //this gives you the information about your image as stored by TinyPNG + //they don't give you the actual bits as you may want to chain this with a resize + //operation without caring for the originally sied image. For that, we need to: + var compressedImage = await result.Download(); //get the image data as a byte array - var bytes = await compressResult.GetImageByteData(); + var bytes = await compressedImage.GetImageByteData(); //get a stream instead - var stream = await compressResult.GetImageStreamData() + var stream = await compressedImage.GetImageStreamData() //or just save to disk - await compressResult.SaveImageToDisk("pathToSaveImage"); + await compressedImage.SaveImageToDisk("pathToSaveImage"); } ``` -Further details about the result of the compression are also available on the `Input` and `Output` properties. Some examples: +Further details about the result of the compression are also available on the `Input` and `Output` properties of a `Compress` operation. Some examples: ```csharp //old size - compressResult.Input.Size; + result.Input.Size; //new size - compressResult.Output.Size; + result.Output.Size; //URL of the compressed Image - compressResult.Output.Url; + result.Output.Url; ``` @@ -72,9 +77,9 @@ Further details about the result of the compression are also available on the `I ```csharp using (var png = new TinyPngClient("yourSecretApiKey")) { - var compressResult = await png.Compress("pathToFile or byte array or stream"); + var result = await png.Compress("pathToFile or byte array or stream"); - var resizedImage = await png.Resize(compressResult, width, height, ResizeType); + var resizedImage = await png.Resize(result, width, height, ResizeType); await resizedImage.SaveImageToDisk("pathToSaveImage"); } @@ -90,12 +95,12 @@ depending on the type of resize you want to do. ```csharp using (var png = new TinyPngClient("yourSecretApiKey")) { - var compressResult = await png.Compress("pathToFile or byte array or stream"); + var result = await png.Compress("pathToFile or byte array or stream"); - await png.Resize(compressResult, new ScaleWidthResizeOperation(width)); - await png.Resize(compressResult, new ScaleHeightResizeOperation(width)); - await png.Resize(compressResult, new FitResizeOperation(width, height)); - await png.Resize(compressResult, new CoverResizeOperation(width, height)); + await png.Resize(result, new ScaleWidthResizeOperation(width)); + await png.Resize(result, new ScaleHeightResizeOperation(width)); + await png.Resize(result, new FitResizeOperation(width, height)); + await png.Resize(result, new CoverResizeOperation(width, height)); } ``` @@ -104,7 +109,8 @@ The same `Byte[]`, `Stream` and `File` path API's are available from the result ## Amazon S3 Storage -The result of any compress operation can be stored directly on to Amazon S3 storage. There are two ways to configure this. +The result of any compress operation can be stored directly on to Amazon S3 storage. I'd strongly recommend referring to [TinyPNG.com's documentation](https://tinypng.com/developers/reference) with regard to how to configure +the appropriate S3 access. If you're going to be storing images for most requests onto S3, then you can pass in an `AmazonS3Configuration` object to the constructor. diff --git a/TinyPNG.v2.ncrunchsolution b/TinyPNG.v2.ncrunchsolution new file mode 100644 index 0000000..b98737f --- /dev/null +++ b/TinyPNG.v2.ncrunchsolution @@ -0,0 +1,14 @@ + + 1 + false + false + true + UseDynamicAnalysis + UseStaticAnalysis + UseStaticAnalysis + UseStaticAnalysis + UseDynamicAnalysis + + + + \ No newline at end of file diff --git a/src/TinyPNG/Extensions.cs b/src/TinyPNG/Extensions.cs index a82c458..c178d60 100644 --- a/src/TinyPNG/Extensions.cs +++ b/src/TinyPNG/Extensions.cs @@ -6,13 +6,12 @@ namespace TinyPng { public static class Extensions { - /// /// Get the image data as a byte array /// /// The result from compress /// Byte array of the image data - public async static Task GetImageByteData(this TinyPngResponse result) + public async static Task GetImageByteData(this TinyPngImageResponse result) { return await result.HttpResponseMessage.Content.ReadAsByteArrayAsync(); } @@ -22,7 +21,7 @@ public async static Task GetImageByteData(this TinyPngResponse result) /// /// The result from compress /// Stream of compressed image data - public async static Task GetImageStreamData(this TinyPngResponse result) + public async static Task GetImageStreamData(this TinyPngImageResponse result) { return await result.HttpResponseMessage.Content.ReadAsStreamAsync(); } @@ -33,7 +32,7 @@ public async static Task GetImageStreamData(this TinyPngResponse result) /// The result from compress /// The path to store the file /// - public async static Task SaveImageToDisk(this TinyPngResponse result, string filePath) + public async static Task SaveImageToDisk(this TinyPngImageResponse result, string filePath) { var byteData = await result.GetImageByteData(); File.WriteAllBytes(filePath, byteData); diff --git a/src/TinyPNG/Responses/TinyPngCompressResponse.cs b/src/TinyPNG/Responses/TinyPngCompressResponse.cs index fdda53b..d2ff429 100644 --- a/src/TinyPNG/Responses/TinyPngCompressResponse.cs +++ b/src/TinyPNG/Responses/TinyPngCompressResponse.cs @@ -10,6 +10,7 @@ public class TinyPngCompressResponse : TinyPngResponse public TinyPngApiInput Input { get; private set; } public TinyPngApiOutput Output { get; private set; } public TinyPngApiResult ApiResult { get; private set; } + private readonly JsonSerializerSettings jsonSettings; public TinyPngCompressResponse(HttpResponseMessage msg) : base(msg) diff --git a/src/TinyPNG/Responses/TinyPngImageResponse.cs b/src/TinyPNG/Responses/TinyPngImageResponse.cs new file mode 100644 index 0000000..af5ebef --- /dev/null +++ b/src/TinyPNG/Responses/TinyPngImageResponse.cs @@ -0,0 +1,14 @@ +using System.Net.Http; + +namespace TinyPng.Responses +{ + /// + /// This is a response which contains actual image data + /// + public class TinyPngImageResponse : TinyPngResponse + { + public TinyPngImageResponse(HttpResponseMessage msg) : base(msg) + { + } + } +} diff --git a/src/TinyPNG/Responses/TinyPngResizeResponse.cs b/src/TinyPNG/Responses/TinyPngResizeResponse.cs index 34a2542..b8ad868 100644 --- a/src/TinyPNG/Responses/TinyPngResizeResponse.cs +++ b/src/TinyPNG/Responses/TinyPngResizeResponse.cs @@ -2,7 +2,7 @@ namespace TinyPng.Responses { - public class TinyPngResizeResponse : TinyPngResponse + public class TinyPngResizeResponse : TinyPngImageResponse { public TinyPngResizeResponse(HttpResponseMessage msg) : base(msg) { diff --git a/src/TinyPNG/Responses/TinyPngResponse.cs b/src/TinyPNG/Responses/TinyPngResponse.cs index e886c3e..4e4fa86 100644 --- a/src/TinyPNG/Responses/TinyPngResponse.cs +++ b/src/TinyPNG/Responses/TinyPngResponse.cs @@ -6,17 +6,11 @@ namespace TinyPng.Responses { public class TinyPngResponse { - public HttpResponseMessage HttpResponseMessage { get; private set; } + public HttpResponseMessage HttpResponseMessage { get; } private int compressionCount; - public int CompressionCount - { - get - { - return compressionCount; - } - } + public int CompressionCount => compressionCount; protected TinyPngResponse(HttpResponseMessage msg) { diff --git a/src/TinyPNG/TinyPng.cs b/src/TinyPNG/TinyPng.cs index b90dc92..8f81252 100644 --- a/src/TinyPNG/TinyPng.cs +++ b/src/TinyPNG/TinyPng.cs @@ -14,7 +14,8 @@ public class TinyPngClient : IDisposable { private readonly string _apiKey; private const string ApiEndpoint = "https://api.tinify.com/shrink"; - private HttpClient httpClient = new HttpClient(); + + public HttpClient httpClient = new HttpClient(); private readonly JsonSerializerSettings jsonSettings; /// @@ -125,6 +126,22 @@ public async Task Compress(Stream data) var errorMsg = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); throw new TinyPngApiException((int)response.StatusCode, response.ReasonPhrase, errorMsg.Error, errorMsg.Message); } + public async Task Download(TinyPngCompressResponse result) + { + if (result == null) + throw new ArgumentNullException(nameof(result)); + + var msg = new HttpRequestMessage(HttpMethod.Get, result.Output.Url); + + var response = await httpClient.SendAsync(msg); + if (response.IsSuccessStatusCode) + { + return new TinyPngImageResponse(response); + } + + var errorMsg = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + throw new TinyPngApiException((int)response.StatusCode, response.ReasonPhrase, errorMsg.Error, errorMsg.Message); + } /// @@ -142,7 +159,6 @@ public async Task Resize(TinyPngCompressResponse result, var requestBody = JsonConvert.SerializeObject(new { resize = resizeOperation }, jsonSettings); - var msg = new HttpRequestMessage(HttpMethod.Post, result.Output.Url); msg.Content = new StringContent(requestBody, System.Text.Encoding.UTF8, "application/json"); @@ -217,8 +233,9 @@ public async Task SaveCompressedImageToAmazonS3(TinyPngCompressResponse res /// Stores a previously compressed image directly into Amazon S3 storage /// /// The previously compressed image - /// The path and bucket to store in: bucket/file.png format - /// Optional: To override the previosly configured region + /// The path to storage the image as + /// Optional: To override the previously configured bucket + /// Optional: To override the previously configured region /// public async Task SaveCompressedImageToAmazonS3(TinyPngCompressResponse result, string path, string bucketOverride = "", string regionOverride = "") { diff --git a/src/TinyPNG/project.json b/src/TinyPNG/project.json index c526b68..a8a1af3 100644 --- a/src/TinyPNG/project.json +++ b/src/TinyPNG/project.json @@ -6,7 +6,7 @@ "copyright": "Copyright 2016 Chad Tolkien", "packOptions": { "iconUrl": "https://raw.githubusercontent.com/ctolkien/TinyPNG/master/icon.png", - "releaseNotes": "Support for .NET Core 1.0", + "releaseNotes": "Major refactor of the API", "summary": "This is a .NET wrapper around the TinyPng.com image compression service.", "description": "This is a .NET wrapper around the TinyPng.com image compression service. Supports .Net Core and full .Net Framework. Non-blocking async turtles all the way down. Byte[], Stream and File API's available.", "repository": { @@ -49,5 +49,5 @@ } } }, - "version": "1.3.0-*" + "version": "2.0.0-*" } diff --git a/tests/TinyPng.Tests/FakeResponseHandler.cs b/tests/TinyPng.Tests/FakeResponseHandler.cs new file mode 100644 index 0000000..98ddec2 --- /dev/null +++ b/tests/TinyPng.Tests/FakeResponseHandler.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace TinyPng.Tests +{ + public class FakeResponseHandler : DelegatingHandler + { + private readonly Dictionary _FakeGetResponses = new Dictionary(); + private readonly Dictionary _FakePostResponses = new Dictionary(); + + + public void AddFakeGetResponse(Uri uri, HttpResponseMessage responseMessage) + { + _FakeGetResponses.Add(uri, responseMessage); + } + public void AddFakePostResponse(Uri uri, HttpResponseMessage responseMessage) + { + _FakePostResponses.Add(uri, responseMessage); + } + + protected async override Task SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) + { + if (request.Method == HttpMethod.Get && _FakeGetResponses.ContainsKey(request.RequestUri)) + { + return _FakeGetResponses[request.RequestUri]; + } + if (request.Method == HttpMethod.Post &&_FakePostResponses.ContainsKey(request.RequestUri)) + { + return _FakePostResponses[request.RequestUri]; + } + else + { + return new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request }; + } + + } + } +} diff --git a/tests/TinyPng.Tests/Resources/cat.jpg b/tests/TinyPng.Tests/Resources/cat.jpg index 8dea0c5..b35300f 100644 Binary files a/tests/TinyPng.Tests/Resources/cat.jpg and b/tests/TinyPng.Tests/Resources/cat.jpg differ diff --git a/tests/TinyPng.Tests/Resources/compressedcat.jpg b/tests/TinyPng.Tests/Resources/compressedcat.jpg new file mode 100644 index 0000000..ee75612 Binary files /dev/null and b/tests/TinyPng.Tests/Resources/compressedcat.jpg differ diff --git a/tests/TinyPng.Tests/Resources/resizedcat.jpg b/tests/TinyPng.Tests/Resources/resizedcat.jpg new file mode 100644 index 0000000..64d603c Binary files /dev/null and b/tests/TinyPng.Tests/Resources/resizedcat.jpg differ diff --git a/tests/TinyPng.Tests/TinyPngTests.cs b/tests/TinyPng.Tests/TinyPngTests.cs index 464fef5..8ac0054 100644 --- a/tests/TinyPng.Tests/TinyPngTests.cs +++ b/tests/TinyPng.Tests/TinyPngTests.cs @@ -1,48 +1,148 @@ -using System; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Net.Http; using System.Threading.Tasks; using Xunit; namespace TinyPng.Tests { + static class Extensions + { + public static FakeResponseHandler Compress(this FakeResponseHandler fakeResponse) + { + + var content = new TinyPngApiResult(); + content.Input = new TinyPngApiInput + { + Size = 18031, + Type = "image/jpeg" + }; + content.Output = new TinyPngApiOutput + { + Width = 400, + Height = 400, + Size = 16646, + Type = "image/jpeg", + Ratio = 0.9232f, + Url = "https://api.tinify.com/output" + }; + + var compressResponseMessage = new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.Created, + Content = new StringContent(JsonConvert.SerializeObject(content)), + }; + compressResponseMessage.Headers.Location = new Uri("https://api.tinify.com/output"); + compressResponseMessage.Headers.Add("Compression-Count", "99"); + + fakeResponse.AddFakePostResponse(new Uri("https://api.tinify.com/shrink"), compressResponseMessage); + return fakeResponse; + } + public static FakeResponseHandler Download(this FakeResponseHandler fakeResponse) + { + var compressedCatStream = File.OpenRead(TinyPngTests.CompressedCat); + var outputResponseMessage = new HttpResponseMessage + { + Content = new StreamContent(compressedCatStream), + StatusCode = System.Net.HttpStatusCode.OK + }; + + fakeResponse.AddFakeGetResponse(new Uri("https://api.tinify.com/output"), outputResponseMessage); + return fakeResponse; + } + + public static FakeResponseHandler Resize(this FakeResponseHandler fakeResponse) + { + var resizedCatStream = File.OpenRead(TinyPngTests.ResizedCat); + var resizeMessage = new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.OK, + Content = new StreamContent(resizedCatStream) + }; + resizeMessage.Headers.Add("Image-Width", "150"); + resizeMessage.Headers.Add("Image-Height", "150"); + + fakeResponse.AddFakePostResponse(new Uri("https://api.tinify.com/output"), resizeMessage); + return fakeResponse; + } + + public static FakeResponseHandler S3(this FakeResponseHandler fakeResponse) + { + var amazonMessage = new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.OK + }; + amazonMessage.Headers.Add("Location", "https://s3-ap-southeast-2.amazonaws.com/tinypng-test-bucket/path.jpg"); + + fakeResponse.AddFakePostResponse(new Uri("https://api.tinify.com/output"), amazonMessage); + return fakeResponse; + } + } + public class TinyPngTests { const string apiKey = "lolwat"; - const string Cat = "Resources/cat.jpg"; + internal const string Cat = "Resources/cat.jpg"; + internal const string CompressedCat = "Resources/compressedcat.jpg"; + internal const string ResizedCat = "Resources/resizedcat.jpg"; - [Fact(Skip = "Integration")] + [Fact] public async Task Compression() { var pngx = new TinyPngClient(apiKey); + pngx.httpClient = new HttpClient(new FakeResponseHandler().Compress()); var result = await pngx.Compress(Cat); Assert.Equal("image/jpeg", result.Input.Type); + Assert.Equal(400, result.Output.Width); + Assert.Equal(400, result.Output.Height); + + } - Assert.Equal(300, result.Output.Width); + [Fact] + public async Task CompressionAndDownload() + { + var pngx = new TinyPngClient(apiKey); + pngx.httpClient = new HttpClient(new FakeResponseHandler() + .Compress() + .Download()); - Assert.Equal(182, (await result.GetImageByteData()).Length); + var result = await pngx.Compress(Cat); + + var downloadResult = await pngx.Download(result); + + Assert.Equal(16646, (await downloadResult.GetImageByteData()).Length); } - [Fact(Skip = "Integration")] + [Fact] public async Task Resizing() { var pngx = new TinyPngClient(apiKey); + pngx.httpClient = new HttpClient(new FakeResponseHandler() + .Compress() + .Resize()); var result = await pngx.Compress(Cat); - var resized = await pngx.Resize(result, new ScaleHeightResizeOperation(100)); + var resized = await pngx.Resize(result, new ScaleHeightResizeOperation(150)); - Assert.Equal(7111, (await resized.GetImageByteData()).Length); + var resizedImageByteData = await resized.GetImageByteData(); - } + Assert.Equal(5970, resizedImageByteData.Length); + } - [Fact(Skip = "Integration")] + [Fact] public async Task CompressAndStoreToS3ShouldThrowIfS3HasNotBeenConfigured() { var pngx = new TinyPngClient(apiKey); + pngx.httpClient = new HttpClient(new FakeResponseHandler() + .Compress() + .S3()); var result = await pngx.Compress(Cat); @@ -53,10 +153,13 @@ public async Task CompressAndStoreToS3ShouldThrowIfS3HasNotBeenConfigured() private const string ApiKey = "lolwat"; private const string ApiAccessKey = "lolwat"; - [Fact(Skip = "Integration")] + [Fact] public async Task CompressAndStoreToS3() { var pngx = new TinyPngClient(apiKey); + pngx.httpClient = new HttpClient(new FakeResponseHandler() + .Compress() + .S3()); var result = await pngx.Compress(Cat);