|
1 | 1 | using System; |
2 | 2 | using System.ComponentModel; |
| 3 | +using System.IO; |
3 | 4 | using System.Text.Json.Serialization; |
4 | 5 | using System.Text.RegularExpressions; |
5 | 6 | using System.Windows; |
@@ -133,6 +134,13 @@ public bool HasNewContent |
133 | 134 | // Validation state |
134 | 135 | public bool IsRegexValid { get; private set; } |
135 | 136 |
|
| 137 | + // Virtual content management for large files |
| 138 | + private readonly string? _tempFilePath; |
| 139 | + private long _totalContentLength; |
| 140 | + private const long MAX_MEMORY_CONTENT = 25_000_000; // 25MB in memory |
| 141 | + private const long KEEP_RECENT_CONTENT = 5_000_000; // Keep last 5MB in memory |
| 142 | + private bool _isUsingDiskStorage = false; |
| 143 | + |
136 | 144 | /// <summary> |
137 | 145 | /// Creates a new TabInfo with references to all controls |
138 | 146 | /// </summary> |
@@ -163,6 +171,11 @@ public TabInfo( |
163 | 171 | AfterLinesCurrent = 0; |
164 | 172 | IsAutoCreated = isAutoCreated; |
165 | 173 |
|
| 174 | + // Initialize virtual content management |
| 175 | + _tempFilePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"X4LogWatcher_Tab_{Guid.NewGuid():N}.tmp"); |
| 176 | + _totalContentLength = 0; |
| 177 | + _isUsingDiskStorage = false; |
| 178 | + |
166 | 179 | // Wire up events |
167 | 180 | RegexTextBox.TextChanged += RegexTextBox_TextChanged; |
168 | 181 |
|
@@ -308,6 +321,9 @@ public void AppendContent(string text) |
308 | 321 |
|
309 | 322 | ContentTextBox.AppendText(text); |
310 | 323 | ContentTextBox.ScrollToEnd(); |
| 324 | + |
| 325 | + // Check if we need to optimize memory after adding content |
| 326 | + OptimizeMemoryIfNeeded(); |
311 | 327 | } |
312 | 328 |
|
313 | 329 | /// <summary> |
@@ -339,6 +355,102 @@ private void UpdateNewContentIndicator() |
339 | 355 | UpdateTabHeader(); |
340 | 356 | } |
341 | 357 |
|
| 358 | + /// <summary> |
| 359 | + /// Get estimated memory usage of this tab's content |
| 360 | + /// </summary> |
| 361 | + /// <returns>Estimated memory usage in bytes</returns> |
| 362 | + public long GetEstimatedMemoryUsage() |
| 363 | + { |
| 364 | + if (_disposed) |
| 365 | + return 0; |
| 366 | + |
| 367 | + try |
| 368 | + { |
| 369 | + // Estimate memory usage: string content + UI overhead |
| 370 | + var textLength = ContentTextBox?.Text?.Length ?? 0; |
| 371 | + return textLength * 2; // Unicode characters are 2 bytes each |
| 372 | + } |
| 373 | + catch |
| 374 | + { |
| 375 | + return 0; |
| 376 | + } |
| 377 | + } |
| 378 | + |
| 379 | + /// <summary> |
| 380 | + /// Optimize memory usage by moving old content to disk if needed |
| 381 | + /// </summary> |
| 382 | + public void OptimizeMemoryIfNeeded() |
| 383 | + { |
| 384 | + if (_disposed || ContentTextBox == null) |
| 385 | + return; |
| 386 | + |
| 387 | + try |
| 388 | + { |
| 389 | + var currentLength = ContentTextBox.Text.Length; |
| 390 | + |
| 391 | + if (currentLength > MAX_MEMORY_CONTENT) |
| 392 | + { |
| 393 | + // Keep only the most recent content in memory |
| 394 | + var text = ContentTextBox.Text; |
| 395 | + var keepFromIndex = Math.Max(0, currentLength - (int)KEEP_RECENT_CONTENT); |
| 396 | + |
| 397 | + // Find a good line boundary to cut at |
| 398 | + var cutIndex = text.LastIndexOf('\n', keepFromIndex); |
| 399 | + if (cutIndex < 0) |
| 400 | + cutIndex = keepFromIndex; |
| 401 | + |
| 402 | + var oldContent = text.Substring(0, cutIndex); |
| 403 | + var recentContent = text.Substring(cutIndex); |
| 404 | + |
| 405 | + // Save old content to disk if we have a temp file path |
| 406 | + if (!string.IsNullOrEmpty(_tempFilePath)) |
| 407 | + { |
| 408 | + File.AppendAllText(_tempFilePath, oldContent); |
| 409 | + _isUsingDiskStorage = true; |
| 410 | + } |
| 411 | + |
| 412 | + // Keep only recent content in memory |
| 413 | + ContentTextBox.Text = recentContent; |
| 414 | + _totalContentLength += oldContent.Length; |
| 415 | + |
| 416 | + System.Diagnostics.Debug.WriteLine( |
| 417 | + $"Tab '{TabName}' optimized: moved {oldContent.Length} chars to disk, kept {recentContent.Length} in memory" |
| 418 | + ); |
| 419 | + } |
| 420 | + } |
| 421 | + catch (Exception ex) |
| 422 | + { |
| 423 | + System.Diagnostics.Debug.WriteLine($"Error optimizing memory for tab '{TabName}': {ex.Message}"); |
| 424 | + } |
| 425 | + } |
| 426 | + |
| 427 | + /// <summary> |
| 428 | + /// Get the full content including disk-stored content (expensive operation) |
| 429 | + /// </summary> |
| 430 | + public string GetFullContent() |
| 431 | + { |
| 432 | + if (_disposed) |
| 433 | + return string.Empty; |
| 434 | + |
| 435 | + try |
| 436 | + { |
| 437 | + var memoryContent = ContentTextBox?.Text ?? string.Empty; |
| 438 | + |
| 439 | + if (_isUsingDiskStorage && !string.IsNullOrEmpty(_tempFilePath) && File.Exists(_tempFilePath)) |
| 440 | + { |
| 441 | + var diskContent = File.ReadAllText(_tempFilePath); |
| 442 | + return diskContent + memoryContent; |
| 443 | + } |
| 444 | + |
| 445 | + return memoryContent; |
| 446 | + } |
| 447 | + catch (Exception ex) |
| 448 | + { |
| 449 | + System.Diagnostics.Debug.WriteLine($"Error reading full content for tab '{TabName}': {ex.Message}"); |
| 450 | + return ContentTextBox?.Text ?? string.Empty; |
| 451 | + } |
| 452 | + } |
| 453 | + |
342 | 454 | #region IDisposable Implementation |
343 | 455 |
|
344 | 456 | /// <summary> |
@@ -389,6 +501,22 @@ protected virtual void Dispose(bool disposing) |
389 | 501 | { |
390 | 502 | ContentTextBox.Clear(); |
391 | 503 | } |
| 504 | + |
| 505 | + // Clean up temporary file if it exists |
| 506 | + if (_isUsingDiskStorage && !string.IsNullOrEmpty(_tempFilePath)) |
| 507 | + { |
| 508 | + try |
| 509 | + { |
| 510 | + if (File.Exists(_tempFilePath)) |
| 511 | + { |
| 512 | + File.Delete(_tempFilePath); |
| 513 | + } |
| 514 | + } |
| 515 | + catch (Exception deleteEx) |
| 516 | + { |
| 517 | + System.Diagnostics.Debug.WriteLine($"Error deleting temp file {_tempFilePath}: {deleteEx.Message}"); |
| 518 | + } |
| 519 | + } |
392 | 520 | } |
393 | 521 | catch (Exception ex) |
394 | 522 | { |
|
0 commit comments