Add backgroundService.

ReportService uses backgroundService.
Drilling program alpha
This commit is contained in:
Фролов 2022-02-17 15:37:27 +05:00
parent e4e906c8d7
commit 7a77ff7904
16 changed files with 407 additions and 182 deletions

View File

@ -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);
/// <summary>
/// Добавляет в очередь задач новую задачу
/// </summary>
/// <param name="id">id задачи в очереди</param>
/// <param name="func">делегат</param>
/// <returns>id задачи в очереди</returns>
string Enqueue(string id, Func<string, CancellationToken, Task> func);
string Enqueue(Func<string, CancellationToken, Task> func);
string Enqueue(string id, Func<string, CancellationToken, Task> func, Func<string, Exception, CancellationToken, Task> onError);
bool TryRemove(string id);
}
}

View File

@ -13,10 +13,11 @@ namespace AsbCloudApp.Services
CancellationToken token = default);
Task<int> AddOrReplaceFileMarkAsync(FileMarkDto fileMarkDto, int idUser, CancellationToken token);
Task<int> MarkAsDeletedFileMarkAsync(int idFileMark, CancellationToken token);
Task<int> AddPartAsync(int idWell, int idFileCategory, CancellationToken token = default);
Task<int> RemovePartAsync(int idWell, int idFileCategory, CancellationToken token = default);
Task<int> AddPartsAsync(int idWell, IEnumerable<int> idFileCategories, CancellationToken token = default);
Task<int> RemovePartsAsync(int idWell, IEnumerable<int> idFileCategories, CancellationToken token = default);
Task<int> AddUserAsync(int idUser, int idPart, int idUserRole, CancellationToken token = default);
Task<int> RemoveUserAsync(int idUser, int idPart, int idUserRole, CancellationToken token = default);
Task<int> AddFile(int idPart, int idUser, string fileFullName, Stream fileStream, CancellationToken token = default);
Task<IEnumerable<UserDto>> GetAvailableUsers(int idWell, CancellationToken token = default);
}
}

View File

@ -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<object, int> handleReportProgress);
Action<object, string> handleReportProgress);
int GetReportPagesCount(int idWell, DateTime begin, DateTime end,
int stepSeconds, int format);
DatesRangeDto GetDatesRangeOrDefault(int idWell);

View File

@ -41,6 +41,7 @@ namespace AsbCloudDb.Model
DatabaseFacade Database { get; }
DbSet<DrillingProgramPart> DrillingProgramParts { get; set; }
DbSet<RelationUserDrillingProgramPart> RelationDrillingProgramPartUsers { get; set; }
DbSet<RelationCompanyWell> RelationCompaniesWells { get; set; }
int SaveChanges();
int SaveChanges(bool acceptAllChangesOnSuccess);

View File

@ -51,13 +51,12 @@ namespace AsbCloudInfrastructure
services.AddScoped<IAsbCloudDbContext>(provider => provider.GetService<AsbCloudDbContext>());
services.AddScoped<IFileShareService, GoogleDriveService>();
services.AddHostedService<ReportsBackgroundService>();
services.AddHostedService<TelemetryAnalyticsBackgroundService>();
services.AddHostedService<TelemetryAnalyticsBackgroundService>();// replace by BackgroundWorkerService
services.AddSingleton(new CacheDb());
services.AddSingleton<ITelemetryTracker, TelemetryTracker>();
services.AddSingleton<IReportsBackgroundQueue, ReportsBackgroundQueue>();
services.AddSingleton<IRequerstTrackerService, RequestTrackerService>();
services.AddSingleton<IBackgroundWorkerService, BackgroundWorkerService>();
services.AddTransient<IAuthService, AuthService>();
services.AddTransient<IClusterService, ClusterService>();

View File

@ -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)

View File

@ -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
{
/// <summary>
/// Сервис выстраивает очередь из фоновых задач. Ограничивает количество одновременно выполняющихся задач.
/// </summary>
public class BackgroundWorkerService : IDisposable, IBackgroundWorkerService
{
private readonly Worker[] workers;
private readonly Dictionary<string, Work> works = new Dictionary<string, Work>();
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<string, CancellationToken, Task> func)
{
var work = new Work
{
ActionAsync = func
};
return Enqueue(work);
}
public string Enqueue(string id, Func<string, CancellationToken, Task> func)
{
var work = new Work(id, func);
return Enqueue(work);
}
public string Enqueue(string id, Func<string, CancellationToken, Task> func, Func<string, Exception, CancellationToken, Task> 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<string, CancellationToken, Task> ActionAsync { get; set; }
public Func<string, Exception, CancellationToken, Task> OnErrorAsync { get; set; }
public Work()
{
Id = Guid.NewGuid().ToString();
}
public Work(string id, Func<string, CancellationToken, Task> actionAsync)
{
Id = id;
ActionAsync = actionAsync;
}
}
}

View File

@ -66,7 +66,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
// return fileInfo;
//}
private static void UniteExcelFiles(IEnumerable<string> excelFilesNames, string resultExcelPath)
public static void UniteExcelFiles(IEnumerable<string> excelFilesNames, string resultExcelPath)
{
var resultExcelFile = new XLWorkbook(XLEventTracking.Disabled);

View File

@ -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<IEnumerable<UserDto>> 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<UserDto>();
return usersDto;
}
public async Task<IEnumerable<FileCategoryDto>> GetCategoriesAsync(CancellationToken token = default)
@ -54,15 +81,6 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
public async Task<DrillingProgramStateDto> 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<FileInfoDto>();
var state = new DrillingProgramStateDto
{
Parts = parts,
Program = files.FirstOrDefault(f => f.IdCategory == idFileCategoryDrillingProgram)
.Adapt<FileInfoDto>()
};
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<int> AddPartAsync(int idWell, int idFileCategory, CancellationToken token = default)
public async Task<int> AddPartsAsync(int idWell, IEnumerable<int> 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<int> RemovePartAsync(int idWell, int idFileCategory, CancellationToken token = default)
public async Task<int> RemovePartsAsync(int idWell, IEnumerable<int> 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<FileCategory> fileCategories, List<FileInfo> files, DrillingProgramPart partEntity)
private static DrillingProgramPartDto ConvertPart(int idUser, List<FileCategory> fileCategories, List<AsbCloudDb.Model.FileInfo> 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<UserDto>()),
.Where(r => r.IdUserRole == idUserRoleApprover)
.Select(r => r.User.Adapt<UserDto>()),
Publishers = partEntity.RelatedUsers
.Where(u => u.IdUserRole == idUserRolePublisher)
.Select(u => u.Adapt<UserDto>()),
.Where(r => r.IdUserRole == idUserRolePublisher)
.Select(r => r.User.Adapt<UserDto>()),
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<FileInfoDto>();
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<AsbCloudDbContext>()
.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<int> 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}";
}
}

View File

@ -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);
}

View File

@ -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<object, int> progressHandler)
public string CreateReport(int idWell, int idUser, int stepSeconds, int format, DateTime begin,
DateTime end, Action<object, string> 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<AsbCloudDbContext>();
optionsBuilder.UseNpgsql(connectionString);
var tempDir = Path.Combine(Path.GetTempPath(), "report");
var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>()
.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
{

View File

@ -1,28 +0,0 @@
using AsbCloudApp.Services;
using System;
using System.Collections.Concurrent;
namespace AsbCloudInfrastructure.Services
{
class ReportsBackgroundQueue : IReportsBackgroundQueue
{
private readonly ConcurrentQueue<(Action<int> action, int id)> tasks =
new ConcurrentQueue<(Action<int> action, int id)>();
private int id = 0;
public int EnqueueTask(Action<int> action)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
id++;
tasks.Enqueue(new(action, id));
return id;
}
public bool TryDequeue(out (Action<int> action, int id) item)
=> tasks.TryDequeue(out item);
}
}

View File

@ -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);
}
}
}

View File

@ -41,10 +41,10 @@ namespace AsbCloudInfrastructure.Services
public TelemetryTracker(CacheDb cacheDb, IConfiguration configuration)
{
var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>()
.UseNpgsql(configuration.GetConnectionString("DefaultConnection"))
.Options;
var db = new AsbCloudDbContext(options);
var db = new AsbCloudDbContext(contextOptions);
var cacheTelemetry = cacheDb.GetCachedTable<Telemetry>(db);
var keyValuePairs = new Dictionary<string, TrackerStat>(cacheTelemetry.Count());

View File

@ -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);
}
/// <summary>
/// Список пользователей
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("availableUsers")]
[ProducesResponseType(typeof(FileCategoryDto[]), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetAvailableUsers(int idWell, CancellationToken token)
{
var result = await drillingProgramService.GetAvailableUsers(idWell, token);
return Ok(result);
}
/// <summary>
/// Состояние процесса согласования программы бурения
/// </summary>
@ -67,16 +80,16 @@ namespace AsbCloudWebApi.Controllers
}
/// <summary>
/// Загрузка файла программы бурения
/// Загрузка файла для части программы бурения
/// </summary>
/// <param name="idPart">ID части программы. Не путать с категорией файла</param>
/// <param name="idWell"></param>
/// <param name="files"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("{idPart}")]
[HttpPost("part/{idPart}")]
[Permission]
[ProducesResponseType(typeof(Task<int>), (int)System.Net.HttpStatusCode.OK)]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> 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
}
/// <summary>
/// Добавить раздел программы бурения
/// Добавляет разделы программы бурения
/// </summary>
/// <param name="idWell"></param>
/// <param name="idFileCategory"></param>
/// <param name="idFileCategories">Список категорий файлов, по которым будут добавлены части ПБ</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("part")]
[Permission]
[ProducesResponseType(typeof(Task<int>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> AddPartAsync(int idWell, int idFileCategory, CancellationToken token)
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> AddPartsAsync(int idWell, [Required] IEnumerable<int> 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);
}
/// <summary>
/// Удалить раздел программы бурения
/// Удаляет разделы программы бурения
/// </summary>
/// <param name="idWell"></param>
/// <param name="idFileCategory"></param>
/// <param name="idFileCategories">Список категорий файлов, по которым будут удалены части ПБ</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("part")]
[HttpDelete("part")]
[Permission]
[ProducesResponseType(typeof(Task<int>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> RemovePartAsync(int idWell, int idFileCategory, CancellationToken token)
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> RemovePartsAsync(int idWell, [Required] IEnumerable<int> 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}")]
/// <summary>
/// Добавить пользователя в список publishers или approvers части программы бурения
/// </summary>
/// <param name="idWell"></param>
/// <param name="idUser"></param>
/// <param name="idPart"></param>
/// <param name="idUserRole"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("part/{idPart}/user")]
[Permission]
[ProducesResponseType(typeof(Task<int>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> AddUserAsync(int idWell, int idUser, int idPart, int idUserRole, CancellationToken token = default)
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> 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);
}
/// <summary>
/// Удалить пользователя из списка publishers или approvers части программы бурения
/// </summary>
/// <param name="idWell"></param>
/// <param name="idUser"></param>
/// <param name="idPart"></param>
/// <param name="idUserRole"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpDelete("part/{idPart}/user/{idUser}")]
[Permission]
[ProducesResponseType(typeof(Task<int>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> RemoveUserAsync(int idWell, int idUser, int idPart, int idUserRole, CancellationToken token = default)
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> 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
/// <param name="idMark"> id метки </param>
/// <param name="token"> Токен отмены задачи </param>
/// <returns></returns>
[HttpDelete("fileMark")]
[HttpDelete("fileMark/{idMark}")]
[Permission]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> MarkAsDeletedFileMarkAsync(int idWell, int idMark,

View File

@ -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(