| //+------------------------------------------------------------------+
|
| //| 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
|