using Immense.RemoteControl.Server.Abstractions; using Microsoft.Extensions.Logging; using Remotely.Server.Hubs; using Remotely.Shared.Entities; using Remotely.Shared.Enums; using Remotely.Shared.Utilities; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Remotely.Server.Services; public interface IScriptScheduleDispatcher { Task DispatchPendingScriptRuns(); } public class ScriptScheduleDispatcher : IScriptScheduleDispatcher { private readonly IDataService _dataService; private readonly IAgentHubSessionCache _serviceSessionCache; private readonly ICircuitConnection _circuitConnection; private readonly ILogger _logger; public ScriptScheduleDispatcher(IDataService dataService, IAgentHubSessionCache serviceSessionCache, ICircuitConnection circuitConnection, ILogger logger) { _dataService = dataService; _serviceSessionCache = serviceSessionCache; _circuitConnection = circuitConnection; _logger = logger; } public async Task DispatchPendingScriptRuns() { try { _logger.LogDebug("Script Schedule Dispatcher started."); var schedules = await _dataService.GetScriptSchedulesDue(); if (schedules?.Any() != true) { _logger.LogDebug("No schedules are due."); return; } foreach (var schedule in schedules) { try { _logger.LogDebug("Considering {scheduleName}. Interval: {interval}. Next Run: {nextRun}.", schedule.Name, schedule.Interval, schedule.NextRun); if (!AdvanceSchedule(schedule)) { _logger.LogDebug("Schedule is not due."); continue; } _logger.LogDebug("Creating script run for schedule {scheduleName}.", schedule.Name); var scriptRun = new ScriptRun() { OrganizationID = schedule.OrganizationID, RunAt = Time.Now, SavedScriptId = schedule.SavedScriptId, RunOnNextConnect = schedule.RunOnNextConnect, Initiator = $"Schedule: {schedule.Name}", ScheduleId = schedule.Id, InputType = ScriptInputType.ScheduledScript }; var deviceIdsFromDeviceGroups = schedule.DeviceGroups?.SelectMany(dg => dg.Devices.Select(d => d.ID)); var deviceIds = schedule.Devices.Select(x => x.ID) .Concat(deviceIdsFromDeviceGroups ?? Array.Empty()) .Distinct() .ToArray(); var onlineDevices = _serviceSessionCache.FilterDevicesByOnlineStatus(deviceIds, true); if (schedule.RunOnNextConnect) { scriptRun.Devices = _dataService.GetDevices(deviceIds); } else { scriptRun.Devices = _dataService.GetDevices(onlineDevices); } await _dataService.AddScriptRun(scriptRun); await _circuitConnection.RunScript(onlineDevices, schedule.SavedScriptId, scriptRun.Id, ScriptInputType.ScheduledScript, true); _logger.LogDebug("Created script run for schedule {scheduleName}.", schedule.Name); schedule.LastRun = Time.Now; await _dataService.AddOrUpdateScriptSchedule(schedule); } catch (Exception ex) { _logger.LogError(ex, "Error while generating script run."); } } } catch (Exception ex) { _logger.LogError(ex, "Error while dispatching script runs."); } } private bool AdvanceSchedule(ScriptSchedule schedule) { if (schedule is null) { return false; } if (schedule.NextRun > Time.Now) { return false; } switch (schedule.Interval) { case RepeatInterval.Hourly: CatchUpNextRun(schedule, TimeSpan.FromHours(1)); break; case RepeatInterval.Daily: CatchUpNextRun(schedule, TimeSpan.FromDays(1)); break; case RepeatInterval.Weekly: CatchUpNextRun(schedule, TimeSpan.FromDays(7)); break; case RepeatInterval.Monthly: for (var i = 0; schedule.NextRun < Time.Now; i++) { schedule.NextRun = schedule.StartAt.AddMonths(i); } break; default: return false; } return true; } private void CatchUpNextRun(ScriptSchedule schedule, TimeSpan interval) { while (schedule.NextRun < Time.Now) { schedule.NextRun += interval; } } }