diff --git a/products/ASC.Files/Core/ApiModels/ResponseDto/FileDto.cs b/products/ASC.Files/Core/ApiModels/ResponseDto/FileDto.cs index b7a2d37eef0..5d8142d9438 100644 --- a/products/ASC.Files/Core/ApiModels/ResponseDto/FileDto.cs +++ b/products/ASC.Files/Core/ApiModels/ResponseDto/FileDto.cs @@ -229,11 +229,22 @@ private async Task> GetFileWrapperAsync(File file, string order if (fileType == FileType.Pdf) { - var linkDao = daoFactory.GetLinkDao(); var folderDao = daoFactory.GetCacheFolderDao(); - var linkedIdTask = linkDao.GetLinkedAsync(file.Id); - var propertiesTask = fileDao.GetProperties(file.Id); + Task linkedIdTask; + Task> propertiesTask; + + if (file.FormInfo != null) + { + linkedIdTask = Task.FromResult(file.FormInfo.LinkedId); + propertiesTask = Task.FromResult(file.FormInfo.Properties); + } + else + { + linkedIdTask = daoFactory.GetLinkDao().GetLinkedAsync(file.Id); + propertiesTask = fileDao.GetProperties(file.Id); + } + var currentFolderTask = folderDao.GetFolderAsync(file.ParentId); await Task.WhenAll(linkedIdTask, propertiesTask, currentFolderTask); diff --git a/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs b/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs index db559def77f..bb1b6f17df6 100644 --- a/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs +++ b/products/ASC.Files/Core/Core/Dao/Interfaces/IFileDao.cs @@ -341,6 +341,8 @@ IAsyncEnumerable> GetFilesAsync(IEnumerable parentIds, FilterType fil Task> GetProperties(T fileId); + Task>> GetPropertiesAsync(IEnumerable filesIds); + Task SaveProperties(T fileId, EntryProperties entryProperties); Task GetFilesCountAsync(T parentId, FilterType filterType, bool subjectGroup, Guid subjectId, string searchText, string[] extension, bool searchInContent, diff --git a/products/ASC.Files/Core/Core/Dao/Interfaces/ILinkDao.cs b/products/ASC.Files/Core/Core/Dao/Interfaces/ILinkDao.cs index 1c419ac5350..17b7fb4e7ae 100644 --- a/products/ASC.Files/Core/Core/Dao/Interfaces/ILinkDao.cs +++ b/products/ASC.Files/Core/Core/Dao/Interfaces/ILinkDao.cs @@ -31,6 +31,7 @@ public interface ILinkDao Task AddLinkAsync(T sourceId, T linkedId); Task GetSourceAsync(T linkedId); Task GetLinkedAsync(T sourceId); + Task> GetLinkedIdsAsync(IEnumerable sourceIds); Task DeleteLinkAsync(T sourceId); Task DeleteAllLinkAsync(T sourceId); } diff --git a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs index 6a9321bb86d..9dd4df3ec44 100644 --- a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs +++ b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs @@ -1805,6 +1805,25 @@ public async Task> GetProperties(int fileId) return data != null ? EntryProperties.Deserialize(data, logger) : null; } + public async Task>> GetPropertiesAsync(IEnumerable filesIds) + { + var tenantId = await _tenantManager.GetCurrentTenantIdAsync(); + await using var filesDbContext = await _dbContextFactory.CreateDbContextAsync(); + + var properties = await filesDbContext.FilesPropertiesAsync(tenantId, filesIds.Select(f => f.ToString())).ToListAsync(); + + var propertiesMap = new Dictionary>(properties.Count); + foreach (var property in properties) + { + if (int.TryParse(property.EntryId, out var id)) + { + propertiesMap.TryAdd(id, EntryProperties.Deserialize(property.Data, logger)); + } + } + + return propertiesMap; + } + public async Task SaveProperties(int fileId, EntryProperties entryProperties) { string data; diff --git a/products/ASC.Files/Core/Core/Dao/TeamlabDao/LinkDao.cs b/products/ASC.Files/Core/Core/Dao/TeamlabDao/LinkDao.cs index cbb59ca123d..289e443bb2c 100644 --- a/products/ASC.Files/Core/Core/Dao/TeamlabDao/LinkDao.cs +++ b/products/ASC.Files/Core/Core/Dao/TeamlabDao/LinkDao.cs @@ -82,7 +82,7 @@ public async Task GetSourceAsync(T linkedId) if (Equals(fromDb, default)) { return default; - } + } return (T)Convert.ChangeType(fromDb, typeof(T)); } @@ -101,11 +101,27 @@ public async Task GetLinkedAsync(T sourceId) if (Equals(fromDb, default)) { return default; - } + } return (T)Convert.ChangeType(fromDb, typeof(T)); } + public async Task> GetLinkedIdsAsync(IEnumerable sourceIds) + { + var tenantId = await _tenantManager.GetCurrentTenantIdAsync(); + var mapping = daoFactory.GetMapping(); + + var mappedIds = await sourceIds.ToAsyncEnumerable().SelectAwait(async x => await mapping.MappingIdAsync(x)).ToListAsync(); + var source = mappedIds.Select(x => x.ToString()); + + await using var filesDbContext = await _dbContextFactory.CreateDbContextAsync(); + + return await filesDbContext.FilesLinksAsync(tenantId, source, _authContext.CurrentAccount.ID) + .ToDictionaryAsync( + x => (T)Convert.ChangeType(x.SourceId, typeof(T)), + x => (T)Convert.ChangeType(x.LinkedId, typeof(T))); + } + public async Task DeleteLinkAsync(T sourceId) { var tenantId = await _tenantManager.GetCurrentTenantIdAsync(); diff --git a/products/ASC.Files/Core/Core/EF/Queries/FileQueries.cs b/products/ASC.Files/Core/Core/EF/Queries/FileQueries.cs index a7a858c0cc0..cb9a58c96a8 100644 --- a/products/ASC.Files/Core/Core/EF/Queries/FileQueries.cs +++ b/products/ASC.Files/Core/Core/EF/Queries/FileQueries.cs @@ -273,6 +273,12 @@ public Task DataAsync(int tenantId, string entryId) return FileQueries.DataAsync(this, tenantId, entryId); } + [PreCompileQuery([PreCompileQuery.DefaultInt, null])] + public IAsyncEnumerable FilesPropertiesAsync(int tenantId, IEnumerable filesIds) + { + return FileQueries.FilesPropertiesAsync(this, tenantId, filesIds); + } + [PreCompileQuery([PreCompileQuery.DefaultInt, null])] public Task DeleteFilesPropertiesAsync(int tenantId, string entryId) { @@ -830,6 +836,13 @@ select f .Where(r => r.EntryId == entryId) .Select(r => r.Data) .FirstOrDefault()); + + public static readonly Func, IAsyncEnumerable> FilesPropertiesAsync = + Microsoft.EntityFrameworkCore.EF.CompileAsyncQuery( + (FilesDbContext ctx, int tenantId, IEnumerable filesIds) => + ctx.FilesProperties + .Where(r => r.TenantId == tenantId) + .Where(r => filesIds.Contains(r.EntryId))); public static readonly Func> DeleteFilesPropertiesAsync = Microsoft.EntityFrameworkCore.EF.CompileAsyncQuery( diff --git a/products/ASC.Files/Core/Core/EF/Queries/LinkQueries.cs b/products/ASC.Files/Core/Core/EF/Queries/LinkQueries.cs index ee4e600ac5f..f36d418f0ab 100644 --- a/products/ASC.Files/Core/Core/EF/Queries/LinkQueries.cs +++ b/products/ASC.Files/Core/Core/EF/Queries/LinkQueries.cs @@ -40,6 +40,11 @@ public Task LinkedIdAsync(int tenantId, string sourceId, Guid id) return LinkQueries.LinkedIdAsync(this, tenantId, sourceId, id); } + [PreCompileQuery([PreCompileQuery.DefaultInt, null, PreCompileQuery.DefaultGuid])] + public IAsyncEnumerable FilesLinksAsync(int tenantId, IEnumerable sourceIds, Guid id) + { + return LinkQueries.FilesLinksAsync(this, tenantId, sourceIds, id); + } [PreCompileQuery([PreCompileQuery.DefaultInt, null, PreCompileQuery.DefaultGuid])] public Task FileLinkAsync(int tenantId, string sourceId, Guid id) @@ -73,6 +78,14 @@ static file class LinkQueries .OrderByDescending(r => r) .LastOrDefault()); + public static readonly Func, Guid, IAsyncEnumerable> FilesLinksAsync = + Microsoft.EntityFrameworkCore.EF.CompileAsyncQuery( + (FilesDbContext ctx, int tenantId, IEnumerable sourceIds, Guid id) => + ctx.FilesLink + .Where(r => r.TenantId == tenantId && sourceIds.Contains(r.SourceId) && r.LinkedFor == id) + .GroupBy(r => r.SourceId) + .Select(g => g.OrderByDescending(r => r.LinkedId).LastOrDefault())); + public static readonly Func> FileLinkAsync = Microsoft.EntityFrameworkCore.EF.CompileAsyncQuery( (FilesDbContext ctx, int tenantId, string sourceId, Guid id) => diff --git a/products/ASC.Files/Core/Core/Entries/File.cs b/products/ASC.Files/Core/Core/Entries/File.cs index 8ef864c7e0b..5374806b4fa 100644 --- a/products/ASC.Files/Core/Core/Entries/File.cs +++ b/products/ASC.Files/Core/Core/Entries/File.cs @@ -236,4 +236,12 @@ public string ConvertedExtension } public DateTime? LastOpened { get; set; } + public FormInfo FormInfo { get; set; } +} + +public record FormInfo +{ + public T LinkedId { get; init; } + public EntryProperties Properties { get; init; } + public static FormInfo Empty => new(); } diff --git a/products/ASC.Files/Core/Core/FileStorageService.cs b/products/ASC.Files/Core/Core/FileStorageService.cs index ae53b482ff7..81dc07f222e 100644 --- a/products/ASC.Files/Core/Core/FileStorageService.cs +++ b/products/ASC.Files/Core/Core/FileStorageService.cs @@ -1656,8 +1656,14 @@ public async IAsyncEnumerable> GetFileHistoryAsync(T fileId) { throw new InvalidOperationException(FilesCommonResource.ErrorMessage_SecurityException_ReadFile); } + + var history = await fileDao.GetFileHistoryAsync(fileId).ToListAsync(); + + var t1 = entryStatusManager.SetFileStatusAsync(history); + var t2 = entryStatusManager.SetFormInfoAsync(history); + await Task.WhenAll(t1, t2); - await foreach (var r in fileDao.GetFileHistoryAsync(fileId)) + foreach (var r in history) { await entryStatusManager.SetFileStatusAsync(r); yield return r; diff --git a/products/ASC.Files/Core/Core/Thirdparty/ThirdPartyFileDao.cs b/products/ASC.Files/Core/Core/Thirdparty/ThirdPartyFileDao.cs index 8973a0f44f1..c7b71812e8f 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/ThirdPartyFileDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/ThirdPartyFileDao.cs @@ -720,6 +720,11 @@ public Task> GetProperties(string fileId) return Task.FromResult>(null); } + public Task>> GetPropertiesAsync(IEnumerable filesIds) + { + return Task.FromResult>>(null); + } + public Task SaveProperties(string fileId, EntryProperties entryProperties) { return Task.CompletedTask; diff --git a/products/ASC.Files/Core/Core/Thirdparty/ThirdPartyProviderDao.cs b/products/ASC.Files/Core/Core/Thirdparty/ThirdPartyProviderDao.cs index 7764213880a..08162e9a67d 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/ThirdPartyProviderDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/ThirdPartyProviderDao.cs @@ -97,6 +97,11 @@ public Task> GetProperties(string fileId) { return Task.FromResult>(null); } + + public Task>> GetPropertiesAsync(IEnumerable filesIds) + { + return Task.FromResult>>(null); + } public Task SaveProperties(string fileId, EntryProperties entryProperties) { diff --git a/products/ASC.Files/Core/Utils/EntryManager.cs b/products/ASC.Files/Core/Utils/EntryManager.cs index 7b78b4dd3b0..b14a1f8ab5e 100644 --- a/products/ASC.Files/Core/Utils/EntryManager.cs +++ b/products/ASC.Files/Core/Utils/EntryManager.cs @@ -202,6 +202,58 @@ public async Task SetIsFavoriteFoldersAsync(IEnumerable> folders) folder.IsFavorite = true; } } + + public async Task SetFormInfoAsync(IEnumerable> files) + { + if (!files.Any()) + { + return; + } + + var pdfs = new List>(); + + foreach (var file in files) + { + var extension = FileUtility.GetFileExtension(file.Title); + var fileType = FileUtility.GetFileTypeByExtention(extension); + + if (fileType != FileType.Pdf) + { + continue; + } + + if (!file.ProviderEntry) + { + pdfs.Add(file); + } + else + { + file.FormInfo = FormInfo.Empty; + } + } + + if (pdfs.Count == 0) + { + return; + } + + var linkDao = daoFactory.GetLinkDao(); + var fileDao = daoFactory.GetFileDao(); + + var ids = pdfs.Select(f => f.Id); + + var linkedIdsMap = await linkDao.GetLinkedIdsAsync(ids); + var entryPropertiesMap = await fileDao.GetPropertiesAsync(ids); + + foreach (var pdf in pdfs) + { + pdf.FormInfo = new FormInfo + { + LinkedId = linkedIdsMap.GetValueOrDefault(pdf.Id), + Properties = entryPropertiesMap.GetValueOrDefault(pdf.Id) + }; + } + } } [Scope] @@ -404,7 +456,6 @@ public class EntryManager(IDaoFactory daoFactory, var allFoldersCountTask = folderDao.GetFoldersCountAsync(parent.Id, foldersFilterType, subjectGroup, subjectId, foldersSearchText, withSubfolders, excludeSubject, roomId); var allFilesCountTask = fileDao.GetFilesCountAsync(parent.Id, filesFilterType, subjectGroup, subjectId, filesSearchText, fileExtension, searchInContent, withSubfolders, excludeSubject, roomId); var filesToUpdate = new List>(); - if (room is { FolderType: FolderType.VirtualDataRoom, SettingsIndexing: true }) { @@ -508,10 +559,12 @@ public class EntryManager(IDaoFactory daoFactory, } var fileStatusTask = entryStatusManager.SetFileStatusAsync(filesToUpdate); + var formInfoTask = entryStatusManager.SetFormInfoAsync(filesToUpdate); + var tagsNewTask = fileMarker.SetTagsNewAsync(parent, entries); var originsTask = SetOriginsAsync(parent, entries); - await Task.WhenAll(fileStatusTask, tagsNewTask, originsTask); + await Task.WhenAll(fileStatusTask, tagsNewTask, originsTask, formInfoTask); total = await allFoldersCountTask + await allFilesCountTask; @@ -627,7 +680,10 @@ public class EntryManager(IDaoFactory daoFactory, var t2 = entryStatusManager.SetIsFavoriteFoldersAsync(internalFolders); var t3 = entryStatusManager.SetFileStatusAsync(thirdPartyFiles); var t4 = entryStatusManager.SetIsFavoriteFoldersAsync(thirdPartyFolders); - await Task.WhenAll(t1, t2, t3, t4); + var t5 = entryStatusManager.SetFormInfoAsync(internalFiles); + var t6 = entryStatusManager.SetFormInfoAsync(thirdPartyFiles); + + await Task.WhenAll(t1, t2, t3, t4, t5, t6); return (data, total); @@ -2063,6 +2119,7 @@ await folderDao.GetFolderAsync(inProcessFormFolderId) await InitFormFillingProperties(folder.Id, Path.GetFileNameWithoutExtension(file.Title), file.Id, inProcessFormFolderId, readyFormFolderId, folder.CreateBy, properties, fileDao, folderDao); } + public async Task<(T readyFormFolderId, T inProcessFolderId)> InitSystemFormFillingFolders(T formFillingRoomId, IFolderDao folderDao, Guid createBy) { var readyFormFolder = serviceProvider.GetService>(); @@ -2084,6 +2141,7 @@ await folderDao.GetFolderAsync(inProcessFormFolderId) return (await readyFormFolderTask, await inProcessFolderTask); } + private async Task CreateFormFillingFolder(string sourceTitle, T parentId, FolderType folderType, Guid createBy, IFolderDao folderDao) { var folder = serviceProvider.GetService>(); @@ -2094,18 +2152,20 @@ private async Task CreateFormFillingFolder(string sourceTitle, T parentId, return await folderDao.SaveFolderAsync(folder); } + private async Task CreateCsvResult(T resultsFolderId, Guid createBy, string sourceTitle, IFileDao fileDao) { using var textStream = new MemoryStream(Encoding.UTF8.GetBytes("")); - var csvFile = serviceProvider.GetService>(); - csvFile.ParentId = resultsFolderId; - csvFile.Title = Global.ReplaceInvalidCharsAndTruncate(sourceTitle + ".csv"); - csvFile.CreateBy = createBy; + var csvFile = serviceProvider.GetService>(); + csvFile.ParentId = resultsFolderId; + csvFile.Title = Global.ReplaceInvalidCharsAndTruncate(sourceTitle + ".csv"); + csvFile.CreateBy = createBy; - var file = await fileDao.SaveFileAsync(csvFile, textStream, false); + var file = await fileDao.SaveFileAsync(csvFile, textStream, false); - return file.Id; - } + return file.Id; + } + private async Task> InitFormFillingProperties(T roomId, string sourceTitle, T sourceFileId, T inProcessFormFolderId, T readyFormFolderId, Guid createBy, EntryProperties properties, IFileDao fileDao, IFolderDao folderDao) { var templatesFolderTask = CreateFormFillingFolder(sourceTitle, inProcessFormFolderId, FolderType.FormFillingFolderInProgress, createBy, folderDao); @@ -2128,7 +2188,8 @@ private async Task> InitFormFillingProperties(T roomId, st await fileDao.SaveProperties(sourceFileId, properties); return properties; - } + } + private async Task SetOriginsAsync(IFolder parent, IEnumerable entries) { if (parent.FolderType != FolderType.TRASH || !entries.Any()) diff --git a/products/ASC.Files/Core/Utils/FileMarker.cs b/products/ASC.Files/Core/Utils/FileMarker.cs index 6b261968aa7..0a57b7fad72 100644 --- a/products/ASC.Files/Core/Utils/FileMarker.cs +++ b/products/ASC.Files/Core/Utils/FileMarker.cs @@ -105,7 +105,8 @@ public class FileMarker( RoomsNotificationSettingsHelper roomsNotificationSettingsHelper, FileMarkerCache fileMarkerCache, IDistributedLockProvider distributedLockProvider, - FileMarkerHelper fileMarkerHelper) + FileMarkerHelper fileMarkerHelper, + EntryStatusManager entryStatusManager) { private const string CacheKeyFormat = "MarkedAsNew/{0}/folder_{1}"; private const string LockKey = "file_marker"; @@ -972,10 +973,12 @@ private async Task, Tag>> GetEntryTagsAsync(IEnumerab var filesTags = tags.Where(t => t.EntryType == FileEntryType.File).ToDictionary(t => (T)t.EntryId); var foldersTags = tags.Where(t => t.EntryType == FileEntryType.Folder).ToDictionary(t => (T)t.EntryId); - var files = fileDao.GetFilesAsync(filesTags.Keys); - var folders = folderDao.GetFoldersAsync(foldersTags.Keys); + var files = await fileDao.GetFilesAsync(filesTags.Keys).ToListAsync(); + var folders = await folderDao.GetFoldersAsync(foldersTags.Keys).ToListAsync(); - await foreach (var file in files) + await entryStatusManager.SetFormInfoAsync(files); + + foreach (var file in files) { if (filesTags.TryGetValue(file.Id, out var tag)) { @@ -983,7 +986,7 @@ private async Task, Tag>> GetEntryTagsAsync(IEnumerab } } - await foreach (var folder in folders) + foreach (var folder in folders) { if (foldersTags.TryGetValue(folder.Id, out var tag)) {