206 lines
6.5 KiB
C#
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();
|
|
}
|
|
}
|