-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProcessMonitorService.cs
More file actions
156 lines (135 loc) · 6 KB
/
Copy pathProcessMonitorService.cs
File metadata and controls
156 lines (135 loc) · 6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
using MarketAlly.ProcessMonitor.Interfaces;
using MarketAlly.ProcessMonitor.Models;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace MarketAlly.ProcessMonitor.Services;
/// <summary>
/// Background service that monitors and manages processes
/// </summary>
public class ProcessMonitorService : BackgroundService
{
private readonly ILogger<ProcessMonitorService> _logger;
private readonly IProcessManager _processManager;
private readonly IScheduler _scheduler;
private readonly IConfigurationService _configurationService;
private readonly AppSettings _appSettings;
private readonly Dictionary<string, bool> _initializedProcesses = new();
private ProcessConfiguration? _currentConfiguration;
public ProcessMonitorService(
ILogger<ProcessMonitorService> logger,
IProcessManager processManager,
IScheduler scheduler,
IConfigurationService configurationService)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_processManager = processManager ?? throw new ArgumentNullException(nameof(processManager));
_scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler));
_configurationService = configurationService ?? throw new ArgumentNullException(nameof(configurationService));
_appSettings = _configurationService.GetAppSettings();
// Subscribe to configuration changes
_configurationService.ConfigurationChanged += OnConfigurationChanged;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Process Monitor Service starting");
// Ask user about window preference once at startup
var openInNewWindow = await GetUserPreferenceAsync();
while (!stoppingToken.IsCancellationRequested)
{
try
{
var configuration = await _configurationService.GetConfigurationAsync();
if (_currentConfiguration == null ||
configuration.LastModified != _currentConfiguration.LastModified)
{
_currentConfiguration = configuration;
await ProcessConfigurationAsync(configuration, openInNewWindow, stoppingToken);
}
await Task.Delay(TimeSpan.FromSeconds(_appSettings.MonitoringIntervalSeconds), stoppingToken);
}
catch (OperationCanceledException)
{
// Expected when cancellation is requested
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in process monitoring loop");
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); // Wait before retry
}
}
_logger.LogInformation("Process Monitor Service stopping");
}
private async Task<bool> GetUserPreferenceAsync()
{
// In production, this could be read from configuration
// For now, keeping the interactive prompt
await Task.Run(() =>
{
Console.WriteLine("Do you want to start processes in a new window? (yes/no)");
Console.WriteLine("This preference will be used for all processes during this session.");
});
string? userInput = await Task.Run(() => Console.ReadLine());
return userInput?.ToLower() == "yes";
}
private async Task ProcessConfigurationAsync(
ProcessConfiguration configuration,
bool openInNewWindow,
CancellationToken cancellationToken)
{
foreach (var processInfo in configuration.Processes.Where(p => p.Enable))
{
if (cancellationToken.IsCancellationRequested) break;
try
{
var isFirstRun = !_initializedProcesses.ContainsKey(processInfo.Name);
if (isFirstRun)
{
_initializedProcesses[processInfo.Name] = true;
// Schedule if it has interval
if (processInfo.Interval.HasValue)
{
_scheduler.ScheduleRepeatedExecution(processInfo, openInNewWindow);
}
// Schedule if it has specific time
else if (!string.IsNullOrEmpty(processInfo.Time))
{
_scheduler.ScheduleProcessStart(processInfo, openInNewWindow);
}
}
// Always ensure continuous processes are running
if (string.IsNullOrEmpty(processInfo.Time) && !processInfo.Interval.HasValue)
{
await _processManager.EnsureProcessRunningAsync(
processInfo,
processInfo.Count,
openInNewWindow,
cancellationToken);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing configuration for {ProcessName}", processInfo.Name);
}
}
}
private void OnConfigurationChanged(object? sender, ProcessConfiguration newConfiguration)
{
_logger.LogInformation("Configuration changed, updating process monitoring");
// Cancel tasks for processes that are no longer enabled
var disabledProcesses = _initializedProcesses.Keys
.Where(name => !newConfiguration.Processes.Any(p => p.Name == name && p.Enable))
.ToList();
foreach (var processName in disabledProcesses)
{
_scheduler.CancelScheduledTasks(processName);
_initializedProcesses.Remove(processName);
_logger.LogInformation("Cancelled monitoring for disabled process: {ProcessName}", processName);
}
}
public override void Dispose()
{
_configurationService.ConfigurationChanged -= OnConfigurationChanged;
base.Dispose();
}
}