mql5/Experts/Advisors/DualEA/Include/CUnifiedFileIO.mqh

552 lines
16 KiB
MQL5
Raw Permalink Normal View History

2026-04-14 20:08:20 -04:00
//+------------------------------------------------------------------+
//| 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