diff --git a/AsbCloudApp/Repositories/IFileRepository.cs b/AsbCloudApp/Repositories/IFileRepository.cs index e36d1001..488630ae 100644 --- a/AsbCloudApp/Repositories/IFileRepository.cs +++ b/AsbCloudApp/Repositories/IFileRepository.cs @@ -1,4 +1,5 @@ using AsbCloudApp.Data; +using AsbCloudApp.Requests; using AsbCloudApp.Services; using System; using System.Collections.Generic; @@ -7,6 +8,7 @@ using System.Threading.Tasks; namespace AsbCloudApp.Repositories { +#nullable enable /// /// Сервис доступа к файлам /// @@ -24,19 +26,10 @@ namespace AsbCloudApp.Repositories /// /// Получить список файлов в контейнере /// - /// - /// - /// - /// - /// - /// - /// - /// + /// /// /// - Task> GetInfosAsync(int idWell, - int idCategory, string companyName = default, string fileName = default, DateTime begin = default, - DateTime end = default, int skip = 0, int take = 32, CancellationToken token = default); + Task> GetInfosAsync(FileServiceRequest request, CancellationToken token = default); /// /// Пометить файл как удаленный @@ -90,9 +83,10 @@ namespace AsbCloudApp.Repositories /// /// Получение файлов по скважине /// - /// + /// /// /// - Task> GetInfosByWellIdAsync(int idWell, CancellationToken token); + Task> GetInfosByWellIdAsync(FileServiceRequest request, CancellationToken token); } +#nullable disable } diff --git a/AsbCloudApp/Repositories/IFileStorageRepository.cs b/AsbCloudApp/Repositories/IFileStorageRepository.cs index 9d821861..72c11d21 100644 --- a/AsbCloudApp/Repositories/IFileStorageRepository.cs +++ b/AsbCloudApp/Repositories/IFileStorageRepository.cs @@ -1,9 +1,12 @@ -using System.IO; +using AsbCloudApp.Data; +using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; namespace AsbCloudApp.Repositories { +#nullable enable /// /// Репозиторий хранения фалов /// @@ -47,5 +50,31 @@ namespace AsbCloudApp.Repositories /// /// bool FileExists(string fullPath, string fileName); + + /// + /// Удаление всех файлов с диска о которых нет информации в базе + /// + /// + /// + int DeleteFilesNotExistStorage(int idWell, IEnumerable idsFiles); + + /// + /// Вывод списка всех файлов из базы, для которых нет файла на диске + /// + /// + /// + /// + IEnumerable GetListFilesNotDisc(int idWell, IEnumerable files); + + /// + /// Получение пути к файлу + /// + /// + /// + /// + /// + /// + string MakeFilePath(int idWell, int idCategory, string fileFullName, int fileId); } +#nullable disable } diff --git a/AsbCloudApp/Requests/FileServiceRequest.cs b/AsbCloudApp/Requests/FileServiceRequest.cs new file mode 100644 index 00000000..bc561bdd --- /dev/null +++ b/AsbCloudApp/Requests/FileServiceRequest.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace AsbCloudApp.Requests +{ +#nullable enable + /// + /// Параметры запроса для файлового сервиса + /// + public class FileServiceRequest : RequestBase + { + /// + /// Идентификатор скважины + /// + [Required] + public int? IdWell { get; set; } + + /// + /// Идентификатор категории файла + /// + [Required] + public int? IdCategory { get; set; } + + /// + /// Наименование компании + /// + public string? CompanyName { get; set; } + + /// + /// Имя файла + /// + public string? FileName { get; set; } + + /// + /// Дата начала периода + /// + public DateTime? Begin { get; set; } + + /// + /// Дата окончания периода + /// + public DateTime? End { get; set; } + + /// + /// Идентификатор файла + /// + public int? IdFile { get; set; } + + /// + /// Идентификатор отметки + /// + public int? IdMark { get; set; } + + /// + /// Признак удаления + /// + public bool? IsDeleted { get; set; } + } +#nullable disable +} diff --git a/AsbCloudApp/Services/FileService.cs b/AsbCloudApp/Services/FileService.cs index b27c4383..934e5dc3 100644 --- a/AsbCloudApp/Services/FileService.cs +++ b/AsbCloudApp/Services/FileService.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Data; using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; using System; using System.Collections.Generic; using System.IO; @@ -9,6 +10,7 @@ using System.Threading.Tasks; namespace AsbCloudApp.Services { +#nullable enable /// /// Сервис доступа к файлам /// @@ -56,7 +58,7 @@ namespace AsbCloudApp.Services var fileId = await fileRepository.InsertAsync(dto, token) .ConfigureAwait(false); - string filePath = MakeFilePath(idWell, idCategory, destinationFileName, fileId); + string filePath = fileStorageRepository.MakeFilePath(idWell, idCategory, destinationFileName, fileId); fileStorageRepository.MoveFile(srcFilePath, filePath); return await GetInfoAsync(fileId, token); @@ -89,7 +91,7 @@ namespace AsbCloudApp.Services .ConfigureAwait(false); //save stream to disk - string filePath = MakeFilePath(idWell, idCategory, fileFullName, fileId); + string filePath = fileStorageRepository.MakeFilePath(idWell, idCategory, fileFullName, fileId); await fileStorageRepository.CopyFileAsync(filePath, fileStream, token); return await GetInfoAsync(fileId, token); @@ -215,20 +217,11 @@ namespace AsbCloudApp.Services /// /// Получить список файлов в контейнере /// - /// - /// - /// - /// - /// - /// - /// - /// + /// /// /// - public async Task> GetInfosAsync(int idWell, - int idCategory, string companyName = default, string fileName = default, DateTime begin = default, - DateTime end = default, int skip = 0, int take = 32, CancellationToken token = default) - => await fileRepository.GetInfosAsync(idWell, idCategory, companyName, fileName, begin, end, skip, take, token) + public async Task> GetInfosAsync(FileServiceRequest request, CancellationToken token) + => await fileRepository.GetInfosAsync(request, token) .ConfigureAwait(false); /// @@ -275,7 +268,7 @@ namespace AsbCloudApp.Services /// /// получить инфо о файле по метке /// - /// + /// /// /// public async Task MarkFileMarkAsDeletedAsync(IEnumerable idsMarks, CancellationToken token) @@ -289,7 +282,7 @@ namespace AsbCloudApp.Services /// /// public async Task> GetInfosByWellIdAsync(int idWell, CancellationToken token) - => await fileRepository.GetInfosByWellIdAsync(idWell, token) + => await fileRepository.GetInfosByWellIdAsync(new FileServiceRequest { IdWell = idWell, IsDeleted = false }, token) .ConfigureAwait(false); /// @@ -303,10 +296,58 @@ namespace AsbCloudApp.Services => await fileRepository.GetInfosByCategoryAsync(idWell, idCategory, token) .ConfigureAwait(false); - private string MakeFilePath(int idWell, int idCategory, string fileFullName, int fileId) + /// + /// Удаление всех файлов по скважине помеченных как удаленные + /// + /// + /// + /// + public async Task DeleteFilesFromDbMarkedDeletionByIdWell(int idWell, CancellationToken token) { - return Path.Combine(fileStorageRepository.RootPath, $"{idWell}", - $"{idCategory}", $"{fileId}" + $"{Path.GetExtension(fileFullName)}"); + var files = await fileRepository.GetInfosByWellIdAsync( + new FileServiceRequest + { + IdWell = idWell, + IsDeleted = true + }, + token); + var result = await DeleteAsync(files.Select(x => x.Id), token); + return result; + } + + /// + /// Удаление всех файлов с диска о которых нет информации в базе + /// + /// + /// + public async Task DeleteFilesNotExistStorage(int idWell, CancellationToken token) + { + var files = await fileRepository.GetInfosByWellIdAsync( + new FileServiceRequest + { + IdWell = idWell + }, + token); + var result = await Task.FromResult(fileStorageRepository.DeleteFilesNotExistStorage(idWell, files.Select(x => x.Id))); + return result; + } + + /// + /// Вывод списка всех файлов из базы, для которых нет файла на диске + /// + /// + /// + public async Task> GetListFilesNotDisc(int idWell, CancellationToken token) + { + var files = await fileRepository.GetInfosByWellIdAsync( + new FileServiceRequest + { + IdWell = idWell + }, + token); + var result = fileStorageRepository.GetListFilesNotDisc(idWell, files); + return result; } } +#nullable disable } diff --git a/AsbCloudInfrastructure/Repository/FileRepository.cs b/AsbCloudInfrastructure/Repository/FileRepository.cs index 0cca5b8b..16367b09 100644 --- a/AsbCloudInfrastructure/Repository/FileRepository.cs +++ b/AsbCloudInfrastructure/Repository/FileRepository.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Data; using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; using AsbCloudDb.Model; using Mapster; using Microsoft.EntityFrameworkCore; @@ -12,6 +13,7 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository { +#nullable enable public class FileRepository : IFileRepository { private readonly IQueryable dbSetConfigured; @@ -41,22 +43,23 @@ namespace AsbCloudInfrastructure.Repository return dtos; } - public async Task> GetInfosAsync(int idWell, - int idCategory, string companyName = default, string fileName = default, DateTime begin = default, - DateTime end = default, int skip = 0, int take = 32, CancellationToken token = default) + public async Task> GetInfosAsync(FileServiceRequest request, CancellationToken token = default) { var query = dbSetConfigured - .Where(e => e.IdWell == idWell && - e.IdCategory == idCategory && + .Where(e => e.IdWell == request.IdWell && + e.IdCategory == request.IdCategory && !e.IsDeleted); - if (!string.IsNullOrEmpty(companyName)) + if (request.CompanyName is not null) query = query.Where(e => (e.Author == null) || (e.Author.Company == null) || - e.Author.Company.Caption.Contains(companyName)); + e.Author.Company.Caption.Contains(request.CompanyName)); - if (!string.IsNullOrEmpty(fileName)) - query = query.Where(e => e.Name.ToLower().Contains(fileName.ToLower())); + if (request.FileName is not null) + query = query.Where(e => e.Name.ToLower().Contains(request.FileName.ToLower())); + + var skip = request.Skip ?? 0; + var take = request.Take ?? 32; var firstFile = await query.FirstOrDefaultAsync(token); if (firstFile is null) @@ -69,15 +72,15 @@ namespace AsbCloudInfrastructure.Repository var timezoneOffset = firstFile.Well.Timezone?.Hours ?? 5; - if (begin != default) + if (request.Begin is not null) { - var beginUtc = begin.ToUtcDateTimeOffset(timezoneOffset); + var beginUtc = request.Begin.Value.ToUtcDateTimeOffset(timezoneOffset); query = query.Where(e => e.UploadDate >= beginUtc); } - if (end != default) + if (request.End is not null) { - var endUtc = end.ToUtcDateTimeOffset(timezoneOffset); + var endUtc = request.End.Value.ToUtcDateTimeOffset(timezoneOffset); query = query.Where(e => e.UploadDate <= endUtc); } @@ -201,10 +204,13 @@ namespace AsbCloudInfrastructure.Repository return await db.SaveChangesAsync(token); } - public async Task> GetInfosByWellIdAsync(int idWell, CancellationToken token) + public async Task> GetInfosByWellIdAsync(FileServiceRequest request, CancellationToken token) { - var entities = await dbSetConfigured - .Where(e => e.IdWell == idWell && e.IsDeleted == false) + var query = dbSetConfigured.Where(e => e.IdWell == request.IdWell); + if (request.IsDeleted is not null) + query = query.Where(x => x.IsDeleted == request.IsDeleted); + + var entities = await query .AsNoTracking() .ToListAsync(token) .ConfigureAwait(false); @@ -302,4 +308,5 @@ namespace AsbCloudInfrastructure.Repository throw new NotImplementedException(); } } +#nullable disable } diff --git a/AsbCloudInfrastructure/Repository/FileStorageRepository.cs b/AsbCloudInfrastructure/Repository/FileStorageRepository.cs index 7466f3a3..2b1f84f1 100644 --- a/AsbCloudInfrastructure/Repository/FileStorageRepository.cs +++ b/AsbCloudInfrastructure/Repository/FileStorageRepository.cs @@ -1,11 +1,17 @@ -using AsbCloudApp.Exceptions; +using AsbCloudApp.Data; +using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using FileInfo = System.IO.FileInfo; namespace AsbCloudInfrastructure.Repository { +#nullable enable public class FileStorageRepository : IFileStorageRepository { public string RootPath { get; private set; } @@ -51,9 +57,68 @@ namespace AsbCloudInfrastructure.Repository return true; } - private void CreateDirectory(string filePath) + public string MakeFilePath(int idWell, int idCategory, string fileFullName, int fileId) + { + return Path.Combine(RootPath, $"{idWell}", + $"{idCategory}", $"{fileId}" + $"{Path.GetExtension(fileFullName)}"); + } + + public int DeleteFilesNotExistStorage(int idWell, IEnumerable idsFiles) + { + var allFilesPath = GetFilesPath(idWell); + var resutl = 0; + + foreach (var filePath in allFilesPath) + { + var idFile = Path.GetFileNameWithoutExtension(filePath); + if (!idsFiles.Any(x => x.ToString() == idFile)) + { + File.Delete(filePath); + resutl++; + } + } + + return resutl; + } + + public IEnumerable GetListFilesNotDisc(int idWell, IEnumerable files) + { + var resutl = new List(); + var idsFilesStorage = GetIdsFiles(idWell); + + foreach (var file in files) + { + if (!idsFilesStorage.Any(x => x == file.Id)) + resutl.Add(file); + } + + return resutl; + } + + private IEnumerable GetIdsFiles(int idWell) + { + var result = new List(); + var allFilesPath = GetFilesPath(idWell); + + foreach (var filePath in allFilesPath) + { + var idFileStr = Path.GetFileNameWithoutExtension(filePath); + result.Add(Convert.ToInt32(idFileStr)); + } + + return result; + } + + private IEnumerable GetFilesPath(int idWell) + { + var path = Path.Combine(RootPath, $"{idWell}"); + return Directory.GetFiles(path, "*.*", SearchOption.AllDirectories); + } + + private static void CreateDirectory(string filePath) { Directory.CreateDirectory(Path.GetDirectoryName(filePath)); } } +#nullable disable } diff --git a/AsbCloudWebApi.Tests/ServicesTests/FileServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/FileServiceTest.cs index 025cc7bb..91b2af74 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/FileServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/FileServiceTest.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Data; using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.Repository; @@ -115,9 +116,9 @@ namespace AsbCloudWebApi.Tests.ServicesTests return Task.FromResult(result); }); - repositoryMock.Setup(x => x.GetInfosByWellIdAsync(It.IsAny(), It.IsAny())) - .Returns((int idWell, CancellationToken token) => { - var data = Files.Where(x => x.IdWell == idWell); + repositoryMock.Setup(x => x.GetInfosByWellIdAsync(It.IsAny(), It.IsAny())) + .Returns((FileServiceRequest request, CancellationToken token) => { + var data = Files.Where(x => x.IdWell == request.IdWell); return Task.FromResult(data); }); diff --git a/AsbCloudWebApi/Controllers/FileController.cs b/AsbCloudWebApi/Controllers/FileController.cs index 378b5a50..499129f2 100644 --- a/AsbCloudWebApi/Controllers/FileController.cs +++ b/AsbCloudWebApi/Controllers/FileController.cs @@ -1,4 +1,5 @@ using AsbCloudApp.Data; +using AsbCloudApp.Requests; using AsbCloudApp.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -12,6 +13,7 @@ using System.Threading.Tasks; namespace AsbCloudWebApi.Controllers { +#nullable enable /// /// Хранение файлов /// @@ -70,38 +72,27 @@ namespace AsbCloudWebApi.Controllers /// /// Возвращает информацию о файлах для скважины в выбраной категории /// - /// id скважины - /// id категории файла - /// id компаний для фильтрации возвращаемых файлов - /// часть имени файла для поиска - /// дата начала - /// дата окончания - /// для пагинации кол-во записей пропустить - /// для пагинации кол-во записей взять + /// /// Токен отмены задачи /// Список информации о файлах в этой категории [HttpGet] + [Route("/api/files")] [Permission] [ProducesResponseType(typeof(PaginationContainer), (int)System.Net.HttpStatusCode.OK)] public async Task GetFilesInfoAsync( - [FromRoute] int idWell, - int idCategory = default, - string companyName = default, - string fileName = default, - DateTime begin = default, - DateTime end = default, - int skip = 0, - int take = 32, + [FromQuery] FileServiceRequest request, CancellationToken token = default) { int? idCompany = User.GetCompanyId(); - if (idCompany is null || !await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, - idWell, token).ConfigureAwait(false)) + if (request.IdWell is null || request.IdCategory is null || idCompany is null) return Forbid(); - var filesInfo = await fileService.GetInfosAsync(idWell, idCategory, - companyName, fileName, begin, end, skip, take, token).ConfigureAwait(false); + if (!await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, + request.IdWell.Value, token).ConfigureAwait(false)) + return Forbid(); + + var filesInfo = await fileService.GetInfosAsync(request, token).ConfigureAwait(false); return Ok(filesInfo); } @@ -254,4 +245,5 @@ namespace AsbCloudWebApi.Controllers } } } +#nullable disable }