diff --git a/src/LogExpert.Core/Classes/Log/LogfileReader.cs b/src/LogExpert.Core/Classes/Log/LogfileReader.cs index 445ffd1e..a05ffcac 100644 --- a/src/LogExpert.Core/Classes/Log/LogfileReader.cs +++ b/src/LogExpert.Core/Classes/Log/LogfileReader.cs @@ -18,7 +18,17 @@ public class LogfileReader : IAutoLogLineColumnizerCallback, IDisposable private readonly GetLogLineFx _logLineFx; - private readonly string _fileName; + private string FileName + { + get; init + { + var uri = new Uri(value); + if (uri.IsFile) + { + field = uri.LocalPath; // Convert the URI to a local file path + } + } + } private readonly int _max_buffers; private readonly int _maxLinesPerBuffer; @@ -34,7 +44,6 @@ public class LogfileReader : IAutoLogLineColumnizerCallback, IDisposable private long _fileLength; private Task _garbageCollectorTask; - private Task _monitorTask; private readonly CancellationTokenSource _cts = new(); private bool _isDeleted; @@ -46,6 +55,7 @@ public class LogfileReader : IAutoLogLineColumnizerCallback, IDisposable private ReaderWriterLock _lruCacheDictLock; + private FileSystemWatcher _watcher; private bool _shouldStop; private bool _disposed; private ILogFileInfo _watchedILogFileInfo; @@ -56,12 +66,15 @@ public class LogfileReader : IAutoLogLineColumnizerCallback, IDisposable public LogfileReader (string fileName, EncodingOptions encodingOptions, bool multiFile, int bufferCount, int linesPerBuffer, MultiFileOptions multiFileOptions, IPluginRegistry pluginRegistry) { + PreProcessColumnizer = null; + IsXmlMode = false; +//TODO: This is not a good approach. The constructor should be called only if it has valid options and throw an exception if not. this is a bad mix. if (fileName == null) { return; } - _fileName = fileName; + FileName = fileName; EncodingOptions = encodingOptions; IsMultiFile = multiFile; _max_buffers = bufferCount; @@ -97,6 +110,9 @@ public LogfileReader (string fileName, EncodingOptions encodingOptions, bool mul public LogfileReader (string[] fileNames, EncodingOptions encodingOptions, int bufferCount, int linesPerBuffer, MultiFileOptions multiFileOptions, IPluginRegistry pluginRegistry) { + PreProcessColumnizer = null; + IsXmlMode = false; +//TODO: This is not a good approach. The constructor should be called only if it has valid options and throw an exception if not. this is a bad mix. if (fileNames == null || fileNames.Length < 1) { return; @@ -120,7 +136,7 @@ public LogfileReader (string[] fileNames, EncodingOptions encodingOptions, int b } _watchedILogFileInfo = fileInfo; - _fileName = fileInfo.FullName; + FileName = fileInfo.FullName; StartGCThread(); } @@ -128,7 +144,6 @@ public LogfileReader (string[] fileNames, EncodingOptions encodingOptions, int b #endregion #region Delegates - private delegate Task GetLogLineFx (int lineNum); #endregion @@ -178,7 +193,7 @@ public int LineCount public IXmlLogConfiguration XmlLogConfig { get; set; } - public IPreProcessColumnizer PreProcessColumnizer { get; set; } + public IPreProcessColumnizer PreProcessColumnizer { get; set; } = null; public EncodingOptions EncodingOptions { @@ -256,7 +271,7 @@ public void ReadFiles () /// public int ShiftBuffers () { - _logger.Info(CultureInfo.InvariantCulture, "ShiftBuffers() begin for {0}{1}", _fileName, IsMultiFile ? " (MultiFile)" : ""); + _logger.Info(CultureInfo.InvariantCulture, "ShiftBuffers() begin for {0}{1}", FileName, IsMultiFile ? " (MultiFile)" : ""); AcquireBufferListWriterLock(); var offset = 0; _isLineCountDirty = true; @@ -559,28 +574,142 @@ public int GetRealLineNumForVirtualLineNum (int lineNum) return result; } - public void StartMonitoring () + public async Task StartMonitoring () { - _logger.Info(CultureInfo.InvariantCulture, "startMonitoring()"); - _monitorTask = Task.Run(MonitorThreadProc, _cts.Token); + _logger.Info("StartMonitoring() for file ${_watchedILogFileInfo.FullName}"); + + await Task.Run(() => + { + _logger.Info("MonitorThreadProc() for file {0}", _watchedILogFileInfo.FullName); + + long oldSize = 0; + try + { + OnLoadingStarted(new LoadFileEventArgs(FileName, 0, false, 0, false)); + ReadFiles(); + if (!_isDeleted) + { + oldSize = _fileLength; + OnLoadingFinished(); + } + } + catch (Exception e) + { + _logger.Error(e); + } + }); + + try + { + _watcher = new FileSystemWatcher + { + NotifyFilter = //NotifyFilters.Attributes + //| NotifyFilters.CreationTime + //| NotifyFilters.DirectoryName + //| + NotifyFilters.FileName + //| NotifyFilters.LastAccess + | NotifyFilters.LastWrite + //| NotifyFilters.Security + | NotifyFilters.Size, + + Path = Path.GetDirectoryName(FileName) ?? throw new ArgumentException("Invalid file path"), + Filter = Path.GetFileName(FileName), // Sets filter to the specific + EnableRaisingEvents = true + }; + } + catch (UnauthorizedAccessException ex) + { + Console.WriteLine($"Access denied: {ex.Message}"); + } + catch (ArgumentException ex) + { + Console.WriteLine($"Invalid argument: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + } + + _watcher.Error += (sender, e) => + { + Console.WriteLine($"Error occurred: {e.GetException().Message}"); + Task.Delay(5000).ContinueWith(_ => + { + try + { + Console.WriteLine("Attempting to restart the watcher..."); + _watcher.EnableRaisingEvents = true; + } + catch (Exception ex) + { + Console.WriteLine($"Failed to restart the watcher: {ex.Message}"); + } + }); + }; + _watcher.Changed += OnFileChanged; + _watcher.Created += OnCreated; + _watcher.Deleted += OnFileDeleted; + _watcher.Renamed += OnFileRenamed; + _watcher.Error += OnFileError; + _shouldStop = false; } + private void OnFileError (object sender, ErrorEventArgs e) + { + throw new NotImplementedException(); + } + + private void OnFileRenamed (object sender, RenamedEventArgs e) + { + throw new NotImplementedException(); + } + + private void OnCreated (object sender, FileSystemEventArgs e) + { + //TODO: This should be deleted before merge? + throw new NotImplementedException(); + } + + private void OnFileDeleted (object sender, FileSystemEventArgs e) + { + MonitoredFileNotFound(); + } + + private void OnFileChanged (object sender, FileSystemEventArgs e) + { + try + { + _watchedILogFileInfo.FileHasChanged(); + _fileLength = _watchedILogFileInfo.Length; + FileChanged(); + } + catch (FileNotFoundException ex) + { + MonitoredFileNotFound(); + } + catch (Exception ex) + { + throw new NotImplementedException(); + } + } + public void StopMonitoring () { _logger.Info(CultureInfo.InvariantCulture, "stopMonitoring()"); _shouldStop = true; - Thread.Sleep(_watchedILogFileInfo.PollInterval); // leave time for the threads to stop by themselves - - if (_monitorTask != null) + if (_watcher != null) { - if (_monitorTask.Status == TaskStatus.Running) // if thread has not finished, abort it - { - _cts.Cancel(); - } + _watcher.EnableRaisingEvents = false; // Stop watching + _watcher.Dispose(); // Release resources + _watcher = null; // Clear the reference + } + Thread.Sleep(_watchedILogFileInfo.PollInterval); // leave time for the threads to stop by themselves + if (_garbageCollectorTask.IsCanceled == false) { if (_garbageCollectorTask.Status == TaskStatus.Running) // if thread has not finished, abort it @@ -618,11 +747,11 @@ public void DeleteAllContent () { if (_contentDeleted) { - _logger.Debug(CultureInfo.InvariantCulture, "Buffers for {0} already deleted.", Util.GetNameFromPath(_fileName)); + _logger.Debug(CultureInfo.InvariantCulture, "Buffers for {0} already deleted.", Util.GetNameFromPath(FileName)); return; } - _logger.Info(CultureInfo.InvariantCulture, "Deleting all log buffers for {0}. Used mem: {1:N0}", Util.GetNameFromPath(_fileName), GC.GetTotalMemory(true)); //TODO [Z] uh GC collect calls creepy + _logger.Info(CultureInfo.InvariantCulture, "Deleting all log buffers for {0}. Used mem: {1:N0}", Util.GetNameFromPath(FileName), GC.GetTotalMemory(true)); //TODO [Z] uh GC collect calls creepy AcquireBufferListWriterLock(); _lruCacheDictLock.AcquireWriterLock(Timeout.Infinite); _disposeLock.AcquireWriterLock(Timeout.Infinite); @@ -689,7 +818,7 @@ public void LogBufferInfoForLine (int lineNum) if (buffer == null) { ReleaseBufferListReaderLock(); - _logger.Error("Cannot find buffer for line {0}, file: {1}{2}", lineNum, _fileName, IsMultiFile ? " (MultiFile)" : ""); + _logger.Error("Cannot find buffer for line {0}, file: {1}{2}", lineNum, FileName, IsMultiFile ? " (MultiFile)" : ""); return; } @@ -714,7 +843,7 @@ public void LogBufferDiagnostic () _lruCacheDictLock.ReleaseReaderLock(); AcquireBufferListReaderLock(); - _logger.Info(CultureInfo.InvariantCulture, "File: {0}\r\nBuffer count: {1}\r\nDisposed buffers: {2}", _fileName, _bufferList.Count, _bufferList.Count - cacheCount); + _logger.Info(CultureInfo.InvariantCulture, "File: {0}\r\nBuffer count: {1}\r\nDisposed buffers: {2}", FileName, _bufferList.Count, _bufferList.Count - cacheCount); var lineNum = 0; long disposeSum = 0; long maxDispose = 0; @@ -770,7 +899,7 @@ private Task GetLogLineInternal (int lineNum) if (logBuffer == null) { ReleaseBufferListReaderLock(); - _logger.Error("Cannot find buffer for line {0}, file: {1}{2}", lineNum, _fileName, IsMultiFile ? " (MultiFile)" : ""); + _logger.Error("Cannot find buffer for line {0}, file: {1}{2}", lineNum, FileName, IsMultiFile ? " (MultiFile)" : ""); return null; } @@ -1140,7 +1269,7 @@ private void GarbageCollectLruCache () #if DEBUG if (diff > 0) { - _logger.Info(CultureInfo.InvariantCulture, "Removing {0} entries from LRU cache for {1}", diff, Util.GetNameFromPath(_fileName)); + _logger.Info(CultureInfo.InvariantCulture, "Removing {0} entries from LRU cache for {1}", diff, Util.GetNameFromPath(FileName)); } #endif SortedList useSorterList = []; @@ -1457,74 +1586,6 @@ private LogBuffer GetFirstBufferForFileByLogBuffer (LogBuffer logBuffer) return resultBuffer; } - private void MonitorThreadProc () - { - Thread.CurrentThread.Name = "MonitorThread"; - //IFileSystemPlugin fs = PluginRegistry.GetInstance().FindFileSystemForUri(this.watchedILogFileInfo.FullName); - _logger.Info(CultureInfo.InvariantCulture, "MonitorThreadProc() for file {0}", _watchedILogFileInfo.FullName); - - long oldSize; - try - { - OnLoadingStarted(new LoadFileEventArgs(_fileName, 0, false, 0, false)); - ReadFiles(); - if (!_isDeleted) - { - oldSize = _fileLength; - OnLoadingFinished(); - } - } - catch (Exception e) - { - _logger.Error(e); - } - - while (!_shouldStop) - { - try - { - var pollInterval = _watchedILogFileInfo.PollInterval; - //#if DEBUG - // if (_logger.IsDebug) - // { - // _logger.logDebug("Poll interval for " + this.fileName + ": " + pollInterval); - // } - //#endif - Thread.Sleep(pollInterval); - } - catch (Exception e) - { - _logger.Error(e); - } - - if (_shouldStop) - { - return; - } - - try - { - if (_watchedILogFileInfo.FileHasChanged()) - { - _fileLength = _watchedILogFileInfo.Length; - if (_fileLength == -1) - { - MonitoredFileNotFound(); - } - else - { - oldSize = _fileLength; - FileChanged(); - } - } - } - catch (FileNotFoundException) - { - MonitoredFileNotFound(); - } - } - } - private void MonitoredFileNotFound () { long oldSize; @@ -1556,7 +1617,7 @@ private void FileChanged () var newSize = _fileLength; //if (this.currFileSize != newSize) { - _logger.Info(CultureInfo.InvariantCulture, "file size changed. new size={0}, file: {1}", newSize, _fileName); + _logger.Info(CultureInfo.InvariantCulture, "file size changed. new size={0}, file: {1}", newSize, FileName); FireChangeEvent(); } } @@ -1579,7 +1640,7 @@ private void FireChangeEvent () { // ReloadBufferList(); // removed because reloading is triggered by owning LogWindow // Trigger "new file" handling (reload) - OnLoadFile(new LoadFileEventArgs(_fileName, 0, true, _fileLength, true)); + OnLoadFile(new LoadFileEventArgs(FileName, 0, true, _fileLength, true)); if (_isDeleted) { diff --git a/src/LogExpert.Core/LogExpert.Core.csproj b/src/LogExpert.Core/LogExpert.Core.csproj index 094d3861..65121833 100644 --- a/src/LogExpert.Core/LogExpert.Core.csproj +++ b/src/LogExpert.Core/LogExpert.Core.csproj @@ -3,6 +3,7 @@ net8.0 true + preview diff --git a/src/LogExpert.Resources/Resources.Designer.cs b/src/LogExpert.Resources/Resources.Designer.cs index 81aaf6ae..98bd2e92 100644 --- a/src/LogExpert.Resources/Resources.Designer.cs +++ b/src/LogExpert.Resources/Resources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -39,7 +39,7 @@ internal Resources() { public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LogExpert.Resources.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LogExpert.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; diff --git a/src/LogExpert.sln b/src/LogExpert.sln index e215b839..a5d9b6c2 100644 --- a/src/LogExpert.sln +++ b/src/LogExpert.sln @@ -27,6 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Solution Items\AssemblyInfo.cs = Solution Items\AssemblyInfo.cs Directory.Build.props = Directory.Build.props Directory.Packages.props = Directory.Packages.props + ..\global.json = ..\global.json Solution Items\usedComponents.json = Solution Items\usedComponents.json EndProjectSection EndProject