Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>PaderbornUniversity.SILab.Hip.ThumbnailService</RootNamespace>
<NoWarn>1701;1702;1705;1591</NoWarn>
<Version>1.0.0</Version>
<Version>1.2.0</Version>
<Version Condition="'$(VersionSuffix)' != ''">$(Version)-$(VersionSuffix)</Version>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>


<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
</ItemGroup>

</Project>
21 changes: 21 additions & 0 deletions HiP-ThumbnailService.Sdk/ThumbnailConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace PaderbornUniversity.SILab.Hip.ThumbnailService
{
/// <summary>
/// Configuration properties for clients using the thumbnail service.
/// </summary>
public sealed class ThumbnailConfig
{
/// <summary>
/// URL pointing to a running instance of the thumbnail service.
/// Example: "https://docker-hip.cs.upb.de/develop/thumbnailservice"
/// </summary>
public string ThumbnailServiceHost { get; set; }

/// <summary>
/// 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"
/// </summary>
public string ThumbnailUrlPattern { get; set; }
}
}
91 changes: 91 additions & 0 deletions HiP-ThumbnailService.Sdk/ThumbnailService.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// A service that can be used with ASP.NET Core dependency injection.
/// Usage: In ConfigureServices():
/// <code>
/// services.Configure&lt;ThumbnailConfig&gt;(Configuration.GetSection("Thumbnails"));
/// services.AddSingleton&lt;ThumbnailService&gt;();
/// </code>
/// </summary>
public class ThumbnailService
{
private readonly ThumbnailConfig _config;
private readonly ILogger<ThumbnailService> _logger;
private readonly IHttpContextAccessor _httpContextAccessor;

public ThumbnailsClient ThumbnailsClient => new ThumbnailsClient(_config.ThumbnailServiceHost)
{
Authorization = _httpContextAccessor.HttpContext?.Request.Headers["Authorization"]
};

public ThumbnailService(IOptions<ThumbnailConfig> config, ILogger<ThumbnailService> 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!");
}

/// <summary>
/// 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"
/// </summary>
/// <param name="args">Arguments replacing the placeholders in <see cref="ThumbnailConfig.ThumbnailUrlPattern"/></param>
public string GetThumbnailUrl(params object[] args) =>
$"{_config.ThumbnailServiceHost}/api/Thumbnails?Url={GetThumbnailUrlArgument(args)}";

/// <summary>
/// Constructs the relative URL that is used to request thumbnails.
/// </summary>
/// <param name="args">Arguments replacing the placeholders in <see cref="ThumbnailConfig.ThumbnailUrlPattern"/></param>
public string GetThumbnailUrlArgument(params object[] args) =>
string.Format(_config.ThumbnailUrlPattern ?? "", args);

/// <summary>
/// Tries to delete all cached thumbnails of an image in the thumbnail service.
/// Exceptions are catched and logged as warning.
/// </summary>
/// <param name="args">Arguments replacing the placeholders in <see cref="ThumbnailConfig.ThumbnailUrlPattern"/></param>
public async Task<bool> 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;
}
}
}
}
67 changes: 16 additions & 51 deletions HiP-ThumbnailService/Arguments/CreationArgs.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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)
/// </summary>
[Required]
public string Url { get; set; }

/// <summary>
/// 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.
/// </summary>
public string Size { get; set; }

/// <summary>
/// Specifies crop mode that is used. If no mode is specified <see cref="CropMode.FillSquare"/> is used
/// Image cropping mode that is used. Defaults to <see cref="CropMode.FillSquare"/>.
/// </summary>
[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;

/// <summary>
/// Specifies image format that the resulting thumbnail has. If no format is specified <see cref="RequestedImageFormat.Jpeg"/> is used
/// The desired image format of the resulting thumbnail.
/// Defaults to <see cref="RequestedImageFormat.Jpeg"/>.
/// </summary>
[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;
}
}
2 changes: 1 addition & 1 deletion HiP-ThumbnailService/Arguments/CropMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
/// <summary>
/// Describes the different modes that can be used for cropping.
/// <see cref="FillSquare"/> crops at the center of the image resulting in a square
/// <see cref="FillSquare"/> crops at the center of the image resulting in a square.
/// <see cref="Uniform"/> keeps the aspect ratio while cropping the longer side to the specified value.
/// </summary>
public enum CropMode
Expand Down
Loading