diff --git a/Core/Models/TagDatabase/TagDatabase.ExternalTransaction.cs b/Core/Models/TagDatabase/TagDatabase.ExternalTransaction.cs
new file mode 100644
index 0000000..321f96c
--- /dev/null
+++ b/Core/Models/TagDatabase/TagDatabase.ExternalTransaction.cs
@@ -0,0 +1,54 @@
+using Microsoft.Data.Sqlite;
+
+namespace TagHierarchyManager.Models;
+
+public partial class TagDatabase
+{
+ ///
+ /// An object representing an SQLite transaction started outside of the confines of the TagDatabase object (e.g. in the
+ /// Tag Hierarchy Manager UI).
+ ///
+ public class ExternalTransaction(TagDatabase db, SqliteTransaction transaction) : IAsyncDisposable
+ {
+ internal readonly SqliteTransaction Transaction = transaction;
+
+ private readonly List _newlyAddedTags = [];
+ private readonly List _updatedTags = [];
+
+ public async Task CommitAsync()
+ {
+ await this.Transaction.CommitAsync();
+
+ foreach (var tag in this._newlyAddedTags) db.Tags.Add(tag);
+
+ foreach (var tag in this._updatedTags)
+ {
+ var index = db.Tags.FindIndex(t => t.Id == tag.Id);
+ if (index != -1)
+ db.Tags[index] = tag;
+ }
+
+ db.TagsWritten?.Invoke(db, new DatabaseEditResult(this._newlyAddedTags, this._updatedTags, []));
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ await this.Transaction.DisposeAsync();
+ }
+
+ public async Task RollbackAsync()
+ {
+ await this.Transaction.RollbackAsync();
+ }
+
+ internal void AccumulateAdditions(Tag tag)
+ {
+ this._newlyAddedTags.Add(tag);
+ }
+
+ internal void AccumulateUpdates(Tag tag)
+ {
+ this._updatedTags.Add(tag);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs b/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs
index 2591134..326b4a9 100644
--- a/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs
+++ b/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs
@@ -6,106 +6,127 @@ namespace TagHierarchyManager.Models;
public partial class TagDatabase
{
///
- /// Saves all tag objects to the database.
+ /// Saves a Tag object to the database.
///
- /// The List of Tag objects.
+ /// The List of Tag objects.
/// The SqliteTransaction to execute queries on, will make its own if null.
/// Thrown if the tag already existed in the database.
- public async Task WriteTagsToDatabase(List tags, SqliteTransaction? transaction = null)
+ public async Task WriteTagToDatabase(Tag tag, SqliteTransaction? transaction = null)
{
this.CheckInitialisation();
- bool isTransactionOwner = transaction == null;
+ var isTransactionOwner = transaction == null;
transaction ??= (SqliteTransaction)await this.currentConnection.BeginTransactionAsync().ConfigureAwait(false);
- List oldTags = [];
- List<(Tag tag, int id)> originalIds = [];
- List updatedTags = [];
- List newlyAddedTags = [];
+ var oldTag = new Tag
+ {
+ Id = tag.Id,
+ Name = tag.Name,
+ ParentIds = tag.ParentIds,
+ TagBindings = tag.TagBindings,
+ Aliases = tag.Aliases,
+ Notes = tag.Notes,
+ IsTopLevel = tag.IsTopLevel
+ };
+ var originalId = 0;
- List<(int index, Tag tag)> pendingUpdates = [];
- List pendingAdditions = [];
-
try
{
- foreach (var tag in tags)
- {
- if (tag.Id != 0) oldTags.Add(new Tag
- {
- Id = tag.Id,
- Name = tag.Name,
- ParentIds = tag.ParentIds,
- TagBindings = tag.TagBindings,
- Aliases = tag.Aliases,
- Notes = tag.Notes,
- IsTopLevel = tag.IsTopLevel,
- });
- originalIds.Add((tag, tag.Id));
-
- SqliteCommand addCommand = this.currentConnection.CreateCommand();
- addCommand.Transaction = transaction;
- QueryProcessorHandler.ProcessTagSaveCommand(addCommand, tag);
-
- tag.Id = Convert.ToInt32(await addCommand.ExecuteScalarAsync().ConfigureAwait(false),
- CultureInfo.InvariantCulture);
-
- await SaveTagParentIds(transaction, tag);
-
- int index = this.Tags.FindIndex(t => t.Id == tag.Id);
- if (index != -1)
- {
- pendingUpdates.Add((index, tag));
- updatedTags.Add(tag);
- }
- else
- {
- pendingAdditions.Add(tag);
- newlyAddedTags.Add(tag);
- }
-
- tag.CreatedAt ??= DateTime.Now;
- tag.UpdatedAt = DateTime.Now;
-
-
- }
-
+ originalId = tag.Id;
+
+ var addCommand = this.currentConnection.CreateCommand();
+ addCommand.Transaction = transaction;
+ QueryProcessorHandler.ProcessTagSaveCommand(addCommand, tag);
+
+ tag.Id = Convert.ToInt32(await addCommand.ExecuteScalarAsync().ConfigureAwait(false),
+ CultureInfo.InvariantCulture);
+ await this.SaveTagParentIds(transaction, tag);
+
+ var index = this.Tags.FindIndex(t => t.Id == tag.Id);
+
+ tag.CreatedAt ??= DateTime.Now;
+ tag.UpdatedAt = DateTime.Now;
+
if (isTransactionOwner) await transaction.CommitAsync();
-
- foreach (var (index, tag) in pendingUpdates)
+ if (originalId != 0)
+ {
this.Tags[index] = tag;
- foreach (var tag in pendingAdditions)
+ this.TagsWritten?.Invoke(this, new DatabaseEditResult([], [tag], []));
+ }
+ else
+ {
this.Tags.Add(tag);
-
- TagsWritten?.Invoke(this, new DatabaseEditResult(newlyAddedTags, updatedTags, []));
+ this.TagsWritten?.Invoke(this, new DatabaseEditResult([tag], [], []));
+ }
}
catch (Exception)
{
await transaction.RollbackAsync().ConfigureAwait(false);
- foreach (var tag in oldTags)
+ if (oldTag.Id != 0)
{
- int index = this.Tags.FindIndex(t => t.Id == tag.Id);
-
- if (index != -1) this.Tags[index] = tag;
-
+ var index = this.Tags.FindIndex(t => t.Id == tag.Id);
+ this.Tags[index] = oldTag;
}
-
- foreach (var (tag, id) in originalIds)
- tag.Id = id;
-
+
throw;
}
finally
{
- if (isTransactionOwner) await transaction.DisposeAsync().ConfigureAwait(false);
+ await transaction.DisposeAsync().ConfigureAwait(false);
}
}
+ public async Task WriteTagToDatabase(Tag tag, ExternalTransaction transaction)
+ {
+ this.CheckInitialisation();
+
+ var oldTag = new Tag
+ {
+ Id = tag.Id,
+ Name = tag.Name,
+ ParentIds = tag.ParentIds,
+ TagBindings = tag.TagBindings,
+ Aliases = tag.Aliases,
+ Notes = tag.Notes,
+ IsTopLevel = tag.IsTopLevel
+ };
+
+ try
+ {
+ var addCommand = this.currentConnection.CreateCommand();
+ addCommand.Transaction = transaction.Transaction;
+ QueryProcessorHandler.ProcessTagSaveCommand(addCommand, tag);
+
+ tag.Id = Convert.ToInt32(await addCommand.ExecuteScalarAsync().ConfigureAwait(false),
+ CultureInfo.InvariantCulture);
+ await this.SaveTagParentIds(transaction.Transaction, tag);
+
+ tag.CreatedAt ??= DateTime.Now;
+ tag.UpdatedAt = DateTime.Now;
+
+ if (oldTag.Id != 0)
+ transaction.AccumulateUpdates(tag);
+ else transaction.AccumulateAdditions(tag);
+ }
+ catch (Exception)
+ {
+ if (oldTag.Id != 0)
+ {
+ var index = this.Tags.FindIndex(t => t.Id == tag.Id);
+ this.Tags[index] = oldTag;
+ }
+
+ throw;
+ }
+ }
+
+
private async Task SaveTagParentIds(SqliteTransaction transaction, Tag tag)
{
if (tag.ParentIds.Count == 0) return;
this.CheckInitialisation();
-
+
// clear existing tag parents so we have a clean slate.
- SqliteCommand deleteCommand = this.currentConnection.CreateCommand();
+ var deleteCommand = this.currentConnection.CreateCommand();
deleteCommand.Transaction = transaction;
deleteCommand.CommandText = """
DELETE FROM tag_parent_link
@@ -115,7 +136,7 @@ public partial class TagDatabase
await deleteCommand.ExecuteNonQueryAsync().ConfigureAwait(false);
// process + add parent links
- SqliteCommand parentCommand = this.currentConnection.CreateCommand();
+ var parentCommand = this.currentConnection.CreateCommand();
parentCommand.Transaction = transaction;
parentCommand.CommandText = """
INSERT INTO tag_parent_link (target_tag_id, parent_tag_id)
@@ -126,13 +147,13 @@ public partial class TagDatabase
parentCommand.Parameters.Add("@parent_tag_id", SqliteType.Integer);
await parentCommand.PrepareAsync();
- foreach (int parentId in tag.ParentIds)
+ foreach (var parentId in tag.ParentIds)
{
parentCommand.Parameters["@parent_tag_id"].Value = (long)parentId;
await parentCommand.ExecuteNonQueryAsync().ConfigureAwait(false);
}
}
-
+
private async Task SaveTagParents(SqliteTransaction transaction, int id, IReadOnlyCollection parents,
Tag? tag = null)
{
@@ -142,17 +163,17 @@ public partial class TagDatabase
List parentIds = [];
// process parents, grabbing the names first in case the user wants to change the parents.
- foreach (string parentName in parents)
+ foreach (var parentName in parents)
{
- Tag? retrievedTag = this.Tags.SingleOrDefault(t => t.Name == parentName)
- ?? await this.SelectTagFromDatabase(parentName).ConfigureAwait(false);
+ var retrievedTag = this.Tags.SingleOrDefault(t => t.Name == parentName)
+ ?? await this.SelectTagFromDatabase(parentName).ConfigureAwait(false);
if (retrievedTag is null) throw new ArgumentException(ErrorMessages.TagNotFound);
parentIds.Add(retrievedTag.Id);
}
// clear existing tag parents so we have a clean slate.
- SqliteCommand deleteCommand = this.currentConnection.CreateCommand();
+ var deleteCommand = this.currentConnection.CreateCommand();
deleteCommand.Transaction = transaction;
deleteCommand.CommandText = """
DELETE FROM tag_parent_link
@@ -162,7 +183,7 @@ public partial class TagDatabase
await deleteCommand.ExecuteNonQueryAsync().ConfigureAwait(false);
// process + add parent links
- SqliteCommand parentCommand = this.currentConnection.CreateCommand();
+ var parentCommand = this.currentConnection.CreateCommand();
parentCommand.Transaction = transaction;
parentCommand.CommandText = """
INSERT INTO tag_parent_link (target_tag_id, parent_tag_id)
@@ -173,7 +194,7 @@ public partial class TagDatabase
parentCommand.Parameters.Add("@parent_tag_id", SqliteType.Integer);
await parentCommand.PrepareAsync();
- foreach (int parentId in parentIds)
+ foreach (var parentId in parentIds)
{
parentCommand.Parameters["@parent_tag_id"].Value = (long)parentId;
await parentCommand.ExecuteNonQueryAsync().ConfigureAwait(false);
diff --git a/Core/Models/TagDatabase/TagDatabase.cs b/Core/Models/TagDatabase/TagDatabase.cs
index 72c440e..fb17d3e 100644
--- a/Core/Models/TagDatabase/TagDatabase.cs
+++ b/Core/Models/TagDatabase/TagDatabase.cs
@@ -10,15 +10,15 @@ namespace TagHierarchyManager.Models;
public partial class TagDatabase
{
private const string AliasesColumnName = "also_known_as";
+ private const string DateCreatedColumnName = "date_created";
+ private const string DateModifiedColumnName = "date_modified";
private const string IdColumnName = "id";
+ private const int LatestVersion = 3;
private const string NameColumnName = "name";
private const string NotesColumnName = "notes";
private const string ParentIdsColumnName = "parent_ids";
private const string TagBindingsColumnName = "tags_to_bind";
private const string TopLevelColumnName = "top_level";
- private const string DateCreatedColumnName = "date_created";
- private const string DateModifiedColumnName = "date_modified";
- private const int LatestVersion = 3;
private SqliteConnection? currentConnection;
private List defaultBindings = ["genre"];
@@ -78,7 +78,7 @@ public partial class TagDatabase
/// Gets version of the database. Cannot be set outside of initialisation.
///
public int Version { get; private set; }
-
+
///
/// Gets the SQLite connection associated with the .
///
@@ -94,9 +94,10 @@ public partial class TagDatabase
///
private bool Initialised { get; set; }
- public async Task BeginTransactionAsync()
+ public async Task BeginExternalTransactionAsync()
{
this.CheckInitialisation();
- return (SqliteTransaction)await this.currentConnection.BeginTransactionAsync();
+ var transaction = (SqliteTransaction)await this.currentConnection.BeginTransactionAsync();
+ return new ExternalTransaction(this, transaction);
}
}
\ No newline at end of file
diff --git a/Tests/TagDatabaseSaveTests.cs b/Tests/TagDatabaseSaveTests.cs
index f5f2a20..00022be 100644
--- a/Tests/TagDatabaseSaveTests.cs
+++ b/Tests/TagDatabaseSaveTests.cs
@@ -26,7 +26,7 @@ public class TagDatabaseWriteTests : TestBase
public async Task ClearDatabaseAndAddSampleData()
{
this.Database.ClearTags();
- await this.Database.WriteTagsToDatabase([TestSampleTags.Ambient, TestSampleTags.Electronic]);
+ await this.Database.WriteTagToDatabase([TestSampleTags.Ambient, TestSampleTags.Electronic]);
}
///
@@ -42,7 +42,7 @@ public class TagDatabaseWriteTests : TestBase
Name = "DELETE ME",
IsTopLevel = true,
};
- await this.Database.WriteTagsToDatabase([deletedTag]);
+ await this.Database.WriteTagToDatabase([deletedTag]);
// Act
await this.Database.DeleteTag(deletedTag.Id);
@@ -93,7 +93,7 @@ public class TagDatabaseWriteTests : TestBase
TagBindings = ["genre"],
Parents = ["Test parent tag 1"],
};
- await this.Database.WriteTagsToDatabase([firstParentTag, secondParentTag, childTag]);
+ await this.Database.WriteTagToDatabase([firstParentTag, secondParentTag, childTag]);
int childTagId = childTag.Id;
List expectedParents = [firstParentTag.Id, secondParentTag.Id];
@@ -112,7 +112,7 @@ public class TagDatabaseWriteTests : TestBase
childTag.Parents.Add(addedParentName);
childTag.Notes = newNotes;
childTag.Aliases = newAliases;
- await this.Database.WriteTagsToDatabase([childTag]);
+ await this.Database.WriteTagToDatabase([childTag]);
// Assert
Tag? editedChildTag = await this.Database.SelectTagFromDatabase(childTagId);
@@ -140,7 +140,7 @@ public class TagDatabaseWriteTests : TestBase
// Act/Assert
ArgumentException? ex =
- Assert.ThrowsAsync(async () => await this.Database.WriteTagsToDatabase([ambient]));
+ Assert.ThrowsAsync(async () => await this.Database.WriteTagToDatabase([ambient]));
Assert.That(ex!.Message, Does.EndWith("already exists in the database."));
}
@@ -157,7 +157,7 @@ public class TagDatabaseWriteTests : TestBase
bool tagValidated = inputTag.Validate();
// Act
- await this.Database.WriteTagsToDatabase([inputTag]);
+ await this.Database.WriteTagToDatabase([inputTag]);
Tag? savedTag = await this.Database.SelectTagFromDatabase(inputTag.Name);
// Assert
@@ -192,7 +192,7 @@ public class TagDatabaseWriteTests : TestBase
this.Database.ClearTags();
// Act
- await this.Database.WriteTagsToDatabase(testTags);
+ await this.Database.WriteTagToDatabase(testTags);
// Assert
var retrievedTags = await this.Database.GetAllTagsFromDatabase();
@@ -215,7 +215,7 @@ public class TagDatabaseWriteTests : TestBase
db.ClearTags();
// Act/Assert
- await db.WriteTagsToDatabase(testTags);
+ await db.WriteTagToDatabase(testTags);
Assert.That(test.Added.Count, Is.EqualTo(testTags.Count));
Assert.That(test.Updated.Count, Is.EqualTo(0));
Assert.That(test.Deleted.Count, Is.EqualTo(0));
@@ -223,7 +223,7 @@ public class TagDatabaseWriteTests : TestBase
Tag? retrievedTag = await db.SelectTagFromDatabase(testTags[0].Name);
Assert.That(retrievedTag, Is.Not.Null);
retrievedTag.Notes = "test edit";
- await db.WriteTagsToDatabase([retrievedTag]);
+ await db.WriteTagToDatabase([retrievedTag]);
Assert.That(test.Updated.Count, Is.EqualTo(1));
Tag? deletedTag = await db.SelectTagFromDatabase(testTags[2].Name);
diff --git a/Tests/TagDatabaseSearchTests.cs b/Tests/TagDatabaseSearchTests.cs
index 8f7d72f..cc7f1ec 100644
--- a/Tests/TagDatabaseSearchTests.cs
+++ b/Tests/TagDatabaseSearchTests.cs
@@ -124,7 +124,7 @@ public class TagDatabaseSearchTests : TestBase
Parents = ["Ambient", "Electronic"],
Aliases = ["áéíóúçýỷủ"],
};
- await this.Database.WriteTagsToDatabase([normalisedTest]);
+ await this.Database.WriteTagToDatabase([normalisedTest]);
// Act
List tags = this.Database.SearchWithAliases(query, mode);
diff --git a/Tests/TestBase.cs b/Tests/TestBase.cs
index 01f3109..a8630c3 100644
--- a/Tests/TestBase.cs
+++ b/Tests/TestBase.cs
@@ -63,7 +63,7 @@ public abstract class TestBase
List sampleTags = TestSampleTags.AllTags();
this.Database.ClearTags();
- await this.Database.WriteTagsToDatabase(sampleTags);
+ await this.Database.WriteTagToDatabase(sampleTags);
}
// do not use in Test1_Init. That needs to be tested at a lower level.
diff --git a/UI/TagDatabaseService.cs b/UI/TagDatabaseService.cs
index dc40219..fcfc67e 100644
--- a/UI/TagDatabaseService.cs
+++ b/UI/TagDatabaseService.cs
@@ -94,36 +94,36 @@ public class TagDatabaseService : ObservableObject
: [];
}
+ public List GetAllTags(bool topLevelOnly = false)
+ {
+ if (this.Database is null) return [];
+
+ return topLevelOnly
+ ? this.Database.Tags.Where(t => t.IsTopLevel).OrderBy(t => t.Name).ToList()
+ : this.Database.Tags.OrderBy(t => t.Name).ToList();
+ }
+
public Dictionary> GetChildLookup()
{
- if (this.Database is null) return new();
+ if (this.Database is null) return new Dictionary>();
var lookup = new Dictionary>();
foreach (var tag in this.Database.Tags)
+ foreach (var parentId in tag.ParentIds)
{
- foreach (var parentId in tag.ParentIds)
+ if (!lookup.TryGetValue(parentId, out var children))
{
- if (!lookup.TryGetValue(parentId, out var children))
- {
- children = [];
- lookup[parentId] = children;
- }
- children.Add(tag);
+ children = [];
+ lookup[parentId] = children;
}
+
+ children.Add(tag);
}
+
foreach (var children in lookup.Values)
children.Sort((a, b) => a.Name.CompareTo(b.Name, StringComparison.CurrentCultureIgnoreCase));
return lookup;
}
- public List GetAllTags(bool topLevelOnly = false)
- {
- if (this.Database is null) return [];
-
- return topLevelOnly
- ? this.Database.Tags.Where(t => t.IsTopLevel).OrderBy(t => t.Name).ToList()
- : this.Database.Tags.OrderBy(t => t.Name).ToList();
- }
-
public List GetParentNamesByIds(List ids)
{
return ids.Select(id => this.Database?.Tags.FirstOrDefault(t => t.Id == id))
@@ -179,38 +179,6 @@ public class TagDatabaseService : ObservableObject
this.NotifyDatabasePropertiesChanged();
}
- private List SortTagsTopologically(List tags)
- {
- var sorted = new List();
- var checkedNames = new HashSet();
- var checking = new HashSet();
-
- void CheckParents(Tag tag)
- {
- if (checking.Contains(tag))
- throw new InvalidOperationException($"Circular parent depedency detected involving {tag.Name}");
-
- if (!checkedNames.Add(tag)) return;
-
- checking.Add(tag);
-
- foreach (var parentName in tag.Parents)
- {
- var parents = tags.Where(t => t.Name == parentName).ToList();
- if (parents.Count == 1)
- CheckParents(parents[0]);
- }
-
- checking.Remove(tag);
- sorted.Add(tag);
- }
-
- foreach (var tag in tags)
- CheckParents(tag);
-
- return sorted;
- }
-
public async Task WriteTagsToDatabase(List tags)
{
if (this.Database is null) return;
@@ -229,40 +197,40 @@ public class TagDatabaseService : ObservableObject
.Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(),
Notes = vm.EditingNotes,
Parents = vm.EditingParents
- .Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(),
+ .Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList()
};
-
+
if (tags.Count == 1)
{
var success = await this.GetSavingTagParents(tag);
if (!success) return;
tag.Validate();
}
-
+
tagsToSave.Add((vm, tag));
}
-
+
if (tags.Count > 1)
{
var sortedPairs = this.SortTagsTopologically(tagsToSave.Select(x => x.tag).ToList())
.Select(t => tagsToSave.First(x => x.tag == t))
.ToList();
-
- var transaction = await this.Database.BeginTransactionAsync();
+
+ var transaction = await this.Database.BeginExternalTransactionAsync();
try
{
- foreach (var (vm, tag) in sortedPairs)
+ foreach (var (_, tag) in sortedPairs)
{
var success = await this.GetSavingTagParents(tag, tagsToSave.Select(x => x.tag).ToList());
if (!success)
{
- transaction.Rollback();
+ transaction.RollbackAsync();
return;
}
-
+
tag.Validate();
- await this.Database.WriteTagsToDatabase([tag], transaction: transaction);
+ await this.Database.WriteTagToDatabase(tag, transaction);
}
await transaction.CommitAsync();
@@ -272,12 +240,29 @@ public class TagDatabaseService : ObservableObject
await transaction.RollbackAsync();
throw;
}
- finally { await transaction.DisposeAsync(); }
+ finally
+ {
+ await transaction.DisposeAsync();
+ }
+ }
+ else
+ {
+ await this.Database.WriteTagToDatabase(tagsToSave[0].tag);
}
- else await this.Database.WriteTagsToDatabase(tagsToSave.Select(x => x.tag).ToList());
+
tagsToSave.ForEach(pair => pair.vm.CommitEdit(pair.tag));
}
+ private static IExporter PickExporterFromFileExt(string path)
+ {
+ var fileExt = Path.GetExtension(path);
+
+ // ReSharper disable once ConvertIfStatementToReturnStatement
+ if (fileExt == FileTypes.MusicBeeTagHierarchyTemplate.FileExtension) return new MusicBeeTagHierarchyExporter();
+
+ throw new NotSupportedException($"File extension '{fileExt}' is not supported.");
+ }
+
private async Task GetSavingTagParents(Tag tag, List? batch = null)
{
if (this.Database is null) return false;
@@ -299,28 +284,20 @@ public class TagDatabaseService : ObservableObject
var ambiguousVm = new SaveAmbiguousViewModel(this, tag, parentTags);
var dialog = new SaveAmbiguousDialog();
- var dialogOwner = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)
+ var dialogOwner =
+ (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)
?.Windows
.FirstOrDefault(w => w.IsActive);
dialog.DataContext = ambiguousVm;
var result = await dialog.ShowDialog(dialogOwner!);
-
+
if (result == null) return false;
tag.ParentIds.Add(result.Id);
break;
}
}
- return true;
- }
-
- private static IExporter PickExporterFromFileExt(string path)
- {
- var fileExt = Path.GetExtension(path);
-
- // ReSharper disable once ConvertIfStatementToReturnStatement
- if (fileExt == FileTypes.MusicBeeTagHierarchyTemplate.FileExtension) return new MusicBeeTagHierarchyExporter();
- throw new NotSupportedException($"File extension '{fileExt}' is not supported.");
+ return true;
}
private void NotifyDatabasePropertiesChanged()
@@ -349,6 +326,38 @@ public class TagDatabaseService : ObservableObject
throw new NotSupportedException(string.Format(Resources.ErrorImportFileTypeNotSupported, fileExt));
}
+ private List SortTagsTopologically(List tags)
+ {
+ var sorted = new List();
+ var checkedNames = new HashSet();
+ var checking = new HashSet();
+
+ void CheckParents(Tag tag)
+ {
+ if (checking.Contains(tag))
+ throw new InvalidOperationException($"Circular parent depedency detected involving {tag.Name}");
+
+ if (!checkedNames.Add(tag)) return;
+
+ checking.Add(tag);
+
+ foreach (var parentName in tag.Parents)
+ {
+ var parents = tags.Where(t => t.Name == parentName).ToList();
+ if (parents.Count == 1)
+ CheckParents(parents[0]);
+ }
+
+ checking.Remove(tag);
+ sorted.Add(tag);
+ }
+
+ foreach (var tag in tags)
+ CheckParents(tag);
+
+ return sorted;
+ }
+
private void SubscribeToEvents()
{
if (this.Database is null) return;