diff options
author | crupest <crupest@outlook.com> | 2021-04-23 17:28:18 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-04-23 17:37:37 +0800 |
commit | 6e067a28d8527726a2a17045bef0f0e3d3430ed5 (patch) | |
tree | 967ed4c84981d6b626a3370c726d35d1c3e2c2b4 /BackEnd/Timeline/Services/DatabaseManagement | |
parent | 6acef36dc717834605eda2af9e1738dac8fa2f6d (diff) | |
download | timeline-6e067a28d8527726a2a17045bef0f0e3d3430ed5.tar.gz timeline-6e067a28d8527726a2a17045bef0f0e3d3430ed5.tar.bz2 timeline-6e067a28d8527726a2a17045bef0f0e3d3430ed5.zip |
refactor: Refactor a lot.
Diffstat (limited to 'BackEnd/Timeline/Services/DatabaseManagement')
6 files changed, 225 insertions, 0 deletions
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<IDatabaseCustomMigration> _migrations;
+ private DatabaseContext _database;
+
+ private ILogger<DatabaseCustomMigrator> _logger;
+
+ public DatabaseCustomMigrator(IEnumerable<IDatabaseCustomMigration> migrations, DatabaseContext database, ILogger<DatabaseCustomMigrator> 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<IDatabaseCustomMigrator, DatabaseCustomMigrator>();
+ services.AddScoped<IDatabaseCustomMigration, TimelinePostContentToDataMigration>();
+ services.AddScoped<IDatabaseBackupService, DatabaseBackupService>();
+ services.AddHostedService<DatabaseManagementService>();
+ 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
+ }
+ }
+}
|