Files
CGW-Printing/PrintService/Services/FileMonitorService.cs
T

206 lines
6.5 KiB
C#

using Microsoft.Extensions.Options;
using PrintService.Models;
namespace PrintService.Services;
/// <summary>
/// Monitors the Captures folder for new files and moves them to queue
/// </summary>
public class FileMonitorService : IDisposable
{
private readonly AppSettings _settings;
private readonly PrintQueueService _queueService;
private readonly ILogger<FileMonitorService> _logger;
private FileSystemWatcher? _watcher;
private readonly Dictionary<string, DateTime> _pendingFiles = new();
private readonly Timer _debounceTimer;
private readonly object _lock = new();
public FileMonitorService(
IOptions<AppSettings> settings,
PrintQueueService queueService,
ILogger<FileMonitorService> logger)
{
_settings = settings.Value;
_queueService = queueService;
_logger = logger;
// Timer for debouncing file system events
_debounceTimer = new Timer(ProcessPendingFiles, null,
TimeSpan.FromMilliseconds(_settings.DebounceDelayMs),
TimeSpan.FromMilliseconds(_settings.DebounceDelayMs));
}
/// <summary>
/// Start monitoring the Captures folder
/// </summary>
public void Start()
{
if (!Directory.Exists(_settings.CapturesPath))
{
Directory.CreateDirectory(_settings.CapturesPath);
_logger.LogInformation("Created captures directory: {Path}", _settings.CapturesPath);
}
_watcher = new FileSystemWatcher(_settings.CapturesPath)
{
Filter = "*.TXT",
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime,
EnableRaisingEvents = true
};
_watcher.Created += OnFileCreated;
_watcher.Changed += OnFileChanged;
_logger.LogInformation("Started monitoring: {Path}", _settings.CapturesPath);
}
/// <summary>
/// Stop monitoring
/// </summary>
public void Stop()
{
if (_watcher != null)
{
_watcher.EnableRaisingEvents = false;
_watcher.Dispose();
_watcher = null;
}
_logger.LogInformation("Stopped monitoring");
}
private void OnFileCreated(object sender, FileSystemEventArgs e)
{
lock (_lock)
{
_pendingFiles[e.FullPath] = DateTime.Now;
if (_settings.VerboseLogging)
{
_logger.LogDebug("File created: {Path}", e.FullPath);
}
}
}
private void OnFileChanged(object sender, FileSystemEventArgs e)
{
lock (_lock)
{
_pendingFiles[e.FullPath] = DateTime.Now;
if (_settings.VerboseLogging)
{
_logger.LogDebug("File changed: {Path}", e.FullPath);
}
}
}
/// <summary>
/// Process files after debounce delay
/// </summary>
private void ProcessPendingFiles(object? state)
{
List<string> filesToProcess;
lock (_lock)
{
var cutoff = DateTime.Now.AddMilliseconds(-_settings.DebounceDelayMs);
filesToProcess = _pendingFiles
.Where(kvp => kvp.Value <= cutoff)
.Select(kvp => kvp.Key)
.ToList();
foreach (var file in filesToProcess)
{
_pendingFiles.Remove(file);
}
}
foreach (var filePath in filesToProcess)
{
ProcessFile(filePath);
}
}
/// <summary>
/// Process a single file: move to queue with GUID name
/// </summary>
private void ProcessFile(string filePath)
{
try
{
if (!File.Exists(filePath))
{
return; // File may have been moved or deleted
}
var fileName = Path.GetFileName(filePath);
var jobId = Guid.NewGuid();
var queuedFileName = $"{jobId}.txt";
var queuedPath = Path.Combine(_settings.QueuePath, queuedFileName);
// Move file to queue with GUID name to prevent collisions
File.Move(filePath, queuedPath);
_logger.LogInformation("Moved {FileName} to queue as {QueuedFile}", fileName, queuedFileName);
// Note: Job will be created when CLI sends command with document type
// For now, we just have the file safely in the queue folder
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process file: {Path}", filePath);
}
}
/// <summary>
/// Manually process a file (called from IPC when CLI provides metadata)
/// </summary>
public PrintJob CreateJobFromFile(string filePath, string documentType, string? orderNumber)
{
var fileName = Path.GetFileName(filePath);
var jobId = Guid.NewGuid();
var queuedFileName = $"{jobId}.txt";
var queuedPath = Path.Combine(_settings.QueuePath, queuedFileName);
// Check if this document type has SkipPostProcessing enabled
var config = _settings.DocumentTypes.FirstOrDefault(dt =>
dt.Name.Equals(documentType, StringComparison.OrdinalIgnoreCase));
if (config?.SkipPostProcessing == true)
{
// Copy instead of move for testing scenarios
File.Copy(filePath, queuedPath, overwrite: true);
_logger.LogInformation("Copied {FileName} to queue (test mode - source file preserved)", fileName);
}
else
{
// Move file to queue (normal operation)
File.Move(filePath, queuedPath, overwrite: true);
_logger.LogInformation("Moved {FileName} to queue", fileName);
}
var job = new PrintJob
{
Id = jobId,
SourceFilePath = filePath,
QueuedFilePath = queuedPath,
DocumentType = documentType,
OrderNumber = orderNumber,
OriginalFilename = fileName,
Status = PrintJobStatus.Pending,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
_logger.LogInformation("Created job {JobId} for {FileName} (type: {DocType})",
job.Id, fileName, documentType);
return job;
}
public void Dispose()
{
_debounceTimer?.Dispose();
Stop();
}
}