diff --git a/AsbCloudApp/Data/BackgroundWorkDto.cs b/AsbCloudApp/Data/BackgroundWorkDto.cs
new file mode 100644
index 00000000..b2a3d161
--- /dev/null
+++ b/AsbCloudApp/Data/BackgroundWorkDto.cs
@@ -0,0 +1,206 @@
+using System;
+using System.Diagnostics;
+
+namespace AsbCloudApp.Data
+{
+ ///
+ /// Информация о фоновой работе
+ ///
+ public class BackgroundWorkDto
+ {
+ ///
+ /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки.
+ ///
+ public string Id { get; init; } = null!;
+
+ ///
+ /// Класс описания состояния
+ ///
+ public class CurrentStateInfo
+ {
+ private string state = "start";
+
+ ///
+ /// Время последнего запуска
+ ///
+ public DateTime Start { get; } = DateTime.Now;
+
+ ///
+ /// Текущее время выполнения
+ ///
+ public TimeSpan ExecutionTime => DateTime.Now - Start;
+
+ ///
+ /// Текстовое описание того, что происходит в задаче.
+ ///
+ public string State
+ {
+ get => state;
+ internal set
+ {
+ state = value;
+ StateUpdate = DateTime.Now;
+ }
+ }
+
+ ///
+ /// Прогресс
+ ///
+ public double Progress { get; internal set; } = 0;
+
+ ///
+ /// Время последнего запуска
+ ///
+ public DateTime StateUpdate { get; private set; } = DateTime.Now;
+ }
+
+ ///
+ /// Инфо о последней ошибке
+ ///
+ public class LastErrorInfo : LastCompleteInfo
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ public LastErrorInfo(CurrentStateInfo state, string errorText)
+ : base(state)
+ {
+ ErrorText = errorText;
+ }
+
+ ///
+ /// Последняя ошибка
+ ///
+ public string ErrorText { get; init; } = null!;
+ }
+
+ ///
+ /// Инфо о последнем завершении
+ ///
+ public class LastCompleteInfo
+ {
+ ///
+ /// Дата запуска
+ ///
+ public DateTime Start { get; init; }
+
+ ///
+ /// Дата завершения
+ ///
+ public DateTime End { get; init; }
+
+ ///
+ /// Продолжительность последнего выполнения
+ ///
+ public TimeSpan ExecutionTime => End - Start;
+
+ ///
+ /// Состояние на момент завершения
+ ///
+ public string State { get; init; }
+
+ ///
+ /// ctor
+ ///
+ ///
+ public LastCompleteInfo(CurrentStateInfo state)
+ {
+ Start = state.Start;
+ End = DateTime.Now;
+ State = state.State;
+ }
+ }
+
+ ///
+ /// Текущее состояние
+ ///
+ public CurrentStateInfo? CurrentState { get; private set; }
+
+ ///
+ /// Последняя ошибка
+ ///
+ public LastErrorInfo? LastError { get; private set; }
+
+ ///
+ /// Последняя завершенная
+ ///
+ public LastCompleteInfo? LastComplete { get; private set; }
+
+ ///
+ /// Кол-во запусков
+ ///
+ public int CountStart { get; private set; }
+
+ ///
+ /// Кол-во завершений
+ ///
+ public int CountComplete { get; private set; }
+
+ ///
+ /// Кол-во ошибок
+ ///
+ public int CountErrors { get; private set; }
+
+ ///
+ /// Максимально допустимое время выполнения работы
+ ///
+ public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1);
+
+ private string WorkNameForTrace => $"Backgroud work:\"{Id}\"";
+
+ ///
+ /// Обновления состояния при запуске работы
+ ///
+ protected void SetStatusStart()
+ {
+ CurrentState = new();
+ CountStart++;
+ Trace.TraceInformation($"{WorkNameForTrace} state: starting");
+ }
+
+ ///
+ /// Обновления состояния в процессе работы
+ ///
+ protected void UpdateStatus(string newState, double? progress)
+ {
+ if (CurrentState is null)
+ return;
+
+ CurrentState.State = newState;
+ if (progress.HasValue)
+ CurrentState.Progress = progress.Value;
+
+ Trace.TraceInformation($"{WorkNameForTrace} state: {newState}");
+ }
+
+ ///
+ /// Обновления состояния при успешном завершении работы
+ ///
+ protected void SetStatusComplete()
+ {
+ if (CurrentState is null)
+ return;
+
+ LastComplete = new(CurrentState);
+ CurrentState = null;
+ CountComplete++;
+ Trace.TraceInformation($"{WorkNameForTrace} state: completed");
+ }
+
+ ///
+ /// Обновления состояния при ошибке в работе
+ ///
+ protected void SetLastError(string errorMessage)
+ {
+ if (CurrentState is null)
+ return;
+
+ LastError = new LastErrorInfo(CurrentState, errorMessage);
+ CurrentState = null;
+ CountErrors++;
+ Trace.TraceError($"{WorkNameForTrace} throw exception[{CountErrors}]: {errorMessage}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Data/WellboreDto.cs b/AsbCloudApp/Data/WellboreDto.cs
index dbd9b697..c153b14f 100644
--- a/AsbCloudApp/Data/WellboreDto.cs
+++ b/AsbCloudApp/Data/WellboreDto.cs
@@ -7,7 +7,10 @@ namespace AsbCloudApp.Data;
///
public class WellboreDto
{
- public WellWithTimezoneDto Well { get; set; }
+ ///
+ /// Скважина
+ ///
+ public WellWithTimezoneDto Well { get; set; } = null!;
///
/// Идентификатор
diff --git a/AsbCloudApp/Services/Notifications/NotificationService.cs b/AsbCloudApp/Services/Notifications/NotificationService.cs
index 85c521e3..4537f04e 100644
--- a/AsbCloudApp/Services/Notifications/NotificationService.cs
+++ b/AsbCloudApp/Services/Notifications/NotificationService.cs
@@ -90,7 +90,6 @@ public class NotificationService
/// Отправка уведомлений, которые не были отправлены
///
///
- ///
///
///
public async Task RenotifyAsync(int idUser, CancellationToken cancellationToken)
diff --git a/AsbCloudInfrastructure/Background/BackgroundWorker.cs b/AsbCloudInfrastructure/Background/BackgroundWorker.cs
index 7c88fade..cacb5a60 100644
--- a/AsbCloudInfrastructure/Background/BackgroundWorker.cs
+++ b/AsbCloudInfrastructure/Background/BackgroundWorker.cs
@@ -1,96 +1,49 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
-using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
-namespace AsbCloudInfrastructure.Background
+namespace AsbCloudInfrastructure.Background;
+
+///
+/// Сервис для фонового выполнения работы
+///
+public class BackgroundWorker : BackgroundService
{
- ///
- /// Сервис для фонового выполнения работы
- ///
- public class BackgroundWorker : BackgroundService
+ private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
+ private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2);
+ private readonly IServiceProvider serviceProvider;
+
+ public WorkStore WorkStore { get; } = new WorkStore();
+ public Work? CurrentWork;
+
+ public BackgroundWorker(IServiceProvider serviceProvider)
{
- private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
- private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2);
- private static readonly TimeSpan exceptionHandleTimeout = TimeSpan.FromSeconds(2);
- private readonly IServiceProvider serviceProvider;
- private readonly WorkQueue workQueue = new WorkQueue();
- public string? CurrentWorkId;
- public BackgroundWorker(IServiceProvider serviceProvider)
- {
- this.serviceProvider = serviceProvider;
- }
+ this.serviceProvider = serviceProvider;
+ }
- ///
- /// Добавление задачи в очередь.
- /// Не периодические задачи будут выполняться вперед.
- ///
- ///
- /// Id mast be unique
- public void Push(WorkBase work)
+ protected override async Task ExecuteAsync(CancellationToken token)
+ {
+ while (!token.IsCancellationRequested)
{
- workQueue.Push(work);
- }
-
- ///
- /// Проверяет наличие работы с указанным Id
- ///
- ///
- ///
- public bool Contains(string id)
- {
- return workQueue.Contains(id);
- }
-
- ///
- /// Удаление работы по ID
- ///
- ///
- ///
- public bool Delete(string id)
- {
- return workQueue.Delete(id);
- }
-
- protected override async Task ExecuteAsync(CancellationToken token)
- {
- while (!token.IsCancellationRequested)
+ var work = WorkStore.GetNext();
+ if (work is null)
{
- var dateStart = DateTime.Now;
- var work = workQueue.Pop();
- if (work is null)
- {
- await Task.Delay(executePeriod, token);
- continue;
- }
- CurrentWorkId = work.Id;
- using var scope = serviceProvider.CreateScope();
-
- try
- {
- Trace.TraceInformation($"Backgroud work:\"{work.Id}\" start.");
- var task = work.ActionAsync(work.Id, scope.ServiceProvider, token);
- await task.WaitAsync(work.Timeout, token);
-
- work.ExecutionTime = DateTime.Now - dateStart;
- Trace.TraceInformation($"Backgroud work:\"{work.Id}\" done. ExecutionTime: {work.ExecutionTime:hh\\:mm\\:ss\\.fff}");
- }
- catch (Exception exception)
- {
- Trace.TraceError($"Backgroud work:\"{work.Id}\" throw exception: {exception.Message}");
- if (work.OnErrorAsync is not null)
- {
- using var task = Task.Run(
- async () => await work.OnErrorAsync(work.Id, exception, token),
- token);
- await task.WaitAsync(exceptionHandleTimeout, token);
- }
- }
- CurrentWorkId = null;
- await Task.Delay(minDelay, token);
+ await Task.Delay(executePeriod, token);
+ continue;
}
+
+ CurrentWork = work;
+ using var scope = serviceProvider.CreateScope();
+
+ var result = await work.Start(scope.ServiceProvider, token);
+
+ if (!result)
+ WorkStore.Felled.Add(work);
+
+ CurrentWork = null;
+ await Task.Delay(minDelay, token);
}
}
}
diff --git a/AsbCloudInfrastructure/Background/OrderedList.cs b/AsbCloudInfrastructure/Background/OrderedList.cs
new file mode 100644
index 00000000..1f583a82
--- /dev/null
+++ b/AsbCloudInfrastructure/Background/OrderedList.cs
@@ -0,0 +1,41 @@
+using System.Linq;
+
+namespace System.Collections.Generic
+{
+ public class OrderedList: IEnumerable, ICollection
+ where T : notnull
+ {
+ private readonly List list = new List();
+
+ private readonly Func keySelector;
+ private readonly bool isDescending = false;
+
+ private IOrderedEnumerable OrdredList => isDescending
+ ? list.OrderByDescending(keySelector)
+ : list.OrderBy(keySelector);
+
+ public int Count => list.Count;
+
+ public bool IsReadOnly => false;
+
+ public OrderedList(Func keySelector, bool isDescending = false)
+ {
+ this.keySelector = keySelector;
+ this.isDescending = isDescending;
+ }
+
+ public void Add(T item) => list.Add(item);
+
+ public void Clear()=> list.Clear();
+
+ public bool Contains(T item)=> list.Contains(item);
+
+ public void CopyTo(T[] array, int arrayIndex)=> list.CopyTo(array, arrayIndex);
+
+ public bool Remove(T item)=> list.Remove(item);
+
+ public IEnumerator GetEnumerator() => OrdredList.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+}
diff --git a/AsbCloudInfrastructure/Background/Work.cs b/AsbCloudInfrastructure/Background/Work.cs
new file mode 100644
index 00000000..eba7df66
--- /dev/null
+++ b/AsbCloudInfrastructure/Background/Work.cs
@@ -0,0 +1,125 @@
+using AsbCloudApp.Data;
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AsbCloudInfrastructure.Background;
+
+///
+/// Класс разовой работы.
+/// Разовая работа приоритетнее периодической.
+///
+public abstract class Work : BackgroundWorkDto
+{
+ private sealed class WorkBase : Work
+ {
+ private Func, CancellationToken, Task> ActionAsync { get; }
+ public WorkBase(string id, Func, CancellationToken, Task> actionAsync)
+ : base(id)
+ {
+ ActionAsync = actionAsync;
+ }
+
+ protected override Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token)
+ => ActionAsync(id, services, onProgressCallback, token);
+ }
+
+ ///
+ /// Делегат обработки ошибки.
+ /// Не должен выполняться долго.
+ ///
+ public Func? OnErrorAsync { get; set; }
+
+ ///
+ /// макс продолжительность обработки исключения
+ ///
+ public TimeSpan OnErrorHandlerTimeout { get; set; } = TimeSpan.FromSeconds(5);
+
+ ///
+ /// Базовая работа
+ ///
+ ///
+ public Work(string id)
+ {
+ Id = id;
+ }
+
+ ///
+ /// Создать работу на основе делегата
+ ///
+ ///
+ ///
+ ///
+ [Obsolete("Use implement Work class")]
+ public static Work CreateByDelegate(string id, Func, CancellationToken, Task> actionAsync)
+ {
+ return new WorkBase(id, actionAsync);
+ }
+
+ ///
+ /// Запустить работу
+ ///
+ ///
+ ///
+ /// True - success, False = fail
+ public async Task Start(IServiceProvider services, CancellationToken token)
+ {
+ SetStatusStart();
+ try
+ {
+ var task = Action(Id, services, UpdateStatus, token);
+ await task.WaitAsync(Timeout, token);
+ SetStatusComplete();
+ return true;
+ }
+ catch (Exception exception)
+ {
+ var message = FormatExceptionMessage(exception);
+ SetLastError(message);
+ if (OnErrorAsync is not null)
+ {
+ var task = Task.Run(
+ async () => await OnErrorAsync(Id, exception, token),
+ token);
+ await task.WaitAsync(OnErrorHandlerTimeout, token);
+ }
+ }
+ return false;
+ }
+
+ private static string FormatExceptionMessage(Exception exception)
+ {
+ var firstException = FirstException(exception);
+ var message = new StringBuilder();
+ if (firstException != exception)
+ {
+ message.Append("top exception:");
+ message.AppendLine(exception.Message);
+
+ message.Append("inner exception:");
+ message.AppendLine(firstException.Message);
+ }
+ else
+ message.AppendLine(firstException.Message);
+ message.AppendLine(exception.StackTrace);
+ return message.ToString();
+ }
+
+ private static Exception FirstException(Exception exception)
+ {
+ if (exception.InnerException is not null)
+ return FirstException(exception.InnerException);
+ return exception;
+ }
+
+ ///
+ /// делегат фоновой работы
+ ///
+ /// Идентификатор работы
+ /// Поставщик сервисов
+ /// on progress callback. String - new state text. double? - optional progress 0-100%
+ ///
+ ///
+ protected abstract Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token);
+}
diff --git a/AsbCloudInfrastructure/Background/WorkBase.cs b/AsbCloudInfrastructure/Background/WorkBase.cs
deleted file mode 100644
index e8adf04c..00000000
--- a/AsbCloudInfrastructure/Background/WorkBase.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace AsbCloudInfrastructure.Background
-{
-
- ///
- /// Класс разовой работы.
- /// Разовая работа приоритетнее периодической.
- ///
- public class WorkBase
- {
- ///
- /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки.
- ///
- public string Id { get; private set; }
-
- ///
- /// Делегат работы.
- ///
- /// Параметры:
- ///
- /// -
- /// string
- /// Id Идентификатор работы
- ///
- /// -
- /// IServiceProvider
- /// Поставщик сервисов
- ///
- /// -
- /// CancellationToken
- /// Токен отмены задачи
- ///
- ///
- ///
- ///
- internal Func ActionAsync { get; set; }
-
- ///
- /// Делегат обработки ошибки.
- /// Не должен выполняться долго.
- ///
- public Func? OnErrorAsync { get; set; }
-
- ///
- /// максимально допустимое время выполнения работы
- ///
- public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1);
-
- ///
- /// Фактическое время успешного выполнения работы
- ///
- public TimeSpan? ExecutionTime { get; internal set; }
-
- ///
- /// Время последнего запуска
- ///
- public DateTime LastStart { get; set; }
-
- public WorkBase(string id, Func actionAsync)
- {
- Id = id;
- ActionAsync = actionAsync;
- }
- }
-
-}
diff --git a/AsbCloudInfrastructure/Background/WorkLimitingParameterCalc.cs b/AsbCloudInfrastructure/Background/WorkLimitingParameterCalc.cs
new file mode 100644
index 00000000..c0d6eb48
--- /dev/null
+++ b/AsbCloudInfrastructure/Background/WorkLimitingParameterCalc.cs
@@ -0,0 +1,145 @@
+using AsbCloudDb.Model;
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Data.Common;
+using System.Data;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace AsbCloudInfrastructure.Background;
+
+public class WorkLimitingParameterCalc : Work
+{
+ public WorkLimitingParameterCalc()
+ : base("Limiting parameter calc")
+ {
+ Timeout = TimeSpan.FromMinutes(30);
+ }
+
+ protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token)
+ {
+ using var db = services.GetRequiredService();
+ db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
+ var lastDetectedDates = await db.LimitingParameter
+ .GroupBy(o => o.IdTelemetry)
+ .Select(g => new
+ {
+ IdTelemetry = g.Key,
+ LastDate = g.Max(o => o.DateEnd)
+ })
+ .ToListAsync(token);
+
+ var telemetryIds = await db.Telemetries
+ .Where(t => t.Info != null && t.TimeZone != null)
+ .Select(t => t.Id)
+ .ToListAsync(token);
+
+ var telemetryLastDetectedDates = telemetryIds
+ .GroupJoin(lastDetectedDates,
+ t => t,
+ o => o.IdTelemetry,
+ (outer, inner) => new
+ {
+ IdTelemetry = outer,
+ inner.SingleOrDefault()?.LastDate,
+ });
+
+ var count = telemetryLastDetectedDates.Count();
+ var i = 0d;
+ foreach (var item in telemetryLastDetectedDates)
+ {
+ onProgressCallback($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count);
+ var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
+ if (newLimitingParameters?.Any() == true)
+ {
+ db.LimitingParameter.AddRange(newLimitingParameters);
+ await db.SaveChangesAsync(token);
+ }
+ }
+ }
+
+ private static async Task> GetLimitingParameterAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
+ {
+ var query =
+ $"select " +
+ $"limiting_parameters.date, limiting_parameters.id_feed_regulator, limiting_parameters.well_depth " +
+ $"from ( " +
+ $"select " +
+ $"date, id_feed_regulator, well_depth, " +
+ $"lag(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lag, " +
+ $"lead(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lead " +
+ $"from t_telemetry_data_saub " +
+ $"where id_feed_regulator is not null " +
+ $"and id_telemetry = {idTelemetry} " +
+ $"and date >= '{begin:u}' " +
+ $"order by date) as limiting_parameters " +
+ $"where id_feed_regulator_lag is null " +
+ $"or (id_feed_regulator != id_feed_regulator_lag and id_feed_regulator_lead != id_feed_regulator_lag) " +
+ $"order by date;";
+
+ var limitingParameters = new List(32);
+ using (var result = await ExecuteReaderAsync(db, query, token))
+ {
+ LimitingParameter? limitingLast = null;
+ while (result.Read())
+ {
+ var date = result.GetFieldValue(0);
+ var idLimiting = result.GetFieldValue(1);
+ var wellDepth = result.GetFieldValue(2);
+
+ if (limitingLast is null)
+ {
+ limitingLast = new LimitingParameter
+ {
+ DateStart = date,
+ DepthStart = wellDepth,
+ IdFeedRegulator = idLimiting
+ };
+ }
+
+ if (limitingLast.IdFeedRegulator != idLimiting || limitingLast.DepthStart < wellDepth)
+ {
+ limitingParameters.Add(new LimitingParameter
+ {
+ IdTelemetry = idTelemetry,
+ IdFeedRegulator = limitingLast.IdFeedRegulator,
+ DateStart = limitingLast.DateStart,
+ DateEnd = date,
+ DepthStart = limitingLast.DepthStart,
+ DepthEnd = wellDepth
+ });
+
+ limitingLast = new LimitingParameter
+ {
+ DateStart = date,
+ DepthStart = wellDepth,
+ IdFeedRegulator = idLimiting
+ };
+ }
+ }
+ }
+
+ return limitingParameters;
+ }
+
+ private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token)
+ {
+ var connection = db.Database.GetDbConnection();
+ if (
+ connection?.State is null ||
+ connection.State == ConnectionState.Broken ||
+ connection.State == ConnectionState.Closed)
+ {
+ await db.Database.OpenConnectionAsync(token);
+ connection = db.Database.GetDbConnection();
+ }
+ using var command = connection.CreateCommand();
+ command.CommandText = query;
+
+ var result = await command.ExecuteReaderAsync(token);
+ return result;
+ }
+}
diff --git a/AsbCloudInfrastructure/Background/WorkPeriodic.cs b/AsbCloudInfrastructure/Background/WorkPeriodic.cs
index cbd34fec..4cd22af6 100644
--- a/AsbCloudInfrastructure/Background/WorkPeriodic.cs
+++ b/AsbCloudInfrastructure/Background/WorkPeriodic.cs
@@ -1,36 +1,42 @@
using System;
-using System.Threading;
-using System.Threading.Tasks;
-namespace AsbCloudInfrastructure.Background
+namespace AsbCloudInfrastructure.Background;
+
+///
+/// Класс периодической работы.
+///
+public class WorkPeriodic
{
+ public Work Work { get; }
///
- /// Класс периодической работы.
+ /// Период выполнения задачи
///
- public class WorkPeriodic : WorkBase
+ public TimeSpan Period { get; set; }
+
+ ///
+ /// Время следующего запуска
+ ///
+ public DateTime NextStart
{
- ///
- /// Период выполнения задачи
- ///
- public TimeSpan Period { get; set; }
-
- ///
- /// Время следующего запуска
- ///
- public DateTime NextStart => LastStart + Period;
-
- ///
- /// Класс периодической работы
- ///
- /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки
- /// Делегат работы
- /// Период выполнения задачи
- public WorkPeriodic(string id, Func actionAsync, TimeSpan period)
- : base(id, actionAsync)
+ get
{
- Period = period;
+ var lastStart = Work.LastComplete?.Start ?? DateTime.MinValue;
+ if (Work.LastError?.Start > lastStart)
+ lastStart = Work.LastError.Start;
+ return lastStart + Period;
}
}
+ ///
+ /// Класс периодической работы
+ ///
+ /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки
+ /// Делегат работы
+ /// Период выполнения задачи
+ public WorkPeriodic(Work work, TimeSpan period)
+ {
+ Work = work;
+ Period = period;
+ }
}
diff --git a/AsbCloudInfrastructure/Background/WorkQueue.cs b/AsbCloudInfrastructure/Background/WorkQueue.cs
deleted file mode 100644
index ce77fa94..00000000
--- a/AsbCloudInfrastructure/Background/WorkQueue.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace AsbCloudInfrastructure.Background
-{
-
- ///
- ///
- /// Очередь работ
- ///
- /// Не периодические задачи будут возвращаться первыми, как самые приоритетные.
- ///
- class WorkQueue
- {
- private Queue Primary = new(8);
- private readonly List Periodic = new(8);
-
- ///
- /// Добавление работы.
- ///
- ///
- /// Id mast be unique
- public void Push(WorkBase work)
- {
- if (Periodic.Any(w => w.Id == work.Id))
- throw new ArgumentException("work.Id is not unique", nameof(work));
-
- if (Primary.Any(w => w.Id == work.Id))
- throw new ArgumentException("work.Id is not unique", nameof(work));
-
- if (work is WorkPeriodic workPeriodic)
- {
- Periodic.Add(workPeriodic);
- return;
- }
-
- Primary.Enqueue(work);
- }
-
- ///
- /// Удаление работы по ID
- ///
- ///
- ///
- public bool Delete(string id)
- {
- var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id);
- if (workPeriodic is not null)
- {
- Periodic.Remove(workPeriodic);
- return true;
- }
-
- var work = Primary.FirstOrDefault(w => w.Id == id);
- if (work is not null)
- {
- Primary = new Queue(Primary.Where(w => w.Id != id));
- return true;
- }
-
- return false;
- }
-
- public bool Contains(string id)
- {
- var result = Periodic.Any(w => w.Id == id) || Primary.Any(w => w.Id == id);
- return result;
- }
-
- ///
- ///
- /// Возвращает приоритетную задачу.
- ///
- ///
- /// Если приоритетные закончились, то ищет ближайшую периодическую.
- /// Если до старта ближайшей периодической работы меньше 20 сек,
- /// то этой задаче устанавливается время последнего запуска в now и она возвращается.
- /// Если больше 20 сек, то возвращается null.
- ///
- ///
- ///
- ///
- public WorkBase? Pop()
- {
- if (Primary.Any())
- return Primary.Dequeue();
-
- var work = GetNextPeriodic();
- if (work is null || work.NextStart > DateTime.Now)
- return null;
-
- work.LastStart = DateTime.Now;
- return work;
- }
-
- private WorkPeriodic? GetNextPeriodic()
- {
- var work = Periodic
- .OrderBy(w => w.NextStart)
- .ThenByDescending(w => w.Period)
- .FirstOrDefault();
- return work;
- }
- }
-
-}
diff --git a/AsbCloudInfrastructure/Background/WorkStore.cs b/AsbCloudInfrastructure/Background/WorkStore.cs
new file mode 100644
index 00000000..b57fc104
--- /dev/null
+++ b/AsbCloudInfrastructure/Background/WorkStore.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AsbCloudInfrastructure.Background;
+
+///
+///
+/// Очередь работ
+///
+/// Не периодические задачи будут возвращаться первыми, как самые приоритетные.
+///
+public class WorkStore
+{
+ private readonly List periodics = new(8);
+
+ ///
+ /// Список периодических задач
+ ///
+ public IEnumerable Periodics => periodics;
+
+ ///
+ /// Работы выполняемые один раз
+ ///
+ public Queue RunOnceQueue { get; private set; } = new(8);
+
+ ///
+ /// Завершившиеся с ошибкой
+ ///
+ public CyclycArray Felled { get; } = new(16);
+
+ ///
+ /// Добавить фоновую работу выполняющуюся с заданным периодом
+ ///
+ ///
+ ///
+ public void AddPeriodic(TimeSpan period)
+ where T : Work, new()
+ {
+ var work = new T();
+ var periodic = new WorkPeriodic(work, period);
+ periodics.Add(periodic);
+ }
+
+ ///
+ /// Добавить фоновую работу выполняющуюся с заданным периодом
+ ///
+ ///
+ ///
+ public void AddPeriodic(Work work, TimeSpan period)
+ {
+ var periodic = new WorkPeriodic(work, period);
+ periodics.Add(periodic);
+ }
+
+ ///
+ /// Удаление работы по ID из одноразовой очереди
+ ///
+ ///
+ ///
+ public bool TryRemoveFromRunOnceQueue(string id)
+ {
+ var work = RunOnceQueue.FirstOrDefault(w => w.Id == id);
+ if (work is not null)
+ {
+ RunOnceQueue = new Queue(RunOnceQueue.Where(w => w.Id != id));
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ ///
+ /// Возвращает приоритетную задачу.
+ ///
+ ///
+ /// Если приоритетные закончились, то ищет ближайшую периодическую.
+ /// Если до старта ближайшей периодической работы меньше 20 сек,
+ /// то этой задаче устанавливается время последнего запуска в now и она возвращается.
+ /// Если больше 20 сек, то возвращается null.
+ ///
+ ///
+ ///
+ ///
+ public Work? GetNext()
+ {
+ if (RunOnceQueue.Any())
+ return RunOnceQueue.Dequeue();
+
+ var work = GetNextPeriodic();
+ if (work is null || work.NextStart > DateTime.Now)
+ return null;
+
+ return work.Work;
+ }
+
+ private WorkPeriodic? GetNextPeriodic()
+ {
+ var work = Periodics
+ .OrderBy(w => w.NextStart)
+ .FirstOrDefault();
+ return work;
+ }
+}
diff --git a/AsbCloudInfrastructure/Background/todo.md b/AsbCloudInfrastructure/Background/todo.md
index f30ce6c4..b8b30852 100644
--- a/AsbCloudInfrastructure/Background/todo.md
+++ b/AsbCloudInfrastructure/Background/todo.md
@@ -1,5 +1,12 @@
#
--
+- .
+ - ,
+ - ,
+ - ,
- . .
- /
- /
+
+#
+- dto
+- , .
diff --git a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs
index 2f402285..03ed6e07 100644
--- a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs
+++ b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs
@@ -226,7 +226,7 @@ public class AutoGeneratedDailyReportService : IAutoGeneratedDailyReportService
.OrderBy(w => w.DateStart);
}
- private Task?> GetSubsystemStatsAsync(int idWell, DateTime startDate,
+ private Task> GetSubsystemStatsAsync(int idWell, DateTime startDate,
DateTime finishDate, CancellationToken cancellationToken)
{
var request = new SubsystemOperationTimeRequest
diff --git a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs b/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs
deleted file mode 100644
index da171f13..00000000
--- a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs
+++ /dev/null
@@ -1,163 +0,0 @@
-using AsbCloudDb.Model;
-using Microsoft.EntityFrameworkCore;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
-using AsbCloudInfrastructure.Background;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace AsbCloudInfrastructure.Services.DetectOperations
-{
-
- public static class OperationDetectionWorkFactory
- {
- private const string workId = "Operation detection";
- private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
- private static string progress = "no progress";
-
- private static readonly DetectorAbstract[] detectors = new DetectorAbstract[]
- {
- new DetectorRotor(),
- new DetectorSlide(),
- //new DetectorDevelopment(),
- //new DetectorTemplating(),
- new DetectorSlipsTime(),
- //new DetectorStaticSurveying(),
- //new DetectorFlashingBeforeConnection(),
- //new DetectorFlashing(),
- //new DetectorTemplatingWhileDrilling(),
- };
-
- public static WorkPeriodic MakeWork()
- {
- var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod);
- workPeriodic.Timeout = TimeSpan.FromSeconds(15 * 60);
- workPeriodic.OnErrorAsync = (id, exception, token) =>
- {
- var text = $"work {id}, when {progress}, throw error:{exception.Message}";
- Trace.TraceWarning(text);
- return Task.CompletedTask;
- };
- return workPeriodic;
- }
-
- // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД.
- private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token)
- {
- using var db = serviceProvider.GetRequiredService();
-
- var lastDetectedDates = await db.DetectedOperations
- .GroupBy(o => o.IdTelemetry)
- .Select(g => new
- {
- IdTelemetry = g.Key,
- LastDate = g.Max(o => o.DateEnd)
- })
- .ToListAsync(token);
-
- var telemetryIds = await db.Telemetries
- .Where(t => t.Info != null && t.TimeZone != null)
- .Select(t => t.Id)
- .ToListAsync(token);
-
- var joinedlastDetectedDates = telemetryIds
- .GroupJoin(lastDetectedDates,
- t => t,
- o => o.IdTelemetry,
- (outer, inner) => new
- {
- IdTelemetry = outer,
- inner.SingleOrDefault()?.LastDate,
- });
-
- var affected = 0;
- foreach (var item in joinedlastDetectedDates)
- {
- var stopwatch = Stopwatch.StartNew();
- var newOperations = await DetectOperationsAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
- stopwatch.Stop();
- if (newOperations.Any())
- {
- db.DetectedOperations.AddRange(newOperations);
- affected += await db.SaveChangesAsync(token);
- }
- }
- }
-
- private static async Task> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
- {
- var query = db.TelemetryDataSaub
- .AsNoTracking()
- .Where(d => d.IdTelemetry == idTelemetry)
- .Where(d => d.BlockPosition >= 0)
- .Select(d => new DetectableTelemetry
- {
- DateTime = d.DateTime,
- IdUser = d.IdUser,
- WellDepth = d.WellDepth ?? float.NaN,
- Pressure = d.Pressure ?? float.NaN,
- HookWeight = d.HookWeight ?? float.NaN,
- BlockPosition = d.BlockPosition ?? float.NaN,
- BitDepth = d.BitDepth ?? float.NaN,
- RotorSpeed = d.RotorSpeed ?? float.NaN,
- })
- .OrderBy(d => d.DateTime);
-
- var take = 4 * 86_400; // 4 дня
- var startDate = begin;
- var detectedOperations = new List(8);
- DetectedOperation? lastDetectedOperation = null;
- const int minOperationLength = 5;
- const int maxDetectorsInterpolationFrameLength = 30;
- const int gap = maxDetectorsInterpolationFrameLength + minOperationLength;
-
- while (true)
- {
- var data = await query
- .Where(d => d.DateTime > startDate)
- .Take(take)
- .ToArrayAsync(token);
-
- if (data.Length < gap)
- break;
-
- var isDetected = false;
- var positionBegin = 0;
- var positionEnd = data.Length - gap;
- var step = 10;
- while (positionEnd > positionBegin)
- {
- step ++;
- for (int i = 0; i < detectors.Length; i++)
- {
- progress = $"telemetry:{idTelemetry}, date:{startDate}, pos:{positionBegin}, detector:{detectors[i]}";
- if (detectors[i].TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out OperationDetectorResult? result))
- {
- detectedOperations.Add(result!.Operation);
- lastDetectedOperation = result.Operation;
- isDetected = true;
- step = 1;
- positionBegin = result.TelemetryEnd;
- break;
- }
- }
- if (step > 20)
- step = 10;
- positionBegin += step;
- }
-
- if (isDetected)
- startDate = lastDetectedOperation!.DateEnd;
- else
- startDate = data[positionEnd].DateTime;
- }
-
- return detectedOperations;
- }
- }
-
-}
diff --git a/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs b/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs
new file mode 100644
index 00000000..eef423b9
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs
@@ -0,0 +1,157 @@
+using AsbCloudDb.Model;
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
+using AsbCloudInfrastructure.Background;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace AsbCloudInfrastructure.Services.DetectOperations;
+
+public class WorkOperationDetection: Work
+{
+ private static readonly DetectorAbstract[] detectors = new DetectorAbstract[]
+ {
+ new DetectorRotor(),
+ new DetectorSlide(),
+ //new DetectorDevelopment(),
+ //new DetectorTemplating(),
+ new DetectorSlipsTime(),
+ //new DetectorStaticSurveying(),
+ //new DetectorFlashingBeforeConnection(),
+ //new DetectorFlashing(),
+ //new DetectorTemplatingWhileDrilling(),
+ };
+
+ public WorkOperationDetection()
+ :base("Operation detection")
+ {
+ Timeout = TimeSpan.FromMinutes(20);
+ OnErrorAsync = (id, exception, token) =>
+ {
+ var text = $"work {id}, when {CurrentState?.State}, throw error:{exception.Message}";
+ Trace.TraceWarning(text);
+ return Task.CompletedTask;
+ };
+ }
+
+ protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token)
+ {
+ using var db = services.GetRequiredService();
+
+ var lastDetectedDates = await db.DetectedOperations
+ .GroupBy(o => o.IdTelemetry)
+ .Select(g => new
+ {
+ IdTelemetry = g.Key,
+ LastDate = g.Max(o => o.DateEnd)
+ })
+ .ToListAsync(token);
+
+ var telemetryIds = await db.Telemetries
+ .Where(t => t.Info != null && t.TimeZone != null)
+ .Select(t => t.Id)
+ .ToListAsync(token);
+
+ var joinedlastDetectedDates = telemetryIds
+ .GroupJoin(lastDetectedDates,
+ t => t,
+ o => o.IdTelemetry,
+ (outer, inner) => new
+ {
+ IdTelemetry = outer,
+ inner.SingleOrDefault()?.LastDate,
+ });
+
+ var affected = 0;
+ var count = joinedlastDetectedDates.Count();
+ var i = 0d;
+ foreach (var item in joinedlastDetectedDates)
+ {
+ var stopwatch = Stopwatch.StartNew();
+ var startDate = item.LastDate ?? DateTimeOffset.MinValue;
+ onProgressCallback($"start detecting telemetry: {item.IdTelemetry} from {startDate}", i++ / count);
+ var newOperations = await DetectOperationsAsync(item.IdTelemetry, startDate, db, token);
+ stopwatch.Stop();
+ if (newOperations.Any())
+ {
+ db.DetectedOperations.AddRange(newOperations);
+ affected += await db.SaveChangesAsync(token);
+ }
+ }
+ }
+
+ private static async Task> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
+ {
+ var query = db.TelemetryDataSaub
+ .AsNoTracking()
+ .Where(d => d.IdTelemetry == idTelemetry)
+ .Where(d => d.BlockPosition >= 0)
+ .Select(d => new DetectableTelemetry
+ {
+ DateTime = d.DateTime,
+ IdUser = d.IdUser,
+ WellDepth = d.WellDepth ?? float.NaN,
+ Pressure = d.Pressure ?? float.NaN,
+ HookWeight = d.HookWeight ?? float.NaN,
+ BlockPosition = d.BlockPosition ?? float.NaN,
+ BitDepth = d.BitDepth ?? float.NaN,
+ RotorSpeed = d.RotorSpeed ?? float.NaN,
+ })
+ .OrderBy(d => d.DateTime);
+
+ var take = 4 * 86_400; // 4 дня
+ var startDate = begin;
+ var detectedOperations = new List(8);
+ DetectedOperation? lastDetectedOperation = null;
+ const int minOperationLength = 5;
+ const int maxDetectorsInterpolationFrameLength = 30;
+ const int gap = maxDetectorsInterpolationFrameLength + minOperationLength;
+
+ while (true)
+ {
+ var data = await query
+ .Where(d => d.DateTime > startDate)
+ .Take(take)
+ .ToArrayAsync(token);
+
+ if (data.Length < gap)
+ break;
+
+ var isDetected = false;
+ var positionBegin = 0;
+ var positionEnd = data.Length - gap;
+ var step = 10;
+ while (positionEnd > positionBegin)
+ {
+ step ++;
+ for (int i = 0; i < detectors.Length; i++)
+ {
+ if (detectors[i].TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out OperationDetectorResult? result))
+ {
+ detectedOperations.Add(result!.Operation);
+ lastDetectedOperation = result.Operation;
+ isDetected = true;
+ step = 1;
+ positionBegin = result.TelemetryEnd;
+ break;
+ }
+ }
+ if (step > 20)
+ step = 10;
+ positionBegin += step;
+ }
+
+ if (isDetected)
+ startDate = lastDetectedOperation!.DateEnd;
+ else
+ startDate = data[positionEnd].DateTime;
+ }
+
+ return detectedOperations;
+ }
+}
diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs
index adb410d6..228a3564 100644
--- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs
+++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs
@@ -513,17 +513,18 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
if (state.IdState == idStateCreating)
{
var workId = MakeWorkId(idWell);
- if (!backgroundWorker.Contains(workId))
+ if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId))
{
var well = (await wellService.GetOrDefaultAsync(idWell, token))!;
var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.pdf";
var convertedFilesDir = Path.Combine(Path.GetTempPath(), "drillingProgram", $"{well.Cluster}_{well.Caption}");
var tempResultFilePath = Path.Combine(convertedFilesDir, resultFileName);
- var workAction = async (string workId, IServiceProvider serviceProvider, CancellationToken token) =>
+ var workAction = async (string workId, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) =>
{
var context = serviceProvider.GetRequiredService();
var fileService = serviceProvider.GetRequiredService();
var files = state.Parts.Select(p => fileService.GetUrl(p.File!));
+ onProgress($"Start converting {files.Count()} files to PDF.", null);
await ConvertToPdf.GetConverteAndMergedFileAsync(files, tempResultFilePath, convertedFilesDir, token);
await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, tempResultFilePath, token);
};
@@ -539,13 +540,9 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
return Task.CompletedTask;
};
- var work = new WorkBase(workId, workAction)
- {
- ExecutionTime = TimeSpan.FromMinutes(1),
- OnErrorAsync = onErrorAction
- };
-
- backgroundWorker.Push(work);
+ var work = Work.CreateByDelegate(workId, workAction);
+ work.OnErrorAsync = onErrorAction;
+ backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
}
}
}
@@ -559,7 +556,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
private async Task RemoveDrillingProgramAsync(int idWell, CancellationToken token)
{
var workId = MakeWorkId(idWell);
- backgroundWorker.Delete(workId);
+ backgroundWorker.WorkStore.TryRemoveFromRunOnceQueue(workId);
var filesIds = await context.Files
.Where(f => f.IdWell == idWell &&
diff --git a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs
index 26eed2be..bbbc8196 100644
--- a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs
+++ b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs
@@ -10,6 +10,7 @@ using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services.Notifications;
using AsbCloudInfrastructure.Background;
+using DocumentFormat.OpenXml.Presentation;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -51,12 +52,12 @@ namespace AsbCloudInfrastructure.Services.Email
}
var workId = MakeWorkId(notification.IdUser, notification.Title, notification.Message);
- if (!backgroundWorker.Contains(workId))
+ if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w=>w.Id==workId))
{
var workAction = MakeEmailSendWorkAction(notification);
- var work = new WorkBase(workId, workAction);
- backgroundWorker.Push(work);
+ var work = Work.CreateByDelegate(workId, workAction);
+ backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
}
return Task.CompletedTask;
@@ -70,9 +71,9 @@ namespace AsbCloudInfrastructure.Services.Email
return Task.WhenAll(tasks);
}
- private Func MakeEmailSendWorkAction(NotificationDto notification)
+ private Func, CancellationToken, Task> MakeEmailSendWorkAction(NotificationDto notification)
{
- return async (_, serviceProvider, token) =>
+ return async (_, serviceProvider, onProgress, token) =>
{
var notificationRepository = serviceProvider.GetRequiredService();
var userRepository = serviceProvider.GetRequiredService();
diff --git a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs b/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs
deleted file mode 100644
index 7c875e70..00000000
--- a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-using AsbCloudDb.Model;
-using Microsoft.EntityFrameworkCore;
-using System;
-using System.Data.Common;
-using System.Data;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-using AsbCloudInfrastructure.Background;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace AsbCloudInfrastructure.Services
-{
-
- internal static class LimitingParameterCalcWorkFactory
- {
- private const string workId = "Limiting parameter calc";
- private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
-
- public static WorkPeriodic MakeWork()
- {
- var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod)
- {
- Timeout = TimeSpan.FromMinutes(30)
- };
- return workPeriodic;
- }
-
- // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД.
- private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token)
- {
- using var db = serviceProvider.GetRequiredService();
- var lastDetectedDates = await db.LimitingParameter
- .GroupBy(o => o.IdTelemetry)
- .Select(g => new
- {
- IdTelemetry = g.Key,
- LastDate = g.Max(o => o.DateEnd)
- })
- .ToListAsync(token);
-
- var telemetryIds = await db.Telemetries
- .Where(t => t.Info != null && t.TimeZone != null)
- .Select(t => t.Id)
- .ToListAsync(token);
-
- var telemetryLastDetectedDates = telemetryIds
- .GroupJoin(lastDetectedDates,
- t => t,
- o => o.IdTelemetry,
- (outer, inner) => new
- {
- IdTelemetry = outer,
- inner.SingleOrDefault()?.LastDate,
- });
-
- foreach (var item in telemetryLastDetectedDates)
- {
- var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
- if (newLimitingParameters?.Any() == true)
- {
- db.LimitingParameter.AddRange(newLimitingParameters);
- await db.SaveChangesAsync(token);
- }
- }
- }
-
- private static async Task> GetLimitingParameterAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
- {
- var query =
- $"select " +
- $"limiting_parameters.date, limiting_parameters.id_feed_regulator, limiting_parameters.well_depth " +
- $"from ( " +
- $"select " +
- $"date, id_feed_regulator, well_depth, " +
- $"lag(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lag, " +
- $"lead(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lead " +
- $"from t_telemetry_data_saub " +
- $"where id_feed_regulator is not null " +
- $"and id_telemetry = {idTelemetry}" +
- $"and date >= '{begin:u}'" +
- $"order by date) as limiting_parameters " +
- $"where id_feed_regulator_lag is null " +
- $"or (id_feed_regulator != id_feed_regulator_lag and id_feed_regulator_lead != id_feed_regulator_lag) " +
- $"order by date;";
-
- var limitingParameters = new List(32);
- using (var result = await ExecuteReaderAsync(db, query, token))
- {
- LimitingParameter? limitingLast = null;
- while (result.Read())
- {
- var date = result.GetFieldValue(0);
- var idLimiting = result.GetFieldValue(1);
- var wellDepth = result.GetFieldValue(2);
-
- if (limitingLast is null)
- {
- limitingLast = new LimitingParameter
- {
- DateStart = date,
- DepthStart = wellDepth,
- IdFeedRegulator = idLimiting
- };
- }
-
- if (limitingLast.IdFeedRegulator != idLimiting || limitingLast.DepthStart < wellDepth)
- {
- limitingParameters.Add(new LimitingParameter {
- IdTelemetry = idTelemetry,
- IdFeedRegulator = limitingLast.IdFeedRegulator,
- DateStart = limitingLast.DateStart,
- DateEnd = date,
- DepthStart = limitingLast.DepthStart,
- DepthEnd = wellDepth
- });
-
- limitingLast = new LimitingParameter
- {
- DateStart = date,
- DepthStart = wellDepth,
- IdFeedRegulator = idLimiting
- };
- }
- }
- }
-
- return limitingParameters;
- }
-
- private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token)
- {
- var connection = db.Database.GetDbConnection();
- if (
- connection?.State is null ||
- connection.State == ConnectionState.Broken ||
- connection.State == ConnectionState.Closed)
- {
- await db.Database.OpenConnectionAsync(token);
- connection = db.Database.GetDbConnection();
- }
- using var command = connection.CreateCommand();
- command.CommandText = query;
-
- var result = await command.ExecuteReaderAsync(token);
- return result;
- }
- }
-
-}
diff --git a/AsbCloudInfrastructure/Services/ReportService.cs b/AsbCloudInfrastructure/Services/ReportService.cs
index eca8f84e..6a66c30e 100644
--- a/AsbCloudInfrastructure/Services/ReportService.cs
+++ b/AsbCloudInfrastructure/Services/ReportService.cs
@@ -51,7 +51,7 @@ namespace AsbCloudInfrastructure.Services
var workId = $"create report by wellid:{idWell} for userid:{idUser} requested at {DateTime.Now}";
- var workAction = async (string id, IServiceProvider serviceProvider, CancellationToken token) =>
+ var workAction = async (string id, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) =>
{
using var context = serviceProvider.GetRequiredService();
var fileService = serviceProvider.GetRequiredService();
@@ -64,7 +64,9 @@ namespace AsbCloudInfrastructure.Services
generator.OnProgress += (s, e) =>
{
- progressHandler.Invoke(e.Adapt(), id);
+ var arg = e.Adapt();
+ onProgress(arg.Operation?? string.Empty, arg.Progress);
+ progressHandler.Invoke(arg, id);
};
generator.Make(reportFileName);
@@ -92,8 +94,8 @@ namespace AsbCloudInfrastructure.Services
context.SaveChanges();
};
- var work = new WorkBase(workId, workAction);
- backgroundWorkerService.Push(work);
+ var work = Work.CreateByDelegate(workId, workAction);
+ backgroundWorkerService.WorkStore.RunOnceQueue.Enqueue(work);
progressHandler.Invoke(new ReportProgressDto
{
diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs
index d1a7fea1..9bb2dc79 100644
--- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs
+++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs
@@ -48,12 +48,12 @@ namespace AsbCloudInfrastructure.Services.SAUB
instance = new TelemetryDataCache();
var worker = provider.GetRequiredService();
var workId = $"Telemetry cache loading from DB {typeof(TEntity).Name}";
- var work = new WorkBase(workId, async (workId, provider, token) => {
+ var work = Work.CreateByDelegate(workId, async (workId, provider, onProgress, token) => {
var db = provider.GetRequiredService();
- await instance.InitializeCacheFromDBAsync(db, token);
+ await instance.InitializeCacheFromDBAsync(db, onProgress, token);
});
- worker.Push(work);
+ worker.WorkStore.RunOnceQueue.Enqueue(work);
}
instance.provider = provider;
return instance;
@@ -150,7 +150,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
return new DatesRangeDto { From = from.Value, To = to };
}
- private async Task InitializeCacheFromDBAsync(IAsbCloudDbContext db, CancellationToken token)
+ private async Task InitializeCacheFromDBAsync(IAsbCloudDbContext db, Action onProgress, CancellationToken token)
where TEntity : class, AsbCloudDb.Model.ITelemetryData
{
if (isLoading)
@@ -159,7 +159,6 @@ namespace AsbCloudInfrastructure.Services.SAUB
isLoading = true;
var defaultTimeout = db.Database.GetCommandTimeout();
- System.Diagnostics.Trace.TraceInformation($"cache loading starting. Setting CommandTimeout 90s ({defaultTimeout})");
db.Database.SetCommandTimeout(TimeSpan.FromSeconds(90));
Well[] wells = await db.Set()
@@ -168,6 +167,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
.Where(well => well.IdTelemetry != null)
.ToArrayAsync(token);
+ var count = wells.Length;
+ var i = 0d;
foreach (Well well in wells)
{
var capacity = well.IdState == 1
@@ -176,21 +177,13 @@ namespace AsbCloudInfrastructure.Services.SAUB
var idTelemetry = well.IdTelemetry!.Value;
var hoursOffset = well.Timezone.Hours;
-
- System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}>: Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}");
+
+ onProgress($"Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}", i++/count);
var cacheItem = await GetOrDefaultCacheDataFromDbAsync(db, idTelemetry, capacity, hoursOffset, token);
if(cacheItem is not null)
- {
caches.TryAdd(idTelemetry, cacheItem);
- System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}> for well: {well.Cluster?.Caption}/{well.Caption} loaded");
- }
- else
- {
- System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}> for well: {well.Cluster?.Caption}/{well.Caption} has no data");
- }
}
- System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}> load complete");
isLoading = false;
db.Database.SetCommandTimeout(defaultTimeout);
}
diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs
deleted file mode 100644
index d5b86803..00000000
--- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs
+++ /dev/null
@@ -1,300 +0,0 @@
-using AsbCloudDb.Model;
-using AsbCloudDb.Model.Subsystems;
-using AsbCloudInfrastructure.Background;
-using AsbCloudInfrastructure.Services.Subsystems.Utils;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Data.Common;
-using System.Diagnostics;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace AsbCloudInfrastructure.Services.Subsystems
-{
- internal static class SubsystemOperationTimeCalcWorkFactory
- {
- private const string workId = "Subsystem operation time calc";
- private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
-
- private const int idSubsytemTorqueMaster = 65537;
- private const int idSubsytemSpinMaster = 65536;
- private const int idSubsystemAPDRotor = 11;
- private const int idSubsystemAPDSlide = 12;
- private const int idSubsytemMse = 2;
-
- public static WorkPeriodic MakeWork()
- {
- var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod)
- {
- Timeout = TimeSpan.FromMinutes(30)
- };
- return workPeriodic;
- }
-
- // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД.
- private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token)
- {
- using var db = serviceProvider.GetRequiredService();
-
- var lastDetectedDates = await db.SubsystemOperationTimes
- .GroupBy(o => o.IdTelemetry)
- .Select(g => new
- {
- IdTelemetry = g.Key,
- LastDate = g.Max(o => o.DateEnd)
- })
- .ToListAsync(token);
-
- var telemetryIds = await db.Telemetries
- .Where(t => t.Info != null && t.TimeZone != null)
- .Select(t => t.Id)
- .ToListAsync(token);
-
- var telemetryLastDetectedDates = telemetryIds
- .GroupJoin(lastDetectedDates,
- t => t,
- o => o.IdTelemetry,
- (outer, inner) => new
- {
- IdTelemetry = outer,
- inner.SingleOrDefault()?.LastDate,
- });
-
- foreach (var item in telemetryLastDetectedDates)
- {
- var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
- if (newOperationsSaub?.Any() == true)
- {
- db.SubsystemOperationTimes.AddRange(newOperationsSaub);
- await db.SaveChangesAsync(token);
- }
- var newOperationsSpin = await OperationTimeSpinAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
- if (newOperationsSpin?.Any() == true)
- {
- db.SubsystemOperationTimes.AddRange(newOperationsSpin);
- await db.SaveChangesAsync(token);
- }
- }
- }
-
- private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token)
- {
- var connection = db.Database.GetDbConnection();
- if (
- connection?.State is null ||
- connection.State == ConnectionState.Broken ||
- connection.State == ConnectionState.Closed)
- {
- await db.Database.OpenConnectionAsync(token);
- connection = db.Database.GetDbConnection();
- }
- using var command = connection.CreateCommand();
- command.CommandText = query;
-
- var result = await command.ExecuteReaderAsync(token);
- return result;
- }
-
- private static async Task> OperationTimeSaubAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
- {
- static bool isSubsytemAkbRotor(short? mode) => mode == 1;
-
- static bool isSubsytemAkbSlide(short? mode) => mode == 3;
-
- static bool IsSubsystemMse(short? state) => (state & 1) > 0;
-
- var query =
- $"select tt.date, tt.mode, tt.well_depth, tt.mse_state " +
- $"from ( " +
- $" select " +
- $" date, " +
- $" mode, " +
- $" mse_state, " +
- $" well_depth, " +
- $" lag(mode,1) over (order by date) as mode_lag, " +
- $" lead(mode,1) over (order by date) as mode_lead " +
- $" from t_telemetry_data_saub " +
- $" where id_telemetry = {idTelemetry} and well_depth is not null and well_depth > 0" +
- $" order by date ) as tt " +
- $"where (tt.mode_lag is null or (tt.mode != tt.mode_lag and tt.mode_lead != tt.mode_lag)) and tt.date >= '{begin:u}' " +
- $"order by tt.date;";
-
- using var result = await ExecuteReaderAsync(db, query, token);
-
- var subsystemsOperationTimes = new List();
- var detectorRotor = new SubsystemDetector(idTelemetry, idSubsystemAPDRotor, isSubsytemAkbRotor, IsValid);
- var detectorSlide = new SubsystemDetector(idTelemetry, idSubsystemAPDSlide, isSubsytemAkbSlide, IsValid);
- var detectorMse = new SubsystemDetector(idTelemetry, idSubsytemMse, IsSubsystemMse, IsValid);
-
- while (result.Read())
- {
- var mode = result.GetFieldValue(1);
- var state = result.GetFieldValue(3);
-
- var isAkbRotorEnable = isSubsytemAkbRotor(mode);
- var isAkbSlideEnable = isSubsytemAkbSlide(mode);
- var isMseEnable = IsSubsystemMse(state);
- var date = result.GetFieldValue(0);
- var depth = result.GetFieldValue(2);
-
- if (detectorRotor.TryDetect(mode, date, depth, out var detectedRotor))
- subsystemsOperationTimes.Add(detectedRotor!);
-
- if (detectorSlide.TryDetect(mode, date, depth, out var detectedSlide))
- subsystemsOperationTimes.Add(detectedSlide!);
-
- if (detectorMse.TryDetect(mode, date, depth, out var detectedMse))
- subsystemsOperationTimes.Add(detectedMse!);
- }
-
- return subsystemsOperationTimes;
- }
-
- private static async Task> OperationTimeSpinAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
- {
- static int? GetSubsytemId(short? mode, int? state)
- {
- // При изменении следующего кода сообщи в Vladimir.Sobolev@nedra.digital
- if (state == 7 && (mode & 2) > 0)
- return idSubsytemTorqueMaster;// демпфер
-
- if (state != 0 && state != 5 && state != 6 && state != 7)
- return idSubsytemSpinMaster;// осцилляция
-
- return null;
- }
-
- var querySpin =
- $"select " +
- $" tspin.date, " +
- $" tspin.mode, " +
- $" tspin.state " +
- $"from ( " +
- $" select " +
- $" date, " +
- $" mode, " +
- $" lag(mode, 1) over (order by date) as mode_lag, " +
- $" lead(mode, 1) over (order by date) as mode_lead, " +
- $" state, " +
- $" lag(state, 1) over (order by date) as state_lag " +
- $" from t_telemetry_data_spin " +
- $" where id_telemetry = {idTelemetry} and date >= '{begin:u}'" +
- $" order by date ) as tspin " +
- $"where mode_lag is null or state_lag is null or (mode != mode_lag and mode_lead != mode_lag) or state != state_lag " +
- $"order by date;";
-
- var rows = new List<(int? IdSubsystem, DateTimeOffset Date)>(32);
-
- using var resultSpin = await ExecuteReaderAsync(db, querySpin, token);
- int? idSubsystemLast = null;
- while (resultSpin.Read())
- {
- var mode = resultSpin.GetFieldValue(1);
- var state = resultSpin.GetFieldValue(2);
- var idSubsystem = GetSubsytemId(mode, state);
- if (idSubsystemLast != idSubsystem)
- {
- idSubsystemLast = idSubsystem;
- var date = resultSpin.GetFieldValue(0);
- rows.Add((idSubsystem, date));
- }
- }
- await resultSpin.DisposeAsync();
-
- if (rows.Count < 2)
- return Enumerable.Empty();
-
- var minSpinDate = rows.Min(i => i.Date);
- var maxSpinDate = rows.Max(i => i.Date);
- var depthInterpolation = await GetInterpolation(db, idTelemetry, minSpinDate, maxSpinDate, token);
-
- if (depthInterpolation is null)
- return Enumerable.Empty();
-
- var subsystemsOperationTimes = new List(32);
-
- for (int i = 1; i < rows.Count; i++)
- {
- var r0 = rows[i - 1];
- var r1 = rows[i];
- if (r0.IdSubsystem is not null && r0.IdSubsystem != r1.IdSubsystem)
- {
- var subsystemOperationTime = new SubsystemOperationTime()
- {
- IdTelemetry = idTelemetry,
- IdSubsystem = r0.IdSubsystem.Value,
- DateStart = r0.Date,
- DateEnd = r1.Date,
- DepthStart = depthInterpolation.GetDepth(r0.Date),
- DepthEnd = depthInterpolation.GetDepth(r1.Date),
- };
-
- if (IsValid(subsystemOperationTime))
- subsystemsOperationTimes.Add(subsystemOperationTime);
- }
- }
-
- return subsystemsOperationTimes;
- }
-
- private static bool IsValid(SubsystemOperationTime item)
- {
- var validateCode = GetValidateErrorCode(item);
- if (validateCode != 0)
- {
- var str = System.Text.Json.JsonSerializer.Serialize(item);
- Trace.TraceWarning($"Wrong({validateCode}) SubsystemOperationTime: {str}");
- }
- return validateCode == 0;
- }
-
- private static int GetValidateErrorCode(SubsystemOperationTime item)
- {
- if (item.DateStart > item.DateEnd)
- return -1;
- if ((item.DateEnd - item.DateStart).TotalHours > 48)
- return -2;
- if (item.DepthEnd < item.DepthStart)
- return -3;
- if (item.DepthEnd - item.DepthStart > 2000d)
- return -4;
- if (item.DepthEnd < 0d)
- return -5;
- if (item.DepthStart < 0d)
- return -6;
- if (item.DepthEnd > 24_0000d)
- return -7;
- if (item.DepthStart > 24_0000d)
- return -8;
- return 0;
- }
-
- private static async Task GetInterpolation(IAsbCloudDbContext db, int idTelemetry, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token)
- {
- var dataDepthFromSaub = await db.TelemetryDataSaub
- .Where(d => d.IdTelemetry == idTelemetry)
- .Where(d => d.DateTime >= dateBegin)
- .Where(d => d.DateTime <= dateEnd)
- .Where(d => d.WellDepth != null)
- .Where(d => d.WellDepth > 0)
- .GroupBy(d => Math.Ceiling(d.WellDepth ?? 0 * 10))
- .Select(g => new {
- DateMin = g.Min(d => d.DateTime),
- DepthMin = g.Min(d => d.WellDepth) ?? 0,
- })
- .OrderBy(i => i.DateMin)
- .ToArrayAsync(token);
-
- if (!dataDepthFromSaub.Any())
- return null;
-
- var depthInterpolation = new DepthInterpolation(dataDepthFromSaub.Select(i => (i.DateMin, i.DepthMin)));
- return depthInterpolation;
- }
- }
-
-}
diff --git a/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs b/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs
new file mode 100644
index 00000000..5b9d00f8
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs
@@ -0,0 +1,295 @@
+using AsbCloudDb.Model;
+using AsbCloudDb.Model.Subsystems;
+using AsbCloudInfrastructure.Background;
+using AsbCloudInfrastructure.Services.Subsystems.Utils;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AsbCloudInfrastructure.Services.Subsystems;
+
+public class WorkSubsystemOperationTimeCalc : Work
+{
+ private const int idSubsytemTorqueMaster = 65537;
+ private const int idSubsytemSpinMaster = 65536;
+ private const int idSubsystemAPDRotor = 11;
+ private const int idSubsystemAPDSlide = 12;
+ private const int idSubsytemMse = 2;
+
+ public WorkSubsystemOperationTimeCalc()
+ : base("Subsystem operation time calc")
+ {
+ Timeout = TimeSpan.FromMinutes(20);
+ }
+
+ protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token)
+ {
+ using var db = services.GetRequiredService();
+ db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
+ var lastDetectedDates = await db.SubsystemOperationTimes
+ .GroupBy(o => o.IdTelemetry)
+ .Select(g => new
+ {
+ IdTelemetry = g.Key,
+ LastDate = g.Max(o => o.DateEnd)
+ })
+ .ToListAsync(token);
+
+ var telemetryIds = await db.Telemetries
+ .Where(t => t.Info != null && t.TimeZone != null)
+ .Select(t => t.Id)
+ .ToListAsync(token);
+
+ var telemetryLastDetectedDates = telemetryIds
+ .GroupJoin(lastDetectedDates,
+ t => t,
+ o => o.IdTelemetry,
+ (outer, inner) => new
+ {
+ IdTelemetry = outer,
+ inner.SingleOrDefault()?.LastDate,
+ });
+
+ var count = telemetryLastDetectedDates.Count();
+ var i = 0d;
+ foreach (var item in telemetryLastDetectedDates)
+ {
+ onProgressCallback($"Start handling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count);
+ var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
+ if (newOperationsSaub?.Any() == true)
+ {
+ db.SubsystemOperationTimes.AddRange(newOperationsSaub);
+ await db.SaveChangesAsync(token);
+ }
+ var newOperationsSpin = await OperationTimeSpinAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
+ if (newOperationsSpin?.Any() == true)
+ {
+ db.SubsystemOperationTimes.AddRange(newOperationsSpin);
+ await db.SaveChangesAsync(token);
+ }
+ }
+ }
+
+ private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token)
+ {
+ var connection = db.Database.GetDbConnection();
+ if (
+ connection?.State is null ||
+ connection.State == ConnectionState.Broken ||
+ connection.State == ConnectionState.Closed)
+ {
+ await db.Database.OpenConnectionAsync(token);
+ connection = db.Database.GetDbConnection();
+ }
+ using var command = connection.CreateCommand();
+ command.CommandText = query;
+
+ var result = await command.ExecuteReaderAsync(token);
+ return result;
+ }
+
+ private static async Task> OperationTimeSaubAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
+ {
+ static bool isSubsytemAkbRotor(short? mode) => mode == 1;
+
+ static bool isSubsytemAkbSlide(short? mode) => mode == 3;
+
+ static bool IsSubsystemMse(short? state) => (state & 1) > 0;
+
+ var query =
+ $"select tt.date, tt.mode, tt.well_depth, tt.mse_state " +
+ $"from ( " +
+ $" select " +
+ $" date, " +
+ $" mode, " +
+ $" mse_state, " +
+ $" well_depth, " +
+ $" lag(mode,1) over (order by date) as mode_lag, " +
+ $" lead(mode,1) over (order by date) as mode_lead " +
+ $" from t_telemetry_data_saub " +
+ $" where id_telemetry = {idTelemetry} and well_depth is not null and well_depth > 0 " +
+ $" order by date ) as tt " +
+ $"where (tt.mode_lag is null or (tt.mode != tt.mode_lag and tt.mode_lead != tt.mode_lag)) and tt.date >= '{begin:u}' " +
+ $"order by tt.date;";
+
+ using var result = await ExecuteReaderAsync(db, query, token);
+
+ var subsystemsOperationTimes = new List();
+ var detectorRotor = new SubsystemDetector(idTelemetry, idSubsystemAPDRotor, isSubsytemAkbRotor, IsValid);
+ var detectorSlide = new SubsystemDetector(idTelemetry, idSubsystemAPDSlide, isSubsytemAkbSlide, IsValid);
+ var detectorMse = new SubsystemDetector(idTelemetry, idSubsytemMse, IsSubsystemMse, IsValid);
+
+ while (result.Read())
+ {
+ var mode = result.GetFieldValue(1);
+ var state = result.GetFieldValue(3);
+
+ var isAkbRotorEnable = isSubsytemAkbRotor(mode);
+ var isAkbSlideEnable = isSubsytemAkbSlide(mode);
+ var isMseEnable = IsSubsystemMse(state);
+ var date = result.GetFieldValue(0);
+ var depth = result.GetFieldValue(2);
+
+ if (detectorRotor.TryDetect(mode, date, depth, out var detectedRotor))
+ subsystemsOperationTimes.Add(detectedRotor!);
+
+ if (detectorSlide.TryDetect(mode, date, depth, out var detectedSlide))
+ subsystemsOperationTimes.Add(detectedSlide!);
+
+ if (detectorMse.TryDetect(mode, date, depth, out var detectedMse))
+ subsystemsOperationTimes.Add(detectedMse!);
+ }
+
+ return subsystemsOperationTimes;
+ }
+
+ private static async Task> OperationTimeSpinAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
+ {
+ static int? GetSubsytemId(short? mode, int? state)
+ {
+ // При изменении следующего кода сообщи в Vladimir.Sobolev@nedra.digital
+ if (state == 7 && (mode & 2) > 0)
+ return idSubsytemTorqueMaster;// демпфер
+
+ if (state != 0 && state != 5 && state != 6 && state != 7)
+ return idSubsytemSpinMaster;// осцилляция
+
+ return null;
+ }
+
+ var querySpin =
+ $"select " +
+ $" tspin.date, " +
+ $" tspin.mode, " +
+ $" tspin.state " +
+ $"from ( " +
+ $" select " +
+ $" date, " +
+ $" mode, " +
+ $" lag(mode, 1) over (order by date) as mode_lag, " +
+ $" lead(mode, 1) over (order by date) as mode_lead, " +
+ $" state, " +
+ $" lag(state, 1) over (order by date) as state_lag " +
+ $" from t_telemetry_data_spin " +
+ $" where id_telemetry = {idTelemetry} and date >= '{begin:u}'" +
+ $" order by date ) as tspin " +
+ $"where mode_lag is null or state_lag is null or (mode != mode_lag and mode_lead != mode_lag) or state != state_lag " +
+ $"order by date;";
+
+ var rows = new List<(int? IdSubsystem, DateTimeOffset Date)>(32);
+
+ using var resultSpin = await ExecuteReaderAsync(db, querySpin, token);
+ int? idSubsystemLast = null;
+ while (resultSpin.Read())
+ {
+ var mode = resultSpin.GetFieldValue(1);
+ var state = resultSpin.GetFieldValue(2);
+ var idSubsystem = GetSubsytemId(mode, state);
+ if (idSubsystemLast != idSubsystem)
+ {
+ idSubsystemLast = idSubsystem;
+ var date = resultSpin.GetFieldValue(0);
+ rows.Add((idSubsystem, date));
+ }
+ }
+ await resultSpin.DisposeAsync();
+
+ if (rows.Count < 2)
+ return Enumerable.Empty();
+
+ var minSpinDate = rows.Min(i => i.Date);
+ var maxSpinDate = rows.Max(i => i.Date);
+ var depthInterpolation = await GetInterpolation(db, idTelemetry, minSpinDate, maxSpinDate, token);
+
+ if (depthInterpolation is null)
+ return Enumerable.Empty();
+
+ var subsystemsOperationTimes = new List(32);
+
+ for (int i = 1; i < rows.Count; i++)
+ {
+ var r0 = rows[i - 1];
+ var r1 = rows[i];
+ if (r0.IdSubsystem is not null && r0.IdSubsystem != r1.IdSubsystem)
+ {
+ var subsystemOperationTime = new SubsystemOperationTime()
+ {
+ IdTelemetry = idTelemetry,
+ IdSubsystem = r0.IdSubsystem.Value,
+ DateStart = r0.Date,
+ DateEnd = r1.Date,
+ DepthStart = depthInterpolation.GetDepth(r0.Date),
+ DepthEnd = depthInterpolation.GetDepth(r1.Date),
+ };
+
+ if (IsValid(subsystemOperationTime))
+ subsystemsOperationTimes.Add(subsystemOperationTime);
+ }
+ }
+
+ return subsystemsOperationTimes;
+ }
+
+ private static bool IsValid(SubsystemOperationTime item)
+ {
+ var validateCode = GetValidateErrorCode(item);
+ if (validateCode != 0)
+ {
+ var str = System.Text.Json.JsonSerializer.Serialize(item);
+ Trace.TraceWarning($"Wrong({validateCode}) SubsystemOperationTime: {str}");
+ }
+ return validateCode == 0;
+ }
+
+ private static int GetValidateErrorCode(SubsystemOperationTime item)
+ {
+ if (item.DateStart > item.DateEnd)
+ return -1;
+ if ((item.DateEnd - item.DateStart).TotalHours > 48)
+ return -2;
+ if (item.DepthEnd < item.DepthStart)
+ return -3;
+ if (item.DepthEnd - item.DepthStart > 2000d)
+ return -4;
+ if (item.DepthEnd < 0d)
+ return -5;
+ if (item.DepthStart < 0d)
+ return -6;
+ if (item.DepthEnd > 24_0000d)
+ return -7;
+ if (item.DepthStart > 24_0000d)
+ return -8;
+ return 0;
+ }
+
+ private static async Task GetInterpolation(IAsbCloudDbContext db, int idTelemetry, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token)
+ {
+ var dataDepthFromSaub = await db.TelemetryDataSaub
+ .Where(d => d.IdTelemetry == idTelemetry)
+ .Where(d => d.DateTime >= dateBegin)
+ .Where(d => d.DateTime <= dateEnd)
+ .Where(d => d.WellDepth != null)
+ .Where(d => d.WellDepth > 0)
+ .GroupBy(d => Math.Ceiling(d.WellDepth ?? 0 * 10))
+ .Select(g => new
+ {
+ DateMin = g.Min(d => d.DateTime),
+ DepthMin = g.Min(d => d.WellDepth) ?? 0,
+ })
+ .OrderBy(i => i.DateMin)
+ .ToArrayAsync(token);
+
+ if (!dataDepthFromSaub.Any())
+ return null;
+
+ var depthInterpolation = new DepthInterpolation(dataDepthFromSaub.Select(i => (i.DateMin, i.DepthMin)));
+ return depthInterpolation;
+ }
+}
diff --git a/AsbCloudInfrastructure/Services/WellInfoService.cs b/AsbCloudInfrastructure/Services/WellInfoService.cs
index eea573cc..bbaba879 100644
--- a/AsbCloudInfrastructure/Services/WellInfoService.cs
+++ b/AsbCloudInfrastructure/Services/WellInfoService.cs
@@ -18,62 +18,31 @@ using System.Threading.Tasks;
using AsbCloudApp.IntegrationEvents;
using AsbCloudApp.IntegrationEvents.Interfaces;
-namespace AsbCloudInfrastructure.Services
+namespace AsbCloudInfrastructure.Services;
+
+public class WellInfoService
{
- public class WellInfoService
+ public class WorkWellInfoUpdate : Work
{
- class WellMapInfoWithComanies : WellMapInfoDto
+ public WorkWellInfoUpdate()
+ : base("Well statistics update")
{
- public int? IdTelemetry { get; set; }
- public IEnumerable IdsCompanies { get; set; } = null!;
+ Timeout = TimeSpan.FromMinutes(30);
}
- private const string workId = "Well statistics update";
- private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
-
- private readonly TelemetryDataCache telemetryDataSaubCache;
- private readonly TelemetryDataCache telemetryDataSpinCache;
- private readonly IWitsRecordRepository witsRecord7Repository;
- private readonly IWitsRecordRepository witsRecord1Repository;
- private readonly IGtrRepository gtrRepository;
- private static IEnumerable WellMapInfo = Enumerable.Empty();
-
- public WellInfoService(
- TelemetryDataCache telemetryDataSaubCache,
- TelemetryDataCache telemetryDataSpinCache,
- IWitsRecordRepository witsRecord7Repository,
- IWitsRecordRepository witsRecord1Repository,
- IGtrRepository gtrRepository)
+ protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token)
{
- this.telemetryDataSaubCache = telemetryDataSaubCache;
- this.telemetryDataSpinCache = telemetryDataSpinCache;
-
- this.witsRecord7Repository = witsRecord7Repository;
- this.witsRecord1Repository = witsRecord1Repository;
- this.gtrRepository = gtrRepository;
- }
-
- public static WorkPeriodic MakeWork()
- {
- var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod)
- {
- Timeout = TimeSpan.FromMinutes(30)
- };
- return workPeriodic;
- }
-
- private static async Task WorkAction(string workName, IServiceProvider serviceProvider, CancellationToken token)
- {
- var wellService = serviceProvider.GetRequiredService();
- var operationsStatService = serviceProvider.GetRequiredService();
- var processMapRepository = serviceProvider.GetRequiredService();
- var subsystemOperationTimeService = serviceProvider.GetRequiredService();
- var telemetryDataSaubCache = serviceProvider.GetRequiredService>();
- var messageHub = serviceProvider.GetRequiredService>();
+ var wellService = services.GetRequiredService();
+ var operationsStatService = services.GetRequiredService();
+ var processMapRepository = services.GetRequiredService();
+ var subsystemOperationTimeService = services.GetRequiredService();
+ var telemetryDataSaubCache = services.GetRequiredService>();
+ var messageHub = services.GetRequiredService>();
var wells = await wellService.GetAllAsync(token);
+ var activeWells = wells.Where(well => well.IdState == 1);
- var wellsIds = wells.Select(w => w.Id);
+ var wellsIds = activeWells.Select(w => w.Id);
var processMapRequests = wellsIds.Select(id => new ProcessMapRequest { IdWell = id });
var processMaps = await processMapRepository.GetProcessMapAsync(processMapRequests, token);
@@ -87,29 +56,32 @@ namespace AsbCloudInfrastructure.Services
});
var operationsStat = await operationsStatService.GetWellsStatAsync(wellsIds, token);
-
+
var subsystemStat = await subsystemOperationTimeService
.GetStatByActiveWells(wellsIds, token);
+ subsystemStat = subsystemStat.ToArray();
- WellMapInfo = wells.Select(well => {
+ var count = activeWells.Count();
+ var i = 0d;
+ WellMapInfo = activeWells.Select(well => {
var wellMapInfo = well.Adapt();
wellMapInfo.IdState = well.IdState;
-
+ onProgressCallback($"Start updating info by well({well.Id}): {well.Caption}", i++ / count);
double? currentDepth = null;
TelemetryDataSaubDto? lastSaubTelemetry = null;
-
+
if (well.IdTelemetry.HasValue)
{
wellMapInfo.IdTelemetry = well.IdTelemetry.Value;
lastSaubTelemetry = telemetryDataSaubCache.GetLastOrDefault(well.IdTelemetry.Value);
- if(lastSaubTelemetry is not null)
+ if (lastSaubTelemetry is not null)
{
currentDepth = lastSaubTelemetry.WellDepth;
}
}
- var wellOperationsStat = operationsStat.FirstOrDefault(s => s.Id == well.Id);
+ var wellOperationsStat = operationsStat.FirstOrDefault(s => s.Id == well.Id);
var wellLastFactSection = wellOperationsStat?.Sections.LastOrDefault(s => s.Fact is not null);
currentDepth ??= wellLastFactSection?.Fact?.WellDepthEnd;
@@ -124,7 +96,7 @@ namespace AsbCloudInfrastructure.Services
{
wellProcessMap = wellProcessMaps.FirstOrDefault(p => p.IdWellSectionType == idSection);
}
- else if(currentDepth.HasValue)
+ else if (currentDepth.HasValue)
{
wellProcessMap = wellProcessMaps.FirstOrDefault(p => p.DepthStart <= currentDepth.Value && p.DepthEnd >= currentDepth.Value);
}
@@ -134,8 +106,8 @@ namespace AsbCloudInfrastructure.Services
planTotalDepth ??= wellOperationsStat?.Total.Plan?.WellDepthEnd;
wellMapInfo.Section = wellLastFactSection?.Caption;
-
- wellMapInfo.FirstFactOperationDateStart = wellOperationsStat?.Total.Fact?.Start
+
+ wellMapInfo.FirstFactOperationDateStart = wellOperationsStat?.Total.Fact?.Start
?? wellOperationsStat?.Total.Plan?.Start;
wellMapInfo.LastPredictOperationDateEnd = wellOperationsStat?.Total.Plan?.End;
@@ -163,7 +135,7 @@ namespace AsbCloudInfrastructure.Services
Plan = wellProcessMap?.Pressure.Plan,
Fact = lastSaubTelemetry?.Pressure
};
-
+
wellMapInfo.PressureSp = lastSaubTelemetry?.PressureSp;
wellMapInfo.WellDepth = new()
@@ -195,51 +167,79 @@ namespace AsbCloudInfrastructure.Services
return wellMapInfo;
}).ToArray();
- var updateWellInfoEventTasks = wellsIds.Select(idWell =>
+ var updateWellInfoEventTasks = wellsIds.Select(idWell =>
messageHub.HandleAsync(new UpdateWellInfoEvent(idWell), token));
-
+
await Task.WhenAll(updateWellInfoEventTasks);
}
+ }
- private WellMapInfoWithTelemetryStat Convert(WellMapInfoWithComanies wellInfo)
+ class WellMapInfoWithComanies : WellMapInfoDto
+ {
+ public int? IdTelemetry { get; set; }
+ public IEnumerable IdsCompanies { get; set; } = null!;
+ }
+
+ private readonly TelemetryDataCache telemetryDataSaubCache;
+ private readonly TelemetryDataCache telemetryDataSpinCache;
+ private readonly IWitsRecordRepository witsRecord7Repository;
+ private readonly IWitsRecordRepository witsRecord1Repository;
+ private readonly IGtrRepository gtrRepository;
+ private static IEnumerable WellMapInfo = Enumerable.Empty();
+
+ public WellInfoService(
+ TelemetryDataCache telemetryDataSaubCache,
+ TelemetryDataCache telemetryDataSpinCache,
+ IWitsRecordRepository witsRecord7Repository,
+ IWitsRecordRepository witsRecord1Repository,
+ IGtrRepository gtrRepository)
+ {
+ this.telemetryDataSaubCache = telemetryDataSaubCache;
+ this.telemetryDataSpinCache = telemetryDataSpinCache;
+
+ this.witsRecord7Repository = witsRecord7Repository;
+ this.witsRecord1Repository = witsRecord1Repository;
+ this.gtrRepository = gtrRepository;
+ }
+
+ private WellMapInfoWithTelemetryStat Convert(WellMapInfoWithComanies wellInfo)
+ {
+ var result = wellInfo.Adapt();
+ if (wellInfo.IdTelemetry.HasValue)
{
- var result = wellInfo.Adapt();
- if (wellInfo.IdTelemetry.HasValue)
- {
- var idTelemetry = wellInfo.IdTelemetry.Value;
- result.LastDataSaub = telemetryDataSaubCache.GetLastOrDefault(idTelemetry);
- result.LastDataSpin = telemetryDataSpinCache.GetLastOrDefault(idTelemetry);
- result.LastDataDdsDate = GetLastOrDefaultDdsTelemetry(idTelemetry);
- result.LastDataGtrDate = gtrRepository.GetLastData(wellInfo.Id)
- .MaxOrDefault(item => item.Date);
- result.LastDataDpcsDate = null;
- result.LastDataDpcsDate = null;
- }
-
- return result;
+ var idTelemetry = wellInfo.IdTelemetry.Value;
+ result.LastDataSaub = telemetryDataSaubCache.GetLastOrDefault(idTelemetry);
+ result.LastDataSpin = telemetryDataSpinCache.GetLastOrDefault(idTelemetry);
+ result.LastDataDdsDate = GetLastOrDefaultDdsTelemetry(idTelemetry);
+ result.LastDataGtrDate = gtrRepository.GetLastData(wellInfo.Id)
+ .MaxOrDefault(item => item.Date);
+ result.LastDataDpcsDate = null;
+ result.LastDataDpcsDate = null;
}
- private DateTime? GetLastOrDefaultDdsTelemetry(int idTelemetry)
- {
- var lastDdsRecord1Date = witsRecord1Repository.GetLastOrDefault(idTelemetry)?.DateTime;
- var lastDdsRecord7Date = witsRecord7Repository.GetLastOrDefault(idTelemetry)?.DateTime;
+ return result;
+ }
- if (lastDdsRecord1Date.HasValue && lastDdsRecord7Date.HasValue)
- if (lastDdsRecord1Date.Value > lastDdsRecord7Date.Value)
- return lastDdsRecord1Date.Value;
- else
- return lastDdsRecord7Date.Value;
+ private DateTime? GetLastOrDefaultDdsTelemetry(int idTelemetry)
+ {
+ var lastDdsRecord1Date = witsRecord1Repository.GetLastOrDefault(idTelemetry)?.DateTime;
+ var lastDdsRecord7Date = witsRecord7Repository.GetLastOrDefault(idTelemetry)?.DateTime;
- return lastDdsRecord1Date ?? lastDdsRecord7Date;
- }
+ if (lastDdsRecord1Date.HasValue && lastDdsRecord7Date.HasValue)
+ if (lastDdsRecord1Date.Value > lastDdsRecord7Date.Value)
+ return lastDdsRecord1Date.Value;
+ else
+ return lastDdsRecord7Date.Value;
- public WellMapInfoWithTelemetryStat? FirstOrDefault(Func predicate)
- {
- var first = WellMapInfo.FirstOrDefault(predicate);
- if (first is WellMapInfoWithComanies wellMapInfoWithComanies)
- return Convert(wellMapInfoWithComanies);
+ return lastDdsRecord1Date ?? lastDdsRecord7Date;
+ }
- return null;
- }
+ public WellMapInfoWithTelemetryStat? FirstOrDefault(Func predicate)
+ {
+ var first = WellMapInfo.FirstOrDefault(predicate);
+ if (first is WellMapInfoWithComanies wellMapInfoWithComanies)
+ return Convert(wellMapInfoWithComanies);
+
+ return null;
}
}
diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs
index c1c3cc8f..9aff9ca9 100644
--- a/AsbCloudInfrastructure/Startup.cs
+++ b/AsbCloudInfrastructure/Startup.cs
@@ -1,7 +1,6 @@
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.DetectOperations;
-using AsbCloudInfrastructure.Services.Subsystems;
using AsbCloudInfrastructure.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
@@ -12,10 +11,10 @@ using System.Threading;
using AsbCloudInfrastructure.Background;
using AsbCloudApp.Data.SAUB;
using AsbCloudInfrastructure.Services.SAUB;
+using AsbCloudInfrastructure.Services.Subsystems;
namespace AsbCloudInfrastructure
{
-
public class Startup
{
public static void BeforeRunHandler(IHost host)
@@ -24,7 +23,7 @@ namespace AsbCloudInfrastructure
var provider = scope.ServiceProvider;
var context = provider.GetRequiredService();
- context.Database.SetCommandTimeout(TimeSpan.FromSeconds(2 * 60));
+ context.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
context.Database.Migrate();
// TODO: Сделать инициализацию кеша телеметрии более явной.
@@ -32,11 +31,11 @@ namespace AsbCloudInfrastructure
_ = provider.GetRequiredService>();
var backgroundWorker = provider.GetRequiredService();
- backgroundWorker.Push(WellInfoService.MakeWork());
- backgroundWorker.Push(OperationDetectionWorkFactory.MakeWork());
- backgroundWorker.Push(SubsystemOperationTimeCalcWorkFactory.MakeWork());
- backgroundWorker.Push(LimitingParameterCalcWorkFactory.MakeWork());
- backgroundWorker.Push(MakeMemoryMonitoringWork());
+ backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30));
+ backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(15));
+ backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30));
+ backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30));
+ backgroundWorker.WorkStore.AddPeriodic(MakeMemoryMonitoringWork(), TimeSpan.FromMinutes(1));
var notificationBackgroundWorker = provider.GetRequiredService();
@@ -48,17 +47,15 @@ namespace AsbCloudInfrastructure
});
}
- static WorkPeriodic MakeMemoryMonitoringWork()
+ static Work MakeMemoryMonitoringWork()
{
- var workId = "Memory monitoring";
- var workAction = (string _, IServiceProvider _, CancellationToken _) => {
+ var workAction = (string _, IServiceProvider _, Action _, CancellationToken _) => {
var bytes = GC.GetTotalMemory(false);
var bytesString = FromatBytes(bytes);
System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes. DbContext count is:{AsbCloudDbContext.ReferenceCount}");
return Task.CompletedTask;
};
- var workPeriod = TimeSpan.FromMinutes(1);
- var work = new WorkPeriodic(workId, workAction, workPeriod);
+ var work = Work.CreateByDelegate("Memory monitoring", workAction);
return work;
}
diff --git a/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs b/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs
index 96556845..b118e179 100644
--- a/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs
+++ b/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs
@@ -1,4 +1,6 @@
-using AsbCloudApp.Data.SAUB;
+using AsbCloudApp.Data;
+using AsbCloudApp.Data.SAUB;
+using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
@@ -35,12 +37,22 @@ namespace AsbCloudWebApi.Tests.Middlware
public class TelemetryDataSaubService : ITelemetryDataSaubService
{
- public async Task?> GetAsync(int idWell, DateTime dateBegin = default, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default)
+ public async Task> GetAsync(int idWell, DateTime dateBegin = default, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default)
{
await Task.Delay(1000, token);
return Enumerable.Empty();
}
+ public Task> GetAsync(int idWell, TelemetryDataRequest request, CancellationToken token)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetRangeAsync(int idWell, DateTimeOffset start, DateTimeOffset end, CancellationToken token)
+ {
+ throw new NotImplementedException();
+ }
+
public Task> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token) => throw new NotImplementedException();
public Task GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token)
diff --git a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs
index 133f6b36..f4a03f2f 100644
--- a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs
+++ b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs
@@ -88,7 +88,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
await BackgroundWorker.StartAsync(CancellationToken.None);
await Task.Delay(10);
- Assert.True(work.ExecutionTime > TimeSpan.Zero);
+ Assert.True(work.LastExecutionTime > TimeSpan.Zero);
}
[Fact]
diff --git a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs
index 12bc81a2..54f5d19c 100644
--- a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs
+++ b/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs
@@ -366,7 +366,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);
Assert.Equal(2, state.IdState);
- backgroundWorkerMock.Verify(s => s.Push(It.IsAny()));
+ backgroundWorkerMock.Verify(s => s.Push(It.IsAny()));
}
[Fact]
diff --git a/AsbCloudWebApi/Controllers/BackgroundWorkController.cs b/AsbCloudWebApi/Controllers/BackgroundWorkController.cs
new file mode 100644
index 00000000..a0bf6560
--- /dev/null
+++ b/AsbCloudWebApi/Controllers/BackgroundWorkController.cs
@@ -0,0 +1,50 @@
+using AsbCloudApp.Data;
+using AsbCloudInfrastructure.Background;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using System.Linq;
+
+namespace AsbCloudWebApi.Controllers
+{
+ [Route("api/[controller]")]
+ [Authorize]
+ [ApiController]
+ public class BackgroundWorkController : ControllerBase
+ {
+ private readonly BackgroundWorker backgroundWorker;
+
+ public BackgroundWorkController(BackgroundWorker backgroundWorker)
+ {
+ this.backgroundWorker = backgroundWorker;
+ }
+
+ [HttpGet]
+ public IActionResult GetAll()
+ {
+ var result = new {
+ CurrentWork = (BackgroundWorkDto?)backgroundWorker.CurrentWork,
+ RunOnceQueue = backgroundWorker.WorkStore.RunOnceQueue.Select(work => (BackgroundWorkDto)work),
+ Periodics = backgroundWorker.WorkStore.Periodics.Select(work => (BackgroundWorkDto)work.Work),
+ Felled = backgroundWorker.WorkStore.Felled.Select(work => (BackgroundWorkDto)work),
+ };
+ return Ok(result);
+ }
+
+ [HttpGet("Current")]
+ public IActionResult GetCurrent()
+ {
+ var work = backgroundWorker.CurrentWork;
+ if (work == null)
+ return NoContent();
+
+ return Ok(work);
+ }
+
+ [HttpGet("Failed")]
+ public IActionResult GetFelled()
+ {
+ var result = backgroundWorker.WorkStore.Felled.Select(work => (BackgroundWorkDto)work);
+ return Ok(result);
+ }
+ }
+}
diff --git a/AsbCloudWebApi/Controllers/MockController.cs b/AsbCloudWebApi/Controllers/MockController.cs
new file mode 100644
index 00000000..0c95725e
--- /dev/null
+++ b/AsbCloudWebApi/Controllers/MockController.cs
@@ -0,0 +1,72 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+
+namespace AsbCloudWebApi.Controllers
+{
+ ///
+ /// Имитирует разные типы ответа сервера
+ ///
+ [Route("api/[controller]")]
+ [ApiController]
+ public class MockController : ControllerBase
+ {
+ ///
+ /// имитирует http-400
+ ///
+ [HttpGet("400")]
+ [ProducesResponseType(typeof(ValidationProblemDetails), (int)System.Net.HttpStatusCode.BadRequest)]
+ public IActionResult Get400([FromQuery, Required]IDictionary args)
+ {
+ var errors = new Dictionary();
+
+ foreach (var arg in args)
+ {
+ var countOfErrors = ((arg.Key + arg.Value).Length % 3) + 1;
+ var errorsText = Enumerable.Range(0, countOfErrors)
+ .Select(i => $"{arg.Value} не соответствует критериям проверки № {i}");
+
+ errors.Add(arg.Key, errorsText.ToArray());
+ }
+
+ if (errors.Any())
+ {
+ var problem = new ValidationProblemDetails(errors);
+ return BadRequest(problem);
+ }
+ else
+ {
+ var problem = new ValidationProblemDetails { Detail = "at least one argument must be provided" };
+ return BadRequest(problem);
+ }
+ }
+
+ ///
+ /// имитирует http-403
+ ///
+ [HttpGet("403")]
+ public IActionResult Get403()
+ {
+ return Forbid();
+ }
+
+ ///
+ /// имитирует http-401
+ ///
+ [HttpGet("401")]
+ public IActionResult Get401()
+ {
+ return Unauthorized();
+ }
+
+ ///
+ /// имитирует http-500
+ ///
+ [HttpGet("500")]
+ public IActionResult Get500()
+ {
+ throw new System.Exception("Это тестовое исключение");
+ }
+ }
+}
diff --git a/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs b/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs
index 39d60ea9..efa60638 100644
--- a/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs
+++ b/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs
@@ -1,11 +1,9 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.Subsystems;
-using AsbCloudApp.Exceptions;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudApp.Services.Subsystems;
using AsbCloudDb.Model;
-using AsbCloudInfrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
@@ -56,7 +54,6 @@ namespace AsbCloudWebApi.Controllers.Subsystems
{
if (!await UserHasAccesToWellAsync(request.IdWell, token))
return Forbid();
- await CustomValidate(request, token);
var subsystemResult = await subsystemOperationTimeService.GetStatAsync(request, token);
return Ok(subsystemResult);
}
@@ -133,7 +130,6 @@ namespace AsbCloudWebApi.Controllers.Subsystems
{
if (!await UserHasAccesToWellAsync(request.IdWell, token))
return Forbid();
- await CustomValidate(request, token);
var result = await subsystemOperationTimeService.GetOperationTimeAsync(request, token);
return Ok(result);
@@ -155,7 +151,6 @@ namespace AsbCloudWebApi.Controllers.Subsystems
{
if (!await UserHasAccesToWellAsync(request.IdWell, token))
return Forbid();
- await CustomValidate(request, token);
var result = await subsystemOperationTimeService.DeleteAsync(request, token);
return Ok(result);
}
@@ -180,24 +175,5 @@ namespace AsbCloudWebApi.Controllers.Subsystems
return true;
return false;
}
-
- ///
- /// Валидирует запрос и бросает исключение ArgumentInvalidException
- ///
- ///
- ///
- ///
- ///
- private async Task CustomValidate(SubsystemOperationTimeRequest request, CancellationToken token)
- {
- var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
- if (well is not null && request.LtDate.HasValue)
- {
- var ltDate = request.LtDate.Value;
- var utcDateRequest = ltDate.ToUtcDateTimeOffset(well.Timezone.Hours);
- if (utcDateRequest.AddHours(2) > DateTime.UtcNow)
- throw new ArgumentInvalidException(nameof(request.LtDate), "Запрашиваемый диапазон должен заканчиваться за 2 часа до текущего времени");
- }
- }
}
}
diff --git a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs
index b418c0b5..962e5e0f 100644
--- a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs
+++ b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs
@@ -29,19 +29,19 @@ public class SignalRNotificationTransportService : INotificationTransportService
{
var workId = HashCode.Combine(notifications.Select(n => n.Id)).ToString("x");
- if (backgroundWorker.Contains(workId))
+ if (backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId))
return Task.CompletedTask;
var workAction = MakeSignalRSendWorkAction(notifications);
- var work = new WorkBase(workId, workAction);
- backgroundWorker.Push(work);
+ var work = Work.CreateByDelegate(workId, workAction);
+ backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
return Task.CompletedTask;
}
- private Func MakeSignalRSendWorkAction(IEnumerable notifications)
+ private Func, CancellationToken, Task> MakeSignalRSendWorkAction(IEnumerable notifications)
{
- return async (_, serviceProvider, cancellationToken) =>
+ return async (_, serviceProvider, onProgress, cancellationToken) =>
{
var notificationPublisher = serviceProvider.GetRequiredService();
diff --git a/ConsoleApp1/DebugWellOperationsStatService.cs b/ConsoleApp1/DebugWellOperationsStatService.cs
deleted file mode 100644
index acb6452c..00000000
--- a/ConsoleApp1/DebugWellOperationsStatService.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using AsbCloudApp.Data;
-using System;
-using System.Collections.Generic;
-
-namespace ConsoleApp1
-{
- public static class DebugWellOperationsStatService
- {
- public static void Main(/*string[] args*/)
- {
- //var options = new DbContextOptionsBuilder()
- // .UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True")
- // .Options;
- //using var db = new AsbCloudDbContext(options);
- //var cacheDb = new CacheDb();
- //var telemetryService = new TelemetryService(db, new TelemetryTracker(cacheDb), cacheDb);
- //var wellService = new WellService(db, telemetryService, cacheDb);
- //var wellOptsStat = new OperationsStatService(db, cacheDb, wellService);
- //var tvd = wellOptsStat.GetTvdAsync(1, default).Result;
- //Print(tvd);
- }
-
- private static void Print(IEnumerable> tvd)
- {
- Console.WriteLine("|\tplan\t|\tfact\t|\tprog\t|");
- Console.WriteLine("|:-------------:|:-------------:|:-------------:|");
- foreach (var item in tvd)
- Print(item);
- }
-
- private static void Print(PlanFactPredictBase item)
- {
- static string GetText(WellOperationDto item)
- => (item is null)
- ? " --------- "
- : $"{item.IdCategory} d:{item.DepthStart} ";
- Console.WriteLine($"|\t{GetText(item.Plan)}\t|\t{GetText(item.Fact)}\t|\t{GetText(item.Predict)}\t|");
- }
- }
-}