fileconverter: moved timer to separete worker service
This commit is contained in:
parent
3757174d08
commit
7b1ea58cdf
42
common/ASC.Common/Data/TypeExtension.cs
Normal file
42
common/ASC.Common/Data/TypeExtension.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -305,6 +305,13 @@ public class DistributedTaskQueue
|
|||||||
|
|
||||||
private void SaveToCache(IEnumerable<DistributedTask> queueTasks)
|
private void SaveToCache(IEnumerable<DistributedTask> queueTasks)
|
||||||
{
|
{
|
||||||
|
if (!queueTasks.Any())
|
||||||
|
{
|
||||||
|
_distributedCache.Remove(_name);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
using var ms = new MemoryStream();
|
using var ms = new MemoryStream();
|
||||||
|
|
||||||
Serializer.Serialize(ms, queueTasks);
|
Serializer.Serialize(ms, queueTasks);
|
||||||
@ -332,21 +339,11 @@ public class DistributedTaskQueue
|
|||||||
|
|
||||||
private IEnumerable<DistributedTask> DeleteOrphanCacheItem(IEnumerable<DistributedTask> queueTasks)
|
private IEnumerable<DistributedTask> DeleteOrphanCacheItem(IEnumerable<DistributedTask> queueTasks)
|
||||||
{
|
{
|
||||||
if (!queueTasks.Any())
|
var listTasks = queueTasks.ToList();
|
||||||
{
|
|
||||||
return queueTasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
var orphans = queueTasks.Where(IsOrphanCacheItem);
|
listTasks.RemoveAll(IsOrphanCacheItem);
|
||||||
|
|
||||||
if (!orphans.Any())
|
SaveToCache(listTasks);
|
||||||
{
|
|
||||||
return queueTasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
queueTasks = queueTasks.Except(queueTasks);
|
|
||||||
|
|
||||||
SaveToCache(queueTasks);
|
|
||||||
|
|
||||||
return queueTasks;
|
return queueTasks;
|
||||||
}
|
}
|
||||||
|
@ -149,3 +149,4 @@ global using ProtoBuf;
|
|||||||
global using Telegram.Bot;
|
global using Telegram.Bot;
|
||||||
|
|
||||||
global using static ASC.Security.Cryptography.EmailValidationKeyProvider;
|
global using static ASC.Security.Cryptography.EmailValidationKeyProvider;
|
||||||
|
global using ASC.Common.Data;
|
@ -79,7 +79,7 @@ public class RegisterInstanceDao<T> : IRegisterInstanceDao<T> where T : IHostedS
|
|||||||
public async Task<IEnumerable<InstanceRegistration>> GetAll()
|
public async Task<IEnumerable<InstanceRegistration>> GetAll()
|
||||||
{
|
{
|
||||||
return await _instanceRegistrationContext.InstanceRegistrations
|
return await _instanceRegistrationContext.InstanceRegistrations
|
||||||
.Where(x => x.WorkerTypeName == typeof(T).Name)
|
.Where(x => x.WorkerTypeName == typeof(T).GetFormattedName())
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ public class RegisterInstanceManager<T> : IRegisterInstanceManager<T> where T :
|
|||||||
var instance = registeredInstance ?? new InstanceRegistration
|
var instance = registeredInstance ?? new InstanceRegistration
|
||||||
{
|
{
|
||||||
InstanceRegistrationId = instanceId,
|
InstanceRegistrationId = instanceId,
|
||||||
WorkerTypeName = typeof(T).Name
|
WorkerTypeName = typeof(T).GetFormattedName()
|
||||||
};
|
};
|
||||||
|
|
||||||
instance.LastUpdated = DateTime.UtcNow;
|
instance.LastUpdated = DateTime.UtcNow;
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
// 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
|
// 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]
|
[Singletone]
|
||||||
public class RegisterInstanceWorkerService<T> : BackgroundService where T : IHostedService
|
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 IHostApplicationLifetime _applicationLifetime;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
public static readonly string InstanceId =
|
public static readonly string InstanceId =
|
||||||
$"{typeof(T).Name}_{DateTime.UtcNow.Ticks}";
|
$"{typeof(T).GetFormattedName()}_{DateTime.UtcNow.Ticks}";
|
||||||
|
|
||||||
public RegisterInstanceWorkerService(
|
public RegisterInstanceWorkerService(
|
||||||
ILogger<RegisterInstanceWorkerService<T>> logger,
|
ILogger<RegisterInstanceWorkerService<T>> logger,
|
||||||
@ -58,7 +58,7 @@ public class RegisterInstanceWorkerService<T> : BackgroundService where T : IHos
|
|||||||
await registerInstanceService.Register(InstanceId);
|
await registerInstanceService.Register(InstanceId);
|
||||||
await registerInstanceService.DeleteOrphanInstances();
|
await registerInstanceService.DeleteOrphanInstances();
|
||||||
|
|
||||||
_logger.InformationWorkingRunnging(DateTimeOffset.Now);
|
_logger.TraceWorkingRunnging(DateTimeOffset.Now);
|
||||||
|
|
||||||
await Task.Delay(1000, stoppingToken);
|
await Task.Delay(1000, stoppingToken);
|
||||||
}
|
}
|
||||||
@ -87,5 +87,6 @@ public class RegisterInstanceWorkerService<T> : BackgroundService where T : IHos
|
|||||||
}
|
}
|
||||||
|
|
||||||
await base.StopAsync(cancellationToken);
|
await base.StopAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ internal static partial class RegisterInstanceWorkerServiceLogger
|
|||||||
[LoggerMessage(Level = LogLevel.Trace, Message = "DbUpdateConcurrencyException: then updating {instanceName} at {time} time.")]
|
[LoggerMessage(Level = LogLevel.Trace, Message = "DbUpdateConcurrencyException: then updating {instanceName} at {time} time.")]
|
||||||
public static partial void TraceDbUpdateConcurrencyException(this ILogger logger, string instanceName, DateTimeOffset time);
|
public static partial void TraceDbUpdateConcurrencyException(this ILogger logger, string instanceName, DateTimeOffset time);
|
||||||
|
|
||||||
[LoggerMessage(Level = LogLevel.Information, Message = "Worker running at: {time}")]
|
[LoggerMessage(Level = LogLevel.Trace, Message = "Worker running at: {time}")]
|
||||||
public static partial void InformationWorkingRunnging(this ILogger logger, DateTimeOffset time);
|
public static partial void TraceWorkingRunnging(this ILogger logger, DateTimeOffset time);
|
||||||
|
|
||||||
[LoggerMessage(Level = LogLevel.Information, Message = "UnRegister Instance {instanceName} running at: {time}.")]
|
[LoggerMessage(Level = LogLevel.Information, Message = "UnRegister Instance {instanceName} running at: {time}.")]
|
||||||
public static partial void InformationUnRegister(this ILogger logger, string instanceName, DateTimeOffset time);
|
public static partial void InformationUnRegister(this ILogger logger, string instanceName, DateTimeOffset time);
|
||||||
|
@ -26,16 +26,32 @@
|
|||||||
|
|
||||||
namespace ASC.Web.Files.Services.WCFService.FileOperations;
|
namespace ASC.Web.Files.Services.WCFService.FileOperations;
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
[ProtoInclude(100, typeof(FileConverterOperationResult))]
|
||||||
public class FileOperationResult
|
public class FileOperationResult
|
||||||
{
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("operation")]
|
[JsonPropertyName("operation")]
|
||||||
|
[ProtoMember(2)]
|
||||||
public FileOperationType OperationType { get; set; }
|
public FileOperationType OperationType { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
public int Progress { get; set; }
|
public int Progress { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
public string Source { get; set; }
|
public string Source { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
public string Result { get; set; }
|
public string Result { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(6)]
|
||||||
public string Error { get; set; }
|
public string Error { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(7)]
|
||||||
public string Processed { get; set; }
|
public string Processed { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(8)]
|
||||||
public bool Finished { get; set; }
|
public bool Finished { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -24,44 +24,40 @@
|
|||||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
// 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
|
// 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;
|
namespace ASC.Web.Files.Utils;
|
||||||
|
|
||||||
[Singletone(Additional = typeof(FileConverterQueueExtension))]
|
[Singletone]
|
||||||
internal class FileConverterQueue<T> : IDisposable
|
public class FileConverterQueue<T>
|
||||||
{
|
{
|
||||||
private readonly object _singleThread = new object();
|
private readonly object _locker = new object();
|
||||||
private readonly IDictionary<File<T>, ConvertFileOperationResult> _conversionQueue;
|
private readonly IDistributedCache _distributedCache;
|
||||||
private readonly Timer _timer;
|
private readonly string _name = "asc_file_converter_queue_";
|
||||||
private readonly object _locker;
|
|
||||||
private readonly ICache _cache;
|
|
||||||
private const int _timerPeriod = 500;
|
|
||||||
|
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
public FileConverterQueue(IDistributedCache distributedCache)
|
||||||
|
|
||||||
public FileConverterQueue(IServiceScopeFactory serviceScopeFactory, ICache cache)
|
|
||||||
{
|
{
|
||||||
_conversionQueue = new Dictionary<File<T>, ConvertFileOperationResult>(new FileComparer<T>());
|
_distributedCache = distributedCache;
|
||||||
_timer = new Timer(CheckConvertFilesStatus, null, 0, Timeout.Infinite);
|
|
||||||
_locker = new object();
|
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
|
||||||
_cache = cache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
lock (_locker)
|
||||||
{
|
{
|
||||||
if (_conversionQueue.ContainsKey(file))
|
if (Contains(file))
|
||||||
{
|
{
|
||||||
return;
|
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,
|
OperationType = FileOperationType.Convert,
|
||||||
Error = string.Empty,
|
Error = string.Empty,
|
||||||
Progress = 0,
|
Progress = 0,
|
||||||
@ -69,316 +65,82 @@ internal class FileConverterQueue<T> : IDisposable
|
|||||||
Processed = "",
|
Processed = "",
|
||||||
Id = string.Empty,
|
Id = string.Empty,
|
||||||
TenantId = tenantId,
|
TenantId = tenantId,
|
||||||
Account = account,
|
Account = account.ID,
|
||||||
Delete = deleteAfter,
|
Delete = deleteAfter,
|
||||||
StartDateTime = DateTime.Now,
|
StartDateTime = DateTime.Now,
|
||||||
Url = url,
|
Url = url,
|
||||||
Password = password,
|
Password = password,
|
||||||
ServerRootPath = serverRootPath
|
ServerRootPath = serverRootPath
|
||||||
};
|
};
|
||||||
_conversionQueue.Add(file, queueResult);
|
|
||||||
_cache.Insert(GetKey(file), queueResult, TimeSpan.FromMinutes(10));
|
Enqueue(queueResult);
|
||||||
|
|
||||||
_timer.Change(0, Timeout.Infinite);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 file = pair.Key;
|
||||||
var key = GetKey(file);
|
var operation = PeekTask(pair.Key);
|
||||||
var operation = _cache.Get<ConvertFileOperationResult>(key);
|
|
||||||
if (operation != null && (pair.Value || await fileSecurity.CanReadAsync(file)))
|
if (operation != null && (pair.Value || await fileSecurity.CanReadAsync(file)))
|
||||||
{
|
{
|
||||||
lock (_locker)
|
if (operation.Progress == 100)
|
||||||
{
|
{
|
||||||
if (operation.Progress == 100)
|
Dequeue(pair.Key);
|
||||||
{
|
|
||||||
_conversionQueue.Remove(file);
|
|
||||||
_cache.Remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return operation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return operation;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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)
|
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);
|
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)
|
private string GetKey(File<T> f)
|
||||||
{
|
{
|
||||||
return string.Format("fileConvertation-{0}", f.Id);
|
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)
|
if (file == null)
|
||||||
{
|
{
|
||||||
@ -406,94 +168,85 @@ internal class FileConverterQueue<T> : IDisposable
|
|||||||
}, options);
|
}, 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 class FileJsonSerializerData<T>
|
||||||
{
|
{
|
||||||
public T Id { get; set; }
|
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 uriTuple = await _documentServiceConnector.GetConvertedUriAsync(fileUri, fileExtension, toExtension, docKey, null, null, null, false);
|
||||||
var convertUri = uriTuple.ConvertedDocumentUri;
|
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,
|
OperationType = FileOperationType.Convert,
|
||||||
Error = string.Empty,
|
Error = string.Empty,
|
||||||
Progress = 0,
|
Progress = 0,
|
||||||
@ -720,7 +473,7 @@ public class FileConverter
|
|||||||
Processed = "",
|
Processed = "",
|
||||||
Id = string.Empty,
|
Id = string.Empty,
|
||||||
TenantId = _tenantManager.GetCurrentTenant().Id,
|
TenantId = _tenantManager.GetCurrentTenant().Id,
|
||||||
Account = _authContext.CurrentAccount,
|
Account = _authContext.CurrentAccount.ID,
|
||||||
Delete = false,
|
Delete = false,
|
||||||
StartDateTime = DateTime.Now,
|
StartDateTime = DateTime.Now,
|
||||||
Url = _httpContextAccesor?.HttpContext != null ? _httpContextAccesor.HttpContext.Request.GetUrlRewriter().ToString() : null,
|
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
|
public static class FileConverterExtension
|
||||||
{
|
{
|
||||||
|
@ -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; }
|
||||||
|
}
|
355
products/ASC.Files/Service/FileConverterService.cs
Normal file
355
products/ASC.Files/Service/FileConverterService.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -25,7 +25,7 @@
|
|||||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||||
|
|
||||||
namespace ASC.Files.Core.Log;
|
namespace ASC.Files.Core.Log;
|
||||||
internal static partial class FileConverterLogger
|
public static partial class FileConverterLogger
|
||||||
{
|
{
|
||||||
[LoggerMessage(Level = LogLevel.Debug, Message = "Run CheckConvertFilesStatus: count {count}")]
|
[LoggerMessage(Level = LogLevel.Debug, Message = "Run CheckConvertFilesStatus: count {count}")]
|
||||||
public static partial void DebugRunCheckConvertFilesStatus(this ILogger logger, int count);
|
public static partial void DebugRunCheckConvertFilesStatus(this ILogger logger, int count);
|
@ -68,6 +68,13 @@ builder.Host.ConfigureDefault(args, (hostContext, config, env, path) =>
|
|||||||
|
|
||||||
diHelper.TryAdd<FileDataQueue>();
|
diHelper.TryAdd<FileDataQueue>();
|
||||||
|
|
||||||
|
// services.AddActivePassiveHostedService<FileConverterService<string>>();
|
||||||
|
// diHelper.TryAdd<FileConverterService<string>>();
|
||||||
|
|
||||||
|
services.AddActivePassiveHostedService<FileConverterService<int>>();
|
||||||
|
diHelper.TryAdd<FileConverterService<int>>();
|
||||||
|
|
||||||
|
|
||||||
services.AddHostedService<ThumbnailBuilderService>();
|
services.AddHostedService<ThumbnailBuilderService>();
|
||||||
diHelper.TryAdd<ThumbnailBuilderService>();
|
diHelper.TryAdd<ThumbnailBuilderService>();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user