Db: Backups
This commit is contained in:
parent
e0d4a5d676
commit
802e0c4cde
@ -15,6 +15,7 @@ namespace Pal.Client.Configuration
|
|||||||
public DeepDungeonConfiguration DeepDungeons { get; set; } = new();
|
public DeepDungeonConfiguration DeepDungeons { get; set; } = new();
|
||||||
public RendererConfiguration Renderer { get; set; } = new();
|
public RendererConfiguration Renderer { get; set; } = new();
|
||||||
public List<AccountConfigurationV7> Accounts { get; set; } = new();
|
public List<AccountConfigurationV7> Accounts { get; set; } = new();
|
||||||
|
public BackupConfiguration Backups { get; set; } = new();
|
||||||
|
|
||||||
public IAccountConfiguration CreateAccount(string server, Guid accountId)
|
public IAccountConfiguration CreateAccount(string server, Guid accountId)
|
||||||
{
|
{
|
||||||
|
@ -22,6 +22,7 @@ namespace Pal.Client.Configuration
|
|||||||
|
|
||||||
DeepDungeonConfiguration DeepDungeons { get; set; }
|
DeepDungeonConfiguration DeepDungeons { get; set; }
|
||||||
RendererConfiguration Renderer { get; set; }
|
RendererConfiguration Renderer { get; set; }
|
||||||
|
BackupConfiguration Backups { get; set; }
|
||||||
|
|
||||||
IAccountConfiguration CreateAccount(string server, Guid accountId);
|
IAccountConfiguration CreateAccount(string server, Guid accountId);
|
||||||
IAccountConfiguration? FindAccount(string server);
|
IAccountConfiguration? FindAccount(string server);
|
||||||
@ -92,4 +93,10 @@ namespace Pal.Client.Configuration
|
|||||||
|
|
||||||
bool EncryptIfNeeded();
|
bool EncryptIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class BackupConfiguration
|
||||||
|
{
|
||||||
|
public int MinimumBackupsToKeep { get; set; } = 3;
|
||||||
|
public int DaysToDeleteAfter { get; set; } = 21;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
using System;
|
using System.Globalization;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
@ -11,7 +9,6 @@ using Dalamud.Game.ClientState.Objects;
|
|||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Logging;
|
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -23,7 +20,6 @@ using Pal.Client.Configuration.Legacy;
|
|||||||
using Pal.Client.Database;
|
using Pal.Client.Database;
|
||||||
using Pal.Client.DependencyInjection;
|
using Pal.Client.DependencyInjection;
|
||||||
using Pal.Client.DependencyInjection.Logging;
|
using Pal.Client.DependencyInjection.Logging;
|
||||||
using Pal.Client.Extensions;
|
|
||||||
using Pal.Client.Floors;
|
using Pal.Client.Floors;
|
||||||
using Pal.Client.Net;
|
using Pal.Client.Net;
|
||||||
using Pal.Client.Properties;
|
using Pal.Client.Properties;
|
||||||
@ -129,7 +125,7 @@ namespace Pal.Client
|
|||||||
services.AddScoped<StatisticsService>();
|
services.AddScoped<StatisticsService>();
|
||||||
services.AddScoped<StatisticsWindow>();
|
services.AddScoped<StatisticsWindow>();
|
||||||
|
|
||||||
// these should maybe be scoped
|
// rendering
|
||||||
services.AddScoped<SimpleRenderer>();
|
services.AddScoped<SimpleRenderer>();
|
||||||
services.AddScoped<SplatoonRenderer>();
|
services.AddScoped<SplatoonRenderer>();
|
||||||
services.AddScoped<RenderAdapter>();
|
services.AddScoped<RenderAdapter>();
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -44,16 +50,11 @@ namespace Pal.Client
|
|||||||
_logger.LogInformation("Starting async init");
|
_logger.LogInformation("Starting async init");
|
||||||
chat = _serviceProvider.GetService<Chat>();
|
chat = _serviceProvider.GetService<Chat>();
|
||||||
|
|
||||||
// initialize database
|
await RemoveOldBackups();
|
||||||
await using (var scope = _serviceProvider.CreateAsyncScope())
|
await CreateBackups();
|
||||||
{
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
_logger.LogInformation("Loading database & running migrations");
|
|
||||||
await using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
|
||||||
|
|
||||||
// takes 2-3 seconds with initializing connections, loading driver etc.
|
await RunMigrations(cancellationToken);
|
||||||
await dbContext.Database.MigrateAsync(cancellationToken);
|
|
||||||
_logger.LogInformation("Completed database migrations");
|
|
||||||
}
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
@ -91,12 +92,100 @@ namespace Pal.Client
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, "Async load failed");
|
_logger.LogError(e, "Async load failed");
|
||||||
InitCompleted?.Invoke(() => chat?.Error(string.Format(Localization.Error_LoadFailed, $"{e.GetType()} - {e.Message}")));
|
InitCompleted?.Invoke(() =>
|
||||||
|
chat?.Error(string.Format(Localization.Error_LoadFailed, $"{e.GetType()} - {e.Message}")));
|
||||||
|
|
||||||
LoadState = ELoadState.Error;
|
LoadState = ELoadState.Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task RemoveOldBackups()
|
||||||
|
{
|
||||||
|
await using var scope = _serviceProvider.CreateAsyncScope();
|
||||||
|
var pluginInterface = scope.ServiceProvider.GetRequiredService<DalamudPluginInterface>();
|
||||||
|
var configuration = scope.ServiceProvider.GetRequiredService<IPalacePalConfiguration>();
|
||||||
|
|
||||||
|
var paths = Directory.GetFiles(pluginInterface.GetPluginConfigDirectory(), "backup-*.data.sqlite3",
|
||||||
|
new EnumerationOptions
|
||||||
|
{
|
||||||
|
IgnoreInaccessible = true,
|
||||||
|
RecurseSubdirectories = false,
|
||||||
|
MatchCasing = MatchCasing.CaseSensitive,
|
||||||
|
AttributesToSkip = FileAttributes.ReadOnly | FileAttributes.Hidden | FileAttributes.System,
|
||||||
|
ReturnSpecialDirectories = false,
|
||||||
|
});
|
||||||
|
if (paths.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Regex backupRegex = new Regex(@"backup-([\d\-]{10})\.data\.sqlite3", RegexOptions.Compiled);
|
||||||
|
List<(DateTime Date, string Path)> backupFiles = new();
|
||||||
|
foreach (string path in paths)
|
||||||
|
{
|
||||||
|
var match = backupRegex.Match(Path.GetFileName(path));
|
||||||
|
if (!match.Success)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (DateTime.TryParseExact(match.Groups[1].Value, "yyyy-MM-dd", CultureInfo.InvariantCulture,
|
||||||
|
DateTimeStyles.AssumeUniversal, out DateTime backupDate))
|
||||||
|
{
|
||||||
|
backupFiles.Add((backupDate, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var toDelete = backupFiles.OrderByDescending(x => x.Date)
|
||||||
|
.Skip(configuration.Backups.MinimumBackupsToKeep)
|
||||||
|
.Where(x => (DateTime.Today.ToUniversalTime() - x.Date).Days > configuration.Backups.DaysToDeleteAfter)
|
||||||
|
.Select(x => x.Path);
|
||||||
|
foreach (var path in toDelete)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
_logger.LogInformation("Deleted old backup file '{Path}'", path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(e, "Could not delete backup file '{Path}'", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CreateBackups()
|
||||||
|
{
|
||||||
|
await using var scope = _serviceProvider.CreateAsyncScope();
|
||||||
|
|
||||||
|
var pluginInterface = scope.ServiceProvider.GetRequiredService<DalamudPluginInterface>();
|
||||||
|
string backupPath = Path.Join(pluginInterface.GetPluginConfigDirectory(),
|
||||||
|
$"backup-{DateTime.Today.ToUniversalTime():yyyy-MM-dd}.data.sqlite3");
|
||||||
|
if (!File.Exists(backupPath))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Creating database backup '{Path}'", backupPath);
|
||||||
|
|
||||||
|
await using var db = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
||||||
|
await using SqliteConnection source = new(db.Database.GetConnectionString());
|
||||||
|
await source.OpenAsync();
|
||||||
|
await using SqliteConnection backup = new($"Data Source={backupPath}");
|
||||||
|
source.BackupDatabase(backup);
|
||||||
|
SqliteConnection.ClearPool(backup);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_logger.LogInformation("Database backup in '{Path}' already exists", backupPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunMigrations(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// initialize database
|
||||||
|
await using (var scope = _serviceProvider.CreateAsyncScope())
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Loading database & running migrations");
|
||||||
|
await using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
||||||
|
|
||||||
|
// takes 2-3 seconds with initializing connections, loading driver etc.
|
||||||
|
await dbContext.Database.MigrateAsync(cancellationToken);
|
||||||
|
_logger.LogInformation("Completed database migrations");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum ELoadState
|
public enum ELoadState
|
||||||
{
|
{
|
||||||
Initializing,
|
Initializing,
|
||||||
|
Loading…
Reference in New Issue
Block a user