add events
This commit is contained in:
		| @@ -89,6 +89,8 @@ public class MarePlugin : MediatorSubscriberBase, IHostedService | |||||||
|     { |     { | ||||||
|         var version = Assembly.GetExecutingAssembly().GetName().Version!; |         var version = Assembly.GetExecutingAssembly().GetName().Version!; | ||||||
|         Logger.LogInformation("Launching {name} {major}.{minor}.{build}-lop{rev}", "Loporrit Sync", version.Major, version.Minor, version.Build, version.Revision); |         Logger.LogInformation("Launching {name} {major}.{minor}.{build}-lop{rev}", "Loporrit Sync", version.Major, version.Minor, version.Build, version.Revision); | ||||||
|  |         Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(MarePlugin), Services.Events.EventSeverity.Informational, | ||||||
|  |             $"Starting Loporrit Sync {version.Major}.{version.Minor}.{version.Build}-lop{version.Revision}"))); | ||||||
|  |  | ||||||
|         Mediator.Subscribe<SwitchToMainUiMessage>(this, (msg) => _ = Task.Run(WaitForPlayerAndLaunchCharacterManager)); |         Mediator.Subscribe<SwitchToMainUiMessage>(this, (msg) => _ = Task.Run(WaitForPlayerAndLaunchCharacterManager)); | ||||||
|         Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn()); |         Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn()); | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ using MareSynchronos.Interop; | |||||||
| using MareSynchronos.PlayerData.Factories; | using MareSynchronos.PlayerData.Factories; | ||||||
| using MareSynchronos.PlayerData.Pairs; | using MareSynchronos.PlayerData.Pairs; | ||||||
| using MareSynchronos.Services; | using MareSynchronos.Services; | ||||||
|  | using MareSynchronos.Services.Events; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| using MareSynchronos.Services.ServerConfiguration; | using MareSynchronos.Services.ServerConfiguration; | ||||||
| using MareSynchronos.Utils; | using MareSynchronos.Utils; | ||||||
| @@ -111,6 +112,9 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|             if (_isVisible != value) |             if (_isVisible != value) | ||||||
|             { |             { | ||||||
|                 _isVisible = value; |                 _isVisible = value; | ||||||
|  |                 string text = "User Visibility Changed, now: " + (_isVisible ? "Is Visible" : "Is not Visible"); | ||||||
|  |                 Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), | ||||||
|  |                     EventSeverity.Informational, text))); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -126,6 +130,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|     { |     { | ||||||
|         if (_dalamudUtil.IsInCombat) |         if (_dalamudUtil.IsInCombat) | ||||||
|         { |         { | ||||||
|  |             Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Warning, | ||||||
|  |                 "Cannot apply character data: you are in combat, deferring application"))); | ||||||
|             Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat", applicationBase); |             Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat", applicationBase); | ||||||
|             _dataReceivedInCombat = new(applicationBase, characterData, forceApplyCustomization); |             _dataReceivedInCombat = new(applicationBase, characterData, forceApplyCustomization); | ||||||
|             SetUploading(isUploading: false); |             SetUploading(isUploading: false); | ||||||
| @@ -134,6 +140,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|         if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero)) |         if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero)) | ||||||
|         { |         { | ||||||
|  |             Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Warning, | ||||||
|  |                 "Cannot apply character data: Receiving Player is in an invalid state, deferring application"))); | ||||||
|             Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}", |             Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}", | ||||||
|                 applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero); |                 applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero); | ||||||
|             var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _cachedData, Logger, |             var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _cachedData, Logger, | ||||||
| @@ -154,10 +162,15 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|         if (_dalamudUtil.IsInCutscene || _dalamudUtil.IsInGpose || !_ipcManager.CheckPenumbraApi() || !_ipcManager.CheckGlamourerApi()) |         if (_dalamudUtil.IsInCutscene || _dalamudUtil.IsInGpose || !_ipcManager.CheckPenumbraApi() || !_ipcManager.CheckGlamourerApi()) | ||||||
|         { |         { | ||||||
|  |             Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Warning, | ||||||
|  |                 "Cannot apply character data: you are in GPose, a Cutscene or Penumbra/Glamourer is not available"))); | ||||||
|             Logger.LogInformation("[BASE-{appbase}] Application of data for {player} while in cutscene/gpose or Penumbra/Glamourer unavailable, returning", applicationBase, this); |             Logger.LogInformation("[BASE-{appbase}] Application of data for {player} while in cutscene/gpose or Penumbra/Glamourer unavailable, returning", applicationBase, this); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, | ||||||
|  |             "Applying Character Data"))); | ||||||
|  |  | ||||||
|         _forceApplyMods |= forceApplyCustomization; |         _forceApplyMods |= forceApplyCustomization; | ||||||
|  |  | ||||||
|         var charaDataToUpdate = characterData.CheckUpdatedData(applicationBase, _cachedData?.DeepClone() ?? new(), Logger, this, forceApplyCustomization, _forceApplyMods); |         var charaDataToUpdate = characterData.CheckUpdatedData(applicationBase, _cachedData?.DeepClone() ?? new(), Logger, this, forceApplyCustomization, _forceApplyMods); | ||||||
| @@ -217,6 +230,11 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|             _charaHandler?.Dispose(); |             _charaHandler?.Dispose(); | ||||||
|             _charaHandler = null; |             _charaHandler = null; | ||||||
|  |  | ||||||
|  |             if (!string.IsNullOrEmpty(name)) | ||||||
|  |             { | ||||||
|  |                 Mediator.Publish(new EventMessage(new Event(name, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, "Disposing User"))); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             if (_lifetime.IsCancellationRequested) return; |             if (_lifetime.IsCancellationRequested) return; | ||||||
|  |  | ||||||
|             Logger.LogDebug("[{applicationId}] Removing Temp Collection for {name} ({user})", applicationId, name, OnlineUser); |             Logger.LogDebug("[{applicationId}] Removing Temp Collection for {name} ({user})", applicationId, name, OnlineUser); | ||||||
| @@ -365,6 +383,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|                     Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData); |                     Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData); | ||||||
|                     if (toDownloadReplacements.Any()) |                     if (toDownloadReplacements.Any()) | ||||||
|                     { |                     { | ||||||
|  |                         Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, | ||||||
|  |                             $"Starting download for {toDownloadReplacements.Count} files"))); | ||||||
|                         await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); |                         await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); | ||||||
|                         _downloadManager.CancelDownload(); |                         _downloadManager.CancelDownload(); | ||||||
|                     } |                     } | ||||||
| @@ -469,6 +489,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|             Logger.LogDebug("One-Time Initializing {this}", this); |             Logger.LogDebug("One-Time Initializing {this}", this); | ||||||
|             Initialize(pc.Name); |             Initialize(pc.Name); | ||||||
|             Logger.LogDebug("One-Time Initialized {this}", this); |             Logger.LogDebug("One-Time Initialized {this}", this); | ||||||
|  |             Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, | ||||||
|  |                 $"Initializing User For Character {pc.Name}"))); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (_charaHandler?.Address != nint.Zero && !IsVisible) |         if (_charaHandler?.Address != nint.Zero && !IsVisible) | ||||||
|   | |||||||
| @@ -152,7 +152,7 @@ public class Pair | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             CachedPlayer?.Dispose(); |             CachedPlayer?.Dispose(); | ||||||
|             CachedPlayer = _cachedPlayerFactory.Create(_onlineUserIdentDto!); |             CachedPlayer = _cachedPlayerFactory.Create(new OnlineUserIdentDto(UserData, _onlineUserIdentDto!.Ident)); | ||||||
|         } |         } | ||||||
|         finally |         finally | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ using MareSynchronos.API.Dto.Group; | |||||||
| using MareSynchronos.API.Dto.User; | using MareSynchronos.API.Dto.User; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
| using MareSynchronos.PlayerData.Factories; | using MareSynchronos.PlayerData.Factories; | ||||||
|  | using MareSynchronos.Services.Events; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
| @@ -138,8 +139,9 @@ public sealed class PairManager : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|     public void ReceiveCharaData(OnlineUserCharaDataDto dto) |     public void ReceiveCharaData(OnlineUserCharaDataDto dto) | ||||||
|     { |     { | ||||||
|         if (!_allClientPairs.ContainsKey(dto.User)) throw new InvalidOperationException("No user found for " + dto.User); |         if (!_allClientPairs.TryGetValue(dto.User, out var pair)) throw new InvalidOperationException("No user found for " + dto.User); | ||||||
|  |  | ||||||
|  |         Mediator.Publish(new EventMessage(new Event(pair.UserData, nameof(PairManager), EventSeverity.Informational, "Received Character Data"))); | ||||||
|         _allClientPairs[dto.User].ApplyData(dto); |         _allClientPairs[dto.User].ApplyData(dto); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ using MareSynchronos.PlayerData.Factories; | |||||||
| using MareSynchronos.PlayerData.Pairs; | using MareSynchronos.PlayerData.Pairs; | ||||||
| using MareSynchronos.PlayerData.Services; | using MareSynchronos.PlayerData.Services; | ||||||
| using MareSynchronos.Services; | using MareSynchronos.Services; | ||||||
|  | using MareSynchronos.Services.Events; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| using MareSynchronos.Services.ServerConfiguration; | using MareSynchronos.Services.ServerConfiguration; | ||||||
| using MareSynchronos.UI; | using MareSynchronos.UI; | ||||||
| @@ -91,6 +92,8 @@ public sealed class Plugin : IDalamudPlugin | |||||||
|             collection.AddSingleton<FileCompactor>(); |             collection.AddSingleton<FileCompactor>(); | ||||||
|             collection.AddSingleton<TagHandler>(); |             collection.AddSingleton<TagHandler>(); | ||||||
|             collection.AddSingleton<UidDisplayHandler>(); |             collection.AddSingleton<UidDisplayHandler>(); | ||||||
|  |             collection.AddSingleton((s) => new EventAggregator(pluginInterface.ConfigDirectory.FullName, | ||||||
|  |                 s.GetRequiredService<ILogger<EventAggregator>>(), s.GetRequiredService<MareMediator>())); | ||||||
|             collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService<ILogger<DalamudUtilService>>(), |             collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService<ILogger<DalamudUtilService>>(), | ||||||
|                 clientState, objectTable, framework, gameGui, toastGui, condition, gameData, targetManager, |                 clientState, objectTable, framework, gameGui, toastGui, condition, gameData, targetManager, | ||||||
|                 s.GetRequiredService<MareMediator>(), s.GetRequiredService<PerformanceCollectorService>())); |                 s.GetRequiredService<MareMediator>(), s.GetRequiredService<PerformanceCollectorService>())); | ||||||
| @@ -118,6 +121,7 @@ public sealed class Plugin : IDalamudPlugin | |||||||
|             collection.AddScoped<WindowMediatorSubscriberBase, DownloadUi>(); |             collection.AddScoped<WindowMediatorSubscriberBase, DownloadUi>(); | ||||||
|             collection.AddScoped<WindowMediatorSubscriberBase, PopoutProfileUi>(); |             collection.AddScoped<WindowMediatorSubscriberBase, PopoutProfileUi>(); | ||||||
|             collection.AddScoped<WindowMediatorSubscriberBase, DataAnalysisUi>(); |             collection.AddScoped<WindowMediatorSubscriberBase, DataAnalysisUi>(); | ||||||
|  |             collection.AddScoped<WindowMediatorSubscriberBase, EventViewerUI>(); | ||||||
|  |  | ||||||
|             collection.AddScoped<WindowMediatorSubscriberBase, EditProfileUi>((s) => new EditProfileUi(s.GetRequiredService<ILogger<EditProfileUi>>(), |             collection.AddScoped<WindowMediatorSubscriberBase, EditProfileUi>((s) => new EditProfileUi(s.GetRequiredService<ILogger<EditProfileUi>>(), | ||||||
|                 s.GetRequiredService<MareMediator>(), s.GetRequiredService<ApiController>(), pluginInterface.UiBuilder, s.GetRequiredService<UiSharedService>(), |                 s.GetRequiredService<MareMediator>(), s.GetRequiredService<ApiController>(), pluginInterface.UiBuilder, s.GetRequiredService<UiSharedService>(), | ||||||
| @@ -153,6 +157,7 @@ public sealed class Plugin : IDalamudPlugin | |||||||
|             collection.AddHostedService(p => p.GetRequiredService<PerformanceCollectorService>()); |             collection.AddHostedService(p => p.GetRequiredService<PerformanceCollectorService>()); | ||||||
|             collection.AddHostedService(p => p.GetRequiredService<DtrEntry>()); |             collection.AddHostedService(p => p.GetRequiredService<DtrEntry>()); | ||||||
|             collection.AddHostedService(p => p.GetRequiredService<MarePlugin>()); |             collection.AddHostedService(p => p.GetRequiredService<MarePlugin>()); | ||||||
|  |             collection.AddHostedService(p => p.GetRequiredService<EventAggregator>()); | ||||||
|         }) |         }) | ||||||
|         .Build() |         .Build() | ||||||
|         .RunAsync(_pluginCts.Token); |         .RunAsync(_pluginCts.Token); | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								MareSynchronos/Services/Events/Event.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								MareSynchronos/Services/Events/Event.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | using MareSynchronos.API.Data; | ||||||
|  |  | ||||||
|  | namespace MareSynchronos.Services.Events; | ||||||
|  |  | ||||||
|  | public record Event | ||||||
|  | { | ||||||
|  |     public DateTime EventTime { get; } | ||||||
|  |     public string UID { get; } | ||||||
|  |     public string Character { get; } | ||||||
|  |     public string EventSource { get; } | ||||||
|  |     public EventSeverity EventSeverity { get; } | ||||||
|  |     public string Message { get; } | ||||||
|  |  | ||||||
|  |     public Event(string? Character, UserData UserData, string EventSource, EventSeverity EventSeverity, string Message) | ||||||
|  |     { | ||||||
|  |         EventTime = DateTime.Now; | ||||||
|  |         this.UID = UserData.AliasOrUID; | ||||||
|  |         this.Character = Character ?? string.Empty; | ||||||
|  |         this.EventSource = EventSource; | ||||||
|  |         this.EventSeverity = EventSeverity; | ||||||
|  |         this.Message = Message; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Event(UserData UserData, string EventSource, EventSeverity EventSeverity, string Message) : this(null, UserData, EventSource, EventSeverity, Message) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Event(string EventSource, EventSeverity EventSeverity, string Message) | ||||||
|  |         : this(new UserData(string.Empty), EventSource, EventSeverity, Message) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public override string ToString() | ||||||
|  |     { | ||||||
|  |         if (string.IsNullOrEmpty(UID)) | ||||||
|  |             return $"{EventTime:HH:mm:ss.fff}\t[{EventSource}]{{{(int)EventSeverity}}}\t{Message}"; | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             if (string.IsNullOrEmpty(Character)) | ||||||
|  |                 return $"{EventTime:HH:mm:ss.fff}\t[{EventSource}]{{{(int)EventSeverity}}}\t<{UID}> {Message}"; | ||||||
|  |             else | ||||||
|  |                 return $"{EventTime:HH:mm:ss.fff}\t[{EventSource}]{{{(int)EventSeverity}}}\t<{UID}\\{Character}> {Message}"; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										112
									
								
								MareSynchronos/Services/Events/EventAggregator.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								MareSynchronos/Services/Events/EventAggregator.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | |||||||
|  | using MareSynchronos.Services.Mediator; | ||||||
|  | using MareSynchronos.Utils; | ||||||
|  | using Microsoft.Extensions.Hosting; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  |  | ||||||
|  | namespace MareSynchronos.Services.Events; | ||||||
|  |  | ||||||
|  | public class EventAggregator : MediatorSubscriberBase, IHostedService | ||||||
|  | { | ||||||
|  |     private readonly RollingList<Event> _events = new(500); | ||||||
|  |     private readonly SemaphoreSlim _lock = new(1); | ||||||
|  |     private readonly string _configDirectory; | ||||||
|  |     private readonly ILogger<EventAggregator> _logger; | ||||||
|  |  | ||||||
|  |     public Lazy<List<Event>> EventList { get; private set; } | ||||||
|  |     public bool NewEventsAvailable => !EventList.IsValueCreated; | ||||||
|  |     public string EventLogFolder => Path.Combine(_configDirectory, "eventlog"); | ||||||
|  |     private string CurrentLogName => $"{DateTime.Now:yyyy-MM-dd}-events.log"; | ||||||
|  |     private DateTime _currentTime; | ||||||
|  |  | ||||||
|  |     public EventAggregator(string configDirectory, ILogger<EventAggregator> logger, MareMediator mareMediator) : base(logger, mareMediator) | ||||||
|  |     { | ||||||
|  |         Logger.LogInformation("Starting EventAggregatorService"); | ||||||
|  |         Logger.LogInformation("Started EventAggregatorService"); | ||||||
|  |  | ||||||
|  |         Mediator.Subscribe<EventMessage>(this, (msg) => | ||||||
|  |         { | ||||||
|  |             _lock.Wait(); | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 Logger.LogTrace("Received Event: {evt}", msg.Event.ToString()); | ||||||
|  |                 _events.Add(msg.Event); | ||||||
|  |                 WriteToFile(msg.Event); | ||||||
|  |             } | ||||||
|  |             finally | ||||||
|  |             { | ||||||
|  |                 _lock.Release(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             RecreateLazy(); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         EventList = CreateEventLazy(); | ||||||
|  |         _configDirectory = configDirectory; | ||||||
|  |         _logger = logger; | ||||||
|  |         _currentTime = DateTime.Now - TimeSpan.FromDays(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void RecreateLazy() | ||||||
|  |     { | ||||||
|  |         if (!EventList.IsValueCreated) return; | ||||||
|  |  | ||||||
|  |         EventList = CreateEventLazy(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Lazy<List<Event>> CreateEventLazy() | ||||||
|  |     { | ||||||
|  |         return new Lazy<List<Event>>(() => | ||||||
|  |         { | ||||||
|  |             _lock.Wait(); | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 return [.. _events]; | ||||||
|  |             } | ||||||
|  |             finally | ||||||
|  |             { | ||||||
|  |                 _lock.Release(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void WriteToFile(Event receivedEvent) | ||||||
|  |     { | ||||||
|  |         if (DateTime.Now.Day != _currentTime.Day) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 _currentTime = DateTime.Now; | ||||||
|  |                 var filesInDirectory = Directory.EnumerateFiles(EventLogFolder, "*.log"); | ||||||
|  |                 if (filesInDirectory.Skip(10).Any()) | ||||||
|  |                 { | ||||||
|  |                     File.Delete(filesInDirectory.OrderBy(f => new FileInfo(f).LastWriteTimeUtc).First()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 _logger.LogWarning(ex, "Could not delete last events"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var eventLogFile = Path.Combine(EventLogFolder, CurrentLogName); | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             if (!Directory.Exists(EventLogFolder)) Directory.CreateDirectory(EventLogFolder); | ||||||
|  |             File.AppendAllLines(eventLogFile, [receivedEvent.ToString()]); | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             _logger.LogWarning(ex, $"Could not write to event file {eventLogFile}"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Task StartAsync(CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         return Task.CompletedTask; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Task StopAsync(CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         return Task.CompletedTask; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								MareSynchronos/Services/Events/EventSeverity.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								MareSynchronos/Services/Events/EventSeverity.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | namespace MareSynchronos.Services.Events; | ||||||
|  |  | ||||||
|  | public enum EventSeverity | ||||||
|  | { | ||||||
|  |     Informational = 0, | ||||||
|  |     Warning = 1, | ||||||
|  |     Error = 2 | ||||||
|  | } | ||||||
| @@ -4,6 +4,7 @@ using MareSynchronos.API.Dto; | |||||||
| using MareSynchronos.API.Dto.Group; | using MareSynchronos.API.Dto.Group; | ||||||
| using MareSynchronos.PlayerData.Handlers; | using MareSynchronos.PlayerData.Handlers; | ||||||
| using MareSynchronos.PlayerData.Pairs; | using MareSynchronos.PlayerData.Pairs; | ||||||
|  | using MareSynchronos.Services.Events; | ||||||
| using MareSynchronos.WebAPI.Files.Models; | using MareSynchronos.WebAPI.Files.Models; | ||||||
| using System.Numerics; | using System.Numerics; | ||||||
|  |  | ||||||
| @@ -78,6 +79,7 @@ public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : Mess | |||||||
| public record TargetPairMessage(Pair Pair) : MessageBase; | public record TargetPairMessage(Pair Pair) : MessageBase; | ||||||
| public record CombatStartMessage : MessageBase; | public record CombatStartMessage : MessageBase; | ||||||
| public record CombatEndMessage : MessageBase; | public record CombatEndMessage : MessageBase; | ||||||
|  | public record EventMessage(Event Event) : MessageBase; | ||||||
| public record PenumbraDirectoryChangedMessage(string? ModDirectory) : MessageBase; | public record PenumbraDirectoryChangedMessage(string? ModDirectory) : MessageBase; | ||||||
|  |  | ||||||
| public record UserChatMsgMessage(SignedChatMessage ChatMsg) : MessageBase; | public record UserChatMsgMessage(SignedChatMessage ChatMsg) : MessageBase; | ||||||
|   | |||||||
| @@ -502,12 +502,21 @@ public class CompactUi : WindowMediatorSubscriberBase | |||||||
|             ImGui.TextUnformatted(downloadText); |             ImGui.TextUnformatted(downloadText); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Mare Character Data Analysis", WindowContentWidth)) |         var bottomButtonWidth = (WindowContentWidth - ImGui.GetStyle().ItemSpacing.X) / 2; | ||||||
|  |  | ||||||
|  |         if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Character Analysis", bottomButtonWidth)) | ||||||
|         { |         { | ||||||
|             Mediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi))); |             Mediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi))); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         ImGui.SameLine(); |         ImGui.SameLine(); | ||||||
|  |  | ||||||
|  |         if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.NotesMedical, "Event Viewer", bottomButtonWidth)) | ||||||
|  |         { | ||||||
|  |             Mediator.Publish(new UiToggleMessage(typeof(EventViewerUI))); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ImGui.SameLine(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void DrawUIDHeader() |     private void DrawUIDHeader() | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase | |||||||
|     private bool _showModal = false; |     private bool _showModal = false; | ||||||
|  |  | ||||||
|     public DataAnalysisUi(ILogger<DataAnalysisUi> logger, MareMediator mediator, CharacterAnalyzer characterAnalyzer, IpcManager ipcManager, PerformanceCollectorService performanceCollectorService) |     public DataAnalysisUi(ILogger<DataAnalysisUi> logger, MareMediator mediator, CharacterAnalyzer characterAnalyzer, IpcManager ipcManager, PerformanceCollectorService performanceCollectorService) | ||||||
|         : base(logger, mediator, "Mare Character Data Analysis", performanceCollectorService) |         : base(logger, mediator, "Character Data Analysis", performanceCollectorService) | ||||||
|     { |     { | ||||||
|         _characterAnalyzer = characterAnalyzer; |         _characterAnalyzer = characterAnalyzer; | ||||||
|         _ipcManager = ipcManager; |         _ipcManager = ipcManager; | ||||||
|   | |||||||
							
								
								
									
										223
									
								
								MareSynchronos/UI/EventViewerUI.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								MareSynchronos/UI/EventViewerUI.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,223 @@ | |||||||
|  | using Dalamud.Interface; | ||||||
|  | using Dalamud.Interface.Colors; | ||||||
|  | using Dalamud.Interface.Utility; | ||||||
|  | using Dalamud.Interface.Utility.Raii; | ||||||
|  | using ImGuiNET; | ||||||
|  | using MareSynchronos.Services; | ||||||
|  | using MareSynchronos.Services.Events; | ||||||
|  | using MareSynchronos.Services.Mediator; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | using System.Diagnostics; | ||||||
|  | using System.Globalization; | ||||||
|  | using System.Numerics; | ||||||
|  |  | ||||||
|  | namespace MareSynchronos.UI; | ||||||
|  |  | ||||||
|  | internal class EventViewerUI : WindowMediatorSubscriberBase | ||||||
|  | { | ||||||
|  |     private readonly EventAggregator _eventAggregator; | ||||||
|  |     private readonly UiSharedService _uiSharedService; | ||||||
|  |     private List<Event> _currentEvents = new(); | ||||||
|  |     private Lazy<List<Event>> _filteredEvents; | ||||||
|  |     private string _filterFreeText = string.Empty; | ||||||
|  |     private string _filterCharacter = string.Empty; | ||||||
|  |     private string _filterUid = string.Empty; | ||||||
|  |     private string _filterSource = string.Empty; | ||||||
|  |     private string _filterEvent = string.Empty; | ||||||
|  |  | ||||||
|  |     private List<Event> CurrentEvents | ||||||
|  |     { | ||||||
|  |         get | ||||||
|  |         { | ||||||
|  |             return _currentEvents; | ||||||
|  |         } | ||||||
|  |         set | ||||||
|  |         { | ||||||
|  |             _currentEvents = value; | ||||||
|  |             _filteredEvents = RecreateFilter(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public EventViewerUI(ILogger<EventViewerUI> logger, MareMediator mediator, | ||||||
|  |         EventAggregator eventAggregator, UiSharedService uiSharedService, | ||||||
|  |         PerformanceCollectorService performanceCollectorService) : base(logger, mediator, "Event Viewer", performanceCollectorService) | ||||||
|  |     { | ||||||
|  |         _eventAggregator = eventAggregator; | ||||||
|  |         _uiSharedService = uiSharedService; | ||||||
|  |         SizeConstraints = new() | ||||||
|  |         { | ||||||
|  |             MinimumSize = new(600, 500), | ||||||
|  |             MaximumSize = new(1000, 2000) | ||||||
|  |         }; | ||||||
|  |         _filteredEvents = RecreateFilter(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Lazy<List<Event>> RecreateFilter() | ||||||
|  |     { | ||||||
|  |         return new(() => | ||||||
|  |             CurrentEvents.Where(f => | ||||||
|  |                 (string.IsNullOrEmpty(_filterFreeText) | ||||||
|  |                 || (f.EventSource.Contains(_filterFreeText, StringComparison.OrdinalIgnoreCase) | ||||||
|  |                     || f.Character.Contains(_filterFreeText, StringComparison.OrdinalIgnoreCase) | ||||||
|  |                     || f.UID.Contains(_filterFreeText, StringComparison.OrdinalIgnoreCase) | ||||||
|  |                     || f.Message.Contains(_filterFreeText, StringComparison.OrdinalIgnoreCase) | ||||||
|  |                 )) | ||||||
|  |                 && | ||||||
|  |                 (string.IsNullOrEmpty(_filterUid) | ||||||
|  |                     || (f.UID.Contains(_filterUid, StringComparison.OrdinalIgnoreCase)) | ||||||
|  |                 ) | ||||||
|  |                 && | ||||||
|  |                 (string.IsNullOrEmpty(_filterSource) | ||||||
|  |                     || (f.EventSource.Contains(_filterSource, StringComparison.OrdinalIgnoreCase)) | ||||||
|  |                 ) | ||||||
|  |                 && | ||||||
|  |                 (string.IsNullOrEmpty(_filterCharacter) | ||||||
|  |                     || (f.Character.Contains(_filterCharacter, StringComparison.OrdinalIgnoreCase)) | ||||||
|  |                 ) | ||||||
|  |                 && | ||||||
|  |                 (string.IsNullOrEmpty(_filterEvent) | ||||||
|  |                     || (f.Message.Contains(_filterEvent, StringComparison.OrdinalIgnoreCase)) | ||||||
|  |                 ) | ||||||
|  |              ).ToList()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void ClearFilters() | ||||||
|  |     { | ||||||
|  |         _filterFreeText = string.Empty; | ||||||
|  |         _filterCharacter = string.Empty; | ||||||
|  |         _filterUid = string.Empty; | ||||||
|  |         _filterSource = string.Empty; | ||||||
|  |         _filterEvent = string.Empty; | ||||||
|  |         _filteredEvents = RecreateFilter(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public override void OnOpen() | ||||||
|  |     { | ||||||
|  |         CurrentEvents = _eventAggregator.EventList.Value.OrderByDescending(f => f.EventTime).ToList(); | ||||||
|  |         ClearFilters(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected override void DrawInternal() | ||||||
|  |     { | ||||||
|  |         using (ImRaii.Disabled(!_eventAggregator.NewEventsAvailable)) | ||||||
|  |         { | ||||||
|  |             if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.ArrowsToCircle, "Refresh events")) | ||||||
|  |             { | ||||||
|  |                 CurrentEvents = _eventAggregator.EventList.Value.OrderByDescending(f => f.EventTime).ToList(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (_eventAggregator.NewEventsAvailable) | ||||||
|  |         { | ||||||
|  |             ImGui.SameLine(); | ||||||
|  |             ImGui.AlignTextToFramePadding(); | ||||||
|  |             UiSharedService.ColorTextWrapped("New events are available, press refresh to update", ImGuiColors.DalamudYellow); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var buttonSize = UiSharedService.GetNormalizedIconTextButtonSize(FontAwesomeIcon.FolderOpen, "Open EventLog Folder"); | ||||||
|  |         var dist = ImGui.GetWindowContentRegionMax().X - buttonSize.X; | ||||||
|  |         ImGui.SameLine(dist); | ||||||
|  |         if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.FolderOpen, "Open EventLog folder")) | ||||||
|  |         { | ||||||
|  |             ProcessStartInfo ps = new() | ||||||
|  |             { | ||||||
|  |                 FileName = _eventAggregator.EventLogFolder, | ||||||
|  |                 UseShellExecute = true, | ||||||
|  |                 WindowStyle = ProcessWindowStyle.Normal | ||||||
|  |             }; | ||||||
|  |             Process.Start(ps); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         UiSharedService.FontText("Last Events", _uiSharedService.UidFont); | ||||||
|  |         var foldOut = ImRaii.TreeNode("Filter"); | ||||||
|  |         if (foldOut) | ||||||
|  |         { | ||||||
|  |             if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.Ban, "Clear Filters")) | ||||||
|  |             { | ||||||
|  |                 ClearFilters(); | ||||||
|  |             } | ||||||
|  |             bool changedFilter = false; | ||||||
|  |             ImGui.SetNextItemWidth(200); | ||||||
|  |             changedFilter |= ImGui.InputText("Search all columns", ref _filterFreeText, 50); | ||||||
|  |             ImGui.SetNextItemWidth(200); | ||||||
|  |             changedFilter |= ImGui.InputText("Filter by Source", ref _filterSource, 50); | ||||||
|  |             ImGui.SetNextItemWidth(200); | ||||||
|  |             changedFilter |= ImGui.InputText("Filter by UID", ref _filterUid, 50); | ||||||
|  |             ImGui.SetNextItemWidth(200); | ||||||
|  |             changedFilter |= ImGui.InputText("Filter by Character", ref _filterCharacter, 50); | ||||||
|  |             ImGui.SetNextItemWidth(200); | ||||||
|  |             changedFilter |= ImGui.InputText("Filter by Event", ref _filterEvent, 50); | ||||||
|  |             if (changedFilter) _filteredEvents = RecreateFilter(); | ||||||
|  |         } | ||||||
|  |         foldOut.Dispose(); | ||||||
|  |  | ||||||
|  |         var cursorPos = ImGui.GetCursorPosY(); | ||||||
|  |         var max = ImGui.GetWindowContentRegionMax(); | ||||||
|  |         var min = ImGui.GetWindowContentRegionMin(); | ||||||
|  |         var width = max.X - min.X; | ||||||
|  |         var height = max.Y - cursorPos; | ||||||
|  |         using var table = ImRaii.Table("eventTable", 6, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg, | ||||||
|  |             new Vector2(width, height)); | ||||||
|  |         if (table) | ||||||
|  |         { | ||||||
|  |             ImGui.TableSetupScrollFreeze(0, 1); | ||||||
|  |             ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.NoSort); | ||||||
|  |             ImGui.TableSetupColumn("Time"); | ||||||
|  |             ImGui.TableSetupColumn("Source"); | ||||||
|  |             ImGui.TableSetupColumn("UID"); | ||||||
|  |             ImGui.TableSetupColumn("Character"); | ||||||
|  |             ImGui.TableSetupColumn("Event"); | ||||||
|  |             ImGui.TableHeadersRow(); | ||||||
|  |             foreach (var ev in _filteredEvents.Value) | ||||||
|  |             { | ||||||
|  |                 var icon = ev.EventSeverity switch | ||||||
|  |                 { | ||||||
|  |                     EventSeverity.Informational => FontAwesomeIcon.InfoCircle, | ||||||
|  |                     EventSeverity.Warning => FontAwesomeIcon.ExclamationTriangle, | ||||||
|  |                     EventSeverity.Error => FontAwesomeIcon.Cross, | ||||||
|  |                     _ => FontAwesomeIcon.QuestionCircle | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 var iconColor = ev.EventSeverity switch | ||||||
|  |                 { | ||||||
|  |                     EventSeverity.Informational => new Vector4(), | ||||||
|  |                     EventSeverity.Warning => ImGuiColors.DalamudYellow, | ||||||
|  |                     EventSeverity.Error => ImGuiColors.DalamudRed, | ||||||
|  |                     _ => new Vector4() | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 ImGui.TableNextColumn(); | ||||||
|  |                 UiSharedService.NormalizedIcon(icon, iconColor == new Vector4() ? null : iconColor); | ||||||
|  |                 UiSharedService.AttachToolTip(ev.EventSeverity.ToString()); | ||||||
|  |                 ImGui.TableNextColumn(); | ||||||
|  |                 ImGui.AlignTextToFramePadding(); | ||||||
|  |                 ImGui.TextUnformatted(ev.EventTime.ToString("G", CultureInfo.CurrentCulture)); | ||||||
|  |                 ImGui.TableNextColumn(); | ||||||
|  |                 ImGui.AlignTextToFramePadding(); | ||||||
|  |                 ImGui.TextUnformatted(ev.EventSource); | ||||||
|  |                 ImGui.TableNextColumn(); | ||||||
|  |                 ImGui.AlignTextToFramePadding(); | ||||||
|  |                 ImGui.TextUnformatted(string.IsNullOrEmpty(ev.UID) ? "--" : ev.UID); | ||||||
|  |                 ImGui.TableNextColumn(); | ||||||
|  |                 ImGui.AlignTextToFramePadding(); | ||||||
|  |                 ImGui.TextUnformatted(string.IsNullOrEmpty(ev.Character) ? "--" : ev.Character); | ||||||
|  |                 ImGui.TableNextColumn(); | ||||||
|  |                 ImGui.AlignTextToFramePadding(); | ||||||
|  |                 var posX = ImGui.GetCursorPosX(); | ||||||
|  |                 var maxTextLength = ImGui.GetWindowContentRegionMax().X - posX; | ||||||
|  |                 var textSize = ImGui.CalcTextSize(ev.Message).X; | ||||||
|  |                 var msg = ev.Message; | ||||||
|  |                 while (textSize > maxTextLength) | ||||||
|  |                 { | ||||||
|  |                     msg = msg[..^5] + "..."; | ||||||
|  |                     textSize = ImGui.CalcTextSize(msg).X; | ||||||
|  |                 } | ||||||
|  |                 ImGui.TextUnformatted(msg); | ||||||
|  |                 if (!string.Equals(msg, ev.Message, StringComparison.Ordinal)) | ||||||
|  |                 { | ||||||
|  |                     UiSharedService.AttachToolTip(ev.Message); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -128,6 +128,8 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM | |||||||
|         await StopConnection(ServerState.Disconnected).ConfigureAwait(false); |         await StopConnection(ServerState.Disconnected).ConfigureAwait(false); | ||||||
|  |  | ||||||
|         Logger.LogInformation("Recreating Connection"); |         Logger.LogInformation("Recreating Connection"); | ||||||
|  |         Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(ApiController), Services.Events.EventSeverity.Informational, | ||||||
|  |             $"Starting Connection to {_serverManager.CurrentServer.ServerName}"))); | ||||||
|  |  | ||||||
|         _connectionCancellationTokenSource.Cancel(); |         _connectionCancellationTokenSource.Cancel(); | ||||||
|         _connectionCancellationTokenSource = new CancellationTokenSource(); |         _connectionCancellationTokenSource = new CancellationTokenSource(); | ||||||
| @@ -411,6 +413,9 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM | |||||||
|         _healthCheckTokenSource?.Cancel(); |         _healthCheckTokenSource?.Cancel(); | ||||||
|         ServerState = ServerState.Reconnecting; |         ServerState = ServerState.Reconnecting; | ||||||
|         Logger.LogWarning(arg, "Connection closed... Reconnecting"); |         Logger.LogWarning(arg, "Connection closed... Reconnecting"); | ||||||
|  |         Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(ApiController), Services.Events.EventSeverity.Warning, | ||||||
|  |             $"Connection interrupted, reconnecting to {_serverManager.CurrentServer.ServerName}"))); | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task StopConnection(ServerState state) |     private async Task StopConnection(ServerState state) | ||||||
| @@ -422,6 +427,9 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM | |||||||
|  |  | ||||||
|         if (_mareHub is not null) |         if (_mareHub is not null) | ||||||
|         { |         { | ||||||
|  |             Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(ApiController), Services.Events.EventSeverity.Informational, | ||||||
|  |                 $"Stopping existing connection to {_serverManager.CurrentServer.ServerName}"))); | ||||||
|  |  | ||||||
|             _initialized = false; |             _initialized = false; | ||||||
|             _healthCheckTokenSource?.Cancel(); |             _healthCheckTokenSource?.Cancel(); | ||||||
|             Mediator.Publish(new DisconnectedMessage()); |             Mediator.Publish(new DisconnectedMessage()); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 rootdarkarchon
					rootdarkarchon