fileconverter: moved timer to separete worker service

This commit is contained in:
Alexey Bannov 2022-06-08 17:25:25 +03:00
parent 3757174d08
commit 7b1ea58cdf
13 changed files with 636 additions and 429 deletions

View File

@ -0,0 +1,42 @@
// (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.Common.Data;
public static class TypeExtension
{
public static string GetFormattedName(this Type type)
{
if (type.IsGenericType)
{
var genericArguments = type.GetGenericArguments()
.Select(x => x.Name)
.Aggregate((x1, x2) => $"{x1}, {x2}");
return $"{type.Name.Substring(0, type.Name.IndexOf("`"))}"
+ $"<{genericArguments}>";
}
return type.Name;
}
}

View File

@ -305,6 +305,13 @@ public class DistributedTaskQueue
private void SaveToCache(IEnumerable<DistributedTask> queueTasks)
{
if (!queueTasks.Any())
{
_distributedCache.Remove(_name);
return;
}
using var ms = new MemoryStream();
Serializer.Serialize(ms, queueTasks);
@ -332,21 +339,11 @@ public class DistributedTaskQueue
private IEnumerable<DistributedTask> DeleteOrphanCacheItem(IEnumerable<DistributedTask> queueTasks)
{
if (!queueTasks.Any())
{
return queueTasks;
}
var listTasks = queueTasks.ToList();
var orphans = queueTasks.Where(IsOrphanCacheItem);
listTasks.RemoveAll(IsOrphanCacheItem);
if (!orphans.Any())
{
return queueTasks;
}
queueTasks = queueTasks.Except(queueTasks);
SaveToCache(queueTasks);
SaveToCache(listTasks);
return queueTasks;
}

View File

@ -149,3 +149,4 @@ global using ProtoBuf;
global using Telegram.Bot;
global using static ASC.Security.Cryptography.EmailValidationKeyProvider;
global using ASC.Common.Data;

View File

@ -79,7 +79,7 @@ public class RegisterInstanceDao<T> : IRegisterInstanceDao<T> where T : IHostedS
public async Task<IEnumerable<InstanceRegistration>> GetAll()
{
return await _instanceRegistrationContext.InstanceRegistrations
.Where(x => x.WorkerTypeName == typeof(T).Name)
.Where(x => x.WorkerTypeName == typeof(T).GetFormattedName())
.ToListAsync();
}

View File

@ -51,7 +51,7 @@ public class RegisterInstanceManager<T> : IRegisterInstanceManager<T> where T :
var instance = registeredInstance ?? new InstanceRegistration
{
InstanceRegistrationId = instanceId,
WorkerTypeName = typeof(T).Name
WorkerTypeName = typeof(T).GetFormattedName()
};
instance.LastUpdated = DateTime.UtcNow;

View File

@ -24,7 +24,7 @@
// 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.Core.Common.Hosting;
namespace ASC.Core.Common.Hosting;
[Singletone]
public class RegisterInstanceWorkerService<T> : BackgroundService where T : IHostedService
@ -33,7 +33,7 @@ public class RegisterInstanceWorkerService<T> : BackgroundService where T : IHos
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly IServiceProvider _serviceProvider;
public static readonly string InstanceId =
$"{typeof(T).Name}_{DateTime.UtcNow.Ticks}";
$"{typeof(T).GetFormattedName()}_{DateTime.UtcNow.Ticks}";
public RegisterInstanceWorkerService(
ILogger<RegisterInstanceWorkerService<T>> logger,
@ -58,7 +58,7 @@ public class RegisterInstanceWorkerService<T> : BackgroundService where T : IHos
await registerInstanceService.Register(InstanceId);
await registerInstanceService.DeleteOrphanInstances();
_logger.InformationWorkingRunnging(DateTimeOffset.Now);
_logger.TraceWorkingRunnging(DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
@ -87,5 +87,6 @@ public class RegisterInstanceWorkerService<T> : BackgroundService where T : IHos
}
await base.StopAsync(cancellationToken);
}
}
}

View File

@ -31,8 +31,8 @@ internal static partial class RegisterInstanceWorkerServiceLogger
[LoggerMessage(Level = LogLevel.Trace, Message = "DbUpdateConcurrencyException: then updating {instanceName} at {time} time.")]
public static partial void TraceDbUpdateConcurrencyException(this ILogger logger, string instanceName, DateTimeOffset time);
[LoggerMessage(Level = LogLevel.Information, Message = "Worker running at: {time}")]
public static partial void InformationWorkingRunnging(this ILogger logger, DateTimeOffset time);
[LoggerMessage(Level = LogLevel.Trace, Message = "Worker running at: {time}")]
public static partial void TraceWorkingRunnging(this ILogger logger, DateTimeOffset time);
[LoggerMessage(Level = LogLevel.Information, Message = "UnRegister Instance {instanceName} running at: {time}.")]
public static partial void InformationUnRegister(this ILogger logger, string instanceName, DateTimeOffset time);

View File

@ -26,16 +26,32 @@
namespace ASC.Web.Files.Services.WCFService.FileOperations;
[ProtoContract]
[ProtoInclude(100, typeof(FileConverterOperationResult))]
public class FileOperationResult
{
[ProtoMember(1)]
public string Id { get; set; }
[JsonPropertyName("operation")]
[ProtoMember(2)]
public FileOperationType OperationType { get; set; }
[ProtoMember(3)]
public int Progress { get; set; }
[ProtoMember(4)]
public string Source { get; set; }
[ProtoMember(5)]
public string Result { get; set; }
[ProtoMember(6)]
public string Error { get; set; }
[ProtoMember(7)]
public string Processed { get; set; }
[ProtoMember(8)]
public bool Finished { get; set; }
}

View File

@ -24,44 +24,40 @@
// 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
using Microsoft.Extensions.Caching.Distributed;
using Timeout = System.Threading.Timeout;
namespace ASC.Web.Files.Utils;
[Singletone(Additional = typeof(FileConverterQueueExtension))]
internal class FileConverterQueue<T> : IDisposable
[Singletone]
public class FileConverterQueue<T>
{
private readonly object _singleThread = new object();
private readonly IDictionary<File<T>, ConvertFileOperationResult> _conversionQueue;
private readonly Timer _timer;
private readonly object _locker;
private readonly ICache _cache;
private const int _timerPeriod = 500;
private readonly object _locker = new object();
private readonly IDistributedCache _distributedCache;
private readonly string _name = "asc_file_converter_queue_";
private readonly IServiceScopeFactory _serviceScopeFactory;
public FileConverterQueue(IServiceScopeFactory serviceScopeFactory, ICache cache)
public FileConverterQueue(IDistributedCache distributedCache)
{
_conversionQueue = new Dictionary<File<T>, ConvertFileOperationResult>(new FileComparer<T>());
_timer = new Timer(CheckConvertFilesStatus, null, 0, Timeout.Infinite);
_locker = new object();
_serviceScopeFactory = serviceScopeFactory;
_cache = cache;
_distributedCache = distributedCache;
}
public void Add(File<T> file, string password, int tenantId, IAccount account, bool deleteAfter, string url, string serverRootPath)
public void Add(File<T> file,
string password,
int tenantId,
IAccount account,
bool deleteAfter,
string url,
string serverRootPath)
{
lock (_locker)
{
if (_conversionQueue.ContainsKey(file))
if (Contains(file))
{
return;
}
var queueResult = new ConvertFileOperationResult
var queueResult = new FileConverterOperationResult
{
Source = string.Format("{{\"id\":\"{0}\", \"version\":\"{1}\"}}", file.Id, file.Version),
Source = System.Text.Json.JsonSerializer.Serialize(new { id = file.Id, version = file.Version }),
OperationType = FileOperationType.Convert,
Error = string.Empty,
Progress = 0,
@ -69,316 +65,82 @@ internal class FileConverterQueue<T> : IDisposable
Processed = "",
Id = string.Empty,
TenantId = tenantId,
Account = account,
Account = account.ID,
Delete = deleteAfter,
StartDateTime = DateTime.Now,
Url = url,
Password = password,
ServerRootPath = serverRootPath
};
_conversionQueue.Add(file, queueResult);
_cache.Insert(GetKey(file), queueResult, TimeSpan.FromMinutes(10));
_timer.Change(0, Timeout.Infinite);
Enqueue(queueResult);
}
}
public void Enqueue(FileConverterOperationResult val)
{
var fromCache = LoadFromCache().ToList();
fromCache.Add(val);
SaveToCache(fromCache);
}
public void Dequeue(File<T> file)
{
var fromCache = LoadFromCache().ToList();
var task = PeekTask(file);
fromCache.Remove(task);
SaveToCache(fromCache);
}
public async Task<ConvertFileOperationResult> GetStatusAsync(KeyValuePair<File<T>, bool> pair, FileSecurity fileSecurity)
public async Task<FileConverterOperationResult> GetStatusAsync(KeyValuePair<File<T>, bool> pair, FileSecurity fileSecurity)
{
var file = pair.Key;
var key = GetKey(file);
var operation = _cache.Get<ConvertFileOperationResult>(key);
var operation = PeekTask(pair.Key);
if (operation != null && (pair.Value || await fileSecurity.CanReadAsync(file)))
{
lock (_locker)
if (operation.Progress == 100)
{
if (operation.Progress == 100)
{
_conversionQueue.Remove(file);
_cache.Remove(key);
}
return operation;
Dequeue(pair.Key);
}
return operation;
}
return null;
}
public FileConverterOperationResult PeekTask(File<T> file)
{
var exist = LoadFromCache();
return exist.FirstOrDefault(x =>
{
var fileId = JsonDocument.Parse(x.Source).RootElement.GetProperty("id").Deserialize<T>();
var fileVersion = JsonDocument.Parse(x.Source).RootElement.GetProperty("version").Deserialize<int>();
return file.Id.ToString() == fileId.ToString() && file.Version == fileVersion;
});
}
public bool IsConverting(File<T> file)
{
var result = _cache.Get<ConvertFileOperationResult>(GetKey(file));
var result = PeekTask(file);
return result != null && result.Progress != 100 && string.IsNullOrEmpty(result.Error);
}
private void CheckConvertFilesStatus(object _)
{
if (Monitor.TryEnter(_singleThread))
{
using var scope = _serviceScopeFactory.CreateScope();
TenantManager tenantManager;
UserManager userManager;
SecurityContext securityContext;
IDaoFactory daoFactory;
FileSecurity fileSecurity;
PathProvider pathProvider;
SetupInfo setupInfo;
FileUtility fileUtility;
DocumentServiceHelper documentServiceHelper;
DocumentServiceConnector documentServiceConnector;
EntryStatusManager entryManager;
FileConverter fileConverter;
var logger = scope.ServiceProvider.GetService<ILogger<FileConverterQueue<T>>>();
try
{
var filesIsConverting = new List<File<T>>();
lock (_locker)
{
_timer.Change(Timeout.Infinite, Timeout.Infinite);
var queues = _conversionQueue.Where(x => !string.IsNullOrEmpty(x.Value.Processed)
&& (x.Value.Progress == 100 && DateTime.UtcNow - x.Value.StopDateTime > TimeSpan.FromMinutes(1) ||
DateTime.UtcNow - x.Value.StopDateTime > TimeSpan.FromMinutes(10)))
.ToList();
foreach (var q in queues)
{
_conversionQueue.Remove(q);
_cache.Remove(GetKey(q.Key));
}
logger.DebugRunCheckConvertFilesStatus(_conversionQueue.Count);
if (_conversionQueue.Count == 0)
{
return;
}
filesIsConverting = _conversionQueue
.Where(x => string.IsNullOrEmpty(x.Value.Processed))
.Select(x => x.Key)
.ToList();
}
string convertedFileUrl = null;
foreach (var file in filesIsConverting)
{
var fileUri = file.Id.ToString();
int operationResultProgress;
try
{
int tenantId;
IAccount account;
string password;
string serverRootPath;
lock (_locker)
{
if (!_conversionQueue.ContainsKey(file))
{
continue;
}
var operationResult = _conversionQueue[file];
if (!string.IsNullOrEmpty(operationResult.Processed))
{
continue;
}
operationResult.Processed = "1";
tenantId = operationResult.TenantId;
account = operationResult.Account;
password = operationResult.Password;
serverRootPath = operationResult.ServerRootPath;
//if (HttpContext.Current == null && !WorkContext.IsMono)
//{
// HttpContext.Current = new HttpContext(
// new HttpRequest("hack", operationResult.Url, string.Empty),
// new HttpResponse(new StringWriter()));
//}
_cache.Insert(GetKey(file), operationResult, TimeSpan.FromMinutes(10));
}
var commonLinkUtilitySettings = scope.ServiceProvider.GetService<CommonLinkUtilitySettings>();
commonLinkUtilitySettings.ServerUri = serverRootPath;
var scopeClass = scope.ServiceProvider.GetService<FileConverterQueueScope>();
(_, tenantManager, userManager, securityContext, daoFactory, fileSecurity, pathProvider, setupInfo, fileUtility, documentServiceHelper, documentServiceConnector, entryManager, fileConverter) = scopeClass;
tenantManager.SetCurrentTenant(tenantId);
securityContext.AuthenticateMeWithoutCookie(account);
var user = userManager.GetUsers(account.ID);
var culture = string.IsNullOrEmpty(user.CultureName) ? tenantManager.GetCurrentTenant().GetCulture() : CultureInfo.GetCultureInfo(user.CultureName);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
if (!fileSecurity.CanReadAsync(file).Result && file.RootFolderType != FolderType.BUNCH)
{
//No rights in CRM after upload before attach
throw new SecurityException(FilesCommonResource.ErrorMassage_SecurityException_ReadFile);
}
if (file.ContentLength > setupInfo.AvailableFileSize)
{
throw new Exception(string.Format(FilesCommonResource.ErrorMassage_FileSizeConvert, FileSizeComment.FilesSizeToString(setupInfo.AvailableFileSize)));
}
fileUri = pathProvider.GetFileStreamUrl(file);
var toExtension = fileUtility.GetInternalExtension(file.Title);
var fileExtension = file.ConvertedExtension;
var docKey = documentServiceHelper.GetDocKey(file);
fileUri = documentServiceConnector.ReplaceCommunityAdress(fileUri);
(operationResultProgress, convertedFileUrl) = documentServiceConnector.GetConvertedUriAsync(fileUri, fileExtension, toExtension, docKey, password, null, null, true).Result;
}
catch (Exception exception)
{
var password = exception.InnerException is DocumentServiceException documentServiceException
&& documentServiceException.Code == DocumentServiceException.ErrorCode.ConvertPassword;
logger.ErrorConvertFileWithUrl(file.Id.ToString(), fileUri, exception);
lock (_locker)
{
if (_conversionQueue.TryGetValue(file, out var operationResult))
{
if (operationResult.Delete)
{
_conversionQueue.Remove(file);
_cache.Remove(GetKey(file));
}
else
{
operationResult.Progress = 100;
operationResult.StopDateTime = DateTime.UtcNow;
operationResult.Error = exception.Message;
if (password)
{
operationResult.Result = "password";
}
_cache.Insert(GetKey(file), operationResult, TimeSpan.FromMinutes(10));
}
}
}
continue;
}
operationResultProgress = Math.Min(operationResultProgress, 100);
if (operationResultProgress < 100)
{
lock (_locker)
{
if (_conversionQueue.TryGetValue(file, out var operationResult))
{
if (DateTime.Now - operationResult.StartDateTime > TimeSpan.FromMinutes(10))
{
operationResult.StopDateTime = DateTime.UtcNow;
operationResult.Error = FilesCommonResource.ErrorMassage_ConvertTimeout;
logger.ErrorCheckConvertFilesStatus(file.Id.ToString(), file.ContentLength);
}
else
{
operationResult.Processed = "";
}
operationResult.Progress = operationResultProgress;
_cache.Insert(GetKey(file), operationResult, TimeSpan.FromMinutes(10));
}
}
logger.DebugCheckConvertFilesStatusIterationContinue();
continue;
}
File<T> newFile = null;
var operationResultError = string.Empty;
try
{
newFile = fileConverter.SaveConvertedFileAsync(file, convertedFileUrl).Result;
}
catch (Exception e)
{
operationResultError = e.Message;
logger.ErrorOperation(operationResultError, convertedFileUrl, fileUri, e);
continue;
}
finally
{
lock (_locker)
{
if (_conversionQueue.TryGetValue(file, out var operationResult))
{
if (operationResult.Delete)
{
_conversionQueue.Remove(file);
_cache.Remove(GetKey(file));
}
else
{
if (newFile != null)
{
var folderDao = daoFactory.GetFolderDao<T>();
var folder = folderDao.GetFolderAsync(newFile.ParentId).Result;
var folderTitle = fileSecurity.CanReadAsync(folder).Result ? folder.Title : null;
operationResult.Result = FileJsonSerializerAsync(entryManager, newFile, folderTitle).Result;
}
operationResult.Progress = 100;
operationResult.StopDateTime = DateTime.UtcNow;
operationResult.Processed = "1";
if (!string.IsNullOrEmpty(operationResultError))
{
operationResult.Error = operationResultError;
}
_cache.Insert(GetKey(file), operationResult, TimeSpan.FromMinutes(10));
}
}
}
}
logger.DebugCheckConvertFilesStatusIterationEnd();
}
lock (_locker)
{
_timer.Change(_timerPeriod, _timerPeriod);
}
}
catch (Exception exception)
{
logger.ErrorWithException(exception);
lock (_locker)
{
_timer.Change(Timeout.Infinite, Timeout.Infinite);
}
}
finally
{
Monitor.Exit(_singleThread);
}
}
}
private string GetKey(File<T> f)
{
return string.Format("fileConvertation-{0}", f.Id);
}
internal async Task<string> FileJsonSerializerAsync(EntryStatusManager EntryManager, File<T> file, string folderTitle)
public async Task<string> FileJsonSerializerAsync(EntryStatusManager EntryManager, File<T> file, string folderTitle)
{
if (file == null)
{
@ -406,94 +168,85 @@ internal class FileConverterQueue<T> : IDisposable
}, options);
}
public void Dispose()
private bool Contains(File<T> file)
{
if (_timer != null)
var queueTasks = LoadFromCache();
return queueTasks.Any(x =>
{
_timer.Dispose();
var fileId = JsonDocument.Parse(x.Source).RootElement.GetProperty("id").Deserialize<T>();
var fileVersion = JsonDocument.Parse(x.Source).RootElement.GetProperty("version").Deserialize<int>();
return file.Id.ToString() == fileId.ToString() && file.Version == fileVersion;
});
}
private bool IsOrphanCacheItem(FileConverterOperationResult x)
{
return !string.IsNullOrEmpty(x.Processed)
&& (x.Progress == 100 && DateTime.UtcNow - x.StopDateTime > TimeSpan.FromMinutes(1) ||
DateTime.UtcNow - x.StopDateTime > TimeSpan.FromMinutes(10));
}
private IEnumerable<FileConverterOperationResult> DeleteOrphanCacheItem(IEnumerable<FileConverterOperationResult> queueTasks)
{
var listTasks = queueTasks.ToList();
listTasks.RemoveAll(IsOrphanCacheItem);
SaveToCache(listTasks);
return queueTasks;
}
public IEnumerable<FileConverterOperationResult> GetAllTaskStatus()
{
var queueTasks = LoadFromCache();
queueTasks = DeleteOrphanCacheItem(queueTasks);
return queueTasks;
}
public void SaveToCache(IEnumerable<FileConverterOperationResult> queueTasks)
{
if (!queueTasks.Any())
{
_distributedCache.Remove(GetKey());
return;
}
using var ms = new MemoryStream();
ProtoBuf.Serializer.Serialize(ms, queueTasks);
_distributedCache.Set(GetKey(), ms.ToArray(), new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromMinutes(15)
});
}
private string GetKey()
{
return $"{_name}_{typeof(T).Name}".ToLowerInvariant();
}
private IEnumerable<FileConverterOperationResult> LoadFromCache()
{
var serializedObject = _distributedCache.Get(GetKey());
if (serializedObject == null)
{
return new List<FileConverterOperationResult>();
}
using var ms = new MemoryStream(serializedObject);
return ProtoBuf.Serializer.Deserialize<List<FileConverterOperationResult>>(ms);
}
}
[Scope]
public class FileConverterQueueScope
{
private readonly ILogger _options;
private readonly TenantManager _tenantManager;
private readonly UserManager _userManager;
private readonly SecurityContext _securityContext;
private readonly IDaoFactory _daoFactory;
private readonly FileSecurity _fileSecurity;
private readonly PathProvider _pathProvider;
private readonly SetupInfo _setupInfo;
private readonly FileUtility _fileUtility;
private readonly DocumentServiceHelper _documentServiceHelper;
private readonly DocumentServiceConnector _documentServiceConnector;
private readonly EntryStatusManager _entryManager;
private readonly FileConverter _fileConverter;
public FileConverterQueueScope(
ILogger<FileConverterQueueScope> options,
TenantManager tenantManager,
UserManager userManager,
SecurityContext securityContext,
IDaoFactory daoFactory,
FileSecurity fileSecurity,
PathProvider pathProvider,
SetupInfo setupInfo,
FileUtility fileUtility,
DocumentServiceHelper documentServiceHelper,
DocumentServiceConnector documentServiceConnector,
EntryStatusManager entryManager,
FileConverter fileConverter)
{
_options = options;
_tenantManager = tenantManager;
_userManager = userManager;
_securityContext = securityContext;
_daoFactory = daoFactory;
_fileSecurity = fileSecurity;
_pathProvider = pathProvider;
_setupInfo = setupInfo;
_fileUtility = fileUtility;
_documentServiceHelper = documentServiceHelper;
_documentServiceConnector = documentServiceConnector;
_entryManager = entryManager;
_fileConverter = fileConverter;
}
public void Deconstruct(out ILogger optionsMonitor,
out TenantManager tenantManager,
out UserManager userManager,
out SecurityContext securityContext,
out IDaoFactory daoFactory,
out FileSecurity fileSecurity,
out PathProvider pathProvider,
out SetupInfo setupInfo,
out FileUtility fileUtility,
out DocumentServiceHelper documentServiceHelper,
out DocumentServiceConnector documentServiceConnector,
out EntryStatusManager entryManager,
out FileConverter fileConverter)
{
optionsMonitor = _options;
tenantManager = _tenantManager;
userManager = _userManager;
securityContext = _securityContext;
daoFactory = _daoFactory;
fileSecurity = _fileSecurity;
pathProvider = _pathProvider;
setupInfo = _setupInfo;
fileUtility = _fileUtility;
documentServiceHelper = _documentServiceHelper;
documentServiceConnector = _documentServiceConnector;
entryManager = _entryManager;
fileConverter = _fileConverter;
}
}
public class FileJsonSerializerData<T>
{
public T Id { get; set; }
@ -710,9 +463,9 @@ public class FileConverter
var uriTuple = await _documentServiceConnector.GetConvertedUriAsync(fileUri, fileExtension, toExtension, docKey, null, null, null, false);
var convertUri = uriTuple.ConvertedDocumentUri;
var operationResult = new ConvertFileOperationResult
var operationResult = new FileConverterOperationResult
{
Source = string.Format("{{\"id\":\"{0}\", \"version\":\"{1}\"}}", file.Id, file.Version),
Source = System.Text.Json.JsonSerializer.Serialize(new { id = file.Id, version = file.Version }),
OperationType = FileOperationType.Convert,
Error = string.Empty,
Progress = 0,
@ -720,7 +473,7 @@ public class FileConverter
Processed = "",
Id = string.Empty,
TenantId = _tenantManager.GetCurrentTenant().Id,
Account = _authContext.CurrentAccount,
Account = _authContext.CurrentAccount.ID,
Delete = false,
StartDateTime = DateTime.Now,
Url = _httpContextAccesor?.HttpContext != null ? _httpContextAccesor.HttpContext.Request.GetUrlRewriter().ToString() : null,
@ -918,27 +671,6 @@ internal class FileComparer<T> : IEqualityComparer<File<T>>
}
}
internal class ConvertFileOperationResult : FileOperationResult
{
public DateTime StartDateTime { get; set; }
public DateTime StopDateTime { get; set; }
public int TenantId { get; set; }
public IAccount Account { get; set; }
public bool Delete { get; set; }
public string Url { get; set; }
public string Password { get; set; }
//hack for download
public string ServerRootPath { get; set; }
}
public static class FileConverterQueueExtension
{
public static void Register(DIHelper services)
{
services.TryAdd<FileConverterQueueScope>();
}
}
public static class FileConverterExtension
{

View File

@ -0,0 +1,56 @@
// (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.Web.Files.Utils;
[ProtoContract]
public class FileConverterOperationResult : FileOperationResult
{
[ProtoMember(1)]
public DateTime StartDateTime { get; set; }
[ProtoMember(2)]
public DateTime StopDateTime { get; set; }
[ProtoMember(3)]
public int TenantId { get; set; }
[ProtoMember(4)]
public Guid Account { get; set; }
[ProtoMember(5)]
public bool Delete { get; set; }
[ProtoMember(6)]
public string Url { get; set; }
[ProtoMember(7)]
public string Password { get; set; }
[ProtoMember(8)]
//hack for download
public string ServerRootPath { get; set; }
}

View File

@ -0,0 +1,355 @@
// (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
using System.Globalization;
using System.Text.Json;
using ASC.Common.Log;
using ASC.Core.Common.Hosting;
using ASC.Core.Common.Hosting.Interfaces;
using ASC.Files.Core.Log;
using ASC.Files.Core.Resources;
using ASC.Web.Files.Utils;
using ASC.Web.Studio.Core;
using static ASC.Web.Core.Files.DocumentService;
namespace ASC.Files.ThumbnailBuilder;
[Singletone(Additional = typeof(FileConverterQueueExtension))]
internal class FileConverterService<T> : BackgroundService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly int _timerDelay = 1000;
public FileConverterService(
IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var serviceScope = _serviceScopeFactory.CreateScope();
var registerInstanceService = serviceScope.ServiceProvider.GetService<IRegisterInstanceManager<FileConverterService<T>>>();
if (!await registerInstanceService.IsActive(RegisterInstanceWorkerService<FileConverterService<T>>.InstanceId))
{
await Task.Delay(1000, stoppingToken);
continue;
}
await ExecuteCheckFileConverterStatus(serviceScope);
await Task.Delay(_timerDelay, stoppingToken);
}
}
private async Task ExecuteCheckFileConverterStatus(IServiceScope scope)
{
TenantManager tenantManager;
UserManager userManager;
SecurityContext securityContext;
IDaoFactory daoFactory;
FileSecurity fileSecurity;
PathProvider pathProvider;
SetupInfo setupInfo;
FileUtility fileUtility;
DocumentServiceHelper documentServiceHelper;
DocumentServiceConnector documentServiceConnector;
EntryStatusManager entryManager;
FileConverter fileConverter;
FileConverterQueue<T> fileConverterQueue;
var logger = scope.ServiceProvider.GetService<ILogger<FileConverterQueue<T>>>();
try
{
var scopeClass = scope.ServiceProvider.GetService<FileConverterQueueScope>();
(_, tenantManager, userManager, securityContext, daoFactory, fileSecurity, pathProvider, setupInfo, fileUtility, documentServiceHelper, documentServiceConnector, entryManager, fileConverter) = scopeClass;
fileConverterQueue = scope.ServiceProvider.GetService<FileConverterQueue<T>>();
var _conversionQueue = fileConverterQueue.GetAllTaskStatus().ToList();
var filesIsConverting = _conversionQueue
.Where(x => string.IsNullOrEmpty(x.Processed))
.ToList();
foreach (var converter in filesIsConverting)
{
var fileId = JsonDocument.Parse(converter.Source).RootElement.GetProperty("id").Deserialize<T>();
var fileVersion = JsonDocument.Parse(converter.Source).RootElement.GetProperty("version").Deserialize<int>();
int operationResultProgress;
var password = converter.Password;
var commonLinkUtilitySettings = scope.ServiceProvider.GetService<CommonLinkUtilitySettings>();
commonLinkUtilitySettings.ServerUri = converter.ServerRootPath;
tenantManager.SetCurrentTenant(converter.TenantId);
securityContext.AuthenticateMeWithoutCookie(converter.Account);
var file = await daoFactory.GetFileDao<T>().GetFileAsync(fileId, fileVersion);
var fileUri = file.Id.ToString();
string convertedFileUrl;
try
{
var user = userManager.GetUsers(converter.Account);
var culture = string.IsNullOrEmpty(user.CultureName) ? tenantManager.GetCurrentTenant().GetCulture() : CultureInfo.GetCultureInfo(user.CultureName);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
if (!fileSecurity.CanReadAsync(file).Result && file.RootFolderType != FolderType.BUNCH)
{
//No rights in CRM after upload before attach
throw new System.Security.SecurityException(FilesCommonResource.ErrorMassage_SecurityException_ReadFile);
}
if (file.ContentLength > setupInfo.AvailableFileSize)
{
throw new Exception(string.Format(FilesCommonResource.ErrorMassage_FileSizeConvert, FileSizeComment.FilesSizeToString(setupInfo.AvailableFileSize)));
}
fileUri = pathProvider.GetFileStreamUrl(file);
var toExtension = fileUtility.GetInternalExtension(file.Title);
var fileExtension = file.ConvertedExtension;
var docKey = documentServiceHelper.GetDocKey(file);
fileUri = documentServiceConnector.ReplaceCommunityAdress(fileUri);
(operationResultProgress, convertedFileUrl) = documentServiceConnector.GetConvertedUriAsync(fileUri, fileExtension, toExtension, docKey, password, null, null, true).Result;
}
catch (Exception exception)
{
var password1 = exception.InnerException is DocumentServiceException documentServiceException
&& documentServiceException.Code == DocumentServiceException.ErrorCode.ConvertPassword;
logger.ErrorConvertFileWithUrl(file.Id.ToString(), fileUri, exception);
var operationResult = converter;
if (operationResult.Delete)
{
_conversionQueue.Remove(operationResult);
}
else
{
operationResult.Progress = 100;
operationResult.StopDateTime = DateTime.UtcNow;
operationResult.Error = exception.Message;
if (password1)
{
operationResult.Result = "password";
}
}
continue;
}
operationResultProgress = Math.Min(operationResultProgress, 100);
if (operationResultProgress < 100)
{
var operationResult = converter;
if (DateTime.Now - operationResult.StartDateTime > TimeSpan.FromMinutes(10))
{
operationResult.StopDateTime = DateTime.UtcNow;
operationResult.Error = FilesCommonResource.ErrorMassage_ConvertTimeout;
logger.ErrorCheckConvertFilesStatus(file.Id.ToString(), file.ContentLength);
}
else
{
operationResult.Processed = "";
}
operationResult.Progress = operationResultProgress;
logger.DebugCheckConvertFilesStatusIterationContinue();
continue;
}
File<T> newFile = null;
var operationResultError = string.Empty;
try
{
newFile = fileConverter.SaveConvertedFileAsync(file, convertedFileUrl).Result;
}
catch (Exception e)
{
operationResultError = e.Message;
logger.ErrorOperation(operationResultError, convertedFileUrl, fileUri, e);
continue;
}
finally
{
var operationResult = converter;
if (operationResult.Delete)
{
_conversionQueue.Remove(operationResult);
}
else
{
if (newFile != null)
{
var folderDao = daoFactory.GetFolderDao<T>();
var folder = folderDao.GetFolderAsync(newFile.ParentId).Result;
var folderTitle = fileSecurity.CanReadAsync(folder).Result ? folder.Title : null;
operationResult.Result = fileConverterQueue.FileJsonSerializerAsync(entryManager, newFile, folderTitle).Result;
}
operationResult.Progress = 100;
operationResult.StopDateTime = DateTime.UtcNow;
operationResult.Processed = "1";
if (!string.IsNullOrEmpty(operationResultError))
{
operationResult.Error = operationResultError;
}
}
}
logger.DebugCheckConvertFilesStatusIterationEnd();
}
fileConverterQueue.SaveToCache(_conversionQueue);
}
catch (Exception exception)
{
logger.ErrorWithException(exception);
}
}
}
public static class FileConverterQueueExtension
{
public static void Register(DIHelper services)
{
services.TryAdd<FileConverterQueueScope>();
}
}
[Scope]
public class FileConverterQueueScope
{
private readonly ILogger _options;
private readonly TenantManager _tenantManager;
private readonly UserManager _userManager;
private readonly SecurityContext _securityContext;
private readonly IDaoFactory _daoFactory;
private readonly FileSecurity _fileSecurity;
private readonly PathProvider _pathProvider;
private readonly SetupInfo _setupInfo;
private readonly FileUtility _fileUtility;
private readonly DocumentServiceHelper _documentServiceHelper;
private readonly DocumentServiceConnector _documentServiceConnector;
private readonly EntryStatusManager _entryManager;
private readonly FileConverter _fileConverter;
public FileConverterQueueScope(
ILogger<FileConverterQueueScope> options,
TenantManager tenantManager,
UserManager userManager,
SecurityContext securityContext,
IDaoFactory daoFactory,
FileSecurity fileSecurity,
PathProvider pathProvider,
SetupInfo setupInfo,
FileUtility fileUtility,
DocumentServiceHelper documentServiceHelper,
DocumentServiceConnector documentServiceConnector,
EntryStatusManager entryManager,
FileConverter fileConverter)
{
_options = options;
_tenantManager = tenantManager;
_userManager = userManager;
_securityContext = securityContext;
_daoFactory = daoFactory;
_fileSecurity = fileSecurity;
_pathProvider = pathProvider;
_setupInfo = setupInfo;
_fileUtility = fileUtility;
_documentServiceHelper = documentServiceHelper;
_documentServiceConnector = documentServiceConnector;
_entryManager = entryManager;
_fileConverter = fileConverter;
}
public void Deconstruct(out ILogger optionsMonitor,
out TenantManager tenantManager,
out UserManager userManager,
out SecurityContext securityContext,
out IDaoFactory daoFactory,
out FileSecurity fileSecurity,
out PathProvider pathProvider,
out SetupInfo setupInfo,
out FileUtility fileUtility,
out DocumentServiceHelper documentServiceHelper,
out DocumentServiceConnector documentServiceConnector,
out EntryStatusManager entryManager,
out FileConverter fileConverter)
{
optionsMonitor = _options;
tenantManager = _tenantManager;
userManager = _userManager;
securityContext = _securityContext;
daoFactory = _daoFactory;
fileSecurity = _fileSecurity;
pathProvider = _pathProvider;
setupInfo = _setupInfo;
fileUtility = _fileUtility;
documentServiceHelper = _documentServiceHelper;
documentServiceConnector = _documentServiceConnector;
entryManager = _entryManager;
fileConverter = _fileConverter;
}
}

View File

@ -25,7 +25,7 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Files.Core.Log;
internal static partial class FileConverterLogger
public static partial class FileConverterLogger
{
[LoggerMessage(Level = LogLevel.Debug, Message = "Run CheckConvertFilesStatus: count {count}")]
public static partial void DebugRunCheckConvertFilesStatus(this ILogger logger, int count);

View File

@ -68,6 +68,13 @@ builder.Host.ConfigureDefault(args, (hostContext, config, env, path) =>
diHelper.TryAdd<FileDataQueue>();
// services.AddActivePassiveHostedService<FileConverterService<string>>();
// diHelper.TryAdd<FileConverterService<string>>();
services.AddActivePassiveHostedService<FileConverterService<int>>();
diHelper.TryAdd<FileConverterService<int>>();
services.AddHostedService<ThumbnailBuilderService>();
diHelper.TryAdd<ThumbnailBuilderService>();