//+------------------------------------------------------------------+ //| CUnifiedFileIO.mqh - Thread-Safe File Operations with Retry Logic | //| Provides robust file I/O with automatic retry, buffering, and | //| atomic write operations | //+------------------------------------------------------------------+ #ifndef CUNIFIEDFILEIO_MQH #define CUNIFIEDFILEIO_MQH #include "CErrorRecovery.mqh" // File operation types enum ENUM_FILE_OPERATION { FILE_OP_READ = 0, FILE_OP_WRITE = 1, FILE_OP_APPEND = 2, FILE_OP_DELETE = 3 }; // Buffer strategy enum ENUM_BUFFER_STRATEGY { BUFFER_NONE = 0, // Direct I/O BUFFER_MEMORY = 1, // Memory buffer with async flush BUFFER_DISK = 2 // Disk buffer for critical data }; //+------------------------------------------------------------------+ //| File Buffer Entry | //+------------------------------------------------------------------+ struct SFileBufferEntry { string data; datetime timestamp; int priority; int retry_count; SFileBufferEntry() { data = ""; timestamp = 0; priority = 0; retry_count = 0; } }; //+------------------------------------------------------------------+ //| Unified File I/O Manager | //+------------------------------------------------------------------+ class CUnifiedFileIO { private: static CUnifiedFileIO* s_instance; // Memory buffer SFileBufferEntry m_buffer[]; int m_buffer_count; int m_buffer_max; int m_buffer_critical_threshold; // Statistics int m_total_operations; int m_successful_operations; int m_failed_operations; int m_buffered_operations; // Configuration int m_max_retries; int m_retry_delay_ms; bool m_use_atomic_writes; bool m_verify_writes; int m_file_share_flags; // Error recovery CErrorRecovery* m_error_recovery; // Folder creation tracking string m_created_folders[]; int m_created_count; public: CUnifiedFileIO() { m_buffer_count = 0; m_buffer_max = 1000; m_buffer_critical_threshold = 800; ArrayResize(m_buffer, m_buffer_max); m_total_operations = 0; m_successful_operations = 0; m_failed_operations = 0; m_buffered_operations = 0; m_max_retries = 3; m_retry_delay_ms = 50; m_use_atomic_writes = true; m_verify_writes = true; m_file_share_flags = FILE_SHARE_READ | FILE_SHARE_WRITE; m_error_recovery = CErrorRecovery::Instance(); m_created_count = 0; ArrayResize(m_created_folders, 100); } ~CUnifiedFileIO() { // Flush any remaining buffered data FlushBuffer(true); // Force flush } static CUnifiedFileIO* Instance() { if(s_instance == NULL) s_instance = new CUnifiedFileIO(); return s_instance; } static void Destroy() { if(s_instance != NULL) { delete s_instance; s_instance = NULL; } } //+------------------------------------------------------------------+ //| Configuration | //+------------------------------------------------------------------+ void SetMaxRetries(int max_retries) { m_max_retries = max_retries; } void SetRetryDelay(int delay_ms) { m_retry_delay_ms = delay_ms; } void SetBufferSize(int size) { m_buffer_max = size; ArrayResize(m_buffer, size); } void SetAtomicWrites(bool atomic) { m_use_atomic_writes = atomic; } void SetVerifyWrites(bool verify) { m_verify_writes = verify; } //+------------------------------------------------------------------+ //| Safe file read with retry logic | //+------------------------------------------------------------------+ bool ReadFile(const string filename, string &content, bool use_common = true) { m_total_operations++; // Clear any pending errors ClearError(); // Ensure folder exists EnsureFolderExists(filename, use_common); int flags = FILE_READ | FILE_TXT | FILE_ANSI; if(use_common) flags |= FILE_COMMON; flags |= m_file_share_flags; int handle = INVALID_HANDLE; // Retry loop for(int attempt = 1; attempt <= m_max_retries; attempt++) { handle = FileOpen(filename, flags); if(handle != INVALID_HANDLE) break; int error_code = GetLastError(); if(error_code != ERR_FILE_NOT_FOUND && attempt < m_max_retries) { Log("File read attempt " + IntegerToString(attempt) + "/" + IntegerToString(m_max_retries) + " failed for " + filename + ": " + IntegerToString(error_code)); Sleep(m_retry_delay_ms * attempt); ClearError(); } else { break; } } if(handle == INVALID_HANDLE) { m_failed_operations++; int err = GetLastError(); if(err != 0) m_error_recovery.HandleError(err, "CUnifiedFileIO::ReadFile", filename); return false; } // Read content content = ""; while(!FileIsEnding(handle)) { content += FileReadString(handle) + "\n"; } FileClose(handle); m_successful_operations++; return true; } //+------------------------------------------------------------------+ //| Safe file write with atomic operation option | //+------------------------------------------------------------------+ bool WriteFile(const string filename, const string content, bool use_common = true, bool append = false) { m_total_operations++; // Clear any pending errors ClearError(); // Ensure folder exists EnsureFolderExists(filename, use_common); if(m_use_atomic_writes && !append) { return WriteFileAtomic(filename, content, use_common); } int flags = FILE_WRITE | FILE_TXT | FILE_ANSI; if(use_common) flags |= FILE_COMMON; if(append) flags |= FILE_READ; // Required for seek flags |= m_file_share_flags; int handle = INVALID_HANDLE; // Retry loop for(int attempt = 1; attempt <= m_max_retries; attempt++) { if(append) { handle = FileOpen(filename, flags, ','); if(handle != INVALID_HANDLE) FileSeek(handle, 0, SEEK_END); } else { handle = FileOpen(filename, flags); } if(handle != INVALID_HANDLE) break; int error_code = GetLastError(); if(attempt < m_max_retries) { Log("File write attempt " + IntegerToString(attempt) + "/" + IntegerToString(m_max_retries) + " failed for " + filename + ": " + IntegerToString(error_code)); Sleep(m_retry_delay_ms * attempt); ClearError(); } else { break; } } if(handle == INVALID_HANDLE) { m_failed_operations++; int err = GetLastError(); if(err != 0) m_error_recovery.HandleError(err, "CUnifiedFileIO::WriteFile", filename); // Buffer to memory as fallback BufferWrite(filename, content); return false; } // Write content bool write_ok = (FileWriteString(handle, content) > 0); FileFlush(handle); // Verify write if requested if(m_verify_writes && write_ok && !append) { long size = FileSize(handle); if(size <= 0) { Log("Write verification failed for " + filename); write_ok = false; } } FileClose(handle); if(write_ok) { m_successful_operations++; return true; } else { m_failed_operations++; BufferWrite(filename, content); return false; } } //+------------------------------------------------------------------+ //| Atomic file write (write to temp, then rename) | //+------------------------------------------------------------------+ bool WriteFileAtomic(const string filename, const string content, bool use_common = true) { string temp_filename = filename + ".tmp"; // Write to temp file int flags = FILE_WRITE | FILE_TXT | FILE_ANSI; if(use_common) flags |= FILE_COMMON; flags |= m_file_share_flags; int handle = FileOpen(temp_filename, flags); if(handle == INVALID_HANDLE) { // Fallback to non-atomic write return WriteFile(filename, content, use_common, false); } bool write_ok = (FileWriteString(handle, content) > 0); FileFlush(handle); long temp_size = FileSize(handle); FileClose(handle); if(!write_ok || temp_size <= 0) { // Clean up temp file FileDelete(temp_filename, use_common ? FILE_COMMON : 0); return false; } // Delete old file if exists if(FileIsExist(filename, use_common ? FILE_COMMON : 0)) { FileDelete(filename, use_common ? FILE_COMMON : 0); } // Rename temp to final // Note: MQL5 doesn't have direct FileRename, so we copy and delete bool renamed = FileCopy(temp_filename, use_common ? FILE_COMMON : 0, filename, use_common ? FILE_COMMON : 0, FILE_REWRITE); if(renamed) { FileDelete(temp_filename, use_common ? FILE_COMMON : 0); m_successful_operations++; return true; } else { // Cleanup FileDelete(temp_filename, use_common ? FILE_COMMON : 0); m_failed_operations++; BufferWrite(filename, content); return false; } } //+------------------------------------------------------------------+ //| Buffer write for failed operations | //+------------------------------------------------------------------+ void BufferWrite(const string filename, const string content) { if(m_buffer_count >= m_buffer_max) { // Remove oldest entry for(int i = 0; i < m_buffer_count - 1; i++) { m_buffer[i] = m_buffer[i + 1]; } m_buffer_count--; } SFileBufferEntry entry; entry.data = filename + "|" + content; entry.timestamp = TimeCurrent(); entry.priority = 1; entry.retry_count = 0; m_buffer[m_buffer_count] = entry; m_buffer_count++; m_buffered_operations++; if(m_buffer_count >= m_buffer_critical_threshold) { Log("WARNING: File buffer at " + IntegerToString(m_buffer_count) + "/" + IntegerToString(m_buffer_max)); } } //+------------------------------------------------------------------+ //| Flush buffer to disk | //+------------------------------------------------------------------+ int FlushBuffer(bool force = false) { if(m_buffer_count == 0) return 0; int flushed = 0; int max_flush = force ? m_buffer_count : MathMin(100, m_buffer_count); for(int i = 0; i < max_flush; i++) { SFileBufferEntry &entry = m_buffer[i]; // Parse filename and content int sep_pos = StringFind(entry.data, "|"); if(sep_pos < 0) continue; string filename = StringSubstr(entry.data, 0, sep_pos); string content = StringSubstr(entry.data, sep_pos + 1); if(WriteFile(filename, content, true, true)) // Append mode { flushed++; } else { entry.retry_count++; if(entry.retry_count >= m_max_retries) { Log("Failed to flush buffer entry after " + IntegerToString(m_max_retries) + " retries"); // Keep in buffer for next attempt } } } // Remove flushed entries int remaining = 0; for(int i = 0; i < m_buffer_count; i++) { if(i < flushed) continue; // Skip flushed entries m_buffer[remaining] = m_buffer[i]; remaining++; } m_buffer_count = remaining; ArrayResize(m_buffer, m_buffer_max); // Ensure capacity if(flushed > 0) { Log("Flushed " + IntegerToString(flushed) + " buffer entries, " + IntegerToString(m_buffer_count) + " remaining"); } return flushed; } //+------------------------------------------------------------------+ //| Ensure folder exists | //+------------------------------------------------------------------+ void EnsureFolderExists(const string path, bool use_common) { // Extract folder path int last_sep = -1; for(int i = StringLen(path) - 1; i >= 0; i--) { string c = StringSubstr(path, i, 1); if(c == "\\" || c == "/") { last_sep = i; break; } } if(last_sep < 0) return; // No folder component string folder = StringSubstr(path, 0, last_sep); if(folder == "") return; // Check if already created for(int i = 0; i < m_created_count; i++) { if(m_created_folders[i] == folder) return; } // Create folder int flags = use_common ? FILE_COMMON : 0; bool created = FolderCreate(folder, flags); if(created || GetLastError() == 0) { // Track created folder if(m_created_count < ArraySize(m_created_folders)) { m_created_folders[m_created_count] = folder; m_created_count++; } } ClearError(); } //+------------------------------------------------------------------+ //| Statistics and reporting | //+------------------------------------------------------------------+ string GetStatistics() { string stats = StringFormat( "=== Unified File I/O Statistics ===\n" + "Total Operations: %d\n" + "Successful: %d (%.1f%%)\n" + "Failed: %d (%.1f%%)\n" + "Buffered: %d\n" + "Buffer: %d/%d entries", m_total_operations, m_successful_operations, (m_total_operations > 0 ? 100.0 * m_successful_operations / m_total_operations : 0), m_failed_operations, (m_total_operations > 0 ? 100.0 * m_failed_operations / m_total_operations : 0), m_buffered_operations, m_buffer_count, m_buffer_max ); return stats; } //+------------------------------------------------------------------+ //| Utility functions | //+------------------------------------------------------------------+ void Log(const string message) { Print("[UnifiedFileIO] " + message); } void ClearError() { int _ = GetLastError(); } // Getters int GetBufferedCount() const { return m_buffer_count; } bool IsBufferCritical() const { return m_buffer_count >= m_buffer_critical_threshold; } int GetBufferUtilizationPercent() const { return (m_buffer_count * 100) / m_buffer_max; } }; // Static instance initialization CUnifiedFileIO* CUnifiedFileIO::s_instance = NULL; // Convenience macros #define SAFE_FILE_READ(filename, content, use_common) \ CUnifiedFileIO::Instance().ReadFile(filename, content, use_common) #define SAFE_FILE_WRITE(filename, content, use_common) \ CUnifiedFileIO::Instance().WriteFile(filename, content, use_common, false) #define SAFE_FILE_APPEND(filename, content, use_common) \ CUnifiedFileIO::Instance().WriteFile(filename, content, use_common, true) #define FLUSH_FILE_BUFFER(force) \ CUnifiedFileIO::Instance().FlushBuffer(force) #endif // CUNIFIEDFILEIO_MQH