DocSpace-client/products/ASC.Files/Service/Thumbnail/Builder.cs

365 lines
14 KiB
C#
Raw Normal View History

2022-03-15 18:00:53 +00:00
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Files.ThumbnailBuilder;
[Singletone]
2022-02-24 20:28:07 +00:00
public class BuilderQueue<T>
2021-05-30 18:52:32 +00:00
{
private readonly ThumbnailSettings _config;
private readonly ILog _logger;
2022-02-24 20:28:07 +00:00
private readonly IServiceScopeFactory _serviceScopeFactory;
2022-02-24 20:28:07 +00:00
public BuilderQueue(IServiceScopeFactory serviceScopeFactory, IOptionsMonitor<ILog> log, ThumbnailSettings settings)
2021-05-30 18:52:32 +00:00
{
_logger = log.Get("ASC.Files.ThumbnailBuilder");
2022-02-24 20:28:07 +00:00
_serviceScopeFactory = serviceScopeFactory;
_config = settings;
}
2021-05-30 18:52:32 +00:00
2022-04-14 19:42:15 +00:00
public async Task BuildThumbnails(IEnumerable<FileData<T>> filesWithoutThumbnails)
{
try
2021-05-30 18:52:32 +00:00
{
2022-04-14 19:42:15 +00:00
await Parallel.ForEachAsync(
filesWithoutThumbnails,
new ParallelOptions { MaxDegreeOfParallelism = _config.MaxDegreeOfParallelism },
async (fileData, token) =>
{
using var scope = _serviceScopeFactory.CreateScope();
var commonLinkUtilitySettings = scope.ServiceProvider.GetService<CommonLinkUtilitySettings>();
commonLinkUtilitySettings.ServerUri = fileData.BaseUri;
2022-04-14 19:42:15 +00:00
var builder = scope.ServiceProvider.GetService<Builder<T>>();
await builder.BuildThumbnail(fileData);
}
);
}
catch (Exception exception)
{
2022-04-25 18:02:18 +00:00
_logger.LogError(exception, string.Format("BuildThumbnails: filesWithoutThumbnails.Count: {0}.", filesWithoutThumbnails.Count()));
}
}
}
[Scope]
2022-02-24 20:28:07 +00:00
public class Builder<T>
{
private readonly ThumbnailSettings _config;
private readonly ILog _logger;
private readonly TenantManager _tenantManager;
private readonly IDaoFactory _daoFactory;
private readonly DocumentServiceConnector _documentServiceConnector;
private readonly DocumentServiceHelper _documentServiceHelper;
private readonly Global _global;
private readonly PathProvider _pathProvider;
private readonly IHttpClientFactory _clientFactory;
public Builder(
ThumbnailSettings settings,
TenantManager tenantManager,
IDaoFactory daoFactory,
DocumentServiceConnector documentServiceConnector,
DocumentServiceHelper documentServiceHelper,
Global global,
PathProvider pathProvider,
IOptionsMonitor<ILog> log,
IHttpClientFactory clientFactory)
{
2022-02-24 10:03:21 +00:00
_config = settings;
_tenantManager = tenantManager;
_daoFactory = daoFactory;
_documentServiceConnector = documentServiceConnector;
_documentServiceHelper = documentServiceHelper;
_global = global;
_pathProvider = pathProvider;
_logger = log.Get("ASC.Files.ThumbnailBuilder");
_clientFactory = clientFactory;
}
2022-04-14 19:42:15 +00:00
internal async Task BuildThumbnail(FileData<T> fileData)
{
try
{
_tenantManager.SetCurrentTenant(fileData.TenantId);
var fileDao = _daoFactory.GetFileDao<T>();
if (fileDao == null)
{
2022-04-25 18:02:18 +00:00
_logger.LogError("BuildThumbnail: TenantId: {0}. FileDao could not be null.", fileData.TenantId);
return;
}
2022-04-14 19:42:15 +00:00
await GenerateThumbnail(fileDao, fileData);
}
catch (Exception exception)
{
2022-04-25 18:02:18 +00:00
_logger.LogError(exception, string.Format("BuildThumbnail: TenantId: {0}.", fileData.TenantId));
}
finally
{
2022-02-24 09:51:12 +00:00
FileDataQueue.Queue.TryRemove(fileData.FileId, out _);
}
}
2022-04-14 19:42:15 +00:00
private async Task GenerateThumbnail(IFileDao<T> fileDao, FileData<T> fileData)
{
File<T> file = null;
try
{
2022-04-14 19:42:15 +00:00
file = await fileDao.GetFileAsync(fileData.FileId);
if (file == null)
{
2022-04-25 18:02:18 +00:00
_logger.LogError("GenerateThumbnail: FileId: {0}. File not found.", fileData.FileId);
return;
}
if (file.ThumbnailStatus != Thumbnail.Waiting)
{
2022-04-25 18:02:18 +00:00
_logger.LogInformation("GenerateThumbnail: FileId: {0}. Thumbnail already processed.", fileData.FileId);
return;
}
var ext = FileUtility.GetFileExtension(file.Title);
if (!_config.FormatsArray.Contains(ext) || file.Encrypted || file.RootFolderType == FolderType.TRASH || file.ContentLength > _config.AvailableFileSize)
{
file.ThumbnailStatus = Thumbnail.NotRequired;
2022-04-14 19:42:15 +00:00
await fileDao.SaveThumbnailAsync(file, null);
return;
}
if (IsImage(file))
{
2022-04-14 19:42:15 +00:00
await CropImage(fileDao, file);
}
else
{
2022-04-14 19:42:15 +00:00
await MakeThumbnail(fileDao, file);
}
}
catch (Exception exception)
{
2022-04-25 18:02:18 +00:00
_logger.LogError(exception, string.Format("GenerateThumbnail: FileId: {0}.", fileData.FileId));
if (file != null)
{
file.ThumbnailStatus = Thumbnail.Error;
2022-04-14 19:42:15 +00:00
await fileDao.SaveThumbnailAsync(file, null);
}
}
}
2022-04-14 19:42:15 +00:00
private async Task MakeThumbnail(IFileDao<T> fileDao, File<T> file)
{
2022-04-25 18:02:18 +00:00
_logger.LogDebug("MakeThumbnail: FileId: {0}.", file.Id);
string thumbnailUrl = null;
var attempt = 1;
do
{
try
{
2022-04-14 19:42:15 +00:00
var (result, url) = await GetThumbnailUrl(file, _global.ThumbnailExtension);
thumbnailUrl = url;
if (result)
{
break;
}
}
catch (Exception exception)
{
if (exception.InnerException != null)
{
var documentServiceException = exception.InnerException as DocumentService.DocumentServiceException;
if (documentServiceException != null)
2021-10-29 14:21:51 +00:00
{
if (documentServiceException.Code == DocumentService.DocumentServiceException.ErrorCode.ConvertPassword)
{
throw new Exception(string.Format("MakeThumbnail: FileId: {0}. Encrypted file.", file.Id));
}
if (documentServiceException.Code == DocumentService.DocumentServiceException.ErrorCode.Convert)
{
throw new Exception(string.Format("MakeThumbnail: FileId: {0}. Could not convert.", file.Id));
}
}
}
}
if (attempt >= _config.AttemptsLimit)
{
throw new Exception(string.Format("MakeThumbnail: FileId: {0}. Attempts limmit exceeded.", file.Id));
}
else
{
2022-04-25 18:02:18 +00:00
_logger.LogDebug("MakeThumbnail: FileId: {0}. Sleep {1} after attempt #{2}. ", file.Id, _config.AttemptWaitInterval, attempt);
attempt++;
}
Thread.Sleep(_config.AttemptWaitInterval);
}
while (string.IsNullOrEmpty(thumbnailUrl));
2022-04-14 19:42:15 +00:00
await SaveThumbnail(fileDao, file, thumbnailUrl);
2021-05-30 18:52:32 +00:00
}
2022-04-14 19:42:15 +00:00
private async Task<(bool, string)> GetThumbnailUrl(File<T> file, string toExtension)
2021-05-30 18:52:32 +00:00
{
var fileUri = _pathProvider.GetFileStreamUrl(file);
fileUri = _documentServiceConnector.ReplaceCommunityAdress(fileUri);
var fileExtension = file.ConvertedExtension;
var docKey = _documentServiceHelper.GetDocKey(file);
var thumbnail = new DocumentService.ThumbnailData
2021-05-30 18:52:32 +00:00
{
Aspect = 2,
First = true,
//Height = config.ThumbnaillHeight,
//Width = config.ThumbnaillWidth
};
var spreadsheetLayout = new DocumentService.SpreadsheetLayout
{
IgnorePrintArea = true,
//Orientation = "landscape", // "297mm" x "210mm"
FitToHeight = 0,
FitToWidth = 1,
Headings = false,
GridLines = false,
Margins = new DocumentService.SpreadsheetLayout.LayoutMargins
{
Top = "0mm",
Right = "0mm",
Bottom = "0mm",
Left = "0mm"
},
PageSize = new DocumentService.SpreadsheetLayout.LayoutPageSize
{
Width = (_config.ThumbnaillWidth * 1.5) + "mm", // 192 * 1.5 = "288mm",
Height = (_config.ThumbnaillHeight * 1.5) + "mm" // 128 * 1.5 = "192mm"
}
};
2021-10-12 10:14:33 +00:00
2022-04-14 19:42:15 +00:00
var (operationResultProgress, url) = await _documentServiceConnector.GetConvertedUriAsync(fileUri, fileExtension, toExtension, docKey, null, thumbnail, spreadsheetLayout, false);
operationResultProgress = Math.Min(operationResultProgress, 100);
2022-04-14 19:42:15 +00:00
return (operationResultProgress == 100, url);
}
2022-04-14 19:42:15 +00:00
private async Task SaveThumbnail(IFileDao<T> fileDao, File<T> file, string thumbnailUrl)
{
2022-04-25 18:02:18 +00:00
_logger.LogDebug("SaveThumbnail: FileId: {0}. ThumbnailUrl {1}.", file.Id, thumbnailUrl);
2022-04-14 19:42:15 +00:00
using var request = new HttpRequestMessage();
request.RequestUri = new Uri(thumbnailUrl);
var httpClient = _clientFactory.CreateClient();
using var response = httpClient.Send(request);
using (var stream = new ResponseStream(response))
{
2022-04-14 19:42:15 +00:00
await Crop(fileDao, file, stream);
}
2022-04-25 18:02:18 +00:00
_logger.LogDebug("SaveThumbnail: FileId: {0}. Successfully saved.", file.Id);
}
private bool IsImage(File<T> file)
{
var extension = FileUtility.GetFileExtension(file.Title);
return FileUtility.ExtsImage.Contains(extension);
}
2022-04-14 19:42:15 +00:00
private async Task CropImage(IFileDao<T> fileDao, File<T> file)
{
2022-04-25 18:02:18 +00:00
_logger.LogDebug("CropImage: FileId: {0}.", file.Id);
2022-04-14 19:42:15 +00:00
using (var stream = await fileDao.GetFileStreamAsync(file))
{
2022-04-14 19:42:15 +00:00
await Crop(fileDao, file, stream);
}
2022-04-25 18:02:18 +00:00
_logger.LogDebug("CropImage: FileId: {0}. Successfully saved.", file.Id);
}
2022-04-14 19:42:15 +00:00
private async Task Crop(IFileDao<T> fileDao, File<T> file, Stream stream)
{
using (var sourceImg = Image.Load(stream))
{
using (var targetImg = GetImageThumbnail(sourceImg))
{
using (var targetStream = new MemoryStream())
{
targetImg.Save(targetStream, PngFormat.Instance);
2022-04-14 19:42:15 +00:00
//targetImg.Save(targetStream, JpegFormat.Instance);
await fileDao.SaveThumbnailAsync(file, targetStream);
}
2021-11-06 20:11:18 +00:00
}
}
GC.Collect();
}
private Image GetImageThumbnail(Image sourceBitmap)
{
//bad for small or disproportionate images
//return sourceBitmap.GetThumbnailImage(config.ThumbnaillWidth, config.ThumbnaillHeight, () => false, IntPtr.Zero);
var targetSize = new Size(Math.Min(sourceBitmap.Width, _config.ThumbnaillWidth), Math.Min(sourceBitmap.Height, _config.ThumbnaillHeight));
var point = new Point(0, 0);
var size = targetSize;
if (sourceBitmap.Width > _config.ThumbnaillWidth && sourceBitmap.Height > _config.ThumbnaillHeight)
{
if (sourceBitmap.Width > sourceBitmap.Height)
{
var width = (int)(_config.ThumbnaillWidth * (sourceBitmap.Height / (1.0 * _config.ThumbnaillHeight)));
size = new Size(width, sourceBitmap.Height);
}
else
{
var height = (int)(_config.ThumbnaillHeight * (sourceBitmap.Width / (1.0 * _config.ThumbnaillWidth)));
size = new Size(sourceBitmap.Width, height);
}
}
if (sourceBitmap.Width > sourceBitmap.Height)
{
point.X = (sourceBitmap.Width - size.Width) / 2;
}
var targetThumbnailSettings = new UserPhotoThumbnailSettings(point, size);
return UserPhotoThumbnailManager.GetImage(sourceBitmap, targetSize, targetThumbnailSettings);
}
2021-05-30 18:52:32 +00:00
}