1010using SixLabors . ImageSharp ;
1111using SixLabors . ImageSharp . PixelFormats ;
1212using System ;
13+ using System . Collections . Concurrent ;
1314using System . Collections . Generic ;
1415using System . Collections . ObjectModel ;
1516using System . IO ;
2021
2122namespace OpenLoco . Gui . Models
2223{
23- public class ObjectEditorModel
24+ public class ObjectEditorModel : IDisposable
2425 {
2526 public EditorSettings Settings { get ; private set ; }
2627
@@ -58,11 +59,15 @@ public class ObjectEditorModel
5859
5960 public HttpClient ? WebClient { get ; }
6061
62+ readonly ConcurrentQueue < string > logQueue = new ( ) ;
63+ readonly SemaphoreSlim logFileLock = new ( 1 , 1 ) ; // Allow only 1 concurrent write
64+
6165 public ObjectEditorModel ( )
6266 {
6367 Logger = new Logger ( ) ;
6468 LoggerObservableLogs = [ ] ;
6569 Logger . LogAdded += ( sender , laea ) => Dispatcher . UIThread . Post ( ( ) => LoggerObservableLogs . Insert ( 0 , laea . Log ) ) ;
70+ Logger . LogAdded += ( sender , laea ) => LogAsync ( laea . Log . ToString ( ) ) . ConfigureAwait ( false ) ;
6671
6772 LoadSettings ( ) ;
6873 InitialiseDownloadDirectory ( ) ;
@@ -80,6 +85,43 @@ public ObjectEditorModel()
8085 }
8186 }
8287
88+ public async Task LogAsync ( string message )
89+ {
90+ logQueue . Enqueue ( message ) ;
91+ await WriteLogsToFileAsync ( ) ; // Start the async writing process
92+ }
93+
94+ async Task WriteLogsToFileAsync ( )
95+ {
96+ if ( logQueue . IsEmpty )
97+ {
98+ return ; // Nothing to write
99+ }
100+
101+ if ( await logFileLock . WaitAsync ( 0 ) ) // Non-blocking wait if available.
102+ {
103+ try
104+ {
105+ while ( logQueue . TryDequeue ( out var logMessage ) )
106+ {
107+ try
108+ {
109+ await File . AppendAllTextAsync ( LoggingFile , logMessage + Environment . NewLine ) ;
110+ }
111+ catch ( Exception ex )
112+ {
113+ Console . WriteLine ( $ "Error writing to log file: { ex . Message } ") ;
114+ // Consider logging to a separate error file or using a more robust logging framework
115+ }
116+ }
117+ }
118+ finally
119+ {
120+ _ = logFileLock . Release ( ) ; // Release the semaphore
121+ }
122+ }
123+ }
124+
83125 void LoadSettings ( )
84126 {
85127 Settings = EditorSettings . Load ( SettingsFile , Logger ) ;
@@ -430,5 +472,34 @@ public async Task UploadDatToServer(ObjectIndexEntry dat)
430472 await Client . UploadDatFileAsync ( WebClient , dat . Filename , await File . ReadAllBytesAsync ( filename ) , lastModifiedTime , Logger ) ;
431473 await Task . Delay ( 100 ) ; // wait 100ms, ie don't DoS the server
432474 }
475+
476+ public async Task CloseAsync ( )
477+ {
478+ // Wait for any pending writes to complete.
479+ await logFileLock . WaitAsync ( ) ; // Acquire the semaphore
480+ _ = logFileLock . Release ( ) ; // Release it immediately after. This is just to wait.
481+
482+ // Process any remaining logs in the queue.
483+ await WriteLogsToFileAsync ( ) ; // One last flush.
484+
485+ logFileLock . Dispose ( ) ; // Dispose of the semaphore
486+ }
487+
488+ public void Dispose ( )
489+ {
490+ Dispose ( true ) ;
491+ GC . SuppressFinalize ( this ) ; // Important for proper disposal
492+ }
493+
494+ protected virtual void Dispose ( bool disposing )
495+ {
496+ if ( disposing )
497+ {
498+ // Wait for logging to complete synchronously.
499+ Task . Run ( CloseAsync ) . Wait ( ) ; // <--- Key change
500+ logFileLock ? . Dispose ( ) ; // Dispose of the semaphore
501+ }
502+ }
503+
433504 }
434505}
0 commit comments