-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
150 lines (132 loc) · 6.47 KB
/
Program.cs
File metadata and controls
150 lines (132 loc) · 6.47 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
using Serilog;
using Serilog.Events;
using System;
using System.IO;
using System.Linq;
using System.Threading;
namespace LogFileCollector
{
/// <summary>
/// Application entry point.
/// - Loads configuration from %ProgramData%\Virinco\WATS\LogFileCollector\appsettings.json
/// - Configures Serilog (console + rolling file)
/// - Handles --reset and --rescan
/// - Starts the watcher and optional periodic full rescans
/// </summary>
internal class Program
{
private static Timer _periodicRescanTimer;
static int Main(string[] args)
{
// Resolve ProgramData config dir (works for both service and user contexts)
string dataDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"Virinco", "WATS", "LogFileCollector");
Directory.CreateDirectory(dataDir);
// Load config from ProgramData
string configPath = Path.Combine(dataDir, "appsettings.json");
if (!File.Exists(configPath))
{
Console.Error.WriteLine("Config not found: " + configPath);
return 2;
}
try
{
AppSettings config = ConfigLoader.Load(configPath);
// Resolve DB + Log paths (relative => under ProgramData; absolute/UNC => as-is)
Func<string, string> resolveInData = p => Path.IsPathRooted(p) ? p : Path.Combine(dataDir, p);
string dbPath = resolveInData(config.DatabasePath);
string logPath = resolveInData(config.Logging.LogFilePath);
// Map LogLevel string to Serilog LogEventLevel (use simple switch for C# 7.3 compatibility)
LogEventLevel minLevel;
switch ((config.Logging.LogLevel ?? "Information").Trim().ToLowerInvariant())
{
case "debug": minLevel = LogEventLevel.Debug; break;
case "warning": minLevel = LogEventLevel.Warning; break;
case "error": minLevel = LogEventLevel.Error; break;
case "verbose": minLevel = LogEventLevel.Verbose; break;
default: minLevel = LogEventLevel.Information; break;
}
// Map RollingInterval
RollingInterval interval;
switch ((config.Logging.RollingInterval ?? "Day").Trim().ToLowerInvariant())
{
case "hour": interval = RollingInterval.Hour; break;
case "month": interval = RollingInterval.Month; break;
case "year": interval = RollingInterval.Year; break;
case "infinite": interval = RollingInterval.Infinite; break;
default: interval = RollingInterval.Day; break;
}
// Configure Serilog
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Is(minLevel)
.WriteTo.Console(outputTemplate: config.Logging.LogOutputTemplate)
.WriteTo.File(
path: logPath,
outputTemplate: config.Logging.LogOutputTemplate,
rollingInterval: interval,
retainedFileCountLimit: config.Logging.RetainedFileCountLimit)
.CreateLogger();
Log.Information("LogFileCollector starting.");
bool reset = args.Any(a => a.Equals("--reset", StringComparison.OrdinalIgnoreCase));
bool rescan = args.Any(a => a.Equals("--rescan", StringComparison.OrdinalIgnoreCase));
// Reset option: delete DB file
if (reset && File.Exists(dbPath))
{
File.Delete(dbPath);
Log.Warning("Database reset: {Db}", dbPath);
}
// Initialize DB and processor
Database db = new Database(dbPath);
FileProcessor processor = new FileProcessor(config, db);
// One-time rescan on startup if requested
if (rescan)
{
Log.Information("Rescan starting (Filter={Filter}, Subdirs={Subdirs})",
config.Filter, config.IncludeSubdirectories);
processor.ProcessAllFiles();
Log.Information("Rescan completed. Run={RunStats} Total={TotalStats}", processor.RunStats, processor.TotalStats);
Log.Information("Switching to watcher mode…");
}
// Always start watcher
processor.StartWatching();
// Periodic rescan (safety net against missed events)
if (config.PeriodicRescanMinutes > 0)
{
TimeSpan rescanInterval = TimeSpan.FromMinutes(config.PeriodicRescanMinutes);
_periodicRescanTimer = new Timer(state =>
{
try
{
Log.Information("Starting periodic rescan (every {Minutes} minutes)...", config.PeriodicRescanMinutes);
processor.ProcessAllFiles();
Log.Information("Periodic rescan completed. Run={RunStats} Total={TotalStats}", processor.RunStats, processor.TotalStats);
}
catch (Exception ex)
{
Log.Error(ex, "Error during periodic rescan");
}
}, null, rescanInterval, rescanInterval);
Log.Information("Periodic rescan enabled (every {Minutes} minutes)", config.PeriodicRescanMinutes);
}
// Keep running until Ctrl+C
Log.Information("Watcher running. Press Ctrl+C to stop.");
ManualResetEvent exitEvent = new ManualResetEvent(false);
Console.CancelKeyPress += (s, e) => { e.Cancel = true; exitEvent.Set(); };
exitEvent.WaitOne();
// Final stats on shutdown
Log.Information("Shutdown. Final statistics: Total={TotalStats}", processor.TotalStats);
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Fatal error");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
}
}