diff --git a/.nuget/packages/SharpZipLib.1.4.2.nupkg b/.nuget/packages/SharpZipLib.1.4.2.nupkg new file mode 100644 index 0000000000..797c71078e Binary files /dev/null and b/.nuget/packages/SharpZipLib.1.4.2.nupkg differ diff --git a/common/ASC.Api.Core/Core/CustomHealthCheck.cs b/common/ASC.Api.Core/Core/CustomHealthCheck.cs index 38f3417243..248a5fed19 100644 --- a/common/ASC.Api.Core/Core/CustomHealthCheck.cs +++ b/common/ASC.Api.Core/Core/CustomHealthCheck.cs @@ -88,6 +88,13 @@ public static class CustomHealthCheck if (redisConfiguration != null) { + // https://github.com/imperugo/StackExchange.Redis.Extensions/issues/513 + if (configuration.GetSection("Redis").GetValue("User") != null) + { + redisConfiguration.ConfigurationOptions.User = configuration.GetSection("Redis").GetValue("User"); + } + + hcBuilder.AddRedis(redisConfiguration.ConfigurationOptions.ToString(), name: "redis", tags: new string[] { "redis" }, diff --git a/common/ASC.Api.Core/Extensions/ServiceCollectionExtension.cs b/common/ASC.Api.Core/Extensions/ServiceCollectionExtension.cs index fdb7ec621d..d1861972a7 100644 --- a/common/ASC.Api.Core/Extensions/ServiceCollectionExtension.cs +++ b/common/ASC.Api.Core/Extensions/ServiceCollectionExtension.cs @@ -34,7 +34,13 @@ public static class ServiceCollectionExtension var rabbitMQConfiguration = configuration.GetSection("RabbitMQ").Get(); if (redisConfiguration != null) - { + { + // https://github.com/imperugo/StackExchange.Redis.Extensions/issues/513 + if (configuration.GetSection("Redis").GetValue("User") != null) + { + redisConfiguration.ConfigurationOptions.User = configuration.GetSection("Redis").GetValue("User"); + } + services.AddStackExchangeRedisExtensions(redisConfiguration); services.AddSingleton(typeof(ICacheNotify<>), typeof(RedisCacheNotify<>)); @@ -58,7 +64,13 @@ public static class ServiceCollectionExtension var redisConfiguration = configuration.GetSection("Redis").Get(); if (redisConfiguration != null) - { + { + // https://github.com/imperugo/StackExchange.Redis.Extensions/issues/513 + if (configuration.GetSection("Redis").GetValue("User") != null) + { + redisConfiguration.ConfigurationOptions.User = configuration.GetSection("Redis").GetValue("User"); + } + services.AddStackExchangeRedisCache(config => { config.ConfigurationOptions = redisConfiguration.ConfigurationOptions; diff --git a/common/ASC.Data.Backup.Core/Core/DbBackupProvider.cs b/common/ASC.Data.Backup.Core/Core/DbBackupProvider.cs index 7188c1df49..1d9c3fc486 100644 --- a/common/ASC.Data.Backup.Core/Core/DbBackupProvider.cs +++ b/common/ASC.Data.Backup.Core/Core/DbBackupProvider.cs @@ -45,7 +45,7 @@ public class DbBackupProvider : IBackupProvider public event EventHandler ProgressChanged; - public Task> GetElements(int tenant, string[] configs, IDataWriteOperator writer) + public async Task> GetElements(int tenant, string[] configs, IDataWriteOperator writer) { _processedTables.Clear(); var xml = new List(); @@ -66,11 +66,11 @@ public class DbBackupProvider : IBackupProvider else { connectionKeys.Add(connectionKey, connectionString.Name); - node.Add(BackupDatabase(tenant, connectionString, writer)); + node.Add(await BackupDatabase(tenant, connectionString, writer)); } } - return Task.FromResult(xml.AsEnumerable()); + return xml.AsEnumerable(); } public Task LoadFrom(IEnumerable elements, int tenant, string[] configs, IDataReadOperator reader) @@ -137,7 +137,7 @@ public class DbBackupProvider : IBackupProvider return ConfigurationManager.OpenExeConfiguration(config); } - private List BackupDatabase(int tenant, ConnectionStringSettings connectionString, IDataWriteOperator writer) + private async Task> BackupDatabase(int tenant, ConnectionStringSettings connectionString, IDataWriteOperator writer) { var xml = new List(); var errors = 0; @@ -187,7 +187,7 @@ public class DbBackupProvider : IBackupProvider using (var file = _tempStream.Create()) { dataTable.WriteXml(file, XmlWriteMode.WriteSchema); - writer.WriteEntry($"{Name}\\{connectionString.Name}\\{table}".ToLower(), file); + await writer.WriteEntryAsync($"{Name}\\{connectionString.Name}\\{table}".ToLower(), file); } _processedTables.Add(table); diff --git a/common/ASC.Data.Backup.Core/GlobalUsings.cs b/common/ASC.Data.Backup.Core/GlobalUsings.cs index c488a83300..fa9c684b43 100644 --- a/common/ASC.Data.Backup.Core/GlobalUsings.cs +++ b/common/ASC.Data.Backup.Core/GlobalUsings.cs @@ -32,16 +32,11 @@ global using System.Reflection; global using System.Security.Cryptography; global using System.ServiceModel; global using System.Text; -global using System.Text.RegularExpressions; +global using System.Text.RegularExpressions; global using System.Xml; -global using System.Xml.Linq; +global using System.Xml.Linq; global using System.Xml.XPath; -global using Amazon; -global using Amazon.S3; -global using Amazon.S3.Model; -global using Amazon.S3.Transfer; - global using ASC.Api.Utils; global using ASC.Common; global using ASC.Common.Caching; @@ -50,6 +45,7 @@ global using ASC.Common.Threading; global using ASC.Common.Utils; global using ASC.Core; global using ASC.Core.Billing; +global using ASC.Core.ChunkedUploader; global using ASC.Core.Common.Configuration; global using ASC.Core.Common.EF; global using ASC.Core.Common.EF.Context; @@ -73,6 +69,7 @@ global using ASC.Data.Backup.Utils; global using ASC.Data.Storage; global using ASC.Data.Storage.Configuration; global using ASC.Data.Storage.DiscStorage; +global using ASC.Data.Storage.ZipOperators; global using ASC.EventBus.Events; global using ASC.Files.Core; global using ASC.MessagingSystem.Core; @@ -91,9 +88,6 @@ global using ASC.Web.Studio.Utility; global using Autofac; -global using ICSharpCode.SharpZipLib.GZip; -global using ICSharpCode.SharpZipLib.Tar; - global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Http; global using Microsoft.EntityFrameworkCore; diff --git a/common/ASC.Data.Backup.Core/Service/ProgressItems/BackupProgressItem.cs b/common/ASC.Data.Backup.Core/Service/ProgressItems/BackupProgressItem.cs index 5add171d34..f7aaac89f5 100644 --- a/common/ASC.Data.Backup.Core/Service/ProgressItems/BackupProgressItem.cs +++ b/common/ASC.Data.Backup.Core/Service/ProgressItems/BackupProgressItem.cs @@ -23,31 +23,7 @@ // 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 - -/* - * - * (c) Copyright Ascensio System Limited 2010-2020 - * - * This program is freeware. You can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html). - * In accordance with Section 7(a) of the GNU GPL 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 more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html - * - * You can contact Ascensio System SIA by email at sales@onlyoffice.com - * - * The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display - * Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3. - * - * Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains - * relevant author attributions when distributing the software. If the display of the logo in its graphic - * form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE" - * in every copy of the program you distribute. - * Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks. - * -*/ + namespace ASC.Data.Backup.Services; @@ -69,6 +45,7 @@ public class BackupProgressItem : BaseBackupProgressItem private BackupStorageFactory _backupStorageFactory; private BackupRepository _backupRepository; private BackupPortalTask _backupPortalTask; + private TempStream _tempStream; private readonly ILogger _logger; private readonly CoreBaseSettings _coreBaseSettings; private readonly NotifyHelper _notifyHelper; @@ -122,18 +99,23 @@ public class BackupProgressItem : BaseBackupProgressItem _backupStorageFactory = scope.ServiceProvider.GetService(); _backupRepository = scope.ServiceProvider.GetService(); _backupPortalTask = scope.ServiceProvider.GetService(); + _tempStream = scope.ServiceProvider.GetService(); var dateTime = _coreBaseSettings.Standalone ? DateTime.Now : DateTime.UtcNow; var backupName = string.Format("{0}_{1:yyyy-MM-dd_HH-mm-ss}.{2}", _tenantManager.GetTenant(TenantId).Alias, dateTime, ArchiveFormat); var tempFile = CrossPlatform.PathCombine(TempFolder, backupName); - var storagePath = tempFile; + var storagePath = tempFile; + string hash; try { - var backupTask = _backupPortalTask; + var backupStorage = _backupStorageFactory.GetBackupStorage(_storageType, TenantId, StorageParams); + var writer = await ZipWriteOperatorFactory.GetWriteOperatorAsync(_tempStream, _storageBasePath, backupName, TempFolder, _userId, backupStorage as IGetterWriteOperator); + + var backupTask = _backupPortalTask; - backupTask.Init(TenantId, tempFile, _limit); + backupTask.Init(TenantId, tempFile, _limit, writer); backupTask.ProgressChanged += (sender, args) => { @@ -141,14 +123,19 @@ public class BackupProgressItem : BaseBackupProgressItem PublishChanges(); }; - await backupTask.RunJob(); - - var backupStorage = _backupStorageFactory.GetBackupStorage(_storageType, TenantId, StorageParams); - if (backupStorage != null) - { - storagePath = await backupStorage.Upload(_storageBasePath, tempFile, _userId); - Link = await backupStorage.GetPublicLink(storagePath); - } + await backupTask.RunJob(); + + if (writer.NeedUpload) + { + storagePath = await backupStorage.Upload(_storageBasePath, tempFile, _userId); + hash = BackupWorker.GetBackupHash(tempFile); + } + else + { + storagePath = writer.StoragePath; + hash = writer.Hash; + } + Link = await backupStorage.GetPublicLink(storagePath); var repo = _backupRepository; @@ -165,7 +152,7 @@ public class BackupProgressItem : BaseBackupProgressItem CreatedOn = DateTime.UtcNow, ExpiresOn = _storageType == BackupStorageType.DataStore ? DateTime.UtcNow.AddDays(1) : DateTime.MinValue, StorageParams = JsonConvert.SerializeObject(StorageParams), - Hash = BackupWorker.GetBackupHash(tempFile), + Hash = hash, Removed = false }); diff --git a/common/ASC.Data.Backup.Core/Service/ProgressItems/RestoreProgressItem.cs b/common/ASC.Data.Backup.Core/Service/ProgressItems/RestoreProgressItem.cs index 12172badff..62e49651a3 100644 --- a/common/ASC.Data.Backup.Core/Service/ProgressItems/RestoreProgressItem.cs +++ b/common/ASC.Data.Backup.Core/Service/ProgressItems/RestoreProgressItem.cs @@ -96,7 +96,8 @@ public class RestoreProgressItem : BaseBackupProgressItem TenantId = request.TenantId; Notify = request.NotifyAfterCompletion; StoragePath = request.FilePathOrId; - StorageType = request.StorageType; + StorageType = request.StorageType; + StorageParams = request.StorageParams; TempFolder = tempFolder; _upgradesPath = upgradesPath; _region = region; @@ -110,7 +111,6 @@ public class RestoreProgressItem : BaseBackupProgressItem try { - await using var scope = _serviceScopeProvider.CreateAsyncScope(); _tenantManager = scope.ServiceProvider.GetService(); @@ -186,7 +186,7 @@ public class RestoreProgressItem : BaseBackupProgressItem } _tenantManager.SaveTenant(restoredTenant); - _tenantManager.SetCurrentTenant(restoredTenant); + _tenantManager.SetCurrentTenant(restoredTenant); TenantId = restoredTenant.Id; _notifyHelper.SendAboutRestoreCompleted(restoredTenant, Notify); @@ -198,13 +198,13 @@ public class RestoreProgressItem : BaseBackupProgressItem File.Delete(tempFile); - Percentage = 100; + Percentage = 100; Status = DistributedTaskStatus.Completed; } catch (Exception error) { _logger.ErrorRestoreProgressItem(error); - Exception = error; + Exception = error; Status = DistributedTaskStatus.Failted; if (tenant != null) diff --git a/common/ASC.Data.Backup.Core/Storage/BackupStorageFactory.cs b/common/ASC.Data.Backup.Core/Storage/BackupStorageFactory.cs index df92d26ccc..f8b40b626b 100644 --- a/common/ASC.Data.Backup.Core/Storage/BackupStorageFactory.cs +++ b/common/ASC.Data.Backup.Core/Storage/BackupStorageFactory.cs @@ -31,7 +31,6 @@ public class BackupStorageFactory { private readonly ConfigurationExtension _configuration; private readonly DocumentsBackupStorage _documentsBackupStorage; - private readonly DataStoreBackupStorage _dataStoreBackupStorage; private readonly ILogger _logger; private readonly LocalBackupStorage _localBackupStorage; private readonly ConsumerBackupStorage _consumerBackupStorage; @@ -43,12 +42,10 @@ public class BackupStorageFactory ConfigurationExtension configuration, DocumentsBackupStorage documentsBackupStorage, TenantManager tenantManager, - DataStoreBackupStorage dataStoreBackupStorage, ILogger logger) { _configuration = configuration; _documentsBackupStorage = documentsBackupStorage; - _dataStoreBackupStorage = dataStoreBackupStorage; _logger = logger; _localBackupStorage = localBackupStorage; _consumerBackupStorage = consumerBackupStorage; @@ -78,15 +75,15 @@ public class BackupStorageFactory case BackupStorageType.Documents: case BackupStorageType.ThridpartyDocuments: { - _documentsBackupStorage.Init(tenantId); + _documentsBackupStorage.Init(tenantId); return _documentsBackupStorage; } case BackupStorageType.DataStore: { - _dataStoreBackupStorage.Init(tenantId); + _consumerBackupStorage.Init(tenantId); - return _dataStoreBackupStorage; + return _consumerBackupStorage; } case BackupStorageType.Local: return _localBackupStorage; diff --git a/common/ASC.Data.Backup.Core/Storage/ConsumerBackupStorage.cs b/common/ASC.Data.Backup.Core/Storage/ConsumerBackupStorage.cs index 635c8f4305..70e3771537 100644 --- a/common/ASC.Data.Backup.Core/Storage/ConsumerBackupStorage.cs +++ b/common/ASC.Data.Backup.Core/Storage/ConsumerBackupStorage.cs @@ -22,27 +22,50 @@ // // 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 - +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + namespace ASC.Data.Backup.Storage; [Scope] -public class ConsumerBackupStorage : IBackupStorage +public class ConsumerBackupStorage : IBackupStorage, IGetterWriteOperator { - private const string Domain = "backup"; - private IDataStore _store; - private readonly StorageSettingsHelper _storageSettingsHelper; + private readonly StorageSettingsHelper _storageSettingsHelper; + private readonly TempPath _tempPath; + private readonly ILogger _logger; + private readonly SetupInfo _setupInfo; + private readonly StorageFactory _storageFactory; + + private bool _isTemporary; + private string Domain { get => _isTemporary ? "" : "backup"; } + private CommonChunkedUploadSessionHolder _sessionHolder; - public ConsumerBackupStorage(StorageSettingsHelper storageSettingsHelper) + public ConsumerBackupStorage( + StorageSettingsHelper storageSettingsHelper, + TempPath tempPath, + ILogger logger, + SetupInfo setupInfo, + StorageFactory storageFactory) { - _storageSettingsHelper = storageSettingsHelper; + _storageSettingsHelper = storageSettingsHelper; + _tempPath = tempPath; + _logger = logger; + _setupInfo = setupInfo; + _storageFactory = storageFactory; } public void Init(IReadOnlyDictionary storageParams) { var settings = new StorageSettings { Module = storageParams["module"], Props = storageParams.Where(r => r.Key != "module").ToDictionary(r => r.Key, r => r.Value) }; - _store = _storageSettingsHelper.DataStore(settings); + _store = _storageSettingsHelper.DataStore(settings); + _sessionHolder = new CommonChunkedUploadSessionHolder(_tempPath, _logger, _store, Domain, _setupInfo.ChunkUploadSize); + } + + public void Init(int tenant) + { + _isTemporary = true; + _store = _storageFactory.GetStorage(tenant, "backup"); + _sessionHolder = new CommonChunkedUploadSessionHolder(_tempPath, _logger, _store, Domain, _setupInfo.ChunkUploadSize); } public async Task Upload(string storageBasePath, string localPath, Guid userId) @@ -82,6 +105,23 @@ public class ConsumerBackupStorage : IBackupStorage public async Task GetPublicLink(string storagePath) { - return (await _store.GetInternalUriAsync(Domain, storagePath, TimeSpan.FromDays(1), null)).AbsoluteUri; + if (_isTemporary) + { + return (await _store.GetPreSignedUriAsync(Domain, storagePath, TimeSpan.FromDays(1), null)).ToString(); + } + else + { + return (await _store.GetInternalUriAsync(Domain, storagePath, TimeSpan.FromDays(1), null)).AbsoluteUri; + } + } + + public async Task GetWriteOperatorAsync(string storageBasePath, string title, Guid userId) + { + var session = new CommonChunkedUploadSession(-1) + { + TempPath = title, + UploadId = await _store.InitiateChunkedUploadAsync(Domain, title) + }; + return _store.CreateDataWriteOperator(session, _sessionHolder); } } diff --git a/common/ASC.Data.Backup.Core/Storage/DataStoreBackupStorage.cs b/common/ASC.Data.Backup.Core/Storage/DataStoreBackupStorage.cs deleted file mode 100644 index 98b5a24621..0000000000 --- a/common/ASC.Data.Backup.Core/Storage/DataStoreBackupStorage.cs +++ /dev/null @@ -1,84 +0,0 @@ -// (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.Data.Backup.Storage; - -[Scope] -public class DataStoreBackupStorage : IBackupStorage -{ - private int _tenant; - private readonly StorageFactory _storageFactory; - - public DataStoreBackupStorage(StorageFactory storageFactory) - { - _storageFactory = storageFactory; - } - - public void Init(int tenant) - { - _tenant = tenant; - } - - public async Task Upload(string storageBasePath, string localPath, Guid userId) - { - using var stream = File.OpenRead(localPath); - var storagePath = Path.GetFileName(localPath); - await GetDataStore().SaveAsync("", storagePath, stream); - - return storagePath; - } - - public async Task Download(string storagePath, string targetLocalPath) - { - using var source = await GetDataStore().GetReadStreamAsync("", storagePath); - using var destination = File.OpenWrite(targetLocalPath); - source.CopyTo(destination); - } - - public async Task Delete(string storagePath) - { - var dataStore = GetDataStore(); - if (await dataStore.IsFileAsync("", storagePath)) - { - await dataStore.DeleteAsync("", storagePath); - } - } - - public async Task IsExists(string storagePath) - { - return await GetDataStore().IsFileAsync("", storagePath); - } - - public async Task GetPublicLink(string storagePath) - { - return (await GetDataStore().GetPreSignedUriAsync("", storagePath, TimeSpan.FromDays(1), null)).ToString(); - } - - protected virtual IDataStore GetDataStore() - { - return _storageFactory.GetStorage(_tenant, "backup"); - } -} diff --git a/common/ASC.Data.Backup.Core/Storage/DocumentsBackupStorage.cs b/common/ASC.Data.Backup.Core/Storage/DocumentsBackupStorage.cs index c7620ba800..3482a98664 100644 --- a/common/ASC.Data.Backup.Core/Storage/DocumentsBackupStorage.cs +++ b/common/ASC.Data.Backup.Core/Storage/DocumentsBackupStorage.cs @@ -24,10 +24,12 @@ // 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 ASC.Web.Files.Utils; + namespace ASC.Data.Backup.Storage; [Scope] -public class DocumentsBackupStorage : IBackupStorage +public class DocumentsBackupStorage : IBackupStorage, IGetterWriteOperator { private int _tenantId; private readonly SetupInfo _setupInfo; @@ -35,7 +37,10 @@ public class DocumentsBackupStorage : IBackupStorage private readonly SecurityContext _securityContext; private readonly IDaoFactory _daoFactory; private readonly StorageFactory _storageFactory; - private readonly IServiceProvider _serviceProvider; + private readonly IServiceProvider _serviceProvider; + private FilesChunkedUploadSessionHolder _sessionHolder; + private readonly TempPath _tempPath; + private readonly ILogger _logger; public DocumentsBackupStorage( SetupInfo setupInfo, @@ -43,21 +48,27 @@ public class DocumentsBackupStorage : IBackupStorage SecurityContext securityContext, IDaoFactory daoFactory, StorageFactory storageFactory, - IServiceProvider serviceProvider) + IServiceProvider serviceProvider, + TempPath tempPath, + ILogger logger) { _setupInfo = setupInfo; _tenantManager = tenantManager; _securityContext = securityContext; _daoFactory = daoFactory; _storageFactory = storageFactory; - _serviceProvider = serviceProvider; + _serviceProvider = serviceProvider; + _tempPath = tempPath; + _logger = logger; } public void Init(int tenantId) { _tenantId = tenantId; - } - + var store = _storageFactory.GetStorage(_tenantId, "files"); + _sessionHolder = new FilesChunkedUploadSessionHolder(_daoFactory, _tempPath, _logger, store, "", _setupInfo.ChunkUploadSize); + } + public async Task Upload(string folderId, string localPath, Guid userId) { _tenantManager.SetCurrentTenant(_tenantId); @@ -195,6 +206,48 @@ public class DocumentsBackupStorage : IBackupStorage { return false; } + } + + public async Task GetWriteOperatorAsync(string storageBasePath, string title, Guid userId) + { + _tenantManager.SetCurrentTenant(_tenantId); + if (!userId.Equals(Guid.Empty)) + { + _securityContext.AuthenticateMeWithoutCookie(userId); + } + else + { + var tenant = _tenantManager.GetTenant(_tenantId); + _securityContext.AuthenticateMeWithoutCookie(tenant.OwnerId); + } + if (int.TryParse(storageBasePath, out var fId)) + { + var uploadSession = await InitUploadChunkAsync(fId, title); + var folderDao = GetFolderDao(); + return folderDao.CreateDataWriteOperator(fId, uploadSession, _sessionHolder); + } + else + { + var uploadSession = await InitUploadChunkAsync(storageBasePath, title); + var folderDao = GetFolderDao(); + return folderDao.CreateDataWriteOperator(storageBasePath, uploadSession, _sessionHolder); + } + } + + private async Task InitUploadChunkAsync(T folderId, string title) + { + var folderDao = GetFolderDao(); + var fileDao = GetFileDao(); + + var folder = await folderDao.GetFolderAsync(folderId); + var newFile = _serviceProvider.GetService>(); + + newFile.Title = title; + newFile.ParentId = folder.Id; + + var chunkedUploadSession = await fileDao.CreateUploadSessionAsync(newFile, -1); + chunkedUploadSession.CheckQuota = false; + return chunkedUploadSession; } private IFolderDao GetFolderDao() diff --git a/common/ASC.Data.Backup.Core/Storage/LocalBackupStorage.cs b/common/ASC.Data.Backup.Core/Storage/LocalBackupStorage.cs index c0d341ccc9..af5caa4160 100644 --- a/common/ASC.Data.Backup.Core/Storage/LocalBackupStorage.cs +++ b/common/ASC.Data.Backup.Core/Storage/LocalBackupStorage.cs @@ -27,7 +27,7 @@ namespace ASC.Data.Backup.Storage; [Scope] -public class LocalBackupStorage : IBackupStorage +public class LocalBackupStorage : IBackupStorage, IGetterWriteOperator { public Task Upload(string storageBasePath, string localPath, Guid userId) { @@ -66,4 +66,9 @@ public class LocalBackupStorage : IBackupStorage { return Task.FromResult(string.Empty); } + + public Task GetWriteOperatorAsync(string storageBasePath, string title, Guid userId) + { + return Task.FromResult(null); + } } diff --git a/common/ASC.Data.Backup.Core/Storage/S3BackupStorage.cs b/common/ASC.Data.Backup.Core/Storage/S3BackupStorage.cs deleted file mode 100644 index 6fdad25a10..0000000000 --- a/common/ASC.Data.Backup.Core/Storage/S3BackupStorage.cs +++ /dev/null @@ -1,145 +0,0 @@ -// (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.Data.Backup.Storage; - -internal class S3BackupStorage : IBackupStorage -{ - private readonly string _accessKeyId; - private readonly string _secretAccessKey; - private readonly string _bucket; - private readonly string _region; - private readonly ILogger _logger; - - public S3BackupStorage(ILogger logger, string accessKeyId, string secretAccessKey, string bucket, string region) - { - _logger = logger; - _accessKeyId = accessKeyId; - _secretAccessKey = secretAccessKey; - _bucket = bucket; - _region = region; - } - - public async Task Upload(string storageBasePath, string localPath, Guid userId) - { - string key; - - if (string.IsNullOrEmpty(storageBasePath)) - { - key = "backup/" + Path.GetFileName(localPath); - } - else - { - key = string.Concat(storageBasePath.Trim(new char[] { ' ', '/', '\\' }), "/", Path.GetFileName(localPath)); - } - - using (var fileTransferUtility = new TransferUtility(_accessKeyId, _secretAccessKey, RegionEndpoint.GetBySystemName(_region))) - { - await fileTransferUtility.UploadAsync( - new TransferUtilityUploadRequest - { - BucketName = _bucket, - FilePath = localPath, - StorageClass = S3StorageClass.StandardInfrequentAccess, - PartSize = 6291456, // 6 MB. - Key = key - }); - } - - - return key; - } - - public async Task Download(string storagePath, string targetLocalPath) - { - var request = new GetObjectRequest - { - BucketName = _bucket, - Key = GetKey(storagePath), - }; - - using var s3 = GetClient(); - using var response = await s3.GetObjectAsync(request); - await response.WriteResponseStreamToFileAsync(targetLocalPath, true, new CancellationToken()); - } - - public async Task Delete(string storagePath) - { - using var s3 = GetClient(); - await s3.DeleteObjectAsync(new DeleteObjectRequest - { - BucketName = _bucket, - Key = GetKey(storagePath) - }); - } - - public async Task IsExists(string storagePath) - { - using var s3 = GetClient(); - try - { - var request = new ListObjectsRequest { BucketName = _bucket, Prefix = GetKey(storagePath) }; - var response = await s3.ListObjectsAsync(request); - - return response.S3Objects.Count > 0; - } - catch (AmazonS3Exception ex) - { - _logger.WarningWithException(ex); - - return false; - } - } - - public Task GetPublicLink(string storagePath) - { - using var s3 = GetClient(); - - return Task.FromResult(s3.GetPreSignedURL( - new GetPreSignedUrlRequest - { - BucketName = _bucket, - Key = GetKey(storagePath), - Expires = DateTime.UtcNow.AddDays(1), - Verb = HttpVerb.GET - })); - } - - private string GetKey(string fileName) - { - // return "backup/" + Path.GetFileName(fileName); - return fileName; - } - - private AmazonS3Client GetClient() - { - return new AmazonS3Client(_accessKeyId, _secretAccessKey, - new AmazonS3Config - { - RegionEndpoint = RegionEndpoint.GetBySystemName(_region) - }); - } -} diff --git a/common/ASC.Data.Backup.Core/Tasks/BackupPortalTask.cs b/common/ASC.Data.Backup.Core/Tasks/BackupPortalTask.cs index 1befd2127a..3b5ae9c157 100644 --- a/common/ASC.Data.Backup.Core/Tasks/BackupPortalTask.cs +++ b/common/ASC.Data.Backup.Core/Tasks/BackupPortalTask.cs @@ -60,12 +60,13 @@ public class BackupPortalTask : PortalTaskBase _tempStream = tempStream; } - public void Init(int tenantId, string toFilePath, int limit) + public void Init(int tenantId, string toFilePath, int limit, IDataWriteOperator writeOperator) { ArgumentNullOrEmptyException.ThrowIfNullOrEmpty(toFilePath); BackupFilePath = toFilePath; - Limit = limit; + Limit = limit; + WriteOperator = writeOperator; Init(tenantId); } @@ -75,12 +76,11 @@ public class BackupPortalTask : PortalTaskBase _logger.DebugBeginBackup(TenantId); _tenantManager.SetCurrentTenant(TenantId); - - using (var writer = new ZipWriteOperator(_tempStream, BackupFilePath)) + await using (WriteOperator) { if (_dump) { - await DoDump(writer); + await DoDump(WriteOperator); } else { @@ -93,11 +93,11 @@ public class BackupPortalTask : PortalTaskBase foreach (var module in modulesToProcess) { - DoBackupModule(writer, module); + await DoBackupModule(WriteOperator, module); } if (ProcessStorage) { - await DoBackupStorage(writer, fileGroups); + await DoBackupStorage(WriteOperator, fileGroups); } } } @@ -157,7 +157,7 @@ public class BackupPortalTask : PortalTaskBase using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(true.ToString()))) { - writer.WriteEntry(KeyHelper.GetDumpKey(), stream); + await writer.WriteEntryAsync(KeyHelper.GetDumpKey(), stream); } var files = new List(); @@ -184,7 +184,7 @@ public class BackupPortalTask : PortalTaskBase foreach (var db in databases) { - DoDump(writer, db.Key.Item1, db.Key.Item2, db.Value); + await DoDump(writer, db.Key.Item1, db.Key.Item2, db.Value); } var dir = Path.GetDirectoryName(BackupFilePath); var subDir = Path.Combine(dir, Path.GetFileNameWithoutExtension(BackupFilePath)); @@ -194,11 +194,11 @@ public class BackupPortalTask : PortalTaskBase if (ProcessStorage) { - DoDumpStorage(writer, files); + await DoDumpStorage(writer, files); } } - private void DoDump(IDataWriteOperator writer, string dbName, string connectionString, List tables) + private async Task DoDump(IDataWriteOperator writer, string dbName, string connectionString, List tables) { var excluded = ModuleProvider.AllModules.Where(r => _ignoredModules.Contains(r.ModuleName)).SelectMany(r => r.Tables).Select(r => r.Name).ToList(); excluded.AddRange(_ignoredTables); @@ -254,7 +254,7 @@ public class BackupPortalTask : PortalTaskBase Task.WaitAll(tasks.ToArray()); - ArchiveDir(writer, subDir); + await ArchiveDir(writer, subDir); } } @@ -524,7 +524,7 @@ public class BackupPortalTask : PortalTaskBase } } - private void DoDumpStorage(IDataWriteOperator writer, IReadOnlyList files) + private async Task DoDumpStorage(IDataWriteOperator writer, IReadOnlyList files) { _logger.DebugBeginBackupStorage(); @@ -549,7 +549,7 @@ public class BackupPortalTask : PortalTaskBase Task.WaitAll(tasks.ToArray()); - ArchiveDir(writer, subDir); + await ArchiveDir(writer, subDir); Directory.Delete(storageDir, true); } @@ -562,7 +562,7 @@ public class BackupPortalTask : PortalTaskBase using (var tmpFile = new FileStream(tmpPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose)) { restoreInfoXml.WriteTo(tmpFile); - writer.WriteEntry(KeyHelper.GetStorageRestoreInfoZipKey(), tmpFile); + await writer.WriteEntryAsync(KeyHelper.GetStorageRestoreInfoZipKey(), tmpFile); } SetStepCompleted(); @@ -599,7 +599,7 @@ public class BackupPortalTask : PortalTaskBase SetStepCompleted(); } - private void ArchiveDir(IDataWriteOperator writer, string subDir) + private async Task ArchiveDir(IDataWriteOperator writer, string subDir) { _logger.DebugArchiveDirStart(subDir); foreach (var enumerateFile in Directory.EnumerateFiles(subDir, "*", SearchOption.AllDirectories)) @@ -612,7 +612,7 @@ public class BackupPortalTask : PortalTaskBase using (var tmpFile = new FileStream(f, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose)) { - writer.WriteEntry(enumerateFile.Substring(subDir.Length), tmpFile); + await writer.WriteEntryAsync(enumerateFile.Substring(subDir.Length), tmpFile); } SetStepCompleted(); @@ -633,7 +633,7 @@ public class BackupPortalTask : PortalTaskBase return files.GroupBy(file => file.Module).ToList(); } - private void DoBackupModule(IDataWriteOperator writer, IModuleSpecifics module) + private async Task DoBackupModule(IDataWriteOperator writer, IModuleSpecifics module) { _logger.DebugBeginSavingDataForModule(module.ModuleName); var tablesToProcess = module.Tables.Where(t => !_ignoredTables.Contains(t.Name) && t.InsertMethod != InsertMethod.None).ToList(); @@ -684,7 +684,7 @@ public class BackupPortalTask : PortalTaskBase data.WriteXml(file, XmlWriteMode.WriteSchema); data.Clear(); - writer.WriteEntry(KeyHelper.GetTableZipKey(module, data.TableName), file); + await writer.WriteEntryAsync(KeyHelper.GetTableZipKey(module, data.TableName), file); } _logger.DebugEndSavingTable(table.Name); @@ -714,7 +714,7 @@ public class BackupPortalTask : PortalTaskBase { var f = (BackupFileInfo)state; using var fileStream = await storage.GetReadStreamAsync(f.Domain, f.Path); - writer.WriteEntry(file1.GetZipKey(), fileStream); + await writer.WriteEntryAsync(file1.GetZipKey(), fileStream); }, file, 5, error => _logger.WarningCanNotBackupFile(file1.Module, file1.Path, error)); SetCurrentStepProgress((int)(++filesProcessed * 100 / (double)filesCount)); @@ -730,7 +730,7 @@ public class BackupPortalTask : PortalTaskBase using (var tmpFile = _tempStream.Create()) { restoreInfoXml.WriteTo(tmpFile); - writer.WriteEntry(KeyHelper.GetStorageRestoreInfoZipKey(), tmpFile); + await writer.WriteEntryAsync(KeyHelper.GetStorageRestoreInfoZipKey(), tmpFile); } _logger.DebugEndBackupStorage(); diff --git a/common/ASC.Data.Backup.Core/Tasks/PortalTaskBase.cs b/common/ASC.Data.Backup.Core/Tasks/PortalTaskBase.cs index 911eb6f7c3..aa03dfe124 100644 --- a/common/ASC.Data.Backup.Core/Tasks/PortalTaskBase.cs +++ b/common/ASC.Data.Backup.Core/Tasks/PortalTaskBase.cs @@ -45,7 +45,8 @@ public abstract class PortalTaskBase protected ILogger Logger { get; set; } public int Progress { get; private set; } public int TenantId { get; private set; } - public bool ProcessStorage { get; set; } + public bool ProcessStorage { get; set; } + protected IDataWriteOperator WriteOperator { get; set; } protected ModuleProvider ModuleProvider { get; set; } protected DbFactory DbFactory { get; set; } diff --git a/common/ASC.Data.Backup.Core/Tasks/TransferPortalTask.cs b/common/ASC.Data.Backup.Core/Tasks/TransferPortalTask.cs index 896a5a6207..1dc4ef0f14 100644 --- a/common/ASC.Data.Backup.Core/Tasks/TransferPortalTask.cs +++ b/common/ASC.Data.Backup.Core/Tasks/TransferPortalTask.cs @@ -96,7 +96,7 @@ public class TransferPortalTask : PortalTaskBase //save db data to temporary file var backupTask = _serviceProvider.GetService(); - backupTask.Init(TenantId, backupFilePath, Limit); + backupTask.Init(TenantId, backupFilePath, Limit, ZipWriteOperatorFactory.GetDefaultWriteOperator(_tempStream, backupFilePath)); backupTask.ProcessStorage = false; backupTask.ProgressChanged += (sender, args) => SetCurrentStepProgress(args.Progress); foreach (var moduleName in _ignoredModules) diff --git a/common/ASC.Data.Storage/ASC.Data.Storage.csproj b/common/ASC.Data.Storage/ASC.Data.Storage.csproj index 81d6ea5efe..5f945e268f 100644 --- a/common/ASC.Data.Storage/ASC.Data.Storage.csproj +++ b/common/ASC.Data.Storage/ASC.Data.Storage.csproj @@ -45,6 +45,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/common/ASC.Data.Storage/BaseStorage.cs b/common/ASC.Data.Storage/BaseStorage.cs index 7f62d63737..76520ef9ca 100644 --- a/common/ASC.Data.Storage/BaseStorage.cs +++ b/common/ASC.Data.Storage/BaseStorage.cs @@ -192,6 +192,13 @@ public abstract class BaseStorage : IDataStore throw new NotImplementedException(); } + public virtual IDataWriteOperator CreateDataWriteOperator( + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder) + { + return new ChunkZipWriteOperator(_tempStream, chunkedUploadSession, sessionHolder); + } + #endregion public abstract Task DeleteAsync(string domain, string path); diff --git a/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSession.cs b/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSession.cs index 0364831379..c3801dae4b 100644 --- a/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSession.cs +++ b/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSession.cs @@ -34,7 +34,8 @@ public class CommonChunkedUploadSession : ICloneable public DateTime Expired { get; set; } public string Location { get; set; } public long BytesUploaded { get; set; } - public long BytesTotal { get; set; } + public long BytesTotal { get; set; } + public bool LastChunk { get; set; } public int TenantId { get; set; } public Guid UserId { get; set; } public bool UseChunks { get; set; } @@ -72,7 +73,8 @@ public class CommonChunkedUploadSession : ICloneable Created = DateTime.UtcNow; BytesUploaded = 0; BytesTotal = bytesTotal; - UseChunks = true; + UseChunks = true; + LastChunk = false; } public T GetItemOrDefault(string key) diff --git a/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSessionHolder.cs b/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSessionHolder.cs index 3974867221..e82b1bb32f 100644 --- a/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSessionHolder.cs +++ b/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSessionHolder.cs @@ -34,9 +34,10 @@ public class CommonChunkedUploadSessionHolder private readonly TempPath _tempPath; private readonly ILogger _logger; private readonly string _domain; - private readonly long _maxChunkUploadSize; + public long MaxChunkUploadSize; private const string StoragePath = "sessions"; + private readonly object _locker = new object(); public CommonChunkedUploadSessionHolder( TempPath tempPath, @@ -49,7 +50,7 @@ public class CommonChunkedUploadSessionHolder _logger = logger; DataStore = dataStore; _domain = domain; - _maxChunkUploadSize = maxChunkUploadSize; + MaxChunkUploadSize = maxChunkUploadSize; } public async Task DeleteExpiredAsync() @@ -83,7 +84,7 @@ public class CommonChunkedUploadSessionHolder public Task InitAsync(CommonChunkedUploadSession chunkedUploadSession) { - if (chunkedUploadSession.BytesTotal < _maxChunkUploadSize) + if (chunkedUploadSession.BytesTotal < MaxChunkUploadSize && chunkedUploadSession.BytesTotal != -1) { chunkedUploadSession.UseChunks = false; return Task.CompletedTask; @@ -101,15 +102,14 @@ public class CommonChunkedUploadSessionHolder chunkedUploadSession.UploadId = uploadId; } - public async Task FinalizeAsync(CommonChunkedUploadSession uploadSession) + public virtual async Task FinalizeAsync(CommonChunkedUploadSession uploadSession) { var tempPath = uploadSession.TempPath; var uploadId = uploadSession.UploadId; - var eTags = uploadSession.GetItemOrDefault>("ETag") - .Select((x, i) => new KeyValuePair(i + 1, x)) - .ToDictionary(x => x.Key, x => x.Value); + var eTags = uploadSession.GetItemOrDefault>("ETag"); - await DataStore.FinalizeChunkedUploadAsync(_domain, tempPath, uploadId, eTags); + await DataStore.FinalizeChunkedUploadAsync(_domain, tempPath, uploadId, eTags); + return Path.GetFileName(tempPath); } public async Task MoveAsync(CommonChunkedUploadSession chunkedUploadSession, string newPath, bool quotaCheckFileSize = true) @@ -132,23 +132,29 @@ public class CommonChunkedUploadSessionHolder } } - public async Task UploadChunkAsync(CommonChunkedUploadSession uploadSession, Stream stream, long length) + public virtual async Task UploadChunkAsync(CommonChunkedUploadSession uploadSession, Stream stream, long length) { var tempPath = uploadSession.TempPath; var uploadId = uploadSession.UploadId; int chunkNumber; - int.TryParse(uploadSession.GetItemOrDefault("ChunksUploaded"), out chunkNumber); - chunkNumber++; + lock (_locker) + { + int.TryParse(uploadSession.GetItemOrDefault("ChunksUploaded"), out chunkNumber); + chunkNumber++; + uploadSession.Items["ChunksUploaded"] = chunkNumber.ToString(); + uploadSession.BytesUploaded += length; + } - var eTag = await DataStore.UploadChunkAsync(_domain, tempPath, uploadId, stream, _maxChunkUploadSize, chunkNumber, length); + var eTag = await DataStore.UploadChunkAsync(_domain, tempPath, uploadId, stream, MaxChunkUploadSize, chunkNumber, length); - uploadSession.Items["ChunksUploaded"] = chunkNumber.ToString(); - uploadSession.BytesUploaded += length; - - var eTags = uploadSession.GetItemOrDefault>("ETag") ?? new List(); - eTags.Add(eTag); - uploadSession.Items["ETag"] = eTags; + lock (_locker) + { + var eTags = uploadSession.GetItemOrDefault>("ETag") ?? new Dictionary(); + eTags.Add(chunkNumber, eTag); + uploadSession.Items["ETag"] = eTags; + } + return Path.GetFileName(tempPath); } public Stream UploadSingleChunk(CommonChunkedUploadSession uploadSession, Stream stream, long chunkLength) diff --git a/common/ASC.Data.Storage/GlobalUsings.cs b/common/ASC.Data.Storage/GlobalUsings.cs index f4bce2d0e5..dc7f7a96b0 100644 --- a/common/ASC.Data.Storage/GlobalUsings.cs +++ b/common/ASC.Data.Storage/GlobalUsings.cs @@ -66,6 +66,7 @@ global using ASC.Data.Storage.GoogleCloud; global using ASC.Data.Storage.Log; global using ASC.Data.Storage.RackspaceCloud; global using ASC.Data.Storage.S3; +global using ASC.Data.Storage.ZipOperators; global using ASC.EventBus.Events; global using ASC.Notify.Messages; global using ASC.Protos.Migration; @@ -74,6 +75,9 @@ global using ASC.Security.Cryptography; global using Google.Apis.Auth.OAuth2; global using Google.Cloud.Storage.V1; +global using ICSharpCode.SharpZipLib.GZip; +global using ICSharpCode.SharpZipLib.Tar; + global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; diff --git a/common/ASC.Data.Storage/IDataStore.cs b/common/ASC.Data.Storage/IDataStore.cs index 68d995b7d3..7d263731bf 100644 --- a/common/ASC.Data.Storage/IDataStore.cs +++ b/common/ASC.Data.Storage/IDataStore.cs @@ -31,6 +31,10 @@ namespace ASC.Data.Storage; /// public interface IDataStore { + IDataWriteOperator CreateDataWriteOperator( + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder); + IQuotaController QuotaController { get; set; } TimeSpan GetExpire(string domain); diff --git a/common/ASC.Data.Storage/S3/S3Storage.cs b/common/ASC.Data.Storage/S3/S3Storage.cs index c473c0bdb3..1b9e427d2e 100644 --- a/common/ASC.Data.Storage/S3/S3Storage.cs +++ b/common/ASC.Data.Storage/S3/S3Storage.cs @@ -383,6 +383,12 @@ public class S3Storage : BaseStorage await s3.AbortMultipartUploadAsync(request); } + public override IDataWriteOperator CreateDataWriteOperator(CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder) + { + return new S3ZipWriteOperator(_tempStream, chunkedUploadSession, sessionHolder); + } + #endregion public override async Task DeleteAsync(string domain, string path) diff --git a/common/ASC.Data.Storage/ZipOperators/ChunkZipWriteOperator.cs b/common/ASC.Data.Storage/ZipOperators/ChunkZipWriteOperator.cs new file mode 100644 index 0000000000..3dad0f86cd --- /dev/null +++ b/common/ASC.Data.Storage/ZipOperators/ChunkZipWriteOperator.cs @@ -0,0 +1,142 @@ +// (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.Data.Storage.ZipOperators; + +public class ChunkZipWriteOperator : IDataWriteOperator +{ + private readonly TarOutputStream _tarOutputStream; + private readonly GZipOutputStream _gZipOutputStream; + private readonly CommonChunkedUploadSession _chunkedUploadSession; + private readonly CommonChunkedUploadSessionHolder _sessionHolder; + private readonly SHA256 _sha; + private Stream _fileStream; + private readonly TempStream _tempStream; + + public string Hash { get; private set; } + public string StoragePath { get; private set; } + public bool NeedUpload + { + get + { + return false; + } + } + + public ChunkZipWriteOperator(TempStream tempStream, + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder) + { + _tempStream = tempStream; + _chunkedUploadSession = chunkedUploadSession; + _sessionHolder = sessionHolder; + + _fileStream = _tempStream.Create(); + _gZipOutputStream = new GZipOutputStream(_fileStream) + { + IsStreamOwner = false + }; + _tarOutputStream = new TarOutputStream(_gZipOutputStream, Encoding.UTF8); + _sha = SHA256.Create(); + } + + public async Task WriteEntryAsync(string key, Stream stream) + { + if (_fileStream == null) + { + _fileStream = _tempStream.Create(); + _gZipOutputStream.baseOutputStream_ = _fileStream; + } + using (var buffered = _tempStream.GetBuffered(stream)) + { + var entry = TarEntry.CreateTarEntry(key); + entry.Size = buffered.Length; + await _tarOutputStream.PutNextEntryAsync(entry, default); + buffered.Position = 0; + await buffered.CopyToAsync(_tarOutputStream); + await _tarOutputStream.FlushAsync(); + await _tarOutputStream.CloseEntryAsync(default); + } + + if (_fileStream.Length > _sessionHolder.MaxChunkUploadSize) + { + await UploadAsync(false); + } + } + + private async Task UploadAsync(bool last) + { + var chunkUploadSize = _sessionHolder.MaxChunkUploadSize; + + var buffer = new byte[chunkUploadSize]; + int bytesRead; + _fileStream.Position = 0; + while ((bytesRead = _fileStream.Read(buffer, 0, (int)chunkUploadSize)) > 0) + { + using (var theMemStream = new MemoryStream()) + { + await theMemStream.WriteAsync(buffer, 0, bytesRead); + theMemStream.Position = 0; + if (bytesRead == chunkUploadSize || last) + { + if (_fileStream.Position == _fileStream.Length) + { + _chunkedUploadSession.LastChunk = true; + } + + theMemStream.Position = 0; + StoragePath = await _sessionHolder.UploadChunkAsync(_chunkedUploadSession, theMemStream, theMemStream.Length); + } + else + { + await _fileStream.DisposeAsync(); + _fileStream = _tempStream.Create(); + _gZipOutputStream.baseOutputStream_ = _fileStream; + + await theMemStream.CopyToAsync(_fileStream); + _fileStream.Flush(); + } + _sha.TransformBlock(buffer, 0, bytesRead, buffer, 0); + } + } + if (last) + { + _sha.TransformFinalBlock(buffer, 0, 0); + } + } + + public async ValueTask DisposeAsync() + { + _tarOutputStream.Close(); + _tarOutputStream.Dispose(); + + await UploadAsync(true); + _fileStream.Dispose(); + + Hash = BitConverter.ToString(_sha.Hash).Replace("-", string.Empty); + _sha.Dispose(); + } +} \ No newline at end of file diff --git a/common/ASC.Data.Backup.Core/Core/IDataOperator.cs b/common/ASC.Data.Storage/ZipOperators/IDataOperator.cs similarity index 87% rename from common/ASC.Data.Backup.Core/Core/IDataOperator.cs rename to common/ASC.Data.Storage/ZipOperators/IDataOperator.cs index b21c7c9f7a..181ce81e96 100644 --- a/common/ASC.Data.Backup.Core/Core/IDataOperator.cs +++ b/common/ASC.Data.Storage/ZipOperators/IDataOperator.cs @@ -24,16 +24,20 @@ // 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.Data.Backup; +namespace ASC.Data.Storage.ZipOperators; -public interface IDataWriteOperator : IDisposable +public interface IDataWriteOperator : IAsyncDisposable { - void WriteEntry(string key, Stream stream); + Task WriteEntryAsync(string key, Stream stream); + bool NeedUpload { get; } + string Hash { get; } + string StoragePath { get; } } public interface IDataReadOperator : IDisposable { Stream GetEntry(string key); - IEnumerable GetEntries(string key); + IEnumerable GetEntries(string key); IEnumerable GetDirectories(string key); + } diff --git a/common/ASC.Data.Storage/ZipOperators/IGetterWriteOperator.cs b/common/ASC.Data.Storage/ZipOperators/IGetterWriteOperator.cs new file mode 100644 index 0000000000..0ecb2bc919 --- /dev/null +++ b/common/ASC.Data.Storage/ZipOperators/IGetterWriteOperator.cs @@ -0,0 +1,32 @@ +// (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.Data.Storage.ZipOperators; + +public interface IGetterWriteOperator +{ + Task GetWriteOperatorAsync(string storageBasePath, string title, Guid userId); +} diff --git a/common/ASC.Data.Storage/ZipOperators/S3ZipWriteOperator.cs b/common/ASC.Data.Storage/ZipOperators/S3ZipWriteOperator.cs new file mode 100644 index 0000000000..b918beb0d1 --- /dev/null +++ b/common/ASC.Data.Storage/ZipOperators/S3ZipWriteOperator.cs @@ -0,0 +1,148 @@ +// (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.Data.Storage.ZipOperators; + +public class S3ZipWriteOperator : IDataWriteOperator +{ + private readonly TarOutputStream _tarOutputStream; + private readonly GZipOutputStream _gZipOutputStream; + private readonly CommonChunkedUploadSession _chunkedUploadSession; + private readonly CommonChunkedUploadSessionHolder _sessionHolder; + private readonly SHA256 _sha; + private Stream _fileStream; + protected const int TasksLimit = 10; + private readonly List _tasks = new List(TasksLimit); + private readonly List _streams = new List(TasksLimit); + private readonly TempStream _tempStream; + + public string Hash { get; private set; } + public string StoragePath { get; private set; } + public bool NeedUpload + { + get + { + return false; + } + } + + public S3ZipWriteOperator(TempStream tempStream, + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder) + { + _tempStream = tempStream; + _chunkedUploadSession = chunkedUploadSession; + _sessionHolder = sessionHolder; + + + _fileStream = _tempStream.Create(); + _gZipOutputStream = new GZipOutputStream(_fileStream) + { + IsStreamOwner = false + }; + _tarOutputStream = new TarOutputStream(_gZipOutputStream, Encoding.UTF8); + _sha = SHA256.Create(); + } + + public async Task WriteEntryAsync(string key, Stream stream) + { + if (_fileStream == null) + { + _fileStream = _tempStream.Create(); + _gZipOutputStream.baseOutputStream_ = _fileStream; + } + using (var buffered = _tempStream.GetBuffered(stream)) + { + var entry = TarEntry.CreateTarEntry(key); + entry.Size = buffered.Length; + await _tarOutputStream.PutNextEntryAsync(entry, default); + buffered.Position = 0; + await buffered.CopyToAsync(_tarOutputStream); + await _tarOutputStream.FlushAsync(); + await _tarOutputStream.CloseEntryAsync(default); + } + + if (_fileStream.Length > _sessionHolder.MaxChunkUploadSize) + { + var fs = _fileStream; + _fileStream = null; + Upload(fs); + Computehash(fs, false); + } + } + + private void Computehash(Stream stream, bool last) + { + stream.Position = 0; + var buffer = new byte[_sessionHolder.MaxChunkUploadSize]; + int bytesRead; + while ((bytesRead = _fileStream.Read(buffer, 0, (int)_sessionHolder.MaxChunkUploadSize)) > 0) + { + _sha.TransformBlock(buffer, 0, bytesRead, buffer, 0); + } + if (last) + { + _sha.TransformFinalBlock(buffer, 0, 0); + } + } + + private void Upload(Stream stream) + { + stream.Position = 0; + if (_tasks.Count == TasksLimit) + { + Task.WaitAny(_tasks.ToArray()); + for (var i = 0; i < _tasks.Count; i++) + { + if (_tasks[i].IsCompleted) + { + _tasks.RemoveAt(i); + _streams[i].Dispose(); + _streams.RemoveAt(i); + } + } + } + _streams.Add(stream); + _tasks.Add(_sessionHolder.UploadChunkAsync(_chunkedUploadSession, stream, stream.Length)); + } + + public async ValueTask DisposeAsync() + { + _tarOutputStream.Close(); + _tarOutputStream.Dispose(); + + Upload(_fileStream); + Task.WaitAll(_tasks.ToArray()); + + StoragePath = await _sessionHolder.FinalizeAsync(_chunkedUploadSession); + + Computehash(_fileStream, true); + Hash = BitConverter.ToString(_sha.Hash).Replace("-", string.Empty); + _sha.Dispose(); + + _streams.ForEach(s => s.Dispose()); + } +} diff --git a/common/ASC.Data.Backup.Core/Core/ZipOperator.cs b/common/ASC.Data.Storage/ZipOperators/ZipOperator.cs similarity index 65% rename from common/ASC.Data.Backup.Core/Core/ZipOperator.cs rename to common/ASC.Data.Storage/ZipOperators/ZipOperator.cs index 4d707a94cc..76f4f38b4e 100644 --- a/common/ASC.Data.Backup.Core/Core/ZipOperator.cs +++ b/common/ASC.Data.Storage/ZipOperators/ZipOperator.cs @@ -24,55 +24,69 @@ // 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.Data.Backup; +namespace ASC.Data.Storage.ZipOperators; + public class ZipWriteOperator : IDataWriteOperator { - + private readonly GZipOutputStream _gZipOutputStream; private readonly TarOutputStream _tarOutputStream; + private readonly Stream _file; private readonly TempStream _tempStream; + public bool NeedUpload + { + get + { + return true; + } + } + + public string Hash => throw new NotImplementedException(); + + public string StoragePath => throw new NotImplementedException(); + public ZipWriteOperator(TempStream tempStream, string targetFile) { - var file = new FileStream(targetFile, FileMode.Create); - var gZipOutputStream = new GZipOutputStream(file); - _tarOutputStream = new TarOutputStream(gZipOutputStream, Encoding.UTF8); _tempStream = tempStream; + _file = new FileStream(targetFile, FileMode.Create); + _gZipOutputStream = new GZipOutputStream(_file); + _tarOutputStream = new TarOutputStream(_gZipOutputStream, Encoding.UTF8); } - public void WriteEntry(string key, Stream stream) + public async Task WriteEntryAsync(string key, Stream stream) { using (var buffered = _tempStream.GetBuffered(stream)) { var entry = TarEntry.CreateTarEntry(key); entry.Size = buffered.Length; - _tarOutputStream.PutNextEntry(entry); + await _tarOutputStream.PutNextEntryAsync(entry, default); buffered.Position = 0; - buffered.CopyTo(_tarOutputStream); - _tarOutputStream.CloseEntry(); + await buffered.CopyToAsync(_tarOutputStream); + await _tarOutputStream.CloseEntryAsync(default); } } - public void Dispose() + public async ValueTask DisposeAsync() { _tarOutputStream.Close(); - _tarOutputStream.Dispose(); + await _tarOutputStream.DisposeAsync(); } } public class ZipReadOperator : IDataReadOperator { - private readonly string _tmpDir; + private readonly string tmpdir; public ZipReadOperator(string targetFile) { - _tmpDir = Path.Combine(Path.GetDirectoryName(targetFile), Path.GetFileNameWithoutExtension(targetFile).Replace('>', '_').Replace(':', '_').Replace('?', '_')); + tmpdir = Path.Combine(Path.GetDirectoryName(targetFile), Path.GetFileNameWithoutExtension(targetFile).Replace('>', '_').Replace(':', '_').Replace('?', '_')); using (var stream = File.OpenRead(targetFile)) using (var reader = new GZipInputStream(stream)) using (var tarOutputStream = TarArchive.CreateInputTarArchive(reader, Encoding.UTF8)) { - tarOutputStream.ExtractContents(_tmpDir); + tarOutputStream.ExtractContents(tmpdir); } File.Delete(targetFile); @@ -80,32 +94,29 @@ public class ZipReadOperator : IDataReadOperator public Stream GetEntry(string key) { - var filePath = Path.Combine(_tmpDir, key); - - return File.Exists(filePath) - ? File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read) - : null; + var filePath = Path.Combine(tmpdir, key); + return File.Exists(filePath) ? File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read) : null; } public IEnumerable GetEntries(string key) { - var path = Path.Combine(_tmpDir, key); + var path = Path.Combine(tmpdir, key); var files = Directory.EnumerateFiles(path); - return files; - } - public IEnumerable GetDirectories(string key) - { - var path = Path.Combine(_tmpDir, key); - var files = Directory.EnumerateDirectories(path); - return files; - } + } + + public IEnumerable GetDirectories(string key) + { + var path = Path.Combine(tmpdir, key); + var files = Directory.EnumerateDirectories(path); + return files; + } public void Dispose() { - if (Directory.Exists(_tmpDir)) + if (Directory.Exists(tmpdir)) { - Directory.Delete(_tmpDir, true); + Directory.Delete(tmpdir, true); } } } diff --git a/common/ASC.Data.Storage/ZipOperators/ZipWriteOperatorFactory.cs b/common/ASC.Data.Storage/ZipOperators/ZipWriteOperatorFactory.cs new file mode 100644 index 0000000000..a0727f6787 --- /dev/null +++ b/common/ASC.Data.Storage/ZipOperators/ZipWriteOperatorFactory.cs @@ -0,0 +1,43 @@ +// (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.Data.Storage.ZipOperators; + +public static class ZipWriteOperatorFactory +{ + public static async Task GetWriteOperatorAsync(TempStream tempStream, string storageBasePath, string title, string tempFolder, Guid userId, IGetterWriteOperator getter) + { + var writer = await getter.GetWriteOperatorAsync(storageBasePath, title, userId); + + return writer ?? new ZipWriteOperator(tempStream, Path.Combine(tempFolder, title)); + } + + public static IDataWriteOperator GetDefaultWriteOperator(TempStream tempStream, string backupFilePath) + { + return new ZipWriteOperator(tempStream, backupFilePath); + } +} + \ No newline at end of file diff --git a/common/services/ASC.ApiSystem/Program.cs b/common/services/ASC.ApiSystem/Program.cs index d086c85bfd..b9e673fa67 100644 --- a/common/services/ASC.ApiSystem/Program.cs +++ b/common/services/ASC.ApiSystem/Program.cs @@ -50,13 +50,6 @@ var logger = LogManager.Setup() var path = builder.Configuration["pathToConf"]; logger.Debug("path: " + path); logger.Debug("EnvironmentName: " + builder.Environment.EnvironmentName); - -var redisConfiguration = builder.Configuration.GetSection("Redis").Get(); -logger.Error($"redisConfiguration is null: {redisConfiguration == null}"); -var kafkaConfiguration = builder.Configuration.GetSection("kafka").Get(); -logger.Debug($"kafkaConfiguration is null: {kafkaConfiguration == null}"); -var rabbitMQConfiguration = builder.Configuration.GetSection("RabbitMQ").Get(); -logger.Debug($"rabbitMQConfiguration is null: {rabbitMQConfiguration == null}"); try { diff --git a/packages/common/api/files/index.js b/packages/common/api/files/index.js index 28928741f4..6854cfe786 100644 --- a/packages/common/api/files/index.js +++ b/packages/common/api/files/index.js @@ -32,6 +32,17 @@ export function openEdit(fileId, version, doc, view) { return request(options); } +export function getReferenceData(object) { + const data = object; + const options = { + method: "post", + url: `/files/file/referencedata`, + data, + }; + + return request(options); +} + export function getFolderInfo(folderId) { const options = { method: "get", diff --git a/packages/editor/src/client/components/Editor.js b/packages/editor/src/client/components/Editor.js index 79abe347fc..d664ebca66 100644 --- a/packages/editor/src/client/components/Editor.js +++ b/packages/editor/src/client/components/Editor.js @@ -12,6 +12,7 @@ import { updateFile, checkFillFormDraft, convertFile, + getReferenceData, } from "@docspace/common/api/files"; import { EditorWrapper } from "../components/StyledEditor"; import { useTranslation } from "react-i18next"; @@ -232,6 +233,14 @@ function Editor({ } }; + const onSDKRequestReferenceData = async (event) => { + const referenceData = await getReferenceData( + event.data.referenceData ?? event.data + ); + + docEditor.setReferenceData(referenceData); + }; + const onMakeActionLink = (event) => { const url = window.location.href; const actionLink = config?.editorConfig?.actionLink; @@ -571,7 +580,8 @@ function Editor({ onRequestMailMergeRecipients, onRequestCompareFile, onRequestRestore, - onRequestHistory; + onRequestHistory, + onRequestReferenceData; // if (isSharingAccess) { // onRequestSharingSettings = onSDKRequestSharingSettings; @@ -599,8 +609,13 @@ function Editor({ onRequestRestore = onSDKRequestRestore; } + if (!fileInfo?.providerKey) { + onRequestReferenceData = onSDKRequestReferenceData; + } + const events = { events: { + onRequestReferenceData, onAppReady: onSDKAppReady, onDocumentStateChange: onDocumentStateChange, onMetaChange: onMetaChange, diff --git a/products/ASC.Files/Core/ASC.Files.Core.csproj b/products/ASC.Files/Core/ASC.Files.Core.csproj index b9369ce432..9bc59ccee0 100644 --- a/products/ASC.Files/Core/ASC.Files.Core.csproj +++ b/products/ASC.Files/Core/ASC.Files.Core.csproj @@ -27,7 +27,7 @@ - + @@ -170,60 +170,60 @@ FilesCommonResource.resx - - FilesCommonResource.resx - - - FilesCommonResource.resx - + + FilesCommonResource.resx + + + FilesCommonResource.resx + - FilesCommonResource.resx - - - FilesCommonResource.resx - + FilesCommonResource.resx + + + FilesCommonResource.resx + - FilesCommonResource.resx - + FilesCommonResource.resx + - FilesCommonResource.resx - - - FilesCommonResource.resx - - - FilesCommonResource.resx - - - FilesCommonResource.resx - - - FilesCommonResource.resx - - - FilesCommonResource.resx - - - FilesCommonResource.resx - - - FilesCommonResource.resx - - - FilesCommonResource.resx - - - FilesCommonResource.resx - + FilesCommonResource.resx + + + FilesCommonResource.resx + + + FilesCommonResource.resx + + + FilesCommonResource.resx + + + FilesCommonResource.resx + + + FilesCommonResource.resx + + + FilesCommonResource.resx + + + FilesCommonResource.resx + + + FilesCommonResource.resx + + + FilesCommonResource.resx + - FilesCommonResource.resx - - - FilesCommonResource.resx - - - FilesCommonResource.resx - + FilesCommonResource.resx + + + FilesCommonResource.resx + + + FilesCommonResource.resx + FilesPatternResource.resx diff --git a/products/ASC.Files/Core/ApiModels/RequestDto/GetReferenceDataDto.cs b/products/ASC.Files/Core/ApiModels/RequestDto/GetReferenceDataDto.cs new file mode 100644 index 0000000000..523b9532c2 --- /dev/null +++ b/products/ASC.Files/Core/ApiModels/RequestDto/GetReferenceDataDto.cs @@ -0,0 +1,34 @@ +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +namespace ASC.Files.Core.ApiModels.RequestDto; +public class GetReferenceDataDto +{ + public T FileKey { get; set; } + public string InstanceId { get; set; } + public T SourceFileId { get; set; } + public string Path { get; set; } +} \ No newline at end of file diff --git a/products/ASC.Files/Core/ApiModels/RequestDto/GetReferenceDataFromPathDto.cs b/products/ASC.Files/Core/ApiModels/RequestDto/GetReferenceDataFromPathDto.cs new file mode 100644 index 0000000000..ca3b39d585 --- /dev/null +++ b/products/ASC.Files/Core/ApiModels/RequestDto/GetReferenceDataFromPathDto.cs @@ -0,0 +1,30 @@ +// (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 + +public class GetReferenceDataFromPathDto +{ + public string Path { get; set; } +} \ No newline at end of file diff --git a/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs b/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs index a0e4688716..68c1198b4c 100644 --- a/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs +++ b/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs @@ -236,6 +236,7 @@ public interface IFileDao Task> CreateUploadSessionAsync(File file, long contentLength); Task> UploadChunkAsync(ChunkedUploadSession uploadSession, Stream chunkStream, long chunkLength); + Task> FinalizeUploadSessionAsync(ChunkedUploadSession uploadSession); Task AbortUploadSessionAsync(ChunkedUploadSession uploadSession); #endregion diff --git a/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs b/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs index a103328cbd..32fc27fe9b 100644 --- a/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs +++ b/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs @@ -211,6 +211,11 @@ public interface IFolderDao /// Maximum size of file which can be uploaded to folder Task GetMaxUploadSizeAsync(T folderId, bool chunkedUpload = false); + IDataWriteOperator CreateDataWriteOperator( + T folderId, + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder); + #region Only for TMFolderDao /// diff --git a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs index ab500b6f10..b1b8513845 100644 --- a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs +++ b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs @@ -1170,15 +1170,16 @@ internal class FileDao : AbstractDao, IFileDao await _chunkedUploadSessionHolder.UploadChunkAsync(uploadSession, stream, chunkLength); - if (uploadSession.BytesUploaded == uploadSession.BytesTotal) + if (uploadSession.BytesUploaded == uploadSession.BytesTotal || uploadSession.LastChunk) { + uploadSession.BytesTotal = uploadSession.BytesUploaded; uploadSession.File = await FinalizeUploadSessionAsync(uploadSession); } return uploadSession.File; } - private async Task> FinalizeUploadSessionAsync(ChunkedUploadSession uploadSession) + public async Task> FinalizeUploadSessionAsync(ChunkedUploadSession uploadSession) { await _chunkedUploadSessionHolder.FinalizeUploadSessionAsync(uploadSession); diff --git a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs index 13d02c0a9a..fcea1d7a45 100644 --- a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs +++ b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs @@ -48,6 +48,7 @@ internal class FolderDao : AbstractDao, IFolderDao private readonly CrossDao _crossDao; private readonly IMapper _mapper; private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1); + private readonly GlobalStore _globalStore; public FolderDao( FactoryIndexerFolder factoryIndexer, @@ -67,7 +68,8 @@ internal class FolderDao : AbstractDao, IFolderDao IDaoFactory daoFactory, ProviderFolderDao providerFolderDao, CrossDao crossDao, - IMapper mapper) + IMapper mapper, + GlobalStore globalStore) : base( dbContextManager, userManager, @@ -88,6 +90,7 @@ internal class FolderDao : AbstractDao, IFolderDao _providerFolderDao = providerFolderDao; _crossDao = crossDao; _mapper = mapper; + _globalStore = globalStore; } public async Task> GetFolderAsync(int folderId) @@ -159,7 +162,7 @@ internal class FolderDao : AbstractDao, IFolderDao return _mapper.Map>(dbFolder); } - + public IAsyncEnumerable> GetFoldersAsync(int parentId) { return GetFoldersAsync(parentId, default, FilterType.None, false, default, string.Empty); @@ -1496,12 +1499,12 @@ internal class FolderDao : AbstractDao, IFolderDao { var q2 = filesDbContext.Security.Where(r => r.TimeStamp > fromTime).Select(r => r.TenantId).Distinct(); - await foreach (var q in q2.AsAsyncEnumerable()) - { - yield return q; - } + await foreach (var q in q2.AsAsyncEnumerable()) + { + yield return q; } } + } private IQueryable BuildRoomsQuery(FilesDbContext filesDbContext, IQueryable query, FolderType filterByType, IEnumerable tags, Guid subjectId, bool searchByTags, bool withoutTags, bool searchByFilter, bool withSubfolders, bool excludeSubject, SubjectFilter subjectFilter, IEnumerable subjectEntriesIds) @@ -1634,6 +1637,14 @@ internal class FolderDao : AbstractDao, IFolderDao }; } + public IDataWriteOperator CreateDataWriteOperator( + int folderId, + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder) + { + return _globalStore.GetStore().CreateDataWriteOperator(chunkedUploadSession, sessionHolder); + } + private string GetProjectTitle(object folderID) { return ""; diff --git a/products/ASC.Files/Core/Core/FileStorageService.cs b/products/ASC.Files/Core/Core/FileStorageService.cs index 634c832d41..0ac804d3b6 100644 --- a/products/ASC.Files/Core/Core/FileStorageService.cs +++ b/products/ASC.Files/Core/Core/FileStorageService.cs @@ -2694,6 +2694,72 @@ public class FileStorageService //: IFileStorageService return InternalSharedUsersAsync(fileId); } + public async Task> GetReferenceDataAsync(T fileId, string portalName, T sourceFileId, string path) + { + File file = null; + var fileDao = _daoFactory.GetFileDao(); + if (portalName == _tenantManager.GetCurrentTenant().Id.ToString()) + { + file = await fileDao.GetFileAsync(fileId); + } + + if (file == null) + { + var source = await fileDao.GetFileAsync(sourceFileId); + + if (source == null) + { + return new FileReference + { + Error = FilesCommonResource.ErrorMassage_FileNotFound + }; + } + + if (!await _fileSecurity.CanReadAsync(source)) + { + return new FileReference + { + Error = FilesCommonResource.ErrorMassage_SecurityException_ReadFile + }; + } + + var folderDao = _daoFactory.GetFolderDao(); + var folder = await folderDao.GetFolderAsync(source.ParentId); + if (!await _fileSecurity.CanReadAsync(folder)) + { + return new FileReference + { + Error = FilesCommonResource.ErrorMassage_SecurityException_ReadFolder + }; + } + + var list = fileDao.GetFilesAsync(folder.Id, new OrderBy(SortedByType.AZ, true), FilterType.FilesOnly, false, Guid.Empty, path, false, false); + file = await list.FirstOrDefaultAsync(fileItem => fileItem.Title == path); + } + + if (!await _fileSecurity.CanReadAsync(file)) + { + return new FileReference + { + Error = FilesCommonResource.ErrorMassage_SecurityException_ReadFile + }; + } + + var fileReference = new FileReference + { + Path = file.Title, + ReferenceData = new FileReferenceData + { + FileKey = file.Id, + InstanceId = _tenantManager.GetCurrentTenant().Id.ToString() + }, + Url = _documentServiceConnector.ReplaceCommunityAdress(_pathProvider.GetFileStreamUrl(file, lastVersion: true)), + FileType = file.ConvertedExtension.Trim('.') + }; + fileReference.Token = _documentServiceHelper.GetSignature(fileReference); + return fileReference; + } + public async Task> InternalSharedUsersAsync(T fileId) { FileEntry file; diff --git a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFileDao.cs b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFileDao.cs index 525f40b4a6..bb1659135a 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFileDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFileDao.cs @@ -636,7 +636,7 @@ internal class BoxFileDao : BoxDaoBase, IFileDao uploadSession.BytesUploaded += chunkLength; - if (uploadSession.BytesUploaded == uploadSession.BytesTotal) + if (uploadSession.BytesUploaded == uploadSession.BytesTotal || uploadSession.LastChunk) { using var fs = new FileStream(uploadSession.GetItemOrDefault("TempPath"), FileMode.Open, FileAccess.Read, System.IO.FileShare.None, 4096, FileOptions.DeleteOnClose); @@ -650,6 +650,11 @@ internal class BoxFileDao : BoxDaoBase, IFileDao return uploadSession.File; } + public Task> FinalizeUploadSessionAsync(ChunkedUploadSession uploadSession) + { + throw new NotImplementedException(); + } + public Task AbortUploadSessionAsync(ChunkedUploadSession uploadSession) { if (uploadSession.Items.ContainsKey("TempPath")) diff --git a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFolderDao.cs b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFolderDao.cs index 3344be42c6..5864f49cbf 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFolderDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFolderDao.cs @@ -33,6 +33,7 @@ internal class BoxFolderDao : BoxDaoBase, IFolderDao private readonly BoxDaoSelector _boxDaoSelector; private readonly IFileDao _fileDao; private readonly IFolderDao _folderDao; + private readonly TempStream _tempStream; public BoxFolderDao( IServiceProvider serviceProvider, @@ -48,13 +49,15 @@ internal class BoxFolderDao : BoxDaoBase, IFolderDao IFileDao fileDao, IFolderDao folderDao, TempPath tempPath, - AuthContext authContext) + AuthContext authContext, + TempStream tempStream) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility, tempPath, authContext) { _crossDao = crossDao; _boxDaoSelector = boxDaoSelector; _fileDao = fileDao; _folderDao = folderDao; + _tempStream = tempStream; } public async Task> GetFolderAsync(string folderId) @@ -96,10 +99,10 @@ internal class BoxFolderDao : BoxDaoBase, IFolderDao rooms = FilterByTags(rooms, withoutTags, tags, filesDbContext); await foreach (var room in rooms) - { + { yield return room; } - } + } public async IAsyncEnumerable> GetFoldersAsync(string parentId) { @@ -515,4 +518,9 @@ internal class BoxFolderDao : BoxDaoBase, IFolderDao return chunkedUpload ? storageMaxUploadSize : Math.Min(storageMaxUploadSize, _setupInfo.AvailableFileSize); } -} + + public IDataWriteOperator CreateDataWriteOperator(string folderId, CommonChunkedUploadSession chunkedUploadSession, CommonChunkedUploadSessionHolder sessionHolder) + { + return new ChunkZipWriteOperator(_tempStream, chunkedUploadSession, sessionHolder); + } +} diff --git a/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxFileDao.cs b/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxFileDao.cs index 40c1bde441..bdfdeb6dbd 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxFileDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxFileDao.cs @@ -654,7 +654,7 @@ internal class DropboxFileDao : DropboxDaoBase, IFileDao uploadSession.BytesUploaded += chunkLength; - if (uploadSession.BytesUploaded == uploadSession.BytesTotal) + if (uploadSession.BytesUploaded == uploadSession.BytesTotal || uploadSession.LastChunk) { uploadSession.File = await FinalizeUploadSessionAsync(uploadSession); } diff --git a/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxFolderDao.cs b/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxFolderDao.cs index e5296c5985..da19447402 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxFolderDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxFolderDao.cs @@ -33,6 +33,7 @@ internal class DropboxFolderDao : DropboxDaoBase, IFolderDao private readonly DropboxDaoSelector _dropboxDaoSelector; private readonly IFileDao _fileDao; private readonly IFolderDao _folderDao; + private readonly TempStream _tempStream; public DropboxFolderDao( IServiceProvider serviceProvider, @@ -48,13 +49,15 @@ internal class DropboxFolderDao : DropboxDaoBase, IFolderDao IFileDao fileDao, IFolderDao folderDao, TempPath tempPath, - AuthContext authContext) + AuthContext authContext, + TempStream tempStream) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility, tempPath, authContext) { _crossDao = crossDao; _dropboxDaoSelector = dropboxDaoSelector; _fileDao = fileDao; _folderDao = folderDao; + _tempStream = tempStream; } public async Task> GetFolderAsync(string folderId) @@ -98,10 +101,10 @@ internal class DropboxFolderDao : DropboxDaoBase, IFolderDao rooms = FilterByTags(rooms, withoutTags, tags, filesDbContext); await foreach (var room in rooms) - { + { yield return room; } - } + } public async IAsyncEnumerable> GetFoldersAsync(string parentId) { @@ -506,4 +509,12 @@ internal class DropboxFolderDao : DropboxDaoBase, IFolderDao return chunkedUpload ? storageMaxUploadSize : Math.Min(storageMaxUploadSize, _setupInfo.AvailableFileSize); } -} + + public IDataWriteOperator CreateDataWriteOperator( + string folderId, + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder) + { + return new ChunkZipWriteOperator(_tempStream, chunkedUploadSession, sessionHolder); + } +} diff --git a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveFileDao.cs b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveFileDao.cs index 286775b22f..16c15ab543 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveFileDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveFileDao.cs @@ -666,7 +666,7 @@ internal class GoogleDriveFileDao : GoogleDriveDaoBase, IFileDao var googleDriveSession = uploadSession.GetItemOrDefault("GoogleDriveSession"); var storage = await ProviderInfo.StorageAsync; storage = await ProviderInfo.StorageAsync; - await storage.TransferAsync(googleDriveSession, stream, chunkLength); + await storage.TransferAsync(googleDriveSession, stream, chunkLength, uploadSession.LastChunk); } else { @@ -677,7 +677,7 @@ internal class GoogleDriveFileDao : GoogleDriveDaoBase, IFileDao uploadSession.BytesUploaded += chunkLength; - if (uploadSession.BytesUploaded == uploadSession.BytesTotal) + if (uploadSession.BytesUploaded == uploadSession.BytesTotal || uploadSession.LastChunk) { uploadSession.File = await FinalizeUploadSessionAsync(uploadSession); } diff --git a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveFolderDao.cs b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveFolderDao.cs index f6f7bec8de..4201c3f6a5 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveFolderDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveFolderDao.cs @@ -33,6 +33,7 @@ internal class GoogleDriveFolderDao : GoogleDriveDaoBase, IFolderDao private readonly GoogleDriveDaoSelector _googleDriveDaoSelector; private readonly IFileDao _fileDao; private readonly IFolderDao _folderDao; + private readonly TempStream _tempStream; public GoogleDriveFolderDao( IServiceProvider serviceProvider, @@ -48,13 +49,14 @@ internal class GoogleDriveFolderDao : GoogleDriveDaoBase, IFolderDao IFileDao fileDao, IFolderDao folderDao, TempPath tempPath, - AuthContext authContext - ) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility, tempPath, authContext) + AuthContext authContext, + TempStream tempStream) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility, tempPath, authContext) { _crossDao = crossDao; _googleDriveDaoSelector = googleDriveDaoSelector; _fileDao = fileDao; _folderDao = folderDao; + _tempStream = tempStream; } public async Task> GetFolderAsync(string folderId) @@ -96,10 +98,10 @@ internal class GoogleDriveFolderDao : GoogleDriveDaoBase, IFolderDao rooms = FilterByTags(rooms, withoutTags, tags, filesDbContext); await foreach (var room in rooms) - { + { yield return room; } - } + } public async IAsyncEnumerable> GetFoldersAsync(string parentId) { @@ -508,4 +510,12 @@ internal class GoogleDriveFolderDao : GoogleDriveDaoBase, IFolderDao return chunkedUpload ? storageMaxUploadSize : Math.Min(storageMaxUploadSize, _setupInfo.AvailableFileSize); } -} + + public IDataWriteOperator CreateDataWriteOperator( + string folderId, + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder) + { + return new ChunkZipWriteOperator(_tempStream ,chunkedUploadSession, sessionHolder); + } +} diff --git a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveStorage.cs b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveStorage.cs index 60a2b24ac0..0ddf903eb8 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveStorage.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveStorage.cs @@ -580,7 +580,7 @@ internal class GoogleDriveStorage : IDisposable return uploadSession; } - public Task TransferAsync(ResumableUploadSession googleDriveSession, Stream stream, long chunkLength) + public Task TransferAsync(ResumableUploadSession googleDriveSession, Stream stream, long chunkLength, bool lastChunk) { ArgumentNullException.ThrowIfNull(stream); @@ -589,10 +589,10 @@ internal class GoogleDriveStorage : IDisposable throw new InvalidOperationException("Can't upload chunk for given upload session."); } - return InternalTransferAsync(googleDriveSession, stream, chunkLength); + return InternalTransferAsync(googleDriveSession, stream, chunkLength, lastChunk); } - private async Task InternalTransferAsync(ResumableUploadSession googleDriveSession, Stream stream, long chunkLength) + private async Task InternalTransferAsync(ResumableUploadSession googleDriveSession, Stream stream, long chunkLength, bool lastChunk) { var request = new HttpRequestMessage { @@ -601,9 +601,21 @@ internal class GoogleDriveStorage : IDisposable }; request.Headers.Add("Authorization", "Bearer " + AccessToken); request.Content = new StreamContent(stream); - request.Content.Headers.ContentRange = new ContentRangeHeaderValue(googleDriveSession.BytesTransfered, - googleDriveSession.BytesTransfered + chunkLength - 1, - googleDriveSession.BytesToTransfer); + if (googleDriveSession.BytesToTransfer > 0) + { + request.Headers.Add("Content-Range", string.Format("bytes {0}-{1}/{2}", + googleDriveSession.BytesTransfered, + googleDriveSession.BytesTransfered + chunkLength - 1, + googleDriveSession.BytesToTransfer)); + } + else + { + var bytesToTransfer = lastChunk ? (googleDriveSession.BytesTransfered + chunkLength).ToString() : "*"; + request.Headers.Add("Content-Range", string.Format("bytes {0}-{1}/{2}", + googleDriveSession.BytesTransfered, + googleDriveSession.BytesTransfered + chunkLength - 1, + bytesToTransfer)); + } var httpClient = _clientFactory.CreateClient(); HttpResponseMessage response; diff --git a/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveFileDao.cs b/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveFileDao.cs index d298fde622..26c6f82273 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveFileDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveFileDao.cs @@ -678,7 +678,7 @@ internal class OneDriveFileDao : OneDriveDaoBase, IFileDao uploadSession.BytesUploaded += chunkLength; - if (uploadSession.BytesUploaded == uploadSession.BytesTotal) + if (uploadSession.BytesUploaded == uploadSession.BytesTotal || uploadSession.LastChunk) { uploadSession.File = await FinalizeUploadSessionAsync(uploadSession); } @@ -690,7 +690,7 @@ internal class OneDriveFileDao : OneDriveDaoBase, IFileDao return uploadSession.File; } - private async Task> FinalizeUploadSessionAsync(ChunkedUploadSession uploadSession) + public async Task> FinalizeUploadSessionAsync(ChunkedUploadSession uploadSession) { if (uploadSession.Items.ContainsKey("OneDriveSession")) { diff --git a/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveFolderDao.cs b/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveFolderDao.cs index 828aacb26f..df3fe6c210 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveFolderDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveFolderDao.cs @@ -73,7 +73,7 @@ internal class OneDriveFolderDao : OneDriveDaoBase, IFolderDao { return GetRootFolderAsync(fileId); } - + public async IAsyncEnumerable> GetRoomsAsync(IEnumerable parentsIds, IEnumerable roomsIds, FilterType filterType, IEnumerable tags, Guid subjectId, string searchText, bool withSubfolders, bool withoutTags, bool excludeSubject, ProviderFilter provider, SubjectFilter subjectFilter, IEnumerable subjectEntriesIds) { if (CheckInvalidFilter(filterType) || (provider != ProviderFilter.None && provider != ProviderFilter.OneDrive)) @@ -95,10 +95,10 @@ internal class OneDriveFolderDao : OneDriveDaoBase, IFolderDao rooms = FilterByTags(rooms, withoutTags, tags, filesDbContext); await foreach (var room in rooms) - { + { yield return room; } - } + } public async IAsyncEnumerable> GetFoldersAsync(string parentId) { @@ -519,4 +519,12 @@ internal class OneDriveFolderDao : OneDriveDaoBase, IFolderDao return chunkedUpload ? storageMaxUploadSize : Math.Min(storageMaxUploadSize, _setupInfo.AvailableFileSize); } -} + + public IDataWriteOperator CreateDataWriteOperator( + string folderId, + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder) + { + return null; + } +} diff --git a/products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFileDao.cs b/products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFileDao.cs index 4034a9db90..3eba9a198a 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFileDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFileDao.cs @@ -463,6 +463,13 @@ internal class ProviderFileDao : ProviderDaoBase, IFileDao return uploadSession.File; } + public Task> FinalizeUploadSessionAsync(ChunkedUploadSession uploadSession) + { + var fileDao = GetFileDao(uploadSession.File); + uploadSession.File = ConvertId(uploadSession.File); + return fileDao.FinalizeUploadSessionAsync(uploadSession); + } + public Task AbortUploadSessionAsync(ChunkedUploadSession uploadSession) { var fileDao = GetFileDao(uploadSession.File); diff --git a/products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFolderDao.cs b/products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFolderDao.cs index 0a549341a8..2d78564c12 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFolderDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFolderDao.cs @@ -473,6 +473,16 @@ internal class ProviderFolderDao : ProviderDaoBase, IFolderDao return storageMaxUploadSize; } + public IDataWriteOperator CreateDataWriteOperator( + string folderId, + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder) + { + var selector = GetSelector(folderId); + var folderDao = selector.GetFolderDao(folderId); + return folderDao.CreateDataWriteOperator(folderId, chunkedUploadSession, sessionHolder); + } + private IAsyncEnumerable> FilterByProvider(IAsyncEnumerable> folders, ProviderFilter provider) { if (provider != ProviderFilter.kDrive && provider != ProviderFilter.WebDav && provider != ProviderFilter.Yandex) diff --git a/products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointFileDao.cs b/products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointFileDao.cs index 0434c888d0..890b34458e 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointFileDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointFileDao.cs @@ -471,6 +471,11 @@ internal class SharePointFileDao : SharePointDaoBase, IFileDao throw new NotImplementedException(); } + public Task> FinalizeUploadSessionAsync(ChunkedUploadSession uploadSession) + { + throw new NotImplementedException(); + } + public Task AbortUploadSessionAsync(ChunkedUploadSession uploadSession) { return Task.FromResult(0); diff --git a/products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointFolderDao.cs b/products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointFolderDao.cs index cb18e56852..e6e75f9e1a 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointFolderDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointFolderDao.cs @@ -81,7 +81,7 @@ internal class SharePointFolderDao : SharePointDaoBase, IFolderDao } public async IAsyncEnumerable> GetRoomsAsync(IEnumerable parentsIds, IEnumerable roomsIds, FilterType filterType, IEnumerable tags, Guid subjectId, string searchText, bool withSubfolders, bool withoutTags, bool excludeSubject, ProviderFilter provider, SubjectFilter subjectFilter, IEnumerable subjectEntriesIds) - { + { if (CheckInvalidFilter(filterType) || (provider != ProviderFilter.None && provider != ProviderFilter.SharePoint)) { yield break; @@ -101,10 +101,10 @@ internal class SharePointFolderDao : SharePointDaoBase, IFolderDao rooms = FilterByTags(rooms, withoutTags, tags, filesDbContext); await foreach (var room in rooms) - { + { yield return room; } - } + } public async IAsyncEnumerable> GetFoldersAsync(string parentId) { @@ -390,10 +390,10 @@ internal class SharePointFolderDao : SharePointDaoBase, IFolderDao if (ProviderInfo.FolderId == oldId) { - await DaoSelector.UpdateProviderFolderId(ProviderInfo, newFolderId); - } + await DaoSelector.UpdateProviderFolderId(ProviderInfo, newFolderId); } } + } await UpdatePathInDBAsync(oldId, newFolderId); @@ -442,4 +442,12 @@ internal class SharePointFolderDao : SharePointDaoBase, IFolderDao { return Task.FromResult(2L * 1024L * 1024L * 1024L); } -} + + public IDataWriteOperator CreateDataWriteOperator( + string folderId, + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder) + { + return null; + } +} diff --git a/products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxFileDao.cs b/products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxFileDao.cs index 91630dd8df..b4b427aaf9 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxFileDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxFileDao.cs @@ -664,7 +664,7 @@ internal class SharpBoxFileDao : SharpBoxDaoBase, IFileDao uploadSession.BytesUploaded += chunkLength; - if (uploadSession.BytesUploaded == uploadSession.BytesTotal) + if (uploadSession.BytesUploaded == uploadSession.BytesTotal || uploadSession.LastChunk) { uploadSession.File = await FinalizeUploadSessionAsync(uploadSession); } diff --git a/products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxFolderDao.cs b/products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxFolderDao.cs index ee76538fd8..db2af57681 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxFolderDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxFolderDao.cs @@ -78,7 +78,7 @@ internal class SharpBoxFolderDao : SharpBoxDaoBase, IFolderDao { return Task.FromResult(ToFolder(RootFolder())); } - + public async IAsyncEnumerable> GetRoomsAsync(IEnumerable parentsIds, IEnumerable roomsIds, FilterType filterType, IEnumerable tags, Guid subjectId, string searchText, bool withSubfolders, bool withoutTags, bool excludeSubject, ProviderFilter provider, SubjectFilter subjectFilter, IEnumerable subjectEntriesIds) { @@ -101,10 +101,10 @@ internal class SharpBoxFolderDao : SharpBoxDaoBase, IFolderDao rooms = FilterByTags(rooms, withoutTags, tags, filesDbContext); await foreach (var room in rooms) - { + { yield return room; } - } + } public IAsyncEnumerable> GetFoldersAsync(string parentId) { @@ -449,11 +449,11 @@ internal class SharpBoxFolderDao : SharpBoxDaoBase, IFolderDao if (ProviderInfo.FolderId == oldId) { - await DaoSelector.UpdateProviderFolderId(ProviderInfo, newId); - } + await DaoSelector.UpdateProviderFolderId(ProviderInfo, newId); } } } + } await UpdatePathInDBAsync(oldId, newId); @@ -509,4 +509,12 @@ internal class SharpBoxFolderDao : SharpBoxDaoBase, IFolderDao return Task.FromResult(chunkedUpload ? storageMaxUploadSize : Math.Min(storageMaxUploadSize, _setupInfo.AvailableFileSize)); } -} + + public IDataWriteOperator CreateDataWriteOperator( + string folderId, + CommonChunkedUploadSession chunkedUploadSession, + CommonChunkedUploadSessionHolder sessionHolder) + { + return null; + } +} diff --git a/products/ASC.Files/Core/GlobalUsings.cs b/products/ASC.Files/Core/GlobalUsings.cs index 6ec7b951dc..fb64066a2c 100644 --- a/products/ASC.Files/Core/GlobalUsings.cs +++ b/products/ASC.Files/Core/GlobalUsings.cs @@ -32,7 +32,8 @@ global using System.Globalization; global using System.Linq.Expressions; global using System.Net; global using System.Net.Http.Headers; -global using System.Net.Http.Json;global using System.Net.Mime; +global using System.Net.Http.Json; +global using System.Net.Mime; global using System.Reflection; global using System.Runtime.Serialization; global using System.Security; @@ -80,6 +81,7 @@ global using ASC.Core.Notify.Socket; global using ASC.Core.Tenants; global using ASC.Core.Users; global using ASC.Data.Storage; +global using ASC.Data.Storage.ZipOperators; global using ASC.ElasticSearch; global using ASC.ElasticSearch.Core; global using ASC.ElasticSearch.Service; @@ -189,13 +191,11 @@ global using Microsoft.AspNetCore.Mvc.ModelBinding; global using Microsoft.AspNetCore.WebUtilities; global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.Infrastructure; -global using Microsoft.EntityFrameworkCore.Metadata; global using Microsoft.EntityFrameworkCore.Storage; global using Microsoft.Extensions.Caching.Memory; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Options; global using Microsoft.Extensions.Primitives; global using Microsoft.Graph; global using Microsoft.OneDrive.Sdk; diff --git a/products/ASC.Files/Core/Services/DocumentService/Configuration.cs b/products/ASC.Files/Core/Services/DocumentService/Configuration.cs index 94ec9868e2..143d1817dc 100644 --- a/products/ASC.Files/Core/Services/DocumentService/Configuration.cs +++ b/products/ASC.Files/Core/Services/DocumentService/Configuration.cs @@ -142,9 +142,11 @@ public class DocumentConfig { private readonly DocumentServiceConnector _documentServiceConnector; private readonly PathProvider _pathProvider; + private readonly TenantManager _tenantManager; private string _fileUri; private string _key = string.Empty; private string _title; + private FileReferenceData _referenceData; public string FileType => Info.GetFile().ConvertedExtension.Trim('.'); public InfoConfig Info { get; set; } public bool IsLinkedForMe { get; set; } @@ -157,6 +159,22 @@ public class DocumentConfig public PermissionsConfig Permissions { get; set; } public string SharedLinkKey { get; set; } + public FileReferenceData ReferenceData + { + get + { + if(_referenceData == null) + { + _referenceData = new FileReferenceData() + { + FileKey = Info.GetFile().Id, + InstanceId = _tenantManager.GetCurrentTenant().Id.ToString() + }; + } + + return _referenceData; + } + } public string Title { @@ -181,12 +199,13 @@ public class DocumentConfig } } - public DocumentConfig(DocumentServiceConnector documentServiceConnector, PathProvider pathProvider, InfoConfig infoConfig) + public DocumentConfig(DocumentServiceConnector documentServiceConnector, PathProvider pathProvider, InfoConfig infoConfig, TenantManager tenantManager) { Info = infoConfig; Permissions = new PermissionsConfig(); _documentServiceConnector = documentServiceConnector; _pathProvider = pathProvider; + _tenantManager = tenantManager; } } @@ -606,6 +625,22 @@ public class PermissionsConfig public bool Review { get; set; } = true; } +public class FileReference +{ + public FileReferenceData ReferenceData { get; set; } + public string Error { get; set; } + public string Path { get; set; } + public string Url { get; set; } + public string FileType { get; set; } + public string Token { get; set; } +} + +public class FileReferenceData +{ + public T FileKey { get; set; } + public string InstanceId { get; set; } +} + #endregion Nested Classes [Transient] diff --git a/products/ASC.Files/Core/Utils/FileUploader.cs b/products/ASC.Files/Core/Utils/FileUploader.cs index 480e4effd7..6191f90277 100644 --- a/products/ASC.Files/Core/Utils/FileUploader.cs +++ b/products/ASC.Files/Core/Utils/FileUploader.cs @@ -334,7 +334,7 @@ public class FileUploader var dao = _daoFactory.GetFileDao(); await dao.UploadChunkAsync(uploadSession, stream, chunkLength); - if (uploadSession.BytesUploaded == uploadSession.BytesTotal) + if (uploadSession.BytesUploaded == uploadSession.BytesTotal || uploadSession.LastChunk) { var linkDao = _daoFactory.GetLinkDao(); await linkDao.DeleteAllLinkAsync(uploadSession.File.Id.ToString()); diff --git a/products/ASC.Files/Core/Utils/FilesChunkedUploadSessionHolder.cs b/products/ASC.Files/Core/Utils/FilesChunkedUploadSessionHolder.cs new file mode 100644 index 0000000000..0629efe4e3 --- /dev/null +++ b/products/ASC.Files/Core/Utils/FilesChunkedUploadSessionHolder.cs @@ -0,0 +1,56 @@ +namespace ASC.Web.Files.Utils; + +public class FilesChunkedUploadSessionHolder : CommonChunkedUploadSessionHolder +{ + private readonly IDaoFactory _daoFactory; + public FilesChunkedUploadSessionHolder(IDaoFactory daoFactory, TempPath tempPath, ILogger logger, IDataStore dataStore, string domain, long maxChunkUploadSize = 10485760) + : base(tempPath, logger, dataStore, domain, maxChunkUploadSize) + { + _daoFactory = daoFactory; + } + public override async Task UploadChunkAsync(CommonChunkedUploadSession uploadSession, Stream stream, long length) + { + if (uploadSession is ChunkedUploadSession) + { + return (await InternalUploadChunkAsync(uploadSession, stream, length)).ToString(); + } + else + { + return await InternalUploadChunkAsync(uploadSession, stream, length); + } + } + + private async Task InternalUploadChunkAsync(CommonChunkedUploadSession uploadSession, Stream stream, long length) + { + var chunkedUploadSession = uploadSession as ChunkedUploadSession; + chunkedUploadSession.File.ContentLength += stream.Length; + var fileDao = GetFileDao(); + var file = await fileDao.UploadChunkAsync(chunkedUploadSession, stream, length); + return file.Id; + } + + public override async Task FinalizeAsync(CommonChunkedUploadSession uploadSession) + { + if (uploadSession is ChunkedUploadSession) + { + return (await InternalFinalizeAsync(uploadSession)).ToString(); + } + else + { + return await InternalFinalizeAsync(uploadSession); + } + } + + private async Task InternalFinalizeAsync(CommonChunkedUploadSession commonChunkedUploadSession) + { + var chunkedUploadSession = commonChunkedUploadSession as ChunkedUploadSession; + var fileDao = GetFileDao(); + var file = await fileDao.FinalizeUploadSessionAsync(chunkedUploadSession); + return file.Id; + } + + private IFileDao GetFileDao() + { + return _daoFactory.GetFileDao(); + } +} diff --git a/products/ASC.Files/Server/Api/EditorController.cs b/products/ASC.Files/Server/Api/EditorController.cs index e2011de23e..3b8ea66078 100644 --- a/products/ASC.Files/Server/Api/EditorController.cs +++ b/products/ASC.Files/Server/Api/EditorController.cs @@ -254,7 +254,14 @@ public abstract class EditorController : ApiControllerBase public Task> SharedUsers(T fileId) { return _fileStorageService.SharedUsersAsync(fileId); - } + } + + [HttpPost("file/referencedata")] + public Task> GetReferenceDataAsync(GetReferenceDataDto inDto) + { + + return _fileStorageService.GetReferenceDataAsync(inDto.FileKey, inDto.InstanceId, inDto.SourceFileId, inDto.Path); + } } public class EditorController : ApiControllerBase diff --git a/products/ASC.Files/Server/GlobalUsings.cs b/products/ASC.Files/Server/GlobalUsings.cs index 9e6010b61f..d99d593b51 100644 --- a/products/ASC.Files/Server/GlobalUsings.cs +++ b/products/ASC.Files/Server/GlobalUsings.cs @@ -44,7 +44,7 @@ global using ASC.Core; global using ASC.Core.Billing; global using ASC.Core.Common.EF; global using ASC.Core.Common.Quota; -global using ASC.Core.Common.Quota.Features; +global using ASC.Core.Common.Quota.Features; global using ASC.Core.Common.Settings; global using ASC.Core.Users; global using ASC.FederatedLogin.Helpers; @@ -54,6 +54,7 @@ global using ASC.Files.Core.ApiModels; global using ASC.Files.Core.ApiModels.RequestDto; global using ASC.Files.Core.ApiModels.ResponseDto; global using ASC.Files.Core.Core; +global using ASC.Files.Core.Data; global using ASC.Files.Core.EF; global using ASC.Files.Core.Helpers; global using ASC.Files.Core.Resources; diff --git a/web/ASC.Web.Api/Api/Settings/WebhooksController.cs b/web/ASC.Web.Api/Api/Settings/WebhooksController.cs index d5ec7b3bdc..6ad3868ea6 100644 --- a/web/ASC.Web.Api/Api/Settings/WebhooksController.cs +++ b/web/ASC.Web.Api/Api/Settings/WebhooksController.cs @@ -90,7 +90,7 @@ public class WebhooksController : BaseSettingsController return _mapper.Map(webhook); } - [HttpDelete("webhook")] + [HttpDelete("webhook/{id}")] public async Task RemoveWebhook(int id) { _permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings); @@ -101,13 +101,13 @@ public class WebhooksController : BaseSettingsController } [HttpGet("webhooks/log")] - public async IAsyncEnumerable GetJournal(DateTime? delivery, string hookname, string route) + public async IAsyncEnumerable GetJournal(WebhooksLogRequest model) { _permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings); var startIndex = Convert.ToInt32(_context.StartIndex); var count = Convert.ToInt32(_context.Count); - await foreach (var j in _webhookDbWorker.ReadJournal(startIndex, count, delivery, hookname, route)) + await foreach (var j in _webhookDbWorker.ReadJournal(startIndex, count, model.Delivery, model.Hookname, model.Route)) { yield return _mapper.Map(j); } diff --git a/web/ASC.Web.Api/ApiModels/RequestsDto/WebhooksLogRequest.cs b/web/ASC.Web.Api/ApiModels/RequestsDto/WebhooksLogRequest.cs new file mode 100644 index 0000000000..7d4d0035c6 --- /dev/null +++ b/web/ASC.Web.Api/ApiModels/RequestsDto/WebhooksLogRequest.cs @@ -0,0 +1,36 @@ +// (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.Api.ApiModels.RequestsDto; + +public class WebhooksLogRequest +{ + public DateTime? Delivery { get; set; } + + public string Hookname { get; set; } + + public string Route { get; set; } +} diff --git a/web/ASC.Web.Api/ApiModels/ResponseDto/WebhooksConfigDto.cs b/web/ASC.Web.Api/ApiModels/ResponseDto/WebhooksConfigDto.cs index 1fc80d61a5..626204763c 100644 --- a/web/ASC.Web.Api/ApiModels/ResponseDto/WebhooksConfigDto.cs +++ b/web/ASC.Web.Api/ApiModels/ResponseDto/WebhooksConfigDto.cs @@ -28,6 +28,8 @@ namespace ASC.Web.Api.ApiModels.ResponseDto; public class WebhooksConfigDto : IMapFrom { + public int Id { get; set; } + public string Name { get; set; } public string Uri { get; set; } public string SecretKey { get; set; } public bool Enabled { get; set; }