From 50cb43d501285949070d913912a30a3dae293e4b Mon Sep 17 00:00:00 2001 From: Neakita Date: Thu, 24 Oct 2024 20:57:26 +0500 Subject: [PATCH] periodic autosaving --- .../Screenshotting/ScreenshotsDataAccess.cs | 17 +++++- SightKeeper.Application/WeightsDataAccess.cs | 47 ++++++++++++++-- .../Setup/ServicesBootstrapper.cs | 20 ++++--- .../BinarySerializationTests.cs | 5 +- SightKeeper.Data/Binary/AppDataFormatter.cs | 41 +++++--------- .../Binary/Conversion/AppDataConverter.cs | 27 +++++++++ .../Binary/Replication/AppDataReplicator.cs | 25 +++++++++ .../Binary/Services/AppDataAccess.cs | 10 ++++ .../Binary/Services/DataSetsDataAccess.cs | 22 ++++++-- .../FileSystemScreenshotsDataAccess.cs | 54 +++++++++++++++--- .../Services/FileSystemWeightsDataAccess.cs | 55 ++++++++++++++++--- .../Binary/Services/GamesDataAccess.cs | 16 +++++- .../Binary/Services/PeriodicAppDataSaver.cs | 31 +++++++++++ 13 files changed, 302 insertions(+), 68 deletions(-) create mode 100644 SightKeeper.Data/Binary/Conversion/AppDataConverter.cs create mode 100644 SightKeeper.Data/Binary/Replication/AppDataReplicator.cs create mode 100644 SightKeeper.Data/Binary/Services/PeriodicAppDataSaver.cs diff --git a/SightKeeper.Application/Screenshotting/ScreenshotsDataAccess.cs b/SightKeeper.Application/Screenshotting/ScreenshotsDataAccess.cs index 42309b44..f4cb0bf7 100644 --- a/SightKeeper.Application/Screenshotting/ScreenshotsDataAccess.cs +++ b/SightKeeper.Application/Screenshotting/ScreenshotsDataAccess.cs @@ -1,4 +1,5 @@ -using System.Reactive.Linq; +using System.Collections.Immutable; +using System.Reactive.Linq; using System.Reactive.Subjects; using CommunityToolkit.HighPerformance; using SightKeeper.Domain.Model; @@ -21,7 +22,7 @@ public Screenshot CreateScreenshot( DateTimeOffset creationDate) { Vector2 resolution = new((ushort)imageData.Width, (ushort)imageData.Height); - var screenshot = library.CreateScreenshot(creationDate, resolution, out var removedScreenshots); + var screenshot = CreateScreenshotInLibrary(library, creationDate, resolution, out var removedScreenshots); foreach (var removedScreenshot in removedScreenshots) { DeleteScreenshotData(removedScreenshot); @@ -32,6 +33,11 @@ public Screenshot CreateScreenshot( return screenshot; } + protected virtual Screenshot CreateScreenshotInLibrary(ScreenshotsLibrary library, DateTimeOffset creationDate, Vector2 resolution, out ImmutableArray removedScreenshots) + { + return library.CreateScreenshot(creationDate, resolution, out removedScreenshots); + } + public Screenshot CreateScreenshot( ScreenshotsLibrary library, ReadOnlySpan2D imageData, @@ -49,7 +55,7 @@ public Screenshot CreateScreenshot( public void DeleteScreenshot(Screenshot screenshot) { - screenshot.DeleteFromLibrary(); + DeleteScreenshotFromLibrary(screenshot); DeleteScreenshotData(screenshot); _removed.OnNext(screenshot); } @@ -60,6 +66,11 @@ public void Dispose() _removed.Dispose(); } + protected virtual void DeleteScreenshotFromLibrary(Screenshot screenshot) + { + screenshot.DeleteFromLibrary(); + } + protected abstract void SaveScreenshotData(Screenshot screenshot, ReadOnlySpan2D image); protected abstract void DeleteScreenshotData(Screenshot screenshot); diff --git a/SightKeeper.Application/WeightsDataAccess.cs b/SightKeeper.Application/WeightsDataAccess.cs index 46a08297..cda46308 100644 --- a/SightKeeper.Application/WeightsDataAccess.cs +++ b/SightKeeper.Application/WeightsDataAccess.cs @@ -18,7 +18,7 @@ public PlainWeights CreateWeights( IEnumerable tags, Composition? composition) where TTag : Tag, MinimumTagsCount { - var weights = library.CreateWeights(creationDate, modelSize, metrics, resolution, tags, composition); + var weights = CreateWeightsInLibrary(library, creationDate, modelSize, metrics, resolution, tags, composition); SaveWeightsData(weights, data); return weights; } @@ -36,14 +36,14 @@ public PoserWeights CreateWeights( where TTag : PoserTag where TKeyPointTag : KeyPointTag { - var weights = library.CreateWeights(creationDate, modelSize, metrics, resolution, tags, keyPointTags, composition); + var weights = CreateWeightsInLibrary(library, creationDate, modelSize, metrics, resolution, tags, keyPointTags, composition); SaveWeightsData(weights, data); return weights; } public void RemoveWeights(PlainWeights weights) where TTag : Tag, MinimumTagsCount { - weights.Library.RemoveWeights(weights); + DeleteWeightsFromLibrary(weights); RemoveWeightsData(weights); } @@ -51,12 +51,51 @@ public void RemoveWeights(PoserWeights w where TTag : PoserTag where TKeyPointTag : KeyPointTag { - weights.Library.RemoveWeights(weights); + DeleteWeightsFromLibrary(weights); RemoveWeightsData(weights); } public abstract byte[] LoadWeightsData(Weights weights); + protected virtual PlainWeights CreateWeightsInLibrary( + WeightsLibrary library, + DateTime creationDate, + ModelSize modelSize, + WeightsMetrics metrics, + Vector2 resolution, + IEnumerable tags, + Composition? composition) + where TTag : Tag, MinimumTagsCount + { + return library.CreateWeights(creationDate, modelSize, metrics, resolution, tags, composition); + } + + protected virtual PoserWeights CreateWeightsInLibrary( + WeightsLibrary library, + DateTime creationDate, + ModelSize modelSize, + WeightsMetrics metrics, + Vector2 resolution, + IEnumerable tags, + IEnumerable keyPointTags, + Composition? composition) + where TTag : PoserTag where TKeyPointTag : KeyPointTag + { + return library.CreateWeights(creationDate, modelSize, metrics, resolution, tags, keyPointTags, composition); + } + + protected virtual void DeleteWeightsFromLibrary(PlainWeights weights) + where TTag : Tag, MinimumTagsCount + { + weights.Library.RemoveWeights(weights); + } + + protected virtual void DeleteWeightsFromLibrary(PoserWeights weights) + where TTag : PoserTag where TKeyPointTag : KeyPointTag + { + weights.Library.RemoveWeights(weights); + } + protected abstract void SaveWeightsData(Weights weights, byte[] data); protected abstract void RemoveWeightsData(Weights weights); } \ No newline at end of file diff --git a/SightKeeper.Avalonia/Setup/ServicesBootstrapper.cs b/SightKeeper.Avalonia/Setup/ServicesBootstrapper.cs index 9da421e3..920df88b 100644 --- a/SightKeeper.Avalonia/Setup/ServicesBootstrapper.cs +++ b/SightKeeper.Avalonia/Setup/ServicesBootstrapper.cs @@ -1,4 +1,5 @@ -using Autofac; +using System; +using Autofac; using FluentValidation; using MemoryPack; using SightKeeper.Application; @@ -22,13 +23,11 @@ internal static class ServicesBootstrapper public static void Setup(ContainerBuilder builder) { SetupBinarySerialization(builder); - builder.RegisterType().As().SingleInstance(); builder.RegisterType(); builder.RegisterType().As>(); builder.RegisterType(); builder.RegisterType().SingleInstance(); builder.RegisterGeneric(typeof(ObservableRepository<>)).SingleInstance(); - builder.RegisterType().As>().As>().As>().SingleInstance(); builder.RegisterType(); builder.RegisterType().SingleInstance(); builder.RegisterType().As(); @@ -40,13 +39,20 @@ public static void Setup(ContainerBuilder builder) private static void SetupBinarySerialization(ContainerBuilder builder) { - FileSystemScreenshotsDataAccess screenshotsDataAccess = new(); - FileSystemWeightsDataAccess weightsDataAccess = new(); - MemoryPackFormatterProvider.Register(new AppDataFormatter(screenshotsDataAccess)); AppDataAccess appDataAccess = new(); + object locker = new(); + FileSystemScreenshotsDataAccess screenshotsDataAccess = new(appDataAccess, locker); + FileSystemWeightsDataAccess weightsDataAccess = new(appDataAccess, locker); + MemoryPackFormatterProvider.Register(new AppDataFormatter(screenshotsDataAccess, locker)); appDataAccess.Load(); builder.RegisterInstance(screenshotsDataAccess).AsSelf().As().As>(); builder.RegisterInstance(weightsDataAccess).As(); - builder.RegisterInstance(appDataAccess).AsSelf().As().OnRelease(dataAccess => dataAccess.Save()); + builder.RegisterInstance(appDataAccess).AsSelf().As(); + DataSetsDataAccess dataSetsDataAccess = new(appDataAccess, locker, screenshotsDataAccess); + builder.RegisterInstance(dataSetsDataAccess).As>().As>().As>(); + Data.Binary.Services.GamesDataAccess gamesDataAccess = new(appDataAccess, locker); + builder.RegisterInstance(gamesDataAccess).As(); + PeriodicAppDataSaver periodicAppDataSaver = new(appDataAccess); + builder.RegisterInstance(periodicAppDataSaver).As(); } } \ No newline at end of file diff --git a/SightKeeper.Data.Tests/BinarySerializationTests.cs b/SightKeeper.Data.Tests/BinarySerializationTests.cs index 40449cae..e48bf546 100644 --- a/SightKeeper.Data.Tests/BinarySerializationTests.cs +++ b/SightKeeper.Data.Tests/BinarySerializationTests.cs @@ -39,8 +39,9 @@ private static ReadOnlySpan2D SampleImageData public void ShouldSaveAndLoadAppData() { AppDataAccess dataAccess = new(); - FileSystemScreenshotsDataAccess screenshotsDataAccess = new(); - MemoryPackFormatterProvider.Register(new AppDataFormatter(screenshotsDataAccess)); + object locker = new(); + FileSystemScreenshotsDataAccess screenshotsDataAccess = new(dataAccess, locker); + MemoryPackFormatterProvider.Register(new AppDataFormatter(screenshotsDataAccess, locker)); Game game = new("PayDay 2", "payday2"); dataAccess.Data.Games.Add(game); foreach (var dataSet in CreateDataSets(screenshotsDataAccess, game)) diff --git a/SightKeeper.Data/Binary/AppDataFormatter.cs b/SightKeeper.Data/Binary/AppDataFormatter.cs index 3fe41b75..faea8f6d 100644 --- a/SightKeeper.Data/Binary/AppDataFormatter.cs +++ b/SightKeeper.Data/Binary/AppDataFormatter.cs @@ -1,22 +1,19 @@ -using System.Collections.Immutable; -using MemoryPack; +using MemoryPack; using SightKeeper.Data.Binary.Conversion; -using SightKeeper.Data.Binary.Conversion.DataSets; -using SightKeeper.Data.Binary.Conversion.Profiles; using SightKeeper.Data.Binary.Replication; -using SightKeeper.Data.Binary.Replication.DataSets; -using SightKeeper.Data.Binary.Replication.Profiles; using SightKeeper.Data.Binary.Services; namespace SightKeeper.Data.Binary; public sealed class AppDataFormatter : MemoryPackFormatter { - public AppDataFormatter(FileSystemScreenshotsDataAccess screenshotsDataAccess) + public AppDataFormatter(FileSystemScreenshotsDataAccess screenshotsDataAccess, object conversionLock) { - _screenshotsDataAccess = screenshotsDataAccess; + _conversionLock = conversionLock; + _converter = new AppDataConverter(screenshotsDataAccess); + _replicator = new AppDataReplicator(screenshotsDataAccess); } - + public override void Serialize(ref MemoryPackWriter writer, scoped ref AppData? value) { if (value == null) @@ -24,17 +21,9 @@ public override void Serialize(ref MemoryPackWriter, ObservableDataAccess, WriteDataAccess +public sealed class DataSetsDataAccess : ReadDataAccess, ObservableDataAccess, + WriteDataAccess { public IReadOnlyCollection Items => _appDataAccess.Data.DataSets; public IObservable Added => _added.AsObservable(); public IObservable Removed => _removed.AsObservable(); - public DataSetsDataAccess(AppDataAccess appDataAccess) + public DataSetsDataAccess(AppDataAccess appDataAccess, object locker, FileSystemScreenshotsDataAccess screenshotsDataAccess) { _appDataAccess = appDataAccess; + _locker = locker; + _screenshotsDataAccess = screenshotsDataAccess; } public void Add(DataSet dataSet) { - Guard.IsTrue(_appDataAccess.Data.DataSets.Add(dataSet)); + bool isAdded; + lock (_locker) + isAdded = _appDataAccess.Data.DataSets.Add(dataSet); + Guard.IsTrue(isAdded); + _appDataAccess.SetDataChanged(); _added.OnNext(dataSet); } public void Remove(DataSet dataSet) { - Guard.IsTrue(_appDataAccess.Data.DataSets.Remove(dataSet)); + bool isRemoved; + lock (_locker) + isRemoved = _appDataAccess.Data.DataSets.Remove(dataSet); + _screenshotsDataAccess.DeleteAllScreenshotsData(dataSet.ScreenshotsLibrary); + Guard.IsTrue(isRemoved); + _appDataAccess.SetDataChanged(); _removed.OnNext(dataSet); } private readonly AppDataAccess _appDataAccess; + private readonly object _locker; + private readonly FileSystemScreenshotsDataAccess _screenshotsDataAccess; private readonly Subject _added = new(); private readonly Subject _removed = new(); } \ No newline at end of file diff --git a/SightKeeper.Data/Binary/Services/FileSystemScreenshotsDataAccess.cs b/SightKeeper.Data/Binary/Services/FileSystemScreenshotsDataAccess.cs index c36c2614..9e9ab66f 100644 --- a/SightKeeper.Data/Binary/Services/FileSystemScreenshotsDataAccess.cs +++ b/SightKeeper.Data/Binary/Services/FileSystemScreenshotsDataAccess.cs @@ -1,8 +1,10 @@ -using System.IO.Compression; +using System.Collections.Immutable; +using System.IO.Compression; using System.Runtime.InteropServices; using CommunityToolkit.HighPerformance; using FlakeId; using SightKeeper.Application.Screenshotting; +using SightKeeper.Domain.Model; using SightKeeper.Domain.Model.DataSets.Screenshots; using SixLabors.ImageSharp.PixelFormats; @@ -12,28 +14,53 @@ public sealed class FileSystemScreenshotsDataAccess : ScreenshotsDataAccess { public string DirectoryPath { - get => _dataAccess.DirectoryPath; - set => _dataAccess.DirectoryPath = value; + get => _screenshotsDataAccess.DirectoryPath; + set => _screenshotsDataAccess.DirectoryPath = value; + } + + public FileSystemScreenshotsDataAccess(AppDataAccess appDataAccess, object locker) + { + _appDataAccess = appDataAccess; + _locker = locker; } public override Stream LoadImage(Screenshot screenshot) { - return new ZLibStream(_dataAccess.OpenReadStream(screenshot), CompressionMode.Decompress); + return new ZLibStream(_screenshotsDataAccess.OpenReadStream(screenshot), CompressionMode.Decompress); } public Id GetId(Screenshot screenshot) { - return _dataAccess.GetId(screenshot); + return _screenshotsDataAccess.GetId(screenshot); } internal void AssociateId(Screenshot screenshot, Id id) { - _dataAccess.AssociateId(screenshot, id); + _screenshotsDataAccess.AssociateId(screenshot, id); + } + + internal void DeleteAllScreenshotsData(ScreenshotsLibrary library) + { + foreach (var screenshot in library.Screenshots) + DeleteScreenshotData(screenshot); + } + + protected override Screenshot CreateScreenshotInLibrary( + ScreenshotsLibrary library, + DateTimeOffset creationDate, + Vector2 resolution, + out ImmutableArray removedScreenshots) + { + Screenshot screenshot; + lock (_locker) + screenshot = base.CreateScreenshotInLibrary(library, creationDate, resolution, out removedScreenshots); + _appDataAccess.SetDataChanged(); + return screenshot; } protected override void SaveScreenshotData(Screenshot screenshot, ReadOnlySpan2D imageData) { - using var stream = new ZLibStream(_dataAccess.OpenWriteStream(screenshot), CompressionLevel.SmallestSize); + using var stream = new ZLibStream(_screenshotsDataAccess.OpenWriteStream(screenshot), CompressionLevel.SmallestSize); if (imageData.TryGetSpan(out var contiguousData)) { var bytes = MemoryMarshal.AsBytes(contiguousData); @@ -48,10 +75,19 @@ protected override void SaveScreenshotData(Screenshot screenshot, ReadOnlySpan2D } } + protected override void DeleteScreenshotFromLibrary(Screenshot screenshot) + { + lock (_locker) + base.DeleteScreenshotFromLibrary(screenshot); + _appDataAccess.SetDataChanged(); + } + protected override void DeleteScreenshotData(Screenshot screenshot) { - _dataAccess.Delete(screenshot); + _screenshotsDataAccess.Delete(screenshot); } - private readonly FileSystemDataAccess _dataAccess = new(".bin"); + private readonly AppDataAccess _appDataAccess; + private readonly object _locker; + private readonly FileSystemDataAccess _screenshotsDataAccess = new(".bin"); } \ No newline at end of file diff --git a/SightKeeper.Data/Binary/Services/FileSystemWeightsDataAccess.cs b/SightKeeper.Data/Binary/Services/FileSystemWeightsDataAccess.cs index b373ec5a..9d712fee 100644 --- a/SightKeeper.Data/Binary/Services/FileSystemWeightsDataAccess.cs +++ b/SightKeeper.Data/Binary/Services/FileSystemWeightsDataAccess.cs @@ -1,5 +1,7 @@ using FlakeId; using SightKeeper.Application; +using SightKeeper.Domain.Model; +using SightKeeper.Domain.Model.DataSets.Screenshots; using SightKeeper.Domain.Model.DataSets.Weights; namespace SightKeeper.Data.Binary.Services; @@ -8,34 +10,71 @@ public sealed class FileSystemWeightsDataAccess: WeightsDataAccess { public string DirectoryPath { - get => _dataAccess.DirectoryPath; - set => _dataAccess.DirectoryPath = value; + get => _weightsDataAccess.DirectoryPath; + set => _weightsDataAccess.DirectoryPath = value; + } + + public FileSystemWeightsDataAccess(AppDataAccess appDataAccess, object locker) + { + _appDataAccess = appDataAccess; + _locker = locker; } public override byte[] LoadWeightsData(Weights weights) { - return _dataAccess.ReadAllBytes(weights); + return _weightsDataAccess.ReadAllBytes(weights); } public Id GetId(Weights weights) { - return _dataAccess.GetId(weights); + return _weightsDataAccess.GetId(weights); } public void AssociateId(Weights weights, Id id) { - _dataAccess.AssociateId(weights, id); + _weightsDataAccess.AssociateId(weights, id); + } + + protected override PlainWeights CreateWeightsInLibrary(WeightsLibrary library, DateTime creationDate, ModelSize modelSize, + WeightsMetrics metrics, Vector2 resolution, IEnumerable tags, Composition? composition) + { + lock (_locker) + return base.CreateWeightsInLibrary(library, creationDate, modelSize, metrics, resolution, tags, composition); + } + + protected override PoserWeights CreateWeightsInLibrary(WeightsLibrary library, DateTime creationDate, + ModelSize modelSize, WeightsMetrics metrics, Vector2 resolution, IEnumerable tags, IEnumerable keyPointTags, + Composition? composition) + { + lock (_locker) + return base.CreateWeightsInLibrary(library, creationDate, modelSize, metrics, resolution, tags, keyPointTags, composition); } protected override void SaveWeightsData(Weights weights, byte[] data) { - _dataAccess.WriteAllBytes(weights, data); + _weightsDataAccess.WriteAllBytes(weights, data); + } + + protected override void DeleteWeightsFromLibrary(PlainWeights weights) + { + lock (_locker) + base.DeleteWeightsFromLibrary(weights); + _appDataAccess.SetDataChanged(); + } + + protected override void DeleteWeightsFromLibrary(PoserWeights weights) + { + lock (_locker) + base.DeleteWeightsFromLibrary(weights); + _appDataAccess.SetDataChanged(); } protected override void RemoveWeightsData(Weights weights) { - _dataAccess.Delete(weights); + _weightsDataAccess.Delete(weights); } - private readonly FileSystemDataAccess _dataAccess = new(".pt"); + private readonly AppDataAccess _appDataAccess; + private readonly object _locker; + private readonly FileSystemDataAccess _weightsDataAccess = new(".pt"); } \ No newline at end of file diff --git a/SightKeeper.Data/Binary/Services/GamesDataAccess.cs b/SightKeeper.Data/Binary/Services/GamesDataAccess.cs index 32cd279b..e7450881 100644 --- a/SightKeeper.Data/Binary/Services/GamesDataAccess.cs +++ b/SightKeeper.Data/Binary/Services/GamesDataAccess.cs @@ -10,24 +10,34 @@ public sealed class GamesDataAccess : Application.Games.GamesDataAccess public IObservable GameRemoved => _gameRemoved; public IReadOnlyCollection Games => _appDataAccess.Data.Games; - public GamesDataAccess(AppDataAccess appDataAccess) + public GamesDataAccess(AppDataAccess appDataAccess, object locker) { _appDataAccess = appDataAccess; + _locker = locker; } public void AddGame(Game game) { - Guard.IsTrue(_appDataAccess.Data.Games.Add(game)); + bool isAdded; + lock (_locker) + isAdded = _appDataAccess.Data.Games.Add(game); + Guard.IsTrue(isAdded); + _appDataAccess.SetDataChanged(); _gameAdded.OnNext(game); } public void RemoveGame(Game game) { - Guard.IsTrue(_appDataAccess.Data.Games.Remove(game)); + bool isRemoved; + lock (_locker) + isRemoved = _appDataAccess.Data.Games.Remove(game); + Guard.IsTrue(isRemoved); + _appDataAccess.SetDataChanged(); _gameRemoved.OnNext(game); } private readonly AppDataAccess _appDataAccess; + private readonly object _locker; private readonly Subject _gameAdded = new(); private readonly Subject _gameRemoved = new(); } \ No newline at end of file diff --git a/SightKeeper.Data/Binary/Services/PeriodicAppDataSaver.cs b/SightKeeper.Data/Binary/Services/PeriodicAppDataSaver.cs new file mode 100644 index 00000000..9d9be942 --- /dev/null +++ b/SightKeeper.Data/Binary/Services/PeriodicAppDataSaver.cs @@ -0,0 +1,31 @@ +namespace SightKeeper.Data.Binary.Services; + +public sealed class PeriodicAppDataSaver : IDisposable +{ + public TimeSpan Period + { + get => _timer.Period; + set => _timer.Period = value; + } + + public PeriodicAppDataSaver(AppDataAccess appDataAccess) + { + _appDataAccess = appDataAccess; + new TaskFactory().StartNew(SavePeriodically, TaskCreationOptions.LongRunning); + } + + public void Dispose() + { + _timer.Dispose(); + } + + private readonly AppDataAccess _appDataAccess; + private readonly PeriodicTimer _timer = new(TimeSpan.FromSeconds(1)); + + private async Task SavePeriodically() + { + while (await _timer.WaitForNextTickAsync()) + _appDataAccess.Save(); + _appDataAccess.Save(); + } +} \ No newline at end of file