diff --git a/Daybreak.GWCA/CMakeLists.txt b/Daybreak.GWCA/CMakeLists.txt index 1db31861..7338517b 100644 --- a/Daybreak.GWCA/CMakeLists.txt +++ b/Daybreak.GWCA/CMakeLists.txt @@ -11,7 +11,7 @@ endif() set(VERSION_MAJOR 0) set(VERSION_MINOR 9) set(VERSION_PATCH 9) -set(VERSION_TWEAK 48) +set(VERSION_TWEAK 52) set(VERSION_RC "${CMAKE_CURRENT_BINARY_DIR}/version.rc") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in" "${VERSION_RC}" @ONLY) diff --git a/Daybreak.Tests/Services/JsonLoggerProviderTests.cs b/Daybreak.Tests/Services/JsonLoggerProviderTests.cs deleted file mode 100644 index 116c79d7..00000000 --- a/Daybreak.Tests/Services/JsonLoggerProviderTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Daybreak.Configuration.Options; -using Daybreak.Services.Logging; -using FluentAssertions; -using LiteDB; -using Microsoft.Extensions.Logging; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NSubstitute; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Logging; - -namespace Daybreak.Tests.Services; - -[TestClass] -public class JsonLoggerProviderTests -{ - private ILogsManager logsManager; - private ILoggerProvider loggerProvider; - private ILiteDatabase liteDatabase; - - [TestInitialize] - public void InitializeProvider() - { - File.Delete("Daybreak.db"); - this.liteDatabase = new LiteDatabase("Daybreak.db"); - var options = Substitute.For>(); - options.Value.Returns(new LauncherOptions { PersistentLogging = true }); - this.logsManager = new JsonLogsManager(this.liteDatabase.GetCollection(), options); - this.loggerProvider = new CVLoggerProvider(this.logsManager); - } - - [TestCleanup] - public void TestCleanup() - { - this.liteDatabase.Dispose(); - } - - [TestMethod] - public void CreateLoggerReturnsLogger() - { - var logger = this.loggerProvider.CreateLogger("SomeCategory"); - logger.Should().NotBeNull(); - } - [TestMethod] - public void LoggerLogsAndReaderReadsFiltered() - { - var logger = this.loggerProvider.CreateLogger("SomeCategory"); - logger.LogTrace("Logging some trace"); - logger.LogInformation("Logging some stuff"); - logger.LogError("Logging some error"); - - this.logsManager.GetLogs(l => l.LogLevel < LogLevel.Information).Should().HaveCount(1); - var log = this.logsManager.GetLogs(l => l.LogLevel < LogLevel.Information).First(); - log.LogLevel.Should().Be(LogLevel.Error); - } - [TestMethod] - public void LoggerLogsAndReaderReads() - { - this.logsManager.DeleteLogs(); - var logger = this.loggerProvider.CreateLogger("SomeCategory"); - logger.LogInformation("Logging some stuff"); - logger.LogError("Logging some error"); - - this.logsManager.GetLogs().Should().HaveCount(2); - } - [TestMethod] - public void DeletingLogsShouldDeleteLogs() - { - var logger = this.loggerProvider.CreateLogger("SomeCategory"); - logger.LogInformation("Logging some stuff"); - logger.LogError("Logging some error"); - - this.logsManager.GetLogs().Should().HaveCount(2); - - this.logsManager.DeleteLogs(); - this.logsManager.GetLogs().Should().HaveCount(0); - } -} diff --git a/Daybreak/Configuration/Options/ILiteCollectionOptions.cs b/Daybreak/Configuration/Options/ILiteCollectionOptions.cs deleted file mode 100644 index d544eeff..00000000 --- a/Daybreak/Configuration/Options/ILiteCollectionOptions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Daybreak.Configuration.Options; - -public interface ILiteCollectionOptions : ILiteCollectionOptions -{ -} - -public interface ILiteCollectionOptions -{ - string CollectionName { get; } -} diff --git a/Daybreak/Configuration/Options/LoggingOptions.cs b/Daybreak/Configuration/Options/LoggingOptions.cs deleted file mode 100644 index 563c7808..00000000 --- a/Daybreak/Configuration/Options/LoggingOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Daybreak.Attributes; - -namespace Daybreak.Configuration.Options; - -[OptionsName(Name = "Logging Options")] -[OptionsIgnore] -[OptionsSynchronizationIgnore] -internal sealed class LoggingOptions : ILiteCollectionOptions -{ - public string CollectionName => "logs"; -} diff --git a/Daybreak/Configuration/Options/NotificationStorageOptions.cs b/Daybreak/Configuration/Options/NotificationStorageOptions.cs deleted file mode 100644 index 8e7d2404..00000000 --- a/Daybreak/Configuration/Options/NotificationStorageOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Daybreak.Attributes; -using Daybreak.Services.Notifications.Models; - -namespace Daybreak.Configuration.Options; - -[OptionsIgnore] -[OptionsSynchronizationIgnore] -internal sealed class NotificationStorageOptions : ILiteCollectionOptions -{ - public string CollectionName => "notifications"; -} diff --git a/Daybreak/Configuration/Options/PriceHistoryOptions.cs b/Daybreak/Configuration/Options/PriceHistoryOptions.cs index ee6fcf6e..8899f2c7 100644 --- a/Daybreak/Configuration/Options/PriceHistoryOptions.cs +++ b/Daybreak/Configuration/Options/PriceHistoryOptions.cs @@ -1,5 +1,4 @@ using Daybreak.Attributes; -using Daybreak.Services.TradeChat.Models; using System; using System.Collections.Generic; @@ -8,13 +7,11 @@ namespace Daybreak.Configuration.Options; [OptionsName(Name = "Price History")] [OptionsIgnore] [OptionsSynchronizationIgnore] -internal sealed class PriceHistoryOptions : ILiteCollectionOptions +internal sealed class PriceHistoryOptions { public string HttpsUri { get; set; } = "https://kamadan.gwtoolbox.com/"; - public string CollectionName => "price_history"; - public TimeSpan UpdateInterval { get; set; } = TimeSpan.FromHours(1); - public Dictionary PriceHistoryMetadata { get; set; } = []; + public Dictionary PricingHistoryMetadata { get; set; } = []; } diff --git a/Daybreak/Configuration/Options/TraderMessagesOptions.cs b/Daybreak/Configuration/Options/TraderMessagesOptions.cs deleted file mode 100644 index b8fa4b17..00000000 --- a/Daybreak/Configuration/Options/TraderMessagesOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Daybreak.Attributes; -using Daybreak.Services.TradeChat.Models; - -namespace Daybreak.Configuration.Options; - -[OptionsIgnore] -[OptionsSynchronizationIgnore] -internal sealed class TraderMessagesOptions : ILiteCollectionOptions -{ - public string CollectionName => "trader_messages"; -} diff --git a/Daybreak/Configuration/ProjectConfiguration.cs b/Daybreak/Configuration/ProjectConfiguration.cs index 65825660..c3329af1 100644 --- a/Daybreak/Configuration/ProjectConfiguration.cs +++ b/Daybreak/Configuration/ProjectConfiguration.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.Logging; using Slim; using System.Extensions; -using LiteDB; using Daybreak.Services.Options; using Daybreak.Models; using Microsoft.CorrelationVector; @@ -90,6 +89,8 @@ using Daybreak.Launch; using Daybreak.Utils; using Daybreak.Views.Installation; +using Realms; +using Daybreak.Services.Logging.Models; namespace Daybreak.Configuration; @@ -137,7 +138,6 @@ public override void RegisterServices(IServiceCollection services) services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService().Cast()); - services.AddSingleton(sp => new LiteDatabase(PathUtils.GetAbsolutePathFromRoot("Daybreak.db"))); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -287,10 +287,10 @@ public override void RegisterStartupActions(IStartupActionProducer startupAction startupActionProducer.RegisterAction(); startupActionProducer.RegisterAction(); startupActionProducer.RegisterAction(); - startupActionProducer.RegisterAction(); startupActionProducer.RegisterAction(); startupActionProducer.RegisterAction(); startupActionProducer.RegisterAction(); + startupActionProducer.RegisterAction(); } public override void RegisterPostUpdateActions(IPostUpdateActionProducer postUpdateActionProducer) @@ -371,13 +371,10 @@ public override void RegisterOptions(IOptionsProducer optionsProducer) optionsProducer.RegisterOptions(); optionsProducer.RegisterOptions(); optionsProducer.RegisterOptions(); - optionsProducer.RegisterOptions(); optionsProducer.RegisterOptions(); optionsProducer.RegisterOptions(); optionsProducer.RegisterOptions(); - optionsProducer.RegisterOptions(); optionsProducer.RegisterOptions(); - optionsProducer.RegisterOptions(); optionsProducer.RegisterOptions(); optionsProducer.RegisterOptions(); optionsProducer.RegisterOptions(); @@ -426,10 +423,10 @@ public override void RegisterLaunchArgumentHandlers(IArgumentHandlerProducer arg private void RegisterLiteCollections(IServiceCollection services) { - this.RegisterLiteCollection(services); - this.RegisterLiteCollection(services); - this.RegisterLiteCollection(services); - this.RegisterLiteCollection(services); + this.RegisterCollection(services); + this.RegisterCollection(services); + this.RegisterCollection(services); + this.RegisterCollection(services); } private IServiceCollection RegisterHttpClients(IServiceCollection services) diff --git a/Daybreak/Daybreak.csproj b/Daybreak/Daybreak.csproj index 39e49e1c..3b74b751 100644 --- a/Daybreak/Daybreak.csproj +++ b/Daybreak/Daybreak.csproj @@ -11,7 +11,7 @@ preview Daybreak.ico true - 0.9.9.51 + 0.9.9.52 true cfb2a489-db80-448d-a969-80270f314c46 True @@ -95,19 +95,19 @@ - - + + @@ -120,8 +120,8 @@ - - + + diff --git a/Daybreak/FodyWeavers.xml b/Daybreak/FodyWeavers.xml new file mode 100644 index 00000000..cc07b895 --- /dev/null +++ b/Daybreak/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Daybreak/Models/Log.cs b/Daybreak/Models/Log.cs index 1faec759..8b332dcb 100644 --- a/Daybreak/Models/Log.cs +++ b/Daybreak/Models/Log.cs @@ -10,5 +10,5 @@ public sealed class Log public LogLevel LogLevel { get; set; } public string? CorrelationVector { get; set; } public string? EventId { get; set; } - public DateTime LogTime { get; set; } + public DateTimeOffset LogTime { get; set; } } diff --git a/Daybreak/Models/Plugins/PluginConfigurationBase.cs b/Daybreak/Models/Plugins/PluginConfigurationBase.cs index 5f264288..7c176cde 100644 --- a/Daybreak/Models/Plugins/PluginConfigurationBase.cs +++ b/Daybreak/Models/Plugins/PluginConfigurationBase.cs @@ -1,6 +1,6 @@ -using Daybreak.Configuration.Options; -using Daybreak.Services.ApplicationArguments; +using Daybreak.Services.ApplicationArguments; using Daybreak.Services.Browser; +using Daybreak.Services.Database; using Daybreak.Services.Drawing; using Daybreak.Services.Metrics; using Daybreak.Services.Mods; @@ -10,11 +10,10 @@ using Daybreak.Services.Startup; using Daybreak.Services.Updater.PostUpdate; using Daybreak.Utils; -using LiteDB; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http.Logging; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; +using Realms; using Slim; using System.Core.Extensions; using System.Net.Http; @@ -43,14 +42,12 @@ public PluginConfigurationBase() { } - public void RegisterLiteCollection(IServiceCollection services) - where TOptionsType : class, ILiteCollectionOptions + public void RegisterCollection(IServiceCollection services) + where TCollectionType : RealmObject, new() { - services.AddSingleton(sp => + services.AddScoped>(sp => { - var options = sp.GetRequiredService>(); - var liteDatabase = sp.GetRequiredService(); - return liteDatabase.GetCollection(options.Value.CollectionName, BsonAutoId.Int64); + return new RealmDatabaseCollection(); }); } diff --git a/Daybreak/Services/Database/IDatabaseCollection.cs b/Daybreak/Services/Database/IDatabaseCollection.cs new file mode 100644 index 00000000..294abed0 --- /dev/null +++ b/Daybreak/Services/Database/IDatabaseCollection.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Daybreak.Services.Database; + +public interface IDatabaseCollection + where TObject : new() +{ + long Count(); + + bool Add(TObject value); + + bool AddBulk(IEnumerable value); + + bool Update(TObject value); + + void Delete(TObject obj); + + void DeleteAll(); + + IEnumerable FindAll(); + + IEnumerable FindAll(Expression> filter); +} diff --git a/Daybreak/Services/Database/RealmDatabaseCollection.cs b/Daybreak/Services/Database/RealmDatabaseCollection.cs new file mode 100644 index 00000000..bbd93968 --- /dev/null +++ b/Daybreak/Services/Database/RealmDatabaseCollection.cs @@ -0,0 +1,85 @@ +using Daybreak.Utils; +using Realms; +using System; +using System.Collections.Generic; +using System.Core.Extensions; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; + +namespace Daybreak.Services.Database; + +internal sealed class RealmDatabaseCollection : IDatabaseCollection + where TObject : IRealmObject, new() +{ + private const string RealmDbFileName = "daybreak.realm.db"; + + private static readonly ThreadLocal RealmInstance = new(() => + Realm.GetInstance(PathUtils.GetAbsolutePathFromRoot(RealmDbFileName)) + ); + + public RealmDatabaseCollection() + { + } + + public long Count() + { + var db = RealmInstance.Value.ThrowIfNull(); + return db.All().Count(); + } + + public bool Update(TObject value) + { + var db = RealmInstance.Value.ThrowIfNull(); + using var transaction = db.BeginWrite(); + var result = db.Add(value, true) is not null; + transaction.Commit(); + return result; + } + + public bool AddBulk(IEnumerable values) + { + var db = RealmInstance.Value.ThrowIfNull(); + using var transaction = db.BeginWrite(); + db.Add(values, false); + transaction.Commit(); + return true; + } + + public bool Add(TObject value) + { + var db = RealmInstance.Value.ThrowIfNull(); + using var transaction = db.BeginWrite(); + var result = db.Add(value, false) is not null; + transaction.Commit(); + return result; + } + + public IEnumerable FindAll() + { + var db = RealmInstance.Value.ThrowIfNull(); + return db.All(); + } + + public IEnumerable FindAll(Expression> filter) + { + var db = RealmInstance.Value.ThrowIfNull(); + return db.All().Where(filter); + } + + public void Delete(TObject obj) + { + var db = RealmInstance.Value.ThrowIfNull(); + using var transaction = db.BeginWrite(); + db.Remove(obj); + transaction.Commit(); + } + + public void DeleteAll() + { + var db = RealmInstance.Value.ThrowIfNull(); + using var transaction = db.BeginWrite(); + db.RemoveAll(); + transaction.Commit(); + } +} diff --git a/Daybreak/Services/Logging/ILogsManager.cs b/Daybreak/Services/Logging/ILogsManager.cs index 98988ca8..1fd351dd 100644 --- a/Daybreak/Services/Logging/ILogsManager.cs +++ b/Daybreak/Services/Logging/ILogsManager.cs @@ -7,9 +7,9 @@ namespace Daybreak.Services.Logging; public interface ILogsManager : ILogsWriter { - event EventHandler? ReceivedLog; + event EventHandler? ReceivedLog; - IEnumerable GetLogs(Expression> filter); - IEnumerable GetLogs(); - int DeleteLogs(); + IEnumerable GetLogs(Expression> filter); + IEnumerable GetLogs(); + void DeleteLogs(); } diff --git a/Daybreak/Services/Logging/JsonLogsManager.cs b/Daybreak/Services/Logging/JsonLogsManager.cs index cf3e3393..c66a512b 100644 --- a/Daybreak/Services/Logging/JsonLogsManager.cs +++ b/Daybreak/Services/Logging/JsonLogsManager.cs @@ -1,5 +1,7 @@ using Daybreak.Configuration.Options; -using LiteDB; +using Daybreak.Services.Database; +using Daybreak.Services.Logging.Models; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Configuration; @@ -8,6 +10,7 @@ using System.Linq; using System.Linq.Expressions; using System.Logging; +using System.Threading; namespace Daybreak.Services.Logging; @@ -15,33 +18,50 @@ internal sealed class JsonLogsManager : ILogsManager { private const int MemoryCacheMaxSize = 5000; - private readonly List memoryCache = []; - private readonly ILiteCollection collection; + private readonly List memoryCache = []; + private readonly SemaphoreSlim semaphoreSlim = new(1); + private readonly IDatabaseCollection collection; private readonly ILiveOptions liveOptions; - public event EventHandler? ReceivedLog; + public event EventHandler? ReceivedLog; public JsonLogsManager( - ILiteCollection collection, + IDatabaseCollection collection, ILiveOptions liveOptions) { this.collection = collection.ThrowIfNull(); this.liveOptions = liveOptions.ThrowIfNull(); } - public IEnumerable GetLogs(Expression> filter) + public IEnumerable GetLogs(Expression> filter) { - return this.collection.Find(filter).Concat(this.memoryCache.Where(filter.Compile())); + return this.collection.FindAll().Select(log => new Daybreak.Models.Log + { + Category = log.Category, + CorrelationVector = log.CorrelationVector, + EventId = log.EventId, + Message = log.Message, + LogLevel = (LogLevel)log.LogLevel, + LogTime = log.LogTime + }).Where(filter.Compile()); } - public IEnumerable GetLogs() + public IEnumerable GetLogs() { - return this.collection.FindAll().Concat(this.memoryCache); + return this.collection.FindAll().Select(log => new Daybreak.Models.Log + { + Category = log.Category, + CorrelationVector = log.CorrelationVector, + EventId = log.EventId, + Message = log.Message, + LogLevel = (LogLevel)log.LogLevel, + LogTime = log.LogTime + }); } - public void WriteLog(Log log) + public async void WriteLog(Log log) { - var dbLog = new Models.Log + var logModel = new Daybreak.Models.Log { - EventId = log.EventId, + EventId = log.EventId ?? string.Empty, Message = log.Exception is null ? log.Message : $"{log.Message}{Environment.NewLine}{log.Exception}", Category = log.Category, LogLevel = log.LogLevel, @@ -51,35 +71,40 @@ public void WriteLog(Log log) if (this.liveOptions.Value.PersistentLogging) { - lock (this.memoryCache) + using var context = await this.semaphoreSlim.Acquire(); + if (this.memoryCache.Count > 100) { - if (this.memoryCache.Count > 0) + this.collection.AddBulk(this.memoryCache.Select(l => new LogDTO { - this.collection.InsertBulk(this.memoryCache, this.memoryCache.Count); - this.memoryCache.Clear(); - } + EventId = l.EventId, + Message = l.Message, + Category = l.Category, + LogLevel = (int)l.LogLevel, + LogTime = l.LogTime, + CorrelationVector = l.CorrelationVector + })); + + this.memoryCache.Clear(); } - this.collection.Insert(dbLog); + this.memoryCache.Add(logModel); } else { - lock(this.memoryCache) + using var context = await this.semaphoreSlim.Acquire(); + if (this.memoryCache.Count >= MemoryCacheMaxSize) { - if (this.memoryCache.Count >= MemoryCacheMaxSize) - { - this.memoryCache.RemoveAt(this.memoryCache.Count - 1); - } - - this.memoryCache.Add(dbLog); + this.memoryCache.RemoveAt(this.memoryCache.Count - 1); } + + this.memoryCache.Add(logModel); } - this.ReceivedLog?.Invoke(this, dbLog); + this.ReceivedLog?.Invoke(this, logModel); } - public int DeleteLogs() + public void DeleteLogs() { this.memoryCache.Clear(); - return this.collection.DeleteAll(); + this.collection.DeleteAll(); } } diff --git a/Daybreak/Services/Logging/Models/LogDTO.cs b/Daybreak/Services/Logging/Models/LogDTO.cs new file mode 100644 index 00000000..a447a4c0 --- /dev/null +++ b/Daybreak/Services/Logging/Models/LogDTO.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.Logging; +using Realms; +using System; + +namespace Daybreak.Services.Logging.Models; +internal sealed class LogDTO : RealmObject +{ + [PrimaryKey] + public string? Id { get; set; } = Guid.NewGuid().ToString(); + public string? Message { get; set; } + public string? Category { get; set; } + public int LogLevel { get; set; } + public string? CorrelationVector { get; set; } + public string? EventId { get; set; } + public DateTimeOffset LogTime { get; set; } +} diff --git a/Daybreak/Services/Notifications/INotificationStorage.cs b/Daybreak/Services/Notifications/INotificationStorage.cs index 945325dc..c77512cb 100644 --- a/Daybreak/Services/Notifications/INotificationStorage.cs +++ b/Daybreak/Services/Notifications/INotificationStorage.cs @@ -5,7 +5,7 @@ namespace Daybreak.Services.Notifications; public interface INotificationStorage { - IEnumerable GetPendingNotifications(int maxCount = int.MaxValue); + IEnumerable GetPendingNotifications(); IEnumerable GetNotifications(); diff --git a/Daybreak/Services/Notifications/Models/NotificationDTO.cs b/Daybreak/Services/Notifications/Models/NotificationDTO.cs index 91619ff9..9bf3af8f 100644 --- a/Daybreak/Services/Notifications/Models/NotificationDTO.cs +++ b/Daybreak/Services/Notifications/Models/NotificationDTO.cs @@ -1,14 +1,16 @@ using Microsoft.Extensions.Logging; +using Realms; using System; namespace Daybreak.Services.Notifications.Models; -public sealed class NotificationDTO +public sealed class NotificationDTO : RealmObject { + [PrimaryKey] public string Id { get; init; } = string.Empty; - public LogLevel Level { get; init; } - public DateTime ExpirationTime { get; init; } - public DateTime CreationTime { get; init; } = DateTime.Now; + public int Level { get; init; } + public DateTimeOffset ExpirationTime { get; init; } + public DateTimeOffset CreationTime { get; init; } = DateTimeOffset.Now; public string? Title { get; init; } public string? Description { get; init; } public string? MetaData { get; init; } diff --git a/Daybreak/Services/Notifications/NotificationService.cs b/Daybreak/Services/Notifications/NotificationService.cs index 094be486..546be3e1 100644 --- a/Daybreak/Services/Notifications/NotificationService.cs +++ b/Daybreak/Services/Notifications/NotificationService.cs @@ -86,7 +86,7 @@ void INotificationProducer.OpenNotification(Notification notification, bool stor Title = notification.Title, Description = notification.Description, Id = notification.Id, - Level = notification.Level, + Level = (int)notification.Level, MetaData = notification.Metadata, HandlerType = notification.HandlingType?.AssemblyQualifiedName, ExpirationTime = notification.ExpirationTime, @@ -184,11 +184,11 @@ private static Notification FromDTO(NotificationDTO dto) return new Notification { Id = dto.Id, - Level = dto.Level, + Level = (LogLevel)dto.Level, Title = dto.Title ?? string.Empty, Description = dto.Description ?? string.Empty, - ExpirationTime = dto.ExpirationTime, - CreationTime = dto.CreationTime, + ExpirationTime = dto.ExpirationTime.LocalDateTime, + CreationTime = dto.CreationTime.LocalDateTime, Metadata = dto.MetaData ?? string.Empty, Dismissible = dto.Dismissible, Closed = dto.Closed, @@ -201,7 +201,7 @@ private static NotificationDTO ToDTO(Notification notification) return new NotificationDTO { Id = notification.Id, - Level = notification.Level, + Level = (int)notification.Level, Title = notification.Title, Description = notification.Description, ExpirationTime = notification.ExpirationTime, diff --git a/Daybreak/Services/Notifications/NotificationStorage.cs b/Daybreak/Services/Notifications/NotificationStorage.cs index 6426b6f4..c4c83f9a 100644 --- a/Daybreak/Services/Notifications/NotificationStorage.cs +++ b/Daybreak/Services/Notifications/NotificationStorage.cs @@ -1,5 +1,5 @@ -using Daybreak.Services.Notifications.Models; -using LiteDB; +using Daybreak.Services.Database; +using Daybreak.Services.Notifications.Models; using System; using System.Collections.Generic; using System.Core.Extensions; @@ -8,17 +8,17 @@ namespace Daybreak.Services.Notifications; internal sealed class NotificationStorage : INotificationStorage { - private readonly ILiteCollection liteCollection; + private readonly IDatabaseCollection liteCollection; public NotificationStorage( - ILiteCollection liteCollection) + IDatabaseCollection liteCollection) { this.liteCollection = liteCollection.ThrowIfNull(); } - public IEnumerable GetPendingNotifications(int maxCount = int.MaxValue) + public IEnumerable GetPendingNotifications() { - return this.liteCollection.Find(dto => dto.Closed == false && dto.ExpirationTime > DateTime.Now, limit: maxCount); + return this.liteCollection.FindAll(dto => dto.Closed == false && dto.ExpirationTime > DateTimeOffset.Now); } public IEnumerable GetNotifications() @@ -29,7 +29,7 @@ public IEnumerable GetNotifications() public void StoreNotification(NotificationDTO notification) { notification.ThrowIfNull(); - this.liteCollection.Upsert(notification.Id, notification); + this.liteCollection.Add(notification); } public void OpenNotification(NotificationDTO notificationDTO) @@ -41,7 +41,7 @@ public void OpenNotification(NotificationDTO notificationDTO) public void RemoveNotification(NotificationDTO notificationDTO) { - this.liteCollection.Delete(notificationDTO.Id); + this.liteCollection.Delete(notificationDTO); } public void RemoveAllNotifications() diff --git a/Daybreak/Services/Startup/Actions/CleanupDatabases.cs b/Daybreak/Services/Startup/Actions/CleanupDatabases.cs index 46ea175e..099245cc 100644 --- a/Daybreak/Services/Startup/Actions/CleanupDatabases.cs +++ b/Daybreak/Services/Startup/Actions/CleanupDatabases.cs @@ -1,26 +1,26 @@ -using Daybreak.Services.Notifications.Models; +using Daybreak.Services.Database; +using Daybreak.Services.Logging.Models; +using Daybreak.Services.Notifications.Models; using Daybreak.Services.TradeChat.Models; -using LiteDB; using Microsoft.Extensions.Logging; using System.Core.Extensions; using System.Extensions; -using System.Threading; -using System.Threading.Tasks; +using System.Linq; namespace Daybreak.Services.Startup.Actions; -public sealed class CleanupDatabases : StartupActionBase +internal sealed class CleanupDatabases : StartupActionBase { - private readonly ILiteCollection loggingCollection; - private readonly ILiteCollection quotesCollection; - private readonly ILiteCollection notificationsCollection; - private readonly ILiteCollection traderMessagesCollection; + private readonly IDatabaseCollection loggingCollection; + private readonly IDatabaseCollection quotesCollection; + private readonly IDatabaseCollection notificationsCollection; + private readonly IDatabaseCollection traderMessagesCollection; private readonly ILogger logger; public CleanupDatabases( - ILiteCollection loggingCollection, - ILiteCollection quotesCollection, - ILiteCollection notificationsCollection, - ILiteCollection traderMessagesCollection, + IDatabaseCollection loggingCollection, + IDatabaseCollection quotesCollection, + IDatabaseCollection notificationsCollection, + IDatabaseCollection traderMessagesCollection, ILogger logger) { this.loggingCollection = loggingCollection.ThrowIfNull(); @@ -33,25 +33,32 @@ public CleanupDatabases( public override void ExecuteOnStartup() { var scopedLogger = this.logger.CreateScopedLogger(nameof(this.ExecuteOnStartup), string.Empty); - if (this.loggingCollection.LongCount() > 50000) + if (this.loggingCollection.Count() > 50000) { this.loggingCollection.DeleteAll(); scopedLogger.LogInformation("Cleared logging database"); } - if (this.notificationsCollection.LongCount() > 1000) + if (this.notificationsCollection.Count() > 1000) { this.notificationsCollection.DeleteAll(); scopedLogger.LogInformation("Cleared notifications database"); } - if (this.quotesCollection.LongCount() > 1000) + if (this.quotesCollection.Count() > 20000) { + // Delete the oldest 2000 entries in the db. We probably won't need them anymore + var quotes = this.quotesCollection.FindAll().OrderBy(q => q.TimeStamp).Take(2000).ToList(); + foreach(var quote in quotes) + { + this.quotesCollection.Delete(quote); + } + this.quotesCollection.DeleteAll(); scopedLogger.LogInformation("Cleared quotes database"); } - if (this.traderMessagesCollection.LongCount() > 1000) + if (this.traderMessagesCollection.Count() > 1000) { this.traderMessagesCollection.DeleteAll(); scopedLogger.LogInformation("Cleared trader messages database"); diff --git a/Daybreak/Services/Startup/Actions/DeleteOldDatabase.cs b/Daybreak/Services/Startup/Actions/DeleteOldDatabase.cs new file mode 100644 index 00000000..3ac0c0e5 --- /dev/null +++ b/Daybreak/Services/Startup/Actions/DeleteOldDatabase.cs @@ -0,0 +1,15 @@ +using Daybreak.Utils; +using System.IO; + +namespace Daybreak.Services.Startup.Actions; +internal sealed class DeleteOldDatabase : StartupActionBase +{ + public override void ExecuteOnStartup() + { + var oldDbPath = PathUtils.GetAbsolutePathFromRoot("daybreak.db"); + if (File.Exists(oldDbPath)) + { + File.Delete(oldDbPath); + } + } +} diff --git a/Daybreak/Services/Startup/Actions/FixPriceHistoryEntries.cs b/Daybreak/Services/Startup/Actions/FixPriceHistoryEntries.cs deleted file mode 100644 index 02a016bf..00000000 --- a/Daybreak/Services/Startup/Actions/FixPriceHistoryEntries.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Daybreak.Configuration.Options; -using Daybreak.Services.Registry; -using LiteDB; -using System.Configuration; -using System.Core.Extensions; -using System.Linq; - -namespace Daybreak.Services.Startup.Actions; -internal sealed class FixPriceHistoryEntries : StartupActionBase -{ - private const string FixPriceHistoryKey = nameof(FixPriceHistoryEntries); - private const string Done = "Done"; - - private readonly IRegistryService registryService; - private readonly IUpdateableOptions priceHistoryOptions; - private readonly ILiteDatabase database; - - public FixPriceHistoryEntries( - IRegistryService registryService, - ILiteDatabase liteDatabase, - IUpdateableOptions options) - { - this.registryService = registryService.ThrowIfNull(); - this.database = liteDatabase.ThrowIfNull(); - this.priceHistoryOptions = options.ThrowIfNull(); - } - - public override void ExecuteOnStartup() - { - if (this.registryService.TryGetValue(FixPriceHistoryKey, out var value) && - value == Done) - { - return; - } - - var collection = this.database.GetCollection(this.priceHistoryOptions.Value.CollectionName); - if (collection.Find(b => b["_id"].AsString.Contains("_this")).Any()) - { - collection.DeleteAll(); - } - - this.registryService.SaveValue(FixPriceHistoryKey, Done); - } -} diff --git a/Daybreak/Services/TradeChat/IPriceHistoryDatabase.cs b/Daybreak/Services/TradeChat/IPriceHistoryDatabase.cs index 56c71c5c..2694ea0b 100644 --- a/Daybreak/Services/TradeChat/IPriceHistoryDatabase.cs +++ b/Daybreak/Services/TradeChat/IPriceHistoryDatabase.cs @@ -11,7 +11,7 @@ public interface IPriceHistoryDatabase IEnumerable GetQuoteHistory(ItemBase item, DateTime? fromTimestamp, DateTime? toTimestamp); - void AddTraderQuotes(IEnumerable traderQuotes); + bool AddTraderQuotes(IEnumerable traderQuotes); IEnumerable GetQuotesByTimestamp(TraderQuoteType type, DateTime? from = default, DateTime? to = default); diff --git a/Daybreak/Services/TradeChat/ITradeHistoryDatabase.cs b/Daybreak/Services/TradeChat/ITradeHistoryDatabase.cs index 94d99d55..238ff9a3 100644 --- a/Daybreak/Services/TradeChat/ITradeHistoryDatabase.cs +++ b/Daybreak/Services/TradeChat/ITradeHistoryDatabase.cs @@ -6,6 +6,6 @@ namespace Daybreak.Services.TradeChat; public interface ITradeHistoryDatabase { - IEnumerable GetTraderMessagesSinceTime(DateTime since); - void StoreTraderMessage(TraderMessageDTO message); + IEnumerable GetTraderMessagesSinceTime(DateTimeOffset since); + bool StoreTraderMessage(TraderMessageDTO message); } diff --git a/Daybreak/Services/TradeChat/Models/TraderMessageDTO.cs b/Daybreak/Services/TradeChat/Models/TraderMessageDTO.cs index 9f9abc8e..1d1cc690 100644 --- a/Daybreak/Services/TradeChat/Models/TraderMessageDTO.cs +++ b/Daybreak/Services/TradeChat/Models/TraderMessageDTO.cs @@ -1,16 +1,17 @@ -using System; +using Realms; +using System; namespace Daybreak.Services.TradeChat.Models; -public sealed class TraderMessageDTO +public sealed class TraderMessageDTO : RealmObject { - public TraderSource TraderSource { get; init; } + public int TraderSource { get; init; } public string Message { get; init; } = string.Empty; public string Sender { get; init; } = string.Empty; - public DateTime Timestamp { get; init; } - + public DateTimeOffset Timestamp { get; init; } + [PrimaryKey] public long Id { get; init; } } diff --git a/Daybreak/Services/TradeChat/Models/TraderQuoteDTO.cs b/Daybreak/Services/TradeChat/Models/TraderQuoteDTO.cs index 26c5f5f4..14cffd45 100644 --- a/Daybreak/Services/TradeChat/Models/TraderQuoteDTO.cs +++ b/Daybreak/Services/TradeChat/Models/TraderQuoteDTO.cs @@ -1,23 +1,19 @@ -using System; -using System.Extensions; +using Realms; +using System; namespace Daybreak.Services.TradeChat.Models; -public sealed class TraderQuoteDTO +public sealed class TraderQuoteDTO : RealmObject { - public string Id => $"{this.ItemId}{(this.ModifiersHash?.IsNullOrWhiteSpace() is true ? "" : $"_{this.ModifiersHash}")}_{(this.IsLatest ? "LATEST" : this.TimeStamp.ToOADate())}_{this.TraderQuoteType}"; - public int ItemId { get; set; } public int Price { get; set; } - public bool IsLatest { get; set; } - public string? ModifiersHash { get; set; } - public TraderQuoteType TraderQuoteType { get; set; } + public int TraderQuoteType { get; set; } - public DateTime TimeStamp { get; set; } + public DateTimeOffset TimeStamp { get; set; } - public DateTime InsertionTime { get; set; } + public DateTimeOffset InsertionTime { get; set; } } diff --git a/Daybreak/Services/TradeChat/Models/TraderQuotePayload.cs b/Daybreak/Services/TradeChat/Models/TraderQuotePayload.cs index 73e9b2de..ea954b2b 100644 --- a/Daybreak/Services/TradeChat/Models/TraderQuotePayload.cs +++ b/Daybreak/Services/TradeChat/Models/TraderQuotePayload.cs @@ -11,4 +11,7 @@ internal sealed class TraderQuotePayload [JsonProperty("t")] [JsonConverter(typeof(UnixDateTimeConverter))] public DateTime TimeStamp { get; set; } + + [JsonProperty("s")] + public int Type { get; set; } } diff --git a/Daybreak/Services/TradeChat/PriceHistoryDatabase.cs b/Daybreak/Services/TradeChat/PriceHistoryDatabase.cs index 63bf14fc..dde4b9e5 100644 --- a/Daybreak/Services/TradeChat/PriceHistoryDatabase.cs +++ b/Daybreak/Services/TradeChat/PriceHistoryDatabase.cs @@ -1,22 +1,23 @@ using Daybreak.Models.Guildwars; +using Daybreak.Services.Database; using Daybreak.Services.TradeChat.Models; -using LiteDB; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Core.Extensions; using System.Extensions; +using System.Linq; namespace Daybreak.Services.TradeChat; internal sealed class PriceHistoryDatabase : IPriceHistoryDatabase { private readonly IItemHashService itemHashService; - private readonly ILiteCollection collection; + private readonly IDatabaseCollection collection; private readonly ILogger logger; public PriceHistoryDatabase( IItemHashService itemHashService, - ILiteCollection collection, + IDatabaseCollection collection, ILogger logger) { this.itemHashService = itemHashService.ThrowIfNull(); @@ -24,39 +25,42 @@ public PriceHistoryDatabase( this.logger = logger.ThrowIfNull(); } - public void AddTraderQuotes(IEnumerable traderQuotes) + public bool AddTraderQuotes(IEnumerable traderQuotes) { var scopedLogger = this.logger.CreateScopedLogger(nameof(this.AddTraderQuotes), string.Empty); scopedLogger.LogDebug("Inserting quotes"); - foreach(var quote in traderQuotes) - { - this.collection.Upsert(quote.Id, quote); - } - + this.collection.AddBulk(traderQuotes); scopedLogger.LogDebug("Inserted quotes"); + return true; } public IEnumerable GetLatestQuotes(TraderQuoteType traderQuoteType) { var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetLatestQuotes), string.Empty); scopedLogger.LogDebug($"Retrieving latest quotes"); - var items = this.collection.Find(t => t.IsLatest == true && t.TraderQuoteType == traderQuoteType); + var items = this.collection.FindAll(t => t.TraderQuoteType == (int)traderQuoteType).OrderByDescending(q => q.InsertionTime).ToList(); + var latestItems = items.GroupBy(q => q.ItemId).Select(g => g.First()).ToList(); scopedLogger.LogDebug($"Retrieved latest quotes"); - return items; + return latestItems; } public IEnumerable GetQuoteHistory(ItemBase item, DateTime? fromTimestamp, DateTime? toTimestamp) { var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetQuoteHistory), item.Id.ToString()); - fromTimestamp ??= DateTime.MinValue; - toTimestamp ??= DateTime.MaxValue; - scopedLogger.LogDebug($"Retrieving quotes for item {item.Id} with timestamp between [{fromTimestamp}] and [{toTimestamp}]"); + var fromO = fromTimestamp.HasValue ? + new DateTimeOffset(fromTimestamp.Value) : + DateTimeOffset.MinValue; + var toO = toTimestamp.HasValue ? + new DateTimeOffset(toTimestamp.Value) : + DateTimeOffset.MaxValue; + scopedLogger.LogDebug($"Retrieving quotes for item {item.Id} with timestamp between [{fromO}] and [{toO}]"); var modifiersHash = item.Modifiers is not null ? this.itemHashService.ComputeHash(item) : default; - var items = this.collection.Find(t => + var items = this.collection.FindAll(t => t.ItemId == item.Id && t.ModifiersHash == modifiersHash && - t.TimeStamp >= fromTimestamp && - t.TimeStamp <= toTimestamp); + t.TimeStamp >= fromO && + t.TimeStamp <= toO && + t.TraderQuoteType == (int)TraderQuoteType.Sell); scopedLogger.LogDebug($"Retrieved quotes for item {item.Id}"); return items; } @@ -64,11 +68,14 @@ public IEnumerable GetQuoteHistory(ItemBase item, DateTime? from public IEnumerable GetQuotesByTimestamp(TraderQuoteType type, DateTime? from = default, DateTime? to = default) { var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetQuotesByTimestamp), string.Empty); - from ??= DateTime.MinValue; - to ??= DateTime.Now; - - scopedLogger.LogDebug($"Retrieving all quotes by timestamp between [{from}] and [{to}]"); - var items = this.collection.Find(t => t.TimeStamp >= from && t.TimeStamp <= to && t.TraderQuoteType == type); + var fromO = from.HasValue ? + new DateTimeOffset(from.Value) : + DateTimeOffset.MinValue; + var toO = to.HasValue ? + new DateTimeOffset(to.Value) : + DateTimeOffset.Now; + scopedLogger.LogDebug($"Retrieving all quotes by timestamp between [{fromO}] and [{toO}]"); + var items = this.collection.FindAll(t => t.TimeStamp >= fromO && t.TimeStamp <= toO && t.TraderQuoteType == (int)type); scopedLogger.LogDebug("Retrieved quotes by timestamp"); return items; } @@ -76,11 +83,14 @@ public IEnumerable GetQuotesByTimestamp(TraderQuoteType type, Da public IEnumerable GetQuotesByInsertionTime(TraderQuoteType type, DateTime? from = default, DateTime? to = default) { var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetQuotesByInsertionTime), string.Empty); - from ??= DateTime.MinValue; - to ??= DateTime.Now; - - scopedLogger.LogDebug($"Retrieving all quotes by insertion time between [{from}] and [{to}]"); - var items = this.collection.Find(t => t.InsertionTime >= from && t.InsertionTime <= to && t.TraderQuoteType == type); + var fromO = from.HasValue ? + new DateTimeOffset(from.Value) : + DateTimeOffset.MinValue; + var toO = to.HasValue ? + new DateTimeOffset(to.Value) : + DateTimeOffset.Now; + scopedLogger.LogDebug($"Retrieving all quotes by insertion time between [{fromO}] and [{toO}]"); + var items = this.collection.FindAll(t => t.InsertionTime >= fromO && t.InsertionTime <= toO && t.TraderQuoteType == (int)type); scopedLogger.LogDebug("Retrieved quotes by insertion time"); return items; } diff --git a/Daybreak/Services/TradeChat/PriceHistoryService.cs b/Daybreak/Services/TradeChat/PriceHistoryService.cs index 62cacc65..a2348928 100644 --- a/Daybreak/Services/TradeChat/PriceHistoryService.cs +++ b/Daybreak/Services/TradeChat/PriceHistoryService.cs @@ -55,7 +55,7 @@ public async Task> GetPriceHistory(ItemBase itemBase, C private async Task> UpdateCacheAndGetPriceHistory(ItemBase itemBase, CancellationToken cancellationToken, DateTime? from = default, DateTime? to = default) { var itemId = itemBase.Modifiers is not null ? $"{itemBase.Id}-{this.itemHashService.ComputeHash(itemBase)}" : itemBase.Id.ToString(); - if (!this.options.Value.PriceHistoryMetadata.TryGetValue(itemId, out var lastQueryTime)) + if (!this.options.Value.PricingHistoryMetadata.TryGetValue(itemId, out var lastQueryTime)) { lastQueryTime = MinEpochTime; } @@ -63,19 +63,18 @@ private async Task> UpdateCacheAndGetPriceHistory(ItemB if (lastQueryTime + this.options.Value.UpdateInterval < DateTime.UtcNow) { await this.FetchAndUpdatePricingHistoryCache(itemBase, cancellationToken, lastQueryTime, DateTime.UtcNow); - this.options.Value.PriceHistoryMetadata[itemId] = DateTime.UtcNow; + this.options.Value.PricingHistoryMetadata[itemId] = DateTime.UtcNow; this.options.UpdateOption(); } - var quotes = this.priceHistoryDatabase.GetQuoteHistory(itemBase, from, to); var retList = new List(); - foreach(var quote in quotes) + foreach (var quote in this.priceHistoryDatabase.GetQuoteHistory(itemBase, from, to)) { retList.Add(new TraderQuote { Item = itemBase, Price = quote.Price, - Timestamp = quote.TimeStamp + Timestamp = quote.TimeStamp.LocalDateTime }); } @@ -92,7 +91,7 @@ private async Task FetchAndUpdatePricingHistoryCache(ItemBase itemBase, Cancella ItemId = quote.Item?.Id ?? 0, Price = quote.Price, ModifiersHash = quote.Item?.Modifiers is not null ? this.itemHashService.ComputeHash(quote.Item) : default, - TraderQuoteType = TraderQuoteType.Buy, + TraderQuoteType = (int)TraderQuoteType.Sell, InsertionTime = quote.Timestamp ?? insertionTime //Use timestamp as this method basically recreates the kamadan tradechat database })); } @@ -116,7 +115,8 @@ private async Task> FetchPricingHistoryInternal(ItemBas var responseString = await response.Content.ReadAsStringAsync(); var responseList = JsonConvert.DeserializeObject>(responseString); - return responseList?.Select(p => new TraderQuote { Item = itemBase, Price = p.Price, Timestamp = p.TimeStamp }) ?? + return responseList?.Where(p => p.Type == 1) + .Select(p => new TraderQuote { Item = itemBase, Price = p.Price, Timestamp = p.TimeStamp }) ?? []; } catch (Exception ex) diff --git a/Daybreak/Services/TradeChat/TradeAlertingService.cs b/Daybreak/Services/TradeChat/TradeAlertingService.cs index 3befbf73..8d01d4cc 100644 --- a/Daybreak/Services/TradeChat/TradeAlertingService.cs +++ b/Daybreak/Services/TradeChat/TradeAlertingService.cs @@ -105,7 +105,7 @@ public void OnStartup() private async void StartAlertingService(CancellationToken cancellationToken) { var lastCheckTime = this.options.Value.LastCheckTime; - var timeSinceLastCheckTime = DateTime.UtcNow - lastCheckTime; + var timeSinceLastCheckTime = DateTimeOffset.UtcNow - lastCheckTime; if (timeSinceLastCheckTime > this.options.Value.MaxLookbackPeriod) { timeSinceLastCheckTime = this.options.Value.MaxLookbackPeriod; @@ -113,7 +113,7 @@ private async void StartAlertingService(CancellationToken cancellationToken) this.options.Value.LastCheckTime = DateTime.UtcNow; this.options.UpdateOption(); - var savedTrades = this.tradeHistoryDatabase.GetTraderMessagesSinceTime(DateTime.UtcNow - timeSinceLastCheckTime).ToList(); + var savedTrades = this.tradeHistoryDatabase.GetTraderMessagesSinceTime(DateTimeOffset.UtcNow - timeSinceLastCheckTime); if (savedTrades.None()) { var kamadanHistoryTradesTask = GetTraderMessages(this.kamadanTradeChatService, TraderSource.Kamadan, DateTime.UtcNow - timeSinceLastCheckTime, cancellationToken); @@ -150,7 +150,7 @@ private async Task CheckLiveTrades(ITradeChatService tradeChatService, Tra Id = message.Timestamp.Ticks, Message = message.Message, Sender = message.Sender, - TraderSource = traderSource + TraderSource = (int)traderSource }; this.tradeHistoryDatabase.StoreTraderMessage(traderMessageDTO); @@ -245,7 +245,7 @@ private void NotifyAlertMatch(TraderMessageDTO traderMessageDTO, ITradeAlert ale { Message = traderMessageDTO.Message, Sender = traderMessageDTO.Sender, - Timestamp = traderMessageDTO.Timestamp + Timestamp = traderMessageDTO.Timestamp.LocalDateTime }; this.notificationService.NotifyInformation( @@ -288,7 +288,7 @@ private static async Task> GetTraderMessages(IT Id = t.Timestamp.Ticks, Message = t.Message, Sender = t.Sender, - TraderSource = traderSource + TraderSource = (int)traderSource }).ToList(); return orderedTrades.Where(t => t.Timestamp > since); @@ -312,15 +312,15 @@ private static async Task> GetTraderMessagesSince< Id = t.Timestamp.Ticks, Message = t.Message, Sender = t.Sender, - TraderSource = traderSource + TraderSource = (int)traderSource }).ToList(); retrievedTrades.AddRange(orderedTrades); retrievedCount = orderedTrades.Count; var maybeMaxTime = orderedTrades.LastOrDefault()?.Timestamp; - if (maybeMaxTime is DateTime maxTime) + if (maybeMaxTime is DateTimeOffset maxTime) { - since = maxTime; + since = maxTime.LocalDateTime; } } while (retrievedCount > 0); diff --git a/Daybreak/Services/TradeChat/TradeHistoryDatabase.cs b/Daybreak/Services/TradeChat/TradeHistoryDatabase.cs index 73f3aae7..f51f6118 100644 --- a/Daybreak/Services/TradeChat/TradeHistoryDatabase.cs +++ b/Daybreak/Services/TradeChat/TradeHistoryDatabase.cs @@ -1,5 +1,5 @@ -using Daybreak.Services.TradeChat.Models; -using LiteDB; +using Daybreak.Services.Database; +using Daybreak.Services.TradeChat.Models; using System; using System.Collections.Generic; using System.Core.Extensions; @@ -8,21 +8,21 @@ namespace Daybreak.Services.TradeChat; internal sealed class TradeHistoryDatabase : ITradeHistoryDatabase { - private readonly ILiteCollection liteCollection; + private readonly IDatabaseCollection liteCollection; public TradeHistoryDatabase( - ILiteCollection liteCollection) + IDatabaseCollection liteCollection) { this.liteCollection = liteCollection.ThrowIfNull(); } - public IEnumerable GetTraderMessagesSinceTime(DateTime since) + public IEnumerable GetTraderMessagesSinceTime(DateTimeOffset since) { - return this.liteCollection.Find(t => t.Timestamp > since); + return this.liteCollection.FindAll(t => t.Timestamp > since); } - public void StoreTraderMessage(TraderMessageDTO message) + public bool StoreTraderMessage(TraderMessageDTO message) { - this.liteCollection.Upsert(message.Id, message); + return this.liteCollection.Update(message); } } diff --git a/Daybreak/Services/TradeChat/TraderQuoteService.cs b/Daybreak/Services/TradeChat/TraderQuoteService.cs index 33519af1..77d3ee0c 100644 --- a/Daybreak/Services/TradeChat/TraderQuoteService.cs +++ b/Daybreak/Services/TradeChat/TraderQuoteService.cs @@ -80,7 +80,7 @@ private async Task> GetBuyQuotesInternal(CancellationTo { Item = item, Price = quoteDTO.Price, - Timestamp = quoteDTO.TimeStamp, + Timestamp = quoteDTO.TimeStamp.LocalDateTime, }); } @@ -110,7 +110,7 @@ private async Task> GetSellQuotesInternal(CancellationT { Item = item, Price = quoteDTO.Price, - Timestamp = quoteDTO.TimeStamp, + Timestamp = quoteDTO.TimeStamp.LocalDateTime, }); } @@ -131,8 +131,7 @@ private async Task> GetSellQuotesInternal(CancellationT ModifiersHash = quote.Item?.Modifiers is null ? string.Empty : this.itemHashService.ComputeHash(quote.Item), InsertionTime = insertionTime, TimeStamp = quote.Timestamp ?? insertionTime, - TraderQuoteType = TraderQuoteType.Buy, - IsLatest = true, + TraderQuoteType = (int)TraderQuoteType.Buy })); this.priceHistoryDatabase.AddTraderQuotes(sellQuotes @@ -144,8 +143,7 @@ private async Task> GetSellQuotesInternal(CancellationT ModifiersHash = quote.Item?.Modifiers is null ? string.Empty : this.itemHashService.ComputeHash(quote.Item), InsertionTime = insertionTime, TimeStamp = quote.Timestamp ?? insertionTime, - TraderQuoteType = TraderQuoteType.Sell, - IsLatest = true + TraderQuoteType = (int)TraderQuoteType.Sell })); this.options.Value.LastCheckTime = insertionTime; diff --git a/Daybreak/Views/LogsView.xaml.cs b/Daybreak/Views/LogsView.xaml.cs index d50ff7b3..88d9249a 100644 --- a/Daybreak/Views/LogsView.xaml.cs +++ b/Daybreak/Views/LogsView.xaml.cs @@ -58,10 +58,10 @@ private async void LogManager_ReceivedLog(object? _, Log e) private async void UpdateLogs() { - var logs = this.logManager.GetLogs().ToArray(); + var logs = this.logManager.GetLogs().ToList(); this.TextEditor.Clear(); this.cachedText.Clear(); - if (logs.Length > MaximumLookbackPeriod) + if (logs.Count > MaximumLookbackPeriod) { var maximumLookbackPeriodMessage = string.Format(MaximumLookbackMessageTemplate, MaximumLookbackPeriod); var adornedMessage = SetupAdornedMessage(maximumLookbackPeriodMessage); diff --git a/Daybreak/Views/NotificationsView.xaml.cs b/Daybreak/Views/NotificationsView.xaml.cs index 60b98277..5beb2276 100644 --- a/Daybreak/Views/NotificationsView.xaml.cs +++ b/Daybreak/Views/NotificationsView.xaml.cs @@ -21,6 +21,7 @@ public partial class NotificationsView : UserControl private readonly INotificationProducer notificationProducer; private readonly ILogger logger; + private readonly SemaphoreSlim semaphoreSlim = new(1); private CancellationTokenSource? cancellationTokenSource; [GenerateDependencyProperty(InitialValue = true)] @@ -57,36 +58,34 @@ private async void PeriodicallyLoadNotifications(CancellationToken cancellationT while (!cancellationToken.IsCancellationRequested) { var unsortedNotifications = this.ShowAll ? - this.notificationProducer.GetAllNotifications() : - this.notificationProducer.GetPendingNotifications(); + this.notificationProducer.GetAllNotifications().ToList() : + this.notificationProducer.GetPendingNotifications().ToList(); var notifications = this.Descending ? unsortedNotifications.OrderByDescending(n => n.CreationTime).Take(100).ToList() : unsortedNotifications.OrderBy(n => n.CreationTime).Take(100).ToList(); - lock (this.Notifications) + using var context = await this.semaphoreSlim.Acquire(); + var notificationsToAdd = notifications.Where(n => this.Notifications.None(n2 => n2.Id == n.Id)).ToList(); + var notificationsToRemove = this.Notifications.Where(n => notifications.None(n2 => n2.Id == n.Id)).ToList(); + foreach (var notification in notificationsToRemove) { - var notificationsToAdd = notifications.Where(n => this.Notifications.None(n2 => n2.Id == n.Id)).ToList(); - var notificationsToRemove = this.Notifications.Where(n => notifications.None(n2 => n2.Id == n.Id)).ToList(); - foreach (var notification in notificationsToRemove) + this.Notifications.Remove(notification); + } + + foreach (var notification in notificationsToAdd) + { + var index = this.Descending ? + this.Notifications.IndexOfWhere(n2 => n2.CreationTime <= notification.CreationTime) : + this.Notifications.IndexOfWhere(n2 => n2.CreationTime >= notification.CreationTime); + if (index == -1) { - this.Notifications.Remove(notification); + this.Notifications.Add(notification); } - - foreach(var notification in notificationsToAdd) + else { - var index = this.Descending ? - this.Notifications.IndexOfWhere(n2 => n2.CreationTime <= notification.CreationTime) : - this.Notifications.IndexOfWhere(n2 => n2.CreationTime >= notification.CreationTime); - if (index == -1) - { - this.Notifications.Add(notification); - } - else - { - this.Notifications.Insert(index, notification); - } + this.Notifications.Insert(index, notification); } } - + await Task.Delay(1000, cancellationToken); } } @@ -121,17 +120,15 @@ private void SortButton_Clicked(object sender, EventArgs e) } } - private void ToggleSwitch_Toggled(object sender, System.Windows.RoutedEventArgs e) + private async void ToggleSwitch_Toggled(object sender, System.Windows.RoutedEventArgs e) { - lock (this.Notifications) - { - var unsortedNotifications = this.ShowAll ? + using var context = await this.semaphoreSlim.Acquire(); + var unsortedNotifications = this.ShowAll ? this.notificationProducer.GetAllNotifications() : this.notificationProducer.GetPendingNotifications(); - var sortedNotification = this.Descending ? - unsortedNotifications.OrderByDescending(n => n.CreationTime).ToList() : - unsortedNotifications.OrderBy(n => n.CreationTime).ToList(); - this.Notifications.ClearAnd().AddRange(sortedNotification); - } + var sortedNotification = this.Descending ? + unsortedNotifications.OrderByDescending(n => n.CreationTime).ToList() : + unsortedNotifications.OrderBy(n => n.CreationTime).ToList(); + this.Notifications.ClearAnd().AddRange(sortedNotification); } } diff --git a/Daybreak/Views/Trade/PriceQuotesView.xaml.cs b/Daybreak/Views/Trade/PriceQuotesView.xaml.cs index 1f6255e2..6fa118f1 100644 --- a/Daybreak/Views/Trade/PriceQuotesView.xaml.cs +++ b/Daybreak/Views/Trade/PriceQuotesView.xaml.cs @@ -88,7 +88,7 @@ private void SearchTextBox_TextChanged(object _, string e) private void HighlightButton_Clicked(object sender, EventArgs _) { if (sender is not FrameworkElement element || - element.DataContext is not TraderQuote traderQuote || + element.DataContext is not TraderQuoteModel traderQuote || traderQuote.Item is not ItemBase item) { return; diff --git a/GWCA b/GWCA index a0e63274..10f4b40a 160000 --- a/GWCA +++ b/GWCA @@ -1 +1 @@ -Subproject commit a0e63274fce713e44d22bdf15191e605ebee2b4c +Subproject commit 10f4b40a3824a5da632b7153c6bc037133d504af