diff --git a/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj b/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj
index 4c301eb..c3774cc 100644
--- a/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj
+++ b/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj
@@ -4,15 +4,17 @@
netstandard2.0
PaderbornUniversity.SILab.Hip.ThumbnailService
1701;1702;1705;1591
- 1.0.0
+ 1.2.0
$(Version)-$(VersionSuffix)
True
-
+
+
+
-
+
diff --git a/HiP-ThumbnailService.Sdk/ThumbnailConfig.cs b/HiP-ThumbnailService.Sdk/ThumbnailConfig.cs
new file mode 100644
index 0000000..598bb6b
--- /dev/null
+++ b/HiP-ThumbnailService.Sdk/ThumbnailConfig.cs
@@ -0,0 +1,21 @@
+namespace PaderbornUniversity.SILab.Hip.ThumbnailService
+{
+ ///
+ /// Configuration properties for clients using the thumbnail service.
+ ///
+ public sealed class ThumbnailConfig
+ {
+ ///
+ /// URL pointing to a running instance of the thumbnail service.
+ /// Example: "https://docker-hip.cs.upb.de/develop/thumbnailservice"
+ ///
+ public string ThumbnailServiceHost { get; set; }
+
+ ///
+ /// Relative URL pattern for generating thumbnail URLs. Should contain one or more placeholders
+ /// "{0}", "{1}" etc. that are replaced with the ID(s) of the requested image at runtime.
+ /// Example: "datastore/api/Media/{0}/File"
+ ///
+ public string ThumbnailUrlPattern { get; set; }
+ }
+}
diff --git a/HiP-ThumbnailService.Sdk/ThumbnailService.cs b/HiP-ThumbnailService.Sdk/ThumbnailService.cs
new file mode 100644
index 0000000..5c1b46a
--- /dev/null
+++ b/HiP-ThumbnailService.Sdk/ThumbnailService.cs
@@ -0,0 +1,91 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System;
+using System.Threading.Tasks;
+
+namespace PaderbornUniversity.SILab.Hip.ThumbnailService
+{
+ ///
+ /// A service that can be used with ASP.NET Core dependency injection.
+ /// Usage: In ConfigureServices():
+ ///
+ /// services.Configure<ThumbnailConfig>(Configuration.GetSection("Thumbnails"));
+ /// services.AddSingleton<ThumbnailService>();
+ ///
+ ///
+ public class ThumbnailService
+ {
+ private readonly ThumbnailConfig _config;
+ private readonly ILogger _logger;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public ThumbnailsClient ThumbnailsClient => new ThumbnailsClient(_config.ThumbnailServiceHost)
+ {
+ Authorization = _httpContextAccessor.HttpContext?.Request.Headers["Authorization"]
+ };
+
+ public ThumbnailService(IOptions config, ILogger logger,
+ IHttpContextAccessor httpContextAccessor)
+ {
+ _config = config.Value;
+ _logger = logger;
+ _httpContextAccessor = httpContextAccessor;
+
+ if (string.IsNullOrWhiteSpace(config.Value.ThumbnailServiceHost))
+ logger.LogWarning($"{nameof(ThumbnailConfig.ThumbnailServiceHost)} is not configured correctly!");
+
+ if (string.IsNullOrWhiteSpace(config.Value.ThumbnailUrlPattern))
+ logger.LogWarning($"{nameof(ThumbnailConfig.ThumbnailUrlPattern)} is not configured correctly!");
+ }
+
+ ///
+ /// Constructs an absolute URL that, when accessed, returns an image from the thumbnail service.
+ ///
+ /// Example: Given
+ /// ThumbnailServiceHost = "https://docker-hip.cs.upb.de/develop/thumbnailservice" and
+ /// ThumbnailUrlPattern = "datastore/api/Media/{0}/File",
+ /// => GetThumbnailUrl(42) = "https://docker-hip.cs.upb.de/develop/thumbnailservice?Url=datastore/api/Media/42/File"
+ ///
+ /// Arguments replacing the placeholders in
+ public string GetThumbnailUrl(params object[] args) =>
+ $"{_config.ThumbnailServiceHost}/api/Thumbnails?Url={GetThumbnailUrlArgument(args)}";
+
+ ///
+ /// Constructs the relative URL that is used to request thumbnails.
+ ///
+ /// Arguments replacing the placeholders in
+ public string GetThumbnailUrlArgument(params object[] args) =>
+ string.Format(_config.ThumbnailUrlPattern ?? "", args);
+
+ ///
+ /// Tries to delete all cached thumbnails of an image in the thumbnail service.
+ /// Exceptions are catched and logged as warning.
+ ///
+ /// Arguments replacing the placeholders in
+ public async Task TryClearThumbnailCacheAsync(params object[] args)
+ {
+ if (string.IsNullOrWhiteSpace(_config.ThumbnailUrlPattern) ||
+ string.IsNullOrWhiteSpace(_config.ThumbnailServiceHost))
+ {
+ return false;
+ }
+
+ var urlArgument = GetThumbnailUrlArgument(args);
+
+ try
+ {
+ await ThumbnailsClient.DeleteAsync(urlArgument);
+ return true;
+ }
+ catch (Exception e)
+ {
+ _logger.LogWarning(e,
+ $"Request to clear thumbnail cache failed for relative URL '{urlArgument}'; " +
+ $"thumbnail service might return outdated images.");
+
+ return false;
+ }
+ }
+ }
+}
diff --git a/HiP-ThumbnailService/Arguments/CreationArgs.cs b/HiP-ThumbnailService/Arguments/CreationArgs.cs
index edcc632..39a4a3f 100644
--- a/HiP-ThumbnailService/Arguments/CreationArgs.cs
+++ b/HiP-ThumbnailService/Arguments/CreationArgs.cs
@@ -1,71 +1,36 @@
-using System;
-using System.ComponentModel;
+using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
-using Newtonsoft.Json;
namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Arguments
{
public class CreationArgs
{
+ ///
+ /// URL from where the thumbnail service can retrieve the original image.
+ /// This URL must be relative to 'HostUrl' configured in the thumbnail service.
+ /// Example: "datastore/Media/42/File"
+ /// (with 'HostUrl' configured as "https://docker-hip.cs.upb.de/develop/" for example)
+ ///
[Required]
public string Url { get; set; }
+ ///
+ /// One of the preconfigured size options, e.g. "small". If null or empty, the image is
+ /// returned in its original size without cropping or resizing being applied.
+ ///
public string Size { get; set; }
///
- /// Specifies crop mode that is used. If no mode is specified is used
+ /// Image cropping mode that is used. Defaults to .
///
- [JsonIgnore]
- public CropMode Mode
- {
-#pragma warning disable 0618
- get => InternalMode ?? CropMode.FillSquare;
- set => InternalMode = value;
-#pragma warning restore 0618
- }
-
- /// Property 'InternalMode' is listed by NSwag, but not by Swashbuckle (due to 'internal').
- /// Property 'Mode' is listed by Swashbuckle, but not by NSwag (due to [JsonIgnore]).
- ///
- /// We need a NULLABLE status parameter for NSwag because:
- /// 1) The status parameter shouldn't be required (clients shouldn't need to pass it, it defaults to FillSquare)
- /// 2) Since status is not required, the NSwag-generated C# client has "CropMode? mode = null" in the
- /// method signature, however if it weren't nullable here, the client would throw an exception if 'mode == null'.
- /// This is weird: The method signature states that status can be null, but passing null throws an exception.
- ///
- /// Why don't we make Mode nullable in general? We don't want the rest of the codebase to have to distinguish
- /// between 'Mode == null' and 'Mode == FillSquare'.
- [JsonProperty("mode")]
[DefaultValue(CropMode.FillSquare)]
- [Obsolete("For internal use only. Use 'Mode' instead.")]
- internal CropMode? InternalMode { get; set; }
+ public CropMode Mode { get; set; } = CropMode.FillSquare;
///
- /// Specifies image format that the resulting thumbnail has. If no format is specified is used
+ /// The desired image format of the resulting thumbnail.
+ /// Defaults to .
///
- [JsonIgnore]
- public RequestedImageFormat Format
- {
-#pragma warning disable 0618
- get => InternalFormat ?? RequestedImageFormat.Jpeg;
- set => InternalFormat = value;
-#pragma warning restore 0618
- }
-
- /// Property 'InternalFormat' is listed by NSwag, but not by Swashbuckle (due to 'internal').
- /// Property 'Format' is listed by Swashbuckle, but not by NSwag (due to [JsonIgnore]).
- ///
- /// We need a NULLABLE status parameter for NSwag because:
- /// 1) The status parameter shouldn't be required (clients shouldn't need to pass it, it defaults to Jpeg)
- /// 2) Since status is not required, the NSwag-generated C# client has "RequestedImageFormat? format = null" in the
- /// method signature, however if it weren't nullable here, the client would throw an exception if 'format == null'.
- /// This is weird: The method signature states that status can be null, but passing null throws an exception.
- ///
- /// Why don't we make Format nullable in general? We don't want the rest of the codebase to have to distinguish
- /// between 'Format == null' and 'Format == Jpeg'.
- [JsonProperty("format")]
[DefaultValue(RequestedImageFormat.Jpeg)]
- [Obsolete("For internal use only. Use 'Format' instead.")]
- internal RequestedImageFormat? InternalFormat { get; set; }
+ public RequestedImageFormat Format { get; set; } = RequestedImageFormat.Jpeg;
}
}
diff --git a/HiP-ThumbnailService/Arguments/CropMode.cs b/HiP-ThumbnailService/Arguments/CropMode.cs
index 6867950..f5c27f6 100644
--- a/HiP-ThumbnailService/Arguments/CropMode.cs
+++ b/HiP-ThumbnailService/Arguments/CropMode.cs
@@ -2,7 +2,7 @@
{
///
/// Describes the different modes that can be used for cropping.
- /// crops at the center of the image resulting in a square
+ /// crops at the center of the image resulting in a square.
/// keeps the aspect ratio while cropping the longer side to the specified value.
///
public enum CropMode
diff --git a/HiP-ThumbnailService/Controllers/ThumbnailsController.cs b/HiP-ThumbnailService/Controllers/ThumbnailsController.cs
index 7e6a228..d5fb005 100644
--- a/HiP-ThumbnailService/Controllers/ThumbnailsController.cs
+++ b/HiP-ThumbnailService/Controllers/ThumbnailsController.cs
@@ -1,19 +1,21 @@
-using System;
-using System.Collections.Concurrent;
-using System.IO;
-using System.Linq;
-using System.Net.Http;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using PaderbornUniversity.SILab.Hip.ThumbnailService.Arguments;
using PaderbornUniversity.SILab.Hip.ThumbnailService.Utility;
using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Transforms;
using SixLabors.Primitives;
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Controllers
{
@@ -23,9 +25,7 @@ public class ThumbnailsController : Controller
{
private readonly ThumbnailConfig _thumbnailConfig;
- ///
- /// This dictionary is used for synchronizaing requests for the same id
- ///
+ // This dictionary is used for synchronizing requests for the same id
private static readonly ConcurrentDictionary LockDictionary = new ConcurrentDictionary();
public ThumbnailsController(IOptions thumbnailConfig)
@@ -33,6 +33,10 @@ public ThumbnailsController(IOptions thumbnailConfig)
_thumbnailConfig = thumbnailConfig.Value;
}
+ ///
+ /// Clears the thumbnail cache for the specified URL.
+ ///
+ /// URL relative to 'HostUrl' configured in the thumbnail service.
[ProducesResponseType(400)]
[ProducesResponseType(204)]
[HttpDelete]
@@ -41,16 +45,14 @@ public async Task Delete(string url)
if (!ModelState.IsValid)
return BadRequest(ModelState);
-
var encodedId = Convert.ToBase64String(Encoding.UTF8.GetBytes(url));
var folderPath = Path.Combine(_thumbnailConfig.Path, encodedId);
-
var semaphore = LockDictionary.GetOrAdd(encodedId, new SemaphoreSlim(1));
await semaphore.WaitAsync();
try
{
- //delete the folder
+ // Delete the folder
if (Directory.Exists(folderPath))
Directory.Delete(folderPath, true);
return NoContent();
@@ -68,22 +70,20 @@ public async Task Delete(string url)
///
[ProducesResponseType(404)]
[ProducesResponseType(400)]
- [ProducesResponseType(typeof(FileStream),200)]
+ [ProducesResponseType(typeof(FileStream), 200)]
[HttpGet]
public async Task Get([FromQuery]CreationArgs args)
{
+ if (!string.IsNullOrEmpty(args.Size) && !_thumbnailConfig.SupportedSizes.ContainsKey(args.Size))
+ ModelState.AddModelError(nameof(args.Size), "Invalid size. Must be one of the following: " +
+ string.Join(", ", _thumbnailConfig.SupportedSizes));
+
if (!ModelState.IsValid)
return BadRequest(ModelState);
- if (string.IsNullOrEmpty(args.Url))
- return BadRequest(new { Message = "An url for image access must be provided" });
-
var encodedId = Convert.ToBase64String(Encoding.UTF8.GetBytes(args.Url));
var folderPath = Path.Combine(_thumbnailConfig.Path, encodedId);
- if (!string.IsNullOrEmpty(args.Size) && !_thumbnailConfig.SupportedSizes.ContainsKey(args.Size))
- return BadRequest(new { Message = "Invalid size" });
-
var semaphore = LockDictionary.GetOrAdd(encodedId, new SemaphoreSlim(1));
await semaphore.WaitAsync();
try
@@ -95,16 +95,15 @@ public async Task Get([FromQuery]CreationArgs args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", Request.Headers["Authorization"].ToString());
- var hostUrl = _thumbnailConfig.HostUrl;
- using (var stream = await client.GetStreamAsync(hostUrl + args.Url))
- {
- //Create Directory if it doesn't exist
+ using (var stream = await client.GetStreamAsync(_thumbnailConfig.HostUrl + args.Url))
+ {
+ // Create directory if it doesn't exist
Directory.CreateDirectory(folderPath);
if (string.IsNullOrEmpty(args.Size))
{
- //The original image should be returned if the size is empty
+ // The original image should be returned if the size is empty
SaveImage(stream, filePath);
}
else
@@ -113,7 +112,6 @@ public async Task Get([FromQuery]CreationArgs args)
GenerateThumbnail(args.Mode, stream, filePath, value);
}
}
-
}
return File(new FileStream(filePath, FileMode.Open), requestedImageFormat.DefaultMimeType,
@@ -132,19 +130,19 @@ public async Task Get([FromQuery]CreationArgs args)
{
semaphore.Release();
}
-
}
///
- /// Generates a new thumbnail which replaces the old thumbnail
+ /// Generates a new thumbnail which replaces the old thumbnail.
///
/// Crop mode
/// File stream
/// Path to thumbnail
- /// Size value
- private static void GenerateThumbnail(CropMode mode, Stream fileStream, string thumbnailPath, int value)
+ /// Size value
+ private static void GenerateThumbnail(CropMode mode, Stream fileStream, string thumbnailPath, int size)
{
- ResizeMode resizeMode = ResizeMode.Crop;
+ var resizeMode = ResizeMode.Crop;
+
switch (mode)
{
case CropMode.FillSquare:
@@ -156,8 +154,7 @@ private static void GenerateThumbnail(CropMode mode, Stream fileStream, string t
}
SaveImage(fileStream, thumbnailPath,
- c => c.Resize(new ResizeOptions { Mode = resizeMode, Size = new Size(value) }));
-
+ c => c.Resize(new ResizeOptions { Mode = resizeMode, Size = new Size(size) }));
}
private static void SaveImage(Stream fileStream, string filePath, Action> operation = null)
@@ -171,8 +168,7 @@ private static void SaveImage(Stream fileStream, string filePath, Action string.IsNullOrEmpty(size) ? "originalImage" : $"{size}({mode})";
-
+ private static string GetFileName(string size, CropMode mode) =>
+ string.IsNullOrEmpty(size) ? "originalImage" : $"{size}({mode})";
}
-
}
diff --git a/HiP-ThumbnailService/HiP-ThumbnailService.csproj b/HiP-ThumbnailService/HiP-ThumbnailService.csproj
index b6abf40..fda25ae 100644
--- a/HiP-ThumbnailService/HiP-ThumbnailService.csproj
+++ b/HiP-ThumbnailService/HiP-ThumbnailService.csproj
@@ -2,7 +2,7 @@
true
- netcoreapp2.0
+ netcoreapp2.1
PaderbornUniversity.SILab.Hip.ThumbnailService
1701;1702;1705;1591
true
@@ -13,10 +13,10 @@
-
-
-
-
+
+
+
+
@@ -26,7 +26,7 @@
-
-
+
+
diff --git a/HiP-ThumbnailService/Startup.cs b/HiP-ThumbnailService/Startup.cs
index 22a9935..683ba1c 100644
--- a/HiP-ThumbnailService/Startup.cs
+++ b/HiP-ThumbnailService/Startup.cs
@@ -1,21 +1,20 @@
-using Microsoft.AspNetCore.Authentication.JwtBearer;
+using System;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using NSwag.AspNetCore;
using PaderbornUniversity.SILab.Hip.ThumbnailService.Utility;
using PaderbornUniversity.SILab.Hip.Webservice;
-using Swashbuckle.AspNetCore.Swagger;
-using System.IO;
+using PaderbornUniversity.SILab.Hip.Webservice.Logging;
namespace PaderbornUniversity.SILab.Hip.ThumbnailService
{
public class Startup
{
- private const string Name = "HiP Thumbnail Service";
- private const string Version = "v1";
-
public Startup(IConfiguration configuration)
{
Configuration = configuration;
@@ -26,20 +25,11 @@ public Startup(IConfiguration configuration)
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
- // Register the Swagger generator
- services.AddSwaggerGen(c =>
- {
- // Define a Swagger document
- c.SwaggerDoc("v1", new Info { Title = Name, Version = Version });
- c.OperationFilter();
- c.DescribeAllEnumsAsStrings();
- c.IncludeXmlComments(Path.ChangeExtension(typeof(Startup).Assembly.Location, ".xml"));
- });
-
- services.Configure(Configuration.GetSection("EndpointConfig"))
- .Configure(Configuration.GetSection("ThumbnailConfig"))
+ services
+ .Configure(Configuration.GetSection("Thumbnails"))
.Configure(Configuration.GetSection("Auth"))
- .Configure(Configuration);
+ .Configure(Configuration)
+ .Configure(Configuration.GetSection("HiPLoggerConfig"));
var serviceProvider = services.BuildServiceProvider(); // allows us to actually get the configured services
var authConfig = serviceProvider.GetService>();
@@ -65,43 +55,45 @@ public void ConfigureServices(IServiceCollection services)
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions endpointConfig, IOptions corsConfig)
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory,
+ IOptions corsConfig, IOptions loggingConfig)
{
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
- }
-
- // Use CORS (important: must be before app.UseMvc())
- app.UseCors(builder =>
- {
- var corsEnvConf = corsConfig.Value.Cors[env.EnvironmentName];
- builder
- .WithOrigins(corsEnvConf.Origins)
- .WithMethods(corsEnvConf.Methods)
- .WithHeaders(corsEnvConf.Headers)
- .WithExposedHeaders(corsEnvConf.ExposedHeaders);
- });
-
- app.UseAuthentication();
- app.UseMvc();
+ loggerFactory.AddConsole(Configuration.GetSection("Logging"))
+ .AddDebug()
+ .AddHipLogger(loggingConfig.Value);
- // Swagger / Swashbuckle configuration:
- // Enable middleware to serve generated Swagger as a JSON endpoint
- app.UseSwagger(c =>
+ var logger = loggerFactory.CreateLogger("ApplicationStartup");
+ try
{
- c.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.Host = httpReq.Host.Value);
- });
-
- // Configure SwaggerUI endpoint
- app.UseSwaggerUI(c =>
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.UseRequestSchemeFixer();
+
+ // Use CORS (important: must be before app.UseMvc())
+ app.UseCors(builder =>
+ {
+ var corsEnvConf = corsConfig.Value.Cors[env.EnvironmentName];
+ builder
+ .WithOrigins(corsEnvConf.Origins)
+ .WithMethods(corsEnvConf.Methods)
+ .WithHeaders(corsEnvConf.Headers)
+ .WithExposedHeaders(corsEnvConf.ExposedHeaders);
+ });
+
+ app.UseAuthentication();
+ app.UseMvc();
+ app.UseSwaggerUiHip();
+
+ logger.LogInformation("ThumbnailService started successfully");
+ }
+ catch (Exception e)
{
- var swaggerJsonUrl = string.IsNullOrEmpty(endpointConfig.Value.SwaggerEndpoint)
- ? $"/swagger/{Version}/swagger.json"
- : endpointConfig.Value.SwaggerEndpoint;
-
- c.SwaggerEndpoint(swaggerJsonUrl, $"{Name} {Version}");
- });
+ logger.LogCritical($"ThumbnailService Startup Failed:{e.Message}");
+ throw;
+ }
}
}
diff --git a/HiP-ThumbnailService/Utility/AuthConfig.cs b/HiP-ThumbnailService/Utility/AuthConfig.cs
deleted file mode 100644
index b9b6dde..0000000
--- a/HiP-ThumbnailService/Utility/AuthConfig.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Utility
-{
- public class AuthConfig
- {
- public string Audience { get; set; }
- public string Authority { get; set; }
- }
-}
diff --git a/HiP-ThumbnailService/Utility/CorsConfig.cs b/HiP-ThumbnailService/Utility/CorsConfig.cs
deleted file mode 100644
index 6465e1b..0000000
--- a/HiP-ThumbnailService/Utility/CorsConfig.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Collections.Generic;
-
-namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Utility
-{
- public class CorsConfig
- {
- public Dictionary Cors { get; set; }
- }
- public class CorsEnvironmentConfig
- {
- public string[] Origins { get; set; }
- public string[] Headers { get; set; }
- public string[] Methods { get; set; }
- public string[] ExposedHeaders { get; set; }
- }
-}
diff --git a/HiP-ThumbnailService/Utility/EndpointConfig.cs b/HiP-ThumbnailService/Utility/EndpointConfig.cs
deleted file mode 100644
index 9ab0aff..0000000
--- a/HiP-ThumbnailService/Utility/EndpointConfig.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Utility
-{
- public class EndpointConfig
- {
- public string SwaggerEndpoint { get; set; }
- }
-}
diff --git a/HiP-ThumbnailService/Utility/ThumbnailConfig.cs b/HiP-ThumbnailService/Utility/ThumbnailConfig.cs
index b5cd337..b19ff25 100644
--- a/HiP-ThumbnailService/Utility/ThumbnailConfig.cs
+++ b/HiP-ThumbnailService/Utility/ThumbnailConfig.cs
@@ -5,18 +5,23 @@ namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Utility
public class ThumbnailConfig
{
///
- /// Path to thumbnail images
+ /// Path where thumbnail images are stored.
+ /// Default value: "Thumbnails"
///
- public string Path { get; set; }
+ public string Path { get; set; } = "Thumbnails";
///
- /// Url which describes where the service is hosted. Should end with a slash
+ /// URL which is a common prefix of all services using the thumbnail service.
+ /// Should end with a slash. This prefix is to be omitted when requesting
+ /// thumbnails from the service.
+ /// Example: "https://docker-hip.cs.upb.de/develop/"
///
public string HostUrl { get; set; }
///
- /// Dictionary for the supported sizes
+ /// The image sizes options that can be chosen when requesting thumbnails.
+ /// Example: { "small": 50, "medium": 100, "large": 250 }
///
- public Dictionary SupportedSizes { get; set; }
+ public Dictionary SupportedSizes { get; set; }
}
}
diff --git a/HiP-ThumbnailService/appsettings.Development.json.example b/HiP-ThumbnailService/appsettings.Development.json.example
deleted file mode 100644
index 8d9a606..0000000
--- a/HiP-ThumbnailService/appsettings.Development.json.example
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "Logging": {
- "IncludeScopes": false,
- "LogLevel": {
- "Default": "Debug",
- "System": "Information",
- "Microsoft": "Information"
- }
- },
-
-
- "ThumbnailConfig": {
- "SupportedSizes": {
- "small": 50,
- "medium": 100,
- "large": 250
- },
- "HostUrl": "https://docker-hip.cs.upb.de/develop/",
- "Path": "Thumbnails"
- }
-
-}
diff --git a/HiP-ThumbnailService/appsettings.json b/HiP-ThumbnailService/appsettings.json
index 01a36f6..844b473 100644
--- a/HiP-ThumbnailService/appsettings.json
+++ b/HiP-ThumbnailService/appsettings.json
@@ -30,8 +30,23 @@
}
},
+ "Thumbnails": {
+ "SupportedSizes": {
+ "small": 50,
+ "medium": 100,
+ "large": 250
+ },
+ "HostUrl": null,
+ "Path": "Thumbnails"
+ },
+
"Auth": {
"Audience": "https://hip.cs.upb.de/API",
"Authority": "https://hip.eu.auth0.com/"
+ },
+ "HiPLoggerConfig": {
+ "LogSource": "HiP-thumbnailService-localhost",
+ "Host": "127.0.0.1",
+ "Port": 12201
}
}
diff --git a/NuGetPack.ps1 b/NuGetPack.ps1
new file mode 100644
index 0000000..1db276a
--- /dev/null
+++ b/NuGetPack.ps1
@@ -0,0 +1,6 @@
+$csproj = (ls *.Sdk\*.csproj).FullName
+
+dotnet pack "$csproj" -o .
+dotnet pack "$csproj" -o . --version-suffix "develop"
+
+$LASTEXITCODE = 0
\ No newline at end of file
diff --git a/README.md b/README.md
index 82a180d..a534e3b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,29 @@
# HiP-ThumbnailService
-Microservice (REST API) for generating thumbnails
+Microservice (REST API) for generating thumbnails.
+
+## Development Environment Setup
+* Clone the repository
+* Configure the app:
+ Create a file `appsettings.Development.json` in the same folder as `HiP-ThumbnailService.csproj` with the following content, replacing "YOUR URL PREFIX" with a valid URL according to the documentation for [ThumbnailConfig.HostUrl](https://github.com/HiP-App/HiP-ThumbnailService/blob/develop/HiP-ThumbnailService/Utility/ThumbnailConfig.cs):
+ ```
+ {
+ "Thumbnails": {
+ "HostUrl": "YOUR URL PREFIX"
+ }
+ }
+ ```
+* Launch the app
+ * via Visual Studio: Open the solution (*.sln) and run the app (F5)
+ * via Terminal: Execute `dotnet run` from the project folder containing `HiP-ThumbnailService.csproj`
+
+The app is preconfigured to run on dev machines with minimal manual configuration. See [appsettings.json](https://github.com/HiP-App/HiP-ThumbnailService/blob/develop/HiP-ThumbnailService/appsettings.json) for a list of configuration fields and their default values.
+
+## Core Concepts
+When requesting thumbnails via `GET /api/Thumbnails?Url=...`, the thumbnail service retrieves the original image from another server or service, resizes and crops the image, and then returns it. The URL to the original image is constructed from two parts:
+
+1. **The "HostUrl" configured in the thumbnail service**
+ (example: "https://docker-hip.cs.upb.de/develop/")
+1. **The relative URL given by the client via `?Url=`**
+ (example: "datastore/Media/42/File")
+
+Thumbnails of different sizes and formats are cached. When the original image changes, the service owning the image needs to clear the thumbnail cache for that image by calling `DELETE /api/Thumbnails?Url=...`, otherwise the thumbnail service will continue to return the outdated, cached thumbnails.
\ No newline at end of file