using System.Globalization; using CsvHelper; namespace Inspectron.Statistics; public class LockedCsvStatistics : IStatistics { private readonly string _filePath; private readonly string _lockFilePath; private readonly string[] _headers; public LockedCsvStatistics(string filePath, string[] headers) { _filePath = filePath; _lockFilePath = filePath + ".lock"; _headers = headers; var directoryPath = Path.GetDirectoryName(filePath); if (directoryPath != null && !Directory.Exists(directoryPath)) { Directory.CreateDirectory(directoryPath); } } public void WriteLine(StatisticsRecord record) { AcquireLock(); try { bool writeHeaders = !File.Exists(_filePath) || new FileInfo(_filePath).Length == 0; using var fileStream = new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.None); using var streamWriter = new StreamWriter(fileStream); using var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture); if (writeHeaders) { foreach (var header in _headers) { csvWriter.WriteField(header); } csvWriter.NextRecord(); csvWriter.Flush(); } foreach (var value in record.Values) { csvWriter.WriteField(value); } csvWriter.NextRecord(); csvWriter.Flush(); } finally { ReleaseLock(); } } public void Dispose() { // No resources to dispose } private void AcquireLock() { while (true) { try { using (File.Open(_lockFilePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None)) { // Lock acquired break; } } catch (IOException) { // Lock file already exists, wait and retry Thread.Sleep(10); } } } private void ReleaseLock() { if (File.Exists(_lockFilePath)) { File.Delete(_lockFilePath); } } }