diff --git a/AsbCloudApp/Services/IBackgroundWorkerService.cs b/AsbCloudApp/Services/IBackgroundWorkerService.cs new file mode 100644 index 00000000..d1831355 --- /dev/null +++ b/AsbCloudApp/Services/IBackgroundWorkerService.cs @@ -0,0 +1,23 @@ +using AsbCloudApp.Data; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudApp.Services +{ + public interface IBackgroundWorkerService + { + bool Contains(string id); + + /// + /// Добавляет в очередь задач новую задачу + /// + /// id задачи в очереди + /// делегат + /// id задачи в очереди + string Enqueue(string id, Func func); + string Enqueue(Func func); + string Enqueue(string id, Func func, Func onError); + bool TryRemove(string id); + } +} \ No newline at end of file diff --git a/AsbCloudApp/Services/IDrillingProgramService.cs b/AsbCloudApp/Services/IDrillingProgramService.cs index ec872a70..71a81d64 100644 --- a/AsbCloudApp/Services/IDrillingProgramService.cs +++ b/AsbCloudApp/Services/IDrillingProgramService.cs @@ -13,10 +13,11 @@ namespace AsbCloudApp.Services CancellationToken token = default); Task AddOrReplaceFileMarkAsync(FileMarkDto fileMarkDto, int idUser, CancellationToken token); Task MarkAsDeletedFileMarkAsync(int idFileMark, CancellationToken token); - Task AddPartAsync(int idWell, int idFileCategory, CancellationToken token = default); - Task RemovePartAsync(int idWell, int idFileCategory, CancellationToken token = default); + Task AddPartsAsync(int idWell, IEnumerable idFileCategories, CancellationToken token = default); + Task RemovePartsAsync(int idWell, IEnumerable idFileCategories, CancellationToken token = default); Task AddUserAsync(int idUser, int idPart, int idUserRole, CancellationToken token = default); Task RemoveUserAsync(int idUser, int idPart, int idUserRole, CancellationToken token = default); Task AddFile(int idPart, int idUser, string fileFullName, Stream fileStream, CancellationToken token = default); + Task> GetAvailableUsers(int idWell, CancellationToken token = default); } } \ No newline at end of file diff --git a/AsbCloudApp/Services/IReportService.cs b/AsbCloudApp/Services/IReportService.cs index b900e877..0261e986 100644 --- a/AsbCloudApp/Services/IReportService.cs +++ b/AsbCloudApp/Services/IReportService.cs @@ -9,9 +9,9 @@ namespace AsbCloudApp.Services public interface IReportService { int ReportCategoryId { get; } - int CreateReport(int idWell, int idUser, int stepSeconds, + string CreateReport(int idWell, int idUser, int stepSeconds, int format, DateTime begin, DateTime end, - Action handleReportProgress); + Action handleReportProgress); int GetReportPagesCount(int idWell, DateTime begin, DateTime end, int stepSeconds, int format); DatesRangeDto GetDatesRangeOrDefault(int idWell); diff --git a/AsbCloudDb/Model/IAsbCloudDbContext.cs b/AsbCloudDb/Model/IAsbCloudDbContext.cs index 65c2ba07..6d6916af 100644 --- a/AsbCloudDb/Model/IAsbCloudDbContext.cs +++ b/AsbCloudDb/Model/IAsbCloudDbContext.cs @@ -41,6 +41,7 @@ namespace AsbCloudDb.Model DatabaseFacade Database { get; } DbSet DrillingProgramParts { get; set; } DbSet RelationDrillingProgramPartUsers { get; set; } + DbSet RelationCompaniesWells { get; set; } int SaveChanges(); int SaveChanges(bool acceptAllChangesOnSuccess); diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 4bf3af2b..ec09d446 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -51,13 +51,12 @@ namespace AsbCloudInfrastructure services.AddScoped(provider => provider.GetService()); services.AddScoped(); - services.AddHostedService(); - services.AddHostedService(); + services.AddHostedService();// replace by BackgroundWorkerService services.AddSingleton(new CacheDb()); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddTransient(); services.AddTransient(); diff --git a/AsbCloudInfrastructure/Services/Analysis/TelemetryAnalyticsBackgroundService.cs b/AsbCloudInfrastructure/Services/Analysis/TelemetryAnalyticsBackgroundService.cs index bb4452fe..6fc77638 100644 --- a/AsbCloudInfrastructure/Services/Analysis/TelemetryAnalyticsBackgroundService.cs +++ b/AsbCloudInfrastructure/Services/Analysis/TelemetryAnalyticsBackgroundService.cs @@ -45,8 +45,7 @@ namespace AsbCloudInfrastructure.Services.Analysis var analyticsService = new TelemetryAnalyticsService(context, telemetryService, cacheDb); - await analyticsService.AnalyzeAndSaveTelemetriesAsync(token).ConfigureAwait(false); - context.ChangeTracker.Clear(); + await analyticsService.AnalyzeAndSaveTelemetriesAsync(token).ConfigureAwait(false); context.Dispose(); } catch (Exception ex) diff --git a/AsbCloudInfrastructure/Services/BackgroundWorkerService.cs b/AsbCloudInfrastructure/Services/BackgroundWorkerService.cs new file mode 100644 index 00000000..30aee4b2 --- /dev/null +++ b/AsbCloudInfrastructure/Services/BackgroundWorkerService.cs @@ -0,0 +1,193 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Services; +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services +{ + /// + /// Сервис выстраивает очередь из фоновых задач. Ограничивает количество одновременно выполняющихся задач. + /// + public class BackgroundWorkerService : IDisposable, IBackgroundWorkerService + { + private readonly Worker[] workers; + private readonly Dictionary works = new Dictionary(); + private bool isRunning = false; + private CancellationTokenSource cts; + private Task task; + + public BackgroundWorkerService(IConfiguration configuration) + { + var workersCount = configuration.GetValue("BackgroundWorkersCount", 4); + workers = new Worker[workersCount]; + for (int i = 0; i < workers.Length; i++) + workers[i] = new Worker(); + } + + ~BackgroundWorkerService() + { + Dispose(); + } + + public string Enqueue(Func func) + { + var work = new Work + { + ActionAsync = func + }; + return Enqueue(work); + } + + public string Enqueue(string id, Func func) + { + var work = new Work(id, func); + return Enqueue(work); + } + + public string Enqueue(string id, Func func, Func onError) + { + var work = new Work(id, func) + { + OnErrorAsync = onError + }; + return Enqueue(work); + } + + string Enqueue(Work work) + { + works[work.Id] = work; + if (!isRunning) + { + isRunning = true; + cts = new CancellationTokenSource(); + task = Task.Run(() => ExecuteAsync(cts.Token), cts.Token); + } + return work.Id; + } + + private Work Dequeue() + { + var item = works.First(); + works.Remove(item.Key); + return item.Value; + } + + public bool TryRemove(string id) + => works.Remove(id); + + public bool Contains(string id) + => works.ContainsKey(id); + + protected async Task ExecuteAsync(CancellationToken token) + { + while (works.Any() && !token.IsCancellationRequested) + { + var freeworker = workers.FirstOrDefault(w => !w.IsBusy); + if (freeworker is not null) + { + var work = Dequeue(); + freeworker.Start(work); + } + else + await Task.Delay(10, token).ConfigureAwait(false); + } + isRunning = false; + } + + public void Dispose() + { + cts?.Cancel(); + task?.Wait(1); + task?.Dispose(); + cts?.Dispose(); + task = null; + cts = null; + GC.SuppressFinalize(this); + } + } + + class Worker : IDisposable + { + private CancellationTokenSource cts; + private Task task; + public bool IsBusy { get; private set; } + + ~Worker() + { + Dispose(); + } + + public void Dispose() + { + Stop(); + GC.SuppressFinalize(this); + } + + public void Start(Work work) + { + IsBusy = true; + cts = new CancellationTokenSource(); + task = Task.Run(async () => + { + try + { + await work.ActionAsync(work.Id, cts.Token).ConfigureAwait(false); + } + catch (Exception ex) + { + Trace.TraceError(ex.Message); + + if (work.OnErrorAsync is not null) + { + try + { + await work.OnErrorAsync(work.Id, ex, cts.Token).ConfigureAwait(false); + } + catch (Exception exOnErrorHandler) + { + Trace.TraceError(exOnErrorHandler.Message); + } + } + } + finally + { + cts?.Dispose(); + cts = null; + IsBusy = false; + } + }, cts.Token); + } + + public void Stop() + { + cts?.Cancel(); + task?.Wait(1); + task = null; + cts?.Dispose(); + cts = null; + IsBusy = false; + } + } + class Work + { + public string Id { get; private set; } + public Func ActionAsync { get; set; } + public Func OnErrorAsync { get; set; } + + public Work() + { + Id = Guid.NewGuid().ToString(); + } + + public Work(string id, Func actionAsync) + { + Id = id; + ActionAsync = actionAsync; + } + } +} diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramMaker.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramMaker.cs index 4b8084b5..8fa06fde 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramMaker.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramMaker.cs @@ -66,7 +66,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram // return fileInfo; //} - private static void UniteExcelFiles(IEnumerable excelFilesNames, string resultExcelPath) + public static void UniteExcelFiles(IEnumerable excelFilesNames, string resultExcelPath) { var resultExcelFile = new XLWorkbook(XLEventTracking.Disabled); diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs index 36d8ed07..b1c6f2fb 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs @@ -4,8 +4,10 @@ using AsbCloudApp.Services; using AsbCloudDb.Model; using Mapster; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -17,6 +19,9 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private readonly IAsbCloudDbContext context; private readonly IFileService fileService; private readonly IUserService userService; + private readonly IWellService wellService; + private readonly IBackgroundWorkerService backgroundWorker; + private readonly string connectionString; private const int idFileCategoryDrillingProgram = 1000; private const int idFileCategoryDrillingProgramPartsStart = 1001; @@ -37,11 +42,33 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private const int idStateCreating = 2; private const int idStateReady = 3; - public DrillingProgramService(IAsbCloudDbContext context, IFileService fileService, IUserService userService) + public DrillingProgramService( + IAsbCloudDbContext context, + IFileService fileService, + IUserService userService, + IWellService wellService, + IConfiguration configuration, + IBackgroundWorkerService backgroundWorker) { this.context = context; this.fileService = fileService; this.userService = userService; + this.wellService = wellService; + this.backgroundWorker = backgroundWorker; + this.connectionString = configuration.GetConnectionString("DefaultConnection"); + } + + public async Task> GetAvailableUsers(int idWell, CancellationToken token = default) + { + var users = await context.RelationCompaniesWells + .Include(r => r.Company) + .ThenInclude(c => c.Users) + .Where(r => r.IdWell == idWell) + .SelectMany(r => r.Company.Users) + .Where(u => u != null && !string.IsNullOrEmpty(u.Email)) + .ToListAsync(token); + var usersDto = users.Adapt(); + return usersDto; } public async Task> GetCategoriesAsync(CancellationToken token = default) @@ -54,15 +81,6 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram public async Task GetStateAsync(int idWell, int idUser, CancellationToken token = default) { - // Задание от геологов - // Профиль ствола скважины(ННБ) - // Технологические расчеты(ННБ) - // Долотная программа - // Программа по растворам - // Программа геофизических исследований - // Планы спусков обсадных колонн - // Программы цементирования обсадных колонн - var fileCategories = await context.FileCategories .Where(c => c.Id >= idFileCategoryDrillingProgramPartsStart && c.Id < idFileCategoryDrillingProgramPartsEnd) @@ -94,10 +112,12 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram parts.Add(part); } - var state = new DrillingProgramStateDto(); - state.Parts = parts; - state.Program = files.FirstOrDefault(f => f.IdCategory == idFileCategoryDrillingProgram) - .Adapt(); + var state = new DrillingProgramStateDto + { + Parts = parts, + Program = files.FirstOrDefault(f => f.IdCategory == idFileCategoryDrillingProgram) + .Adapt() + }; if (parts.Any()) { @@ -105,8 +125,8 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram { if (state.Program is not null) state.IdState = idStateReady; - else - state.IdState = idStateCreating; + else + state.IdState = idStateCreating; } else state.IdState = idStateApproving; @@ -114,6 +134,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram else state.IdState = idStateNotInitialized; + await TryEnqueueMakeProgramAsync(idWell, state, token); return state; } @@ -121,7 +142,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram { var part = await context.DrillingProgramParts .Include(p => p.RelatedUsers) - .FirstOrDefaultAsync(p => p.Id == idPart); + .FirstOrDefaultAsync(p => p.Id == idPart, token); if (part == null) throw new ArgumentInvalidException($"DrillingProgramPart id == {idPart} does not exist", nameof(idPart)); @@ -141,25 +162,35 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram return result.Id; } - public async Task AddPartAsync(int idWell, int idFileCategory, CancellationToken token = default) + public async Task AddPartsAsync(int idWell, IEnumerable idFileCategories, CancellationToken token = default) { - var part = new DrillingProgramPart + if (!idFileCategories.Any()) + return 0; + + var existingCategories = await context.DrillingProgramParts + .Where(p => p.IdWell == idWell) + .Select(p => p.IdFileCategory) + .ToListAsync(token); + + var newParts = idFileCategories + .Where(c => !existingCategories.Any(e => e == c)) + .Select(c => new DrillingProgramPart { IdWell = idWell, - IdFileCategory = idFileCategory, - }; - var entry = context.DrillingProgramParts.Add(part); - await context.SaveChangesAsync(token); + IdFileCategory = c, + }); - await RemoveDrillingProgramAsync(part.IdWell, token); - return entry.Entity.Id; + context.DrillingProgramParts.AddRange(newParts); + var affected = await context.SaveChangesAsync(token); + + await RemoveDrillingProgramAsync(idWell, token); + return affected; } - public async Task RemovePartAsync(int idWell, int idFileCategory, CancellationToken token = default) + public async Task RemovePartsAsync(int idWell, IEnumerable idFileCategories, CancellationToken token = default) { var whereQuery = context.DrillingProgramParts - .Where(r => r.IdWell == idWell && - r.IdFileCategory == idFileCategory); + .Where(p => p.IdWell == idWell && idFileCategories.Contains(p.IdFileCategory)); context.DrillingProgramParts.RemoveRange(whereQuery); @@ -178,7 +209,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram throw new ArgumentInvalidException($"DrillingProgramPart id == {idPart} does not exist", nameof(idPart)); if (idUserRole != idUserRoleApprover && idUserRole != idUserRolePublisher) - throw new ArgumentInvalidException($"idUserRole ({idPart}), should be approver ({idUserRoleApprover}) or publisher({idUserRolePublisher})", nameof(idPart)); + throw new ArgumentInvalidException($"idUserRole ({idPart}), should be approver ({idUserRoleApprover}) or publisher ({idUserRolePublisher})", nameof(idPart)); var newRelation = new RelationUserDrillingProgramPart { @@ -204,7 +235,6 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram if (idUserRole == idUserRoleApprover) { var part = await context.DrillingProgramParts.FirstOrDefaultAsync(p => p.Id == idPart, token); - await CheckAndEnqueueMakeProgramAsync(part.IdWell, token); } return await context.SaveChangesAsync(token); @@ -227,6 +257,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram throw new ArgumentInvalidException($"Этот метод допустим только для файлов-частей программы бурения.", nameof(fileMarkDto)); var part = await context.DrillingProgramParts + .Include(p => p.RelatedUsers) .FirstOrDefaultAsync(p => p.IdWell == fileInfo.IdWell && p.IdFileCategory == fileInfo.IdCategory, token); if (!part.RelatedUsers.Any(r => r.IdUser == idUser && r.IdUserRole == idUserRoleApprover)) @@ -242,9 +273,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram var result = await fileService.CreateFileMarkAsync(fileMarkDto, idUser, token) .ConfigureAwait(false); - if(fileMarkDto.IdMarkType == idMarkTypeApprove) - await CheckAndEnqueueMakeProgramAsync(fileInfo.IdWell, token); - else + if(fileMarkDto.IdMarkType == idMarkTypeReject) await RemoveDrillingProgramAsync(fileInfo.IdWell, token); return result; @@ -267,22 +296,22 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram return result; } - private static DrillingProgramPartDto ConvertPart(int idUser, List fileCategories, List files, DrillingProgramPart partEntity) + private static DrillingProgramPartDto ConvertPart(int idUser, List fileCategories, List files, DrillingProgramPart partEntity) { var part = new DrillingProgramPartDto { IdFileCategory = partEntity.IdFileCategory, Name = fileCategories.FirstOrDefault(c => c.Id == partEntity.IdFileCategory).Name, Approvers = partEntity.RelatedUsers - .Where(u => u.IdUserRole == idUserRoleApprover) - .Select(u => u.Adapt()), + .Where(r => r.IdUserRole == idUserRoleApprover) + .Select(r => r.User.Adapt()), Publishers = partEntity.RelatedUsers - .Where(u => u.IdUserRole == idUserRolePublisher) - .Select(u => u.Adapt()), + .Where(r => r.IdUserRole == idUserRolePublisher) + .Select(r => r.User.Adapt()), PermissionToApprove = partEntity.RelatedUsers - .Any(u => u.IdUserRole == idUserRoleApprover && u.IdUser == idUser), + .Any(r => r.IdUserRole == idUserRoleApprover && r.IdUser == idUser), PermissionToUpload = partEntity.RelatedUsers - .Any(u => u.IdUserRole == idUserRolePublisher && u.IdUser == idUser), + .Any(r => r.IdUserRole == idUserRolePublisher && r.IdUser == idUser), }; var fileEntity = files.LastOrDefault(f => f.IdCategory == partEntity.IdFileCategory); @@ -290,20 +319,15 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram if (fileEntity is not null) { part.File = fileEntity.Adapt(); - var marks = fileEntity.FileMarks.Where(m => !m.IsDeleted); - - if (marks.Any()) + part.IdState = idPartStateApproving; + var marks = fileEntity.FileMarks.Where(m => !m.IsDeleted); + var hasReject = marks.Any(m => m.IdMarkType == idMarkTypeReject); + if (!hasReject) { - var hasReject = marks.Any(m => m.IdMarkType == idMarkTypeReject); - if (!hasReject) - { - var allAproved = part.Approvers.All(a => marks.Any(m => m.IdUser == a.Id && m.IdMarkType == idMarkTypeApprove)); - if (allAproved) - part.IdState = idPartStateApproved; - } + var allAproved = part.Approvers.All(a => marks.Any(m => m.IdUser == a.Id && m.IdMarkType == idMarkTypeApprove)); + if (allAproved) + part.IdState = idPartStateApproved; } - else - part.IdState = idPartStateApproving; } else part.IdState = idPartStateNoFile; @@ -311,19 +335,38 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram return part; } - private async Task CheckAndEnqueueMakeProgramAsync(int idWell, CancellationToken token) + private async Task TryEnqueueMakeProgramAsync(int idWell, DrillingProgramStateDto state, CancellationToken token) { - var state = await GetStateAsync(idWell, 0, token); - if(state.IdState == idStateCreating) + if (state.IdState == idStateCreating) { - // TODO: check if task is running else enqueue task - throw new NotImplementedException(); + var workId = MakeWorkId(idWell); + if (!backgroundWorker.Contains(workId)) + { + var well = await wellService.GetAsync(idWell, token); + var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx"; + var tempResultFilePath = Path.Combine(Path.GetTempPath(), "drillingProgram", resultFileName); + async Task funcProgramMake(string id, CancellationToken token) + { + var contextOptions = new DbContextOptionsBuilder() + .UseNpgsql(connectionString) + .Options; + using var context = new AsbCloudDbContext(contextOptions); + var fileService = new FileService(context); + var files = state.Parts.Select(p => fileService.GetUrl(p.File)); + DrillingProgramMaker.UniteExcelFiles(files, tempResultFilePath); + await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, tempResultFilePath, token); + } + + backgroundWorker.Enqueue(funcProgramMake); + } } } private async Task RemoveDrillingProgramAsync(int idWell, CancellationToken token) { - // TODO: dequeue task if it exist + var workId = MakeWorkId(idWell); + backgroundWorker.TryRemove(workId); + var filesIds = await context.Files .Where(f => f.IdWell == idWell && f.IdCategory == idFileCategoryDrillingProgram) @@ -335,5 +378,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram return 0; } + private static string MakeWorkId(int idWell) + => $"Make drilling program for wellId {idWell}"; } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/FileService.cs b/AsbCloudInfrastructure/Services/FileService.cs index 78d413eb..daacee7f 100644 --- a/AsbCloudInfrastructure/Services/FileService.cs +++ b/AsbCloudInfrastructure/Services/FileService.cs @@ -337,7 +337,7 @@ namespace AsbCloudInfrastructure.Services newFileMark.Id = default; newFileMark.DateCreated = DateTime.UtcNow; newFileMark.IdUser = idUser; - + db.FileMarks.Add(newFileMark); return await db.SaveChangesAsync(token); } diff --git a/AsbCloudInfrastructure/Services/ReportService.cs b/AsbCloudInfrastructure/Services/ReportService.cs index 20986967..8f384c39 100644 --- a/AsbCloudInfrastructure/Services/ReportService.cs +++ b/AsbCloudInfrastructure/Services/ReportService.cs @@ -19,18 +19,17 @@ namespace AsbCloudInfrastructure.Services private readonly IAsbCloudDbContext db; private readonly string connectionString; private readonly ITelemetryService telemetryService; - private readonly IReportsBackgroundQueue queue; private readonly IWellService wellService; + private readonly IBackgroundWorkerService backgroundWorkerService; public ReportService(IAsbCloudDbContext db, IConfiguration configuration, - ITelemetryService telemetryService, - IReportsBackgroundQueue queue, IWellService wellService) + ITelemetryService telemetryService, IWellService wellService, IBackgroundWorkerService backgroundWorkerService) { this.db = db; this.connectionString = configuration.GetConnectionString("DefaultConnection"); this.wellService = wellService; + this.backgroundWorkerService = backgroundWorkerService; this.telemetryService = telemetryService; - this.queue = queue; ReportCategoryId = db.FileCategories.AsNoTracking() .FirstOrDefault(c => c.Name.Equals("Рапорт")).Id; @@ -38,8 +37,8 @@ namespace AsbCloudInfrastructure.Services public int ReportCategoryId { get; private set; } - public int CreateReport(int idWell, int idUser, int stepSeconds, int format, DateTime begin, - DateTime end, Action progressHandler) + public string CreateReport(int idWell, int idUser, int stepSeconds, int format, DateTime begin, + DateTime end, Action progressHandler) { var timezoneOffset = wellService.GetTimezone(idWell).Hours; var beginUtc = begin.ToUtcDateTimeOffset(timezoneOffset); @@ -47,13 +46,14 @@ namespace AsbCloudInfrastructure.Services var beginRemote = begin.ToTimeZoneOffsetHours(timezoneOffset); var endRemote = end.ToTimeZoneOffsetHours(timezoneOffset); - var newReportId = queue.EnqueueTask((id) => + var newReportId = backgroundWorkerService.Enqueue(async (id, token) => { - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseNpgsql(connectionString); - var tempDir = Path.Combine(Path.GetTempPath(), "report"); + var contextOptions = new DbContextOptionsBuilder() + .UseNpgsql(connectionString) + .Options; + using var context = new AsbCloudDbContext(contextOptions); - using var context = new AsbCloudDbContext(optionsBuilder.Options); + var tempDir = Path.Combine(Path.GetTempPath(), "report"); var generator = GetReportGenerator(idWell, beginRemote, endRemote, stepSeconds, format, context); var reportFileName = Path.Combine(tempDir, generator.GetReportDefaultFileName()); @@ -66,7 +66,7 @@ namespace AsbCloudInfrastructure.Services generator.Make(reportFileName); var fileService = new FileService(context); - var fileInfo = fileService.MoveAsync(idWell, idUser, ReportCategoryId, reportFileName, reportFileName).Result; + var fileInfo = await fileService.MoveAsync(idWell, idUser, ReportCategoryId, reportFileName, reportFileName, token); progressHandler.Invoke(new { diff --git a/AsbCloudInfrastructure/Services/ReportsBackgroundQueue.cs b/AsbCloudInfrastructure/Services/ReportsBackgroundQueue.cs deleted file mode 100644 index 132ff352..00000000 --- a/AsbCloudInfrastructure/Services/ReportsBackgroundQueue.cs +++ /dev/null @@ -1,28 +0,0 @@ -using AsbCloudApp.Services; -using System; -using System.Collections.Concurrent; - -namespace AsbCloudInfrastructure.Services -{ - class ReportsBackgroundQueue : IReportsBackgroundQueue - { - private readonly ConcurrentQueue<(Action action, int id)> tasks = - new ConcurrentQueue<(Action action, int id)>(); - - private int id = 0; - - public int EnqueueTask(Action action) - { - if (action == null) - throw new ArgumentNullException(nameof(action)); - - id++; - - tasks.Enqueue(new(action, id)); - return id; - } - - public bool TryDequeue(out (Action action, int id) item) - => tasks.TryDequeue(out item); - } -} diff --git a/AsbCloudInfrastructure/Services/ReportsBackgroundService.cs b/AsbCloudInfrastructure/Services/ReportsBackgroundService.cs deleted file mode 100644 index 705ee426..00000000 --- a/AsbCloudInfrastructure/Services/ReportsBackgroundService.cs +++ /dev/null @@ -1,43 +0,0 @@ -using AsbCloudApp.Services; -using Microsoft.Extensions.Hosting; -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; - -namespace AsbCloudInfrastructure.Services -{ - public class ReportsBackgroundService : BackgroundService - { - private readonly IReportsBackgroundQueue tasksQueue; - - public ReportsBackgroundService(IReportsBackgroundQueue tasksQueue) - { - this.tasksQueue = tasksQueue; - } - - protected override async Task ExecuteAsync(CancellationToken token) - { - while (!token.IsCancellationRequested) - { - try - { - if (tasksQueue.TryDequeue(out var item)) - await Task.Run(() => item.action(item.id), token).ConfigureAwait(false); - else - await Task.Delay(100, token).ConfigureAwait(false); - } - catch(Exception ex) - { - Trace.TraceError(ex.Message); - Console.WriteLine(ex.Message); - } - } - } - - public override async Task StopAsync(CancellationToken token) - { - await base.StopAsync(token).ConfigureAwait(false); - } - } -} diff --git a/AsbCloudInfrastructure/Services/TelemetryTracker.cs b/AsbCloudInfrastructure/Services/TelemetryTracker.cs index c05f2995..f861b8c6 100644 --- a/AsbCloudInfrastructure/Services/TelemetryTracker.cs +++ b/AsbCloudInfrastructure/Services/TelemetryTracker.cs @@ -41,10 +41,10 @@ namespace AsbCloudInfrastructure.Services public TelemetryTracker(CacheDb cacheDb, IConfiguration configuration) { - var options = new DbContextOptionsBuilder() + var contextOptions = new DbContextOptionsBuilder() .UseNpgsql(configuration.GetConnectionString("DefaultConnection")) .Options; - var db = new AsbCloudDbContext(options); + var db = new AsbCloudDbContext(contextOptions); var cacheTelemetry = cacheDb.GetCachedTable(db); var keyValuePairs = new Dictionary(cacheTelemetry.Count()); diff --git a/AsbCloudWebApi/Controllers/DrillingProgramController.cs b/AsbCloudWebApi/Controllers/DrillingProgramController.cs index 4b68db54..0bc72bc7 100644 --- a/AsbCloudWebApi/Controllers/DrillingProgramController.cs +++ b/AsbCloudWebApi/Controllers/DrillingProgramController.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Threading; @@ -18,14 +20,11 @@ namespace AsbCloudWebApi.Controllers public class DrillingProgramController : ControllerBase { private readonly IDrillingProgramService drillingProgramService; - private readonly IFileService fileService; private readonly IWellService wellService; - public DrillingProgramController(IDrillingProgramService drillingProgramService, - IFileService fileService, IWellService wellService) + public DrillingProgramController(IDrillingProgramService drillingProgramService, IWellService wellService) { this.drillingProgramService = drillingProgramService; - this.fileService = fileService; this.wellService = wellService; } @@ -41,7 +40,21 @@ namespace AsbCloudWebApi.Controllers var result = await drillingProgramService.GetCategoriesAsync(token); return Ok(result); } - + + /// + /// Список пользователей + /// + /// id скважины + /// + /// + [HttpGet("availableUsers")] + [ProducesResponseType(typeof(FileCategoryDto[]), (int)System.Net.HttpStatusCode.OK)] + public async Task GetAvailableUsers(int idWell, CancellationToken token) + { + var result = await drillingProgramService.GetAvailableUsers(idWell, token); + return Ok(result); + } + /// /// Состояние процесса согласования программы бурения /// @@ -67,16 +80,16 @@ namespace AsbCloudWebApi.Controllers } /// - /// Загрузка файла программы бурения + /// Загрузка файла для части программы бурения /// /// ID части программы. Не путать с категорией файла /// /// /// /// - [HttpPost("{idPart}")] + [HttpPost("part/{idPart}")] [Permission] - [ProducesResponseType(typeof(Task), (int)System.Net.HttpStatusCode.OK)] + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] public async Task AddFile( int idPart, int idWell, @@ -100,6 +113,10 @@ namespace AsbCloudWebApi.Controllers return BadRequest(ArgumentInvalidException.MakeValidationError(nameof(files), "at list 1 file can be uploaded")); var fileName = files[0].FileName; + + if (!fileName.EndsWith(".xlsx")) + return BadRequest(ArgumentInvalidException.MakeValidationError("file", "Файл должен быть xlsx")); + var fileStream = files[0].OpenReadStream(); var result = await drillingProgramService.AddFile(idPart, (int)idUser, fileName, fileStream, token); @@ -107,16 +124,16 @@ namespace AsbCloudWebApi.Controllers } /// - /// Добавить раздел программы бурения + /// Добавляет разделы программы бурения /// /// - /// + /// Список категорий файлов, по которым будут добавлены части ПБ /// /// [HttpPost("part")] [Permission] - [ProducesResponseType(typeof(Task), (int)System.Net.HttpStatusCode.OK)] - public async Task AddPartAsync(int idWell, int idFileCategory, CancellationToken token) + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] + public async Task AddPartsAsync(int idWell, [Required] IEnumerable idFileCategories, CancellationToken token) { int? idCompany = User.GetCompanyId(); int? idUser = User.GetUserId(); @@ -128,21 +145,21 @@ namespace AsbCloudWebApi.Controllers idWell, token).ConfigureAwait(false)) return Forbid(); - var result = drillingProgramService.AddPartAsync(idWell, idFileCategory, token); + var result = await drillingProgramService.AddPartsAsync(idWell, idFileCategories, token); return Ok(result); } /// - /// Удалить раздел программы бурения + /// Удаляет разделы программы бурения /// /// - /// + /// Список категорий файлов, по которым будут удалены части ПБ /// /// - [HttpPost("part")] + [HttpDelete("part")] [Permission] - [ProducesResponseType(typeof(Task), (int)System.Net.HttpStatusCode.OK)] - public async Task RemovePartAsync(int idWell, int idFileCategory, CancellationToken token) + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] + public async Task RemovePartsAsync(int idWell, [Required] IEnumerable idFileCategories, CancellationToken token) { int? idCompany = User.GetCompanyId(); int? idUser = User.GetUserId(); @@ -154,14 +171,23 @@ namespace AsbCloudWebApi.Controllers idWell, token).ConfigureAwait(false)) return Forbid(); - var result = drillingProgramService.RemovePartAsync(idWell, idFileCategory, token); + var result = await drillingProgramService.RemovePartsAsync(idWell, idFileCategories, token); return Ok(result); } - [HttpPost("part/{idPart}/user/{idUser}")] + /// + /// Добавить пользователя в список publishers или approvers части программы бурения + /// + /// + /// + /// + /// + /// + /// + [HttpPost("part/{idPart}/user")] [Permission] - [ProducesResponseType(typeof(Task), (int)System.Net.HttpStatusCode.OK)] - public async Task AddUserAsync(int idWell, int idUser, int idPart, int idUserRole, CancellationToken token = default) + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] + public async Task AddUserAsync(int idWell, [Required] int idUser, int idPart, [Required] int idUserRole, CancellationToken token = default) { int? idCompany = User.GetCompanyId(); int? idUserEditor = User.GetUserId(); @@ -173,14 +199,23 @@ namespace AsbCloudWebApi.Controllers idWell, token).ConfigureAwait(false)) return Forbid(); - var result = drillingProgramService.AddUserAsync(idUser, idPart, idUserRole, token); + var result = await drillingProgramService.AddUserAsync(idUser, idPart, idUserRole, token); return Ok(result); } + /// + /// Удалить пользователя из списка publishers или approvers части программы бурения + /// + /// + /// + /// + /// + /// + /// [HttpDelete("part/{idPart}/user/{idUser}")] [Permission] - [ProducesResponseType(typeof(Task), (int)System.Net.HttpStatusCode.OK)] - public async Task RemoveUserAsync(int idWell, int idUser, int idPart, int idUserRole, CancellationToken token = default) + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] + public async Task RemoveUserAsync(int idWell, int idUser, int idPart, [Required]int idUserRole, CancellationToken token = default) { int? idCompany = User.GetCompanyId(); int? idUserEditor = User.GetUserId(); @@ -192,7 +227,7 @@ namespace AsbCloudWebApi.Controllers idWell, token).ConfigureAwait(false)) return Forbid(); - var result = drillingProgramService.RemoveUserAsync(idUser, idPart, idUserRole, token); + var result = await drillingProgramService.RemoveUserAsync(idUser, idPart, idUserRole, token); return Ok(result); } @@ -229,7 +264,7 @@ namespace AsbCloudWebApi.Controllers /// id метки /// Токен отмены задачи /// - [HttpDelete("fileMark")] + [HttpDelete("fileMark/{idMark}")] [Permission] [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] public async Task MarkAsDeletedFileMarkAsync(int idWell, int idMark, diff --git a/AsbCloudWebApi/Controllers/ReportController.cs b/AsbCloudWebApi/Controllers/ReportController.cs index 63b623c5..b1838df4 100644 --- a/AsbCloudWebApi/Controllers/ReportController.cs +++ b/AsbCloudWebApi/Controllers/ReportController.cs @@ -58,7 +58,7 @@ namespace AsbCloudWebApi.Controllers idWell, token).ConfigureAwait(false)) return Forbid(); - void HandleReportProgressAsync(object progress, int id) => + void HandleReportProgressAsync(object progress, string id) => Task.Run(() => { reportsHubContext.Clients.Group($"Report_{id}").SendAsync(