/* * SPDX-License-Identifier: AGPL-3.0-or-later * Copyright (C) 2025 Sergej Görzen * This file is part of OmiLAXR. */ using System; using System.IO; using System.Text; using System.Threading; // Für Thread.Sleep im Retry-Block namespace OmiLAXR.Utils { /// /// High-performance UTF-8 file writer with internal buffering for efficient text output. /// Provides thread-safe writing operations with automatic UTF-8 encoding. /// Optimized for scenarios with frequent small writes like logging and analytics data. /// public class BufferedUtf8Writer : IDisposable { /// /// Buffered stream wrapper providing write buffering for improved performance. /// Reduces the number of actual disk I/O operations. /// private readonly BufferedStream _bufferedStream; /// /// Underlying file stream for direct file system access. /// Managed by the BufferedStream for actual I/O operations. /// private readonly FileStream _fileStream; /// /// Reusable byte buffer for UTF-8 encoding operations. /// Prevents repeated memory allocations during string encoding. /// private readonly byte[] _writeBuffer; /// /// File path this writer is associated with. /// Stored for reference and debugging purposes. /// public string FilePath { get; private set; } /// /// Thread synchronization object for ensuring thread-safe operations. /// All public methods acquire this lock before performing I/O operations. /// private readonly object _lock = new object(); private bool _isDisposed; /// /// Initializes a new BufferedUtf8Writer for the specified file path. /// Creates or opens the file and sets up buffering for optimal performance. /// /// File path to write to /// Size of the internal buffer in bytes (default: 8192) public BufferedUtf8Writer(string path, int bufferSize = 8192) { _fileStream = OpenWithRetry(path, bufferSize); _bufferedStream = new BufferedStream(_fileStream, bufferSize); _writeBuffer = new byte[bufferSize]; FilePath = path; _isDisposed = false; } /// /// Opens a FileStream with retry handling for sharing violations. /// private static FileStream OpenWithRetry(string path, int bufferSize) { const int maxAttempts = 5; for (int attempt = 1; attempt <= maxAttempts; attempt++) { try { return new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite, 8192); } catch (IOException ex) when (IsSharingViolation(ex) && attempt < maxAttempts) { Thread.Sleep(30 * attempt); // exponential backoff } } // Let the final attempt throw if still failing return new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite, 8192); } private static bool IsSharingViolation(IOException ex) { const int ERROR_SHARING_VIOLATION = 32; return (ex.HResult & 0xFFFF) == ERROR_SHARING_VIOLATION; } public void WriteLine(string line) { lock (_lock) { // Append platform-specific newline var full = line + Environment.NewLine; // Encode to UTF-8 using reusable buffer to avoid allocations var byteCount = Encoding.UTF8.GetBytes(full, 0, full.Length, _writeBuffer, 0); // Write encoded bytes to buffered stream _bufferedStream.Write(_writeBuffer, 0, byteCount); } } /// /// Writes multiple lines of text efficiently in a single thread-safe operation. /// More efficient than multiple WriteLine calls for bulk operations. /// /// Array of text lines to write public void WriteLines(string[] lines) { lock (_lock) { // Write each line within the same lock for better performance foreach (var line in lines) WriteLine(line); } } /// /// Forces all buffered data to be written to the underlying file system. /// Thread-safe operation that ensures data persistence. /// public void Flush() { lock (_lock) { _bufferedStream.Flush(); } } /// /// Gets the current position in the buffered stream. /// Thread-safe access to the stream position for debugging or state tracking. /// /// Current byte position in the stream public int GetPosition() { lock (_lock) { return (int)_bufferedStream.Position; } } /// /// Sets the position in the buffered stream for random access scenarios. /// Thread-safe positioning operation with bounds checking by the underlying stream. /// /// Byte position to seek to public void SetPosition(int position) { lock (_lock) { _bufferedStream.Position = position; } } /// /// Disposes all resources including streams and buffers. /// Ensures final flush before disposal to prevent data loss. /// Thread-safe cleanup operation. /// public void Dispose() { lock (_lock) { if (!_isDisposed) { // Ensure all data is written before disposing _bufferedStream.Flush(); _bufferedStream.Dispose(); _isDisposed = true; } _fileStream.Dispose(); } } } }