Skip to content

Commit

Permalink
WiP
Browse files Browse the repository at this point in the history
  • Loading branch information
KrzysztofPajak committed Nov 25, 2024
1 parent 3a09954 commit a80622e
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public interface IScheduleTaskService
/// <summary>
/// Gets a task by its type
/// </summary>
/// <param name="type">Task type</param>
/// <param name="name">Task name</param>
/// <returns>Task</returns>
Task<ScheduleTask> GetTaskByType(string type);
Task<ScheduleTask> GetTaskByName(string name);

/// <summary>
/// Gets all tasks
Expand Down
3 changes: 1 addition & 2 deletions src/Core/Grand.Domain/Tasks/ScheduleTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

public class ScheduleTask : BaseEntity
{
public string ScheduleTaskName { get; set; }
public string Type { get; set; }
public string ScheduleTaskName { get; set; }
public bool Enabled { get; set; }
public bool StopOnError { get; set; }
public DateTime? LastStartUtc { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,56 +12,42 @@ protected virtual Task InstallScheduleTasks()
var tasks = new List<ScheduleTask> {
new() {
ScheduleTaskName = "Send emails",
Type =
"Grand.Module.ScheduledTasks.BackgroundServices.QueuedMessagesSendScheduleTask, Grand.Module.ScheduledTasks",
Enabled = true,
StopOnError = false,
TimeInterval = 1
},
new() {
ScheduleTaskName = "Delete guests",
Type =
"Grand.Module.ScheduledTasks.BackgroundServices.DeleteGuestsScheduleTask, Grand.Module.ScheduledTasks",
Enabled = true,
StopOnError = false,
TimeInterval = 1440
},
new() {
ScheduleTaskName = "Clear cache",
Type =
"Grand.Module.ScheduledTasks.BackgroundServices.ClearCacheScheduleTask, Grand.Module.ScheduledTasks",
Enabled = false,
StopOnError = false,
TimeInterval = 120
},
new() {
ScheduleTaskName = "Update currency exchange rates",
Type =
"Grand.Module.ScheduledTasks.BackgroundServices.UpdateExchangeRateScheduleTask, Grand.Module.ScheduledTasks",
Enabled = true,
StopOnError = false,
TimeInterval = 1440
},
new() {
ScheduleTaskName = "Generate sitemap XML file",
Type =
"Grand.Module.ScheduledTasks.BackgroundServices.GenerateSitemapXmlTask, Grand.Module.ScheduledTasks",
Enabled = false,
StopOnError = false,
TimeInterval = 10080
},
new() {
ScheduleTaskName = "End of the auctions",
Type =
"Grand.Module.ScheduledTasks.BackgroundServices.EndAuctionsTask, Grand.Module.ScheduledTasks",
Enabled = false,
StopOnError = false,
TimeInterval = 60
},
new() {
ScheduleTaskName = "Cancel unpaid and pending orders",
Type =
"Grand.Module.ScheduledTasks.BackgroundServices.CancelOrderScheduledTask, Grand.Module.ScheduledTasks",
Enabled = false,
StopOnError = false,
TimeInterval = 1440
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,16 @@ public virtual Task<ScheduleTask> GetTaskById(string taskId)
}

/// <summary>
/// Gets a task by its type
/// Gets a task by its name
/// </summary>
/// <param name="type">Task type</param>
/// <returns>Task</returns>
public virtual async Task<ScheduleTask> GetTaskByType(string type)
public virtual async Task<ScheduleTask> GetTaskByName(string name)
{
if (string.IsNullOrWhiteSpace(type))
if (string.IsNullOrWhiteSpace(name))
return null;

var query = _taskRepository.Table.Where(st => st.Type == type).OrderByDescending(t => t.Id);
return await Task.FromResult(query.FirstOrDefault());
var task = _taskRepository.Table.FirstOrDefault(x => x.ScheduleTaskName == name);
return await Task.FromResult(task);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<OutputPath>..\..\Web\Grand.Web\Modules\Grand.Module.ScheduledTask\</OutputPath>
<OutputPath>..\..\Web\Grand.Web\Modules\Grand.Module.ScheduledTasks\</OutputPath>
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>..\..\Web\Grand.Web\Modules\Grand.Module.ScheduledTask\</OutputPath>
<OutputPath>..\..\Web\Grand.Web\Modules\Grand.Module.ScheduledTasks\</OutputPath>
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ private void RegisterTask(IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<IScheduleTaskService, ScheduleTaskService>();

serviceCollection.AddScoped<IScheduleTask, QueuedMessagesSendScheduleTask>();
serviceCollection.AddScoped<IScheduleTask, ClearCacheScheduleTask>();
serviceCollection.AddScoped<IScheduleTask, GenerateSitemapXmlTask>();
serviceCollection.AddScoped<IScheduleTask, DeleteGuestsScheduleTask>();
serviceCollection.AddScoped<IScheduleTask, UpdateExchangeRateScheduleTask>();
serviceCollection.AddScoped<IScheduleTask, EndAuctionsTask>();
serviceCollection.AddScoped<IScheduleTask, CancelOrderScheduledTask>();
serviceCollection.AddKeyedScoped<IScheduleTask, QueuedMessagesSendScheduleTask>("Send emails");
serviceCollection.AddKeyedScoped<IScheduleTask, ClearCacheScheduleTask>("Clear cache");
serviceCollection.AddKeyedScoped<IScheduleTask, GenerateSitemapXmlTask>("Generate sitemap XML file");
serviceCollection.AddKeyedScoped<IScheduleTask, DeleteGuestsScheduleTask>("Delete guests");
serviceCollection.AddKeyedScoped<IScheduleTask, UpdateExchangeRateScheduleTask>("Update currency exchange rates");
serviceCollection.AddKeyedScoped<IScheduleTask, EndAuctionsTask>("End of the auctions");
serviceCollection.AddKeyedScoped<IScheduleTask, CancelOrderScheduledTask>("Cancel unpaid and pending orders");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,7 @@
<div class="col-md-9 col-sm-9">
<admin-input asp-for="LeasedByMachineName"/>
</div>
</div>

<div class="form-group">
<admin-label asp-for="Type"/>
<div class="col-md-9 col-sm-9">
<label class="control-label"> @Html.DisplayFor(model => model.Type) </label>
</div>
</div>
</div>
<div class="form-group">
<admin-label asp-for="Enabled"/>
<div class="col-md-9 col-sm-9">
Expand Down
10 changes: 4 additions & 6 deletions src/Web/Grand.Web.Admin/Controllers/ScheduleTaskController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ public async Task<IActionResult> EditScheduler(ScheduleTaskModel model, bool con
return RedirectToAction("List");
}

model.ScheduleTaskName = scheduleTask.ScheduleTaskName;
model.Type = scheduleTask.Type;
model.ScheduleTaskName = scheduleTask.ScheduleTaskName;
model = await PrepareStores(model);
Error(ModelState);

Expand All @@ -132,9 +131,8 @@ public async Task<IActionResult> RunNow(string id)
{
var scheduleTask = await _scheduleTaskService.GetTaskById(id);
if (scheduleTask == null) throw new Exception("Schedule task cannot be loaded");
var typeofTask = Type.GetType(scheduleTask.Type);
var task = HttpContext.RequestServices.GetServices<IScheduleTask>()
.FirstOrDefault(x => x.GetType() == typeofTask);

var task = HttpContext.RequestServices.GetRequiredKeyedService<IScheduleTask>(scheduleTask.ScheduleTaskName);
if (task != null)
{
scheduleTask.LastStartUtc = DateTime.UtcNow;
Expand All @@ -157,7 +155,7 @@ public async Task<IActionResult> RunNow(string id)
}
else
{
Error($"Task {typeofTask?.Name} has not been registered");
Error($"Task {scheduleTask.ScheduleTaskName} has not been registered");
}
}
catch (Exception exc)
Expand Down
1 change: 0 additions & 1 deletion src/Web/Grand.Web.Admin/Mapper/ScheduleTaskProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public ScheduleTaskProfile()

CreateMap<ScheduleTaskModel, ScheduleTask>()
.ForMember(dest => dest.Id, mo => mo.Ignore())
.ForMember(dest => dest.Type, mo => mo.Ignore())
.ForMember(dest => dest.ScheduleTaskName, mo => mo.Ignore())
.ForMember(dest => dest.LastNonSuccessEndUtc, mo => mo.Ignore())
.ForMember(dest => dest.LastStartUtc, mo => mo.Ignore())
Expand Down
5 changes: 1 addition & 4 deletions src/Web/Grand.Web.Admin/Models/Tasks/ScheduleTaskModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ public class ScheduleTaskModel : BaseEntityModel
public string ScheduleTaskName { get; set; }

[GrandResourceDisplayName("Admin.System.ScheduleTasks.LeasedByMachineName")]
public string LeasedByMachineName { get; set; }

[GrandResourceDisplayName("Admin.System.ScheduleTasks.Type")]
public string Type { get; set; }
public string LeasedByMachineName { get; set; }

[GrandResourceDisplayName("Admin.System.ScheduleTasks.Enabled")]
public bool Enabled { get; set; }
Expand Down
19 changes: 19 additions & 0 deletions src/Web/Grand.Web.Common/Extensions/KeyedServiceHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.Extensions.DependencyInjection;

namespace Grand.Web.Common.Extensions;

public static class KeyedServiceHelper
{
public static IList<string> GetKeyedServicesForInterface<TInterface>(IServiceCollection services)
{
var keyedServices = new List<string>();
foreach (var service in services)
{
if (service.ServiceType == typeof(TInterface) && service.IsKeyedService)
{
keyedServices.Add(service.ServiceKey.ToString());
}
}
return keyedServices;
}
}
91 changes: 40 additions & 51 deletions src/Web/Grand.Web.Common/Infrastructure/BackgroundServiceTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ namespace Grand.Web.Common.Infrastructure;
public class BackgroundServiceTask : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly string _taskType;
private readonly string Name;

public BackgroundServiceTask(string taskType, IServiceProvider serviceProvider)
public BackgroundServiceTask(string name, IServiceProvider serviceProvider)
{
_taskType = taskType;
Name = name;
_serviceProvider = serviceProvider;
}

Expand All @@ -27,10 +27,10 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
var serviceProvider = scope.ServiceProvider;
var logger = serviceProvider.GetService<ILogger<BackgroundServiceTask>>();
var scheduleTaskService = serviceProvider.GetService<IScheduleTaskService>();
var task = await scheduleTaskService.GetTaskByType(_taskType);
var task = await scheduleTaskService.GetTaskByName(Name);
if (task == null)
{
logger.LogInformation("Task {TaskType} is not exists in the database", _taskType);
logger.LogInformation("Task {TaskName} is not exists in the database", Name);
break;
}

Expand All @@ -39,66 +39,55 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
if (task.Enabled && (string.IsNullOrEmpty(task.LeasedByMachineName) ||
machineName == task.LeasedByMachineName))
{
var typeofTask = Type.GetType(_taskType);
if (typeofTask != null)

var scheduleTask = serviceProvider.GetRequiredKeyedService<IScheduleTask>(task.ScheduleTaskName);
if (scheduleTask != null)
{
var scheduleTask = serviceProvider.GetServices<IScheduleTask>()
.FirstOrDefault(x => x.GetType() == typeofTask);
if (scheduleTask != null)
//assign current customer (background task) / current store (from task)
await WorkContext(serviceProvider, task);
var runTask = true;
if (task.LastStartUtc.HasValue)
{
//assign current customer (background task) / current store (from task)
await WorkContext(serviceProvider, task);
var runTask = true;
if (task.LastStartUtc.HasValue)
var dateTimeNow = DateTime.UtcNow;
if (dateTimeNow < task.LastStartUtc.Value.AddMinutes(task.TimeInterval))
{
var dateTimeNow = DateTime.UtcNow;
if (dateTimeNow < task.LastStartUtc.Value.AddMinutes(task.TimeInterval))
{
runTask = false;
timeInterval =
(int)(task.LastStartUtc.Value.AddMinutes(task.TimeInterval) - dateTimeNow)
.TotalMinutes;
}
else
{
runTask = true;
timeInterval = task.TimeInterval > 0 ? task.TimeInterval : 1;
}
runTask = false;
timeInterval =
(int)(task.LastStartUtc.Value.AddMinutes(task.TimeInterval) - dateTimeNow)
.TotalMinutes;
}

if (runTask)
else
{
task.LastStartUtc = DateTime.UtcNow;
try
{
//TODO - add settings
//logger.Information($"Task {_taskType} execute");
await scheduleTask.Execute();
task.LastSuccessUtc = DateTime.UtcNow;
task.LastNonSuccessEndUtc = null;
}
catch (Exception exc)
{
task.LastNonSuccessEndUtc = DateTime.UtcNow;
task.Enabled = !task.StopOnError;
logger.LogError(exc,
"Error while running the \'{TaskScheduleTaskName}\' schedule task",
task.ScheduleTaskName);
}
runTask = true;
timeInterval = task.TimeInterval > 0 ? task.TimeInterval : 1;
}
}
else

if (runTask)
{
task.Enabled = !task.StopOnError;
task.LastNonSuccessEndUtc = DateTime.UtcNow;
logger.LogError("Type {TaskType} is not registered", _taskType);
task.LastStartUtc = DateTime.UtcNow;
try
{
logger.LogInformation($"Task {Name} execute");
await scheduleTask.Execute();
task.LastSuccessUtc = DateTime.UtcNow;
task.LastNonSuccessEndUtc = null;
}
catch (Exception exc)
{
task.LastNonSuccessEndUtc = DateTime.UtcNow;
task.Enabled = !task.StopOnError;
logger.LogError(exc,
"Error while running the \'{TaskScheduleTaskName}\' schedule task",
task.ScheduleTaskName);
}
}
}
else
{
task.Enabled = !task.StopOnError;
task.LastNonSuccessEndUtc = DateTime.UtcNow;
logger.LogError("Type {TaskType} is null (type not exists)", _taskType);
logger.LogError("Type {TaskName} is not registered", Name);
}

await scheduleTaskService.UpdateTask(task);
Expand Down
19 changes: 4 additions & 15 deletions src/Web/Grand.Web.Common/Startup/TaskHandler.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using Grand.Business.Core.Interfaces.System.ScheduleTasks;
using Grand.Data;
using Grand.Infrastructure.Configuration;
using Grand.Infrastructure.Plugins;
using Grand.Infrastructure.TypeSearch;
using Grand.Web.Common.Extensions;
using Grand.Web.Common.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -17,20 +15,11 @@ public static void RegisterTasks(this IServiceCollection services, IConfiguratio
//database is already installed, so start scheduled tasks
if (!DataSettingsManager.DatabaseIsInstalled()) return;

var appConfig = new AppConfig();
configuration.GetSection("Application").Bind(appConfig);
var scheduleTaskKeyedServices = KeyedServiceHelper.GetKeyedServicesForInterface<IScheduleTask>(services);

var typeSearcher = new TypeSearcher();
var scheduleTasks = typeSearcher.ClassesOfType<IScheduleTask>();

var scheduleTasksInstalled = scheduleTasks
.Where(PluginExtensions.OnlyInstalledPlugins); //ignore not installed plugins

foreach (var task in scheduleTasksInstalled)
foreach (var task in scheduleTaskKeyedServices)
{
var assemblyName = task.Assembly.GetName().Name;
services.AddSingleton<IHostedService, BackgroundServiceTask>(sp =>
new BackgroundServiceTask($"{task.FullName}, {assemblyName}", sp));
services.AddSingleton<IHostedService, BackgroundServiceTask>(sp => new BackgroundServiceTask(task, sp));
}
}
}

0 comments on commit a80622e

Please sign in to comment.