...
 
Commits (3)
......@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.6.1] - 2020-03-15
### Fixed
- Processor lock could not be properly released in a concurrent scenario.
## [1.6.0] - 2020-02-29
### Changed
......@@ -12,4 +18,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated NuGet dependencies.
- Client is built for .NET Standard 2.1 and .NET Framework 4.7.2.
[1.6.1]: https://gitlab.com/pomma89/thumbnailer/-/compare/1.6.0...1.6.1
[1.6.0]: https://gitlab.com/pomma89/thumbnailer/-/compare/1.5.7...1.6.0
\ No newline at end of file
......@@ -32,7 +32,7 @@ namespace PommaLabs.Thumbnailer.Client.Models.Exceptions
/// Represents an error produced by Thumbnailer service.
/// </summary>
[Serializable]
public sealed class ThumbnailerClientException : Exception
public class ThumbnailerClientException : Exception
{
/// <summary>
/// Constructor.
......@@ -66,7 +66,6 @@ public sealed class ThumbnailerClientException : Exception
/// Constructor.
/// </summary>
public ThumbnailerClientException()
: base()
{
}
......@@ -92,13 +91,13 @@ public ThumbnailerClientException(string message, Exception innerException)
/// <summary>
/// Constructor.
/// </summary>
/// <param name="serializationInfo">Serialization info.</param>
/// <param name="streamingContext">Streaming context.</param>
private ThumbnailerClientException(SerializationInfo serializationInfo, StreamingContext streamingContext)
: base(serializationInfo, streamingContext)
/// <param name="info">Serialization info.</param>
/// <param name="context">Streaming context.</param>
protected ThumbnailerClientException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
serializationInfo.AddValue(nameof(HttpStatusCode), HttpStatusCode);
serializationInfo.AddValue(nameof(OperationType), OperationType);
info.AddValue(nameof(HttpStatusCode), HttpStatusCode);
info.AddValue(nameof(OperationType), OperationType);
}
#endregion Standard exception constructors
......
......@@ -120,9 +120,10 @@ public Task SetLastWriteTimeAsync(string path, DateTimeOffset instant)
public async Task<(bool locked, IFileLockHandle lockHandle)> TryLockFileExclusivelyAsync(
string path, CancellationToken cancellationToken)
{
FileStream fileStream = default;
try
{
var fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
if (fileStream.Length >= sizeof(long))
{
var ticks = new byte[sizeof(long)];
......@@ -131,13 +132,15 @@ public Task SetLastWriteTimeAsync(string path, DateTimeOffset instant)
var lockTimestamp = new DateTimeOffset(BitConverter.ToInt64(ticks, 0), TimeSpan.Zero);
if (_clock.UtcNow.Subtract(lockTimestamp) < _processorConfiguration.Value.LockDuration)
{
throw new Exception($"Lock is fresh and was renewed at {lockTimestamp}");
_logger.LogInformation("Lock is fresh and was renewed at {LockTimestamp}", lockTimestamp);
return (false, null);
}
}
var lockHandle = new SystemFileLockHandle(_processorConfiguration, fileStream, _clock);
await lockHandle.RenewAsync(cancellationToken).ConfigureAwait(false);
fileStream = null; // It will be disposed by lock handle.
return (true, lockHandle);
}
catch (IOException ex)
......@@ -145,6 +148,10 @@ public Task SetLastWriteTimeAsync(string path, DateTimeOffset instant)
_logger.LogWarning(ex, "An error occurred while trying to lock file \"{FilePath}\"", path);
return (false, null);
}
finally
{
fileStream?.Dispose();
}
}
/// <inheritdoc/>
......
// File name: CommandException.cs
//
// Author(s): Alessio Parma <[email protected]>
//
// The MIT License (MIT)
//
// Copyright (c) 2019-2020 Alessio Parma <[email protected]>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Runtime.Serialization;
namespace PommaLabs.Thumbnailer.Models.Exceptions
{
/// <summary>
/// Represents an error produced by a low level command.
/// </summary>
[Serializable]
public class CommandException : Exception
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="message">Message.</param>
/// <param name="exitCode">Command exit code.</param>
public CommandException(
string message, int? exitCode)
: base(message)
{
Data[nameof(ExitCode)] = exitCode;
}
/// <summary>
/// Command exti code.
/// </summary>
public int? ExitCode => Data[nameof(ExitCode)] as int?;
#region Standard exception constructors
/// <summary>
/// Constructor.
/// </summary>
public CommandException()
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="message">Message</param>
public CommandException(string message)
: base(message)
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="message">Message.</param>
/// <param name="innerException">Inner exception.</param>
public CommandException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="info">Serialization info.</param>
/// <param name="context">Streaming context.</param>
protected CommandException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
info.AddValue(nameof(ExitCode), ExitCode);
}
#endregion Standard exception constructors
}
}
......@@ -24,5 +24,4 @@
without sacrificing quality, so that your page load times and server bandwidth usage remain low.
</p>
</div>
</div>
</div>
\ No newline at end of file
......@@ -4,7 +4,6 @@
ViewData["Title"] = "Privacy Policy";
}
<div class="row text-center">
<div class="col">
<h1>@ViewData["Title"]</h1>
......@@ -21,5 +20,4 @@
<a href="https://policies.google.com/terms">Terms of Service</a> apply.
</p>
</div>
</div>
</div>
\ No newline at end of file
......@@ -21,12 +21,12 @@
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using PommaLabs.Thumbnailer.Models.Exceptions;
namespace PommaLabs.Thumbnailer.Services.Managers.Command
{
......@@ -75,7 +75,7 @@ public SystemCommandManager(ILogger<SystemCommandManager> logger)
_logger.LogError("An error occurred while running {CommandName} with following arguments: {CommandArgs}. Command error: {CommandError}",
commandName, commandArgs, commandError);
throw new Exception($"External command raised an error");
throw new CommandException($"External command \"{commandName}\" raised an error", command.ExitCode);
}
}
}
......
......@@ -21,10 +21,13 @@
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using PommaLabs.Thumbnailer.Client.Models.Constants;
using PommaLabs.Thumbnailer.Models.DTO;
using PommaLabs.Thumbnailer.Models.Exceptions;
using PommaLabs.Thumbnailer.Services.Managers.Command;
using PommaLabs.Thumbnailer.Services.Stores.TempFiles;
......@@ -36,18 +39,22 @@ namespace PommaLabs.Thumbnailer.Services.Managers.Thumbnail
public sealed class ConcreteThumbnailManager : IThumbnailManager
{
private readonly ICommandManager _commandManager;
private readonly ILogger<ConcreteThumbnailManager> _logger;
private readonly ITempFileStore _tempFileStore;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="commandManager">Command manager.</param>
/// <param name="logger">Logger.</param>
/// <param name="tempFileStore">Temporary file store.</param>
public ConcreteThumbnailManager(
ICommandManager commandManager,
ILogger<ConcreteThumbnailManager> logger,
ITempFileStore tempFileStore)
{
_commandManager = commandManager;
_logger = logger;
_tempFileStore = tempFileStore;
}
......@@ -65,9 +72,19 @@ public async Task<FileDTO> GetThumbnailAsync(FileDTO file, ushort sidePx, ushort
if (file.IsHtmlDocument)
{
await _commandManager.RunCommandAsync(
"wkhtmltoimage", $"-q --width 1024 --disable-smart-width --disable-local-file-access --load-media-error-handling ignore {file.Path} {thumbnail.Path}",
default, cancellationToken).ConfigureAwait(false);
try
{
await _commandManager.RunCommandAsync(
"wkhtmltoimage", $"-q --width 1024 --disable-smart-width --disable-local-file-access --load-media-error-handling ignore {file.Path} {thumbnail.Path}",
default, cancellationToken).ConfigureAwait(false);
}
catch (CommandException ex) when (ex.ExitCode == 1 && new FileInfo(thumbnail.Path).Length > 0)
{
// wkhtmltoimage seems to exit with code 1 when a linked resource (like an image
// or a script) does not exist. However, it generates the image as correct as
// possible: therefore, it is better to ignore this kind of error.
_logger.LogWarning(ex, "An ignorable error occurred while executing wkhmtltoimage command");
}
imFilePath = thumbnail.Path;
imFileFormat = thumbnail.Format;
......
......@@ -21,7 +21,6 @@
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
......
This diff is collapsed.