diff --git a/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs b/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs index 76b2ef8884..71ff6246af 100644 --- a/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs +++ b/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs @@ -61,8 +61,8 @@ public class CustomEndpointDataSource : EndpointDataSource void AddEndpoints(IReadOnlyDictionary defaults = null, RouteValueDictionary policies = null) { var order = constraintRouteAttr != null ? r.Order : r.Order + 2; - endpoints.Add(new RouteEndpoint(r.RequestDelegate, RoutePatternFactory.Parse(r.RoutePattern.RawText, defaults, policies), order, r.Metadata, r.DisplayName)); - endpoints.Add(new RouteEndpoint(r.RequestDelegate, RoutePatternFactory.Parse(r.RoutePattern.RawText + ".{format}", defaults, policies), order - 1, r.Metadata, r.DisplayName)); + endpoints.Add(new RouteEndpoint(r.RequestDelegate, RoutePatternFactory.Parse(r.RoutePattern.RawText, defaults, policies), order + 1, r.Metadata, r.DisplayName)); + endpoints.Add(new RouteEndpoint(r.RequestDelegate, RoutePatternFactory.Parse(r.RoutePattern.RawText + ".{format}", defaults, policies), order, r.Metadata, r.DisplayName)); } }).ToList(); diff --git a/common/ASC.Common/Data/TempPath.cs b/common/ASC.Common/Data/TempPath.cs index a0d5bec57f..6159197821 100644 --- a/common/ASC.Common/Data/TempPath.cs +++ b/common/ASC.Common/Data/TempPath.cs @@ -31,7 +31,7 @@ public class TempPath { private readonly string _tempFolder; - public TempPath(IConfiguration configuration) + public TempPath(IHostEnvironment hostEnvironment, IConfiguration configuration) { var rootFolder = AppContext.BaseDirectory; if (string.IsNullOrEmpty(rootFolder)) @@ -39,7 +39,7 @@ public class TempPath rootFolder = Assembly.GetEntryAssembly().Location; } - _tempFolder = configuration["temp"] ?? Path.Combine("..", "Data", "temp"); + _tempFolder = configuration["web:temp"] ?? CrossPlatform.PathCombine(hostEnvironment.ContentRootPath, "temp"); if (!Path.IsPathRooted(_tempFolder)) { _tempFolder = Path.GetFullPath(Path.Combine(rootFolder, _tempFolder)); @@ -56,7 +56,7 @@ public class TempPath return _tempFolder; } - public string GetTempFileName() + public string GetTempFileName(string ext = "") { FileStream f = null; string path; @@ -66,6 +66,11 @@ public class TempPath { path = Path.Combine(_tempFolder, Path.GetRandomFileName()); + if (!string.IsNullOrEmpty(ext)) + { + path = Path.ChangeExtension(path, ext); + } + try { using (f = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read)) diff --git a/common/ASC.Common/Data/TempStream.cs b/common/ASC.Common/Data/TempStream.cs index 5d1e7e5811..cc809cdc0b 100644 --- a/common/ASC.Common/Data/TempStream.cs +++ b/common/ASC.Common/Data/TempStream.cs @@ -54,7 +54,12 @@ public class TempStream public Stream Create() { - return new FileStream(_tempPath.GetTempFileName(), FileMode.OpenOrCreate, - FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose); + return Create(out _); + } + + public Stream Create(out string path, string ext = "") + { + path = _tempPath.GetTempFileName(ext); + return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose); } } \ No newline at end of file diff --git a/common/ASC.Common/GlobalUsings.cs b/common/ASC.Common/GlobalUsings.cs index d033a5cfef..d701a590e6 100644 --- a/common/ASC.Common/GlobalUsings.cs +++ b/common/ASC.Common/GlobalUsings.cs @@ -86,6 +86,7 @@ global using Microsoft.Extensions.Caching.Memory; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using Microsoft.Extensions.Primitives; diff --git a/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs b/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs index 86853160ed..f7dc53bc0d 100644 --- a/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs +++ b/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs @@ -1,53 +1,53 @@ -// (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; - -[Scope] -public interface IFileDao -{ +// (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; + +[Scope] +public interface IFileDao +{ /// /// Clear the application cache for the specific file - /// - Task InvalidateCacheAsync(T fileId); + /// + Task InvalidateCacheAsync(T fileId); /// /// Receive file /// /// file id - /// - Task> GetFileAsync(T fileId); - + /// + Task> GetFileAsync(T fileId); + /// /// Receive file /// /// file id /// file version - /// - Task> GetFileAsync(T fileId, int fileVersion); - + /// + Task> GetFileAsync(T fileId, int fileVersion); + /// /// Receive file /// @@ -55,29 +55,29 @@ public interface IFileDao /// file name /// /// file - /// - Task> GetFileAsync(T parentId, string title); + /// + Task> GetFileAsync(T parentId, string title); /// /// Receive last file without forcesave /// /// file id /// /// - Task> GetFileStableAsync(T fileId, int fileVersion = -1); + Task> GetFileStableAsync(T fileId, int fileVersion = -1); /// /// Returns all versions of the file /// /// - /// - IAsyncEnumerable> GetFileHistoryAsync(T fileId); - + /// + IAsyncEnumerable> GetFileHistoryAsync(T fileId); + /// /// Gets the file (s) by ID (s) /// /// id file - /// - IAsyncEnumerable> GetFilesAsync(IEnumerable fileIds); - + /// + IAsyncEnumerable> GetFilesAsync(IEnumerable fileIds); + /// /// Gets the file (s) by ID (s) for share /// @@ -88,15 +88,15 @@ public interface IFileDao /// /// /// - IAsyncEnumerable> GetFilesFilteredAsync(IEnumerable fileIds, FilterType filterType, bool subjectGroup, Guid subjectID, string searchText, bool searchInContent, bool checkShared = false); - + IAsyncEnumerable> GetFilesFilteredAsync(IEnumerable fileIds, FilterType filterType, bool subjectGroup, Guid subjectID, string searchText, bool searchInContent, bool checkShared = false); + /// /// /// /// - /// - Task> GetFilesAsync(T parentId); - + /// + Task> GetFilesAsync(T parentId); + /// /// Get files in folder /// @@ -111,39 +111,39 @@ public interface IFileDao /// list of files /// /// Return only the latest versions of files of a folder - /// - IAsyncEnumerable> GetFilesAsync(T parentId, OrderBy orderBy, FilterType filterType, bool subjectGroup, Guid subjectID, string searchText, bool searchInContent, bool withSubfolders = false); - + /// + IAsyncEnumerable> GetFilesAsync(T parentId, OrderBy orderBy, FilterType filterType, bool subjectGroup, Guid subjectID, string searchText, bool searchInContent, bool withSubfolders = false); + /// /// Get stream of file /// /// - /// Stream - Task GetFileStreamAsync(File file); - + /// Stream + Task GetFileStreamAsync(File file); + /// /// Get stream of file /// /// /// - /// Stream - Task GetFileStreamAsync(File file, long offset); - + /// Stream + Task GetFileStreamAsync(File file, long offset); + /// /// Get presigned uri /// /// /// - /// Stream uri - Task GetPreSignedUriAsync(File file, TimeSpan expires); - + /// Stream uri + Task GetPreSignedUriAsync(File file, TimeSpan expires); + /// /// Check is supported PreSignedUri /// /// - /// Stream uri - Task IsSupportedPreSignedUriAsync(File file); - + /// Stream uri + Task IsSupportedPreSignedUriAsync(File file); + /// /// Saves / updates the version of the file /// and save stream of file @@ -158,96 +158,96 @@ public interface IFileDao /// /// Save in all other cases /// - Task> SaveFileAsync(File file, Stream fileStream); + Task> SaveFileAsync(File file, Stream fileStream); /// /// /// /// /// /// - Task> ReplaceFileVersionAsync(File file, Stream fileStream); + Task> ReplaceFileVersionAsync(File file, Stream fileStream); /// /// Deletes a file including all previous versions /// /// file id - Task DeleteFileAsync(T fileId); + Task DeleteFileAsync(T fileId); /// /// Checks whether or not file /// /// file name /// folder id /// Returns true if the file exists, otherwise false - Task IsExistAsync(string title, object folderId); + Task IsExistAsync(string title, object folderId); /// /// Moves a file or set of files in a folder /// /// file id - /// The ID of the destination folder - Task MoveFileAsync(T fileId, T toFolderId); - Task MoveFileAsync(T fileId, TTo toFolderId); - Task MoveFileAsync(T fileId, string toFolderId); - Task MoveFileAsync(T fileId, int toFolderId); - + /// The ID of the destination folder + Task MoveFileAsync(T fileId, T toFolderId); + Task MoveFileAsync(T fileId, TTo toFolderId); + Task MoveFileAsync(T fileId, string toFolderId); + Task MoveFileAsync(T fileId, int toFolderId); + /// /// Copy the files in a folder /// /// file id - /// The ID of the destination folder - Task> CopyFileAsync(T fileId, T toFolderId); - Task> CopyFileAsync(T fileId, TTo toFolderId); - Task> CopyFileAsync(T fileId, string toFolderId); - Task> CopyFileAsync(T fileId, int toFolderId); - + /// The ID of the destination folder + Task> CopyFileAsync(T fileId, T toFolderId); + Task> CopyFileAsync(T fileId, TTo toFolderId); + Task> CopyFileAsync(T fileId, string toFolderId); + Task> CopyFileAsync(T fileId, int toFolderId); + /// /// Rename file /// /// - /// new name - Task FileRenameAsync(File file, string newTitle); - + /// new name + Task FileRenameAsync(File file, string newTitle); + /// /// Update comment file /// /// file id /// file version /// new comment - Task UpdateCommentAsync(T fileId, int fileVersion, string comment); + Task UpdateCommentAsync(T fileId, int fileVersion, string comment); /// /// Complete file version /// /// file id - /// file version - Task CompleteVersionAsync(T fileId, int fileVersion); + /// file version + Task CompleteVersionAsync(T fileId, int fileVersion); /// /// Continue file version /// /// file id - /// file version - Task ContinueVersionAsync(T fileId, int fileVersion); + /// file version + Task ContinueVersionAsync(T fileId, int fileVersion); /// /// Check the need to use the trash before removing /// /// /// - bool UseTrashForRemove(File file); - string GetUniqFilePath(File file, string fileTitle); - + bool UseTrashForRemove(File file); + string GetUniqFilePath(File file, string fileTitle); + #region chunking - - Task> CreateUploadSessionAsync(File file, long contentLength); - Task> UploadChunkAsync(ChunkedUploadSession uploadSession, Stream chunkStream, long chunkLength); - Task AbortUploadSessionAsync(ChunkedUploadSession uploadSession); + + Task> CreateUploadSessionAsync(File file, long contentLength); + Task> UploadChunkAsync(ChunkedUploadSession uploadSession, Stream chunkStream, long chunkLength); + Task AbortUploadSessionAsync(ChunkedUploadSession uploadSession); #endregion - + #region Only in TMFileDao - + /// /// Set created by /// /// - /// - Task ReassignFilesAsync(T[] fileIds, Guid newOwnerId); - + /// + Task ReassignFilesAsync(T[] fileIds, Guid newOwnerId); + /// /// Search files in SharedWithMe & Projects /// @@ -258,7 +258,7 @@ public interface IFileDao /// /// /// - Task>> GetFilesAsync(IEnumerable parentIds, FilterType filterType, bool subjectGroup, Guid subjectID, string searchText, bool searchInContent); + Task>> GetFilesAsync(IEnumerable parentIds, FilterType filterType, bool subjectGroup, Guid subjectID, string searchText, bool searchInContent); /// /// Search the list of files containing text /// Only in TMFileDao @@ -266,34 +266,36 @@ public interface IFileDao /// search text /// /// list of files - IAsyncEnumerable> SearchAsync(string text, bool bunch = false); + IAsyncEnumerable> SearchAsync(string text, bool bunch = false); /// /// Checks whether file exists on storage /// /// file /// - - Task IsExistOnStorageAsync(File file); - - Task SaveEditHistoryAsync(File file, string changes, Stream differenceStream); - - Task> GetEditHistoryAsync(DocumentServiceHelper documentServiceHelper, T fileId, int fileVersion = 0); - - Task GetDifferenceStreamAsync(File file); - - Task ContainChangesAsync(T fileId, int fileVersion); - - Task SaveThumbnailAsync(File file, Stream thumbnail, int width, int height); - - Task GetThumbnailAsync(File file, int width, int height); - - Task> GetFeedsAsync(int tenant, DateTime from, DateTime to); - - Task> GetTenantsWithFeedsAsync(DateTime fromTime); - - Task GetProperties(T fileId); - - Task SaveProperties(T fileId, EntryProperties entryProperties); - + + Task IsExistOnStorageAsync(File file); + + Task SaveEditHistoryAsync(File file, string changes, Stream differenceStream); + + Task> GetEditHistoryAsync(DocumentServiceHelper documentServiceHelper, T fileId, int fileVersion = 0); + + Task GetDifferenceStreamAsync(File file); + + Task ContainChangesAsync(T fileId, int fileVersion); + + Task SaveThumbnailAsync(File file, Stream thumbnail, int width, int height); + + Task GetThumbnailAsync(File file, int width, int height); + + Task GetThumbnailAsync(T fileId, int width, int height); + + Task> GetFeedsAsync(int tenant, DateTime from, DateTime to); + + Task> GetTenantsWithFeedsAsync(DateTime fromTime); + + Task GetProperties(T fileId); + + Task SaveProperties(T fileId, EntryProperties entryProperties); + #endregion } \ No newline at end of file diff --git a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs index b47c6759c2..44c9aabf6c 100644 --- a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs +++ b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs @@ -1465,6 +1465,12 @@ internal class FileDao : AbstractDao, IFileDao await _globalStore.GetStore().SaveAsync(string.Empty, GetUniqFilePath(file, thumnailName), thumbnail, thumnailName); } + public async Task GetThumbnailAsync(int fileId, int width, int height) + { + var file = await GetFileAsync(fileId); + return await GetThumbnailAsync(file, width, height); + } + public async Task GetThumbnailAsync(File file, int width, int height) { var thumnailName = GetThumnailName(width, height); diff --git a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxDaoBase.cs b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxDaoBase.cs index 3d94f49a55..dc009fb741 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxDaoBase.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxDaoBase.cs @@ -192,6 +192,7 @@ internal abstract class BoxDaoBase : ThirdPartyProviderDao file.ModifiedOn = boxFile.ModifiedAt.HasValue ? _tenantUtil.DateTimeFromUtc(boxFile.ModifiedAt.Value.UtcDateTime) : default; file.NativeAccessor = boxFile; file.Title = MakeFileTitle(boxFile); + file.ThumbnailStatus = Thumbnail.Created; return file; } diff --git a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFileDao.cs b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFileDao.cs index f019583119..5a2c4d9245 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFileDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxFileDao.cs @@ -550,6 +550,12 @@ internal class BoxFileDao : BoxDaoBase, IFileDao return false; } + public override Task GetThumbnailAsync(string fileId, int width, int height) + { + var boxFileId = MakeBoxId(_boxDaoSelector.ConvertId(fileId)); + return ProviderInfo.GetThumbnailAsync(boxFileId, width, height); + } + #region chunking private File RestoreIds(File file) diff --git a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxProviderInfo.cs b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxProviderInfo.cs index bf38a2fbe0..bbeee50489 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxProviderInfo.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxProviderInfo.cs @@ -147,6 +147,12 @@ internal class BoxProviderInfo : IProviderInfo { return _boxProviderInfoHelper.CacheResetAsync(BoxRootId, ID, boxPath, isFile); } + + internal async Task GetThumbnailAsync(string boxFileId, int width, int height) + { + var storage = await StorageAsync; + return await _boxProviderInfoHelper.GetThumbnailAsync(storage, boxFileId, width, height); + } } [Scope] @@ -332,4 +338,9 @@ public class BoxProviderInfoHelper await _cacheNotify.PublishAsync(new BoxCacheItem { IsFile = isFile ?? false, IsFileExists = isFile.HasValue, Key = key }, CacheNotifyAction.Remove).ConfigureAwait(false); } } + + internal async Task GetThumbnailAsync(BoxStorage storage, string boxFileId, int width, int height) + { + return await storage.GetThumbnailAsync(boxFileId, width, height).ConfigureAwait(false); + } } diff --git a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxStorage.cs b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxStorage.cs index dcaae2323e..b0b41a8d76 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Box/BoxStorage.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Box/BoxStorage.cs @@ -270,4 +270,13 @@ internal class BoxStorage //todo: without chunked uploader: return Math.Min(max, MaxChunkedUploadFileSize); } + + public async Task GetThumbnailAsync(string fileId, int width, int height) + { + + var boxRepresentation = new BoxRepresentationRequest(); + boxRepresentation.FileId = fileId; + boxRepresentation.XRepHints = $"[jpg?dimensions=320x320]"; + return await _boxClient.FilesManager.GetRepresentationContentAsync(boxRepresentation); + } } diff --git a/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxDaoBase.cs b/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxDaoBase.cs index fc6341f20e..66b3c70534 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxDaoBase.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxDaoBase.cs @@ -196,6 +196,7 @@ internal abstract class DropboxDaoBase : ThirdPartyProviderDao } //Get only files + var items = await GetDropboxItemsAsync(parentId, false).ConfigureAwait(false); var files = items.Select(item => ToFile(item.AsFile)); @@ -548,6 +549,11 @@ internal class DropboxFileDao : DropboxDaoBase, IFileDao return false; } + public override Task GetThumbnailAsync(string fileId, int width, int height) + { + return ProviderInfo.GetThumbnailsAsync(_dropboxDaoSelector.ConvertId(fileId), width, height); + } + #region chunking private File RestoreIds(File file) diff --git a/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxProviderInfo.cs b/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxProviderInfo.cs index e888f4c19b..9c86729b0f 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxProviderInfo.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxProviderInfo.cs @@ -132,9 +132,15 @@ internal class DropboxProviderInfo : IProviderInfo { return _dropboxProviderInfoHelper.CacheResetAsync(ID, dropboxPath, isFile); } + + internal async Task GetThumbnailsAsync(string filePath, int width, int height) + { + var storage = await StorageAsync; + return await _dropboxProviderInfoHelper.GetThumbnailsAsync(storage, filePath, width, height); + } } -[Scope] +[Scope(Additional = typeof(DropboxStorageDisposableWrapperExtention))] internal class DropboxStorageDisposableWrapper : IDisposable { public DropboxStorage Storage { get; private set; } @@ -163,7 +169,7 @@ internal class DropboxStorageDisposableWrapper : IDisposable public async Task InternalCreateStorageAsync(OAuth20Token token, int id) { - var dropboxStorage = _serviceProvider.GetService(); + var dropboxStorage = _serviceProvider.GetRequiredService(); await CheckTokenAsync(token, id).ConfigureAwait(false); @@ -311,4 +317,17 @@ public class DropboxProviderInfoHelper await _cacheNotify.PublishAsync(new DropboxCacheItem { IsFile = isFile ?? false, IsFileExists = isFile.HasValue, Key = key }, CacheNotifyAction.Remove).ConfigureAwait(false); } } + + internal Task GetThumbnailsAsync(DropboxStorage storage, string filePath, int width, int height) + { + return storage.GetThumbnailsAsync(filePath, width, height); + } } + +public static class DropboxStorageDisposableWrapperExtention +{ + public static void Register(DIHelper dIHelper) + { + dIHelper.TryAdd(); + } +} \ No newline at end of file diff --git a/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxStorage.cs b/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxStorage.cs index 5d6ad4a7eb..74a0704ba5 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxStorage.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxStorage.cs @@ -24,8 +24,11 @@ // 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 ThumbnailSize = Dropbox.Api.Files.ThumbnailSize; + namespace ASC.Files.Thirdparty.Dropbox; +[Scope] internal class DropboxStorage : IDisposable { public bool IsOpened { get; private set; } @@ -135,10 +138,38 @@ internal class DropboxStorage : IDisposable public async Task> GetItemsAsync(string folderPath) { var data = await _dropboxClient.Files.ListFolderAsync(folderPath); - return new List(data.Entries); } + public async Task GetThumbnailsAsync(string filePath, int width, int height) + { + try + { + var path = new PathOrLink.Path(filePath); + var size = Convert(width, height); + var arg = new ThumbnailV2Arg(path, size: size); + + var responce = await _dropboxClient.Files.GetThumbnailV2Async(arg); + return await responce.GetContentAsStreamAsync(); + } + catch + { + return null; + } + } + + private ThumbnailSize Convert(int width, int height) + { + if (width > 368) + { + return ThumbnailSize.W480h320.Instance; + } + else + { + return ThumbnailSize.W256h256.Instance; + } + } + public Task DownloadStreamAsync(string filePath, int offset = 0) { ArgumentNullOrEmptyException.ThrowIfNullOrEmpty(filePath); diff --git a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveDaoBase.cs b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveDaoBase.cs index 8c920773d1..75029f89b6 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveDaoBase.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveDaoBase.cs @@ -215,6 +215,7 @@ internal abstract class GoogleDriveDaoBase : ThirdPartyProviderDao return false; } + + public override Task GetThumbnailAsync(string fileId, int width, int height) + { + return ProviderInfo.GetThumbnail(MakeDriveId(_googleDriveDaoSelector.ConvertId(fileId)), width, height); + } + #region chunking private File RestoreIds(File file) @@ -706,5 +712,6 @@ internal class GoogleDriveFileDao : GoogleDriveDaoBase, IFileDao return Task.CompletedTask; } + #endregion } diff --git a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveProviderInfo.cs b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveProviderInfo.cs index 7699c71ac2..19b559495b 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveProviderInfo.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveProviderInfo.cs @@ -158,6 +158,12 @@ internal class GoogleDriveProviderInfo : IProviderInfo { return _googleDriveProviderInfoHelper.CacheResetChildsAsync(ID, parentDriveId, childFolder); } + + internal async Task GetThumbnail(string fileId, int width, int height) + { + var storage = await StorageAsync; + return await storage.GetThumbnail(fileId, width, height); + } } [Scope(Additional = typeof(GoogleDriveProviderInfoExtention))] diff --git a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveStorage.cs b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveStorage.cs index 26dc317985..f89516da16 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveStorage.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveStorage.cs @@ -155,7 +155,6 @@ internal class GoogleDriveStorage : IDisposable try { var request = _driveService.Files.Get(entryId); - request.Fields = GoogleLoginProvider.FilesFields; return await request.ExecuteAsync(); @@ -170,6 +169,21 @@ internal class GoogleDriveStorage : IDisposable } } + public async Task GetThumbnail(string fileId, int width, int height) + { + try + { + var url = $"https://lh3.google.com/u/0/d/{fileId}=w{width}-h{height}-p-k-nu-iv1"; + var httpClient = _driveService.HttpClient; + var response = await httpClient.GetAsync(url); + return await response.Content.ReadAsStreamAsync(); + } + catch (Exception) + { + return null; + } + } + public List GetEntries(string folderId, bool? folders = null) { var request = _driveService.Files.List(); diff --git a/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs b/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs index 940d4e46a4..e2bdbfa20a 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs @@ -71,13 +71,18 @@ internal abstract class ThirdPartyProviderDao return Task.FromResult(false); } - public Task SaveThumbnailAsync(File file, Stream thumbnail, int width, int height) + public Task SaveThumbnailAsync(File file, Stream thumbnail, int width, int height) { //Do nothing return Task.CompletedTask; } - public Task GetThumbnailAsync(File file, int width, int height) + public virtual Task GetThumbnailAsync(File file, int width, int height) + { + return GetThumbnailAsync(file.Id, width, height); + } + + public virtual Task GetThumbnailAsync(string file, int width, int height) { return Task.FromResult(null); } @@ -96,7 +101,7 @@ internal abstract class ThirdPartyProviderDao { return null; } - + public string GetUniqFilePath(File file, string fileTitle) { throw new NotImplementedException(); @@ -177,7 +182,7 @@ internal abstract class ThirdPartyProviderDao { return null; } - + public string GetFolderIDPhotos(bool createIfNotExists) { return null; @@ -240,9 +245,9 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo protected List _constraintFolderFilters = new List { FilterType.FilesOnly, FilterType.ByExtension, FilterType.DocumentsOnly, FilterType.ImagesOnly, FilterType.PresentationsOnly, FilterType.SpreadsheetsOnly, FilterType.ArchiveOnly, FilterType.MediaOnly }; - + protected abstract string Id { get; } - + protected ThirdPartyProviderDao( IServiceProvider serviceProvider, UserManager userManager, @@ -264,14 +269,14 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo _tempPath = tempPath; TenantID = tenantManager.GetCurrentTenant().Id; } - + public void Init(BaseProviderInfo providerInfo, RegexDaoSelectorBase selectorBase) { ProviderInfo = providerInfo.ProviderInfo; PathPrefix = providerInfo.PathPrefix; DaoSelector = selectorBase; } - + protected IQueryable Query(DbSet set) where TSet : class, IDbFile { return set.AsQueryable().Where(r => r.TenantId == TenantID); @@ -283,7 +288,7 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo { return null; } - + return InternalMappingIDAsync(id, saveIfNotExist); } @@ -311,52 +316,52 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo HashId = result, TenantId = TenantID }; - + await FilesDbContext.ThirdpartyIdMapping.AddAsync(newMapping).ConfigureAwait(false); await FilesDbContext.SaveChangesAsync().ConfigureAwait(false); } return result; } - + protected Folder GetFolder() { var folder = _serviceProvider.GetService>(); - + InitFileEntry(folder); - + folder.FolderType = FolderType.DEFAULT; folder.Shareable = false; folder.FilesCount = 0; folder.FoldersCount = 0; - + return folder; } - + protected Folder GetErrorFolder(ErrorEntry entry) { var folder = GetFolder(); - + InitFileEntryError(folder, entry); - + folder.ParentId = null; - + return folder; } - + protected File GetFile() { var file = _serviceProvider.GetService>(); - + InitFileEntry(file); - + file.Access = FileShare.None; file.Shared = false; file.Version = 1; - + return file; } - + protected File GetErrorFile(ErrorEntry entry) { var file = GetFile(); @@ -364,7 +369,7 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo return file; } - + protected void InitFileEntry(FileEntry fileEntry) { fileEntry.CreateBy = ProviderInfo.Owner; @@ -375,7 +380,7 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo fileEntry.RootFolderType = ProviderInfo.RootFolderType; fileEntry.RootId = MakeId(); } - + protected void InitFileEntryError(FileEntry fileEntry, ErrorEntry entry) { fileEntry.Id = MakeId(entry.ErrorId); @@ -406,7 +411,7 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo var filtered = folders.Join(FilesDbContext.ThirdpartyIdMapping.ToAsyncEnumerable(), f => f.Id, m => m.Id, (folder, map) => new { folder, map.HashId }) .Join(FilesDbContext.TagLink.ToAsyncEnumerable(), r => r.HashId, t => t.EntryId, (result, tag) => new { result.folder, tag.TagId }) - .Join(FilesDbContext.Tag.ToAsyncEnumerable(), r => r.TagId, t => t.Id, (result, tagInfo) => new {result.folder, tagInfo.Name }) + .Join(FilesDbContext.Tag.ToAsyncEnumerable(), r => r.TagId, t => t.Id, (result, tagInfo) => new { result.folder, tagInfo.Name }) .Where(r => tagNames.Contains(r.Name)) .Select(r => r.folder); @@ -449,10 +454,10 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo return Task.CompletedTask; } - public Task> GetSharesAsync(IEnumerable subjects) + public Task> GetSharesAsync(IEnumerable subjects) { List result = null; - return Task>.FromResult(result); + return Task>.FromResult(result); } public Task> GetSharesAsync(IEnumerable> entry) @@ -474,7 +479,7 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo { return null; } - + public Task> GetPureShareRecordsAsync(FileEntry entry) { return null; @@ -553,16 +558,16 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo { return new List(); } - + public IEnumerable SaveTags(Tag tag) { return new List(); } - + public void UpdateNewTags(IEnumerable tag) { } - + public void UpdateNewTags(Tag tag) { } @@ -580,7 +585,7 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo public void RemoveTags(IEnumerable tag) { } - + public void RemoveTags(Tag tag) { } @@ -589,7 +594,7 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo { return AsyncEnumerable.Empty(); } - + public void MarkAsNew(Guid subject, FileEntry fileEntry) { } @@ -597,24 +602,24 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo public async IAsyncEnumerable GetNewTagsAsync(Guid subject, Folder parentFolder, bool deepSearch) { var folderId = DaoSelector.ConvertId(parentFolder.Id); - + var entryIDs = await FilesDbContext.ThirdpartyIdMapping .AsQueryable() .Where(r => r.Id.StartsWith(parentFolder.Id)) .Select(r => r.HashId) .ToListAsync() .ConfigureAwait(false); - + if (!entryIDs.Any()) { yield break; } - + var q = from r in FilesDbContext.Tag from l in FilesDbContext.TagLink.AsQueryable().Where(a => a.TenantId == r.TenantId && a.TagId == r.Id).DefaultIfEmpty() where r.TenantId == TenantID && l.TenantId == TenantID && r.Type == TagType.New && entryIDs.Contains(l.EntryId) select new { tag = r, tagLink = l }; - + if (subject != Guid.Empty) { q = q.Where(r => r.tag.Owner == subject); @@ -623,7 +628,7 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo var qList = q .Distinct() .AsAsyncEnumerable(); - + var tags = qList .SelectAwait(async r => new Tag { @@ -636,7 +641,7 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo Id = r.tag.Id }); - + if (deepSearch) { await foreach (var e in tags.ConfigureAwait(false)) @@ -646,7 +651,7 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo yield break; } - + var folderFileIds = new[] { parentFolder.Id } .Concat(await GetChildrenAsync(folderId).ConfigureAwait(false)); @@ -659,7 +664,7 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo protected abstract Task> GetChildrenAsync(string folderId); #endregion - + public void Dispose() { if (ProviderInfo != null) @@ -669,12 +674,12 @@ internal abstract class ThirdPartyProviderDao : ThirdPartyProviderDao, IDispo } } } - + internal class ErrorEntry { public string Error { get; set; } public string ErrorId { get; set; } - + public ErrorEntry(string error, string errorId) { Error = error; @@ -687,16 +692,16 @@ public class TagLink public int TenantId { get; set; } public int Id { get; set; } } - + public class TagLinkComparer : IEqualityComparer { public bool Equals([AllowNull] TagLink x, [AllowNull] TagLink y) { return x.Id == y.Id && x.TenantId == y.TenantId; } - + public int GetHashCode([DisallowNull] TagLink obj) { return obj.Id.GetHashCode() + obj.TenantId.GetHashCode(); } -} +} diff --git a/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveDaoBase.cs b/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveDaoBase.cs index a3368b84e5..b6e4bbf483 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveDaoBase.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveDaoBase.cs @@ -192,6 +192,7 @@ internal abstract class OneDriveDaoBase : ThirdPartyProviderDao return false; } + public override Task GetThumbnailAsync(string fileId, int width, int height) + { + var oneDriveId = MakeOneDriveId(_oneDriveDaoSelector.ConvertId(fileId)); + return ProviderInfo.GetThumbnailAsync(oneDriveId, width, height); + } + #region chunking private File RestoreIds(File file) diff --git a/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveProviderInfo.cs b/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveProviderInfo.cs index ef23e7d21e..6591d2ff81 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveProviderInfo.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveProviderInfo.cs @@ -123,6 +123,12 @@ internal class OneDriveProviderInfo : IProviderInfo { return _oneDriveProviderInfoHelper.CacheResetAsync(ID, onedriveId); } + + internal async Task GetThumbnailAsync(string onedriveId, int width, int height) + { + var storage = await StorageAsync; + return await _oneDriveProviderInfoHelper.GetThumbnailAsync(storage, onedriveId, width, height); + } } [Scope(Additional = typeof(OneDriveProviderInfoExtention))] @@ -284,6 +290,11 @@ public class OneDriveProviderInfoHelper _cacheItem.Remove("onedrive-" + i.Key); } } + + internal async Task GetThumbnailAsync(OneDriveStorage storage, string onedriveId, int width, int height) + { + return await storage.GetThumbnailAsync(onedriveId, width, height).ConfigureAwait(false); + } } public static class OneDriveProviderInfoExtention { diff --git a/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveStorage.cs b/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveStorage.cs index c48801038a..fb5972ff2a 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveStorage.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveStorage.cs @@ -354,6 +354,30 @@ internal class OneDriveStorage var httpClient = _clientFactory.CreateClient(); using var response = await httpClient.SendAsync(request); } + + public async Task GetThumbnailAsync(string fileId, int width, int height) + { + var thumbnails = await OnedriveClient.Drive.Items[fileId].Thumbnails.Request().GetAsync(); + if (thumbnails.Count > 0) + { + var url = thumbnails[0].Medium.Url; + url = url.Substring(0, url.IndexOf("?width")); + url = url + $"?width={width}&height={height}&cropmode=none"; + var request = new HttpRequestMessage + { + RequestUri = new Uri(url), + Method = HttpMethod.Get + }; + var httpClient = _clientFactory.CreateClient(); + using var response = await httpClient.SendAsync(request); + var bytes = await response.Content.ReadAsByteArrayAsync(); + return new MemoryStream(bytes); + } + else + { + 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 74cc8fc121..9b4c569010 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFileDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFileDao.cs @@ -464,7 +464,7 @@ internal class ProviderFileDao : ProviderDaoBase, IFileDao var selector = GetSelector(file.Id); var fileDao = selector.GetFileDao(file.Id); - return fileDao.UseTrashForRemove(file); + return fileDao.UseTrashForRemove(file); } #region chunking @@ -520,5 +520,19 @@ internal class ProviderFileDao : ProviderDaoBase, IFileDao return file; } + + public override Task GetThumbnailAsync(string fileId, int width, int height) + { + var selector = GetSelector(fileId); + var fileDao = selector.GetFileDao(fileId); + return fileDao.GetThumbnailAsync(fileId, width, height); + } + + public override Task GetThumbnailAsync(File file, int width, int height) + { + var fileDao = GetFileDao(file); + return fileDao.GetThumbnailAsync(file, width, height); + } + #endregion } diff --git a/products/ASC.Files/Core/Core/ThumbnailSettings.cs b/products/ASC.Files/Core/Core/ThumbnailSettings.cs index 052127685d..9344914096 100644 --- a/products/ASC.Files/Core/Core/ThumbnailSettings.cs +++ b/products/ASC.Files/Core/Core/ThumbnailSettings.cs @@ -65,7 +65,7 @@ public class ThumbnailSettings private string _formats; public string Formats { - get => _formats ?? ".pptx|.pptm|.ppt|.ppsx|.ppsm|.pps|.potx|.potm|.pot|.odp|.fodp|.otp|.gslides|.xlsx|.xlsm|.xls|.xltx|.xltm|.xlt|.ods|.fods|.ots|.gsheet|.csv|.docx|.docxf|.oform|.docm|.doc|.dotx|.dotm|.dot|.odt|.fodt|.ott|.gdoc|.txt|.rtf|.mht|.html|.htm|.fb2|.epub|.pdf|.djvu|.xps|.oxps|.bmp|.jpeg|.jpg|.png|.gif|.tiff|.tif|.ico"; + get => _formats ?? ".pptx|.pptm|.ppt|.ppsx|.ppsm|.pps|.potx|.potm|.pot|.odp|.fodp|.otp|.gslides|.xlsx|.xlsm|.xls|.xltx|.xltm|.xlt|.ods|.fods|.ots|.gsheet|.csv|.docx|.docxf|.oform|.docm|.doc|.dotx|.dotm|.dot|.odt|.fodt|.ott|.gdoc|.txt|.rtf|.mht|.html|.htm|.fb2|.epub|.pdf|.djvu|.xps|.oxps"; set => _formats = value; } diff --git a/products/ASC.Files/Core/HttpHandlers/FileHandler.ashx.cs b/products/ASC.Files/Core/HttpHandlers/FileHandler.ashx.cs index c58e2ccd8c..3e49a3929a 100644 --- a/products/ASC.Files/Core/HttpHandlers/FileHandler.ashx.cs +++ b/products/ASC.Files/Core/HttpHandlers/FileHandler.ashx.cs @@ -27,18 +27,18 @@ using JWT.Algorithms; using JWT.Builder; -using JsonException = System.Text.Json.JsonException; -using MimeMapping = ASC.Common.Web.MimeMapping; - -namespace ASC.Web.Files; +using JsonException = System.Text.Json.JsonException; +using MimeMapping = ASC.Common.Web.MimeMapping; + +namespace ASC.Web.Files; public class FileHandler { - public FileHandler(RequestDelegate next) + public FileHandler(RequestDelegate next) { } - public async Task Invoke(HttpContext context, FileHandlerService fileHandlerService) + public async Task Invoke(HttpContext context, FileHandlerService fileHandlerService) { await fileHandlerService.Invoke(context).ConfigureAwait(false); } @@ -47,7 +47,7 @@ public class FileHandler [Scope] public class FileHandlerService { - private readonly ThumbnailSettings _thumbnailSettings; + private readonly ThumbnailSettings _thumbnailSettings; public string FileHandlerPath { @@ -109,10 +109,10 @@ public class FileHandlerService IServiceProvider serviceProvider, TempStream tempStream, SocketManager socketManager, - CompressToArchive compressToArchive, - InstanceCrypto instanceCrypto, - IHttpClientFactory clientFactory, - ThumbnailSettings thumbnailSettings) + CompressToArchive compressToArchive, + InstanceCrypto instanceCrypto, + IHttpClientFactory clientFactory, + ThumbnailSettings thumbnailSettings) { _filesLinkUtility = filesLinkUtility; _tenantExtra = tenantExtra; @@ -142,7 +142,7 @@ public class FileHandlerService _userManager = userManager; _logger = logger; _clientFactory = clientFactory; - this._thumbnailSettings = thumbnailSettings; + this._thumbnailSettings = thumbnailSettings; } public Task Invoke(HttpContext context) @@ -225,7 +225,7 @@ public class FileHandlerService filename = _instanceCrypto.Decrypt(Uri.UnescapeDataString(filename)); } - var path = string.Format(@"{0}\{1}", _securityContext.CurrentAccount.ID, filename); + var path = string.Format(@"{0}\{1}", _securityContext.CurrentAccount.ID, filename); var store = _globalStore.GetStore(); if (!await store.IsFileAsync(FileConstant.StorageDomainTmp, path)) @@ -336,7 +336,7 @@ public class FileHandlerService if (!await fileDao.IsExistOnStorageAsync(file)) { - _logger.ErrorDownloadFile2(file.Id.ToString()); + _logger.ErrorDownloadFile2(file.Id.ToString()); context.Response.StatusCode = (int)HttpStatusCode.NotFound; return; @@ -412,7 +412,7 @@ public class FileHandlerService { fileStream = await fileDao.GetFileStreamAsync(file); - _logger.InformationConvertingToMp4(file.Title, file.Id.ToString()); + _logger.InformationConvertingToMp4(file.Title, file.Id.ToString()); var stream = await _fFmpegService.Convert(fileStream, ext); await store.SaveAsync(string.Empty, mp4Path, stream, mp4Name); } @@ -1009,34 +1009,34 @@ public class FileHandlerService } else { - await ThumbnailFile(context, q.FirstOrDefault() ?? ""); + await ThumbnailFileFromThirdparty(context, q.FirstOrDefault() ?? ""); } } - private async Task ThumbnailFile(HttpContext context, T id) + private async Task ThumbnailFile(HttpContext context, int id) { try { - var defaultSize = _thumbnailSettings.Sizes.FirstOrDefault(); - - if (defaultSize == null) - { - context.Response.StatusCode = (int)HttpStatusCode.NotFound; - return; - } - - var width = defaultSize.Width; - var height = defaultSize.Height; - - var size = context.Request.Query["size"].ToString() ?? ""; - var sizes = size.Split('x'); - if (sizes.Length == 2) - { - _ = int.TryParse(sizes[0], out width); - _ = int.TryParse(sizes[1], out height); - } - - var fileDao = _daoFactory.GetFileDao(); + var defaultSize = _thumbnailSettings.Sizes.FirstOrDefault(); + + if (defaultSize == null) + { + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + return; + } + + var width = defaultSize.Width; + var height = defaultSize.Height; + + var size = context.Request.Query["size"].ToString() ?? ""; + var sizes = size.Split('x'); + if (sizes.Length == 2) + { + _ = int.TryParse(sizes[0], out width); + _ = int.TryParse(sizes[1], out height); + } + + var fileDao = _daoFactory.GetFileDao(); var file = int.TryParse(context.Request.Query[FilesLinkUtility.Version], out var version) && version > 0 ? await fileDao.GetFileAsync(id, version) : await fileDao.GetFileAsync(id); @@ -1069,7 +1069,7 @@ public class FileHandlerService context.Response.Headers.Add("Content-Disposition", ContentDispositionUtil.GetHeaderValue("." + _global.ThumbnailExtension)); context.Response.ContentType = MimeMapping.GetMimeMapping("." + _global.ThumbnailExtension); - using (var stream = await fileDao.GetThumbnailAsync(file, width, height)) + using (var stream = await fileDao.GetThumbnailAsync(file, width, height)) { context.Response.Headers.Add("Content-Length", stream.Length.ToString(CultureInfo.InvariantCulture)); await stream.CopyToAsync(context.Response.Body); @@ -1101,9 +1101,68 @@ public class FileHandlerService } } + private async Task ThumbnailFileFromThirdparty(HttpContext context, string id) + { + try + { + var defaultSize = _thumbnailSettings.Sizes.FirstOrDefault(); + + if (defaultSize == null) + { + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + return; + } + + var width = defaultSize.Width; + var height = defaultSize.Height; + + var size = context.Request.Query["size"].ToString() ?? ""; + var sizes = size.Split('x'); + if (sizes.Length == 2) + { + _ = int.TryParse(sizes[0], out width); + _ = int.TryParse(sizes[1], out height); + } + + context.Response.Headers.Add("Content-Disposition", ContentDispositionUtil.GetHeaderValue("." + _global.ThumbnailExtension)); + context.Response.ContentType = MimeMapping.GetMimeMapping("." + _global.ThumbnailExtension); + + var fileDao = _daoFactory.GetFileDao(); + + using (var stream = await fileDao.GetThumbnailAsync(id, width, height)) + { + await stream.CopyToAsync(context.Response.Body); + } + } + catch (FileNotFoundException ex) + { + _logger.ErrorForUrl(context.Request.Url(), ex); + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + await context.Response.WriteAsync(ex.Message); + return; + } + catch (Exception ex) + { + _logger.ErrorForUrl(context.Request.Url(), ex); + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + await context.Response.WriteAsync(ex.Message); + return; + } + + try + { + await context.Response.Body.FlushAsync(); + await context.Response.CompleteAsync(); + } + catch (HttpException he) + { + _logger.ErrorThumbnail(he); + } + } + private static string GetEtag(File file) { - return file.Id + ":" + file.Version + ":" + file.Title.GetHashCode() + ":" + file.ContentLength; + return file.Id + ":" + file.Version + ":" + file.Title.GetHashCode() + ":" + file.ContentLength; } private Task CreateFile(HttpContext context) @@ -1194,33 +1253,33 @@ public class FileHandlerService context.Response.Redirect( (context.Request.Query["openfolder"].FirstOrDefault() ?? "").Equals("true") - ? await _pathProvider.GetFolderUrlByIdAsync(file.ParentId) - : (_filesLinkUtility.GetFileWebEditorUrl(file.Id) + "#message/" + HttpUtility.UrlEncode(string.Format(FilesCommonResource.MessageFileCreated, folder.Title)))); + ? await _pathProvider.GetFolderUrlByIdAsync(file.ParentId) + : (_filesLinkUtility.GetFileWebEditorUrl(file.Id) + "#message/" + HttpUtility.UrlEncode(string.Format(FilesCommonResource.MessageFileCreated, folder.Title)))); } private async Task InternalWriteError(HttpContext context, Exception ex, bool responseMessage) { - _logger.ErrorFileHandler(ex); - - if (responseMessage) - { - await context.Response.WriteAsync("error: " + ex.Message); - return; - } - context.Response.Redirect(PathProvider.StartURL + "#error/" + HttpUtility.UrlEncode(ex.Message), true); - return; - } - - private Task InternalWriteOk(HttpContext context, Folder folder, File file) - { - var message = string.Format(FilesCommonResource.MessageFileCreated, folder.Title); - if (_fileUtility.CanWebRestrictedEditing(file.Title)) - { - message = string.Format(FilesCommonResource.MessageFileCreatedForm, folder.Title); - } - - return context.Response.WriteAsync("ok: " + message); - } + _logger.ErrorFileHandler(ex); + + if (responseMessage) + { + await context.Response.WriteAsync("error: " + ex.Message); + return; + } + context.Response.Redirect(PathProvider.StartURL + "#error/" + HttpUtility.UrlEncode(ex.Message), true); + return; + } + + private Task InternalWriteOk(HttpContext context, Folder folder, File file) + { + var message = string.Format(FilesCommonResource.MessageFileCreated, folder.Title); + if (_fileUtility.CanWebRestrictedEditing(file.Title)) + { + message = string.Format(FilesCommonResource.MessageFileCreatedForm, folder.Title); + } + + return context.Response.WriteAsync("ok: " + message); + } private async Task> CreateFileFromTemplateAsync(Folder folder, string fileTitle, string docType) { @@ -1260,7 +1319,7 @@ public class FileHandlerService var file = _serviceProvider.GetService>(); file.Title = fileTitle; - file.ParentId = folder.Id; + file.ParentId = folder.Id; file.Comment = FilesCommonResource.CommentCreate; var fileDao = _daoFactory.GetFileDao(); @@ -1278,7 +1337,7 @@ public class FileHandlerService var file = _serviceProvider.GetService>(); file.Title = fileTitle; - file.ParentId = folder.Id; + file.ParentId = folder.Id; file.Comment = FilesCommonResource.CommentCreate; var request = new HttpRequestMessage @@ -1351,7 +1410,7 @@ public class FileHandlerService return; } - urlRedirect = _filesLinkUtility.GetFileWebPreviewUrl(_fileUtility, file.Title, file.Id); + urlRedirect = _filesLinkUtility.GetFileWebPreviewUrl(_fileUtility, file.Title, file.Id); } if (string.IsNullOrEmpty(urlRedirect)) @@ -1515,4 +1574,4 @@ public static class FileHandlerExtensions { return builder.UseMiddleware(); } -} +} \ No newline at end of file diff --git a/products/ASC.Files/Core/Log/FFmpegServiceLogger.cs b/products/ASC.Files/Core/Log/FFmpegServiceLogger.cs index 96f26389dc..ce7115db53 100644 --- a/products/ASC.Files/Core/Log/FFmpegServiceLogger.cs +++ b/products/ASC.Files/Core/Log/FFmpegServiceLogger.cs @@ -30,6 +30,9 @@ internal static partial class FFmpegServiceLogger [LoggerMessage(Level = LogLevel.Error, Message = "FFmpeg/avconv was not found in PATH or 'files.ffmpeg' setting")] public static partial void ErrorFFmpeg(this ILogger logger); - [LoggerMessage(Level = LogLevel.Information, Message = "FFmpeg found in {path}")] + [LoggerMessage(Level = LogLevel.Error, Message = "File {file} not found")] + public static partial void ErrorFileNotFound(this ILogger logger, string file); + + [LoggerMessage(Level = LogLevel.Information, Message = "FFmpeg found in {path}")] public static partial void InformationFFmpegFoundIn(this ILogger logger, string path); } diff --git a/products/ASC.Files/Core/Services/FFmpegService/FFmpeg.cs b/products/ASC.Files/Core/Services/FFmpegService/FFmpeg.cs index 0e1d648b2f..b28fa7317b 100644 --- a/products/ASC.Files/Core/Services/FFmpegService/FFmpeg.cs +++ b/products/ASC.Files/Core/Services/FFmpegService/FFmpeg.cs @@ -44,11 +44,25 @@ public class FFmpegService } } + private readonly List _convertableMedia; + private readonly List _fFmpegExecutables = new List() { "ffmpeg", "avconv" }; + private readonly string _fFmpegPath; + private readonly string _fFmpegArgs; + private readonly string _fFmpegThumbnailsArgs; + private readonly List _fFmpegFormats; + + private readonly ILogger _logger; + public bool IsConvertable(string extension) { return MustConvertable.Contains(extension.TrimStart('.')); } + public bool ExistFormat(string extension) + { + return _fFmpegFormats.Contains(extension); + } + public Task Convert(Stream inputStream, string inputFormat) { if (inputStream == null) @@ -68,17 +82,13 @@ public class FFmpegService { var startInfo = PrepareFFmpeg(inputFormat); - Process process; - using (process = new Process { StartInfo = startInfo }) - { - process.Start(); + using var process = Process.Start(startInfo); - await StreamCopyToAsync(inputStream, process.StandardInput.BaseStream, closeDst: true); + await inputStream.CopyToAsync(process.StandardInput.BaseStream); - await ProcessLog(process.StandardError.BaseStream); + await ProcessLog(process.StandardError.BaseStream); - return process.StandardOutput.BaseStream; - } + return process.StandardOutput.BaseStream; } public FFmpegService(ILogger logger, IConfiguration configuration) @@ -86,6 +96,8 @@ public class FFmpegService _logger = logger; _fFmpegPath = configuration["files:ffmpeg:value"]; _fFmpegArgs = configuration["files:ffmpeg:args"] ?? "-i - -preset ultrafast -movflags frag_keyframe+empty_moov -f {0} -"; + _fFmpegThumbnailsArgs = configuration["files:ffmpeg:thumbnails:args"] ?? "-ss 3 -i \"{0}\" -vf \"thumbnail\" -frames:v 1 -vsync vfr \"{1}\" -y"; + _fFmpegFormats = configuration.GetSection("files:ffmpeg:thumbnails:formats").Get>() ?? FileUtility.ExtsVideo; _convertableMedia = (configuration.GetSection("files:ffmpeg:exts").Get() ?? Array.Empty()).ToList(); @@ -120,13 +132,6 @@ public class FFmpegService } } - private readonly List _convertableMedia; - private readonly List _fFmpegExecutables = new List() { "ffmpeg", "avconv" }; - private readonly string _fFmpegPath; - private readonly string _fFmpegArgs; - - private readonly ILogger _logger; - private ProcessStartInfo PrepareFFmpeg(string inputFormat) { if (!_convertableMedia.Contains(inputFormat.TrimStart('.'))) @@ -134,6 +139,15 @@ public class FFmpegService throw new ArgumentException("input format"); } + var startInfo = PrepareCommonFFmpeg(); + + startInfo.Arguments = string.Format(_fFmpegArgs, "mp4"); + + return startInfo; + } + + private ProcessStartInfo PrepareCommonFFmpeg() + { var startInfo = new ProcessStartInfo(); if (string.IsNullOrEmpty(_fFmpegPath)) @@ -143,56 +157,15 @@ public class FFmpegService } startInfo.FileName = _fFmpegPath; - startInfo.WorkingDirectory = Path.GetDirectoryName(_fFmpegPath); - startInfo.Arguments = string.Format(_fFmpegArgs, "mp4"); startInfo.UseShellExecute = false; startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardInput = true; startInfo.RedirectStandardError = true; startInfo.CreateNoWindow = true; startInfo.WindowStyle = ProcessWindowStyle.Normal; - return startInfo; } - private static Task StreamCopyToAsync(Stream srcStream, Stream dstStream, bool closeSrc = false, bool closeDst = false) - { - ArgumentNullException.ThrowIfNull(srcStream); - ArgumentNullException.ThrowIfNull(dstStream); - - return StreamCopyToAsyncInternal(srcStream, dstStream, closeSrc, closeDst); - } - - private static async Task StreamCopyToAsyncInternal(Stream srcStream, Stream dstStream, bool closeSrc, bool closeDst) - { - const int bufs = 2048 * 4; - - var buffer = new byte[bufs]; - int readed; - var total = 0; - while ((readed = await srcStream.ReadAsync(buffer, 0, bufs)) > 0) - { - await dstStream.WriteAsync(buffer, 0, readed); - await dstStream.FlushAsync(); - total += readed; - } - - if (closeSrc) - { - srcStream.Dispose(); - srcStream.Close(); - } - - if (closeDst) - { - await dstStream.FlushAsync(); - dstStream.Dispose(); - dstStream.Close(); - } - - return total; - } - private async Task ProcessLog(Stream stream) { using var reader = new StreamReader(stream, Encoding.UTF8); @@ -202,4 +175,15 @@ public class FFmpegService _logger.Information(line); } } + + public async Task CreateThumbnail(string sourcePath, string destPath) + { + var startInfo = PrepareCommonFFmpeg(); + + startInfo.Arguments = string.Format(_fFmpegThumbnailsArgs, sourcePath, destPath); + + using var process = Process.Start(startInfo); + + await ProcessLog(process.StandardError.BaseStream); + } } diff --git a/products/ASC.Files/Server/Api/FilesController.cs b/products/ASC.Files/Server/Api/FilesController.cs index ec199a9e2d..13299a0067 100644 --- a/products/ASC.Files/Server/Api/FilesController.cs +++ b/products/ASC.Files/Server/Api/FilesController.cs @@ -61,7 +61,7 @@ public class FilesControllerThirdparty : FilesController _documentServiceHelper = documentServiceHelper; } - [HttpGet("file/app-{fileId}", Order = int.MaxValue)] + [HttpGet("file/app-{fileId}", Order = 1)] public async Task GetFileInfoThirdPartyAsync(string fileId) { fileId = "app-" + fileId; diff --git a/products/ASC.Files/Server/Api/UploadController.cs b/products/ASC.Files/Server/Api/UploadController.cs index 8d88887b43..78e10041a5 100644 --- a/products/ASC.Files/Server/Api/UploadController.cs +++ b/products/ASC.Files/Server/Api/UploadController.cs @@ -104,7 +104,7 @@ public abstract class UploadController : ApiControllerBase /// Keep status conversation after finishing /// Uploads /// - [HttpPost("{folderId}/insert", Order = int.MaxValue)] + [HttpPost("{folderId}/insert", Order = 1)] public Task> InsertFileAsync(T folderId, [FromForm][ModelBinder(BinderType = typeof(InsertFileModelBinder))] InsertFileRequestDto inDto) { return _filesControllerHelper.InsertFileAsync(folderId, inDto.Stream, inDto.Title, inDto.CreateNewIfExist, inDto.KeepConvertStatus); @@ -133,7 +133,7 @@ public abstract class UploadController : ApiControllerBase /// If True, upload documents in original formats as well /// Keep status conversation after finishing /// Uploaded file - [HttpPost("{folderId}/upload", Order = int.MaxValue)] + [HttpPost("{folderId}/upload", Order = 1)] public Task UploadFileAsync(T folderId, [ModelBinder(BinderType = typeof(UploadModelBinder))] UploadRequestDto inDto) { return _filesControllerHelper.UploadFileAsync(folderId, inDto); diff --git a/products/ASC.Files/Service/Expired/DeleteExpiredService.cs b/products/ASC.Files/Service/Expired/DeleteExpiredService.cs new file mode 100644 index 0000000000..6b6371a6fa --- /dev/null +++ b/products/ASC.Files/Service/Expired/DeleteExpiredService.cs @@ -0,0 +1,54 @@ +// (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.Expired; + +[Singletone] +public class DeleteExpiredService : BackgroundService +{ + private readonly CommonChunkedUploadSessionHolder _commonChunkedUploadSessionHolder; + private readonly TimeSpan _launchFrequency; + + public DeleteExpiredService( + ILogger log, + SetupInfo setupInfo, + TempPath tempPath, + GlobalStore globalStore, + IConfiguration configuration) + { + _launchFrequency = TimeSpan.Parse(configuration["files:deleteExpired"] ?? "1", CultureInfo.InvariantCulture); + _commonChunkedUploadSessionHolder = new CommonChunkedUploadSessionHolder(tempPath, log, globalStore.GetStore(false), FileConstant.StorageDomainTmp, setupInfo.ChunkUploadSize); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + await _commonChunkedUploadSessionHolder.DeleteExpiredAsync(); + await Task.Delay(_launchFrequency, stoppingToken); + } + } +} \ No newline at end of file diff --git a/products/ASC.Files/Service/GlobalUsings.cs b/products/ASC.Files/Service/GlobalUsings.cs index dfb57e8a05..2dc48c882e 100644 --- a/products/ASC.Files/Service/GlobalUsings.cs +++ b/products/ASC.Files/Service/GlobalUsings.cs @@ -24,62 +24,61 @@ // 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 -global using System.Collections.Concurrent; +global using System.Collections.Concurrent; global using System.Globalization; -global using System.Linq.Expressions; -global using System.Reflection; +global using System.Linq.Expressions; +global using System.Reflection; global using System.Text.Json; global using ASC.Api.Core; -global using ASC.Api.Core.Extensions; -global using ASC.Common; -global using ASC.Common.Caching; -global using ASC.Common.DependencyInjection; +global using ASC.Api.Core.Extensions; +global using ASC.Common; +global using ASC.Common.Caching; +global using ASC.Common.DependencyInjection; global using ASC.Common.Log; -global using ASC.Common.Mapping; -global using ASC.Common.Utils; -global using ASC.Core; -global using ASC.Core.Common; -global using ASC.Core.Common.EF; -global using ASC.Core.Common.Hosting; -global using ASC.Core.Common.Hosting.Interfaces; -global using ASC.Core.Tenants; -global using ASC.ElasticSearch; -global using ASC.ElasticSearch.Service; +global using ASC.Common.Mapping; +global using ASC.Core; +global using ASC.Core.ChunkedUploader; +global using ASC.Core.Common; +global using ASC.Core.Common.EF; +global using ASC.Core.Common.Hosting; +global using ASC.Core.Common.Hosting.Interfaces; +global using ASC.Core.Tenants; +global using ASC.ElasticSearch; +global using ASC.ElasticSearch.Service; global using ASC.EventBus.Abstractions; global using ASC.EventBus.Log; -global using ASC.Feed; -global using ASC.Feed.Aggregator.Service; +global using ASC.Feed; +global using ASC.Feed.Aggregator.Service; global using ASC.Feed.Core; -global using ASC.Feed.Data; +global using ASC.Feed.Data; global using ASC.Files.AutoCleanUp; -global using ASC.Files.Core; -global using ASC.Files.Core.Core.Entries; -global using ASC.Files.Core.EF; +global using ASC.Files.Core; +global using ASC.Files.Core.Core.Entries; +global using ASC.Files.Core.EF; global using ASC.Files.Core.IntegrationEvents.Events; -global using ASC.Files.Core.Log; -global using ASC.Files.Core.Resources; -global using ASC.Files.Core.Security; +global using ASC.Files.Core.Log; +global using ASC.Files.Core.Resources; +global using ASC.Files.Core.Security; +global using ASC.Files.Expired; global using ASC.Files.Service.Log; -global using ASC.Files.ThumbnailBuilder; +global using ASC.Files.ThumbnailBuilder; global using ASC.Thumbnail.IntegrationEvents.EventHandling; -global using ASC.Web.Core; -global using ASC.Web.Core.Files; -global using ASC.Web.Core.Users; -global using ASC.Web.Files.Classes; -global using ASC.Web.Files.Core; -global using ASC.Web.Files.Core.Search; -global using ASC.Web.Files.Services.DocumentService; -global using ASC.Web.Files.Utils; -global using ASC.Web.Studio.Core; +global using ASC.Web.Core; +global using ASC.Web.Core.Files; +global using ASC.Web.Files.Classes; +global using ASC.Web.Files.Core.Search; +global using ASC.Web.Files.Services.DocumentService; +global using ASC.Web.Files.Services.FFmpegService; +global using ASC.Web.Files.Utils; +global using ASC.Web.Studio.Core; -global using Autofac; +global using Autofac; global using Microsoft.AspNetCore.Builder; -global using Microsoft.Extensions.Hosting.WindowsServices; -global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Hosting.WindowsServices; +global using Microsoft.Extensions.Logging; -global using SixLabors.ImageSharp; -global using SixLabors.ImageSharp.Formats.Png; +global using SixLabors.ImageSharp; global using static ASC.Web.Core.Files.DocumentService; \ No newline at end of file diff --git a/products/ASC.Files/Service/Program.cs b/products/ASC.Files/Service/Program.cs index 27f5473b6a..8288bb19c4 100644 --- a/products/ASC.Files/Service/Program.cs +++ b/products/ASC.Files/Service/Program.cs @@ -82,6 +82,9 @@ builder.Host.ConfigureDefault(args, (hostContext, config, env, path) => services.AddHostedService(); diHelper.TryAdd(); + services.AddHostedService(); + diHelper.TryAdd(); + diHelper.TryAdd(); diHelper.TryAdd(); diHelper.TryAdd(); diff --git a/products/ASC.Files/Service/Thumbnail/Builder.cs b/products/ASC.Files/Service/Thumbnail/Builder.cs index 3913604b81..91d9af87f6 100644 --- a/products/ASC.Files/Service/Thumbnail/Builder.cs +++ b/products/ASC.Files/Service/Thumbnail/Builder.cs @@ -42,7 +42,7 @@ public class BuilderQueue _config = settings; } - public async Task BuildThumbnails(IEnumerable> filesWithoutThumbnails) + public async Task BuildThumbnails(IEnumerable> filesWithoutThumbnails) { try { @@ -70,18 +70,25 @@ public class BuilderQueue [Scope] public class Builder { - private readonly ThumbnailSettings _config; - private readonly ILogger _logger; - private readonly TenantManager _tenantManager; - private readonly IDaoFactory _daoFactory; - private readonly DocumentServiceConnector _documentServiceConnector; - private readonly DocumentServiceHelper _documentServiceHelper; - private readonly Global _global; - private readonly PathProvider _pathProvider; + private readonly ThumbnailSettings _config; + private readonly ILogger _logger; + private readonly TenantManager _tenantManager; + private readonly IDaoFactory _daoFactory; + private readonly DocumentServiceConnector _documentServiceConnector; + private readonly DocumentServiceHelper _documentServiceHelper; + private readonly Global _global; + private readonly PathProvider _pathProvider; private readonly IHttpClientFactory _clientFactory; private readonly ThumbnailSettings _thumbnailSettings; - private readonly SocketManager _socketManager; - + private readonly SocketManager _socketManager; + private readonly FFmpegService _fFmpegService; + private readonly TempPath _tempPath; + + private readonly List _imageFormatsCanBeCrop = new List + { + ".bmp", ".gif", ".jpeg", ".jpg", ".pbm", ".png", ".tiff", ".tga", ".webp", + }; + public Builder( ThumbnailSettings settings, TenantManager tenantManager, @@ -92,8 +99,10 @@ public class Builder PathProvider pathProvider, ILoggerProvider log, IHttpClientFactory clientFactory, + FFmpegService fFmpegService, + TempPath tempPath, SocketManager socketManager, - ThumbnailSettings thumbnailSettings) + ThumbnailSettings thumbnailSettings) { _config = settings; _tenantManager = tenantManager; @@ -104,11 +113,13 @@ public class Builder _pathProvider = pathProvider; _logger = log.CreateLogger("ASC.Files.ThumbnailBuilder"); _clientFactory = clientFactory; + _fFmpegService = fFmpegService; + _tempPath = tempPath; _socketManager = socketManager; _thumbnailSettings = thumbnailSettings; } - internal async Task BuildThumbnail(FileData fileData) + internal async Task BuildThumbnail(FileData fileData) { try { @@ -122,7 +133,7 @@ public class Builder return; } - await GenerateThumbnail(fileDao, fileData); + await GenerateThumbnail(fileDao, fileData); } catch (Exception exception) { @@ -134,13 +145,13 @@ public class Builder } } - private async Task GenerateThumbnail(IFileDao fileDao, FileData fileData) + private async Task GenerateThumbnail(IFileDao fileDao, FileData fileData) { File file = null; try { - file = await fileDao.GetFileAsync(fileData.FileId); + file = await fileDao.GetFileAsync(fileData.FileId); if (file == null) { @@ -158,29 +169,37 @@ public class Builder var ext = FileUtility.GetFileExtension(file.Title); - if (!_config.FormatsArray.Contains(ext) || file.Encrypted || file.RootFolderType == FolderType.TRASH || file.ContentLength > _config.AvailableFileSize) + if (!CanCreateThumbnail(ext) || file.Encrypted || file.RootFolderType == FolderType.TRASH || file.ContentLength > _config.AvailableFileSize) { file.ThumbnailStatus = ASC.Files.Core.Thumbnail.NotRequired; foreach (var size in _thumbnailSettings.Sizes) { await fileDao.SaveThumbnailAsync(file, null, size.Width, size.Height); - } + } return; } - if (IsImage(file)) + if (IsVideo(ext)) { - await CropImage(fileDao, file); + await MakeThumbnailFromVideo(fileDao, file); } else { - await MakeThumbnail(fileDao, file); + + if (IsImage(ext)) + { + await CropImage(fileDao, file); + } + else + { + await MakeThumbnail(fileDao, file); + } } var newFile = await fileDao.GetFileStableAsync(file.Id); - await _socketManager.UpdateFileAsync(newFile); + await _socketManager.UpdateFileAsync(newFile); } catch (Exception exception) { @@ -194,9 +213,32 @@ public class Builder } } } - } + } - private async Task MakeThumbnail(IFileDao fileDao, File file) + private async Task MakeThumbnailFromVideo(IFileDao fileDao, File file) + { + var streamFile = await fileDao.GetFileStreamAsync(file); + + var thumbPath = _tempPath.GetTempFileName("jpg"); + var tempFilePath = _tempPath.GetTempFileName(Path.GetExtension(file.Title)); + + using (var fileStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.ReadWrite, System.IO.FileShare.Read)) + { + await streamFile.CopyToAsync(fileStream); + } + + await _fFmpegService.CreateThumbnail(tempFilePath, thumbPath); + + using (var streamThumb = new FileStream(thumbPath, FileMode.Open, FileAccess.ReadWrite, System.IO.FileShare.Read)) + { + await Crop(fileDao, file, streamThumb); + } + + File.Delete(thumbPath); + File.Delete(tempFilePath); + } + + private async Task MakeThumbnail(IFileDao fileDao, File file) { foreach (var w in _config.Sizes) { @@ -254,7 +296,7 @@ public class Builder } } - private async Task<(bool, string)> GetThumbnailUrl(File file, string toExtension, int width, int height) + private async Task<(bool, string)> GetThumbnailUrl(File file, string toExtension, int width, int height) { var fileUri = _pathProvider.GetFileStreamUrl(file); fileUri = _documentServiceConnector.ReplaceCommunityAdress(fileUri); @@ -273,7 +315,7 @@ public class Builder IgnorePrintArea = true, //Orientation = "landscape", // "297mm" x "210mm" FitToHeight = height, - FitToWidth = width, + FitToWidth = width, Headings = false, GridLines = false, Margins = new DocumentService.SpreadsheetLayout.LayoutMargins @@ -288,54 +330,62 @@ public class Builder } }; - var (operationResultProgress, url) = await _documentServiceConnector.GetConvertedUriAsync(fileUri, fileExtension, toExtension, docKey, null, CultureInfo.CurrentCulture.Name, thumbnail, spreadsheetLayout, false); + var (operationResultProgress, url) = await _documentServiceConnector.GetConvertedUriAsync(fileUri, fileExtension, toExtension, docKey, null, CultureInfo.CurrentCulture.Name, thumbnail, spreadsheetLayout, false); operationResultProgress = Math.Min(operationResultProgress, 100); - return (operationResultProgress == 100, url); + return (operationResultProgress == 100, url); } - private async Task SaveThumbnail(IFileDao fileDao, File file, string thumbnailUrl, int width, int height) + private async Task SaveThumbnail(IFileDao fileDao, File file, string thumbnailUrl, int width, int height) { _logger.DebugMakeThumbnail3(file.Id.ToString(), thumbnailUrl); - using var request = new HttpRequestMessage(); + using var request = new HttpRequestMessage(); request.RequestUri = new Uri(thumbnailUrl); var httpClient = _clientFactory.CreateClient(); using var response = httpClient.Send(request); - using (var stream = await response.Content.ReadAsStreamAsync()) + using (var stream = await response.Content.ReadAsStreamAsync()) { using (var sourceImg = await Image.LoadAsync(stream)) { await CropAsync(sourceImg, fileDao, file, width, height); } - } + } _logger.DebugMakeThumbnail4(file.Id.ToString()); } - private bool IsImage(File file) + private bool CanCreateThumbnail(string extention) { - var extension = FileUtility.GetFileExtension(file.Title); - - return FileUtility.ExtsImage.Contains(extension); + return _config.FormatsArray.Contains(extention) || IsVideo(extention) || IsImage(extention); } - private async Task CropImage(IFileDao fileDao, File file) + private bool IsImage(string extention) + { + return _imageFormatsCanBeCrop.Contains(extention); + } + + private bool IsVideo(string extention) + { + return _fFmpegService.ExistFormat(extention); + } + + private async Task CropImage(IFileDao fileDao, File file) { _logger.DebugCropImage(file.Id.ToString()); - using (var stream = await fileDao.GetFileStreamAsync(file)) + using (var stream = await fileDao.GetFileStreamAsync(file)) { - await Crop(fileDao, file, stream); + await Crop(fileDao, file, stream); } _logger.DebugCropImageSuccessfullySaved(file.Id.ToString()); } - private async Task Crop(IFileDao fileDao, File file, Stream stream) + private async Task Crop(IFileDao fileDao, File file, Stream stream) { - using (var sourceImg = await Image.LoadAsync(stream)) + using (var sourceImg = await Image.LoadAsync(stream)) { //var tasks = new List(); @@ -357,12 +407,11 @@ public class Builder GC.Collect(); } - private async ValueTask CropAsync(Image sourceImg, IFileDao fileDao, File file, int width, int height) + private async ValueTask CropAsync(Image sourceImg, IFileDao fileDao, File file, int width, int height) { - var targetSize = new Size(Math.Min(sourceImg.Width, width), Math.Min(sourceImg.Height, height)); - using var targetImg = GetImageThumbnail(sourceImg, targetSize, width, height); + using var targetImg = GetImageThumbnail(sourceImg, width); using var targetStream = new MemoryStream(); - switch (_global.ThumbnailExtension) + switch (_global.ThumbnailExtension) { case ThumbnailExtension.bmp: await targetImg.SaveAsBmpAsync(targetStream); @@ -392,8 +441,8 @@ public class Builder await fileDao.SaveThumbnailAsync(file, targetStream, width, height); } - private Image GetImageThumbnail(Image sourceBitmap, Size targetSize, int thumbnaillWidth, int thumbnaillHeight) + private Image GetImageThumbnail(Image sourceBitmap, int thumbnaillWidth) { return sourceBitmap.Clone(x => x.BackgroundColor(Color.White).Resize(thumbnaillWidth, 0)); } -} +} diff --git a/web/ASC.Web.Api/Api/AuthenticationController.cs b/web/ASC.Web.Api/Api/AuthenticationController.cs index c77564d436..e9de9d48ea 100644 --- a/web/ASC.Web.Api/Api/AuthenticationController.cs +++ b/web/ASC.Web.Api/Api/AuthenticationController.cs @@ -144,7 +144,7 @@ public class AuthenticationController : ControllerBase } [AllowNotPayment] - [HttpPost("{code}", Order = int.MaxValue)] + [HttpPost("{code}", Order = 1)] public AuthenticationTokenDto AuthenticateMeFromBodyWithCode(AuthRequestsDto inDto) { var tenant = _tenantManager.GetCurrentTenant().Id;