From 6e067a28d8527726a2a17045bef0f0e3d3430ed5 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 23 Apr 2021 17:28:18 +0800 Subject: refactor: Refactor a lot. --- .../DatabaseManagement/DatabaseBackupService.cs | 37 ++++++++++++ .../DatabaseManagement/DatabaseCustomMigrator.cs | 56 +++++++++++++++++ .../DatabaseManagementService.cs | 34 +++++++++++ .../DatabaseManagement/IDatabaseCustomMigration.cs | 12 ++++ .../MigationServiceCollectionExtensions.cs | 16 +++++ .../TimelinePostContentToDataMigration.cs | 70 ++++++++++++++++++++++ 6 files changed, 225 insertions(+) create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/DatabaseBackupService.cs create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/DatabaseCustomMigrator.cs create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/DatabaseManagementService.cs create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/IDatabaseCustomMigration.cs create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/MigationServiceCollectionExtensions.cs create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/TimelinePostContentToDataMigration.cs (limited to 'BackEnd/Timeline/Services/DatabaseManagement') diff --git a/BackEnd/Timeline/Services/DatabaseManagement/DatabaseBackupService.cs b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseBackupService.cs new file mode 100644 index 00000000..c00b5f95 --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseBackupService.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Timeline.Entities; + +namespace Timeline.Services.DatabaseManagement +{ + public interface IDatabaseBackupService + { + Task BackupAsync(CancellationToken cancellationToken = default); + } + + public class DatabaseBackupService : IDatabaseBackupService + { + private readonly DatabaseContext _database; + private readonly IPathProvider _pathProvider; + private readonly IClock _clock; + + public DatabaseBackupService(DatabaseContext database, IPathProvider pathProvider, IClock clock) + { + _database = database; + _pathProvider = pathProvider; + _clock = clock; + } + + public async Task BackupAsync(CancellationToken cancellationToken = default) + { + var backupDirPath = _pathProvider.GetDatabaseBackupDirectory(); + Directory.CreateDirectory(backupDirPath); + var fileName = _clock.GetCurrentTime().ToString("yyyy-MM-ddTHH-mm-ss", CultureInfo.InvariantCulture); + var path = Path.Combine(backupDirPath, fileName); + await _database.Database.ExecuteSqlInterpolatedAsync($"VACUUM INTO {path}", cancellationToken); + } + } +} diff --git a/BackEnd/Timeline/Services/DatabaseManagement/DatabaseCustomMigrator.cs b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseCustomMigrator.cs new file mode 100644 index 00000000..2180ad40 --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseCustomMigrator.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Timeline.Entities; + +namespace Timeline.Services.DatabaseManagement +{ + public interface IDatabaseCustomMigrator + { + Task MigrateAsync(CancellationToken cancellationToken = default); + } + + public class DatabaseCustomMigrator : IDatabaseCustomMigrator + { + private IEnumerable _migrations; + private DatabaseContext _database; + + private ILogger _logger; + + public DatabaseCustomMigrator(IEnumerable migrations, DatabaseContext database, ILogger logger) + { + _migrations = migrations; + _database = database; + _logger = logger; + } + + public async Task MigrateAsync(CancellationToken cancellationToken = default) + { + foreach (var migration in _migrations) + { + var name = migration.GetName(); + var isApplied = await _database.Migrations.AnyAsync(m => m.Name == name, cancellationToken); + + _logger.LogInformation("Found custom migration '{0}'. Applied: {1}.", name, isApplied); + + if (!isApplied) + { + _logger.LogWarning("Begin custom migration '{0}'.", name); + + await using var transaction = await _database.Database.BeginTransactionAsync(cancellationToken); + + await migration.ExecuteAsync(_database, cancellationToken); + + _database.Migrations.Add(new MigrationEntity { Name = name }); + await _database.SaveChangesAsync(cancellationToken); + + await transaction.CommitAsync(cancellationToken); + + _logger.LogWarning("End custom migration '{0}'.", name); + } + } + } + } +} diff --git a/BackEnd/Timeline/Services/DatabaseManagement/DatabaseManagementService.cs b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseManagementService.cs new file mode 100644 index 00000000..4d54d3dc --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseManagementService.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; +using System.Threading; +using System.Threading.Tasks; +using Timeline.Entities; + +namespace Timeline.Services.DatabaseManagement +{ + public class DatabaseManagementService : IHostedService + { + private readonly DatabaseContext _database; + private readonly IDatabaseBackupService _backupService; + private readonly IDatabaseCustomMigrator _customMigrator; + + public DatabaseManagementService(DatabaseContext database, IDatabaseBackupService backupService, IDatabaseCustomMigrator customMigrator) + { + _database = database; + _backupService = backupService; + _customMigrator = customMigrator; + } + + public async Task StartAsync(CancellationToken cancellationToken = default) + { + await _backupService.BackupAsync(cancellationToken); + await _database.Database.MigrateAsync(cancellationToken); + await _customMigrator.MigrateAsync(cancellationToken); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/BackEnd/Timeline/Services/DatabaseManagement/IDatabaseCustomMigration.cs b/BackEnd/Timeline/Services/DatabaseManagement/IDatabaseCustomMigration.cs new file mode 100644 index 00000000..7300ccbd --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/IDatabaseCustomMigration.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; +using Timeline.Entities; + +namespace Timeline.Services.DatabaseManagement +{ + public interface IDatabaseCustomMigration + { + string GetName(); + Task ExecuteAsync(DatabaseContext database, CancellationToken cancellationToken = default); + } +} diff --git a/BackEnd/Timeline/Services/DatabaseManagement/MigationServiceCollectionExtensions.cs b/BackEnd/Timeline/Services/DatabaseManagement/MigationServiceCollectionExtensions.cs new file mode 100644 index 00000000..d1f9b51c --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/MigationServiceCollectionExtensions.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Timeline.Services.DatabaseManagement +{ + public static class DatabaseManagementServiceCollectionExtensions + { + public static IServiceCollection AddDatabaseManagementService(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddHostedService(); + return services; + } + } +} \ No newline at end of file diff --git a/BackEnd/Timeline/Services/DatabaseManagement/TimelinePostContentToDataMigration.cs b/BackEnd/Timeline/Services/DatabaseManagement/TimelinePostContentToDataMigration.cs new file mode 100644 index 00000000..605223f3 --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/TimelinePostContentToDataMigration.cs @@ -0,0 +1,70 @@ +using Microsoft.EntityFrameworkCore; +using SixLabors.ImageSharp; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Models; + +namespace Timeline.Services.DatabaseManagement +{ + public class TimelinePostContentToDataMigration : IDatabaseCustomMigration + { + private readonly IDataManager _dataManager; + + public TimelinePostContentToDataMigration(IDataManager dataManager) + { + _dataManager = dataManager; + } + + public string GetName() => "TimelinePostContentToData"; + + public async Task ExecuteAsync(DatabaseContext database, CancellationToken cancellationToken) + { +#pragma warning disable CS0618 + var postEntities = await database.TimelinePosts.ToListAsync(cancellationToken); + + foreach (var postEntity in postEntities) + { + if (postEntity.Content is null) + { + postEntity.Deleted = true; + } + else + { + if (postEntity.ContentType == "text") + { + var tag = await _dataManager.RetainEntry(Encoding.UTF8.GetBytes(postEntity.Content)); + database.TimelinePostData.Add(new TimelinePostDataEntity + { + DataTag = tag, + Kind = MimeTypes.TextPlain, + Index = 0, + PostId = postEntity.Id, + LastUpdated = postEntity.LastUpdated + }); + } + else + { + var data = await _dataManager.GetEntryAndCheck(postEntity.Content, "Old image content does not have corresponding data with the tag."); + var format = Image.DetectFormat(data); + database.TimelinePostData.Add(new TimelinePostDataEntity + { + DataTag = postEntity.Content, + Kind = format.DefaultMimeType, + Index = 0, + PostId = postEntity.Id, + LastUpdated = postEntity.LastUpdated + }); + } + } + postEntity.Content = null; + postEntity.ContentType = null; + postEntity.ExtraContent = null; + } + + await database.SaveChangesAsync(cancellationToken); +#pragma warning restore CS0618 + } + } +} -- cgit v1.2.3