From bab566a2b93e6ffd39b1a498a783a730defac365 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 7 Jun 2021 22:20:44 +0200 Subject: [PATCH] Storage - Keep up to 5 backups of the database Layouts - Prevent LEDs from going outside the layout bounds --- src/Artemis.ConsoleUI/Program.cs | 3 + src/Artemis.Core/Constants.cs | 7 +-- .../Models/Surface/Layout/ArtemisLayout.cs | 20 ++++++ src/Artemis.Core/Ninject/CoreModule.cs | 19 +----- src/Artemis.Storage/StorageManager.cs | 63 +++++++++++++++++++ src/Artemis.UI/Bootstrapper.cs | 9 +++ 6 files changed, 97 insertions(+), 24 deletions(-) create mode 100644 src/Artemis.Storage/StorageManager.cs diff --git a/src/Artemis.ConsoleUI/Program.cs b/src/Artemis.ConsoleUI/Program.cs index 0526b6fce..6d694d8d6 100644 --- a/src/Artemis.ConsoleUI/Program.cs +++ b/src/Artemis.ConsoleUI/Program.cs @@ -4,6 +4,7 @@ using System.Threading; using Artemis.Core; using Artemis.Core.Ninject; using Artemis.Core.Services; +using Artemis.Storage; using Ninject; namespace Artemis.UI.Console @@ -28,6 +29,8 @@ namespace Artemis.UI.Console private static void Main(string[] args) { + StorageManager.CreateBackup(Constants.DataFolder); + Utilities.PrepareFirstLaunch(); Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; StandardKernel kernel = new() {Settings = {InjectNonPublic = true}}; diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index 6f8f1c27d..e978e8598 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -28,12 +28,7 @@ namespace Artemis.Core /// The full path to the Artemis data folder /// public static readonly string DataFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\Artemis\\"; - - /// - /// The connection string used to connect to the database - /// - public static readonly string ConnectionString = $"FileName={DataFolder}\\database.db"; - + /// /// The plugin info used by core components of Artemis /// diff --git a/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs b/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs index 2df270e5b..e2f51547d 100644 --- a/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs +++ b/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs @@ -102,6 +102,26 @@ namespace Artemis.Core foreach (LedId led in ledsToRemove) device.RemoveLed(led); } + + List deviceLeds = device.ToList(); + foreach (Led led in deviceLeds) + { + float x = led.Location.X; + float y = led.Location.Y; + + // Try to move the LED if it falls outside the boundaries of the layout + if (led.Location.X + led.Size.Width > device.Size.Width) + x -= led.Location.X + led.Size.Width - device.Size.Width; + + if (led.Location.Y + led.Size.Height > device.Size.Height) + y -= led.Location.Y + led.Size.Height - device.Size.Height; + + // If not possible because it's too large we'll have to drop it to avoid rendering issues + if (x < 0 || y < 0) + device.RemoveLed(led.Id); + else + led.Location = new Point(x, y); + } } internal void ApplyDevice(ArtemisDevice artemisDevice) diff --git a/src/Artemis.Core/Ninject/CoreModule.cs b/src/Artemis.Core/Ninject/CoreModule.cs index b3dee7af1..66a882bf7 100644 --- a/src/Artemis.Core/Ninject/CoreModule.cs +++ b/src/Artemis.Core/Ninject/CoreModule.cs @@ -50,24 +50,7 @@ namespace Artemis.Core.Ninject .Configure(c => c.When(HasAccessToProtectedService).InSingletonScope()); }); - Kernel.Bind().ToMethod(t => - { - try - { - return new LiteRepository(Constants.ConnectionString); - } - catch (LiteException e) - { - // I don't like this way of error reporting, now I need to use reflection if I want a meaningful error code - if (e.ErrorCode != LiteException.INVALID_DATABASE) - throw new ArtemisCoreException($"LiteDB threw error code {e.ErrorCode}. See inner exception for more details", e); - - // If the DB is invalid it's probably LiteDB v4 (TODO: we'll have to do something better later) - File.Delete($"{Constants.DataFolder}\\database.db"); - return new LiteRepository(Constants.ConnectionString); - } - }).InSingletonScope(); - + Kernel.Bind().ToMethod(_ => StorageManager.CreateRepository(Constants.DataFolder)).InSingletonScope(); Kernel.Bind().ToSelf().InSingletonScope(); // Bind all migrations as singletons diff --git a/src/Artemis.Storage/StorageManager.cs b/src/Artemis.Storage/StorageManager.cs new file mode 100644 index 000000000..267f86fea --- /dev/null +++ b/src/Artemis.Storage/StorageManager.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Linq; +using LiteDB; + +namespace Artemis.Storage +{ + public static class StorageManager + { + private static bool _inUse; + + /// + /// Creates a backup of the database if the last backup is older than 10 minutes + /// Removes the oldest backup if there are more than 5 backups present + /// + /// The Artemis data folder + public static void CreateBackup(string dataFolder) + { + if (_inUse) + throw new Exception("Storage is already in use, can't backup now."); + + string database = $"{dataFolder}\\database.db"; + if (!File.Exists(database)) + return; + + string backupFolder = $"{dataFolder}\\database backups"; + Directory.CreateDirectory(backupFolder); + FileSystemInfo[] files = new DirectoryInfo(backupFolder).GetFileSystemInfos(); + if (files.Length >= 5) + { + FileSystemInfo newest = files.OrderByDescending(fi => fi.CreationTime).First(); + FileSystemInfo oldest = files.OrderBy(fi => fi.CreationTime).First(); + if (DateTime.Now - newest.CreationTime < TimeSpan.FromMinutes(10)) + return; + + oldest.Delete(); + } + + File.Copy(database, $"{backupFolder}\\database-{DateTime.Now:yyyy-dd-M--HH-mm-ss}.db"); + } + + /// + /// Creates the LiteRepository that will be managed by dependency injection + /// + /// The Artemis data folder + public static LiteRepository CreateRepository(string dataFolder) + { + if (_inUse) + throw new Exception("Storage is already in use, use dependency injection to get the repository."); + + try + { + _inUse = true; + return new LiteRepository($"FileName={dataFolder}\\database.db"); + } + catch (LiteException e) + { + // I don't like this way of error reporting, now I need to use reflection if I want a meaningful error message + throw new Exception($"LiteDB threw error code {e.ErrorCode}. See inner exception for more details", e); + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 6b27e2fd1..d444e16e2 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -6,8 +6,10 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Markup; using System.Windows.Threading; +using Artemis.Core; using Artemis.Core.Ninject; using Artemis.Core.Services; +using Artemis.Storage; using Artemis.UI.Ninject; using Artemis.UI.Screens; using Artemis.UI.Services; @@ -25,6 +27,13 @@ namespace Artemis.UI { private ApplicationStateManager _applicationStateManager; private ICoreService _core; + + public Bootstrapper() + { + // This is done at this kind of odd place to ensure it happens before the database is in use + StorageManager.CreateBackup(Constants.DataFolder); + } + public static List StartupArguments { get; private set; } protected override void OnExit(ExitEventArgs e)