Try your search with a different keyword or use * as a wildcard.
using Microsoft.Extensions.DependencyInjection;
using Nop.Core;
using Nop.Core.Configuration;
using Nop.Core.Domain.ScheduleTasks;
using Nop.Core.Http;
using Nop.Core.Infrastructure;
using Nop.Data;
using Nop.Services.Localization;
using Nop.Services.Logging;
namespace Nop.Services.ScheduleTasks;
///
/// Represents task manager
///
public partial class TaskScheduler : ITaskScheduler
{
#region Fields
protected static readonly List _taskThreads = new();
protected readonly AppSettings _appSettings;
private readonly IServiceScopeFactory _serviceScopeFactory;
#endregion
#region Ctor
public TaskScheduler(AppSettings appSettings,
IHttpClientFactory httpClientFactory,
IServiceScopeFactory serviceScopeFactory)
{
_appSettings = appSettings;
_serviceScopeFactory = serviceScopeFactory;
TaskThread.HttpClientFactory = httpClientFactory;
TaskThread.ServiceScopeFactory = serviceScopeFactory;
}
#endregion
#region Methods
///
/// Initializes the task manager
///
public async Task InitializeAsync()
{
if (!DataSettingsManager.IsDatabaseInstalled())
return;
if (_taskThreads.Any())
return;
using var scope = _serviceScopeFactory.CreateScope();
var scheduleTaskService = scope.ServiceProvider.GetService() ?? throw new NullReferenceException($"Can't get {nameof(IScheduleTaskService)} implementation from the scope");
//initialize and start schedule tasks
var scheduleTasks = (await scheduleTaskService.GetAllTasksAsync())
.OrderBy(x => x.Seconds)
.ToList();
var storeContext = scope.ServiceProvider.GetService() ?? throw new NullReferenceException($"Can't get {nameof(IStoreContext)} implementation from the scope");
var store = await storeContext.GetCurrentStoreAsync();
var scheduleTaskUrl = $"{store.Url.TrimEnd('/')}/{NopTaskDefaults.ScheduleTaskPath}";
var timeout = _appSettings.Get().ScheduleTaskRunTimeout;
foreach (var scheduleTask in scheduleTasks)
{
var taskThread = new TaskThread(scheduleTask, scheduleTaskUrl, timeout)
{
Seconds = scheduleTask.Seconds
};
//sometimes a task period could be set to several hours (or even days)
//in this case a probability that it'll be run is quite small (an application could be restarted)
//calculate time before start an interrupted task
if (scheduleTask.LastStartUtc.HasValue)
{
//seconds left since the last start
var secondsLeft = (DateTime.UtcNow - scheduleTask.LastStartUtc).Value.TotalSeconds;
if (secondsLeft >= scheduleTask.Seconds)
//run now (immediately)
taskThread.InitSeconds = 0;
else
//calculate start time
//and round it (so "ensureRunOncePerPeriod" parameter was fine)
taskThread.InitSeconds = (int)(scheduleTask.Seconds - secondsLeft) + 1;
}
else if (scheduleTask.LastEnabledUtc.HasValue)
{
//seconds left since the last enable
var secondsLeft = (DateTime.UtcNow - scheduleTask.LastEnabledUtc).Value.TotalSeconds;
if (secondsLeft >= scheduleTask.Seconds)
//run now (immediately)
taskThread.InitSeconds = 0;
else
//calculate start time
//and round it (so "ensureRunOncePerPeriod" parameter was fine)
taskThread.InitSeconds = (int)(scheduleTask.Seconds - secondsLeft) + 1;
}
else
//first start of a task
taskThread.InitSeconds = scheduleTask.Seconds;
_taskThreads.Add(taskThread);
}
}
///
/// Starts the task scheduler
///
public void StartScheduler()
{
foreach (var taskThread in _taskThreads)
taskThread.InitTimer();
}
///
/// Stops the task scheduler
///
public void StopScheduler()
{
foreach (var taskThread in _taskThreads)
taskThread.Dispose();
}
#endregion
#region Nested class
///
/// Represents task thread
///
protected partial class TaskThread : IDisposable
{
#region Fields
protected readonly string _scheduleTaskUrl;
protected readonly ScheduleTask _scheduleTask;
protected readonly int? _timeout;
protected Timer _timer;
protected bool _disposed;
internal static IHttpClientFactory HttpClientFactory { get; set; }
internal static IServiceScopeFactory ServiceScopeFactory { get; set; }
#endregion
#region Ctor
public TaskThread(ScheduleTask task, string scheduleTaskUrl, int? timeout)
{
_scheduleTaskUrl = scheduleTaskUrl;
_scheduleTask = task;
_timeout = timeout;
Seconds = 10 * 60;
}
#endregion
#region Utilities
///
/// Run task
///
/// A task that represents the asynchronous operation
protected virtual async Task RunAsync()
{
if (Seconds <= 0)
return;
StartedUtc = DateTime.UtcNow;
IsRunning = true;
HttpClient client = null;
try
{
//create and configure client
client = HttpClientFactory.CreateClient(NopHttpDefaults.DefaultHttpClient);
if (_timeout.HasValue)
client.Timeout = TimeSpan.FromMilliseconds(_timeout.Value);
//send post data
var data = new FormUrlEncodedContent(new[] { new KeyValuePair("taskType", _scheduleTask.Type) });
await client.PostAsync(_scheduleTaskUrl, data);
}
catch (Exception ex)
{
using var scope = ServiceScopeFactory.CreateScope();
// Resolve
var logger = EngineContext.Current.Resolve(scope);
var localizationService = EngineContext.Current.Resolve(scope);
var storeContext = EngineContext.Current.Resolve(scope);
var message = ex.InnerException?.GetType() == typeof(TaskCanceledException) ? await localizationService.GetResourceAsync("ScheduleTasks.TimeoutError") : ex.Message;
var store = await storeContext.GetCurrentStoreAsync();
message = string.Format(await localizationService.GetResourceAsync("ScheduleTasks.Error"), _scheduleTask.Name,
message, _scheduleTask.Type, store.Name, _scheduleTaskUrl);
await logger.ErrorAsync(message, ex);
}
finally
{
client?.Dispose();
}
IsRunning = false;
}
///
/// Method that handles calls from a
///
/// An object containing application-specific information relevant to the method invoked by this delegate
protected void TimerHandler(object state)
{
try
{
_timer.Change(Timeout.Infinite, Timeout.Infinite);
RunAsync().Wait();
}
catch
{
// ignore
}
finally
{
if (!_disposed && _timer != null)
{
if (RunOnlyOnce)
Dispose();
else
_timer.Change(Interval, Interval);
}
}
}
///
/// Protected implementation of Dispose pattern.
///
/// Specifies whether to disposing resources
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
lock (this)
_timer?.Dispose();
_disposed = true;
}
#endregion
#region Methods
///
/// Disposes the instance
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Inits a timer
///
public void InitTimer()
{
_timer ??= new Timer(TimerHandler, null, InitInterval, Interval);
}
#endregion
#region Properties
///
/// Gets or sets the interval in seconds at which to run the tasks
///
public int Seconds { get; set; }
///
/// Get or set the interval before timer first start
///
public int InitSeconds { get; set; }
///
/// Get or sets a datetime when thread has been started
///
public DateTime StartedUtc { get; protected set; }
///
/// Get or sets a value indicating whether thread is running
///
public bool IsRunning { get; protected set; }
///
/// Gets the interval (in milliseconds) at which to run the task
///
public int Interval
{
get
{
//if somebody entered more than "2147483" seconds, then an exception could be thrown (exceeds int.MaxValue)
var interval = Seconds * 1000;
if (interval <= 0)
interval = int.MaxValue;
return interval;
}
}
///
/// Gets the due time interval (in milliseconds) at which to begin start the task
///
public int InitInterval
{
get
{
//if somebody entered less than "0" seconds, then an exception could be thrown
var interval = InitSeconds * 1000;
if (interval <= 0)
interval = 0;
return interval;
}
}
///
/// Gets or sets a value indicating whether the thread would be run only once (on application start)
///
public bool RunOnlyOnce { get; set; }
///
/// Gets a value indicating whether the timer is started
///
public bool IsStarted => _timer != null;
///
/// Gets a value indicating whether the timer is disposed
///
public bool IsDisposed => _disposed;
#endregion
}
#endregion
}