- Added handling for DeepL exceptions
- Added debug logging for im- and exporting
- When executing the post script for the unique id migration, ensure
  that new id are generated, only when no id exists
- Fixed section export by loading references to parents
This commit is contained in:
Thorsten Sommer 2023-02-11 21:22:25 +01:00
parent 59da2c23a8
commit 4c6f5b59f0
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
5 changed files with 98 additions and 25 deletions

View File

@ -1,4 +1,5 @@
using System.Text.Json; using System.Linq.Expressions;
using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -151,6 +152,7 @@ public sealed class DataContext : DbContext, IDataContext
/// <param name="includeSensitiveData">When false, exclude sensitive data from export.</param> /// <param name="includeSensitiveData">When false, exclude sensitive data from export.</param>
public async Task ExportAsync(string path, bool includeSensitiveData = false) public async Task ExportAsync(string path, bool includeSensitiveData = false)
{ {
Console.WriteLine("Exporting database to JSON file...");
var jsonSettings = new JsonSerializerOptions var jsonSettings = new JsonSerializerOptions
{ {
WriteIndented = true, WriteIndented = true,
@ -176,19 +178,32 @@ public sealed class DataContext : DbContext, IDataContext
} }
} }
// Use a local reference to the database to use it in the lambda expression trees below:
// (we cannot use "this" in a lambda expression tree; yields an exception at runtime)
var db = this;
await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
await JsonSerializer.SerializeAsync(fileStream, await JsonSerializer.SerializeAsync(fileStream,
new JsonData new JsonData
{ {
// Settings don't have references to other entities; we can just use them here:
Settings = includeSensitiveData ? Settings = includeSensitiveData ?
// Include all settings, including sensitive data: // Include all settings, including sensitive data:
this.Settings.OrderBy(n => n.UniqueId).Select(n => n.ToJsonSetting()).ToList() : this.Settings.OrderBy(n => n.UniqueId).Select(n => n.ToJsonSetting()).ToList() :
// Exclude sensitive data: // Exclude sensitive data:
FilterSensitiveSettings(this.Settings.OrderBy(n => n.UniqueId).Select(n => n.ToJsonSetting()).AsEnumerable()).ToList(), FilterSensitiveSettings(this.Settings.OrderBy(n => n.UniqueId).Select(n => n.ToJsonSetting()).AsEnumerable()).ToList(),
Sections = this.Sections.OrderBy(n => n.UniqueId).Select(n => n.ToJsonSection()).ToList(),
// Warning: the parents cannot pre-loaded, thus, we must load them now inside the lambda expression tree:
Sections = this.Sections.OrderBy(n => n.UniqueId).Select(n => n.ToJsonSection(db)).ToList(),
// All text elements references are pre-loaded, so we can use them here:
TextElements = this.TextElements.OrderBy(n => n.UniqueId).Select(n => n.ToJsonTextElement()).ToList(), TextElements = this.TextElements.OrderBy(n => n.UniqueId).Select(n => n.ToJsonTextElement()).ToList(),
// All translation references are pre-loaded, so we can use them here:
Translations = this.Translations.OrderBy(n => n.UniqueId).Select(n => n.ToJsonTranslation()).ToList(), Translations = this.Translations.OrderBy(n => n.UniqueId).Select(n => n.ToJsonTranslation()).ToList(),
}, jsonSettings); }, jsonSettings);
Console.WriteLine("Export complete.");
} }
/// <summary> /// <summary>
@ -212,6 +227,8 @@ public sealed class DataContext : DbContext, IDataContext
await this.Translations.AnyAsync()) await this.Translations.AnyAsync())
throw new InvalidOperationException("The database is not empty. In order to import data, the database must be empty."); throw new InvalidOperationException("The database is not empty. In order to import data, the database must be empty.");
Console.WriteLine("Start importing data from JSON file...");
// Start a transaction: // Start a transaction:
await using var transaction = await this.Database.BeginTransactionAsync(); await using var transaction = await this.Database.BeginTransactionAsync();
@ -245,6 +262,8 @@ public sealed class DataContext : DbContext, IDataContext
// Convert the next element: // Convert the next element:
var nextSection = Section.FromJsonSection(section); var nextSection = Section.FromJsonSection(section);
// Notice: the parent id is not yet resolved.
// Store the element: // Store the element:
allSections.Add(nextSection.UniqueId, new (section.ParentUniqueId.UniqueId, nextSection)); allSections.Add(nextSection.UniqueId, new (section.ParentUniqueId.UniqueId, nextSection));
sectionToTextElements.Add(nextSection.UniqueId, section.TextElements.Select(n => n.UniqueId).ToList()); sectionToTextElements.Add(nextSection.UniqueId, section.TextElements.Select(n => n.UniqueId).ToList());
@ -252,7 +271,27 @@ public sealed class DataContext : DbContext, IDataContext
// Now, resolve the parent-child relationships for the sections: // Now, resolve the parent-child relationships for the sections:
foreach (var (uniqueId, (parentId, section)) in allSections) foreach (var (uniqueId, (parentId, section)) in allSections)
section.Parent = parentId == Guid.Empty ? null : allSections[parentId].Entity; {
if(parentId == Guid.Empty)
{
if(section.Depth != 0)
Console.WriteLine(@$"Section {uniqueId} ""{section.Name}"" has no parent.");
else
Console.WriteLine(@$"Section {uniqueId} ""{section.Name}"" is a root section, thus, has no parent.");
section.Parent = null;
continue;
}
if(allSections.TryGetValue(parentId, out var parent))
section.Parent = parent.Entity;
else
{
Console.WriteLine(@$"Parent of section {uniqueId} ""{section.Name}"" was not found.");
section.Parent = null;
continue;
}
}
// ------------------------- // -------------------------
// Import the text elements: // Import the text elements:
@ -330,7 +369,15 @@ public sealed class DataContext : DbContext, IDataContext
// Commit the transaction: // Commit the transaction:
await transaction.CommitAsync(); await transaction.CommitAsync();
Console.WriteLine("Finished importing data from JSON file.");
} }
#endregion #endregion
#region Tools
internal Task LoadElementsAsync<TEntry, TProp>(TEntry entry, Expression<Func<TEntry, TProp?>> selector) where TEntry : class where TProp : class => this.Entry<TEntry>(entry).Reference(selector).LoadAsync();
#endregion
} }

View File

@ -22,7 +22,13 @@ public sealed class Section
internal DataContext.JsonUniqueId JsonUniqueId => new(this.DataKey, this.UniqueId, "Sec"); internal DataContext.JsonUniqueId JsonUniqueId => new(this.DataKey, this.UniqueId, "Sec");
internal DataContext.JsonSection ToJsonSection() => new() internal DataContext.JsonSection ToJsonSection(DataContext db)
{
db.LoadElementsAsync(this, n => n.Parent).GetAwaiter().GetResult();
if(this.Depth > 0 && this.Parent == null)
Console.WriteLine($"Found section with depth > 0 and parent is null ==> {this.JsonUniqueId}");
return new()
{ {
UniqueId = this.JsonUniqueId, UniqueId = this.JsonUniqueId,
Name = this.Name, Name = this.Name,
@ -31,6 +37,7 @@ public sealed class Section
ParentUniqueId = this.Parent?.JsonUniqueId ?? new DataContext.JsonUniqueId("null", Guid.Empty, "Sec"), ParentUniqueId = this.Parent?.JsonUniqueId ?? new DataContext.JsonUniqueId("null", Guid.Empty, "Sec"),
TextElements = this.TextElements.Select(n => n.JsonUniqueId).ToList() TextElements = this.TextElements.Select(n => n.JsonUniqueId).ToList()
}; };
}
internal static Section FromJsonSection(DataContext.JsonSection jsonSection) => new() internal static Section FromJsonSection(DataContext.JsonSection jsonSection) => new()
{ {

View File

@ -7,16 +7,28 @@ public static class Script202211AddUniqueIds
public static async Task PostMigrationAsync(DataContext db) public static async Task PostMigrationAsync(DataContext db)
{ {
await foreach (var setting in db.Settings) await foreach (var setting in db.Settings)
{
if(setting.UniqueId == Guid.Empty)
setting.UniqueId = Guid.NewGuid(); setting.UniqueId = Guid.NewGuid();
}
await foreach(var section in db.Sections) await foreach(var section in db.Sections)
{
if(section.UniqueId == Guid.Empty)
section.UniqueId = Guid.NewGuid(); section.UniqueId = Guid.NewGuid();
}
await foreach (var textElement in db.TextElements) await foreach (var textElement in db.TextElements)
{
if(textElement.UniqueId == Guid.Empty)
textElement.UniqueId = Guid.NewGuid(); textElement.UniqueId = Guid.NewGuid();
}
await foreach (var translation in db.Translations) await foreach (var translation in db.Translations)
{
if(translation.UniqueId == Guid.Empty)
translation.UniqueId = Guid.NewGuid(); translation.UniqueId = Guid.NewGuid();
}
await db.SaveChangesAsync(); await db.SaveChangesAsync();
} }

View File

@ -63,8 +63,15 @@ public static class DeepL
return translation.Text; return translation.Text;
} }
catch (AuthorizationException) catch (AuthorizationException e)
{ {
Console.WriteLine($"DeepL authorization failed: {e.Message}");
DEEPL_NOT_AVAILABLE = true;
return string.Empty;
}
catch (DeepLException e)
{
Console.WriteLine($"DeepL issue: {e.Message}");
DEEPL_NOT_AVAILABLE = true; DEEPL_NOT_AVAILABLE = true;
return string.Empty; return string.Empty;
} }

View File

@ -2,5 +2,5 @@
public static class Version public static class Version
{ {
public static string Text => $"v0.8.1 (2023-01-24), .NET {Environment.Version}"; public static string Text => $"v0.8.2 (2023-02-11), .NET {Environment.Version}";
} }