diff --git a/I18N Commander/DataModel/Database/Common/DataContext.cs b/I18N Commander/DataModel/Database/Common/DataContext.cs index 3cfa6dd..0f46a50 100644 --- a/I18N Commander/DataModel/Database/Common/DataContext.cs +++ b/I18N Commander/DataModel/Database/Common/DataContext.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Linq.Expressions; +using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.EntityFrameworkCore; @@ -151,6 +152,7 @@ public sealed class DataContext : DbContext, IDataContext /// When false, exclude sensitive data from export. public async Task ExportAsync(string path, bool includeSensitiveData = false) { + Console.WriteLine("Exporting database to JSON file..."); var jsonSettings = new JsonSerializerOptions { 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 JsonSerializer.SerializeAsync(fileStream, new JsonData { + // Settings don't have references to other entities; we can just use them here: Settings = includeSensitiveData ? // Include all settings, including sensitive data: this.Settings.OrderBy(n => n.UniqueId).Select(n => n.ToJsonSetting()).ToList() : // Exclude sensitive data: 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(), + + // All translation references are pre-loaded, so we can use them here: Translations = this.Translations.OrderBy(n => n.UniqueId).Select(n => n.ToJsonTranslation()).ToList(), }, jsonSettings); + + Console.WriteLine("Export complete."); } /// @@ -211,6 +226,8 @@ public sealed class DataContext : DbContext, IDataContext await this.TextElements.AnyAsync() || await this.Translations.AnyAsync()) 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: await using var transaction = await this.Database.BeginTransactionAsync(); @@ -244,6 +261,8 @@ public sealed class DataContext : DbContext, IDataContext { // Convert the next element: var nextSection = Section.FromJsonSection(section); + + // Notice: the parent id is not yet resolved. // Store the element: allSections.Add(nextSection.UniqueId, new (section.ParentUniqueId.UniqueId, nextSection)); @@ -252,8 +271,28 @@ public sealed class DataContext : DbContext, IDataContext // Now, resolve the parent-child relationships for the sections: 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: // ------------------------- @@ -330,7 +369,15 @@ public sealed class DataContext : DbContext, IDataContext // Commit the transaction: await transaction.CommitAsync(); + + Console.WriteLine("Finished importing data from JSON file."); } #endregion + + #region Tools + + internal Task LoadElementsAsync(TEntry entry, Expression> selector) where TEntry : class where TProp : class => this.Entry(entry).Reference(selector).LoadAsync(); + + #endregion } \ No newline at end of file diff --git a/I18N Commander/DataModel/Database/Section.cs b/I18N Commander/DataModel/Database/Section.cs index 0bd9cb0..a990898 100644 --- a/I18N Commander/DataModel/Database/Section.cs +++ b/I18N Commander/DataModel/Database/Section.cs @@ -22,16 +22,23 @@ public sealed class Section internal DataContext.JsonUniqueId JsonUniqueId => new(this.DataKey, this.UniqueId, "Sec"); - internal DataContext.JsonSection ToJsonSection() => new() + internal DataContext.JsonSection ToJsonSection(DataContext db) { - UniqueId = this.JsonUniqueId, - Name = this.Name, - DataKey = this.DataKey, - Depth = this.Depth, - ParentUniqueId = this.Parent?.JsonUniqueId ?? new DataContext.JsonUniqueId("null", Guid.Empty, "Sec"), - TextElements = this.TextElements.Select(n => n.JsonUniqueId).ToList() - }; - + 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, + Name = this.Name, + DataKey = this.DataKey, + Depth = this.Depth, + ParentUniqueId = this.Parent?.JsonUniqueId ?? new DataContext.JsonUniqueId("null", Guid.Empty, "Sec"), + TextElements = this.TextElements.Select(n => n.JsonUniqueId).ToList() + }; + } + internal static Section FromJsonSection(DataContext.JsonSection jsonSection) => new() { UniqueId = jsonSection.UniqueId.UniqueId, diff --git a/I18N Commander/DataModel/MigrationScripts/202211AddUniqueIds.cs b/I18N Commander/DataModel/MigrationScripts/202211AddUniqueIds.cs index aa85699..76bcc90 100644 --- a/I18N Commander/DataModel/MigrationScripts/202211AddUniqueIds.cs +++ b/I18N Commander/DataModel/MigrationScripts/202211AddUniqueIds.cs @@ -7,16 +7,28 @@ public static class Script202211AddUniqueIds public static async Task PostMigrationAsync(DataContext db) { await foreach (var setting in db.Settings) - setting.UniqueId = Guid.NewGuid(); - + { + if(setting.UniqueId == Guid.Empty) + setting.UniqueId = Guid.NewGuid(); + } + await foreach(var section in db.Sections) - section.UniqueId = Guid.NewGuid(); - + { + if(section.UniqueId == Guid.Empty) + section.UniqueId = Guid.NewGuid(); + } + await foreach (var textElement in db.TextElements) - textElement.UniqueId = Guid.NewGuid(); - + { + if(textElement.UniqueId == Guid.Empty) + textElement.UniqueId = Guid.NewGuid(); + } + await foreach (var translation in db.Translations) - translation.UniqueId = Guid.NewGuid(); + { + if(translation.UniqueId == Guid.Empty) + translation.UniqueId = Guid.NewGuid(); + } await db.SaveChangesAsync(); } diff --git a/I18N Commander/Processor/DeepL.cs b/I18N Commander/Processor/DeepL.cs index af3ffe5..d942e9c 100644 --- a/I18N Commander/Processor/DeepL.cs +++ b/I18N Commander/Processor/DeepL.cs @@ -54,17 +54,24 @@ public static class DeepL { var sourceCultureIndex = await AppSettings.GetDeepLSourceCultureIndex(); var sourceCulture = await AppSettings.GetCultureCode(sourceCultureIndex); - + // In case of the source culture, we cannot specify the region, so we need to remove it: sourceCulture = sourceCulture.Split('-')[0]; - + using var deepl = new Translator(deepLAPIKey); var translation = await deepl.TranslateTextAsync(text, sourceCulture, targetCulture); - + 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; return string.Empty; } diff --git a/I18N Commander/Processor/Version.cs b/I18N Commander/Processor/Version.cs index 592ab98..d6dce1e 100644 --- a/I18N Commander/Processor/Version.cs +++ b/I18N Commander/Processor/Version.cs @@ -2,5 +2,5 @@ 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}"; } \ No newline at end of file