diff --git a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj index 9c753d66..80f095af 100644 --- a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj +++ b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs b/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs index 4c9b9466..ff94e63e 100644 --- a/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs +++ b/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs @@ -23,8 +23,6 @@ internal class DetailView(IPage page) private readonly IPage _page = page; - internal ILocator AudiofileInput => _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).Locator("input[type=\"file\"]"); - internal ILocator CuesheetArtistInput => _page.GetByRole(AriaRole.Textbox, new() { Name = "Cuesheet artist" }); internal ILocator CuesheetTitleInput => _page.GetByRole(AriaRole.Textbox, new() { Name = "Cuesheet title" }); @@ -39,9 +37,19 @@ internal async Task GotoAsync() await _page.WaitForFunctionAsync(@"() => window.Blazor !== undefined"); } - internal async Task AddTrackAsync() + internal async Task AddAudiofileAsync() + { + await _page.GetByRole(AriaRole.Button, new() { Name = "Add file" }).ClickAsync(); + } + + internal async Task SetAudiofileInputFileAsync(int audiofileIndex, string file) + { + await _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).Nth(audiofileIndex).Locator("input[type=\"file\"]").SetInputFilesAsync(file); + } + + internal async Task AddTrackAsync(int audiofileIndex) { - await _page.GetByRole(AriaRole.Button, new() { Name = "Add new track" }).ClickAsync(); + await _page.GetByRole(AriaRole.Button, new() { Name = "Add new track" }).Nth(audiofileIndex).ClickAsync(); } internal async Task EditTrackAsync(string? artist = null, string? title = null) @@ -50,9 +58,6 @@ internal async Task EditTrackAsync(string? artist = null, string? title = null) { await _page.Locator("td:nth-child(3)").ClickAsync(); await _page.Locator("td:nth-child(3)").Last.GetByRole(AriaRole.Textbox).FillAsync(artist); - // Autocomplete overlay will pop up, so we close it - await _page.Locator(".mud-popover-open").WaitForAsync(new() { State = WaitForSelectorState.Visible }); - await _page.Keyboard.PressAsync("Escape"); // Click outside the autocomplete to have an focus lost event for getting the value written to model await _page.GetByRole(AriaRole.Heading, new() { Name = "Playback" }).ClickAsync(new() { Force = true }); await _page.WaitForTimeoutAsync(100); @@ -61,9 +66,6 @@ internal async Task EditTrackAsync(string? artist = null, string? title = null) { await _page.Locator("td:nth-child(4)").ClickAsync(); await _page.Locator("td:nth-child(4)").Last.GetByRole(AriaRole.Textbox).FillAsync(title); - // Autocomplete overlay will pop up, so we close it - await _page.Locator(".mud-popover-open").WaitForAsync(new() { State = WaitForSelectorState.Visible }); - await _page.Keyboard.PressAsync("Escape"); // Click outside the autocomplete to have an focus lost event for getting the value written to model await _page.GetByRole(AriaRole.Heading, new() { Name = "Playback" }).ClickAsync(new() { Force = true }); await _page.WaitForTimeoutAsync(100); @@ -105,16 +107,16 @@ internal async Task EditTracksModalAsync(string artist, string title, string end await _page.GetByRole(AriaRole.Button, new() { Name = "Save changes" }).ClickAsync(); } - internal async Task RenameAudiofileAsync(string filename) + internal async Task RenameAudiofileAsync(int audiofileIndex, string filename) { - await OpenRenameAudiofileDialogAsync(); + await OpenRenameAudiofileDialogAsync(audiofileIndex); await NewFileNameInput.FillAsync(filename); await _page.GetByRole(AriaRole.Button, new() { Name = "Ok" }).ClickAsync(); } - internal async Task OpenRenameAudiofileDialogAsync() + internal async Task OpenRenameAudiofileDialogAsync(int audiofileIndex) { - await _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).GetByLabel("More").ClickAsync(); + await _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).Nth(audiofileIndex).GetByLabel("More").ClickAsync(); await _page.GetByText("Rename file").ClickAsync(); } } diff --git a/AudioCuesheetEditor.End2EndTests/Models/ImportView.cs b/AudioCuesheetEditor.End2EndTests/Models/ImportView.cs index be0c6185..90b7286b 100644 --- a/AudioCuesheetEditor.End2EndTests/Models/ImportView.cs +++ b/AudioCuesheetEditor.End2EndTests/Models/ImportView.cs @@ -23,6 +23,9 @@ partial class ImportView(IPage page, bool mobile) [GeneratedRegex("^Scheme common data$")] private static partial Regex SchemeCommonData(); + [GeneratedRegex("^Scheme audiofiles$")] + private static partial Regex SchemeAudiofiles(); + internal const string BaseUrl = "http://localhost:5132/"; private readonly IPage _page = page; @@ -105,6 +108,11 @@ internal async Task ClearSchemeCommonDataAsync() await _page.Locator("div").Filter(new() { HasTextRegex = SchemeCommonData() }).GetByLabel("Clear").ClickAsync(); } + internal async Task ClearSchemeAudiofilesAsync() + { + await _page.Locator("div").Filter(new() { HasTextRegex = SchemeAudiofiles() }).GetByLabel("Clear").ClickAsync(); + } + internal async Task SetSchemeCommonDataAsync(string schemeCommonData) { await _page.GetByRole(AriaRole.Textbox, new() { Name = "Scheme common data" }).FillAsync(schemeCommonData); diff --git a/AudioCuesheetEditor.End2EndTests/Sample_Inputfile.txt b/AudioCuesheetEditor.End2EndTests/Sample_Inputfile.txt index 5f80c195..9d06c8b8 100644 --- a/AudioCuesheetEditor.End2EndTests/Sample_Inputfile.txt +++ b/AudioCuesheetEditor.End2EndTests/Sample_Inputfile.txt @@ -1,4 +1,5 @@ -CuesheetArtist - CuesheetTitle c:\AudioFile.mp3 +CuesheetArtist - CuesheetTitle +- c:\AudioFile.mp3 Sample Artist 1 - Sample Title 1 00:05:00 Sample Artist 2 - Sample Title 2 00:09:23 Sample Artist 3 - Sample Title 3 00:15:54 diff --git a/AudioCuesheetEditor.End2EndTests/Sample_Project.ace b/AudioCuesheetEditor.End2EndTests/Sample_Project.ace index cf2c12df..d192e583 100644 --- a/AudioCuesheetEditor.End2EndTests/Sample_Project.ace +++ b/AudioCuesheetEditor.End2EndTests/Sample_Project.ace @@ -1 +1 @@ -{"Tracks":[{"Position":1,"Artist":"Sample Artist 1","Title":"Sample Title 1","Begin":"00:00:00","End":"00:05:00","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":2,"Artist":"Sample Artist 2","Title":"Sample Title 2","Begin":"00:05:00","End":"00:09:23","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":3,"Artist":"Sample Artist 3","Title":"Sample Title 3","Begin":"00:09:23","End":"00:15:54","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":4,"Artist":"Sample Artist 4","Title":"Sample Title 4","Begin":"00:15:54","End":"00:20:13","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":5,"Artist":"Sample Artist 5","Title":"Sample Title 5","Begin":"00:20:13","End":"00:24:54","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":6,"Artist":"Sample Artist 6","Title":"Sample Title 6","Begin":"00:24:54","End":"00:31:54","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":7,"Artist":"Sample Artist 7","Title":"Sample Title 7","Begin":"00:31:54","End":"00:45:54","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":8,"Artist":"Sample Artist 8","Title":"Sample Title 8","Begin":"00:45:54","Flags":[],"IsLinkedToPreviousTrack":false}],"Artist":"Sample CD Artist","Title":"Sample CD Title","Audiofile":{"Name":"Sample.mp3"},"Sections":[]} \ No newline at end of file +{"Artist":"Sample CD Artist","Title":"Sample CD Title","Audiofiles":[{"Tracks":[{"Position":1,"Artist":"Sample Artist 1","Title":"Sample Title 1","Begin":"00:00:00","End":"00:05:00","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":2,"Artist":"Sample Artist 2","Title":"Sample Title 2","Begin":"00:05:00","End":"00:09:23","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":3,"Artist":"Sample Artist 3","Title":"Sample Title 3","Begin":"00:09:23","End":"00:15:54","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":4,"Artist":"Sample Artist 4","Title":"Sample Title 4","Begin":"00:15:54","End":"00:20:13","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":5,"Artist":"Sample Artist 5","Title":"Sample Title 5","Begin":"00:20:13","End":"00:24:54","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":6,"Artist":"Sample Artist 6","Title":"Sample Title 6","Begin":"00:24:54","End":"00:31:54","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":7,"Artist":"Sample Artist 7","Title":"Sample Title 7","Begin":"00:31:54","End":"00:45:54","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":8,"Artist":"Sample Artist 8","Title":"Sample Title 8","Begin":"00:45:54","End":"01:15:54","Flags":[],"IsLinkedToPreviousTrack":true}]}]} \ No newline at end of file diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs index e0e507d3..68c221c3 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs @@ -45,8 +45,9 @@ public async Task Audiofile_ShouldBeRenamed_WhenEditingFilename() { var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); - await detailView.RenameAudiofileAsync("Kalimba test 123.mp3"); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.RenameAudiofileAsync(0, "Kalimba test 123.mp3"); await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToMatchAriaSnapshotAsync("- textbox \"Audiofile\": Kalimba test 123.mp3"); } @@ -70,12 +71,65 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected() await bar.ChangeLanguageAsync("German (Germany)"); await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Allgemeine Informationen" })).ToBeVisibleAsync(); await Expect(TestPage.GetByText("Aufnahmeansicht")).ToBeVisibleAsync(); - await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Titel" })).ToBeVisibleAsync(); + await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Dateien" })).ToBeVisibleAsync(); await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Wiedergabe" })).ToBeVisibleAsync(); await bar.OpenExportDialogAsync("Textdatei", "Datei"); - await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToMatchAriaSnapshotAsync("- tabpanel:\n - text: \"Export ist derzeit nicht möglich: Titel hat ungültige Anzahl (0)! Künstler hat keinen Wert! Titel hat keinen Wert! Audiodatei hat keinen Wert! YouTube\"\n - group \"Exportprofil auswählen\"\n - text: Exportprofil auswählen\n - group:\n - button \"Neues Exportprofil hinzufügen\"\n - button \"Ausgewähltes Exportprofil löschen\"\n - separator\n - textbox \"Name\": YouTube\n - group \"Name\"\n - text: Name\n - textbox \"Dateiname\": YouTube.txt\n - group \"Dateiname\"\n - text: Dateiname\n - textbox \"Schema Kopf\": \"%Cuesheet.Artist% - %Cuesheet.Title%\"\n - button \"Clear\"\n - button\n - group \"Schema Kopf\"\n - text: Schema Kopf\n - textbox \"Schema Titel\": \"%Track.Artist% - %Track.Title% %Track.Begin%\"\n - button \"Clear\"\n - button\n - group \"Schema Titel\"\n - text: Schema Titel\n - textbox \"Schema Fuß\"\n - button\n - group \"Schema Fuß\"\n - text: Schema Fuß"); + await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToMatchAriaSnapshotAsync(@"- dialog ""Exportprofile Close"": + - heading ""Exportprofile"" [level=6] + - button ""Close"" + - tablist: + - tab ""Export konfigurieren"" [selected]: + - paragraph: Export konfigurieren + - tab ""2 Export herunterladen"" [disabled]: + - text: ""2"" + - paragraph: Export herunterladen + - tabpanel ""Export konfigurieren"": + - text: ""Export ist derzeit nicht möglich: Künstler hat keinen Wert! Titel hat keinen Wert! Audiodateien hat ungültige Anzahl (0)!"" + - combobox ""Exportprofil auswählen"": YouTube + - group ""Exportprofil auswählen"" + - text: Exportprofil auswählen + - group: + - button ""Neues Exportprofil hinzufügen"" + - button ""Ausgewähltes Exportprofil löschen"" + - separator + - textbox ""Name"": + - /placeholder: Geben Sie hier den Namen für dieses Profil ein + - text: YouTube + - group ""Name"" + - text: Name + - textbox ""Dateiname"": + - /placeholder: Geben Sie hier den Dateinamen für dieses Profil ein + - text: YouTube.txt + - group ""Dateiname"" + - text: Dateiname + - textbox ""Schema Kopf"": + - /placeholder: Geben Sie hier das Kopf-Schema für dieses Profil ein + - text: ""%Cuesheet.Artist% - %Cuesheet.Title%"" + - button ""Clear"" + - button + - group ""Schema Kopf"" + - text: Schema Kopf + - textbox ""Schema Audiodateien"": + - /placeholder: Geben Sie hier das Audiodatei-Schema für dieses Profil ein + - button + - group ""Schema Audiodateien"" + - text: Schema Audiodateien + - textbox ""Schema Titel"": + - /placeholder: Geben Sie hier das Titel-Schema für dieses Profil ein + - text: ""%Track.Artist% - %Track.Title% %Track.Begin%"" + - button ""Clear"" + - button + - group ""Schema Titel"" + - text: Schema Titel + - textbox ""Schema Fuß"": + - /placeholder: Geben Sie hier das Fuß-Schema für dieses Profil ein + - button + - group ""Schema Fuß"" + - text: Schema Fuß + - button ""Previous"" [disabled] + - button ""Next"" [disabled]"); await exportDialog.OpenSchemeMenuAsync("Schema Kopf"); - await Expect(TestPage.Locator("#app")).ToMatchAriaSnapshotAsync("- paragraph: Künstler\n- paragraph: Titel\n- paragraph: Audiodatei\n- paragraph: CDTextdatei\n- paragraph: Katalognummer\n- paragraph: Datum\n- paragraph: Datum & Uhrzeit\n- paragraph: Uhrzeit"); + await Expect(TestPage.Locator("#app")).ToMatchAriaSnapshotAsync("- paragraph: Künstler\n- paragraph: Titel\n- paragraph: CDTextdatei\n- paragraph: Katalognummer\n- paragraph: Datum\n- paragraph: Datum & Uhrzeit\n- paragraph: Uhrzeit"); await TestPage.GetByText("CDTextdatei").ClickAsync(); await exportDialog.OpenSchemeMenuAsync("Schema Titel"); await Expect(TestPage.GetByTestId("menu-wrapper")).ToMatchAriaSnapshotAsync("- paragraph: Position\n- paragraph: Künstler\n- paragraph: Titel\n- paragraph: Begin\n- paragraph: End\n- paragraph: Länge\n- paragraph: Markierungen\n- paragraph: Vorlücke\n- paragraph: Nachlücke"); @@ -84,23 +138,23 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected() [TestMethod] public async Task TrackTableControls_ShouldBeEnabled_WhenSelectingFirstTrackAsync() { - var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); - await detailView.AddTrackAsync(); + await detailView.AddAudiofileAsync(); + await detailView.AddTrackAsync(0); + await detailView.AddTrackAsync(0); await detailView.SelectTracksAsync([1]); - await bar.ChangeLanguageAsync("German (Germany)"); await Expect(TestPage.GetByLabel("Track table controls")).ToMatchAriaSnapshotAsync(@"- group: - - button ""Neuen Titel hinzufügen"" - - button ""Ausgewählte Titel bearbeiten"" - - button ""Ausgewählten Titel kopieren"" - - button ""Ausgewählte Titel löschen"" - - button ""Alle Titel löschen"" + - button ""Add new track"" + - button ""Edit selected tracks"" + - button ""Copy selected tracks"" + - button ""Delete selected tracks"" + - button ""Delete all tracks"" +- group: + - button ""Move selected tracks up"" [disabled] + - button ""Move selected tracks down"" - group: - - button ""Ausgewählte Titel nach oben bewegen"" [disabled] - - button ""Ausgewählte Titel nach unten bewegen"" -- button ""Fester Tabellenkopf"""); + - button ""Fixed table header"""); } [TestMethod] @@ -112,26 +166,32 @@ public async Task KeyboardCommands_ShouldControlDialogs_WhenUsingEnterOrEscapeAs await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenExportDialogAsync("Cuesheet"); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); + await TestPage.GetByRole(AriaRole.Dialog).FocusAsync(); await TestPage.Keyboard.PressAsync("Escape"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenExportDialogAsync("Projectfile"); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); + await TestPage.GetByRole(AriaRole.Dialog).FocusAsync(); await TestPage.Keyboard.PressAsync("Escape"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenExportDialogAsync("Textfile"); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); + await TestPage.GetByRole(AriaRole.Dialog).FocusAsync(); await TestPage.Keyboard.PressAsync("Escape"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenSettingsAsync(); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); + await TestPage.GetByRole(AriaRole.Dialog).FocusAsync(); await TestPage.Keyboard.PressAsync("Escape"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenDisplayHotkeysAsync(); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); + await TestPage.GetByRole(AriaRole.Dialog).FocusAsync(); await TestPage.Keyboard.PressAsync("Escape"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); - await detailView.OpenRenameAudiofileDialogAsync(); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.OpenRenameAudiofileDialogAsync(0); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); await detailView.NewFileNameInput.FillAsync("Test 123"); await TestPage.Keyboard.PressAsync("Enter"); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs index 8e856f81..a87c8844 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs @@ -27,10 +27,11 @@ public async Task DownloadCuesheet_GeneratesCuesheetFile_WhenCuesheetIsValid() var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1"); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.AddTrackAsync(0); await detailView.EditTrackAsync("Track Artist 1", "Track Title 1"); await bar.OpenExportDialogAsync("Cuesheet"); var downloadTask = TestPage.WaitForDownloadAsync(); @@ -56,10 +57,11 @@ public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync( var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1"); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.AddTrackAsync(0); await detailView.EditTrackAsync("Track Artist 1", "Track Title 1"); await bar.OpenExportDialogAsync("Projectfile"); var downloadTask = TestPage.WaitForDownloadAsync(); @@ -68,7 +70,7 @@ public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync( using var stream = await download.CreateReadStreamAsync(); using var reader = new StreamReader(stream); var content = await reader.ReadToEndAsync(TestContext.CancellationToken); - Assert.AreEqual("{\"Tracks\":[{\"Position\":1,\"Artist\":\"Track Artist 1\",\"Title\":\"Track Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:05:48.0608330\",\"Flags\":[],\"IsLinkedToPreviousTrack\":true}],\"Artist\":\"Cuesheet Artist 1\",\"Title\":\"Cuesheet Title 1\",\"Audiofile\":{\"Name\":\"Kalimba.mp3\",\"Duration\":\"00:05:48.0608330\",\"AudioCodec\":{\"MimeType\":\"audio/mpeg\",\"FileExtension\":\".mp3\",\"Name\":\"AudioCodec MP3\"}}}", content); + Assert.AreEqual("{\"Artist\":\"Cuesheet Artist 1\",\"Title\":\"Cuesheet Title 1\",\"Audiofiles\":[{\"Name\":\"Kalimba.mp3\",\"Duration\":\"00:05:48.0608330\",\"AudioCodec\":{\"MimeType\":\"audio/mpeg\",\"FileExtension\":\".mp3\",\"Name\":\"AudioCodec MP3\"},\"Tracks\":[{\"Position\":1,\"Artist\":\"Track Artist 1\",\"Title\":\"Track Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:05:48.0608330\",\"Flags\":[],\"IsLinkedToPreviousTrack\":true}]}]}", content); } [TestMethod] @@ -77,10 +79,11 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync() var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1"); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.AddTrackAsync(0); await detailView.EditTrackAsync("Track Artist 1", "Track Title 1"); await bar.OpenExportDialogAsync("Textfile"); await TestPage.GetByRole(AriaRole.Button, new() { Name = "Next", Exact = true }).ClickAsync(); @@ -92,6 +95,7 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync() var content = await reader.ReadToEndAsync(TestContext.CancellationToken); content = content.Replace("\n", Environment.NewLine); Assert.AreEqual(@"Cuesheet Artist 1 - Cuesheet Title 1 + Track Artist 1 - Track Title 1 00:00:00 ", content); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs index e5d9d58e..f306ab23 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs @@ -25,7 +25,6 @@ public class ImportTest : PlaywrightTestBase public async Task Import_ShouldImportTracks_WhenUsingSampleInputfile() { var importView = new ImportView(TestPage, DeviceName != null); - var detailView = new DetailView(TestPage); await importView.GotoAsync(); await importView.ImportFileAsync("Sample_Inputfile.txt"); await importView.Analyze(); @@ -212,7 +211,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - button - rowgroup: - row"); - await Expect(detailView.AudiofileInput).ToBeEmptyAsync(); + await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToHaveValueAsync(@"c:\AudioFile.mp3"); await importView.GotoAsync(); await Expect(TestPage.GetByRole(AriaRole.Button, new() { Name = "Analyze" })).ToBeVisibleAsync(); } @@ -1183,6 +1182,7 @@ public async Task Import_ShouldImportTracks_WhenUsingSampleInputfile2() await importView.GotoAsync(); await importView.ImportFileAsync("Sample_Inputfile2.txt"); await importView.ClearSchemeCommonDataAsync(); + await importView.ClearSchemeAudiofilesAsync(); await importView.Analyze(); await Expect(importView.CuesheetArtistInput).ToBeEmptyAsync(); await Expect(importView.CuesheetTitleInput).ToBeEmptyAsync(); @@ -2407,7 +2407,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - textbox: 00:14:00 - cell: - button - - row ""Select row 8 Sample Artist 8 Clear Sample Title 8 Clear 00:45:54"": + - row ""Select row 8 Sample Artist 8 Clear Sample Title 8 Clear 00:45:54 01:15:54 00:30:00"": - cell ""Select row"": - checkbox ""Select row"" - text: Select row @@ -2422,16 +2422,16 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - button - cell ""00:45:54"": - textbox: 00:45:54 - - cell: - - textbox - - cell: - - textbox + - cell ""01:15:54"": + - textbox: 01:15:54 + - cell ""00:30:00"": + - textbox: 00:30:00 - cell: - button - rowgroup: - row"); await appBar.UndoAsync(); - await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Tracks has invalid Count (0)!" })).ToBeVisibleAsync(); + await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync(); } [TestMethod] @@ -2626,7 +2626,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - rowgroup: - row"); await appBar.UndoAsync(); - await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Tracks has invalid Count (0)!" })).ToBeVisibleAsync(); + await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync(); } [TestMethod] diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs index a6c48845..4bba8143 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs @@ -27,7 +27,8 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenUndoAndRedoAreUsed() var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); + await detailView.AddAudiofileAsync(); + await detailView.AddTrackAsync(0); await detailView.EditTrackAsync("Test Artist 1"); await Expect(bar.UndoButton).ToBeEnabledAsync(); await Expect(bar.RedoButton).ToBeDisabledAsync(); @@ -187,7 +188,8 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenModalEdit() var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); + await detailView.AddAudiofileAsync(); + await detailView.AddTrackAsync(0); await detailView.SelectTracksAsync([1]); await detailView.EditTracksModalAsync("Test Track Artist 1", "Test Track Title 1", "00:02:23", ["channel audio (4CH)", "Serial copy management system"]); await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table: @@ -239,7 +241,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - columnheader ""Length"" - columnheader ""Status"" - rowgroup: - - row ""Select row 1 00:00:00 End has no value! Length has no value!"" [selected]: + - row ""Select row 1 00:00:00"" [selected]: - cell ""Select row"": - checkbox ""Select row"" [checked] - text: Select row @@ -252,12 +254,10 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - button - cell ""00:00:00"": - textbox: 00:00:00 - - cell ""End has no value!"": + - cell: - textbox - - text: End has no value! - - cell ""Length has no value!"": + - cell: - textbox - - text: Length has no value! - cell - rowgroup: - row"); @@ -308,6 +308,7 @@ public async Task UndoRedo_ShouldRestoreCuesheet_WhenUsingImport() await importView.ImportFileAsync("Textimport with Cuesheetdata.txt"); await importView.SetSchemeCommonDataAsync("Artist - Title - "); await importView.SelectSchemeCommonDataPlaceholderAsync("Cataloguenumber"); + await importView.ClearSchemeAudiofilesAsync(); await importView.Analyze(); await Expect(bar.UndoButton).ToBeDisabledAsync(); await Expect(bar.RedoButton).ToBeDisabledAsync(); @@ -1162,9 +1163,10 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenUndoWithAudiofile() var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); - await detailView.AddTrackAsync(); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.AddTrackAsync(0); + await detailView.AddTrackAsync(0); await Expect(bar.UndoButton).ToBeEnabledAsync(); await Expect(bar.RedoButton).ToBeDisabledAsync(); await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table: @@ -1180,7 +1182,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - columnheader ""Length"" - columnheader ""Status"" - rowgroup: - - row ""Select row 1 00:00:00 End has no value! Length has no value!"": + - row ""Select row 1 00:00:00"": - cell ""Select row"": - checkbox ""Select row"" - text: Select row @@ -1193,12 +1195,10 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - button - cell ""00:00:00"": - textbox: 00:00:00 - - cell ""End has no value!"": + - cell: - textbox - - text: End has no value! - - cell ""Length has no value!"": + - cell: - textbox - - text: Length has no value! - cell - row ""Select row 2 00:05:48.0608330"": - cell ""Select row"": diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs similarity index 67% rename from AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTestSmartphone.cs rename to AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs index 6c87dc81..ae8ab082 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs @@ -19,7 +19,7 @@ namespace AudioCuesheetEditor.End2EndTests.Tests.Smartphone { [TestClass] - public class BasicTestSmartphone : PlaywrightTestBase + public class BasicTest : PlaywrightTestBase { protected override string? DeviceName => "iPhone 13"; @@ -47,8 +47,9 @@ public async Task Audiofile_ShouldBeRenamed_WhenEditingFilename() { var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); - await detailView.RenameAudiofileAsync("Kalimba test 123.mp3"); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.RenameAudiofileAsync(0, "Kalimba test 123.mp3"); await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToMatchAriaSnapshotAsync("- textbox \"Audiofile\": Kalimba test 123.mp3"); } @@ -72,12 +73,65 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected() await bar.ChangeLanguageAsync("German (Germany)"); await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Allgemeine Informationen" })).ToBeVisibleAsync(); await Expect(TestPage.GetByText("Aufnahmeansicht")).ToBeVisibleAsync(); - await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Titel" })).ToBeVisibleAsync(); + await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Dateien" })).ToBeVisibleAsync(); await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Wiedergabe" })).ToBeVisibleAsync(); await bar.OpenExportDialogAsync("Textdatei", "Datei"); - await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToMatchAriaSnapshotAsync("- tabpanel:\n - text: \"Export ist derzeit nicht möglich: Titel hat ungültige Anzahl (0)! Künstler hat keinen Wert! Titel hat keinen Wert! Audiodatei hat keinen Wert! YouTube\"\n - group \"Exportprofil auswählen\"\n - text: Exportprofil auswählen\n - group:\n - button \"Neues Exportprofil hinzufügen\"\n - button \"Ausgewähltes Exportprofil löschen\"\n - separator\n - textbox \"Name\": YouTube\n - group \"Name\"\n - text: Name\n - textbox \"Dateiname\": YouTube.txt\n - group \"Dateiname\"\n - text: Dateiname\n - textbox \"Schema Kopf\": \"%Cuesheet.Artist% - %Cuesheet.Title%\"\n - button \"Clear\"\n - button\n - group \"Schema Kopf\"\n - text: Schema Kopf\n - textbox \"Schema Titel\": \"%Track.Artist% - %Track.Title% %Track.Begin%\"\n - button \"Clear\"\n - button\n - group \"Schema Titel\"\n - text: Schema Titel\n - textbox \"Schema Fuß\"\n - button\n - group \"Schema Fuß\"\n - text: Schema Fuß"); + await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToMatchAriaSnapshotAsync(@"- dialog ""Exportprofile Close"": + - heading ""Exportprofile"" [level=6] + - button ""Close"" + - tablist: + - tab ""Export konfigurieren"" [selected]: + - paragraph: Export konfigurieren + - tab ""2 Export herunterladen"" [disabled]: + - text: ""2"" + - paragraph: Export herunterladen + - tabpanel ""Export konfigurieren"": + - text: ""Export ist derzeit nicht möglich: Künstler hat keinen Wert! Titel hat keinen Wert! Audiodateien hat ungültige Anzahl (0)!"" + - combobox ""Exportprofil auswählen"": YouTube + - group ""Exportprofil auswählen"" + - text: Exportprofil auswählen + - group: + - button ""Neues Exportprofil hinzufügen"" + - button ""Ausgewähltes Exportprofil löschen"" + - separator + - textbox ""Name"": + - /placeholder: Geben Sie hier den Namen für dieses Profil ein + - text: YouTube + - group ""Name"" + - text: Name + - textbox ""Dateiname"": + - /placeholder: Geben Sie hier den Dateinamen für dieses Profil ein + - text: YouTube.txt + - group ""Dateiname"" + - text: Dateiname + - textbox ""Schema Kopf"": + - /placeholder: Geben Sie hier das Kopf-Schema für dieses Profil ein + - text: ""%Cuesheet.Artist% - %Cuesheet.Title%"" + - button ""Clear"" + - button + - group ""Schema Kopf"" + - text: Schema Kopf + - textbox ""Schema Audiodateien"": + - /placeholder: Geben Sie hier das Audiodatei-Schema für dieses Profil ein + - button + - group ""Schema Audiodateien"" + - text: Schema Audiodateien + - textbox ""Schema Titel"": + - /placeholder: Geben Sie hier das Titel-Schema für dieses Profil ein + - text: ""%Track.Artist% - %Track.Title% %Track.Begin%"" + - button ""Clear"" + - button + - group ""Schema Titel"" + - text: Schema Titel + - textbox ""Schema Fuß"": + - /placeholder: Geben Sie hier das Fuß-Schema für dieses Profil ein + - button + - group ""Schema Fuß"" + - text: Schema Fuß + - button ""Previous"" [disabled] + - button ""Next"" [disabled]"); await exportDialog.OpenSchemeMenuAsync("Schema Kopf"); - await Expect(TestPage.Locator("#app")).ToMatchAriaSnapshotAsync("- paragraph: Künstler\n- paragraph: Titel\n- paragraph: Audiodatei\n- paragraph: CDTextdatei\n- paragraph: Katalognummer\n- paragraph: Datum\n- paragraph: Datum & Uhrzeit\n- paragraph: Uhrzeit"); + await Expect(TestPage.Locator("#app")).ToMatchAriaSnapshotAsync("- paragraph: Künstler\n- paragraph: Titel\n- paragraph: CDTextdatei\n- paragraph: Katalognummer\n- paragraph: Datum\n- paragraph: Datum & Uhrzeit\n- paragraph: Uhrzeit"); await TestPage.GetByText("CDTextdatei").ClickAsync(); await exportDialog.OpenSchemeMenuAsync("Schema Titel"); await Expect(TestPage.GetByTestId("menu-wrapper")).ToMatchAriaSnapshotAsync("- paragraph: Position\n- paragraph: Künstler\n- paragraph: Titel\n- paragraph: Begin\n- paragraph: End\n- paragraph: Länge\n- paragraph: Markierungen\n- paragraph: Vorlücke\n- paragraph: Nachlücke"); @@ -86,20 +140,18 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected() [TestMethod] public async Task TrackTableControls_ShouldBeEnabled_WhenSelectingFirstTrackAsync() { - var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); - await detailView.AddTrackAsync(); + await detailView.AddAudiofileAsync(); + await detailView.AddTrackAsync(0); + await detailView.AddTrackAsync(0); await detailView.SelectTracksAsync([1]); - await bar.ChangeLanguageAsync("German (Germany)"); await Expect(TestPage.GetByLabel("Track table controls")).ToMatchAriaSnapshotAsync(@"- group: - - button ""Neuen Titel hinzufügen"" - - button ""Ausgewählte Titel bearbeiten"" - - button - - button ""Alle Titel löschen"" + - button ""Add new track"" + - button ""Edit selected tracks"" - button -- button ""Fester Tabellenkopf"""); + - button ""Delete all tracks"" + - button"); } [TestMethod] @@ -111,26 +163,32 @@ public async Task KeyboardCommands_ShouldControlDialogs_WhenUsingEnterOrEscapeAs await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenExportDialogAsync("Cuesheet"); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); + await TestPage.GetByRole(AriaRole.Dialog).FocusAsync(); await TestPage.Keyboard.PressAsync("Escape"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenExportDialogAsync("Projectfile"); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); + await TestPage.GetByRole(AriaRole.Dialog).FocusAsync(); await TestPage.Keyboard.PressAsync("Escape"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenExportDialogAsync("Textfile"); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); + await TestPage.GetByRole(AriaRole.Dialog).FocusAsync(); await TestPage.Keyboard.PressAsync("Escape"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenSettingsAsync(); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); + await TestPage.GetByRole(AriaRole.Dialog).FocusAsync(); await TestPage.Keyboard.PressAsync("Escape"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenDisplayHotkeysAsync(); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); + await TestPage.GetByRole(AriaRole.Dialog).FocusAsync(); await TestPage.Keyboard.PressAsync("Escape"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); - await detailView.OpenRenameAudiofileDialogAsync(); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.OpenRenameAudiofileDialogAsync(0); await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync(); await detailView.NewFileNameInput.FillAsync("Test 123"); await TestPage.Keyboard.PressAsync("Enter"); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTest.cs similarity index 80% rename from AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs rename to AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTest.cs index 22865d9b..11df1c78 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTest.cs @@ -19,18 +19,21 @@ namespace AudioCuesheetEditor.End2EndTests.Tests.Smartphone { [TestClass] - public class ExportTestSmartphone : PlaywrightTestBase + public class ExportTest : PlaywrightTestBase { + protected override string? DeviceName => "iPhone 13"; + [TestMethod] public async Task DownloadCuesheet_GeneratesCuesheetFile_WhenCuesheetIsValid() { var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1"); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.AddTrackAsync(0); await detailView.EditTrackAsync("Track Artist 1", "Track Title 1"); await bar.OpenExportDialogAsync("Cuesheet"); var downloadTask = TestPage.WaitForDownloadAsync(); @@ -56,10 +59,11 @@ public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync( var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1"); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.AddTrackAsync(0); await detailView.EditTrackAsync("Track Artist 1", "Track Title 1"); await bar.OpenExportDialogAsync("Projectfile"); var downloadTask = TestPage.WaitForDownloadAsync(); @@ -68,7 +72,7 @@ public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync( using var stream = await download.CreateReadStreamAsync(); using var reader = new StreamReader(stream); var content = await reader.ReadToEndAsync(TestContext.CancellationToken); - Assert.AreEqual("{\"Tracks\":[{\"Position\":1,\"Artist\":\"Track Artist 1\",\"Title\":\"Track Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:05:48.0608330\",\"Flags\":[],\"IsLinkedToPreviousTrack\":true}],\"Artist\":\"Cuesheet Artist 1\",\"Title\":\"Cuesheet Title 1\",\"Audiofile\":{\"Name\":\"Kalimba.mp3\",\"Duration\":\"00:05:48.0608330\",\"AudioCodec\":{\"MimeType\":\"audio/mpeg\",\"FileExtension\":\".mp3\",\"Name\":\"AudioCodec MP3\"}}}", content); + Assert.AreEqual("{\"Artist\":\"Cuesheet Artist 1\",\"Title\":\"Cuesheet Title 1\",\"Audiofiles\":[{\"Name\":\"Kalimba.mp3\",\"Duration\":\"00:05:48.0608330\",\"AudioCodec\":{\"MimeType\":\"audio/mpeg\",\"FileExtension\":\".mp3\",\"Name\":\"AudioCodec MP3\"},\"Tracks\":[{\"Position\":1,\"Artist\":\"Track Artist 1\",\"Title\":\"Track Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:05:48.0608330\",\"Flags\":[],\"IsLinkedToPreviousTrack\":true}]}]}", content); } [TestMethod] @@ -77,10 +81,11 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync() var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1"); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.AddTrackAsync(0); await detailView.EditTrackAsync("Track Artist 1", "Track Title 1"); await bar.OpenExportDialogAsync("Textfile"); await TestPage.GetByRole(AriaRole.Button, new() { Name = "Next", Exact = true }).ClickAsync(); @@ -92,6 +97,7 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync() var content = await reader.ReadToEndAsync(TestContext.CancellationToken); content = content.Replace("\n", Environment.NewLine); Assert.AreEqual(@"Cuesheet Artist 1 - Cuesheet Title 1 + Track Artist 1 - Track Title 1 00:00:00 ", content); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs similarity index 99% rename from AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs rename to AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs index 129f49ad..bb24ec81 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs @@ -19,7 +19,7 @@ namespace AudioCuesheetEditor.End2EndTests.Tests.Smartphone { [TestClass] - public class ImportTestSmartphone : PlaywrightTestBase + public class ImportTest : PlaywrightTestBase { protected override string? DeviceName => "iPhone 13"; @@ -27,7 +27,6 @@ public class ImportTestSmartphone : PlaywrightTestBase public async Task Import_ShouldImportTracks_WhenUsingSampleInputfile() { var importView = new ImportView(TestPage, DeviceName != null); - var detailView = new DetailView(TestPage); await importView.GotoAsync(); await importView.ImportFileAsync("Sample_Inputfile.txt"); await importView.Analyze(); @@ -249,7 +248,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - cell ""Status"": - text: Status - button"); - await Expect(detailView.AudiofileInput).ToBeEmptyAsync(); + await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToHaveValueAsync(@"c:\AudioFile.mp3"); await importView.GotoAsync(); await Expect(TestPage.GetByRole(AriaRole.Button, new() { Name = "Analyze" })).ToBeVisibleAsync(); } @@ -1439,6 +1438,7 @@ public async Task Import_ShouldImportTracks_WhenUsingSampleInputfile2() await importView.GotoAsync(); await importView.ImportFileAsync("Sample_Inputfile2.txt"); await importView.ClearSchemeCommonDataAsync(); + await importView.ClearSchemeAudiofilesAsync(); await importView.Analyze(); await Expect(importView.CuesheetArtistInput).ToBeEmptyAsync(); await Expect(importView.CuesheetTitleInput).ToBeEmptyAsync(); @@ -2956,7 +2956,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - cell ""Status"": - text: Status - button - - 'row ""Select row # 8 Artist Sample Artist 8 Clear Title Sample Title 8 Clear Begin 00:45:54 End Length Status""': + - 'row ""Select row # 8 Artist Sample Artist 8 Clear Title Sample Title 8 Clear Begin 00:45:54 End 01:15:54 Length 00:30:00 Status""': - cell ""Select row"": - checkbox ""Select row"" - text: Select row @@ -2974,17 +2974,17 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - cell ""Begin 00:45:54"": - text: Begin - textbox: 00:45:54 - - cell ""End"": + - cell ""End 01:15:54"": - text: End - - textbox - - cell ""Length"": + - textbox: 01:15:54 + - cell ""Length 00:30:00"": - text: Length - - textbox + - textbox: 00:30:00 - cell ""Status"": - text: Status - button"); await appBar.UndoAsync(); - await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Tracks has invalid Count (0)!" })).ToBeVisibleAsync(); + await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync(); } [TestMethod] @@ -3214,7 +3214,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - text: Status - button"); await appBar.UndoAsync(); - await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Tracks has invalid Count (0)!" })).ToBeVisibleAsync(); + await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync(); } [TestMethod] diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTest.cs similarity index 97% rename from AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTestSmartphone.cs rename to AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTest.cs index 1174a2ce..a28c370f 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTest.cs @@ -19,7 +19,7 @@ namespace AudioCuesheetEditor.End2EndTests.Tests.Smartphone { [TestClass] - public class RecordTestSmartphone : PlaywrightTestBase + public class RecordTest : PlaywrightTestBase { protected override string? DeviceName => "iPhone 13"; [TestMethod] diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTest.cs similarity index 98% rename from AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTestSmartphone.cs rename to AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTest.cs index d5ac1b48..fff99f79 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTest.cs @@ -19,7 +19,7 @@ namespace AudioCuesheetEditor.End2EndTests.Tests.Smartphone { [TestClass] - public class TracingTestSmartphone : PlaywrightTestBase + public class TracingTest : PlaywrightTestBase { protected override string? DeviceName => "iPhone 13"; @@ -29,7 +29,8 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenUndoAndRedoAreUsed() var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); + await detailView.AddAudiofileAsync(); + await detailView.AddTrackAsync(0); await detailView.EditTrackAsync("Test Artist 1"); await Expect(bar.UndoButton).ToBeEnabledAsync(); await Expect(bar.RedoButton).ToBeDisabledAsync(); @@ -161,7 +162,8 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenModalEdit() var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); + await detailView.AddAudiofileAsync(); + await detailView.AddTrackAsync(0); await detailView.SelectTracksAsync([1]); await detailView.EditTracksModalAsync("Test Track Artist 1", "Test Track Title 1", "00:02:23", ["channel audio (4CH)", "Serial copy management system"]); await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table: @@ -196,7 +198,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table: - rowgroup - rowgroup: - - 'row ""Select row # 1 Artist Title Begin 00:00:00 End End has no value! Length Length has no value! Status"" [selected]': + - 'row ""Select row # 1 Artist Title Begin 00:00:00 End Length Status"" [selected]': - cell ""Select row"": - checkbox ""Select row"" [checked] - text: Select row @@ -212,14 +214,12 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - cell ""Begin 00:00:00"": - text: Begin - textbox: 00:00:00 - - cell ""End End has no value!"": + - cell ""End"": - text: End - textbox - - text: End has no value! - - cell ""Length Length has no value!"": + - cell ""Length"": - text: Length - textbox - - text: Length has no value! - cell ""Status"""); await bar.RedoAsync(); await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table: @@ -261,6 +261,7 @@ public async Task UndoRedo_ShouldRestoreCuesheet_WhenUsingImport() await importView.ImportFileAsync("Textimport with Cuesheetdata.txt"); await importView.SetSchemeCommonDataAsync("Artist - Title - "); await importView.SelectSchemeCommonDataPlaceholderAsync("Cataloguenumber"); + await importView.ClearSchemeAudiofilesAsync(); await importView.Analyze(); await Expect(bar.UndoButton).ToBeDisabledAsync(); await Expect(bar.RedoButton).ToBeDisabledAsync(); @@ -1336,15 +1337,16 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenUndoWithAudiofile() var bar = new AppBar(TestPage); var detailView = new DetailView(TestPage); await detailView.GotoAsync(); - await detailView.AddTrackAsync(); - await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); - await detailView.AddTrackAsync(); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await detailView.AddTrackAsync(0); + await detailView.AddTrackAsync(0); await Expect(bar.UndoButton).ToBeEnabledAsync(); await Expect(bar.RedoButton).ToBeDisabledAsync(); await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table: - rowgroup - rowgroup: - - 'row ""Select row # 1 Artist Title Begin 00:00:00 End End has no value! Length Length has no value! Status""': + - 'row ""Select row # 1 Artist Title Begin 00:00:00 End Length Status""': - cell ""Select row"": - checkbox ""Select row"" - text: Select row @@ -1360,14 +1362,12 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - cell ""Begin 00:00:00"": - text: Begin - textbox: 00:00:00 - - cell ""End End has no value!"": + - cell ""End"": - text: End - textbox - - text: End has no value! - - cell ""Length Length has no value!"": + - cell ""Length"": - text: Length - textbox - - text: Length has no value! - cell ""Status"" - 'row ""Select row # 2 Artist Title Begin End 00:05:48.0608330 Length Status""': - cell ""Select row"": diff --git a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj index 1ddbafe8..9c8eba6b 100644 --- a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj +++ b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj @@ -16,7 +16,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/AudioCuesheetEditor.Tests/Model/AudioCuesheet/AudiofileTests.cs b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/AudiofileTests.cs new file mode 100644 index 00000000..3feb39f8 --- /dev/null +++ b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/AudiofileTests.cs @@ -0,0 +1,102 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.Entity; +using AudioCuesheetEditor.Model.IO.Audio; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Linq; + +namespace AudioCuesheetEditor.Tests.Model.AudioCuesheet +{ + [TestClass] + public class AudiofileTests + { + [TestMethod] + public void Validate_FilenameNull_ReturnsValidationStatusError() + { + // Arrange + var audiofile = new Audiofile(); + // Act + var validationResult = audiofile.Validate(nameof(Audiofile.Name)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + } + + [TestMethod] + public void Validate_TracksEmpty_ReturnsValidationStatusError() + { + // Arrange + var audiofile = new Audiofile(); + // Act + var validationResult = audiofile.Validate(nameof(Audiofile.Tracks)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} has invalid count ({1})!", validationResult.ValidationMessages.First().Message); + Assert.AreEqual(nameof(Audiofile.Tracks), validationResult.ValidationMessages.First().Parameter?.First().ToString()); + } + + [TestMethod] + public void Validate_TracksWithSamePosition_ReturnsValidationStatusError() + { + // Arrange + var track1 = new Track() + { + Position = 1 + }; + var track2 = new Track() + { + Position = 1 + }; + var audiofile = new Audiofile() + { + Tracks = [track1, track2] + }; + // Act + var validationResult = audiofile.Validate(nameof(Audiofile.Tracks)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!", validationResult.ValidationMessages.First().Message); + } + + [TestMethod] + public void Validate_TracksOverlapping_ReturnsValidationStatusError() + { + // Arrange + var track1 = new Track() + { + Position = 1, + Begin = TimeSpan.Zero, + End = new TimeSpan(0, 3, 45) + }; + var track2 = new Track() + { + Position = 2, + Begin = new TimeSpan(0, 3, 42), + End = new TimeSpan(0, 6, 32) + }; + var audiofile = new Audiofile() + { + Tracks = [track1, track2] + }; + // Act + var validationResult = audiofile.Validate(nameof(Audiofile.Tracks)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!", validationResult.ValidationMessages.First().Message); + } + } +} diff --git a/AudioCuesheetEditor.Tests/Model/AudioCuesheet/CuesheetTests.cs b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/CuesheetTests.cs index 6487ec33..2268757c 100644 --- a/AudioCuesheetEditor.Tests/Model/AudioCuesheet/CuesheetTests.cs +++ b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/CuesheetTests.cs @@ -16,7 +16,6 @@ using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.Entity; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; using System.Linq; namespace AudioCuesheetEditor.Tests.Model.AudioCuesheet @@ -25,12 +24,12 @@ namespace AudioCuesheetEditor.Tests.Model.AudioCuesheet public class CuesheetTests { [TestMethod] - public void Validate_AudiofileNull_ReturnsValidationStatusError() + public void Validate_AudiofilesEmpty_ReturnsValidationStatusError() { // Arrange var cuesheet = new Cuesheet(); // Act - var validationResult = cuesheet.Validate(nameof(Cuesheet.Audiofile)); + var validationResult = cuesheet.Validate(nameof(Cuesheet.Audiofiles)); // Assert Assert.AreEqual(ValidationStatus.Error, validationResult.Status); } @@ -89,73 +88,5 @@ public void Validate_CataloguenumberLengthUnequal13_ReturnsValidationStatusError Assert.AreEqual(nameof(Cuesheet.Cataloguenumber), validationResult.ValidationMessages.First().Parameter?.First().ToString()); Assert.AreEqual(13, validationResult.ValidationMessages.First().Parameter?.Last()); } - - [TestMethod] - public void Validate_TracksEmpty_ReturnsValidationStatusError() - { - // Arrange - var cuesheet = new Cuesheet(); - // Act - var validationResult = cuesheet.Validate(nameof(Cuesheet.Tracks)); - // Assert - Assert.AreEqual(ValidationStatus.Error, validationResult.Status); - Assert.AreEqual("{0} has invalid Count ({1})!", validationResult.ValidationMessages.First().Message); - Assert.AreEqual(nameof(Cuesheet.Tracks), validationResult.ValidationMessages.First().Parameter?.First().ToString()); - Assert.AreEqual(0, validationResult.ValidationMessages.First().Parameter?.Last()); - } - - [TestMethod] - public void Validate_TracksWithSamePosition_ReturnsValidationStatusError() - { - // Arrange - var track1 = new Track() - { - Position = 1 - }; - var track2 = new Track() - { - Position = 1 - }; - var cuesheet = new Cuesheet() - { - Tracks = [track1, track2] - }; - track1.Cuesheet = cuesheet; - track2.Cuesheet = cuesheet; - // Act - var validationResult = cuesheet.Validate(nameof(Cuesheet.Tracks)); - // Assert - Assert.AreEqual(ValidationStatus.Error, validationResult.Status); - Assert.AreEqual("{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!", validationResult.ValidationMessages.First().Message); - } - - [TestMethod] - public void Validate_TracksOverlapping_ReturnsValidationStatusError() - { - // Arrange - var track1 = new Track() - { - Position = 1, - Begin = TimeSpan.Zero, - End = new TimeSpan(0, 3, 45) - }; - var track2 = new Track() - { - Position = 2, - Begin = new TimeSpan(0, 3, 42), - End = new TimeSpan(0, 6, 32) - }; - var cuesheet = new Cuesheet() - { - Tracks = [track1, track2] - }; - track1.Cuesheet = cuesheet; - track2.Cuesheet = cuesheet; - // Act - var validationResult = cuesheet.Validate(nameof(Cuesheet.Tracks)); - // Assert - Assert.AreEqual(ValidationStatus.Error, validationResult.Status); - Assert.AreEqual("{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!", validationResult.ValidationMessages.First().Message); - } } } \ No newline at end of file diff --git a/AudioCuesheetEditor.Tests/Model/AudioCuesheet/TrackTests.cs b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/TrackTests.cs index d29aa2d1..46982836 100644 --- a/AudioCuesheetEditor.Tests/Model/AudioCuesheet/TrackTests.cs +++ b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/TrackTests.cs @@ -60,10 +60,7 @@ public void Validate_PositionOvverlapping_ReturnsValidationStatusError() { Position = 1 }; - var cuesheet = new Cuesheet() - { - Tracks = [track1, track] - }; + var cuesheet = new Cuesheet(); track1.Cuesheet = cuesheet; track.Cuesheet = cuesheet; // Act diff --git a/AudioCuesheetEditor.Tests/Model/IO/Export/ExportprofileTests.cs b/AudioCuesheetEditor.Tests/Model/IO/Export/ExportprofileTests.cs index 2cdd8009..14e1dc34 100644 --- a/AudioCuesheetEditor.Tests/Model/IO/Export/ExportprofileTests.cs +++ b/AudioCuesheetEditor.Tests/Model/IO/Export/ExportprofileTests.cs @@ -16,7 +16,7 @@ using AudioCuesheetEditor.Model.Entity; using AudioCuesheetEditor.Model.IO.Export; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; +using System.Linq; namespace AudioCuesheetEditor.Tests.Model.IO.Export { @@ -24,23 +24,216 @@ namespace AudioCuesheetEditor.Tests.Model.IO.Export public class ExportprofileTests { [TestMethod()] - public void ValidateTest() + public void Validate_EmptyFilename_ReturnsError() { + // Arrange var exportprofile = new Exportprofile { Filename = string.Empty }; - Assert.AreEqual(ValidationStatus.Error, exportprofile.Validate(nameof(Exportprofile.Filename)).Status); - exportprofile.Filename = "Test123"; - Assert.AreEqual(ValidationStatus.Success, exportprofile.Validate(nameof(Exportprofile.Filename)).Status); - exportprofile.SchemeHead = "%Cuesheet.Artist%;%Cuesheet.Title%;%Cuesheet.Cataloguenumber%;%Cuesheet.CDTextfile%"; - Assert.AreEqual(ValidationStatus.Success, exportprofile.Validate(nameof(Exportprofile.SchemeHead)).Status); - exportprofile.SchemeTracks = "%Track.Position%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%;%Track.PreGap%;%Track.PostGap%"; - Assert.AreEqual(ValidationStatus.Success, exportprofile.Validate(nameof(Exportprofile.SchemeTracks)).Status); - exportprofile.SchemeFooter = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor at %Date%"; - Assert.AreEqual(ValidationStatus.Success, exportprofile.Validate(nameof(Exportprofile.SchemeFooter)).Status); - exportprofile.SchemeFooter = "Exported %Track.Title% from %Cuesheet.Artist% using AudioCuesheetEditor at %Date%"; - Assert.AreEqual(ValidationStatus.Error, exportprofile.Validate(nameof(Exportprofile.SchemeFooter)).Status); + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.Filename)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} has no value!", validationResult.ValidationMessages.First().Message); + Assert.AreEqual(nameof(Exportprofile.Filename), validationResult.ValidationMessages.First().Parameter?.First().ToString()); + } + + [TestMethod()] + public void Validate_EmptyName_ReturnsError() + { + // Arrange + var exportprofile = new Exportprofile + { + Name = string.Empty + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.Name)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} has no value!", validationResult.ValidationMessages.First().Message); + Assert.AreEqual(nameof(Exportprofile.Name), validationResult.ValidationMessages.First().Parameter?.First().ToString()); + } + + [TestMethod()] + public void Validate_SchemeHeadWithTrackPlaceholder_ReturnsError() + { + // Arrange + var exportprofile = new Exportprofile + { + SchemeHead = Exportprofile.SchemeTrackTitle + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeHead)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message); + Assert.AreEqual(nameof(Exportprofile.SchemeHead), validationResult.ValidationMessages.First().Parameter?.First().ToString()); + Assert.AreEqual(Exportprofile.SchemeTrackTitle, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString()); + } + + [TestMethod()] + public void Validate_SchemeHeadWithAudiofilePlaceholder_ReturnsError() + { + // Arrange + var exportprofile = new Exportprofile + { + SchemeHead = Exportprofile.SchemeAudiofileName + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeHead)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message); + Assert.AreEqual(nameof(Exportprofile.SchemeHead), validationResult.ValidationMessages.First().Parameter?.First().ToString()); + Assert.AreEqual(Exportprofile.SchemeAudiofileName, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString()); + } + + [TestMethod()] + public void Validate_SchemeTrackWithCuesheetPlaceholder_ReturnsError() + { + // Arrange + var exportprofile = new Exportprofile + { + SchemeTracks = Exportprofile.SchemeCuesheetArtist + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeTracks)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message); + Assert.AreEqual(nameof(Exportprofile.SchemeTracks), validationResult.ValidationMessages.First().Parameter?.First().ToString()); + Assert.AreEqual(Exportprofile.SchemeCuesheetArtist, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString()); + } + + [TestMethod()] + public void Validate_SchemeTrackWithAudiofilePlaceholder_ReturnsError() + { + // Arrange + var exportprofile = new Exportprofile + { + SchemeTracks = Exportprofile.SchemeAudiofileName + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeTracks)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message); + Assert.AreEqual(nameof(Exportprofile.SchemeTracks), validationResult.ValidationMessages.First().Parameter?.First().ToString()); + Assert.AreEqual(Exportprofile.SchemeAudiofileName, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString()); + } + + [TestMethod()] + public void Validate_SchemeFooterWithTrackPlaceholder_ReturnsError() + { + // Arrange + var exportprofile = new Exportprofile + { + SchemeFooter = Exportprofile.SchemeTrackBegin + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeFooter)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message); + Assert.AreEqual(nameof(Exportprofile.SchemeFooter), validationResult.ValidationMessages.First().Parameter?.First().ToString()); + Assert.AreEqual(Exportprofile.SchemeTrackBegin, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString()); + } + + [TestMethod()] + public void Validate_SchemeFooterWithAudiofilePlaceholder_ReturnsError() + { + // Arrange + var exportprofile = new Exportprofile + { + SchemeFooter = Exportprofile.SchemeAudiofileName + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeFooter)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message); + Assert.AreEqual(nameof(Exportprofile.SchemeFooter), validationResult.ValidationMessages.First().Parameter?.First().ToString()); + Assert.AreEqual(Exportprofile.SchemeAudiofileName, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString()); + } + + [TestMethod()] + public void Validate_SchemeHeadCorrectPlaceholder_ReturnsSuccess() + { + // Arrange + var exportprofile = new Exportprofile + { + SchemeHead = Exportprofile.SchemeCuesheetTitle + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeHead)); + // Assert + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); + Assert.IsEmpty(validationResult.ValidationMessages); + } + + [TestMethod()] + public void Validate_SchemeTrackCorrectPlaceholder_ReturnsSuccess() + { + // Arrange + var exportprofile = new Exportprofile + { + SchemeTracks = Exportprofile.SchemeTrackPreGap + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeTracks)); + // Assert + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); + Assert.IsEmpty(validationResult.ValidationMessages); + } + + [TestMethod()] + public void Validate_SchemeAudiofilesWithTrackPlaceholder_ReturnsError() + { + // Arrange + var exportprofile = new Exportprofile + { + SchemeAudiofiles = Exportprofile.SchemeTrackArtist + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeAudiofiles)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message); + Assert.AreEqual(nameof(Exportprofile.SchemeAudiofiles), validationResult.ValidationMessages.First().Parameter?.First().ToString()); + Assert.AreEqual(Exportprofile.SchemeTrackArtist, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString()); + } + + [TestMethod()] + public void Validate_SchemeAudiofilesWithCuesheetPlaceholder_ReturnsError() + { + // Arrange + var exportprofile = new Exportprofile + { + SchemeAudiofiles = Exportprofile.SchemeCuesheetCatalogueNumber + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeAudiofiles)); + // Assert + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message); + Assert.AreEqual(nameof(Exportprofile.SchemeAudiofiles), validationResult.ValidationMessages.First().Parameter?.First().ToString()); + Assert.AreEqual(Exportprofile.SchemeCuesheetCatalogueNumber, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString()); + } + + [TestMethod()] + public void Validate_SchemeAudiofilesCorrectPlaceholder_ReturnsNoValidation() + { + // Arrange + var exportprofile = new Exportprofile + { + SchemeAudiofiles = Exportprofile.SchemeAudiofileName + }; + // Act + var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeAudiofiles)); + // Assert + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); + Assert.IsEmpty(validationResult.ValidationMessages); } } } \ No newline at end of file diff --git a/AudioCuesheetEditor.Tests/Model/IO/ProjectfileTests.cs b/AudioCuesheetEditor.Tests/Model/IO/ProjectfileTests.cs index 335266f1..807be58d 100644 --- a/AudioCuesheetEditor.Tests/Model/IO/ProjectfileTests.cs +++ b/AudioCuesheetEditor.Tests/Model/IO/ProjectfileTests.cs @@ -15,7 +15,6 @@ //. using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.IO; -using AudioCuesheetEditor.Model.IO.Audio; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; @@ -36,11 +35,15 @@ public void GenerateFile_WithoutSections_GeneratesOneFile() { Artist = "CuesheetArtist", Title = "CuesheetTitle", - Audiofile = new Audiofile("AudioFile.mp3"), + Audiofiles = [ + new () { Name = "AudioFile.mp3" }, + new () { Name = "Other audiofile.wav" } + ], CDTextfile = new CDTextfile("CDTextfile.cdt"), Cataloguenumber = "A123" }; var begin = TimeSpan.Zero; + var tracks = new List(); for (ushort i = 1; i <= 10; i++) { var track = new Track @@ -60,8 +63,11 @@ public void GenerateFile_WithoutSections_GeneratesOneFile() track.Flags = flags; begin = begin.Add(new TimeSpan(0, i, i)); track.End = begin; - cuesheet.Tracks = cuesheet.Tracks.Append(track); + track.Cuesheet = cuesheet; + tracks.Add(track); } + cuesheet.Audiofiles.First().Tracks = tracks.GetRange(0, 5); + cuesheet.Audiofiles.Last().Tracks = tracks.GetRange(4, 5); // Act var projectFile = new Projectfile(cuesheet); // Assert @@ -79,24 +85,27 @@ public void GenerateFile_WithoutSections_GeneratesOneFile() public void ImportFile_ValidProjectfile_ShouldImportFile() { // Arrange - var fileContent = "{\"Artist\":\"CuesheetArtist\",\"Title\":\"CuesheetTitle\",\"Cataloguenumber\":\"A123\",\"Audiofile\":{\"Name\":\"AudioFile.mp3\"},\"Tracks\":[{\"Position\":1,\"Artist\":\"Artist 1\",\"Title\":\"Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:01:01\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":2,\"Artist\":\"Artist 2\",\"Title\":\"Title 2\",\"Begin\":\"00:01:01\",\"End\":\"00:03:03\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":3,\"Artist\":\"Artist 3\",\"Title\":\"Title 3\",\"Begin\":\"00:03:03\",\"End\":\"00:06:06\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":4,\"Artist\":\"Artist 4\",\"Title\":\"Title 4\",\"Begin\":\"00:06:06\",\"End\":\"00:10:10\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":5,\"Artist\":\"Artist 5\",\"Title\":\"Title 5\",\"Begin\":\"00:10:10\",\"End\":\"00:15:15\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":6,\"Artist\":\"Artist 6\",\"Title\":\"Title 6\",\"Begin\":\"00:15:15\",\"End\":\"00:21:21\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":7,\"Artist\":\"Artist 7\",\"Title\":\"Title 7\",\"Begin\":\"00:21:21\",\"End\":\"00:28:28\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":8,\"Artist\":\"Artist 8\",\"Title\":\"Title 8\",\"Begin\":\"00:28:28\",\"End\":\"00:36:36\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":9,\"Artist\":\"Artist 9\",\"Title\":\"Title 9\",\"Begin\":\"00:36:36\",\"End\":\"00:45:45\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":10,\"Artist\":\"Artist 10\",\"Title\":\"Title 10\",\"Begin\":\"00:45:45\",\"End\":\"00:55:55\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true}],\"CDTextfile\":{\"Name\":\"CDTextfile.cdt\"}}"; + var fileContent = "{\"Artist\":\"CuesheetArtist\",\"Title\":\"CuesheetTitle\",\"Cataloguenumber\":\"A123\",\"Audiofiles\":[{\"Name\":\"AudioFile.mp3\",\"Duration\":\"00:05:48.0608330\",\"AudioCodec\":{\"MimeType\":\"audio/mpeg\",\"FileExtension\":\".mp3\",\"Name\":\"AudioCodec MP3\"},\"Tracks\":[{\"Position\":1,\"Artist\":\"Artist 1\",\"Title\":\"Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:01:01\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":2,\"Artist\":\"Artist 2\",\"Title\":\"Title 2\",\"Begin\":\"00:01:01\",\"End\":\"00:03:03\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":3,\"Artist\":\"Artist 3\",\"Title\":\"Title 3\",\"Begin\":\"00:03:03\",\"End\":\"00:06:06\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":4,\"Artist\":\"Artist 4\",\"Title\":\"Title 4\",\"Begin\":\"00:06:06\",\"End\":\"00:10:10\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":5,\"Artist\":\"Artist 5\",\"Title\":\"Title 5\",\"Begin\":\"00:10:10\",\"End\":\"00:15:15\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true}]},{\"Name\":\"Other audiofile.wav\",\"Duration\":\"00:05:48.0608330\",\"AudioCodec\":{\"MimeType\":\"audio/wav\",\"FileExtension\":\".wav\",\"Name\":\"AudioCodec WAVE\"},\"Tracks\":[{\"Position\":6,\"Artist\":\"Artist 6\",\"Title\":\"Title 6\",\"Begin\":\"00:15:15\",\"End\":\"00:21:21\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":7,\"Artist\":\"Artist 7\",\"Title\":\"Title 7\",\"Begin\":\"00:21:21\",\"End\":\"00:28:28\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":8,\"Artist\":\"Artist 8\",\"Title\":\"Title 8\",\"Begin\":\"00:28:28\",\"End\":\"00:36:36\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":9,\"Artist\":\"Artist 9\",\"Title\":\"Title 9\",\"Begin\":\"00:36:36\",\"End\":\"00:45:45\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":10,\"Artist\":\"Artist 10\",\"Title\":\"Title 10\",\"Begin\":\"00:45:45\",\"End\":\"00:55:55\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true}]}],\"CDTextfile\":{\"Name\":\"CDTextfile.cdt\"}}"; // Act var cuesheet = Projectfile.ImportFile(fileContent); // Assert Assert.IsNotNull(cuesheet); - Assert.IsTrue(cuesheet.Tracks.All(x => x.Cuesheet == cuesheet)); - Assert.IsTrue(cuesheet.Tracks.All(x => x.IsLinkedToPreviousTrack)); + Assert.HasCount(2, cuesheet.Audiofiles); + Assert.HasCount(5, cuesheet.Audiofiles.First().Tracks); + Assert.HasCount(5, cuesheet.Audiofiles.Last().Tracks); + Assert.IsTrue(cuesheet.Audiofiles.SelectMany(x => x.Tracks).All(x => x.Cuesheet == cuesheet)); + Assert.IsTrue(cuesheet.Audiofiles.SelectMany(x => x.Tracks).All(x => x.IsLinkedToPreviousTrack)); Assert.AreEqual("CuesheetArtist", cuesheet.Artist); Assert.AreEqual("CuesheetTitle", cuesheet.Title); - Assert.AreEqual("AudioFile.mp3", cuesheet.Audiofile?.Name); + Assert.AreEqual("AudioFile.mp3", cuesheet.Audiofiles.First().Name); Assert.AreEqual("A123", cuesheet.Cataloguenumber); Assert.AreEqual(2, cuesheet.Validate(nameof(Cuesheet.Cataloguenumber)).ValidationMessages?.Count); - Assert.HasCount(10, cuesheet.Tracks); - Assert.IsTrue(cuesheet.Tracks.ElementAt(3).Flags.Contains(Flag.DCP)); - Assert.IsTrue(cuesheet.Tracks.ElementAt(3).Flags.Contains(Flag.FourCH)); - Assert.AreEqual("Artist 10", cuesheet.Tracks.Last().Artist); - Assert.AreEqual(new TimeSpan(0, 55, 55), cuesheet.Tracks.Last().End); - Assert.AreEqual((ushort)10, cuesheet.Tracks.Last().Position); + Assert.HasCount(10, cuesheet.Audiofiles.SelectMany(x => x.Tracks)); + Assert.Contains(Flag.DCP, cuesheet.Audiofiles.First().Tracks.ElementAt(3).Flags); + Assert.Contains(Flag.FourCH, cuesheet.Audiofiles.First().Tracks.ElementAt(3).Flags); + Assert.AreEqual("Artist 10", cuesheet.Audiofiles.Last().Tracks.Last().Artist); + Assert.AreEqual(new TimeSpan(0, 55, 55), cuesheet.Audiofiles.Last().Tracks.Last().End); + Assert.AreEqual((ushort)10, cuesheet.Audiofiles.Last().Tracks.Last().Position); } } } \ No newline at end of file diff --git a/AudioCuesheetEditor.Tests/Resources/Sample_Inputfile.txt b/AudioCuesheetEditor.Tests/Resources/Sample_Inputfile.txt index 5f80c195..9d06c8b8 100644 --- a/AudioCuesheetEditor.Tests/Resources/Sample_Inputfile.txt +++ b/AudioCuesheetEditor.Tests/Resources/Sample_Inputfile.txt @@ -1,4 +1,5 @@ -CuesheetArtist - CuesheetTitle c:\AudioFile.mp3 +CuesheetArtist - CuesheetTitle +- c:\AudioFile.mp3 Sample Artist 1 - Sample Title 1 00:05:00 Sample Artist 2 - Sample Title 2 00:09:23 Sample Artist 3 - Sample Title 3 00:15:54 diff --git a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs new file mode 100644 index 00000000..9674ae6e --- /dev/null +++ b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs @@ -0,0 +1,542 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO.Audio; +using AudioCuesheetEditor.Model.UI; +using AudioCuesheetEditor.Services.AudioCuesheet; +using AudioCuesheetEditor.Services.IO; +using AudioCuesheetEditor.Services.UI; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.JSInterop; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; + +namespace AudioCuesheetEditor.Tests.Services.AudioCuesheet +{ + [TestClass] + public class AudiofileManagerTests + { + private readonly AudiofileManager _audiofileManager; + private readonly Mock _fileInputManager; + private readonly Mock _traceChangeManager; + private readonly Mock _jsRuntime; + private readonly Mock _trackManager; + private readonly Mock _sessionStateContainer; + + public AudiofileManagerTests() + { + _fileInputManager = new Mock(); + _traceChangeManager = new Mock(); + _jsRuntime = new Mock(); + _trackManager = new Mock(); + _trackManager.Setup(x => x.SetProperty(It.IsAny(), It.IsAny>>(), It.IsAny())) + .Callback((Track track, LambdaExpression propExpr, object value) => + { + var memberExpression = propExpr.Body as MemberExpression; + if (memberExpression?.Member is PropertyInfo propertyInfo) + { + propertyInfo.SetValue(track, value); + } + }); + _trackManager.Setup(x => x.GetPreviousLinkedTrack(It.IsAny())).Returns(delegate (Track track) + { + if (track.IsLinkedToPreviousTrack == false) + { + return null; + } + if (track.Position.HasValue && (track.Cuesheet?.Audiofiles.SelectMany(x => x.Tracks).All(x => x.Position.HasValue) == true)) + { + return track.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).LastOrDefault(x => x.Position == track.Position - 1 && Equals(x, track) == false); + } + if (track.Begin.HasValue) + { + return track.Cuesheet?.Audiofiles.SelectMany(x => x.Tracks).OrderBy(x => x.End).LastOrDefault(x => x.End <= track.Begin && Equals(x, track) == false); + } + return null; + }); + _sessionStateContainer = new Mock(); + _audiofileManager = new AudiofileManager(_fileInputManager.Object, _traceChangeManager.Object, _jsRuntime.Object, _trackManager.Object, _sessionStateContainer.Object); + } + + [TestMethod] + public async Task SetPropertiesAsync_EmptyBrowserFile_ShouldClearPropertiesAsync() + { + // Arrange + var audiofile = new Audiofile + { + AudioCodec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"), + Name = "test.mp3", + ObjectURL = "Just a test", + Duration = TimeSpan.FromSeconds(120) + }; + var expectedUrl = audiofile.ObjectURL; + // Act + await _audiofileManager.SetPropertiesAsync(audiofile, null, string.Empty); + // Assert + Assert.IsNull(audiofile.AudioCodec); + Assert.IsNull(audiofile.Name); + Assert.IsNull(audiofile.ObjectURL); + Assert.IsNull(audiofile.Duration); + _jsRuntime.Verify(js => js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == expectedUrl)), Times.Once); + } + + [TestMethod] + public async Task SetPropertiesAsync_WithBrowserFile_ShouldSetPropertiesAsync() + { + // Arrange + var objectUrl = "blob:123"; + var filename = "song.mp3"; + var inputId = "inputId"; + var browserFile = new Mock(); + browserFile.SetupGet(b => b.ContentType).Returns("audio/mpeg"); + browserFile.SetupGet(b => b.Name).Returns(filename); + + var codec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"); + _fileInputManager.Setup(f => f.GetAudioCodec("audio/mpeg", filename)).Returns(codec); + _fileInputManager.Setup(f => f.GetObjectUrlAsync(inputId)).ReturnsAsync(objectUrl); + _jsRuntime.Setup(js => js.InvokeAsync("getAudioDurationFromFile", It.IsAny())).Returns(new ValueTask(90.0)); + + var audiofile = new Audiofile(); + + // Act + await _audiofileManager.SetPropertiesAsync(audiofile, browserFile.Object, inputId); + + // Assert + Assert.AreEqual(codec, audiofile.AudioCodec); + Assert.AreEqual(filename, audiofile.Name); + Assert.AreEqual(objectUrl, audiofile.ObjectURL); + Assert.AreEqual(TimeSpan.FromSeconds(90), audiofile.Duration); + + _fileInputManager.Verify(f => f.GetAudioCodec("audio/mpeg", filename), Times.Once); + _fileInputManager.Verify(f => f.GetObjectUrlAsync(inputId), Times.Once); + _jsRuntime.Verify(js => js.InvokeAsync("getAudioDurationFromFile", It.Is(o => o[0] as string == audiofile.ObjectURL)), Times.Once); + } + + [TestMethod] + public async Task SetProperty_AudiofileWithDuration_SetsLastTrackEndAlsoAsync() + { + // Arrange + var objectUrl = "blob:123"; + var filename = "song.mp3"; + var inputId = "inputId"; + var browserFile = new Mock(); + browserFile.SetupGet(b => b.ContentType).Returns("audio/mpeg"); + browserFile.SetupGet(b => b.Name).Returns(filename); + + var codec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"); + _fileInputManager.Setup(f => f.GetAudioCodec("audio/mpeg", filename)).Returns(codec); + _fileInputManager.Setup(f => f.GetObjectUrlAsync(inputId)).ReturnsAsync(objectUrl); + _jsRuntime.Setup(js => js.InvokeAsync("getAudioDurationFromFile", It.IsAny())).Returns(new ValueTask(90.0)); + var track1 = new Track() + { + Position = 1, + Begin = TimeSpan.FromSeconds(0), + End = TimeSpan.FromSeconds(30) + }; + var track2 = new Track() + { + Position = 2, + Begin = track1.End + }; + var audiofile = new Audiofile() + { + Tracks = [track1, track2] + }; + + // Act + await _audiofileManager.SetPropertiesAsync(audiofile, browserFile.Object, inputId); + + // Assert + Assert.AreEqual(codec, audiofile.AudioCodec); + Assert.AreEqual(filename, audiofile.Name); + Assert.AreEqual(objectUrl, audiofile.ObjectURL); + Assert.AreEqual(TimeSpan.FromSeconds(90), audiofile.Duration); + Assert.AreEqual(TimeSpan.FromSeconds(90), track2.End); + + _fileInputManager.Verify(f => f.GetAudioCodec("audio/mpeg", filename), Times.Once); + _fileInputManager.Verify(f => f.GetObjectUrlAsync(inputId), Times.Once); + _jsRuntime.Verify(js => js.InvokeAsync("getAudioDurationFromFile", It.Is(o => o[0] as string == audiofile.ObjectURL)), Times.Once); + } + + [TestMethod] + public async Task SetPropertiesAsync_NullBrowserFile_WithExistingObjectUrl_ShouldRevokeObjectUrl() + { + // Arrange + var audiofile = new Audiofile + { + ObjectURL = "just a test" + }; + var expectedUrl = audiofile.ObjectURL; + + // Act + await _audiofileManager.SetPropertiesAsync(audiofile, null, string.Empty); + + // Assert + _jsRuntime.Verify(js =>js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == expectedUrl)),Times.Once); + Assert.IsNull(audiofile.ObjectURL); + } + + [TestMethod] + public void SetProperty_ChangedName_UpdatesPropertyAndAddsTrace() + { + // Arrange + var audiofile = new Audiofile + { + Name = "oldname.mp3" + }; + + // Act + _audiofileManager.SetProperty(audiofile, x => x.Name, "newname.mp3"); + + // Assert + Assert.AreEqual("newname.mp3", audiofile.Name); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue!.Equals("oldname.mp3") && y.TraceableChange.PropertyName == nameof(Audiofile.Name))), Times.Once); + } + + [TestMethod] + public void SetProperty_EqualValue_DoesntChangeProperty() + { + // Arrange + var audiofile = new Audiofile + { + Name = "oldname.mp3" + }; + + // Act + _audiofileManager.SetProperty(audiofile, x => x.Name, "oldname.mp3"); + + // Assert + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue!.Equals("oldname.mp3") && y.TraceableChange.PropertyName == nameof(Audiofile.Name))), Times.Never); + } + + [TestMethod] + public void SetProperty_DurationChanged_SetsLastTrackEndAlso() + { + // Arrange + var track1 = new Track() + { + Position = 1, + Begin = TimeSpan.FromSeconds(0), + End = TimeSpan.FromSeconds(30) + }; + var track2 = new Track() + { + Position = 2, + Begin = track1.End + }; + var audiofile = new Audiofile + { + Tracks = [track1, track2], + }; + var duration = new TimeSpan(0, 3, 37, 12); + + // Act + _audiofileManager.SetProperty(audiofile, x => x.Duration, duration); + + // Assert + Assert.AreEqual(duration, audiofile.Duration); + Assert.AreEqual(duration, track2.End); + } + + [TestMethod] + public void AddTrack_FirstTrack_AddsNewTrackWithCalulatedTrackProperties() + { + // Arrange + var duration = new TimeSpan(0, 27, 56); + var audiofile = new Audiofile("Audio.mp3", nameof(AddTrack_FirstTrack_AddsNewTrackWithCalulatedTrackProperties), Audiofile.AudioCodecs.First(), duration); + var cuesheet = new Cuesheet() + { + Audiofiles = [audiofile] + }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + var track = new Track(); + // Act + _audiofileManager.AddTrack(audiofile, track); + // Assert + Assert.HasCount(1, audiofile.Tracks); + Assert.AreEqual((ushort)1, audiofile.Tracks.First().Position); + Assert.AreEqual(TimeSpan.Zero, audiofile.Tracks.First().Begin); + Assert.AreEqual(duration, audiofile.Tracks.First().End); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && ((ICollection)y.TraceableChange.PreviousValue!).Count == 0 && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Once); + } + + [TestMethod] + public void AddTrack_AddToPreviousTracks_AddsNewTrackWithCalulatedTrackProperties() + { + // Arrange + var duration = new TimeSpan(0, 27, 56); + var tracks = new List + { + new() + { + Position = 1, + Begin = TimeSpan.Zero, + End = duration, + IsLinkedToPreviousTrack = true + } + }; + var audiofile = new Audiofile("Audio.mp3", nameof(AddTrack_AddToPreviousTracks_AddsNewTrackWithCalulatedTrackProperties), Audiofile.AudioCodecs.First(), duration) + { + Tracks = tracks + }; + var cuesheet = new Cuesheet() + { + Audiofiles = [audiofile] + }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + var track = new Track() + { + IsLinkedToPreviousTrack = true + }; + // Act + _audiofileManager.AddTrack(audiofile, track); + // Assert + Assert.HasCount(2, audiofile.Tracks); + Assert.AreEqual((ushort)2, audiofile.Tracks.Last().Position); + Assert.IsFalse(audiofile.Tracks.First().End.HasValue); + Assert.AreEqual(duration, audiofile.Tracks.Last().End); + Assert.AreEqual(cuesheet, track.Cuesheet); + Assert.AreEqual(audiofile, track.Audiofile); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue == tracks && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = false, Times.Once); + } + + [TestMethod] + public void AddTrack_DuringRecording_AddsNewTrackWithCalulatedTrackProperties() + { + // Arrange + var tracks = new List + { + new() + { + Position = 1, + Begin = TimeSpan.Zero, + IsLinkedToPreviousTrack = true + } + }; + var audiofile = new Audiofile() + { + Tracks = tracks + }; + var cuesheet = new Cuesheet() + { + RecordingStart = DateTime.UtcNow, + Audiofiles = [audiofile] + }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + var track = new Track() + { + IsLinkedToPreviousTrack = true + }; + // Act + _audiofileManager.AddTrack(audiofile, track); + // Assert + Assert.HasCount(2, audiofile.Tracks); + Assert.AreEqual((ushort)2, audiofile.Tracks.Last().Position); + Assert.IsNotNull(audiofile.Tracks.First().End); + Assert.IsNotNull(audiofile.Tracks.Last().Begin); + Assert.AreEqual(cuesheet, track.Cuesheet); + Assert.AreEqual(audiofile, track.Audiofile); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue == tracks && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = false, Times.Once); + } + + [TestMethod] + public void AddTrack_FirstTrackImporting_AddsNewTrackWithCalulatedTrackProperties() + { + // Arrange + var duration = new TimeSpan(0, 27, 56); + var audiofile = new Audiofile("Audio.mp3", nameof(AddTrack_FirstTrackImporting_AddsNewTrackWithCalulatedTrackProperties), Audiofile.AudioCodecs.First(), duration); + var importCuesheet = new Cuesheet() + { + Audiofiles = [audiofile] + }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(importCuesheet); + var track = new Track(); + // Act + _audiofileManager.AddTrack(audiofile, track); + // Assert + Assert.HasCount(1, audiofile.Tracks); + Assert.AreEqual((ushort)1, audiofile.Tracks.First().Position); + Assert.AreEqual(TimeSpan.Zero, audiofile.Tracks.First().Begin); + Assert.AreEqual(duration, audiofile.Tracks.First().End); + Assert.AreEqual(importCuesheet, track.Cuesheet); + Assert.AreEqual(audiofile, track.Audiofile); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && ((ICollection) y.TraceableChange.PreviousValue!).Count == 0 && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = false, Times.Once); + } + + [TestMethod] + public void RemoveTracks_WithAttachedTracks_RemovesTracksAndRecalculatesRemainingTrackProperties() + { + // Arrange + var track1 = new Track() + { + Position = 1, + Begin = TimeSpan.Zero, + End = new TimeSpan(0, 3, 12), + IsLinkedToPreviousTrack = true + }; + var track2 = new Track() + { + Position = 2, + Begin = new TimeSpan(0, 3, 12), + End = new TimeSpan(0, 7, 34), + IsLinkedToPreviousTrack = true + }; + var track3 = new Track() + { + Position = 3, + Begin = new TimeSpan(0, 7, 34), + End = new TimeSpan(0, 10, 4), + IsLinkedToPreviousTrack = true + }; + var track4 = new Track() + { + Position = 4, + Begin = new TimeSpan(0, 10, 4), + End = new TimeSpan(0, 14, 54), + IsLinkedToPreviousTrack = true + }; + var track5 = new Track() + { + Position = 5, + Begin = new TimeSpan(0, 14, 54), + IsLinkedToPreviousTrack = true + }; + var previousValue = new List() { track1, track2, track3, track4, track5 }; + var duration = new TimeSpan(0, 19, 38); + var audiofile = new Audiofile("Audio.mp3", nameof(RemoveTracks_WithAttachedTracks_RemovesTracksAndRecalculatesRemainingTrackProperties), Audiofile.AudioCodecs.First(), duration) + { + Tracks = previousValue + }; + var cuesheet = new Cuesheet() + { + Audiofiles = [audiofile] + }; + track1.Cuesheet = cuesheet; + track2.Cuesheet = cuesheet; + track3.Cuesheet = cuesheet; + track4.Cuesheet = cuesheet; + track5.Cuesheet = cuesheet; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + // Act + _audiofileManager.RemoveTracks(audiofile, [track2, track4]); + // Assert + Assert.HasCount(3, audiofile.Tracks); + Assert.Contains(track1, audiofile.Tracks); + Assert.Contains(track3, audiofile.Tracks); + Assert.Contains(track5, audiofile.Tracks); + Assert.AreEqual((ushort)1, track1.Position); + Assert.AreEqual(TimeSpan.Zero, track1.Begin); + Assert.AreEqual(track1.End, track3.Begin); + Assert.AreEqual((ushort)2, track3.Position); + Assert.AreEqual(track5.Begin, track3.End); + Assert.AreEqual((ushort)3, track5.Position); + Assert.AreEqual(duration, track5.End); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue == previousValue && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = false, Times.Once); + } + + [TestMethod] + public void RemoveTracks_DuringImport_RemovesTracksAndRecalculatesRemainingTrackProperties() + { + // Arrange + var track1 = new Track() + { + Position = 1, + Begin = TimeSpan.Zero, + End = new TimeSpan(0, 3, 12), + IsLinkedToPreviousTrack = true + }; + var track2 = new Track() + { + Position = 2, + Begin = new TimeSpan(0, 3, 12), + End = new TimeSpan(0, 7, 34), + IsLinkedToPreviousTrack = true + }; + var track3 = new Track() + { + Position = 3, + Begin = new TimeSpan(0, 7, 34), + End = new TimeSpan(0, 10, 4), + IsLinkedToPreviousTrack = true + }; + var track4 = new Track() + { + Position = 4, + Begin = new TimeSpan(0, 10, 4), + End = new TimeSpan(0, 14, 54), + IsLinkedToPreviousTrack = true + }; + var track5 = new Track() + { + Position = 5, + Begin = new TimeSpan(0, 14, 54), + IsLinkedToPreviousTrack = true + }; + var previousValue = new List() { track1, track2, track3, track4, track5 }; + var duration = new TimeSpan(0, 19, 38); + var audiofile = new Audiofile("Audio.mp3", nameof(RemoveTracks_DuringImport_RemovesTracksAndRecalculatesRemainingTrackProperties), Audiofile.AudioCodecs.First(), duration) + { + Tracks = previousValue + }; + var importCuesheet = new Cuesheet() + { + Audiofiles = [audiofile] + }; + track1.Cuesheet = importCuesheet; + track2.Cuesheet = importCuesheet; + track3.Cuesheet = importCuesheet; + track4.Cuesheet = importCuesheet; + track5.Cuesheet = importCuesheet; + track1.Audiofile = audiofile; + track2.Audiofile = audiofile; + track3.Audiofile = audiofile; + track4.Audiofile = audiofile; + track5.Audiofile = audiofile; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(importCuesheet); + // Act + _audiofileManager.RemoveTracks(audiofile, [track2, track4]); + // Assert + Assert.HasCount(3, audiofile.Tracks); + Assert.Contains(track1, audiofile.Tracks); + Assert.Contains(track3, audiofile.Tracks); + Assert.Contains(track5, audiofile.Tracks); + Assert.AreEqual((ushort)1, track1.Position); + Assert.AreEqual(TimeSpan.Zero, track1.Begin); + Assert.AreEqual(track1.End, track3.Begin); + Assert.AreEqual((ushort)2, track3.Position); + Assert.AreEqual(track5.Begin, track3.End); + Assert.AreEqual((ushort)3, track5.Position); + Assert.AreEqual(duration, track5.End); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue == previousValue && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = false, Times.Once); + } + } +} diff --git a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/CuesheetManagerTests.cs b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/CuesheetManagerTests.cs index f3496ee8..48b0dc0b 100644 --- a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/CuesheetManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/CuesheetManagerTests.cs @@ -19,6 +19,7 @@ using AudioCuesheetEditor.Services; using AudioCuesheetEditor.Services.AudioCuesheet; using AudioCuesheetEditor.Services.UI; +using Microsoft.JSInterop; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; @@ -36,12 +37,16 @@ public class CuesheetManagerTests private readonly Mock _traceChangeManager; private readonly Mock _sessionStateContainer; private readonly Mock _trackManager; + private readonly Mock _audiofileManager; + private readonly Mock _jsRuntime; public CuesheetManagerTests() { _traceChangeManager = new(); _sessionStateContainer = new(); + _audiofileManager = new(); _trackManager = new(); + _jsRuntime = new Mock(); _trackManager.Setup(x => x.SetProperty(It.IsAny(),It.IsAny>>(),It.IsAny())) .Callback((Track track, LambdaExpression propExpr, object value) => { @@ -51,26 +56,7 @@ public CuesheetManagerTests() propertyInfo.SetValue(track, value); } }); - _trackManager.Setup(x => x.GetPreviousLinkedTrack(It.IsAny())).Returns(delegate (Track track) - { - Track? previousLinkedTrack = null; - if (track.IsLinkedToPreviousTrack) - { - if (track.Position.HasValue) - { - previousLinkedTrack = track.Cuesheet?.Tracks.LastOrDefault(x => x.Position == track.Position - 1 && Equals(x, track) == false); - } - else - { - if (track.Begin.HasValue) - { - previousLinkedTrack = track.Cuesheet?.Tracks.OrderBy(x => x.End).LastOrDefault(x => x.End <= track.Begin); - } - } - } - return previousLinkedTrack; - }); - _cuesheetManager = new(_traceChangeManager.Object, _sessionStateContainer.Object, _trackManager.Object); + _cuesheetManager = new(_traceChangeManager.Object, _sessionStateContainer.Object, _trackManager.Object, _audiofileManager.Object, _jsRuntime.Object); } [TestMethod] @@ -102,26 +88,32 @@ public void SetProperty_EqualValue_DoesntChangeProperty() } [TestMethod] - public void SetProperty_AudiofileWithDuration_SetsLastTrackEndAlso() + public void SetProperty_DeletedAudiofile_RevokesObjectURLs() { // Arrange - var track = new Track() + var audiofile1 = new Audiofile() + { + Name = "Test 1.mp3", + AudioCodec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"), + ObjectURL = "objecturl 1" + }; + var audiofile2 = new Audiofile() { - Position = 1 + Name = "Test 2.mp3", + AudioCodec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"), + ObjectURL = "objecturl 2" }; var cuesheet = new Cuesheet() { - Tracks = [track] + Audiofiles = [audiofile1, audiofile2] }; - track.Cuesheet = cuesheet; _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); - var duration = new TimeSpan(0, 5, 23); - var audiofile = new Audiofile("Test.mp3", nameof(SetProperty_AudiofileWithDuration_SetsLastTrackEndAlso), Audiofile.AudioCodecs.First(), duration); + var files = new List(cuesheet.Audiofiles); + files.Remove(audiofile2); // Act - _cuesheetManager.SetProperty(x => x.Audiofile, audiofile); + _cuesheetManager.SetProperty(x => x.Audiofiles, files); // Assert - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet && y.TraceableChange.PreviousValue == null && y.TraceableChange.PropertyName == nameof(Cuesheet.Audiofile))), Times.Once); - Assert.AreEqual(duration, track.End); + _jsRuntime.Verify(x => x.InvokeAsync("revokeAudioObjectURL", It.Is(args => args.Length == 1 && args[0].Equals(audiofile2.ObjectURL))), Times.Once()); } [TestMethod] @@ -142,8 +134,12 @@ public void IsRecordingPossible_WithTracks_ReturnsFailure() // Arrange var cuesheet = new Cuesheet() { - Tracks = [ - new() + Audiofiles = [ + new() { + Tracks = [ + new() + ] + } ] }; _sessionStateContainer.SetupProperty(x => x.Cuesheet, cuesheet); @@ -154,6 +150,26 @@ public void IsRecordingPossible_WithTracks_ReturnsFailure() Assert.AreEqual("Cuesheet already contains tracks!", result.Error!.Message); } + [TestMethod] + public void IsRecordingPossible_RecordingAlreadyRunning_ReturnsFailure() + { + // Arrange + var cuesheet = new Cuesheet + { + Audiofiles = [ + new() { + } + ], + RecordingStart = DateTime.UtcNow + }; + _sessionStateContainer.SetupProperty(x => x.Cuesheet, cuesheet); + // Act + var result = _cuesheetManager.IsRecordingPossible; + // Assert + Assert.IsFalse(result.IsSuccess); + Assert.AreEqual("Record is already running!", result.Error!.Message); + } + [TestMethod] public void StartRecording_RecordingPossible_ReturnsSuccess() { @@ -171,6 +187,7 @@ public void StartRecording_RecordingPossible_ReturnsSuccess() Assert.IsTrue(result.IsSuccess); Assert.IsTrue(isRecordingChanged); Assert.IsNotNull(cuesheet.RecordingStart); + Assert.HasCount(1, cuesheet.Audiofiles); } [TestMethod] @@ -202,15 +219,19 @@ public void StopRecording_WithActiveRecording_StopsRecordAndSetsTrackDetails() var cuesheet = new Cuesheet() { RecordingStart = DateTime.UtcNow.AddDays(-1), - Tracks = [ + Audiofiles = [ new() { - Position = 1, - Begin = TimeSpan.Zero, - End = new TimeSpan(0, 3, 12) - }, - new() { - Position = 2, - Begin = new TimeSpan(0, 3, 12) + Tracks = [ + new() { + Position = 1, + Begin = TimeSpan.Zero, + End = new TimeSpan(0, 3, 12) + }, + new() { + Position = 2, + Begin = new TimeSpan(0, 3, 12) + } + ] } ] }; @@ -226,7 +247,7 @@ public void StopRecording_WithActiveRecording_StopsRecordAndSetsTrackDetails() Assert.IsFalse(cuesheet.IsRecording); Assert.IsNull(cuesheet.RecordingStart); Assert.IsTrue(isRecordingChanged); - Assert.IsNotNull(cuesheet.Tracks.Last().End); + Assert.IsNotNull(cuesheet.Audiofiles.First().Tracks.Last().End); } [TestMethod] @@ -235,15 +256,19 @@ public void StopRecording_WithoutActiveRecording_ChangesNothing() // Arrange var cuesheet = new Cuesheet() { - Tracks = [ - new() { - Position = 1, - Begin = TimeSpan.Zero, - End = new TimeSpan(0, 3, 12) - }, + Audiofiles = [ new() { - Position = 2, - Begin = new TimeSpan(0, 3, 12) + Tracks = [ + new() { + Position = 1, + Begin = TimeSpan.Zero, + End = new TimeSpan(0, 3, 12) + }, + new() { + Position = 2, + Begin = new TimeSpan(0, 3, 12) + } + ] } ] }; @@ -257,113 +282,11 @@ public void StopRecording_WithoutActiveRecording_ChangesNothing() _cuesheetManager.StopRecording(); // Assert Assert.IsFalse(isRecordingChanged); - Assert.IsNull(cuesheet.Tracks.Last().End); - } - - [TestMethod] - public void AddTrack_FirstTrack_AddsNewTrackWithCalulatedTrackProperties() - { - // Arrange - var duration = new TimeSpan(0, 27, 56); - var cuesheet = new Cuesheet() - { - Audiofile = new("Audio.mp3", nameof(AddTrack_FirstTrack_AddsNewTrackWithCalulatedTrackProperties), Audiofile.AudioCodecs.First(), duration) - }; - _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); - var track = new Track(); - // Act - _cuesheetManager.AddTrack(track); - // Assert - Assert.HasCount(1, cuesheet.Tracks); - Assert.AreEqual((ushort)1, cuesheet.Tracks.First().Position); - Assert.AreEqual(TimeSpan.Zero, cuesheet.Tracks.First().Begin); - Assert.AreEqual(duration, cuesheet.Tracks.First().End); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet && y.TraceableChange.PreviousValue == Enumerable.Empty() && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Once); - } - - [TestMethod] - public void AddTrack_AddToPreviousTracks_AddsNewTrackWithCalulatedTrackProperties() - { - // Arrange - var duration = new TimeSpan(0, 27, 56); - var tracks = new List - { - new() - { - Position = 1, - Begin = TimeSpan.Zero, - End = new TimeSpan(3, 12, 0) - } - }; - var cuesheet = new Cuesheet() - { - Tracks = tracks, - Audiofile = new("Audio.mp3", nameof(AddTrack_AddToPreviousTracks_AddsNewTrackWithCalulatedTrackProperties), Audiofile.AudioCodecs.First(), duration) - }; - _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); - var track = new Track(); - // Act - _cuesheetManager.AddTrack(track); - // Assert - Assert.HasCount(2, cuesheet.Tracks); - Assert.AreEqual((ushort)2, cuesheet.Tracks.Last().Position); - Assert.AreEqual(cuesheet.Tracks.First().End, cuesheet.Tracks.Last().Begin); - Assert.AreEqual(duration, cuesheet.Tracks.Last().End); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet && y.TraceableChange.PreviousValue == tracks && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Once); + Assert.IsNull(cuesheet.Audiofiles.First().Tracks.Last().End); } [TestMethod] - public void AddTrack_DuringRecording_AddsNewTrackWithCalulatedTrackProperties() - { - // Arrange - var tracks = new List - { - new() - { - Position = 1, - Begin = TimeSpan.Zero - } - }; - var cuesheet = new Cuesheet() - { - RecordingStart = DateTime.UtcNow, - Tracks = tracks - }; - _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); - var track = new Track(); - // Act - _cuesheetManager.AddTrack(track); - // Assert - Assert.HasCount(2, cuesheet.Tracks); - Assert.AreEqual((ushort)2, cuesheet.Tracks.Last().Position); - Assert.IsNotNull(cuesheet.Tracks.First().End); - Assert.IsNotNull(cuesheet.Tracks.Last().Begin); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet && y.TraceableChange.PreviousValue == tracks && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Once); - } - - [TestMethod] - public void AddTrack_FirstTrackImporting_AddsNewTrackWithCalulatedTrackProperties() - { - // Arrange - var duration = new TimeSpan(0, 27, 56); - var importCuesheet = new Cuesheet() - { - Audiofile = new("Audio.mp3", nameof(AddTrack_FirstTrackImporting_AddsNewTrackWithCalulatedTrackProperties), Audiofile.AudioCodecs.First(), duration) - }; - _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(importCuesheet); - var track = new Track(); - // Act - _cuesheetManager.AddTrack(track); - // Assert - Assert.HasCount(1, importCuesheet.Tracks); - Assert.AreEqual((ushort)1, importCuesheet.Tracks.First().Position); - Assert.AreEqual(TimeSpan.Zero, importCuesheet.Tracks.First().Begin); - Assert.AreEqual(duration, importCuesheet.Tracks.First().End); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == importCuesheet && y.TraceableChange.PreviousValue == Enumerable.Empty() && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Once); - } - - [TestMethod] - public void RemoveTracks_WithAttachedTracks_RemovesTracksAndRecalculatesRemainingTrackProperties() + public void IsMoveUpPossible_TracksAbove_ReturnsTrue() { // Arrange var track1 = new Track() @@ -387,51 +310,25 @@ public void RemoveTracks_WithAttachedTracks_RemovesTracksAndRecalculatesRemainin End = new TimeSpan(0, 10, 4), IsLinkedToPreviousTrack = true }; - var track4 = new Track() - { - Position = 4, - Begin = new TimeSpan(0, 10, 4), - End = new TimeSpan(0, 14, 54), - IsLinkedToPreviousTrack = true - }; - var track5 = new Track() - { - Position = 5, - Begin = new TimeSpan(0, 14, 54), - IsLinkedToPreviousTrack = true - }; - var previousValue = new List() { track1, track2, track3, track4, track5 }; - var duration = new TimeSpan(0, 19, 38); var cuesheet = new Cuesheet() { - Tracks = previousValue, - Audiofile = new("Audio.mp3", nameof(RemoveTracks_WithAttachedTracks_RemovesTracksAndRecalculatesRemainingTrackProperties), Audiofile.AudioCodecs.First(), duration) + Audiofiles = [ + new() { + Tracks = [track3, track2, track1] + } + ], }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; track3.Cuesheet = cuesheet; - track4.Cuesheet = cuesheet; - track5.Cuesheet = cuesheet; - _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); // Act - _cuesheetManager.RemoveTracks([track2, track4]); + var result = _cuesheetManager.IsMoveUpPossible([track2, track3]); // Assert - Assert.HasCount(3, cuesheet.Tracks); - Assert.IsTrue(cuesheet.Tracks.Contains(track1)); - Assert.IsTrue(cuesheet.Tracks.Contains(track3)); - Assert.IsTrue(cuesheet.Tracks.Contains(track5)); - Assert.AreEqual((ushort)1, track1.Position); - Assert.AreEqual(TimeSpan.Zero, track1.Begin); - Assert.AreEqual(track1.End, track3.Begin); - Assert.AreEqual((ushort)2, track3.Position); - Assert.AreEqual(track5.Begin, track3.End); - Assert.AreEqual((ushort)3, track5.Position); - Assert.AreEqual(duration, track5.End); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet && y.TraceableChange.PreviousValue == previousValue && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Once); + Assert.IsTrue(result); } [TestMethod] - public void RemoveTracks_DuringImport_RemovesTracksAndRecalculatesRemainingTrackProperties() + public void IsMoveUpPossible_NoTracksAbove_ReturnsFalse() { // Arrange var track1 = new Track() @@ -455,51 +352,81 @@ public void RemoveTracks_DuringImport_RemovesTracksAndRecalculatesRemainingTrack End = new TimeSpan(0, 10, 4), IsLinkedToPreviousTrack = true }; - var track4 = new Track() + var cuesheet = new Cuesheet() { - Position = 4, - Begin = new TimeSpan(0, 10, 4), - End = new TimeSpan(0, 14, 54), - IsLinkedToPreviousTrack = true + Audiofiles = [ + new() { + Tracks = [track2, track1, track3] + } + ], }; - var track5 = new Track() + track1.Cuesheet = cuesheet; + track2.Cuesheet = cuesheet; + track3.Cuesheet = cuesheet; + // Act + var result = _cuesheetManager.IsMoveUpPossible([track1, track2]); + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void IsMoveUpPossible_EmptyTracksCollection_ReturnsFalse() + { + // Arrange + // Act + var result = _cuesheetManager.IsMoveUpPossible(new HashSet()); + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void IsMoveUpPossible_AudiofilesAbove_ReturnsTrue() + { + // Arrange + var file1 = new Audiofile(); + var file2 = new Audiofile(); + var file3 = new Audiofile(); + var cuesheet = new Cuesheet() { - Position = 5, - Begin = new TimeSpan(0, 14, 54), - IsLinkedToPreviousTrack = true + Audiofiles = [file1, file2, file3], }; - var previousValue = new List() { track1, track2, track3, track4, track5 }; - var duration = new TimeSpan(0, 19, 38); - var importCuesheet = new Cuesheet() + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + // Act + var result = _cuesheetManager.IsMoveUpPossible([file2, file3]); + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void IsMoveUpPossible_NoAudiofilesAbove_ReturnsFalse() + { + // Arrange + var file1 = new Audiofile(); + var file2 = new Audiofile(); + var file3 = new Audiofile(); + var cuesheet = new Cuesheet() { - Tracks = previousValue, - Audiofile = new("Audio.mp3", nameof(RemoveTracks_DuringImport_RemovesTracksAndRecalculatesRemainingTrackProperties), Audiofile.AudioCodecs.First(), duration) + Audiofiles = [file1, file2, file3], }; - track1.Cuesheet = importCuesheet; - track2.Cuesheet = importCuesheet; - track3.Cuesheet = importCuesheet; - track4.Cuesheet = importCuesheet; - track5.Cuesheet = importCuesheet; - _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(importCuesheet); + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); // Act - _cuesheetManager.RemoveTracks([track2, track4]); + var result = _cuesheetManager.IsMoveUpPossible([file1, file2]); // Assert - Assert.HasCount(3, importCuesheet.Tracks); - Assert.IsTrue(importCuesheet.Tracks.Contains(track1)); - Assert.IsTrue(importCuesheet.Tracks.Contains(track3)); - Assert.IsTrue(importCuesheet.Tracks.Contains(track5)); - Assert.AreEqual((ushort)1, track1.Position); - Assert.AreEqual(TimeSpan.Zero, track1.Begin); - Assert.AreEqual(track1.End, track3.Begin); - Assert.AreEqual((ushort)2, track3.Position); - Assert.AreEqual(track5.Begin, track3.End); - Assert.AreEqual((ushort)3, track5.Position); - Assert.AreEqual(duration, track5.End); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == importCuesheet && y.TraceableChange.PreviousValue == previousValue && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Once); + Assert.IsFalse(result); + } + + [TestMethod] + public void IsMoveUpPossible_EmptyAudiofileCollection_ReturnsFalse() + { + // Arrange + // Act + var result = _cuesheetManager.IsMoveUpPossible(new HashSet()); + // Assert + Assert.IsFalse(result); } [TestMethod] - public void IsMoveTracksUpPossible_TracksAbove_ReturnsTrue() + public void IsMoveDownPossible_TracksBelow_ReturnsTrue() { // Arrange var track1 = new Track() @@ -525,19 +452,24 @@ public void IsMoveTracksUpPossible_TracksAbove_ReturnsTrue() }; var cuesheet = new Cuesheet() { - Tracks = [track3, track2, track1] + Audiofiles = [ + new() { + Tracks = [track3, track2, track1] + } + ], }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; track3.Cuesheet = cuesheet; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); // Act - var result = _cuesheetManager.IsMoveTracksUpPossible([track2, track3]); + var result = _cuesheetManager.IsMoveDownPossible([track2, track1]); // Assert Assert.IsTrue(result); } [TestMethod] - public void IsMoveTracksUpPossible_NoTracksAbove_ReturnsFalse() + public void IsMoveDownPossible_NoTracksBelow_ReturnsFalse() { // Arrange var track1 = new Track() @@ -563,117 +495,263 @@ public void IsMoveTracksUpPossible_NoTracksAbove_ReturnsFalse() }; var cuesheet = new Cuesheet() { - Tracks = [track2, track1, track3] + Audiofiles = [ + new() { + Tracks = [track3, track2, track1] + } + ], }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; track3.Cuesheet = cuesheet; // Act - var result = _cuesheetManager.IsMoveTracksUpPossible([track1, track2]); + var result = _cuesheetManager.IsMoveDownPossible([track3, track2]); + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void IsMoveDownPossible_EmptyTracksCollection_ReturnsFalse() + { + // Arrange + // Act + var result = _cuesheetManager.IsMoveDownPossible(new HashSet()); + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void IsMoveDownPossible_AudiofilesBelow_ReturnsTrue() + { + // Arrange + var file1 = new Audiofile(); + var file2 = new Audiofile(); + var file3 = new Audiofile(); + var cuesheet = new Cuesheet() + { + Audiofiles = [file1, file2, file3], + }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + // Act + var result = _cuesheetManager.IsMoveDownPossible([file2, file1]); + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void IsMoveDownPossible_NoAudiofilesBelow_ReturnsFalse() + { + // Arrange + var file1 = new Audiofile(); + var file2 = new Audiofile(); + var file3 = new Audiofile(); + var cuesheet = new Cuesheet() + { + Audiofiles = [file1, file2, file3], + }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + // Act + var result = _cuesheetManager.IsMoveDownPossible([file3, file2]); // Assert Assert.IsFalse(result); } [TestMethod] - public void IsMoveTracksUpPossible_EmptyCollection_ReturnsFalse() + public void IsMoveDownPossible_EmptyAudiofileCollection_ReturnsFalse() { // Arrange // Act - var result = _cuesheetManager.IsMoveTracksUpPossible([]); + var result = _cuesheetManager.IsMoveDownPossible(new HashSet()); // Assert Assert.IsFalse(result); } [TestMethod] - public void IsMoveTracksDownPossible_TracksBelow_ReturnsTrue() + public void MoveUp_TracksSwitchingAudiofile_ReturnsSuccess() { // Arrange + var track1End = new TimeSpan(0, 3, 12); var track1 = new Track() { Position = 1, Begin = TimeSpan.Zero, - End = new TimeSpan(0, 3, 12), + End = track1End, IsLinkedToPreviousTrack = true }; + var track2End = new TimeSpan(0, 7, 34); var track2 = new Track() { Position = 2, - Begin = new TimeSpan(0, 3, 12), - End = new TimeSpan(0, 7, 34), + Begin = track1End, + End = track2End, IsLinkedToPreviousTrack = true }; var track3 = new Track() { Position = 3, - Begin = new TimeSpan(0, 7, 34), + Begin = track2End, End = new TimeSpan(0, 10, 4), IsLinkedToPreviousTrack = true }; var cuesheet = new Cuesheet() { - Tracks = [track3, track2, track1] + Audiofiles = [ + new() { + Tracks = [track1] + }, + new() { + Tracks = [track3, track2] + } + ], }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; track3.Cuesheet = cuesheet; + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.Last(); + track3.Audiofile = cuesheet.Audiofiles.Last(); _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); // Act - var result = _cuesheetManager.IsMoveTracksDownPossible([track2, track1]); + var result = _cuesheetManager.MoveUp([track2, track3]); // Assert - Assert.IsTrue(result); + Assert.IsTrue(result.IsSuccess); + Assert.AreEqual((ushort?)1, track1.Position); + Assert.AreEqual(TimeSpan.Zero, track1.Begin); + Assert.AreEqual(track1End, track1.End); + Assert.AreEqual(cuesheet.Audiofiles.First(), track1.Audiofile); + Assert.AreEqual((ushort?)2, track2.Position); + Assert.AreEqual(track1End, track2.Begin); + Assert.AreEqual(cuesheet.Audiofiles.First(), track2.Audiofile); + Assert.AreEqual(track2End, track2.End); + Assert.AreEqual((ushort?)3, track3.Position); + Assert.AreEqual(track2End, track3.Begin); + Assert.AreEqual(cuesheet.Audiofiles.First(), track3.Audiofile); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet.Audiofiles.First() && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Exactly(2)); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Once); } [TestMethod] - public void IsMoveTracksDownPossible_NoTracksBelow_ReturnsFalse() + public void MoveUp_TracksAbove_ReturnsSuccess() { // Arrange + var track1End = new TimeSpan(0, 3, 12); var track1 = new Track() { Position = 1, Begin = TimeSpan.Zero, - End = new TimeSpan(0, 3, 12), + End = track1End, IsLinkedToPreviousTrack = true }; + var track2End = new TimeSpan(0, 7, 34); var track2 = new Track() { Position = 2, - Begin = new TimeSpan(0, 3, 12), - End = new TimeSpan(0, 7, 34), + Begin = track1End, + End = track2End, IsLinkedToPreviousTrack = true }; var track3 = new Track() { Position = 3, - Begin = new TimeSpan(0, 7, 34), + Begin = track2End, End = new TimeSpan(0, 10, 4), IsLinkedToPreviousTrack = true }; var cuesheet = new Cuesheet() { - Tracks = [track2, track1, track3] + Audiofiles = [ + new() { + Tracks = [track3, track2, track1] + } + ], }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; track3.Cuesheet = cuesheet; + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.First(); + track3.Audiofile = cuesheet.Audiofiles.First(); + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); // Act - var result = _cuesheetManager.IsMoveTracksDownPossible([track3, track2]); + var result = _cuesheetManager.MoveUp([track2, track3]); // Assert - Assert.IsFalse(result); + Assert.IsTrue(result.IsSuccess); + Assert.AreEqual((ushort?)1, track2.Position); + Assert.AreEqual(TimeSpan.Zero, track2.Begin); + Assert.AreEqual(track1End, track2.End); + Assert.AreEqual((ushort?)2, track3.Position); + Assert.AreEqual(track1End, track3.Begin); + Assert.AreEqual(track2End, track3.End); + Assert.AreEqual((ushort?)3, track1.Position); + Assert.AreEqual(track2End, track1.Begin); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet.Audiofiles.First() && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Never); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Once); } [TestMethod] - public void IsMoveTracksDownPossible_EmptyCollection_ReturnsFalse() + public void MoveUp_NoTracksAbove_ReturnsFailure() { // Arrange - var cuesheet = new Cuesheet(); + var track1End = new TimeSpan(0, 3, 12); + var track1 = new Track() + { + Position = 1, + Begin = TimeSpan.Zero, + End = track1End, + IsLinkedToPreviousTrack = true + }; + var track2End = new TimeSpan(0, 7, 34); + var track2 = new Track() + { + Position = 2, + Begin = track1End, + End = track2End, + IsLinkedToPreviousTrack = true + }; + var track3 = new Track() + { + Position = 3, + Begin = track2End, + End = new TimeSpan(0, 10, 4), + IsLinkedToPreviousTrack = true + }; + var cuesheet = new Cuesheet() + { + Audiofiles = [ + new() { + Tracks = [track3, track2, track1] + } + ] + }; + track1.Cuesheet = cuesheet; + track2.Cuesheet = cuesheet; + track3.Cuesheet = cuesheet; + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.First(); + track3.Audiofile = cuesheet.Audiofiles.First(); + _sessionStateContainer.Setup(x => x.Cuesheet).Returns(cuesheet); // Act - var result = _cuesheetManager.IsMoveTracksDownPossible([]); + var result = _cuesheetManager.MoveUp([track2, track1]); // Assert - Assert.IsFalse(result); + Assert.IsFalse(result.IsSuccess); + Assert.AreEqual(ErrorType.NotPossible, result.Error!.Type); + Assert.AreEqual((ushort?)1, track1.Position); + Assert.AreEqual(TimeSpan.Zero, track1.Begin); + Assert.AreEqual(track1End, track1.End); + Assert.AreEqual((ushort?)2, track2.Position); + Assert.AreEqual(track1End, track2.Begin); + Assert.AreEqual(track2End, track2.End); + Assert.AreEqual((ushort?)3, track3.Position); + Assert.AreEqual(track2End, track3.Begin); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet.Audiofiles.First() && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Never); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Never); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Never); } [TestMethod] - public void MoveTracksUp_TracksAbove_ReturnsSuccess() + public void MoveUp_NoTracksSelected_ReturnsFailure() { // Arrange var track1End = new TimeSpan(0, 3, 12); @@ -701,31 +779,39 @@ public void MoveTracksUp_TracksAbove_ReturnsSuccess() }; var cuesheet = new Cuesheet() { - Tracks = [track3, track2, track1] + Audiofiles = [ + new() { + Tracks = [track3, track2, track1] + } + ], }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; track3.Cuesheet = cuesheet; - _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.First(); + track3.Audiofile = cuesheet.Audiofiles.First(); + _sessionStateContainer.Setup(x => x.Cuesheet).Returns(cuesheet); // Act - var result = _cuesheetManager.MoveTracksUp([track2, track3]); + var result = _cuesheetManager.MoveUp(new HashSet()); // Assert - Assert.IsTrue(result.IsSuccess); - Assert.AreEqual((ushort?)1, track2.Position); - Assert.AreEqual(TimeSpan.Zero, track2.Begin); - Assert.AreEqual(track1End, track2.End); - Assert.AreEqual((ushort?)2, track3.Position); - Assert.AreEqual(track1End, track3.Begin); - Assert.AreEqual(track2End, track3.End); - Assert.AreEqual((ushort?)3, track1.Position); - Assert.AreEqual(track2End, track1.Begin); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Once); - _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Once); - _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Once); + Assert.IsFalse(result.IsSuccess); + Assert.AreEqual(ErrorType.NotPossible, result.Error!.Type); + Assert.AreEqual((ushort?)1, track1.Position); + Assert.AreEqual(TimeSpan.Zero, track1.Begin); + Assert.AreEqual(track1End, track1.End); + Assert.AreEqual((ushort?)2, track2.Position); + Assert.AreEqual(track1End, track2.Begin); + Assert.AreEqual(track2End, track2.End); + Assert.AreEqual((ushort?)3, track3.Position); + Assert.AreEqual(track2End, track3.Begin); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet.Audiofiles.First() && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Never); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Never); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Never); } [TestMethod] - public void MoveTracksUp_NoTracksAbove_ReturnsFailure() + public void MoveUp_AudiofilesAbove_ReturnsSuccess() { // Arrange var track1End = new TimeSpan(0, 3, 12); @@ -751,34 +837,92 @@ public void MoveTracksUp_NoTracksAbove_ReturnsFailure() End = new TimeSpan(0, 10, 4), IsLinkedToPreviousTrack = true }; + var file1 = new Audiofile("Audiofile 1", $"{Guid.NewGuid()}", Audiofile.AudioCodecWEBM); + var file2 = new Audiofile("Audiofile 2", $"{Guid.NewGuid()}", Audiofile.AudioCodecWEBM) + { + Tracks = [track1, track2] + }; + var file3 = new Audiofile("Audiofile 3", $"{Guid.NewGuid()}", Audiofile.AudioCodecWEBM) + { + Tracks = [track3] + }; var cuesheet = new Cuesheet() { - Tracks = [track3, track2, track1] + Audiofiles = [file1, file2, file3], }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; track3.Cuesheet = cuesheet; - _sessionStateContainer.Setup(x => x.Cuesheet).Returns(cuesheet); + track1.Audiofile = file2; + track2.Audiofile = file2; + track3.Audiofile = file3; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); // Act - var result = _cuesheetManager.MoveTracksUp([track2, track1]); + var result = _cuesheetManager.MoveUp([file2, file3]); // Assert - Assert.IsFalse(result.IsSuccess); - Assert.AreEqual(ErrorType.NotPossible, result.Error!.Type); + Assert.IsTrue(result.IsSuccess); + CollectionAssert.AreEquivalent(new List([file2, file3, file1]), cuesheet.Audiofiles.ToList()); + Assert.IsEmpty(file1.Tracks); Assert.AreEqual((ushort?)1, track1.Position); Assert.AreEqual(TimeSpan.Zero, track1.Begin); Assert.AreEqual(track1End, track1.End); + Assert.AreEqual(file2, track1.Audiofile); Assert.AreEqual((ushort?)2, track2.Position); Assert.AreEqual(track1End, track2.Begin); Assert.AreEqual(track2End, track2.End); + Assert.AreEqual(file2, track2.Audiofile); Assert.AreEqual((ushort?)3, track3.Position); Assert.AreEqual(track2End, track3.Begin); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Never); + Assert.AreEqual(file3, track3.Audiofile); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Once); + } + + [TestMethod] + public void MoveUp_NoAudiofilesAbove_ReturnsFailure() + { + // Arrange + var file1 = new Audiofile(); + var file2 = new Audiofile(); + var file3 = new Audiofile(); + var cuesheet = new Cuesheet() + { + Audiofiles = [file1, file2, file3], + }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + // Act + var result = _cuesheetManager.MoveUp([file1, file2]); + // Assert + Assert.IsFalse(result.IsSuccess); + Assert.AreEqual(ErrorType.NotPossible, result.Error!.Type); _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Never); _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Never); } [TestMethod] - public void MoveTracksUp_NoTracksSelected_ReturnsFailure() + public void MoveUp_NoAudiofilesSelected_ReturnsFailure() + { + // Arrange + var file1 = new Audiofile(); + var file2 = new Audiofile(); + var file3 = new Audiofile(); + var cuesheet = new Cuesheet() + { + Audiofiles = [file1, file2, file3], + }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + // Act + var result = _cuesheetManager.MoveUp(new HashSet()); + // Assert + Assert.IsFalse(result.IsSuccess); + Assert.AreEqual(ErrorType.NotPossible, result.Error!.Type); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Never); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Never); + } + + [TestMethod] + public void MoveDown_TracksSwitchingAudiofile_ReturnsSuccess() { // Arrange var track1End = new TimeSpan(0, 3, 12); @@ -806,32 +950,44 @@ public void MoveTracksUp_NoTracksSelected_ReturnsFailure() }; var cuesheet = new Cuesheet() { - Tracks = [track3, track2, track1] + Audiofiles = [ + new() { + Tracks = [track2, track1] + }, + new() { + Tracks = [track3] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; track3.Cuesheet = cuesheet; - _sessionStateContainer.Setup(x => x.Cuesheet).Returns(cuesheet); + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.First(); + track3.Audiofile = cuesheet.Audiofiles.Last(); + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); // Act - var result = _cuesheetManager.MoveTracksUp([]); + var result = _cuesheetManager.MoveDown([track2, track1]); // Assert - Assert.IsFalse(result.IsSuccess); - Assert.AreEqual(ErrorType.NotPossible, result.Error!.Type); + Assert.IsTrue(result.IsSuccess); Assert.AreEqual((ushort?)1, track1.Position); Assert.AreEqual(TimeSpan.Zero, track1.Begin); Assert.AreEqual(track1End, track1.End); + Assert.AreEqual(cuesheet.Audiofiles.Last(), track1.Audiofile); Assert.AreEqual((ushort?)2, track2.Position); Assert.AreEqual(track1End, track2.Begin); Assert.AreEqual(track2End, track2.End); + Assert.AreEqual(cuesheet.Audiofiles.Last(), track2.Audiofile); Assert.AreEqual((ushort?)3, track3.Position); Assert.AreEqual(track2End, track3.Begin); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Never); - _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Never); - _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Never); + Assert.AreEqual(cuesheet.Audiofiles.Last(), track3.Audiofile); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet.Audiofiles.First() && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Exactly(2)); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Once); } [TestMethod] - public void MoveTracksDown_TracksBelow_ReturnsSucess() + public void MoveDown_TracksBelow_ReturnsSuccess() { // Arrange var track1End = new TimeSpan(0, 3, 12); @@ -859,14 +1015,21 @@ public void MoveTracksDown_TracksBelow_ReturnsSucess() }; var cuesheet = new Cuesheet() { - Tracks = [track3, track2, track1] + Audiofiles = [ + new() { + Tracks = [track3, track2, track1] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; track3.Cuesheet = cuesheet; + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.First(); + track3.Audiofile = cuesheet.Audiofiles.First(); _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); // Act - var result = _cuesheetManager.MoveTracksDown([track2, track1]); + var result = _cuesheetManager.MoveDown([track2, track1]); // Assert Assert.IsTrue(result.IsSuccess); Assert.AreEqual((ushort?)1, track3.Position); @@ -877,13 +1040,13 @@ public void MoveTracksDown_TracksBelow_ReturnsSucess() Assert.AreEqual(track2End, track1.End); Assert.AreEqual((ushort?)3, track2.Position); Assert.AreEqual(track2End, track2.Begin); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Once); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet.Audiofiles.First() && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Never); _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Once); _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Once); } [TestMethod] - public void MoveTracksDown_NoTracksBelow_ReturnsFailure() + public void MoveDown_NoTracksBelow_ReturnsFailure() { // Arrange var track1End = new TimeSpan(0, 3, 12); @@ -911,14 +1074,21 @@ public void MoveTracksDown_NoTracksBelow_ReturnsFailure() }; var cuesheet = new Cuesheet() { - Tracks = [track3, track2, track1] + Audiofiles = [ + new() { + Tracks = [track3, track2, track1] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; track3.Cuesheet = cuesheet; + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.First(); + track3.Audiofile = cuesheet.Audiofiles.First(); _sessionStateContainer.Setup(x => x.Cuesheet).Returns(cuesheet); // Act - var result = _cuesheetManager.MoveTracksDown([track2, track3]); + var result = _cuesheetManager.MoveDown([track2, track3]); // Assert Assert.IsFalse(result.IsSuccess); Assert.AreEqual(ErrorType.NotPossible, result.Error!.Type); @@ -930,13 +1100,13 @@ public void MoveTracksDown_NoTracksBelow_ReturnsFailure() Assert.AreEqual(track2End, track2.End); Assert.AreEqual((ushort?)3, track3.Position); Assert.AreEqual(track2End, track3.Begin); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Never); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet.Audiofiles.First() && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Never); _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Never); _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Never); } [TestMethod] - public void MoveTracksDown_NoTracksSelected_ReturnsFailure() + public void MoveDown_NoTracksSelected_ReturnsFailure() { // Arrange var track1End = new TimeSpan(0, 3, 12); @@ -964,14 +1134,21 @@ public void MoveTracksDown_NoTracksSelected_ReturnsFailure() }; var cuesheet = new Cuesheet() { - Tracks = [track3, track2, track1] + Audiofiles = [ + new() { + Tracks = [track3, track2, track1] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; track3.Cuesheet = cuesheet; + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.First(); + track3.Audiofile = cuesheet.Audiofiles.First(); _sessionStateContainer.Setup(x => x.Cuesheet).Returns(cuesheet); // Act - var result = _cuesheetManager.MoveTracksDown([]); + var result = _cuesheetManager.MoveDown(new HashSet()); // Assert Assert.IsFalse(result.IsSuccess); Assert.AreEqual(ErrorType.NotPossible, result.Error!.Type); @@ -983,7 +1160,117 @@ public void MoveTracksDown_NoTracksSelected_ReturnsFailure() Assert.AreEqual(track2End, track2.End); Assert.AreEqual((ushort?)3, track3.Position); Assert.AreEqual(track2End, track3.Begin); - _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet && y.TraceableChange.PropertyName == nameof(Cuesheet.Tracks))), Times.Never); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet.Audiofiles && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Never); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Never); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Never); + } + + [TestMethod] + public void MoveDown_AudiofilesBelow_ReturnsSuccess() + { + // Arrange + var track1End = new TimeSpan(0, 3, 12); + var track1 = new Track() + { + Position = 1, + Begin = TimeSpan.Zero, + End = track1End, + IsLinkedToPreviousTrack = true + }; + var track2End = new TimeSpan(0, 7, 34); + var track2 = new Track() + { + Position = 2, + Begin = track1End, + End = track2End, + IsLinkedToPreviousTrack = true + }; + var track3 = new Track() + { + Position = 3, + Begin = track2End, + End = new TimeSpan(0, 10, 4), + IsLinkedToPreviousTrack = true + }; + var file1 = new Audiofile("Audiofile 1", $"{Guid.NewGuid()}", Audiofile.AudioCodecWEBM); + var file2 = new Audiofile("Audiofile 2", $"{Guid.NewGuid()}", Audiofile.AudioCodecWEBM) + { + Tracks = [track1, track2] + }; + var file3 = new Audiofile("Audiofile 3", $"{Guid.NewGuid()}", Audiofile.AudioCodecWEBM) + { + Tracks = [track3] + }; + var cuesheet = new Cuesheet() + { + Audiofiles = [file1, file2, file3], + }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + track1.Cuesheet = cuesheet; + track2.Cuesheet = cuesheet; + track3.Cuesheet = cuesheet; + track1.Audiofile = file2; + track2.Audiofile = file2; + track3.Audiofile = file3; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + // Act + var result = _cuesheetManager.MoveDown([file2, file1]); + // Assert + Assert.IsTrue(result.IsSuccess); + CollectionAssert.AreEquivalent(new List([file3, file2, file1]), cuesheet.Audiofiles.ToList()); + Assert.AreEqual((ushort?)1, track1.Position); + Assert.AreEqual(TimeSpan.Zero, track1.Begin); + Assert.AreEqual(track1End, track1.End); + Assert.AreEqual(file2, track1.Audiofile); + Assert.AreEqual((ushort?)2, track2.Position); + Assert.AreEqual(track1End, track2.Begin); + Assert.AreEqual(track2End, track2.End); + Assert.AreEqual(file2, track2.Audiofile); + Assert.AreEqual((ushort?)3, track3.Position); + Assert.AreEqual(track2End, track3.Begin); + Assert.AreEqual(file3, track3.Audiofile); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Once); + } + + [TestMethod] + public void MoveDown_NoAudiofilesBelow_ReturnsFailure() + { + // Arrange + var file1 = new Audiofile(); + var file2 = new Audiofile(); + var file3 = new Audiofile(); + var cuesheet = new Cuesheet() + { + Audiofiles = [file1, file2, file3], + }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + // Act + var result = _cuesheetManager.MoveDown([file2, file3]); + // Assert + Assert.IsFalse(result.IsSuccess); + Assert.AreEqual(ErrorType.NotPossible, result.Error!.Type); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Never); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Never); + } + + [TestMethod] + public void MoveDown_NoAudiofilesSelected_ReturnsFailure() + { + // Arrange + var file1 = new Audiofile(); + var file2 = new Audiofile(); + var file3 = new Audiofile(); + var cuesheet = new Cuesheet() + { + Audiofiles = [file1, file2, file3], + }; + _sessionStateContainer.Setup(x => x.GetActiveCuesheet()).Returns(cuesheet); + // Act + var result = _cuesheetManager.MoveDown(new HashSet()); + // Assert + Assert.IsFalse(result.IsSuccess); + Assert.AreEqual(ErrorType.NotPossible, result.Error!.Type); _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Never); _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Never); } diff --git a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/TrackManagerTests.cs b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/TrackManagerTests.cs index f3fb1c6e..21cd0363 100644 --- a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/TrackManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/TrackManagerTests.cs @@ -211,7 +211,12 @@ public void GetPreviousLinkedTrack_LinkedTracks_ReturnsTrack() }; var cuesheet = new Cuesheet() { - Tracks = [track1, track2] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -236,7 +241,12 @@ public void GetPreviousLinkedTrack_NotLinkedTracks_ReturnsNull() }; var cuesheet = new Cuesheet() { - Tracks = [track1, track2] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -261,7 +271,12 @@ public void GetPreviousLinkedTrack_WithoutPosition_ReturnsNull() }; var cuesheet = new Cuesheet() { - Tracks = [track1, track2] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -289,7 +304,12 @@ public void GetPreviousLinkedTrack_WithBegin_ReturnsTrack() }; var cuesheet = new Cuesheet() { - Tracks = [track1, track2] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -316,7 +336,12 @@ public void GetNextLinkedTrack_LinkedTracks_ReturnsTrack() }; var cuesheet = new Cuesheet() { - Tracks = [track1, track2] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -341,7 +366,12 @@ public void GetNextLinkedTrack_NotLinkedTracks_ReturnsNull() }; var cuesheet = new Cuesheet() { - Tracks = [track1, track2] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -366,7 +396,12 @@ public void GetNextLinkedTrack_WithoutPosition_ReturnsNull() }; var cuesheet = new Cuesheet() { - Tracks = [track1, track2] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -395,7 +430,12 @@ public void GetNextLinkedTrack_WithBegin_ReturnsTrack() }; var cuesheet = new Cuesheet() { - Tracks = [track1, track2] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -416,10 +456,7 @@ public void GetNextLinkedTrack_OnlyOneTrack_ReturnsNull() Begin = TimeSpan.Zero, End = new TimeSpan(0, 8, 32) }; - var cuesheet = new Cuesheet() - { - Tracks = [track1] - }; + var cuesheet = new Cuesheet(); track1.Cuesheet = cuesheet; // Act var linkedTrack = _trackManager.GetNextLinkedTrack(track1); @@ -454,7 +491,12 @@ public void GetNextLinkedTrack_MultiplePositions_ReturnsTrack() }; var cuesheet = new Cuesheet() { - Tracks = [track1, track2, track3] + Audiofiles = [ + new() + { + Tracks = [track1, track2, track3] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -483,7 +525,12 @@ public void GetNextLinkedTrack_WithPosition_ReturnsTrack() }; var cuesheet = new Cuesheet() { - Tracks = [track2, track1] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -511,7 +558,12 @@ public void RecalculateLinkedTracksProperties_TrackHasNoBegin_SetsBegin() }; var cuesheet = new Cuesheet() { - Tracks = [track2, track1] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -539,7 +591,12 @@ public void RecalculateLinkedTracksProperties_TrackHasNoPosition_SetsPosition() }; var cuesheet = new Cuesheet() { - Tracks = [track2, track1] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -567,7 +624,12 @@ public void RecalculateLinkedTracksProperties_PreviousTrackHasNoEnd_SetsEnd() }; var cuesheet = new Cuesheet() { - Tracks = [track2, track1] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -595,7 +657,12 @@ public void RecalculateLinkedTracksProperties_NextTrackNoPosition_SetsPosition() }; var cuesheet = new Cuesheet() { - Tracks = [track2, track1] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -623,7 +690,12 @@ public void RecalculateLinkedTracksProperties_NextTrackNoBegin_SetsBegin() }; var cuesheet = new Cuesheet() { - Tracks = [track2, track1] + Audiofiles = [ + new() + { + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; diff --git a/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs b/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs index 13fbd775..622d6607 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs @@ -22,6 +22,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; +using System.Linq; namespace AudioCuesheetEditor.Tests.Services.IO { @@ -55,6 +56,78 @@ public void CanGenerateExportfile_InvalidExtension_ReturnsValidationMessage() Assert.IsTrue(result.Error?.Message.Contains("File extension is not")); } + [TestMethod] + public void CanGenerateExportfile_InvalidAudiofileName_ReturnsValidationMessage() + { + // Arrange + _sessionStateContainerMock.SetupProperty(x => x.Cuesheet, new Cuesheet() + { + Audiofiles = [new()] + }); + _localizerMock.Setup(x => x["{0} has no value!", It.IsAny()]).Returns((string format, object[] args) => new LocalizedString(format, string.Format(format, args))); + _localizerMock.Setup(x => x[nameof(Audiofile.Name)]).Returns(new LocalizedString(nameof(Audiofile.Name), nameof(Audiofile.Name))); + + // Act + var result = _cuesheetExportService.CanGenerateExportfile("test.cue"); + + // Assert + Assert.IsFalse(result.IsSuccess); + Assert.IsTrue(result.Error?.Message.Contains($"{nameof(Audiofile.Name)} has no value!")); + } + + [TestMethod] + public void CanGenerateExportfile_InvalidCuesheeetTitle_ReturnsValidationMessage() + { + // Arrange + _sessionStateContainerMock.SetupProperty(x => x.Cuesheet, new Cuesheet()); + _localizerMock.Setup(x => x["{0} has no value!", It.IsAny()]).Returns((string format, object[] args) => new LocalizedString(format, string.Format(format, args))); + _localizerMock.Setup(x => x[nameof(Cuesheet.Title)]).Returns(new LocalizedString(nameof(Cuesheet.Title), nameof(Cuesheet.Title))); + + // Act + var result = _cuesheetExportService.CanGenerateExportfile("test.cue"); + + // Assert + Assert.IsFalse(result.IsSuccess); + Assert.IsTrue(result.Error?.Message.Contains($"{nameof(Cuesheet.Title)} has no value!")); + } + + [TestMethod] + public void CanGenerateExportfile_InvalidTrackEnd_ReturnsValidationMessage() + { + // Arrange + var cuesheet = new Cuesheet() + { + Artist = "Test Artist", + Title = "Test Title", + Audiofiles = + [ + new() + { + Name = "Test audiofile.mp3", + Tracks = + [ + new() + { + Position = 1, + Begin = TimeSpan.Zero + } + ] + } + ], + + }; + _sessionStateContainerMock.SetupProperty(x => x.Cuesheet, cuesheet); + _localizerMock.Setup(x => x["{0} has no value!", It.IsAny()]).Returns((string format, object[] args) => new LocalizedString(format, string.Format(format, args))); + _localizerMock.Setup(x => x[nameof(Track.End)]).Returns(new LocalizedString(nameof(Track.End), nameof(Track.End))); + + // Act + var result = _cuesheetExportService.CanGenerateExportfile("test.cue"); + + // Assert + Assert.IsFalse(result.IsSuccess); + Assert.IsTrue(result.Error?.Message.Contains($"{nameof(Track.End)} has no value!")); + } + [TestMethod] public void CanGenerateExportfiles_ValidExtension_ReturnsSuccess() { @@ -63,15 +136,24 @@ public void CanGenerateExportfiles_ValidExtension_ReturnsSuccess() { Artist = "Test Artist", Title = "Test Title", - Audiofile = new Audiofile("Audio.mp3"), - Tracks = [ + Audiofiles = + [ new() { - Position = 1, - Begin = TimeSpan.Zero, - End = new TimeSpan(0, 3, 43) + Name = "Test audiofile.mp3", + AudioCodec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"), + Tracks = + [ + new() + { + Position = 1, + Begin = TimeSpan.Zero, + End = new TimeSpan(0, 3, 43) + } + ] } - ] + ], + }; _sessionStateContainerMock.SetupProperty(x => x.Cuesheet, cuesheet); @@ -83,7 +165,7 @@ public void CanGenerateExportfiles_ValidExtension_ReturnsSuccess() } [TestMethod] - public void GenerateExportfiles_WithoutSections_ReturnsExportFile() + public void GenerateExportfiles_SingleAudiofile_ReturnsExportFile() { // Arrange var filename = "Test valid Filename.cue"; @@ -107,8 +189,15 @@ public void GenerateExportfiles_WithoutSections_ReturnsExportFile() { Artist = "Test artist cuesheet", Title = "Test title cuesheet", - Audiofile = new Audiofile("Test audiofile.mp3"), - Tracks = [track1, track2] + Audiofiles = + [ + new() + { + Name = "Test audiofile.mp3", + AudioCodec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"), + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; @@ -137,23 +226,124 @@ TRACK 02 AUDIO } [TestMethod] - public void GenerateExportfiles_WithInvalidTracks_ReturnsFailure() + public void GenerateExportfiles_MultipleAudiofiles_ReturnsExportFile() { // Arrange + var filename = "Test valid Filename.cue"; + var track1 = new Track() + { + Artist = "Test artist 1", + Title = "Test title 1", + Begin = TimeSpan.Zero, + End = new TimeSpan(0, 4, 12), + Position = 1 + }; + var track2 = new Track() + { + Artist = "Test artist 2", + Title = "Test title 2", + Begin = track1.End, + End = new TimeSpan(0, 8, 32), + Position = 2 + }; + var track3 = new Track() + { + Artist = "Test artist 3", + Title = "Test title 3", + Begin = track2.End, + End = new TimeSpan(0, 12, 43), + Position = 3 + }; + var track4 = new Track() + { + Artist = "Test artist 4", + Title = "Test title 4", + Begin = track3.End, + End = new TimeSpan(0, 15, 54), + Position = 4 + }; var cuesheet = new Cuesheet() { - Artist = "Test Artist", - Title = "Test Title", - Audiofile = new Audiofile("Audio.mp3"), - Tracks = [ + Artist = "Test artist cuesheet", + Title = "Test title cuesheet", + Audiofiles = + [ new() { - Position = 1, - Begin = TimeSpan.Zero, + Name = "Test audiofile.mp3", + AudioCodec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"), + Tracks = [track1, track2] }, new() { - Position = 2, + Name = "Test audiofile2.wav", + AudioCodec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".wav"), + Tracks = [track3, track4] + } + ] + }; + track1.Cuesheet = cuesheet; + track2.Cuesheet = cuesheet; + track3.Cuesheet = cuesheet; + track4.Cuesheet = cuesheet; + _sessionStateContainerMock.SetupProperty(x => x.Cuesheet, cuesheet); + + // Act + var result = _cuesheetExportService.GenerateExportfile(filename); + + // Assert + Assert.IsTrue(result.IsSuccess); + Assert.AreEqual(filename, result.Value!.Name); + var content = result.Value!.Content; + Assert.IsNotNull(content); + Assert.AreEqual(@"TITLE ""Test title cuesheet"" +PERFORMER ""Test artist cuesheet"" +FILE ""Test audiofile.mp3"" MP3 + TRACK 01 AUDIO + TITLE ""Test title 1"" + PERFORMER ""Test artist 1"" + INDEX 01 00:00:00 + TRACK 02 AUDIO + TITLE ""Test title 2"" + PERFORMER ""Test artist 2"" + INDEX 01 04:12:00 +FILE ""Test audiofile2.wav"" WAV + TRACK 03 AUDIO + TITLE ""Test title 3"" + PERFORMER ""Test artist 3"" + INDEX 01 08:32:00 + TRACK 04 AUDIO + TITLE ""Test title 4"" + PERFORMER ""Test artist 4"" + INDEX 01 12:43:00 +", content); + } + + [TestMethod] + public void GenerateExportfiles_WithInvalidTracks_ReturnsFailure() + { + // Arrange + var cuesheet = new Cuesheet() + { + Artist = "Test Artist", + Title = "Test Title", + Audiofiles = + [ + new () + { + Name = "Audio.mp3", + Tracks = + [ + new() + { + Position = 1, + Begin = TimeSpan.Zero, + }, + new() + { + Position = 2, + } + ] } ] }; diff --git a/AudioCuesheetEditor.Tests/Services/IO/CuesheetImportServiceTests.cs b/AudioCuesheetEditor.Tests/Services/IO/CuesheetImportServiceTests.cs index c2ab173d..679262ec 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/CuesheetImportServiceTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/CuesheetImportServiceTests.cs @@ -72,7 +72,8 @@ TRACK 08 AUDIO Assert.IsNotNull(importFile); Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.AnalyzedCuesheet); - Assert.HasCount(8, importFile.AnalyzedCuesheet.Tracks); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(8, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); Assert.IsNotNull(importFile.FileContentRecognized); var lines = importFile.FileContentRecognized.Split(Environment.NewLine); Assert.AreEqual(string.Format(CuesheetConstants.RecognizedMarkHTML, "PERFORMER \"Sample CD Artist\""), lines.ElementAt(0)); @@ -108,8 +109,9 @@ public void Analyse_WithCuesheetBug57_CreatesValidCuesheet() //Assert Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.AnalyzedCuesheet); - Assert.HasCount(39, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual(new TimeSpan(2, 8, 21), importFile.AnalyzedCuesheet.Tracks.ElementAt(24).Begin); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(39, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual(new TimeSpan(2, 8, 21), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(24).Begin); } [TestMethod()] @@ -122,8 +124,9 @@ public void Analyse_WithCuesheetBug36_CreatesValidCuesheet() //Assert Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.AnalyzedCuesheet); - Assert.HasCount(12, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual(new TimeSpan(0, 0, 9, 15, 600), importFile.AnalyzedCuesheet.Tracks.ElementAt(2).Begin); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(12, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual(new TimeSpan(0, 0, 9, 15, 600), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(2).Begin); } [TestMethod()] @@ -132,7 +135,7 @@ public void Analyse_WithCDTextFileCatalogueNumberAndPreAndPostGap_CreatesValidCu // Arrange var fileContent = @"PERFORMER ""Sample CD Artist"" TITLE ""Sample CD Title"" -FILE ""AC DC - TNT.mp3"" MP3 +FILE ""Sample Audiofile.mp3"" MP3 CDTEXTFILE ""Testfile.cdt"" CATALOG 0123456789012 TRACK 01 AUDIO @@ -153,24 +156,25 @@ TRACK 04 AUDIO PERFORMER ""Sample Artist 4"" TITLE ""Sample Title 4"" INDEX 01 15:54:00 +FILE ""Sample Input.wav"" WAVE TRACK 05 AUDIO PERFORMER ""Sample Artist 5"" TITLE ""Sample Title 5"" - INDEX 01 20:13:00 + INDEX 01 00:00:00 POSTGAP 00:02:00 TRACK 06 AUDIO PERFORMER ""Sample Artist 6"" TITLE ""Sample Title 6"" - INDEX 01 24:54:00 + INDEX 01 04:54:00 TRACK 07 AUDIO PERFORMER ""Sample Artist 7"" TITLE ""Sample Title 7"" PREGAP 00:04:00 - INDEX 01 31:54:00 + INDEX 01 11:54:00 TRACK 08 AUDIO PERFORMER ""Sample Artist 8"" TITLE ""Sample Title 8"" - INDEX 01 45:51:00"; + INDEX 01 25:51:00"; // Act var importFile = CuesheetImportService.Analyse(fileContent); // Assert @@ -179,15 +183,18 @@ TRACK 08 AUDIO Assert.IsNotNull(importFile.FileContentRecognized); var lines = importFile.FileContentRecognized.Split(Environment.NewLine); Assert.AreEqual(string.Format(" {0}", string.Format(CuesheetConstants.RecognizedMarkHTML, "FLAGS 4CH DCP PRE SCMS")), lines.ElementAt(8)); - Assert.AreEqual(string.Format(" {0}", string.Format(CuesheetConstants.RecognizedMarkHTML, "PREGAP 00:04:00")), lines.ElementAt(35)); - Assert.HasCount(8, importFile.AnalyzedCuesheet.Tracks); + Assert.AreEqual(string.Format(" {0}", string.Format(CuesheetConstants.RecognizedMarkHTML, "PREGAP 00:04:00")), lines.ElementAt(36)); + Assert.HasCount(2, importFile.AnalyzedCuesheet.Audiofiles); + Assert.AreEqual("Sample Audiofile.mp3", importFile.AnalyzedCuesheet.Audiofiles.First().Name); + Assert.AreEqual("Sample Input.wav", importFile.AnalyzedCuesheet.Audiofiles.Last().Name); + Assert.HasCount(4, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); Assert.IsNotNull(importFile.AnalyzedCuesheet.CDTextfile); - Assert.AreEqual(4, importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Flags.Count()); - Assert.AreEqual(2, importFile.AnalyzedCuesheet.Tracks.ElementAt(1).Flags.Count()); - Assert.IsNotNull(importFile.AnalyzedCuesheet.Tracks.ElementAt(1).Flags.SingleOrDefault(x => x.CuesheetLabel == "DCP")); - Assert.IsNotNull(importFile.AnalyzedCuesheet.Tracks.ElementAt(1).Flags.SingleOrDefault(x => x.CuesheetLabel == "PRE")); - Assert.AreEqual(new TimeSpan(0, 0, 2), importFile.AnalyzedCuesheet.Tracks.ElementAt(4).PostGap); - Assert.AreEqual(new TimeSpan(0, 0, 4), importFile.AnalyzedCuesheet.Tracks.ElementAt(6).PreGap); + Assert.HasCount(4, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Flags); + Assert.HasCount(2, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(1).Flags); + Assert.IsNotNull(importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(1).Flags.SingleOrDefault(x => x.CuesheetLabel == "DCP")); + Assert.IsNotNull(importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(1).Flags.SingleOrDefault(x => x.CuesheetLabel == "PRE")); + Assert.AreEqual(new TimeSpan(0, 0, 2), importFile.AnalyzedCuesheet.Audiofiles.Last().Tracks.ElementAt(0).PostGap); + Assert.AreEqual(new TimeSpan(0, 0, 4), importFile.AnalyzedCuesheet.Audiofiles.Last().Tracks.ElementAt(2).PreGap); } } } \ No newline at end of file diff --git a/AudioCuesheetEditor.Tests/Services/IO/ExportfileGeneratorTests.cs b/AudioCuesheetEditor.Tests/Services/IO/ExportfileGeneratorTests.cs index b3855d30..047c6c47 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/ExportfileGeneratorTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/ExportfileGeneratorTests.cs @@ -23,6 +23,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; +using System.Linq; namespace AudioCuesheetEditor.Tests.Services.IO { @@ -38,15 +39,16 @@ public ExportfileGeneratorTests() var mockLocalizer = new Mock>(); _exportfileGenerator = new ExportfileGenerator(_mockSessionStateContainer.Object, mockLocalizer.Object); } - + [TestMethod] - public void GenerateExportFile_ShouldGenerateExportfile_WithoutSections() + public void GenerateExportFile_SeveralAudiofiles_ReturnsSuccess() { // Arrange var exportProfile = new Exportprofile { Name = "TestProfile", SchemeHead = "%Cuesheet.Artist% - %Cuesheet.Title%", + SchemeAudiofiles = Exportprofile.SchemeAudiofileName, SchemeTracks = "%Track.Position% %Track.Artist% - %Track.Title%", Filename = "TestExport.txt" }; @@ -70,11 +72,25 @@ public void GenerateExportFile_ShouldGenerateExportfile_WithoutSections() { Artist = "Test artist cuesheet", Title = "Test title cuesheet", - Audiofile = new Audiofile("Test audiofile.mp3"), - Tracks = [track1, track2] + Audiofiles = [ + new() + { + Name = "Test audiofile.mp3", + AudioCodec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"), + Tracks = [track1] + }, + new() + { + Name = "Test audiofile 2.wav", + AudioCodec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".wav"), + Tracks = [track1] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.Last(); _mockSessionStateContainer.SetupProperty(x => x.Cuesheet, cuesheet); // Act @@ -85,14 +101,16 @@ public void GenerateExportFile_ShouldGenerateExportfile_WithoutSections() var content = result.Value!.Content; Assert.IsNotNull(content); Assert.AreEqual(@"Test artist cuesheet - Test title cuesheet +Test audiofile.mp3 +1 Test artist 1 - Test title 1 +Test audiofile 2.wav 1 Test artist 1 - Test title 1 -2 Test artist 2 - Test title 2 ", content); } [TestMethod] - public void GenerateExportFile_ShouldHandleEmptyProfile() + public void GenerateExportFile_EmptyExportprofile_ReturnsSuccess() { // Arrange var exportProfile = new Exportprofile(); @@ -116,11 +134,19 @@ public void GenerateExportFile_ShouldHandleEmptyProfile() { Artist = "Test artist cuesheet", Title = "Test title cuesheet", - Audiofile = new Audiofile("Test audiofile.mp3"), - Tracks = [track1, track2] + Audiofiles = [ + new() + { + Name = "Test audiofile.mp3", + AudioCodec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"), + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.First(); _mockSessionStateContainer.SetupProperty(x => x.Cuesheet, cuesheet); // Act @@ -160,11 +186,19 @@ public void GenerateExportFile_ReturnsEmpty_WithInvalidTracks() { Artist = "Test artist cuesheet", Title = "Test title cuesheet", - Audiofile = new Audiofile("Test audiofile.mp3"), - Tracks = [track1, track2] + Audiofiles = [ + new() + { + Name = "Test audiofile.mp3", + AudioCodec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"), + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = cuesheet; track2.Cuesheet = cuesheet; + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.First(); _mockSessionStateContainer.SetupProperty(x => x.Cuesheet, cuesheet); // Act diff --git a/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs b/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs index 4e4de503..14022cca 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs @@ -24,7 +24,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -33,19 +32,24 @@ namespace AudioCuesheetEditor.Tests.Services.IO [TestClass()] public class FileInputManagerTests { + private readonly FileInputManager _service; + private readonly Mock _jsRuntimeMock = new(); + + public FileInputManagerTests() + { + var loggerMock = new Mock>(); + _service = new FileInputManager(_jsRuntimeMock.Object, loggerMock.Object); + } + [TestMethod()] public void CheckFileMimeType_ReturnsTrue_WhenContentTypeDoesNotMatchButExtension() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); var fileName = "test.mp3"; var contentType = "audio/wav"; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.CheckFileMimeType(contentType, fileName, "audio/mpeg", [".mp3"]); + var result = _service.CheckFileMimeType(contentType, fileName, "audio/mpeg", [".mp3"]); // Assert Assert.IsTrue(result); @@ -55,15 +59,11 @@ public void CheckFileMimeType_ReturnsTrue_WhenContentTypeDoesNotMatchButExtensio public void CheckFileMimeType_ReturnsTrue_WhenContentTypeDoesMatchButNotExtension() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); var fileName = "test.mpeg"; var contentType = "audio/mpeg"; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.CheckFileMimeType(contentType, fileName, "audio/mpeg", [".mp3", ".txt"]); + var result = _service.CheckFileMimeType(contentType, fileName, "audio/mpeg", [".mp3", ".txt"]); // Assert Assert.IsTrue(result); @@ -73,15 +73,11 @@ public void CheckFileMimeType_ReturnsTrue_WhenContentTypeDoesMatchButNotExtensio public void CheckFileMimeType_ReturnsFalse_WhenExtensionDoesNotMatchAndContentTypeIsEmpty() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); var fileName = "test.flac"; var contentType = string.Empty; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.CheckFileMimeType(contentType, fileName, "audio/flac", [".mp3"]); + var result = _service.CheckFileMimeType(contentType, fileName, "audio/flac", [".mp3"]); // Assert Assert.IsFalse(result); @@ -91,15 +87,11 @@ public void CheckFileMimeType_ReturnsFalse_WhenExtensionDoesNotMatchAndContentTy public void CheckFileMimeType_ReturnsTrue_WhenContentTypeAndExtensionMatch() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); var fileName = "test.wav"; var contentType = "audio/wave"; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.CheckFileMimeType(contentType, fileName, "audio/wave", [".wav"]); + var result = _service.CheckFileMimeType(contentType, fileName, "audio/wave", [".wav"]); // Assert Assert.IsTrue(result); @@ -109,15 +101,12 @@ public void CheckFileMimeType_ReturnsTrue_WhenContentTypeAndExtensionMatch() public void CheckFileMimeType_ReturnsTrue_WhenContentMainTypeMatch() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); + var fileName = "history.txt"; var contentType = "text/plain"; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.CheckFileMimeType(contentType, fileName, "text/*", [".txt", ".text"]); + var result = _service.CheckFileMimeType(contentType, fileName, "text/*", [".txt", ".text"]); // Assert Assert.IsTrue(result); @@ -127,15 +116,11 @@ public void CheckFileMimeType_ReturnsTrue_WhenContentMainTypeMatch() public void IsValidAudiofile_ReturnsTrue_WithValidAudiocodec() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); var fileName = "test.wav"; var contentType = "audio/wav"; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.IsValidAudiofile(contentType, fileName); + var result = _service.IsValidAudiofile(contentType, fileName); // Assert Assert.IsTrue(result); @@ -145,15 +130,11 @@ public void IsValidAudiofile_ReturnsTrue_WithValidAudiocodec() public void IsValidAudiofile_ReturnsFalse_WithInvalidAudiocodecAndExtension() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); var fileName = "test.mock"; var contentType = "just a fantasy"; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.IsValidAudiofile(contentType, fileName); + var result = _service.IsValidAudiofile(contentType, fileName); // Assert Assert.IsFalse(result); @@ -163,15 +144,11 @@ public void IsValidAudiofile_ReturnsFalse_WithInvalidAudiocodecAndExtension() public void GetAudioCodec_ReturnsAudiocodec_WhenContentTypeMatches() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); var fileName = "test.wbem"; var contentType = "audio/webm"; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.GetAudioCodec(contentType, fileName); + var result = _service.GetAudioCodec(contentType, fileName); // Assert Assert.IsNotNull(result); @@ -182,15 +159,11 @@ public void GetAudioCodec_ReturnsAudiocodec_WhenContentTypeMatches() public void GetAudioCodec_ReturnsAudiocodec_WhenContentTypeAndFileExtensionMatches() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); var fileName = "test.wbem"; var contentType = "audio/webm"; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.GetAudioCodec(contentType, fileName); + var result = _service.GetAudioCodec(contentType, fileName); // Assert Assert.IsNotNull(result); @@ -201,15 +174,11 @@ public void GetAudioCodec_ReturnsAudiocodec_WhenContentTypeAndFileExtensionMatch public void GetAudioCodec_ReturnsNull_WhenContentTypeAndFileExtensionNotMatch() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); var fileName = "test.acx"; var contentType = "fantasy stuff"; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.GetAudioCodec(contentType, fileName); + var result = _service.GetAudioCodec(contentType, fileName); // Assert Assert.IsNull(result); @@ -219,15 +188,11 @@ public void GetAudioCodec_ReturnsNull_WhenContentTypeAndFileExtensionNotMatch() public void IsValidForImportView_ReturnsTrue_WhenFileIsHtml() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); var fileName = "test.html"; var contentType = "text/html"; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.IsValidForImportView(contentType, fileName); + var result = _service.IsValidForImportView(contentType, fileName); // Assert Assert.IsTrue(result); @@ -237,15 +202,11 @@ public void IsValidForImportView_ReturnsTrue_WhenFileIsHtml() public void IsValidForImportView_ReturnsFalse_WhenFileIsBinary() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); var fileName = "test.dat"; var contentType = "application/octet-stream"; - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.IsValidForImportView(contentType, fileName); + var result = _service.IsValidForImportView(contentType, fileName); // Assert Assert.IsFalse(result); @@ -255,10 +216,6 @@ public void IsValidForImportView_ReturnsFalse_WhenFileIsBinary() public async Task CreateFileUploadsAsync_ReturnsFileUploads_WhenFileHasTextContentAsync() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); var firstFile = CreateBrowserFile("Test.txt", "text/plain", "Just a test!"); var secondFile = CreateBrowserFile("Test.mp3", "audio/mpeg"); var fileInputId = nameof(CreateFileUploadsAsync_ReturnsFileUploads_WhenFileHasTextContentAsync); @@ -267,11 +224,11 @@ public async Task CreateFileUploadsAsync_ReturnsFileUploads_WhenFileHasTextConte secondFile ]; var objectUrl = "Some object url!"; - jsRuntimeMock.Setup(js => js.InvokeAsync(It.IsAny(), It.IsAny())).ReturnsAsync(objectUrl); + _jsRuntimeMock.Setup(js => js.InvokeAsync(It.IsAny(), It.IsAny())).ReturnsAsync(objectUrl); // Act - var result = await manager.CreateFileUploadsAsync(browserfiles, fileInputId); + var result = await _service.CreateFileUploadsAsync(browserfiles, fileInputId); // Assert - Assert.AreEqual(2, result.Count()); + Assert.HasCount(2, result); Assert.AreEqual(firstFile.Name, result.First().Name); Assert.AreEqual(firstFile.ContentType, result.First().ContentType); Assert.AreEqual("Just a test!", result.First().Content); @@ -285,10 +242,6 @@ public async Task CreateFileUploadsAsync_ReturnsFileUploads_WhenFileHasTextConte public async Task CreateFileUploadsAsync_ReturnsEmpty_WhenFilesHaveInvalidMimeTypeAsync() { // Arrange - var jsRuntimeMock = new Mock(); - var httpClientMock = new Mock(); - var loggerMock = new Mock>(); - var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); var firstFile = CreateBrowserFile("Test.bin", "binary", "Just a test!"); var secondFile = CreateBrowserFile("Test.bin", "octet/stream"); IReadOnlyList browserfiles = [ @@ -296,12 +249,12 @@ public async Task CreateFileUploadsAsync_ReturnsEmpty_WhenFilesHaveInvalidMimeTy secondFile ]; // Act - var result = await manager.CreateFileUploadsAsync(browserfiles); + var result = await _service.CreateFileUploadsAsync(browserfiles); // Assert - Assert.AreEqual(0, result.Count()); + Assert.HasCount(0, result); } - private static IBrowserFile CreateBrowserFile(string name, string contentType, string? content = null) + static IBrowserFile CreateBrowserFile(string name, string contentType, string? content = null) { var fileMock = new Mock(); fileMock.Setup(f => f.Name).Returns(name); diff --git a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs index 66375390..658d9f32 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs @@ -139,15 +139,19 @@ public async Task AnalyseImportfile_WithTextfile_SetsImportCuesheet() { // Arrange var fileContent = "This is just a test"; + var importAudiofile = new ImportAudiofile() + { + Name = "Test Cuesheet Audiofile" + }; var importCuesheet = new ImportCuesheet() { Artist = "Test Cuesheet Artist", Title = "Test Cuesheet Title", - Audiofile = "Test Cuesheet Audiofile", + Audiofiles = [importAudiofile], Cataloguenumber = "Test Cuesheet Cataloguenumber", CDTextfile = "Test Cuesheet CDTextfile" }; - importCuesheet.Tracks.Add(new() + importAudiofile.Tracks.Add(new() { Artist = "Test Track Artist 1", Title = "Test Track Title 1", @@ -166,6 +170,8 @@ public async Task AnalyseImportfile_WithTextfile_SetsImportCuesheet() }; _textImportServiceMock.Setup(x => x.AnalyseAsync(fileContent)).ReturnsAsync(importFile); _sessionStateContainerMock.SetupGet(x => x.Importfile).Returns(importFile); + IList? sessionStateContainerImportAudiofiles = []; + _sessionStateContainerMock.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); Cuesheet? sessionStateContainerImportCuesheet = null; _sessionStateContainerMock.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cs => sessionStateContainerImportCuesheet = cs); _sessionStateContainerMock.SetupGet(x => x.ImportCuesheet).Returns(() => sessionStateContainerImportCuesheet); @@ -178,19 +184,19 @@ public async Task AnalyseImportfile_WithTextfile_SetsImportCuesheet() Assert.IsNotNull(sessionStateContainerImportCuesheet); Assert.AreEqual(importCuesheet.Artist, sessionStateContainerImportCuesheet.Artist); Assert.AreEqual(importCuesheet.Title, sessionStateContainerImportCuesheet.Title); - Assert.IsNotNull(sessionStateContainerImportCuesheet.Audiofile); - Assert.AreEqual(importCuesheet.Audiofile, sessionStateContainerImportCuesheet.Audiofile.Name); + Assert.HasCount(1, sessionStateContainerImportCuesheet.Audiofiles); + Assert.AreEqual(importAudiofile.Name, sessionStateContainerImportCuesheet.Audiofiles.First().Name); Assert.AreEqual(importCuesheet.Cataloguenumber, sessionStateContainerImportCuesheet.Cataloguenumber); Assert.IsNotNull(sessionStateContainerImportCuesheet.CDTextfile); Assert.AreEqual(importCuesheet.CDTextfile, sessionStateContainerImportCuesheet.CDTextfile.Name); - Assert.AreEqual(importCuesheet.Tracks.First().Artist, sessionStateContainerImportCuesheet.Tracks.First().Artist); - Assert.AreEqual(importCuesheet.Tracks.First().Title, sessionStateContainerImportCuesheet.Tracks.First().Title); - Assert.AreEqual(importCuesheet.Tracks.First().Begin, sessionStateContainerImportCuesheet.Tracks.First().Begin); - Assert.AreEqual(importCuesheet.Tracks.First().End, sessionStateContainerImportCuesheet.Tracks.First().End); - CollectionAssert.AreEquivalent(importCuesheet.Tracks.First().Flags.ToList(), sessionStateContainerImportCuesheet.Tracks.First().Flags.ToList()); - Assert.AreEqual(importCuesheet.Tracks.First().Position, sessionStateContainerImportCuesheet.Tracks.First().Position); - Assert.AreEqual(importCuesheet.Tracks.First().PreGap, sessionStateContainerImportCuesheet.Tracks.First().PreGap); - Assert.AreEqual(importCuesheet.Tracks.First().PostGap, sessionStateContainerImportCuesheet.Tracks.First().PostGap); + Assert.AreEqual(importAudiofile.Tracks.First().Artist, sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().Artist); + Assert.AreEqual(importAudiofile.Tracks.First().Title, sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().Title); + Assert.AreEqual(importAudiofile.Tracks.First().Begin, sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().Begin); + Assert.AreEqual(importAudiofile.Tracks.First().End, sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().End); + Assert.AreSequenceEqual([.. importAudiofile.Tracks.First().Flags], sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().Flags.ToList(), Microsoft.VisualStudio.TestTools.UnitTesting.SequenceOrder.InAnyOrder); + Assert.AreEqual(importAudiofile.Tracks.First().Position, sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().Position); + Assert.AreEqual(importAudiofile.Tracks.First().PreGap, sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().PreGap); + Assert.AreEqual(importAudiofile.Tracks.First().PostGap, sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().PostGap); } [TestMethod()] @@ -261,22 +267,28 @@ TRACK 08 AUDIO FileContent = fileContent, FileType = ImportFileType.Cuesheet }; - + IList? sessionStateContainerImportAudiofiles = []; + _sessionStateContainerMock.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); _sessionStateContainerMock.SetupGet(x => x.Importfile).Returns(() => sessionStateContainerImportFile); _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(importfile => sessionStateContainerImportFile = importfile); + Cuesheet? sessionStateContainerImportCuesheet = null; + _sessionStateContainerMock.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cuesheet => sessionStateContainerImportCuesheet = cuesheet); + _sessionStateContainerMock.SetupGet(x => x.ImportCuesheet).Returns(() => sessionStateContainerImportCuesheet); // Act await _service.AnalyseImportfile(); // Assert - Assert.IsNotNull(sessionStateContainerImportFile.AnalyzedCuesheet); - Assert.AreEqual("Sample CD Artist", sessionStateContainerImportFile.AnalyzedCuesheet.Artist); - Assert.AreEqual("Sample CD Title", sessionStateContainerImportFile.AnalyzedCuesheet.Title); - Assert.IsNotNull(sessionStateContainerImportFile.AnalyzedCuesheet.Audiofile); - Assert.AreEqual("Sample.mp3", sessionStateContainerImportFile.AnalyzedCuesheet.Audiofile); - Assert.HasCount(8, sessionStateContainerImportFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual("Sample Artist 1", sessionStateContainerImportFile.AnalyzedCuesheet.Tracks.First().Artist); - Assert.AreEqual(TimeSpan.Zero, sessionStateContainerImportFile.AnalyzedCuesheet.Tracks.First().Begin); - Assert.AreEqual("Sample Title 1", sessionStateContainerImportFile.AnalyzedCuesheet.Tracks.First().Title); + Assert.IsNotNull(sessionStateContainerImportCuesheet); + Assert.AreEqual("Sample CD Artist", sessionStateContainerImportCuesheet.Artist); + Assert.AreEqual("Sample CD Title", sessionStateContainerImportCuesheet.Title); + Assert.HasCount(1, sessionStateContainerImportCuesheet.Audiofiles); + Assert.AreEqual("Sample.mp3", sessionStateContainerImportCuesheet.Audiofiles.First().Name); + Assert.HasCount(8, sessionStateContainerImportCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual("Sample Artist 1", sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().Artist); + Assert.AreEqual(TimeSpan.Zero, sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().Begin); + Assert.AreEqual("Sample Title 1", sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().Title); + Assert.AreEqual((ushort)1, sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.First().Position); + Assert.AreEqual((ushort)8, sessionStateContainerImportCuesheet.Audiofiles.First().Tracks.Last().Position); } [TestMethod] @@ -357,18 +369,19 @@ public async Task UploadFilesAsync_WithAudiofile_ImportsCorrectly() _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); _fileInputManagerMock.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(false); _fileInputManagerMock.Setup(f => f.IsValidAudiofile(file.ContentType, file.Name)).Returns(true); - _fileInputManagerMock.Setup(f => f.CreateAudiofileAsync(It.IsAny())).ReturnsAsync(new Audiofile(file.Name)); + _fileInputManagerMock.Setup(f => f.CreateAudiofileAsync(It.IsAny())).ReturnsAsync(new Audiofile() { Name = file.Name }); IImportfile? sessionStateContainerImportfile = null; _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); - Audiofile? sessionStateContainerImportAudiofile = null; - _sessionStateContainerMock.SetupSet(x => x.ImportAudiofile = It.IsAny()).Callback(x => sessionStateContainerImportAudiofile = x); + IList? sessionStateContainerImportAudiofiles = []; + _sessionStateContainerMock.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); + _sessionStateContainerMock.SetupSet(x => x.ImportAudiofiles = It.IsAny>()).Callback>(x => sessionStateContainerImportAudiofiles = x); // Act await _service.UploadFilesAsync([file]); // Assert Assert.IsNull(sessionStateContainerImportfile); - Assert.IsNotNull(sessionStateContainerImportAudiofile); + Assert.HasCount(1, sessionStateContainerImportAudiofiles); } [TestMethod] @@ -391,28 +404,41 @@ public void ImportCuesheet_WithImportCuesheetAvailable_ImportsCuesheetData() { Artist = "Artist 123", Title = "Title 456", - Tracks = [track1, track2] + Audiofiles = [ + new Audiofile() + { + Name = "song.mp3", + ObjectURL = "Just a test", + Tracks = [track1, track2] + } + ] }; track1.Cuesheet = sessionStateContainerImportCuesheet; track2.Cuesheet = sessionStateContainerImportCuesheet; + track1.Audiofile = sessionStateContainerImportCuesheet.Audiofiles.First(); + track2.Audiofile = sessionStateContainerImportCuesheet.Audiofiles.First(); _sessionStateContainerMock.SetupGet(x => x.ImportCuesheet).Returns(sessionStateContainerImportCuesheet); + _sessionStateContainerMock.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cuesheet => sessionStateContainerImportCuesheet = cuesheet); Cuesheet sessionStateContainerCuesheet = new(); _sessionStateContainerMock.SetupSet(x => x.Cuesheet = It.IsAny()).Callback(cuesheet => sessionStateContainerCuesheet = cuesheet); _sessionStateContainerMock.SetupGet(x => x.Cuesheet).Returns(() => sessionStateContainerCuesheet); + IList? sessionStateContainerImportAudiofiles = []; + _sessionStateContainerMock.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); // Act _service.ImportCuesheet(); // Assert Assert.IsNotNull(sessionStateContainerCuesheet); Assert.AreEqual(sessionStateContainerImportCuesheet.Artist, sessionStateContainerCuesheet.Artist); Assert.AreEqual(sessionStateContainerImportCuesheet.Title, sessionStateContainerCuesheet.Title); - Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.First().Artist, sessionStateContainerCuesheet.Tracks.First().Artist); - Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.First().Title, sessionStateContainerCuesheet.Tracks.First().Title); - Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.First().Begin, sessionStateContainerCuesheet.Tracks.First().Begin); - Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.First().End, sessionStateContainerCuesheet.Tracks.First().End); - Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.Last().Artist, sessionStateContainerCuesheet.Tracks.Last().Artist); - Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.Last().Title, sessionStateContainerCuesheet.Tracks.Last().Title); - Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.Last().Begin, sessionStateContainerCuesheet.Tracks.Last().Begin); - Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.Last().End, sessionStateContainerCuesheet.Tracks.Last().End); + Assert.HasCount(1, sessionStateContainerCuesheet.Audiofiles); + Assert.AreEqual(track1.Artist, sessionStateContainerCuesheet.Audiofiles.First().Tracks.First().Artist); + Assert.AreEqual(track1.Title, sessionStateContainerCuesheet.Audiofiles.First().Tracks.First().Title); + Assert.AreEqual(TimeSpan.Zero, sessionStateContainerCuesheet.Audiofiles.First().Tracks.First().Begin); + Assert.AreEqual(track1.End, sessionStateContainerCuesheet.Audiofiles.First().Tracks.First().End); + Assert.AreEqual(track2.Artist, sessionStateContainerCuesheet.Audiofiles.First().Tracks.Last().Artist); + Assert.AreEqual(track2.Title, sessionStateContainerCuesheet.Audiofiles.First().Tracks.Last().Title); + Assert.AreEqual(track1.End, sessionStateContainerCuesheet.Audiofiles.First().Tracks.Last().Begin); + Assert.AreEqual(track2.End, sessionStateContainerCuesheet.Audiofiles.First().Tracks.Last().End); _traceChangeManagerMock.Verify(x => x.RemoveTracedChanges(It.IsAny>())); } diff --git a/AudioCuesheetEditor.Tests/Services/IO/TextImportServiceTests.cs b/AudioCuesheetEditor.Tests/Services/IO/TextImportServiceTests.cs index 684ceb45..8cc1b33c 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/TextImportServiceTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/TextImportServiceTests.cs @@ -45,7 +45,8 @@ public TextImportServiceTests() public async Task AnalyseAsync_SampleCuesheet_CreatesValidCuesheetAsync() { // Arrange - var fileContent = @"CuesheetArtist - CuesheetTitle c:\tmp\Testfile.mp3 + var fileContent = @"CuesheetArtist - CuesheetTitle +- c:\tmp\Testfile.mp3 Sample Artist 1 - Sample Title 1 00:05:00 Sample Artist 2 - Sample Title 2 00:09:23 Sample Artist 3 - Sample Title 3 00:15:54 @@ -72,12 +73,52 @@ public async Task AnalyseAsync_SampleCuesheet_CreatesValidCuesheetAsync() Assert.IsNotNull(importFile.AnalyzedCuesheet); Assert.AreEqual("CuesheetArtist", importFile.AnalyzedCuesheet.Artist); Assert.AreEqual("CuesheetTitle", importFile.AnalyzedCuesheet.Title); - Assert.AreEqual("c:\\tmp\\Testfile.mp3", importFile.AnalyzedCuesheet.Audiofile); - Assert.HasCount(8, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual("Sample Artist 1", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Artist); - Assert.AreEqual("Sample Title 1", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Title); - Assert.AreEqual(new TimeSpan(0, 5, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(0).End); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(0).IsLinkedToPreviousTrack); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.AreEqual("c:\\tmp\\Testfile.mp3", importFile.AnalyzedCuesheet.Audiofiles.First().Name); + Assert.HasCount(8, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual("Sample Artist 1", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Artist); + Assert.AreEqual("Sample Title 1", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Title); + Assert.AreEqual(new TimeSpan(0, 5, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).End); + Assert.IsTrue(importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).IsLinkedToPreviousTrack); + } + + [TestMethod()] + public async Task AnalyseAsync_InvalidAudiofileSchemeWithMatchingTrackScheme_DoesNotMarkTracksFoundAsync() + { + // Arrange + var fileContent = @"CuesheetArtist - CuesheetTitle c:\tmp\Testfile.mp3 +Sample Artist 1 - Sample Title 1 00:05:00 +Sample Artist 2 - Sample Title 2 00:09:23 +Sample Artist 3 - Sample Title 3 00:15:54 +Sample Artist 4 - Sample Title 4 00:20:13 +Sample Artist 5 - Sample Title 5 00:24:54 +Sample Artist 6 - Sample Title 6 00:31:54 +Sample Artist 7 - Sample Title 7 00:45:54 +Sample Artist 8 - Sample Title 8 01:15:54"; + + var importOptions = new ImportOptions + { + SelectedImportProfile = ImportOptions.DefaultSelectedImportprofile + }; + _localStorageOptionsProviderMock.Setup(x => x.GetOptionsAsync()).ReturnsAsync(importOptions); + var applicationOptions = new ApplicationOptions() + { + DefaultIsLinkedToPreviousTrack = true + }; + _localStorageOptionsProviderMock.Setup(x => x.GetOptionsAsync()).ReturnsAsync(applicationOptions); + // Act + var importFile = await _textImportService.AnalyseAsync(fileContent); + // Assert + Assert.IsNull(importFile.AnalyseException); + Assert.IsNotNull(importFile.AnalyzedCuesheet); + Assert.AreEqual("CuesheetArtist", importFile.AnalyzedCuesheet.Artist); + Assert.AreEqual("CuesheetTitle\t\t\t\tc:\\tmp\\Testfile.mp3", importFile.AnalyzedCuesheet.Title); + Assert.IsEmpty(importFile.AnalyzedCuesheet.Audiofiles); + var lines = importFile.FileContentRecognized!.Split(Environment.NewLine); + Assert.AreEqual(string.Format("{0} - {1}", + string.Format(CuesheetConstants.RecognizedMarkHTML, "CuesheetArtist"), + string.Format(CuesheetConstants.RecognizedMarkHTML, "CuesheetTitle\t\t\t\tc:\\tmp\\Testfile.mp3")), lines.First()); + Assert.AreEqual("Sample Artist 8 - Sample Title 8\t\t\t\t01:15:54", lines.Last()); } [TestMethod()] @@ -144,11 +185,12 @@ public async Task AnalyseAsync_InputfileWithExtraSeperator_CreatesValidCuesheetA Assert.AreEqual("CuesheetArtist", importfile.AnalyzedCuesheet.Artist); Assert.AreEqual("CuesheetTitle", importfile.AnalyzedCuesheet.Title); Assert.AreEqual("c:\\tmp\\TestTextFile.cdt", importfile.AnalyzedCuesheet.CDTextfile); - Assert.HasCount(8, importfile.AnalyzedCuesheet.Tracks); - Assert.AreEqual((ushort)6, importfile.AnalyzedCuesheet.Tracks.ElementAt(5).Position); - Assert.AreEqual("Sample Artist 1", importfile.AnalyzedCuesheet.Tracks.ElementAt(0).Artist); - Assert.AreEqual("Sample Title 1", importfile.AnalyzedCuesheet.Tracks.ElementAt(0).Title); - Assert.AreEqual(new TimeSpan(0, 5, 0), importfile.AnalyzedCuesheet.Tracks.ElementAt(0).End); + Assert.HasCount(1, importfile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(8, importfile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual((ushort)6, importfile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(5).Position); + Assert.AreEqual("Sample Artist 1", importfile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Artist); + Assert.AreEqual("Sample Title 1", importfile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Title); + Assert.AreEqual(new TimeSpan(0, 5, 0), importfile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).End); } [TestMethod()] @@ -184,11 +226,12 @@ public async Task AnalyseAsync_InputfileWithSimplifiedScheme_CreatesValidCueshee Assert.AreEqual("CuesheetArtist", importFile.AnalyzedCuesheet.Artist); Assert.AreEqual("CuesheetTitle", importFile.AnalyzedCuesheet.Title); Assert.AreEqual("c:\\tmp\\TestTextFile.cdt", importFile.AnalyzedCuesheet.CDTextfile); - Assert.HasCount(8, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual((ushort)6, importFile.AnalyzedCuesheet.Tracks.ElementAt(5).Position); - Assert.AreEqual("Sample Artist 1", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Artist); - Assert.AreEqual("Sample Title 1", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Title); - Assert.AreEqual(new TimeSpan(0, 5, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(0).End); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(8, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual((ushort)6, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(5).Position); + Assert.AreEqual("Sample Artist 1", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Artist); + Assert.AreEqual("Sample Title 1", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Title); + Assert.AreEqual(new TimeSpan(0, 5, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).End); } [TestMethod()] @@ -276,11 +319,12 @@ public async Task AnalyseAsync_CuesheetWithTextfileAndCatalogueNumber_CreatesVal Assert.AreEqual("CuesheetTitle", importFile.AnalyzedCuesheet.Title); Assert.AreEqual("c:\\tmp\\TestTextFile.cdt", importFile.AnalyzedCuesheet.CDTextfile); Assert.AreEqual("A83412346734", importFile.AnalyzedCuesheet.Cataloguenumber); - Assert.HasCount(8, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual((ushort)6, importFile.AnalyzedCuesheet.Tracks.ElementAt(5).Position); - Assert.AreEqual("Sample Artist 1", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Artist); - Assert.AreEqual("Sample Title 1", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Title); - Assert.AreEqual(new TimeSpan(0, 5, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(0).End); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(8, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual((ushort)6, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(5).Position); + Assert.AreEqual("Sample Artist 1", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Artist); + Assert.AreEqual("Sample Title 1", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Title); + Assert.AreEqual(new TimeSpan(0, 5, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).End); } [TestMethod()] @@ -312,10 +356,11 @@ public async Task AnalyseAsync_CuesheetTracksOnly_CreatesValidCuesheetAsync() // Assert Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.AnalyzedCuesheet); - Assert.HasCount(9, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual("Sample Artist 1", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Artist); - Assert.AreEqual("Sample Title 1", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Title); - Assert.AreEqual(new TimeSpan(1, 15, 54), importFile.AnalyzedCuesheet.Tracks.ElementAt(7).End); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(9, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual("Sample Artist 1", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Artist); + Assert.AreEqual("Sample Title 1", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Title); + Assert.AreEqual(new TimeSpan(1, 15, 54), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(7).End); } [TestMethod()] @@ -340,8 +385,9 @@ public async Task AnalyseAsync_CuesheetBug213_CreatesValidCuesheetAsync() // Assert Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.AnalyzedCuesheet); - Assert.HasCount(4, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual(new TimeSpan(2, 3, 23), importFile.AnalyzedCuesheet.Tracks.ElementAt(3).End); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(4, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual(new TimeSpan(2, 3, 23), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(3).End); } [TestMethod()] @@ -373,18 +419,19 @@ public async Task AnalyseAsync_CuesheetWithFlags_CreatesValidCuesheetAsync() // Assert Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.AnalyzedCuesheet); - Assert.HasCount(8, importFile.AnalyzedCuesheet.Tracks); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Flags.Contains(Flag.DCP)); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(2).Flags.Contains(Flag.DCP)); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(2).Flags.Contains(Flag.PRE)); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(3).Flags.Contains(Flag.FourCH)); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(5).Flags.Contains(Flag.FourCH)); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(5).Flags.Contains(Flag.PRE)); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(5).Flags.Contains(Flag.DCP)); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(7).Flags.Contains(Flag.DCP)); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(7).Flags.Contains(Flag.PRE)); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(7).Flags.Contains(Flag.FourCH)); - Assert.IsTrue(importFile.AnalyzedCuesheet.Tracks.ElementAt(7).Flags.Contains(Flag.SCMS)); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(8, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.Contains(Flag.DCP, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Flags); + Assert.Contains(Flag.DCP, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(2).Flags); + Assert.Contains(Flag.PRE, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(2).Flags); + Assert.Contains(Flag.FourCH, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(3).Flags); + Assert.Contains(Flag.FourCH, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(5).Flags); + Assert.Contains(Flag.PRE, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(5).Flags); + Assert.Contains(Flag.DCP, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(5).Flags); + Assert.Contains(Flag.DCP, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(7).Flags); + Assert.Contains(Flag.PRE, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(7).Flags); + Assert.Contains(Flag.FourCH, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(7).Flags); + Assert.Contains(Flag.SCMS, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(7).Flags); } [TestMethod()] @@ -416,30 +463,32 @@ public async Task AnalyseAsync_CuesheetWithPreGapAndPostGap_CreatesValidCuesheet // Assert Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.AnalyzedCuesheet); - Assert.HasCount(8, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual(new TimeSpan(0, 0, 2), importFile.AnalyzedCuesheet.Tracks.ElementAt(0).PreGap); - Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(0).PostGap); - Assert.AreEqual(new TimeSpan(0, 0, 4), importFile.AnalyzedCuesheet.Tracks.ElementAt(1).PreGap); - Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(1).PostGap); - Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(2).PreGap); - Assert.AreEqual(new TimeSpan(0, 0, 2), importFile.AnalyzedCuesheet.Tracks.ElementAt(2).PostGap); - Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(3).PreGap); - Assert.AreEqual(new TimeSpan(0, 0, 3), importFile.AnalyzedCuesheet.Tracks.ElementAt(3).PostGap); - Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(4).PreGap); - Assert.AreEqual(new TimeSpan(0, 0, 4), importFile.AnalyzedCuesheet.Tracks.ElementAt(4).PostGap); - Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(5).PreGap); - Assert.AreEqual(new TimeSpan(0, 0, 1), importFile.AnalyzedCuesheet.Tracks.ElementAt(5).PostGap); - Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(6).PreGap); - Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(6).PostGap); - Assert.AreEqual(new TimeSpan(0, 0, 2), importFile.AnalyzedCuesheet.Tracks.ElementAt(7).PreGap); - Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(7).PostGap); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(8, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual(new TimeSpan(0, 0, 2), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).PreGap); + Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).PostGap); + Assert.AreEqual(new TimeSpan(0, 0, 4), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(1).PreGap); + Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(1).PostGap); + Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(2).PreGap); + Assert.AreEqual(new TimeSpan(0, 0, 2), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(2).PostGap); + Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(3).PreGap); + Assert.AreEqual(new TimeSpan(0, 0, 3), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(3).PostGap); + Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(4).PreGap); + Assert.AreEqual(new TimeSpan(0, 0, 4), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(4).PostGap); + Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(5).PreGap); + Assert.AreEqual(new TimeSpan(0, 0, 1), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(5).PostGap); + Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(6).PreGap); + Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(6).PostGap); + Assert.AreEqual(new TimeSpan(0, 0, 2), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(7).PreGap); + Assert.AreEqual(new TimeSpan(0, 0, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(7).PostGap); } [TestMethod()] public async Task AnalyseAsync_SchemeCuesheetOnly_CreatesFileContentRecognizedOnlyForCuesheetAsync() { // Arrange - var fileContent = @"CuesheetArtist - CuesheetTitle c:\tmp\Testfile.mp3 + var fileContent = @"CuesheetArtist - CuesheetTitle +- c:\tmp\Testfile.mp3 Sample Artist 1 - Sample Title 1 00:05:00 Sample Artist 2 - Sample Title 2 00:09:23 Sample Artist 3 - Sample Title 3 00:15:54 @@ -450,7 +499,7 @@ public async Task AnalyseAsync_SchemeCuesheetOnly_CreatesFileContentRecognizedOn Sample Artist 8 - Sample Title 8 01:15:54"; var profile = new Importprofile() { - SchemeCuesheet = $"{nameof(ImportCuesheet.Artist)} - {nameof(ImportCuesheet.Title)}\t{nameof(ImportCuesheet.Audiofile)}" + SchemeCuesheet = $"{nameof(ImportCuesheet.Artist)} - {nameof(ImportCuesheet.Title)}" }; var options = new ImportOptions { @@ -466,22 +515,21 @@ public async Task AnalyseAsync_SchemeCuesheetOnly_CreatesFileContentRecognizedOn Assert.IsNotNull(importFile.AnalyzedCuesheet); Assert.IsNotNull(importFile.FileContentRecognized); var lines = importFile.FileContentRecognized.Split(Environment.NewLine); - Assert.AreEqual(string.Format("{0} - {1} {2}", + Assert.AreEqual(string.Format("{0} - {1}", string.Format(CuesheetConstants.RecognizedMarkHTML, "CuesheetArtist"), - string.Format(CuesheetConstants.RecognizedMarkHTML, "CuesheetTitle"), - string.Format(CuesheetConstants.RecognizedMarkHTML, "c:\\tmp\\Testfile.mp3")), lines.First()); + string.Format(CuesheetConstants.RecognizedMarkHTML, "CuesheetTitle")), lines.First()); Assert.AreEqual("CuesheetArtist", importFile.AnalyzedCuesheet.Artist); Assert.AreEqual("CuesheetTitle", importFile.AnalyzedCuesheet.Title); - Assert.AreEqual("c:\\tmp\\Testfile.mp3", importFile.AnalyzedCuesheet.Audiofile); - Assert.HasCount(0, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual("Sample Artist 1 - Sample Title 1 00:05:00", lines.ElementAt(1)); - Assert.AreEqual("Sample Artist 2 - Sample Title 2 00:09:23", lines.ElementAt(2)); - Assert.AreEqual("Sample Artist 3 - Sample Title 3 00:15:54", lines.ElementAt(3)); - Assert.AreEqual("Sample Artist 4 - Sample Title 4 00:20:13", lines.ElementAt(4)); - Assert.AreEqual("Sample Artist 5 - Sample Title 5 00:24:54", lines.ElementAt(5)); - Assert.AreEqual("Sample Artist 6 - Sample Title 6 00:31:54", lines.ElementAt(6)); - Assert.AreEqual("Sample Artist 7 - Sample Title 7 00:45:54", lines.ElementAt(7)); - Assert.AreEqual("Sample Artist 8 - Sample Title 8 01:15:54", lines.ElementAt(8)); + Assert.HasCount(0, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual("- c:\\tmp\\Testfile.mp3", lines.ElementAt(1)); + Assert.AreEqual("Sample Artist 1 - Sample Title 1 00:05:00", lines.ElementAt(2)); + Assert.AreEqual("Sample Artist 2 - Sample Title 2 00:09:23", lines.ElementAt(3)); + Assert.AreEqual("Sample Artist 3 - Sample Title 3 00:15:54", lines.ElementAt(4)); + Assert.AreEqual("Sample Artist 4 - Sample Title 4 00:20:13", lines.ElementAt(5)); + Assert.AreEqual("Sample Artist 5 - Sample Title 5 00:24:54", lines.ElementAt(6)); + Assert.AreEqual("Sample Artist 6 - Sample Title 6 00:31:54", lines.ElementAt(7)); + Assert.AreEqual("Sample Artist 7 - Sample Title 7 00:45:54", lines.ElementAt(8)); + Assert.AreEqual("Sample Artist 8 - Sample Title 8 01:15:54", lines.ElementAt(9)); } [TestMethod()] @@ -497,9 +545,16 @@ public async Task AnalyseAsync_CuesheetWithoutTracks_CreatesValidFileContentReco Sample Artist 6 - Sample Title 6 00:31:54 Sample Artist 7 - Sample Title 7 00:45:54 Sample Artist 8 - Sample Title 8 01:15:54"; + var importProfile = new Importprofile() + { + UseRegularExpression = true, + SchemeCuesheet = @$"^(?<{nameof(ImportCuesheet.Artist)}>.+?)\s*-\s*(?<{nameof(ImportCuesheet.Title)}>.+?)\t+", + SchemeAudiofiles = @$"\s+(?<{nameof(ImportAudiofile.Name)}>\S+.mp3)", + SchemeTracks = @$"(?<{nameof(ImportTrack.Artist)}>.+?)\s*-\s*(?<{nameof(ImportTrack.Title)}>.+?)\s+(?<{nameof(ImportTrack.End)}>\d{{2}}:\d{{2}}:\d{{2}})" + }; var options = new ImportOptions { - SelectedImportProfile = ImportOptions.DefaultSelectedImportprofile + SelectedImportProfile = importProfile }; _localStorageOptionsProviderMock.Setup(x => x.GetOptionsAsync()).ReturnsAsync(options); var applicationOptions = new ApplicationOptions(); @@ -510,10 +565,11 @@ public async Task AnalyseAsync_CuesheetWithoutTracks_CreatesValidFileContentReco Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.AnalyzedCuesheet); Assert.IsNotNull(importFile.FileContentRecognized); - Assert.HasCount(8, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual("Sample Artist 1", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Artist); - Assert.AreEqual("Sample Title 1", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Title); - Assert.AreEqual(new TimeSpan(0, 5, 0), importFile.AnalyzedCuesheet.Tracks.ElementAt(0).End); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(8, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual("Sample Artist 1", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Artist); + Assert.AreEqual("Sample Title 1", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Title); + Assert.AreEqual(new TimeSpan(0, 5, 0), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).End); var lines = importFile.FileContentRecognized.Split(Environment.NewLine); Assert.AreEqual(string.Format("{0} - {1} {2}", string.Format(CuesheetConstants.RecognizedMarkHTML, "Sample Artist 8"), @@ -575,13 +631,14 @@ public async Task AnalyseAsync_WithRegularExpression_ReturnsCuesheetAsync() Assert.IsNotNull(importFile.AnalyzedCuesheet); Assert.IsNull(importFile.AnalyzedCuesheet.Artist); Assert.IsNull(importFile.AnalyzedCuesheet.Title); - Assert.HasCount(41, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual("Nachap", importFile.AnalyzedCuesheet.Tracks.First().Artist); - Assert.AreEqual("Glass", importFile.AnalyzedCuesheet.Tracks.First().Title); - Assert.AreEqual(new DateTime(2025, 1, 29, 18, 52, 10), importFile.AnalyzedCuesheet.Tracks.First().StartDateTime); - Assert.AreEqual("Inache", importFile.AnalyzedCuesheet.Tracks.Last().Artist); - Assert.AreEqual("Andale (MONTA (TN) Remix)", importFile.AnalyzedCuesheet.Tracks.Last().Title); - Assert.AreEqual(new DateTime(2025, 1, 29, 22, 30, 3), importFile.AnalyzedCuesheet.Tracks.Last().StartDateTime); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(41, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual("Nachap", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.First().Artist); + Assert.AreEqual("Glass", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.First().Title); + Assert.AreEqual(new DateTime(2025, 1, 29, 18, 52, 10), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.First().StartDateTime); + Assert.AreEqual("Inache", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.Last().Artist); + Assert.AreEqual("Andale (MONTA (TN) Remix)", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.Last().Title); + Assert.AreEqual(new DateTime(2025, 1, 29, 22, 30, 3), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.Last().StartDateTime); Assert.IsNotNull(importFile.FileContentRecognized); Assert.Contains(String.Format(CuesheetConstants.RecognizedMarkHTML, "Sasha Fashion"), importFile.FileContentRecognized); Assert.Contains(String.Format(CuesheetConstants.RecognizedMarkHTML, "2025/1/29 21:48:55"), importFile.FileContentRecognized); @@ -610,15 +667,16 @@ public async Task AnalyseAsync_WithoutRegularExpression_ReturnsCuesheetAsync() // Assert Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.AnalyzedCuesheet); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); Assert.AreEqual("DJFreezeT", importFile.AnalyzedCuesheet.Artist); Assert.AreEqual("Rabbit Hole Mix", importFile.AnalyzedCuesheet.Title); Assert.AreEqual("0123456789123", importFile.AnalyzedCuesheet.Cataloguenumber); - Assert.AreEqual("Adriatique", importFile.AnalyzedCuesheet.Tracks.First().Artist); - Assert.AreEqual("X.", importFile.AnalyzedCuesheet.Tracks.First().Title); - Assert.AreEqual(new TimeSpan(0, 0, 5, 24, 250), importFile.AnalyzedCuesheet.Tracks.First().Begin); - Assert.AreEqual("Nikolay Kirov", importFile.AnalyzedCuesheet.Tracks.Last().Artist); - Assert.AreEqual("Chasing the Sun (Original Mix)", importFile.AnalyzedCuesheet.Tracks.Last().Title); - Assert.IsNull(importFile.AnalyzedCuesheet.Tracks.Last().Begin); + Assert.AreEqual("Adriatique", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.First().Artist); + Assert.AreEqual("X.", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.First().Title); + Assert.AreEqual(new TimeSpan(0, 0, 5, 24, 250), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.First().Begin); + Assert.AreEqual("Nikolay Kirov", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.Last().Artist); + Assert.AreEqual("Chasing the Sun (Original Mix)", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.Last().Title); + Assert.IsNull(importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.Last().Begin); Assert.IsNotNull(importFile.FileContentRecognized); Assert.Contains(String.Format(CuesheetConstants.RecognizedMarkHTML, "DJFreezeT"), importFile.FileContentRecognized); Assert.Contains(String.Format(CuesheetConstants.RecognizedMarkHTML, "Rabbit Hole Mix"), importFile.FileContentRecognized); @@ -681,16 +739,17 @@ public async Task AnalyseAsync_TextfileWithStartDateTime_CreatesValidCuesheetAsy // Assert Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.AnalyzedCuesheet); - Assert.HasCount(30, importFile.AnalyzedCuesheet.Tracks); - Assert.AreEqual("Innellea", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Artist); - Assert.AreEqual("The Golden Fort", importFile.AnalyzedCuesheet.Tracks.ElementAt(0).Title); - Assert.AreEqual(new DateTime(2024, 8, 14, 20, 10, 48), importFile.AnalyzedCuesheet.Tracks.ElementAt(0).StartDateTime); - Assert.AreEqual("Nora En Pure", importFile.AnalyzedCuesheet.Tracks.ElementAt(1).Artist); - Assert.AreEqual("Diving with Whales (Daniel Portman Remix)", importFile.AnalyzedCuesheet.Tracks.ElementAt(1).Title); - Assert.AreEqual(new DateTime(2024, 8, 14, 20, 15, 21), importFile.AnalyzedCuesheet.Tracks.ElementAt(1).StartDateTime); - Assert.AreEqual("Local Singles", importFile.AnalyzedCuesheet.Tracks.ElementAt(29).Artist); - Assert.AreEqual("Voices", importFile.AnalyzedCuesheet.Tracks.ElementAt(29).Title); - Assert.AreEqual(new DateTime(2024, 8, 14, 22, 25, 59), importFile.AnalyzedCuesheet.Tracks.ElementAt(29).StartDateTime); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.HasCount(30, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); + Assert.AreEqual("Innellea", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Artist); + Assert.AreEqual("The Golden Fort", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).Title); + Assert.AreEqual(new DateTime(2024, 8, 14, 20, 10, 48), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(0).StartDateTime); + Assert.AreEqual("Nora En Pure", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(1).Artist); + Assert.AreEqual("Diving with Whales (Daniel Portman Remix)", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(1).Title); + Assert.AreEqual(new DateTime(2024, 8, 14, 20, 15, 21), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(1).StartDateTime); + Assert.AreEqual("Local Singles", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(29).Artist); + Assert.AreEqual("Voices", importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(29).Title); + Assert.AreEqual(new DateTime(2024, 8, 14, 22, 25, 59), importFile.AnalyzedCuesheet.Audiofiles.First().Tracks.ElementAt(29).StartDateTime); } [TestMethod()] @@ -700,8 +759,9 @@ public async Task AnalyseAsync_WithCommonDataMatchingMultipleLines_SetsCommonDat var profile = new Importprofile() { UseRegularExpression = false, - SchemeCuesheet = "Artist - Title\tAudiofile", - SchemeTracks = "Artist - Title\tBegin" + SchemeCuesheet = $"{nameof(ImportCuesheet.Artist)} - {nameof(ImportCuesheet.Title)}", + SchemeAudiofiles = $"- {nameof(ImportAudiofile.Name)}", + SchemeTracks = $"{nameof(ImportTrack.Artist)} - {nameof(ImportTrack.Title)}\t{nameof(ImportTrack.StartDateTime)}" }; var fileContent = File.ReadAllText("Resources/Sample_Inputfile.txt"); var options = new ImportOptions @@ -718,8 +778,9 @@ public async Task AnalyseAsync_WithCommonDataMatchingMultipleLines_SetsCommonDat Assert.IsNotNull(importFile.AnalyzedCuesheet); Assert.AreEqual("CuesheetArtist", importFile.AnalyzedCuesheet.Artist); Assert.AreEqual("CuesheetTitle", importFile.AnalyzedCuesheet.Title); - Assert.AreEqual("c:\\AudioFile.mp3", importFile.AnalyzedCuesheet.Audiofile); - Assert.HasCount(8, importFile.AnalyzedCuesheet.Tracks); + Assert.HasCount(1, importFile.AnalyzedCuesheet.Audiofiles); + Assert.AreEqual("c:\\AudioFile.mp3", importFile.AnalyzedCuesheet.Audiofiles.First().Name); + Assert.HasCount(8, importFile.AnalyzedCuesheet.Audiofiles.First().Tracks); Assert.IsNotNull(importFile.FileContentRecognized); Assert.Contains(String.Format(CuesheetConstants.RecognizedMarkHTML, "CuesheetArtist"), importFile.FileContentRecognized); Assert.Contains(String.Format(CuesheetConstants.RecognizedMarkHTML, "CuesheetTitle"), importFile.FileContentRecognized); diff --git a/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs b/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs index 6a2cfe22..91151f28 100644 --- a/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs @@ -79,7 +79,7 @@ public void ResetImport_ShouldClearImportProperties() // Assert Assert.IsNull(_sessionStateContainer.Importfile); - Assert.IsNull(_sessionStateContainer.ImportAudiofile); + Assert.IsEmpty(_sessionStateContainer.ImportAudiofiles); Assert.IsNull(_sessionStateContainer.ImportCuesheet); } diff --git a/AudioCuesheetEditor.Tests/Services/UI/TraceChangeManagerTests.cs b/AudioCuesheetEditor.Tests/Services/UI/TraceChangeManagerTests.cs index 98b99a17..06613a8b 100644 --- a/AudioCuesheetEditor.Tests/Services/UI/TraceChangeManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/UI/TraceChangeManagerTests.cs @@ -14,6 +14,7 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Services.UI; using AudioCuesheetEditor.Tests.Utility; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -221,18 +222,22 @@ public void Undo_ObjectReferences_UndoesChanges() // Arrange var cuesheet = new Cuesheet() { - Tracks = [new()] + Audiofiles = [ + new() { + Tracks = [new()] + } + ] }; var undoDoneEventFired = false; _traceChangeManager.UndoDone += delegate { undoDoneEventFired = true; }; - _traceChangeManager.AddChange(new(cuesheet, new(Enumerable.Empty(), nameof(Cuesheet.Tracks)))); + _traceChangeManager.AddChange(new(cuesheet.Audiofiles.First(), new(Enumerable.Empty(), nameof(Audiofile.Tracks)))); // Act _traceChangeManager.Undo(); // Assert - Assert.IsEmpty(cuesheet.Tracks); + Assert.IsEmpty(cuesheet.Audiofiles.First().Tracks); Assert.IsFalse(_traceChangeManager.CanUndo); Assert.IsTrue(_traceChangeManager.CanRedo); Assert.IsTrue(undoDoneEventFired); @@ -244,19 +249,23 @@ public void Redo_ObjectReferences_UndoesChanges() // Arrange var cuesheet = new Cuesheet() { - Tracks = [new()] + Audiofiles = [ + new() { + Tracks = [new()] + } + ] }; var redoDoneEventFired = false; _traceChangeManager.RedoDone += delegate { redoDoneEventFired = true; }; - _traceChangeManager.AddChange(new(cuesheet, new(Enumerable.Empty(), nameof(Cuesheet.Tracks)))); + _traceChangeManager.AddChange(new(cuesheet.Audiofiles.First(), new(Enumerable.Empty(), nameof(Audiofile.Tracks)))); _traceChangeManager.Undo(); // Act _traceChangeManager.Redo(); // Assert - Assert.HasCount(1, cuesheet.Tracks); + Assert.HasCount(1, cuesheet.Audiofiles.First().Tracks); Assert.IsTrue(_traceChangeManager.CanUndo); Assert.IsFalse(_traceChangeManager.CanRedo); Assert.IsTrue(redoDoneEventFired); @@ -274,9 +283,13 @@ public void Reset_HasChanges_ResetsAllChanges() }; var cuesheet = new Cuesheet() { - Tracks = [track] + Audiofiles = [ + new() { + Tracks = [track], + } + ] }; - _traceChangeManager.AddChange(new(cuesheet, new(Enumerable.Empty(), nameof(Cuesheet.Tracks)))); + _traceChangeManager.AddChange(new(cuesheet.Audiofiles.First(), new(Enumerable.Empty(), nameof(Audiofile.Tracks)))); _traceChangeManager.AddChange(new(track, new(null, nameof(Track.End)))); _traceChangeManager.AddChange(new(track, new(null, nameof(Track.Title)))); _traceChangeManager.AddChange(new(track, new(null, nameof(Track.Artist)))); diff --git a/AudioCuesheetEditor/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index d1ba6148..0855b765 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -20,16 +20,15 @@ - + - - + + - - - - - + + + + diff --git a/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs b/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs index 11e24d17..6cc603bf 100644 --- a/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs +++ b/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs @@ -25,5 +25,11 @@ public async static Task SetCultureFromConfigurationAsync(this WebAssemblyHost h var localizationService = host.Services.GetRequiredService(); await localizationService.SetCultureFromConfigurationAsync(); } + + public async static Task InitializeSessionStateContainer(this WebAssemblyHost host) + { + var sessionStateContainer = host.Services.GetRequiredService(); + await sessionStateContainer.InitializeAsync(); + } } } diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs index 501f90cf..ee8fc393 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs @@ -21,14 +21,11 @@ namespace AudioCuesheetEditor.Model.AudioCuesheet { public class Cuesheet() : Validateable, ICuesheet { - [JsonInclude] - public IEnumerable Tracks { get; set; } = []; - public String? Artist { get; set; } public String? Title { get; set; } - public Audiofile? Audiofile { get; set; } + public IList Audiofiles { get; set; } = []; public CDTextfile? CDTextfile { get; set; } @@ -46,52 +43,12 @@ public override ValidationResult Validate(string property) List? validationMessages = null; switch (property) { - case nameof(Tracks): - validationStatus = ValidationStatus.Success; - if (!Tracks.Any()) - { - validationMessages ??= []; - validationMessages.Add(new ValidationMessage("{0} has invalid Count ({1})!", nameof(Tracks), 0)); - } - else - { - //Check track overlapping - var tracksWithSamePosition = Tracks - .GroupBy(x => x.Position) - .Where(grp => grp.Count() > 1); - if (tracksWithSamePosition.Any()) - { - validationMessages ??= []; - foreach (var track in tracksWithSamePosition) - { - foreach (var trackWithSamePosition in track) - { - validationMessages.Add(new ValidationMessage("{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!", nameof(Track), nameof(Track.Position), track.Key != null ? track.Key : String.Empty, nameof(Track), trackWithSamePosition.Position != null ? trackWithSamePosition.Position : String.Empty, trackWithSamePosition.Artist ?? String.Empty, trackWithSamePosition.Title ?? String.Empty, trackWithSamePosition.Begin != null ? trackWithSamePosition.Begin : String.Empty, trackWithSamePosition.End != null ? trackWithSamePosition.End : String.Empty)); - } - } - } - foreach (var track in Tracks.OrderBy(x => x.Position)) - { - var tracksBetween = Tracks.Where(x => ((track.Begin >= x.Begin && track.Begin < x.End) - || (x.Begin < track.End && track.End <= x.End)) - && (x.Equals(track) == false)); - if (tracksBetween.Any()) - { - validationMessages ??= []; - foreach (var trackBetween in tracksBetween) - { - validationMessages.Add(new ValidationMessage("{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!", nameof(Track), track.Position != null ? track.Position : String.Empty, track.Artist ?? String.Empty, track.Title ?? String.Empty, track.Begin != null ? track.Begin : String.Empty, track.End != null ? track.End : String.Empty, trackBetween.Position != null ? trackBetween.Position : String.Empty, trackBetween.Artist ?? String.Empty, trackBetween.Title ?? String.Empty, trackBetween.Begin != null ? trackBetween.Begin : String.Empty, trackBetween.End != null ? trackBetween.End : String.Empty)); - } - } - } - } - break; - case nameof(Audiofile): + case nameof(Audiofiles): validationStatus = ValidationStatus.Success; - if (Audiofile == null) + if (Audiofiles.Count == 0) { validationMessages ??= []; - validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Audiofile))); + validationMessages.Add(new ValidationMessage("{0} has invalid count ({1})!", nameof(Audiofiles), 0)); } break; case nameof(Artist): diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/IAudiofile.cs b/AudioCuesheetEditor/Model/AudioCuesheet/IAudiofile.cs new file mode 100644 index 00000000..0efa34b0 --- /dev/null +++ b/AudioCuesheetEditor/Model/AudioCuesheet/IAudiofile.cs @@ -0,0 +1,22 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +namespace AudioCuesheetEditor.Model.AudioCuesheet +{ + public interface IAudiofile + { + String? Name { get; set; } + } +} diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportAudiofile.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportAudiofile.cs new file mode 100644 index 00000000..c158067d --- /dev/null +++ b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportAudiofile.cs @@ -0,0 +1,8 @@ +namespace AudioCuesheetEditor.Model.AudioCuesheet.Import +{ + public class ImportAudiofile : IAudiofile + { + public String? Name { get; set; } + public IList Tracks { get; } = []; + } +} diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportCuesheet.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportCuesheet.cs index 053228b6..47f401e0 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportCuesheet.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportCuesheet.cs @@ -21,10 +21,9 @@ public class ImportCuesheet : ICuesheet { public string? Artist { get; set; } public string? Title { get; set; } - public string? Audiofile { get;set; } + public IList Audiofiles { get; set; } = []; /// public string? CDTextfile { get; set; } public string? Cataloguenumber { get; set; } - public ICollection Tracks { get; } = []; } } diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs index 1efc32ef..bd86d17c 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs @@ -14,6 +14,7 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.Entity; +using AudioCuesheetEditor.Model.IO.Audio; using System.Text.Json.Serialization; namespace AudioCuesheetEditor.Model.AudioCuesheet @@ -65,10 +66,10 @@ public TimeSpan? Length } } } - [JsonInclude] - public IEnumerable Flags { get; set; } = []; [JsonIgnore] public Cuesheet? Cuesheet { get; set; } + [JsonInclude] + public IEnumerable Flags { get; set; } = []; /// public TimeSpan? PreGap { get; set; } /// @@ -77,6 +78,8 @@ public TimeSpan? Length /// Set that this track is linked to the previous track in cuesheet /// public Boolean IsLinkedToPreviousTrack { get; set; } + [JsonIgnore] + public Audiofile? Audiofile { get; set; } public override ValidationResult Validate(string property) { @@ -103,7 +106,7 @@ public override ValidationResult Validate(string property) // Check correct track position if (Cuesheet != null) { - var positionTrackShouldHave = Cuesheet.Tracks.OrderBy(x => x.Begin ?? TimeSpan.MaxValue).ThenBy(x => x.Position).ToList().IndexOf(this) + 1; + var positionTrackShouldHave = Cuesheet.Audiofiles.SelectMany(x => x.Tracks).OrderBy(x => x.Begin ?? TimeSpan.MaxValue).ThenBy(x => x.Position).ToList().IndexOf(this) + 1; if (positionTrackShouldHave != Position) { validationMessages ??= []; diff --git a/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx b/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx index aa9e152e..2f6686f8 100644 --- a/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx +++ b/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx @@ -165,6 +165,9 @@ Titel + + Audiodateien + Titel ({0},{1},{2},{3},{4}) hat nicht die korrekte Position '{5}'! @@ -180,7 +183,7 @@ {0} hat eine ungültige Länge. Erlaubte Länge ist {1}! - + {0} hat ungültige Anzahl ({1})! diff --git a/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx b/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx index 4e807726..95e63d1c 100644 --- a/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx +++ b/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx @@ -165,6 +165,9 @@ Title + + Audiofiles + Track({0},{1},{2},{3},{4}) does not have the correct position '{5}'! @@ -180,8 +183,8 @@ {0} has an invalid length. Allowed length is {1}! - - {0} has invalid Count ({1})! + + {0} has invalid count ({1})! {0} has no value! diff --git a/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs b/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs index b6d86c42..c8330b68 100644 --- a/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs +++ b/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs @@ -13,12 +13,13 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. +using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.Entity; using System.Text.Json.Serialization; namespace AudioCuesheetEditor.Model.IO.Audio { - [method: JsonConstructor] - public class Audiofile(String name) + public class Audiofile() : Validateable, IAudiofile { public static readonly AudioCodec AudioCodecWEBM = new("audio/webm", ".webm", "AudioCodec WEBM"); @@ -34,72 +35,119 @@ public class Audiofile(String name) new AudioCodec("audio/flac", ".flac", "AudioCodec FLAC") ]; - private AudioCodec? audioCodec; - private String name = name; - - public Audiofile(String name, String objectURL, AudioCodec? audioCodec, TimeSpan? duration = null) : this(name) + private AudioCodec? _audioCodec; + private String? _name; + + public Audiofile(String? name, String? objectURL, AudioCodec? audioCodec, TimeSpan? duration = null) : this() { - if (String.IsNullOrEmpty(objectURL)) - { - throw new ArgumentNullException(nameof(objectURL)); - } + Name = name; ObjectURL = objectURL; AudioCodec = audioCodec; Duration = duration; } - public String Name + public String? Name { - get => name; + get => _name; set { - if (String.IsNullOrEmpty(value)) - { - throw new ArgumentNullException(nameof(value)); - } var extension = Path.GetExtension(value); - if (extension.Equals(audioCodec?.FileExtension, StringComparison.CurrentCultureIgnoreCase) == false) + if (extension?.Equals(_audioCodec?.FileExtension, StringComparison.CurrentCultureIgnoreCase) == false) { - value = $"{value}{audioCodec?.FileExtension}"; + value = $"{value}{_audioCodec?.FileExtension}"; } - name = value; + _name = value; } } + [JsonIgnore] - public String? ObjectURL { get; private set; } + public String? ObjectURL { get; set; } + /// /// Duration of the audio file /// - public TimeSpan? Duration { get; private set; } + public TimeSpan? Duration { get; set; } public AudioCodec? AudioCodec { - get { return audioCodec; } - private set + get { return _audioCodec; } + set { - audioCodec = value; - if ((audioCodec != null) && (Name?.EndsWith(audioCodec.FileExtension) == false)) + _audioCodec = value; + if ((_audioCodec != null) && (Name?.EndsWith(_audioCodec.FileExtension) == false)) { //Replace file ending - Name = String.Format("{0}{1}", Path.GetFileNameWithoutExtension(Name), audioCodec.FileExtension); + Name = String.Format("{0}{1}", Path.GetFileNameWithoutExtension(Name), _audioCodec.FileExtension); } } } - [JsonIgnore] - public String? AudioFileType + public ICollection Tracks { get; set; } = []; + + public override ValidationResult Validate(string property) { - get + ValidationStatus validationStatus = ValidationStatus.NoValidation; + List? validationMessages = null; + switch (property) { - String? audioFileType = null; - if (AudioCodec != null) - { - audioFileType = AudioCodec.FileExtension.Replace(".", "").ToUpper(); - } - //Try to find by file name - audioFileType ??= Path.GetExtension(Name)?.Replace(".", "").ToUpper(); - return audioFileType; + case nameof(Tracks): + validationStatus = ValidationStatus.Success; + if (Tracks.Count == 0) + { + validationMessages ??= []; + validationMessages.Add(new ValidationMessage("{0} has invalid count ({1})!", nameof(Tracks), 0)); + } + else + { + //Check track overlapping + var tracksWithSamePosition = Tracks + .GroupBy(x => x.Position) + .Where(grp => grp.Count() > 1); + if (tracksWithSamePosition.Any()) + { + validationMessages ??= []; + foreach (var track in tracksWithSamePosition) + { + foreach (var trackWithSamePosition in track) + { + validationMessages.Add(new ValidationMessage("{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!", nameof(Track), nameof(Track.Position), track.Key != null ? track.Key : String.Empty, nameof(Track), trackWithSamePosition.Position != null ? trackWithSamePosition.Position : String.Empty, trackWithSamePosition.Artist ?? String.Empty, trackWithSamePosition.Title ?? String.Empty, trackWithSamePosition.Begin != null ? trackWithSamePosition.Begin : String.Empty, trackWithSamePosition.End != null ? trackWithSamePosition.End : String.Empty)); + } + } + } + foreach (var track in Tracks.OrderBy(x => x.Position)) + { + var tracksBetween = Tracks.Where(x => ((track.Begin >= x.Begin && track.Begin < x.End) + || (x.Begin < track.End && track.End <= x.End)) + && (x.Equals(track) == false)); + if (tracksBetween.Any()) + { + validationMessages ??= []; + foreach (var trackBetween in tracksBetween) + { + validationMessages.Add(new ValidationMessage("{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!", nameof(Track), track.Position != null ? track.Position : String.Empty, track.Artist ?? String.Empty, track.Title ?? String.Empty, track.Begin != null ? track.Begin : String.Empty, track.End != null ? track.End : String.Empty, trackBetween.Position != null ? trackBetween.Position : String.Empty, trackBetween.Artist ?? String.Empty, trackBetween.Title ?? String.Empty, trackBetween.Begin != null ? trackBetween.Begin : String.Empty, trackBetween.End != null ? trackBetween.End : String.Empty)); + } + } + } + } + break; + case nameof(Name): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(Name)) + { + validationMessages ??= []; + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Name))); + } + break; + case nameof(AudioCodec): + validationStatus = ValidationStatus.Success; + if (AudioCodec == null) + { + validationMessages ??= []; + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(AudioCodec))); + } + break; } + return ValidationResult.Create(validationStatus, validationMessages); } } } diff --git a/AudioCuesheetEditor/Model/IO/Export/Exportprofile.cs b/AudioCuesheetEditor/Model/IO/Export/Exportprofile.cs index 1b044244..7fc829da 100644 --- a/AudioCuesheetEditor/Model/IO/Export/Exportprofile.cs +++ b/AudioCuesheetEditor/Model/IO/Export/Exportprofile.cs @@ -15,6 +15,7 @@ //. using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.Entity; +using AudioCuesheetEditor.Model.IO.Audio; namespace AudioCuesheetEditor.Model.IO.Export { @@ -22,118 +23,101 @@ public class Exportprofile : Validateable { public static readonly String DefaultFileName = "Export.txt"; - public static readonly String SchemeCuesheetArtist; - public static readonly String SchemeCuesheetTitle; - public static readonly String SchemeCuesheetAudiofile; - public static readonly String SchemeCuesheetCDTextfile; - public static readonly String SchemeCuesheetCatalogueNumber; - public static readonly String SchemeTrackArtist; - public static readonly String SchemeTrackTitle; - public static readonly String SchemeTrackBegin; - public static readonly String SchemeTrackEnd; - public static readonly String SchemeTrackLength; - public static readonly String SchemeTrackPosition; - public static readonly String SchemeTrackFlags; - public static readonly String SchemeTrackPreGap; - public static readonly String SchemeTrackPostGap; - public static readonly String SchemeDate; - public static readonly String SchemeDateTime; - public static readonly String SchemeTime; + public static readonly String SchemeCuesheetArtist = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Artist), SchemeCharacter); + public static readonly String SchemeCuesheetTitle = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Title), SchemeCharacter); + public static readonly String SchemeAudiofileName = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Audiofile), nameof(Audiofile.Name), SchemeCharacter); + public static readonly String SchemeCuesheetCDTextfile = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.CDTextfile), SchemeCharacter); + public static readonly String SchemeCuesheetCatalogueNumber = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Cataloguenumber), SchemeCharacter); + public static readonly String SchemeTrackArtist = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Artist), SchemeCharacter); + public static readonly String SchemeTrackTitle = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Title), SchemeCharacter); + public static readonly String SchemeTrackBegin = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Begin), SchemeCharacter); + public static readonly String SchemeTrackEnd = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.End), SchemeCharacter); + public static readonly String SchemeTrackLength = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Length), SchemeCharacter); + public static readonly String SchemeTrackPosition = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Position), SchemeCharacter); + public static readonly String SchemeTrackFlags = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Flags), SchemeCharacter); + public static readonly String SchemeTrackPreGap = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.PreGap), SchemeCharacter); + public static readonly String SchemeTrackPostGap = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.PostGap), SchemeCharacter); + public static readonly String SchemeDate = String.Format("{0}Date{1}", SchemeCharacter, SchemeCharacter); + public static readonly String SchemeDateTime = String.Format("{0}DateTime{1}", SchemeCharacter, SchemeCharacter); + public static readonly String SchemeTime = String.Format("{0}Time{1}", SchemeCharacter, SchemeCharacter); - public static readonly Dictionary AvailableCuesheetSchemes; - public static readonly Dictionary AvailableTrackSchemes; - - public const String SchemeCharacter = "%"; - - private String schemeHead; - private String schemeTracks; - private String schemeFooter; - private String filename; - private String name; - - static Exportprofile() + public static readonly Dictionary AvailableCuesheetSchemes = new() { - SchemeDate = String.Format("{0}Date{1}", SchemeCharacter, SchemeCharacter); - SchemeDateTime = String.Format("{0}DateTime{1}", SchemeCharacter, SchemeCharacter); - SchemeTime = String.Format("{0}Time{1}", SchemeCharacter, SchemeCharacter); - - SchemeCuesheetArtist = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Artist), SchemeCharacter); - SchemeCuesheetTitle = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Title), SchemeCharacter); - SchemeCuesheetAudiofile = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Audiofile), SchemeCharacter); - SchemeCuesheetCDTextfile = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.CDTextfile), SchemeCharacter); - SchemeCuesheetCatalogueNumber = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Cataloguenumber), SchemeCharacter); - - AvailableCuesheetSchemes = new Dictionary - { - { nameof(Cuesheet.Artist), SchemeCuesheetArtist }, - { nameof(Cuesheet.Title), SchemeCuesheetTitle }, - { nameof(Cuesheet.Audiofile), SchemeCuesheetAudiofile }, - { nameof(Cuesheet.CDTextfile), SchemeCuesheetCDTextfile }, - { nameof(Cuesheet.Cataloguenumber), SchemeCuesheetCatalogueNumber }, - { "Date", SchemeDate }, - { "DateTime", SchemeDateTime }, - { "Time", SchemeTime } - }; + { nameof(Cuesheet.Artist), SchemeCuesheetArtist }, + { nameof(Cuesheet.Title), SchemeCuesheetTitle }, + { nameof(Cuesheet.CDTextfile), SchemeCuesheetCDTextfile }, + { nameof(Cuesheet.Cataloguenumber), SchemeCuesheetCatalogueNumber }, + { "Date", SchemeDate }, + { "DateTime", SchemeDateTime }, + { "Time", SchemeTime } + }; + public static readonly Dictionary AvailableAudiofileSchemes = new() + { + { nameof(Audiofile.Name), SchemeAudiofileName } + }; + public static readonly Dictionary AvailableTrackSchemes = new() + { + { nameof(Track.Position), SchemeTrackPosition }, + { nameof(Track.Artist), SchemeTrackArtist }, + { nameof(Track.Title), SchemeTrackTitle }, + { nameof(Track.Begin), SchemeTrackBegin }, + { nameof(Track.End), SchemeTrackEnd }, + { nameof(Track.Length), SchemeTrackLength }, + { nameof(Track.Flags), SchemeTrackFlags }, + { nameof(Track.PreGap), SchemeTrackPreGap }, + { nameof(Track.PostGap), SchemeTrackPostGap } + }; - SchemeTrackArtist = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Artist), SchemeCharacter); - SchemeTrackTitle = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Title), SchemeCharacter); - SchemeTrackBegin = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Begin), SchemeCharacter); - SchemeTrackEnd = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.End), SchemeCharacter); - SchemeTrackLength = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Length), SchemeCharacter); - SchemeTrackPosition = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Position), SchemeCharacter); - SchemeTrackFlags = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Flags), SchemeCharacter); - SchemeTrackPreGap = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.PreGap), SchemeCharacter); - SchemeTrackPostGap = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.PostGap), SchemeCharacter); + public const String SchemeCharacter = "%"; - AvailableTrackSchemes = new Dictionary() - { - { nameof(Track.Position), SchemeTrackPosition }, - { nameof(Track.Artist), SchemeTrackArtist }, - { nameof(Track.Title), SchemeTrackTitle }, - { nameof(Track.Begin), SchemeTrackBegin }, - { nameof(Track.End), SchemeTrackEnd }, - { nameof(Track.Length), SchemeTrackLength }, - { nameof(Track.Flags), SchemeTrackFlags }, - { nameof(Track.PreGap), SchemeTrackPreGap }, - { nameof(Track.PostGap), SchemeTrackPostGap } - }; - } + private String _schemeHead; + private String _schemeAudiofiles; + private String _schemeTracks; + private String _schemeFooter; + private String _filename; + private String _name; public Exportprofile() { Id = Guid.NewGuid(); - schemeHead = String.Empty; - schemeTracks = String.Empty; - schemeFooter = String.Empty; - filename = DefaultFileName; + _schemeHead = String.Empty; + _schemeTracks = String.Empty; + _schemeAudiofiles = String.Empty; + _schemeFooter = String.Empty; + _filename = DefaultFileName; var random = new Random(); - name = String.Format("{0}_{1}", nameof(Exportprofile), random.Next(1, 100)); + _name = String.Format("{0}_{1}", nameof(Exportprofile), random.Next(1, 100)); } public Guid Id { get; init; } public String Name { - get => name; - set { name = value; OnValidateablePropertyChanged(); } + get => _name; + set { _name = value; OnValidateablePropertyChanged(); } } public String SchemeHead { - get => schemeHead; - set { schemeHead = value; OnValidateablePropertyChanged(); } + get => _schemeHead; + set { _schemeHead = value; OnValidateablePropertyChanged(); } } public String SchemeTracks { - get => schemeTracks; - set { schemeTracks = value; OnValidateablePropertyChanged(); } + get => _schemeTracks; + set { _schemeTracks = value; OnValidateablePropertyChanged(); } + } + public String SchemeAudiofiles + { + get => _schemeAudiofiles; + set { _schemeAudiofiles = value; OnValidateablePropertyChanged(); } } public String SchemeFooter { - get => schemeFooter; - set { schemeFooter = value; OnValidateablePropertyChanged(); } + get => _schemeFooter; + set { _schemeFooter = value; OnValidateablePropertyChanged(); } } public String Filename { - get => filename; - set { filename = value; OnValidateablePropertyChanged(); } + get => _filename; + set { _filename = value; OnValidateablePropertyChanged(); } } public override ValidationResult Validate(string property) @@ -144,39 +128,19 @@ public override ValidationResult Validate(string property) { case nameof(SchemeHead): validationStatus = ValidationStatus.Success; - foreach (var availableScheme in AvailableTrackSchemes) - { - if (SchemeHead.Contains(availableScheme.Value) == true) - { - validationMessages ??= []; - validationMessages.Add(new ValidationMessage("{0} contains placeholder '{1}' that can not be resolved!", nameof(SchemeHead), availableScheme.Value)); - break; - } - } + validationMessages = CheckForUnresolvablePlaceholders(SchemeHead, nameof(SchemeHead), [AvailableTrackSchemes, AvailableAudiofileSchemes]); break; case nameof(SchemeTracks): validationStatus = ValidationStatus.Success; - foreach (var availableScheme in AvailableCuesheetSchemes) - { - if (SchemeTracks.Contains(availableScheme.Value) == true) - { - validationMessages ??= []; - validationMessages.Add(new ValidationMessage("{0} contains placeholder '{1}' that can not be resolved!", nameof(SchemeTracks), availableScheme.Value)); - break; - } - } + validationMessages = CheckForUnresolvablePlaceholders(SchemeTracks, nameof(SchemeTracks), [AvailableCuesheetSchemes, AvailableAudiofileSchemes]); + break; + case nameof(SchemeAudiofiles): + validationStatus = ValidationStatus.Success; + validationMessages = CheckForUnresolvablePlaceholders(SchemeAudiofiles, nameof(SchemeAudiofiles), [AvailableTrackSchemes, AvailableCuesheetSchemes]); break; case nameof(SchemeFooter): validationStatus = ValidationStatus.Success; - foreach (var availableScheme in AvailableTrackSchemes) - { - if (SchemeFooter.Contains(availableScheme.Value) == true) - { - validationMessages ??= []; - validationMessages.Add(new ValidationMessage("{0} contains placeholder '{1}' that can not be resolved!", nameof(SchemeFooter), availableScheme.Value)); - break; - } - } + validationMessages = CheckForUnresolvablePlaceholders(SchemeFooter, nameof(SchemeFooter), [AvailableTrackSchemes, AvailableAudiofileSchemes]); break; case nameof(Filename): validationStatus = ValidationStatus.Success; @@ -197,5 +161,23 @@ public override ValidationResult Validate(string property) } return ValidationResult.Create(validationStatus, validationMessages); } + + static List? CheckForUnresolvablePlaceholders(String scheme, string schemeName, List> schemesToCheck) + { + List? validationMessages = null; + foreach (var schemeToCheck in schemesToCheck) + { + foreach (var availableScheme in schemeToCheck) + { + if (scheme.Contains(availableScheme.Value) == true) + { + validationMessages ??= []; + validationMessages.Add(new ValidationMessage("{0} contains placeholder '{1}' that can not be resolved!", schemeName, availableScheme.Value)); + break; + } + } + } + return validationMessages; + } } } diff --git a/AudioCuesheetEditor/Model/IO/Import/Importprofile.cs b/AudioCuesheetEditor/Model/IO/Import/Importprofile.cs index b1f824c4..822d1a4e 100644 --- a/AudioCuesheetEditor/Model/IO/Import/Importprofile.cs +++ b/AudioCuesheetEditor/Model/IO/Import/Importprofile.cs @@ -13,7 +13,6 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.AudioCuesheet.Import; using AudioCuesheetEditor.Model.Entity; using AudioCuesheetEditor.Model.Utility; @@ -22,18 +21,17 @@ namespace AudioCuesheetEditor.Model.IO.Import { public class Importprofile : Validateable { - public static readonly IEnumerable AvailableSchemeCuesheet; - public static readonly IEnumerable AvailableSchemesTrack; + public static readonly IEnumerable AvailableSchemeCuesheet = [nameof(ImportCuesheet.Artist), nameof(ImportCuesheet.Title), nameof(ImportCuesheet.CDTextfile), nameof(ImportCuesheet.Cataloguenumber)]; + + public static readonly IEnumerable AvailableSchemeAudiofiles = [nameof(ImportAudiofile.Name)]; + + public static readonly IEnumerable AvailableSchemesTrack = [nameof(ImportTrack.Artist), nameof(ImportTrack.Title), nameof(ImportTrack.Begin), nameof(ImportTrack.End), nameof(ImportTrack.Length), nameof(ImportTrack.Position), nameof(ImportTrack.Flags), nameof(ImportTrack.PreGap), nameof(ImportTrack.PostGap), nameof(ImportTrack.StartDateTime)]; - static Importprofile() - { - AvailableSchemeCuesheet = [nameof(Cuesheet.Artist), nameof(Cuesheet.Title), nameof(Cuesheet.Audiofile), nameof(Cuesheet.CDTextfile), nameof(Cuesheet.Cataloguenumber)]; - AvailableSchemesTrack = [nameof(Track.Artist), nameof(Track.Title), nameof(Track.Begin), nameof(Track.End), nameof(Track.Length), nameof(Track.Position), nameof(Track.Flags), nameof(Track.PreGap), nameof(Track.PostGap), nameof(ImportTrack.StartDateTime)]; - } public Guid Id { get; init; } = Guid.NewGuid(); public String? Name { get; set; } public Boolean UseRegularExpression { get; set; } public String? SchemeCuesheet { get; set; } + public String? SchemeAudiofiles { get; set; } public String? SchemeTracks { get; set; } public TimeSpanFormat? TimeSpanFormat { get; set; } public override ValidationResult Validate(string property) @@ -53,7 +51,7 @@ public override ValidationResult Validate(string property) do { containsPlaceHolder = SchemeCuesheet?.Contains(enumerator.Current) == true; - } while ((containsPlaceHolder == false) && (enumerator.MoveNext())); + } while ((containsPlaceHolder == false) && enumerator.MoveNext()); } if (containsPlaceHolder == false) { @@ -62,6 +60,26 @@ public override ValidationResult Validate(string property) } } break; + case nameof(SchemeAudiofiles): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(SchemeAudiofiles) == false) + { + var containsPlaceHolder = false; + var enumerator = AvailableSchemeAudiofiles.GetEnumerator(); + if (enumerator.MoveNext()) + { + do + { + containsPlaceHolder = SchemeAudiofiles?.Contains(enumerator.Current) == true; + } while ((containsPlaceHolder == false) && enumerator.MoveNext()); + } + if (containsPlaceHolder == false) + { + validationMessages ??= []; + validationMessages.Add(new ValidationMessage("{0} contains no placeholder!", nameof(SchemeAudiofiles))); + } + } + break; case nameof(SchemeTracks): validationStatus = ValidationStatus.Success; if (String.IsNullOrEmpty(SchemeTracks) == false) @@ -73,7 +91,7 @@ public override ValidationResult Validate(string property) do { containsPlaceHolder = SchemeTracks?.Contains(enumerator.Current) == true; - } while ((containsPlaceHolder == false) && (enumerator.MoveNext())); + } while ((containsPlaceHolder == false) && enumerator.MoveNext()); } if (containsPlaceHolder == false) { diff --git a/AudioCuesheetEditor/Model/IO/Projectfile.cs b/AudioCuesheetEditor/Model/IO/Projectfile.cs index a1e187bc..767de0fc 100644 --- a/AudioCuesheetEditor/Model/IO/Projectfile.cs +++ b/AudioCuesheetEditor/Model/IO/Projectfile.cs @@ -39,9 +39,13 @@ public class Projectfile(Cuesheet cuesheet) var cuesheet = JsonSerializer.Deserialize(fileContent, Options); if (cuesheet != null) { - foreach (var track in cuesheet.Tracks) + foreach (var audiofile in cuesheet.Audiofiles) { - track.Cuesheet = cuesheet; + foreach (var track in audiofile.Tracks) + { + track.Audiofile = audiofile; + track.Cuesheet = cuesheet; + } } } return cuesheet; diff --git a/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs b/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs index 6773e70c..06ef0b15 100644 --- a/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs +++ b/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs @@ -41,7 +41,6 @@ public CultureInfo Culture } public TimeSpanFormat? TimeSpanFormat { get; set; } public Boolean DefaultIsLinkedToPreviousTrack { get; set; } = true; - public Boolean FixedTracksTableHeader { get; set; } = false; public String? DisplayTimeSpanFormat { get; set; } public LogLevel MinimumLogLevel { get; set; } = DefaultLogLevel; } diff --git a/AudioCuesheetEditor/Model/Options/ImportOptions.cs b/AudioCuesheetEditor/Model/Options/ImportOptions.cs index 0b186b9f..533c0771 100644 --- a/AudioCuesheetEditor/Model/Options/ImportOptions.cs +++ b/AudioCuesheetEditor/Model/Options/ImportOptions.cs @@ -25,7 +25,8 @@ public class ImportOptions : IOptions { Name = "Textfile (common data in first line)", UseRegularExpression = false, - SchemeCuesheet = $"{nameof(ImportCuesheet.Artist)} - {nameof(ImportCuesheet.Title)}\t{nameof(ImportCuesheet.Audiofile)}", + SchemeCuesheet = $"{nameof(ImportCuesheet.Artist)} - {nameof(ImportCuesheet.Title)}", + SchemeAudiofiles = $"- {nameof(ImportAudiofile.Name)}", SchemeTracks = $"{nameof(ImportTrack.Artist)} - {nameof(ImportTrack.Title)}\t{nameof(ImportTrack.End)}" }; diff --git a/AudioCuesheetEditor/Pages/Index.razor b/AudioCuesheetEditor/Pages/Index.razor index 445ba103..a8b75db0 100644 --- a/AudioCuesheetEditor/Pages/Index.razor +++ b/AudioCuesheetEditor/Pages/Index.razor @@ -27,8 +27,8 @@ along with Foobar. If not, see @inject ISnackbar _snackbar @inject ICuesheetManager _cuesheetManager - - + + @@ -42,14 +42,14 @@ along with Foobar. If not, see @code{ - ViewOptions? options; - ViewMode currentViewmode = ViewMode.DetailView; + ViewOptions? _viewOptions; + ViewMode _currentViewmode = ViewMode.DetailView; protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - options = await base.LocalStorageOptionsProvider.GetOptionsAsync(); - currentViewmode = options.ActiveTab; + _viewOptions = await base.LocalStorageOptionsProvider.GetOptionsAsync(); + _currentViewmode = _viewOptions.ActiveTab; base.LocalStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionSaved; _importManager.UploadFilesFinished += ImportManager_UploadFilesFinished; } @@ -66,16 +66,16 @@ along with Foobar. If not, see async Task ActiveTabIndexChanged(int tabIndex) { - currentViewmode = (ViewMode)tabIndex; - await LocalStorageOptionsProvider.SaveOptionsValueAsync(x => x.ActiveTab, currentViewmode); + _currentViewmode = (ViewMode)tabIndex; + await LocalStorageOptionsProvider.SaveOptionsValueAsync(x => x.ActiveTab, _currentViewmode); } void LocalStorageOptionsProvider_OptionSaved(object? sender, IOptions option) { if (option is ViewOptions viewOptions) { - options = viewOptions; - currentViewmode = options.ActiveTab; + _viewOptions = viewOptions; + _currentViewmode = _viewOptions.ActiveTab; StateHasChanged(); } } @@ -113,10 +113,6 @@ along with Foobar. If not, see await LocalStorageOptionsProvider.SaveOptionsValueAsync(x => x.ActiveTab, ViewMode.ImportView); break; } - if (_sessionStateContainer.ImportAudiofile != null) - { - _cuesheetManager.SetProperty(x => x.Audiofile, _sessionStateContainer.ImportAudiofile); - } } finally { diff --git a/AudioCuesheetEditor/Program.cs b/AudioCuesheetEditor/Program.cs index 031766d3..0f2f478f 100644 --- a/AudioCuesheetEditor/Program.cs +++ b/AudioCuesheetEditor/Program.cs @@ -24,7 +24,6 @@ using AudioCuesheetEditor.Services.UI; using AudioCuesheetEditor.Services.Validation; using BlazorDownloadFile; -using Howler.Blazor.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MudBlazor.Services; @@ -41,21 +40,11 @@ config.PopoverOptions.OverflowPadding = 0; }); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - builder.Services.AddBlazorDownloadFile(); builder.Services.AddScoped(); builder.Services.AddScoped(); - -builder.Services.AddScoped(x => -{ - var localStorageOptionsProvider = x.GetRequiredService(); - var sessionStateContainer = new SessionStateContainer(localStorageOptionsProvider); - _ = sessionStateContainer.InitializeAsync(); - return sessionStateContainer; -}); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -71,6 +60,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddLogging(); // Read out configuration for loglevel @@ -83,5 +73,5 @@ var host = builder.Build(); await host.SetCultureFromConfigurationAsync(); - +await host.InitializeSessionStateContainer(); await host.RunAsync(); \ No newline at end of file diff --git a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs index 68142028..04f785ea 100644 --- a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs +++ b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs @@ -16,21 +16,21 @@ using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Services.UI; -using Howler.Blazor.Components; +using Microsoft.JSInterop; namespace AudioCuesheetEditor.Services.Audio { - public class PlaybackService : IDisposable + public class PlaybackService(IJSRuntime jsRuntime, ISessionStateContainer sessionStateContainer) : IAsyncDisposable { - private readonly ISessionStateContainer _sessionStateContainer; - private readonly IHowl _howl; + private readonly ISessionStateContainer _sessionStateContainer = sessionStateContainer; + private readonly IJSRuntime _jsRuntime = jsRuntime; - private int? _currentPlayingSoundId; private Audiofile? _currentlyPlayingAudiofile; private Timer? _updateTimer; - private bool _disposedValue; private readonly Lock _timerLock = new(); private TimeSpan? _currentPosition; + private DotNetObjectReference? _dotNetObjectReference; + private TimeSpan? _audiofileDurationsBeforeCurrentlyPlayingAudiofile; public event Action? CurrentPositionChanged; @@ -46,64 +46,53 @@ private set } } } - public Track? CurrentlyPlayingTrack => _sessionStateContainer.Cuesheet.Tracks.SingleOrDefault(x => x.Begin.HasValue == true && x.End.HasValue == true && x.Begin <= CurrentPosition && x.End > CurrentPosition); - public TimeSpan? TotalTime => _sessionStateContainer.Cuesheet.Audiofile?.Duration; - public Boolean IsPlaying { get; private set; } = false; - public Boolean IsPlaybackPossible + public Track? CurrentlyPlayingTrack => _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).SingleOrDefault(x => x.Begin.HasValue == true && x.End.HasValue == true && x.Begin <= CurrentPosition && x.End > CurrentPosition); + public TimeSpan? TotalTime { get { - var audiofile = _sessionStateContainer.Cuesheet.Audiofile; - return String.IsNullOrEmpty(audiofile?.ObjectURL) == false && String.IsNullOrEmpty(audiofile?.AudioFileType) == false; + var durations = _sessionStateContainer.Cuesheet.Audiofiles.Where(a => a.Duration.HasValue).Select(x => x.Duration); + if (durations.Any()) + { + return durations.Aggregate((sum, a) => sum + a); + } + return null; } } - public Boolean IsPreviousPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Tracks.FirstOrDefault(x => x.End <= CurrentlyPlayingTrack.Begin) != null; - public Boolean IsNextPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Tracks.FirstOrDefault(x => x.Begin >= CurrentlyPlayingTrack.End) != null; + public Boolean IsPaused { get; private set; } = false; + public Boolean IsPlaybackPossible => _sessionStateContainer.Cuesheet.Audiofiles.Any(x => string.IsNullOrEmpty(x.ObjectURL) == false); + public Boolean IsPreviousPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.End <= CurrentlyPlayingTrack.Begin) != null; + public Boolean IsNextPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Begin >= CurrentlyPlayingTrack.End) != null; + public Boolean IsPlaying => _currentlyPlayingAudiofile != null; - public PlaybackService(ISessionStateContainer sessionStateContainer, IHowl howl) + public async Task InitializeAsync() { - _sessionStateContainer = sessionStateContainer; - _howl = howl; - _howl.OnPlay += Howl_OnPlay; - _howl.OnPause += Howl_OnPause; - _howl.OnEnd += Howl_OnEnd; - _howl.OnStop += Howl_OnStop; + if (_dotNetObjectReference == null) + { + _dotNetObjectReference = DotNetObjectReference.Create(this); + await _jsRuntime.InvokeVoidAsync("audioInterop.register", _dotNetObjectReference); + } } public async Task PlayOrPauseAsync() { - //Reset if the last played audiofile is not the current one - if (_currentlyPlayingAudiofile != _sessionStateContainer.Cuesheet.Audiofile) + if (_currentlyPlayingAudiofile != null) { - _currentPlayingSoundId = null; - } - //If the current audiofile already started, we just pause - if (_currentPlayingSoundId != null) - { - await _howl.Pause(_currentPlayingSoundId.Value); + if (IsPaused == false) + { + await _jsRuntime.InvokeVoidAsync("audioInterop.pauseAudio"); + } + else + { + await _jsRuntime.InvokeVoidAsync("audioInterop.playAudio"); + } } else { - if (IsPlaybackPossible) + var audiofileToPlay = _sessionStateContainer.Cuesheet.Audiofiles.FirstOrDefault(x => string.IsNullOrEmpty(x.ObjectURL) == false); + if (audiofileToPlay != null) { - string[]? sources = null; - string[]? formats = null; - if (_sessionStateContainer.Cuesheet.Audiofile?.ObjectURL != null) - { - sources = [_sessionStateContainer.Cuesheet.Audiofile.ObjectURL]; - } - if (_sessionStateContainer.Cuesheet.Audiofile?.AudioFileType != null) - { - formats = [_sessionStateContainer.Cuesheet.Audiofile.AudioFileType.ToLower()]; - } - var options = new HowlOptions - { - Sources = sources, - Formats = formats, - Html5 = true - }; - _currentPlayingSoundId = await _howl.Play(options); - _currentlyPlayingAudiofile = _sessionStateContainer.Cuesheet.Audiofile; + await PlayAsync(audiofileToPlay); } } } @@ -112,30 +101,21 @@ public async Task PlayAsync(Track trackToPlay) { if (trackToPlay?.Begin.HasValue == true) { - if (IsPlaying == false) - { - await PlayOrPauseAsync(); - } - if (_currentPlayingSoundId.HasValue) - { - await _howl.Seek(_currentPlayingSoundId.Value, trackToPlay.Begin.Value); - } + await SeekAsync(trackToPlay.Begin.Value); } } public async Task StopAsync() { - if (_currentPlayingSoundId != null) - { - await _howl.Stop(_currentPlayingSoundId.Value); - } + Reset(); + await _jsRuntime.InvokeVoidAsync("audioInterop.stopAudio"); } public async Task PlayNextTrackAsync() { if (CurrentlyPlayingTrack != null) { - var trackToPlay = _sessionStateContainer.Cuesheet.Tracks.FirstOrDefault(x => x.Begin >= CurrentlyPlayingTrack.End); + var trackToPlay = _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Begin >= CurrentlyPlayingTrack.End); if (trackToPlay != null) { await PlayAsync(trackToPlay); @@ -147,7 +127,7 @@ public async Task PlayPreviousTrackAsync() { if (CurrentlyPlayingTrack != null) { - var trackToPlay = _sessionStateContainer.Cuesheet.Tracks.LastOrDefault(x => x.End <= CurrentlyPlayingTrack.Begin); + var trackToPlay = _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).LastOrDefault(x => x.End <= CurrentlyPlayingTrack.Begin); if (trackToPlay != null) { await PlayAsync(trackToPlay); @@ -157,92 +137,163 @@ public async Task PlayPreviousTrackAsync() public async Task SeekAsync(TimeSpan time) { - if (_currentPlayingSoundId.HasValue == false) + TimeSpan cumulativeDuration = TimeSpan.Zero; + Audiofile? targetAudiofile = null; + TimeSpan targetPositionInAudiofile = TimeSpan.Zero; + + foreach (var audiofile in _sessionStateContainer.Cuesheet.Audiofiles) { - await PlayOrPauseAsync(); + if (string.IsNullOrEmpty(audiofile.ObjectURL)) + { + continue; + } + + if (audiofile.Duration.HasValue) + { + TimeSpan nextCumulativeDuration = cumulativeDuration + audiofile.Duration.Value; + + if (time >= cumulativeDuration && time < nextCumulativeDuration) + { + targetAudiofile = audiofile; + targetPositionInAudiofile = time - cumulativeDuration; + break; + } + + cumulativeDuration = nextCumulativeDuration; + } } - if (_currentPlayingSoundId.HasValue) + + if (targetAudiofile == null) { - if (IsPlaying == false) + targetAudiofile = _sessionStateContainer.Cuesheet.Audiofiles.FirstOrDefault(x => string.IsNullOrEmpty(x.ObjectURL) == false); + + if (targetAudiofile == null) { - await PlayOrPauseAsync(); + return; } - await _howl.Seek(_currentPlayingSoundId.Value, time); } + + if (_currentlyPlayingAudiofile != targetAudiofile) + { + await PlayAsync(targetAudiofile); + } + + if (IsPaused) + { + await PlayOrPauseAsync(); + } + + await _jsRuntime.InvokeVoidAsync("audioInterop.seekAudio", targetPositionInAudiofile.TotalSeconds); } - public void Dispose() + [JSInvokable] + public void OnPlaybackStarted() { - // Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in der Methode "Dispose(bool disposing)" ein. - Dispose(disposing: true); - GC.SuppressFinalize(this); + IsPaused = false; + StartTimer(); } - protected virtual void Dispose(bool disposing) + [JSInvokable] + public void OnPlaybackEnded(string objectUrlEnded) { - if (!_disposedValue) + var audioFileEnded = _sessionStateContainer.Cuesheet.Audiofiles.Single(x => x.ObjectURL == objectUrlEnded); + var index = _sessionStateContainer.Cuesheet.Audiofiles.IndexOf(audioFileEnded); + if (index < _sessionStateContainer.Cuesheet.Audiofiles.Count - 1) { - if (disposing) + var nextAudioFile = _sessionStateContainer.Cuesheet.Audiofiles.Where(x => string.IsNullOrEmpty(x.ObjectURL) == false).Skip(index + 1).FirstOrDefault(); + if (nextAudioFile != null) { - _howl.OnPlay -= Howl_OnPlay; - _howl.OnPause -= Howl_OnPause; - _howl.OnEnd -= Howl_OnEnd; - _howl.OnStop -= Howl_OnStop; + _ = PlayAsync(nextAudioFile); } - _disposedValue = true; + else + { + // No more audio files to play, we stop playback + Reset(); + } + } + else + { + // No more audio files to play, we stop playback + Reset(); } } - private void Howl_OnStop(Howler.Blazor.Components.Events.HowlEventArgs obj) + [JSInvokable] + public void OnPlaybackPaused() { - IsPlaying = false; - _currentPlayingSoundId = null; + IsPaused = true; StopTimer(); - CurrentPosition = null; - _currentlyPlayingAudiofile = _sessionStateContainer.Cuesheet.Audiofile; + UpdateCurrentPosition(null); } - private void Howl_OnEnd(Howler.Blazor.Components.Events.HowlEventArgs obj) + public async ValueTask DisposeAsync() { - IsPlaying = false; - StopTimer(); - CurrentPosition = null; + GC.SuppressFinalize(this); + await _jsRuntime.InvokeVoidAsync("audioInterop.unregister"); + _dotNetObjectReference?.Dispose(); } - private void Howl_OnPause(Howler.Blazor.Components.Events.HowlEventArgs obj) + async Task PlayAsync(Audiofile audiofileToPlay) { - IsPlaying = false; - StopTimer(); + await _jsRuntime.InvokeVoidAsync("audioInterop.setAudioSource", audiofileToPlay.ObjectURL); + await _jsRuntime.InvokeVoidAsync("audioInterop.playAudio"); + _currentlyPlayingAudiofile = audiofileToPlay; + _audiofileDurationsBeforeCurrentlyPlayingAudiofile = null; } - private void Howl_OnPlay(Howler.Blazor.Components.Events.HowlPlayEventArgs obj) + void Reset() { - IsPlaying = true; - StartTimer(); + StopTimer(); + _currentlyPlayingAudiofile = null; + _audiofileDurationsBeforeCurrentlyPlayingAudiofile = null; + CurrentPosition = null; + IsPaused = false; } - private void StartTimer() + void StartTimer() { _updateTimer ??= new Timer(UpdateCurrentPosition, null, 0, 500); } - private void StopTimer() + void StopTimer() { _updateTimer?.Dispose(); _updateTimer = null; } - private async void UpdateCurrentPosition(object? state) + async void UpdateCurrentPosition(object? state) { - // Thread-safe access lock (_timerLock) { - if (_currentPlayingSoundId == null || !IsPlaying) return; + if (_currentlyPlayingAudiofile == null) + { + StopTimer(); + } + } + CalculateDurationsBeforeCurrentlyPlayingAudiofile(); + var currentSecondsInCurrentlyPlayingAudiofile = await _jsRuntime.InvokeAsync("audioInterop.getAudioCurrentTime"); + if (_audiofileDurationsBeforeCurrentlyPlayingAudiofile.HasValue) + { + CurrentPosition = _audiofileDurationsBeforeCurrentlyPlayingAudiofile + TimeSpan.FromSeconds(currentSecondsInCurrentlyPlayingAudiofile); + } + else + { + CurrentPosition = TimeSpan.FromSeconds(currentSecondsInCurrentlyPlayingAudiofile); + } + } + + void CalculateDurationsBeforeCurrentlyPlayingAudiofile() + { + if ((_audiofileDurationsBeforeCurrentlyPlayingAudiofile != null) || (_currentlyPlayingAudiofile == null)) + { + return; } - CurrentPosition = await _howl.GetCurrentTime(_currentPlayingSoundId.Value); - if (_sessionStateContainer.Cuesheet.Audiofile != _currentlyPlayingAudiofile) + _audiofileDurationsBeforeCurrentlyPlayingAudiofile = TimeSpan.Zero; + var index = _sessionStateContainer.Cuesheet.Audiofiles.IndexOf(_currentlyPlayingAudiofile); + for (int i = 0; i < index; i++) { - await _howl.Stop(_currentPlayingSoundId.Value); + var audiofile = _sessionStateContainer.Cuesheet.Audiofiles[i]; + _audiofileDurationsBeforeCurrentlyPlayingAudiofile += audiofile.Duration; } } } diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs new file mode 100644 index 00000000..a169a3cb --- /dev/null +++ b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs @@ -0,0 +1,224 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO.Audio; +using AudioCuesheetEditor.Services.IO; +using AudioCuesheetEditor.Services.UI; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.JSInterop; +using System.Linq.Expressions; +using System.Reflection; + +namespace AudioCuesheetEditor.Services.AudioCuesheet +{ + public class AudiofileManager(IFileInputManager fileInputManager, ITraceChangeManager traceChangeManager, IJSRuntime jsRuntime, ITrackManager trackManager, ISessionStateContainer sessionStateContainer) : IAudiofileManager + { + private readonly IFileInputManager _fileInputManager = fileInputManager; + private readonly ITraceChangeManager _traceChangeManager = traceChangeManager; + private readonly IJSRuntime _jsRuntime = jsRuntime; + private readonly ITrackManager _trackManager = trackManager; + private readonly ISessionStateContainer _sessionStateContainer = sessionStateContainer; + + /// + public async Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile? browserFile, string fileInputId) + { + _traceChangeManager.BulkEdit = true; + if (browserFile == null) + { + if (string.IsNullOrEmpty(audiofile.ObjectURL) == false) + { + await _jsRuntime.InvokeVoidAsync("revokeAudioObjectURL", audiofile.ObjectURL); + } + SetValue(audiofile, x => x.AudioCodec, null); + SetValue(audiofile, x => x.Name, null); + SetValue(audiofile, x => x.ObjectURL, null); + SetValue(audiofile, x => x.Duration, null); + } + else + { + var codec = _fileInputManager.GetAudioCodec(browserFile.ContentType, browserFile.Name); + var objectUrl = await _fileInputManager.GetObjectUrlAsync(fileInputId); + TimeSpan? duration = null; + if (String.IsNullOrEmpty(objectUrl) == false) + { + var durationSeconds = await _jsRuntime.InvokeAsync("getAudioDurationFromFile", objectUrl); + duration = TimeSpan.FromSeconds(durationSeconds); + } + SetValue(audiofile, x => x.AudioCodec, codec); + SetValue(audiofile, x => x.Name, browserFile.Name); + SetValue(audiofile, x => x.ObjectURL, objectUrl); + SetValue(audiofile, x => x.Duration, duration); + SetLastTrackEnd(audiofile); + } + _traceChangeManager.BulkEdit = false; + } + + /// + public void SetProperty(Audiofile audiofile, Expression> propertyExpression, TProperty value) + { + SetValue(audiofile, propertyExpression, value); + SetLastTrackEnd(audiofile); + } + + /// + public void AddTrack(Audiofile audiofile, Track track, Boolean setTracing = true) + { + if (setTracing) + { + _traceChangeManager.BulkEdit = true; + } + var cuesheet = _sessionStateContainer.GetActiveCuesheet(); + track.Cuesheet = cuesheet; + track.Audiofile = audiofile; + if ((cuesheet?.IsRecording == true) && cuesheet.Audiofiles.SelectMany(x => x.Tracks).Any(x => x.Position >= 1)) + { + _trackManager.SetProperty(track, x => x.Begin, DateTime.UtcNow - cuesheet.RecordingStart); + } + var lastTrack = GetLastTrack(audiofile); + if ((audiofile.Duration.HasValue == true) && (lastTrack?.End.HasValue == true) && (lastTrack.End == audiofile.Duration)) + { + _trackManager.SetProperty(lastTrack, x => x.End, null); + } + var newValue = new List(audiofile.Tracks) + { + track + }; + SetValue(audiofile, x => x.Tracks, newValue); + RecalculateTrackProperties(cuesheet!); + if (setTracing) + { + _traceChangeManager.BulkEdit = false; + } + } + + /// + public void RemoveTracks(Audiofile audiofile, IEnumerable tracksToRemove, Boolean setTracing = true) + { + var cuesheet = _sessionStateContainer.GetActiveCuesheet(); + var intersection = audiofile.Tracks.Intersect(tracksToRemove); + foreach (var track in intersection) + { + track.Audiofile = null; + } + ICollection newValue = [.. audiofile.Tracks.Except(intersection)]; + if (setTracing) + { + _traceChangeManager.BulkEdit = true; + } + SetValue(audiofile, x => x.Tracks, newValue); + RecalculateTrackProperties(cuesheet!); + if (setTracing) + { + _traceChangeManager.BulkEdit = false; + } + } + + void SetValue(Audiofile audiofile, Expression> propertyExpression, TProperty value) + { + if (propertyExpression.Body is not MemberExpression memberExpression) + { + throw new ArgumentException("Expression must be a property"); + } + + if (memberExpression.Member is not PropertyInfo propertyInfo) + { + throw new ArgumentException("Member is not a property"); + } + + var previousValue = (TProperty?)propertyInfo.GetValue(audiofile); + if (Equals(previousValue, value)) + { + return; + } + + propertyInfo.SetValue(audiofile, value); + _traceChangeManager.AddChange(new(audiofile, new(previousValue, propertyInfo.Name))); + } + + void RecalculateTrackProperties(Cuesheet cuesheet) + { + // Unset first track begin + var firstTrack = GetFirstTrack(cuesheet); + if (firstTrack?.Begin == TimeSpan.Zero) + { + _trackManager.SetProperty(firstTrack, x => x.Begin, null); + } + // Recalculate position, begin and end ascending + ushort position = 1; + foreach (var audiofile in cuesheet.Audiofiles) + { + foreach (var track in audiofile.Tracks) + { + if (track.Position != position) + { + _trackManager.SetProperty(track, x => x.Position, position); + } + var previousTrack = _trackManager.GetPreviousLinkedTrack(track); + if (previousTrack?.End.HasValue == true) + { + _trackManager.SetProperty(track, x => x.Begin, previousTrack.End); + } + else + { + if (previousTrack != null) + { + _trackManager.SetProperty(previousTrack, x => x.End, track.Begin); + } + } + position++; + } + } + // Set first track begin + firstTrack = GetFirstTrack(cuesheet); + if (firstTrack?.Begin.HasValue == false) + { + _trackManager.SetProperty(firstTrack, x => x.Begin, TimeSpan.Zero); + } + // Set track ends based on audiofile duration + foreach (var audiofile in cuesheet.Audiofiles) + { + SetLastTrackEnd(audiofile); + } + } + + Track? GetLastTrack(Audiofile audiofile) + { + return audiofile.Tracks.OrderByDescending(x => x.Position.HasValue).ThenBy(x => x.Position) + .ThenByDescending(x => x.Begin.HasValue).ThenBy(x => x.Begin) + .ThenByDescending(x => x.End.HasValue).ThenBy(x => x.End) + .LastOrDefault(); + } + + void SetLastTrackEnd(Audiofile audiofile) + { + var lastTrack = GetLastTrack(audiofile); + if ((lastTrack?.End.HasValue == false) && (audiofile.Duration.HasValue == true)) + { + _trackManager.SetProperty(lastTrack, x => x.End, audiofile.Duration); + } + } + + static Track? GetFirstTrack(Cuesheet cuesheet) + { + return cuesheet.Audiofiles.SelectMany(x => x.Tracks) + .OrderByDescending(x => x.Position.HasValue).ThenBy(x => x.Position) + .ThenByDescending(x => x.Begin.HasValue).ThenBy(x => x.Begin) + .ThenByDescending(x => x.End.HasValue).ThenBy(x => x.End) + .FirstOrDefault(); + } + + } +} diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/CuesheetManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/CuesheetManager.cs index 6f45baa8..5ba287d8 100644 --- a/AudioCuesheetEditor/Services/AudioCuesheet/CuesheetManager.cs +++ b/AudioCuesheetEditor/Services/AudioCuesheet/CuesheetManager.cs @@ -14,18 +14,22 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Services.UI; +using Microsoft.JSInterop; using System.Linq.Expressions; using System.Reflection; namespace AudioCuesheetEditor.Services.AudioCuesheet { /// - public class CuesheetManager(ITraceChangeManager traceChangeManager, ISessionStateContainer sessionStateContainer, ITrackManager trackManager) : ICuesheetManager + public class CuesheetManager(ITraceChangeManager traceChangeManager, ISessionStateContainer sessionStateContainer, ITrackManager trackManager, IAudiofileManager audiofileManager, IJSRuntime jsRuntime) : ICuesheetManager { private readonly ITraceChangeManager _traceChangeManager = traceChangeManager; private readonly ISessionStateContainer _sessionStateContainer = sessionStateContainer; private readonly ITrackManager _trackManager = trackManager; + private readonly IAudiofileManager _audiofileManager = audiofileManager; + private readonly IJSRuntime _jsRuntime = jsRuntime; public event EventHandler? IsRecordingChanged; @@ -34,13 +38,7 @@ public void SetProperty(Expression> propert { _traceChangeManager.BulkEdit = true; var cuesheet = _sessionStateContainer.GetActiveCuesheet(); - var audiofile = cuesheet?.Audiofile; SetValue(cuesheet!, propertyExpression, value); - // If audiofile has been set, we need to calculate last track end - if (audiofile != cuesheet?.Audiofile) - { - SetLastTrackEnd(cuesheet!); - } _traceChangeManager.BulkEdit = false; } @@ -49,7 +47,11 @@ public Result IsRecordingPossible { get { - if (_sessionStateContainer.Cuesheet.Tracks.Any()) + if (_sessionStateContainer.Cuesheet.IsRecording == true) + { + return Result.Failure(new Error(ErrorType.NotPossible, "Record is already running!")); + } + if (_sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).Any()) { return Result.Failure(new Error(ErrorType.NotPossible, "Cuesheet already contains tracks!")); } @@ -69,6 +71,10 @@ public Result StartRecording() return Result.Failure(new Error(ErrorType.NotPossible, "Record is already running!")); } cuesheet.RecordingStart = DateTime.UtcNow; + if (cuesheet.Audiofiles.Count == 0) + { + cuesheet.Audiofiles.Add(new Audiofile()); + } IsRecordingChanged?.Invoke(this, EventArgs.Empty); return Result.Success(); } @@ -81,7 +87,7 @@ public void StopRecording() var cuesheet = _sessionStateContainer.Cuesheet; if (cuesheet.IsRecording == true) { - var lastTrack = cuesheet.Tracks.LastOrDefault(); + var lastTrack = GetLastTrack(cuesheet); if ((lastTrack != null) && cuesheet.RecordingStart.HasValue) { lastTrack.End = DateTime.UtcNow - cuesheet.RecordingStart.Value; @@ -92,145 +98,186 @@ public void StopRecording() } /// - public void AddTrack(Track track) + public bool IsMoveUpPossible(HashSet selectedTracks) => selectedTracks.Count > 0 && selectedTracks.Min(x => x.Position) >= 2; + + /// + public bool IsMoveUpPossible(HashSet selectedAudiofiles) { + if (selectedAudiofiles.Count == 0) + { + return false; + } var cuesheet = _sessionStateContainer.GetActiveCuesheet(); - track.Cuesheet = cuesheet; - // Calculate track properties - _traceChangeManager.BulkEdit = true; - if (cuesheet?.IsRecording == true) + if (cuesheet?.Audiofiles.Count > 0) { - _trackManager.SetProperty(track, x => x.Begin, DateTime.UtcNow - cuesheet.RecordingStart); + return !selectedAudiofiles.Contains(cuesheet.Audiofiles.First()); } - if (cuesheet?.Tracks.Any() == false) + return false; + } + + /// + public bool IsMoveDownPossible(HashSet selectedTracks) => selectedTracks.Count > 0 && selectedTracks.Max(x => x.Position) < _sessionStateContainer.GetActiveCuesheet()?.Audiofiles.SelectMany(x => x.Tracks).Max(x => x.Position); + + /// + public bool IsMoveDownPossible(HashSet selectedAudiofiles) + { + if (selectedAudiofiles.Count == 0) { - _trackManager.SetProperty(track, x => x.Position, (ushort)(1)); - if ((track.Begin.HasValue == false) || cuesheet.IsRecording) - { - _trackManager.SetProperty(track, x => x.Begin, TimeSpan.Zero); - } + return false; } - else + var cuesheet = _sessionStateContainer.GetActiveCuesheet(); + if (cuesheet?.Audiofiles.Count > 0) { - var lastTrack = GetLastTrack(cuesheet!); - if ((cuesheet?.Audiofile?.Duration.HasValue == true) && (lastTrack?.End.HasValue == true) && (lastTrack.End == cuesheet.Audiofile.Duration)) - { - _trackManager.SetProperty(lastTrack, x => x.End, null); - } - if (track.Position.HasValue == false) - { - _trackManager.SetProperty(track, x => x.Position, (ushort?)(lastTrack?.Position + 1)); - } - if (track.Begin.HasValue == false) + return !selectedAudiofiles.Contains(cuesheet.Audiofiles.Last()); + } + return false; + } + + /// + public Result MoveUp(HashSet selectedTracks) + { + if (IsMoveUpPossible(selectedTracks) == false) + { + return Result.Failure(new Error(ErrorType.NotPossible, "Moving tracks up is not possible!")); + } + _traceChangeManager.BulkEdit = true; + var cuesheet = _sessionStateContainer.GetActiveCuesheet(); + foreach (var selectedTrack in selectedTracks.OrderBy(x => x.Position)) + { + var previousTrack = cuesheet?.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Position == selectedTrack.Position - 1); + if (previousTrack?.Audiofile != null && previousTrack.Audiofile != selectedTrack.Audiofile) { - _trackManager.SetProperty(track, x => x.Begin, lastTrack?.End); + SwitchAudiofile(selectedTrack, previousTrack.Audiofile); } else { - if (lastTrack?.End.HasValue == false) + var newBegin = previousTrack?.Begin; + var newEnd = previousTrack?.End; + if (previousTrack != null) { - _trackManager.SetProperty(lastTrack, x => x.End, track.Begin); + _trackManager.SetProperty(previousTrack, x => x.Position, selectedTrack.Position); + _trackManager.SetProperty(previousTrack, x => x.Begin, selectedTrack.Begin); + _trackManager.SetProperty(previousTrack, x => x.End, selectedTrack.End); } + _trackManager.SetProperty(selectedTrack, x => x.Position, (ushort?)(selectedTrack.Position - 1)); + _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin); + _trackManager.SetProperty(selectedTrack, x => x.End, newEnd); } - if (cuesheet?.IsRecording == true && lastTrack != null) - { - _trackManager.SetProperty(lastTrack, x => x.End, track.Begin); - } + } - var newValue = new List(cuesheet!.Tracks) + foreach (var audiofile in cuesheet!.Audiofiles) { - track - }; - SetValue(cuesheet, x => x.Tracks, newValue); - SetLastTrackEnd(cuesheet); + var orderedTracks = audiofile.Tracks.OrderBy(x => x.Position).ToList(); + _audiofileManager.SetProperty(audiofile, x => x.Tracks, orderedTracks); + } _traceChangeManager.BulkEdit = false; + return Result.Success(); } /// - public void RemoveTracks(IEnumerable tracksToRemove) + public Result MoveUp(HashSet selectedAudiofiles) { + if (IsMoveUpPossible(selectedAudiofiles) == false) + { + return Result.Failure(new Error(ErrorType.NotPossible, "Moving audiofiles up is not possible!")); + } var cuesheet = _sessionStateContainer.GetActiveCuesheet(); - var intersection = cuesheet!.Tracks.Intersect(tracksToRemove); - ICollection newValue = [.. cuesheet.Tracks.Except(intersection)]; - //Calculate position and begin of new tracks - ushort position = 1; - foreach (var track in newValue.OrderBy(x => x.Position)) + _traceChangeManager.BulkEdit = true; + var newAudiofiles = new List(cuesheet!.Audiofiles); + foreach (var audiofile in selectedAudiofiles) { - track.Position = position; - position++; - var previousTrack = _trackManager.GetPreviousLinkedTrack(track); - if (previousTrack?.End.HasValue == true) + var index = newAudiofiles.IndexOf(audiofile); + var previousAudiofile = newAudiofiles[index - 1]; + newAudiofiles[index] = previousAudiofile; + newAudiofiles[index - 1] = audiofile; + var previousAudiofileTracks = previousAudiofile.Tracks; + var audiofileTracks = audiofile.Tracks; + _audiofileManager.RemoveTracks(previousAudiofile, previousAudiofileTracks, false); + _audiofileManager.RemoveTracks(audiofile, audiofileTracks, false); + foreach (var track in previousAudiofileTracks) { - track.Begin = previousTrack.End; + _audiofileManager.AddTrack(audiofile, track, false); + } + foreach (var track in audiofileTracks) + { + _audiofileManager.AddTrack(previousAudiofile, track, false); } } - _traceChangeManager.BulkEdit = true; - SetValue(cuesheet, x => x.Tracks, newValue); - SetLastTrackEnd(cuesheet); + SetValue(cuesheet, x => x.Audiofiles, newAudiofiles); _traceChangeManager.BulkEdit = false; + return Result.Success(); } /// - public bool IsMoveTracksUpPossible(HashSet selectedTracks) => selectedTracks.Count > 0 && selectedTracks.Min(x => x.Position) >= 2; - - /// - public bool IsMoveTracksDownPossible(HashSet selectedTracks) => selectedTracks.Count > 0 && selectedTracks.Max(x => x.Position) < _sessionStateContainer.GetActiveCuesheet()?.Tracks.Max(x => x.Position); - - /// - public Result MoveTracksUp(HashSet selectedTracks) + public Result MoveDown(HashSet selectedTracks) { - if (IsMoveTracksUpPossible(selectedTracks) == false) + if (IsMoveDownPossible(selectedTracks) == false) { - return Result.Failure(new Error(ErrorType.NotPossible, "Moving tracks up is not possible!")); + return Result.Failure(new Error(ErrorType.NotPossible, "Moving tracks down is not possible!")); } _traceChangeManager.BulkEdit = true; var cuesheet = _sessionStateContainer.GetActiveCuesheet(); - foreach (var selectedTrack in selectedTracks.OrderBy(x => x.Position)) + foreach (var selectedTrack in selectedTracks.OrderByDescending(x => x.Position)) { - var previousTrack = cuesheet?.Tracks.FirstOrDefault(x => x.Position == selectedTrack.Position - 1); - var newBegin = previousTrack?.Begin; - var newEnd = previousTrack?.End; - if (previousTrack != null) + var nextTrack = cuesheet?.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Position == selectedTrack.Position + 1); + if (nextTrack?.Audiofile != null && nextTrack.Audiofile != selectedTrack.Audiofile) { - _trackManager.SetProperty(previousTrack, x => x.Position, selectedTrack.Position); - _trackManager.SetProperty(previousTrack, x => x.Begin, selectedTrack.Begin); - _trackManager.SetProperty(previousTrack, x => x.End, selectedTrack.End); + SwitchAudiofile(selectedTrack, nextTrack.Audiofile); + } + else + { + var newBegin = nextTrack?.Begin; + var newEnd = nextTrack?.End; + if (nextTrack != null) + { + _trackManager.SetProperty(nextTrack, x => x.Position, selectedTrack.Position); + _trackManager.SetProperty(nextTrack, x => x.Begin, selectedTrack.Begin); + _trackManager.SetProperty(nextTrack, x => x.End, selectedTrack.End); + } + _trackManager.SetProperty(selectedTrack, x => x.Position, (ushort?)(selectedTrack.Position + 1)); + _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin); + _trackManager.SetProperty(selectedTrack, x => x.End, newEnd); } - _trackManager.SetProperty(selectedTrack, x => x.Position, (ushort?)(selectedTrack.Position - 1)); - _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin); - _trackManager.SetProperty(selectedTrack, x => x.End, newEnd); } - SetValue(cuesheet!, x => x.Tracks, cuesheet?.Tracks.OrderBy(x => x.Position)); + foreach (var audiofile in cuesheet!.Audiofiles) + { + var orderedTracks = audiofile.Tracks.OrderBy(x => x.Position).ToList(); + _audiofileManager.SetProperty(audiofile, x => x.Tracks, orderedTracks); + } _traceChangeManager.BulkEdit = false; return Result.Success(); } /// - public Result MoveTracksDown(HashSet selectedTracks) + public Result MoveDown(HashSet selectedAudiofiles) { - var cuesheet = _sessionStateContainer.GetActiveCuesheet(); - if (IsMoveTracksDownPossible(selectedTracks) == false) + if (IsMoveDownPossible(selectedAudiofiles) == false) { - return Result.Failure(new Error(ErrorType.NotPossible, "Moving tracks down is not possible!")); + return Result.Failure(new Error(ErrorType.NotPossible, "Moving audiofiles down is not possible!")); } + var cuesheet = _sessionStateContainer.GetActiveCuesheet(); _traceChangeManager.BulkEdit = true; - foreach (var selectedTrack in selectedTracks.OrderByDescending(x => x.Position)) + var newAudiofiles = new List(cuesheet!.Audiofiles); + foreach (var audiofile in selectedAudiofiles) { - var nextTrack = cuesheet?.Tracks.FirstOrDefault(x => x.Position == selectedTrack.Position + 1); - var newBegin = nextTrack?.Begin; - var newEnd = nextTrack?.End; - if (nextTrack != null) + var index = newAudiofiles.IndexOf(audiofile); + var nextAudiofile = newAudiofiles[index + 1]; + newAudiofiles[index] = nextAudiofile; + newAudiofiles[index + 1] = audiofile; + var nextAudiofileTracks = nextAudiofile.Tracks; + var audiofileTracks = audiofile.Tracks; + _audiofileManager.RemoveTracks(nextAudiofile, nextAudiofileTracks, false); + _audiofileManager.RemoveTracks(audiofile, audiofileTracks, false); + foreach (var track in nextAudiofileTracks) + { + _audiofileManager.AddTrack(audiofile, track, false); + } + foreach (var track in audiofileTracks) { - _trackManager.SetProperty(nextTrack, x => x.Position, selectedTrack.Position); - _trackManager.SetProperty(nextTrack, x => x.Begin, selectedTrack.Begin); - _trackManager.SetProperty(nextTrack, x => x.End, selectedTrack.End); + _audiofileManager.AddTrack(nextAudiofile, track, false); } - var newPosition = (ushort?)(selectedTrack.Position + 1); - _trackManager.SetProperty(selectedTrack, x => x.Position, newPosition); - _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin); - _trackManager.SetProperty(selectedTrack, x => x.End, newEnd); } - SetValue(cuesheet!, x => x.Tracks, cuesheet?.Tracks.OrderBy(x => x.Position)); + SetValue(cuesheet, x => x.Audiofiles, newAudiofiles); _traceChangeManager.BulkEdit = false; return Result.Success(); } @@ -256,20 +303,44 @@ void SetValue(Cuesheet cuesheet, Expression propertyInfo.SetValue(cuesheet, value); _traceChangeManager.AddChange(new(cuesheet, new(previousValue, propertyInfo.Name))); + _ = RevokeObjectUrlOfRemovedAudiofilesAsync(cuesheet, propertyInfo, previousValue); } - void SetLastTrackEnd(Cuesheet cuesheet) + async Task RevokeObjectUrlOfRemovedAudiofilesAsync(Cuesheet cuesheet, PropertyInfo propertyInfo, object? previousValue) { - var lastTrack = GetLastTrack(cuesheet); - if ((lastTrack?.End.HasValue == false) && (cuesheet.Audiofile?.Duration.HasValue == true)) + if (propertyInfo.Name == nameof(Cuesheet.Audiofiles)) { - _trackManager.SetProperty(lastTrack, x => x.End, cuesheet.Audiofile.Duration); + var deletedAudiofiles = ((IList)previousValue!).Except(cuesheet.Audiofiles); + foreach (var deletedAudiofile in deletedAudiofiles) + { + if (!string.IsNullOrEmpty(deletedAudiofile.ObjectURL)) + { + await _jsRuntime.InvokeVoidAsync("revokeAudioObjectURL", deletedAudiofile.ObjectURL); + } + } } } + void SwitchAudiofile(Track trackToMove, Audiofile audiofileToMoveTo) + { + var currentTrackPositionAudiofile = trackToMove.Audiofile; + //Switch audiofiles without audiofilemanager since methods there capsulate much logic + var currentTrackPositionAudiofileTracks = new List(currentTrackPositionAudiofile!.Tracks); + currentTrackPositionAudiofileTracks.Remove(trackToMove); + _traceChangeManager.AddChange(new(currentTrackPositionAudiofile, new(currentTrackPositionAudiofile.Tracks, nameof(Audiofile.Tracks)))); + currentTrackPositionAudiofile.Tracks = currentTrackPositionAudiofileTracks; + var audiofileToMoveToTracks = new List(audiofileToMoveTo.Tracks) + { + trackToMove + }; + trackToMove.Audiofile = audiofileToMoveTo; + _traceChangeManager.AddChange(new(audiofileToMoveTo, new(audiofileToMoveTo.Tracks, nameof(Audiofile.Tracks)))); + audiofileToMoveTo.Tracks = audiofileToMoveToTracks; + } + static Track? GetLastTrack(Cuesheet cuesheet) { - return cuesheet.Tracks + return cuesheet.Audiofiles.SelectMany(x => x.Tracks) .OrderByDescending(x => x.Position.HasValue).ThenBy(x => x.Position) .ThenByDescending(x => x.Begin.HasValue).ThenBy(x => x.Begin) .ThenByDescending(x => x.End.HasValue).ThenBy(x => x.End) diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs new file mode 100644 index 00000000..2509a3da --- /dev/null +++ b/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs @@ -0,0 +1,55 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO.Audio; +using Microsoft.AspNetCore.Components.Forms; +using System.Linq.Expressions; + +namespace AudioCuesheetEditor.Services.AudioCuesheet +{ + public interface IAudiofileManager + { + /// + /// Set properties from a file upload + /// + /// + /// + /// + Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile? browserFile, string fileInputId); + /// + /// Set property for an audio file + /// + /// + /// + /// + /// + void SetProperty(Audiofile audiofile, Expression> propertyExpression, TProperty value); + /// + /// Adds a track to the audiofile + /// + /// + /// + /// Parameter controlling if tracing should be handled by this service or by calling services + void AddTrack(Audiofile audiofile, Track track, Boolean setTracing = true); + /// + /// Remove tracks from the audiofile + /// + /// + /// + /// Parameter controlling if tracing should be handled by this service or by calling services + void RemoveTracks(Audiofile audiofile, IEnumerable tracksToRemove, Boolean setTracing = true); + } +} diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs index 460264ff..bb4daed6 100644 --- a/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs +++ b/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs @@ -14,6 +14,7 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO.Audio; using System.Linq.Expressions; namespace AudioCuesheetEditor.Services.AudioCuesheet @@ -50,37 +51,51 @@ public interface ICuesheetManager /// void StopRecording(); /// - /// Adds a track to the cuesheet + /// Determines if moving tracks up is possible /// - /// - void AddTrack(Track track); + /// + /// + Boolean IsMoveUpPossible(HashSet selectedTracks); /// - /// Remove tracks from cuesheet + /// Determines if moving audiofiles up is possible /// - /// - void RemoveTracks(IEnumerable tracksToRemove); + /// + /// + Boolean IsMoveUpPossible(HashSet selectedAudiofiles); /// - /// Determines if moving tracks up is possible + /// Determines if moving tracks down is possible /// /// /// - Boolean IsMoveTracksUpPossible(HashSet selectedTracks); + Boolean IsMoveDownPossible(HashSet selectedTracks); /// - /// Determines if moving tracks down is possible + /// Determines if moving audiofiles down is possible /// /// /// - Boolean IsMoveTracksDownPossible(HashSet selectedTracks); + Boolean IsMoveDownPossible(HashSet selectedAudiofiles); /// /// Moves selected tracks up /// /// - Result MoveTracksUp(HashSet selectedTracks); + Result MoveUp(HashSet selectedTracks); + /// + /// Moves selected audiofiles up + /// + /// + /// + Result MoveUp(HashSet selectedAudiofiles); /// /// Moves selected tracks down /// /// /// - Result MoveTracksDown(HashSet selectedTracks); + Result MoveDown(HashSet selectedTracks); + /// + /// Moves selected audiofiles down + /// + /// + /// + Result MoveDown(HashSet selectedAudiofiles); } } diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/TrackManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/TrackManager.cs index cbffeb9b..9bcf0463 100644 --- a/AudioCuesheetEditor/Services/AudioCuesheet/TrackManager.cs +++ b/AudioCuesheetEditor/Services/AudioCuesheet/TrackManager.cs @@ -14,6 +14,7 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Services.UI; using System.Linq.Expressions; using System.Reflection; @@ -39,8 +40,14 @@ public Track Clone(ITrack track) { setLength = false; } + Audiofile? audiofile = null; + if (track is Track trackReference) + { + audiofile = trackReference.Audiofile; + } var clone = new Track() { + Audiofile = audiofile, IsLinkedToPreviousTrack = track.IsLinkedToPreviousTrack, Position = track.Position, Artist = track.Artist, @@ -110,13 +117,13 @@ public void CopyValues(ITrack source, Track target, bool setIsLinkedToPreviousTr { return null; } - if (track.Position.HasValue && (track.Cuesheet?.Tracks.All(x => x.Position.HasValue) == true)) + if (track.Position.HasValue && (track.Cuesheet?.Audiofiles.SelectMany(x => x.Tracks).All(x => x.Position.HasValue) == true)) { - return track.Cuesheet?.Tracks.LastOrDefault(x => x.Position == track.Position - 1 && Equals(x, track) == false); + return track.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).LastOrDefault(x => x.Position == track.Position - 1 && Equals(x, track) == false); } if (track.Begin.HasValue) { - return track.Cuesheet?.Tracks.OrderBy(x => x.End).LastOrDefault(x => x.End <= track.Begin && Equals(x, track) == false); + return track.Cuesheet?.Audiofiles.SelectMany(x => x.Tracks).OrderBy(x => x.End).LastOrDefault(x => x.End <= track.Begin && Equals(x, track) == false); } return null; } @@ -124,13 +131,13 @@ public void CopyValues(ITrack source, Track target, bool setIsLinkedToPreviousTr /// public Track? GetNextLinkedTrack(Track track) { - if (track.Position.HasValue && (track.Cuesheet?.Tracks.All(x => x.Position.HasValue) == true)) + if (track.Position.HasValue && (track.Cuesheet?.Audiofiles.SelectMany(x => x.Tracks).All(x => x.Position.HasValue) == true)) { - return track.Cuesheet?.Tracks.OrderBy(x => x.Begin).FirstOrDefault(x => x.Position >= track.Position.Value + 1 && x.IsLinkedToPreviousTrack == true && Equals(x, track) == false); + return track.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).OrderBy(x => x.Begin).FirstOrDefault(x => x.Position >= track.Position.Value + 1 && x.IsLinkedToPreviousTrack == true && Equals(x, track) == false); } if (track.End.HasValue) { - return track.Cuesheet?.Tracks.OrderBy(x => x.Begin).LastOrDefault(x => x.Begin <= track.End && x.IsLinkedToPreviousTrack == true && Equals(x, track) == false); + return track.Cuesheet?.Audiofiles.SelectMany(x => x.Tracks).OrderBy(x => x.Begin).LastOrDefault(x => x.Begin <= track.End && x.IsLinkedToPreviousTrack == true && Equals(x, track) == false); } return null; } diff --git a/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs b/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs index 2c34ee94..2879a7aa 100644 --- a/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs +++ b/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs @@ -38,7 +38,8 @@ public Result CanGenerateExportfile(string? filename) validationMessages.Add(new ValidationMessage("File extension is not '{0}'", FileExtensions.Cuesheet)); } validationMessages.AddRange(_sessionStateContainer.Cuesheet.Validate().ValidationMessages); - validationMessages.AddRange(_sessionStateContainer.Cuesheet.Tracks.Select(x => x.Validate()).SelectMany(x => x.ValidationMessages)); + validationMessages.AddRange(_sessionStateContainer.Cuesheet.Audiofiles.Select(x => x.Validate()).SelectMany(x => x.ValidationMessages)); + validationMessages.AddRange(_sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).Select(x => x.Validate()).SelectMany(x => x.ValidationMessages)); if (validationMessages.Count != 0) { return Result.Failure(new Error(ErrorType.ValidationFailed, string.Join(Environment.NewLine, validationMessages.Select(x => x.GetMessageLocalized(_localizer))))); @@ -53,20 +54,10 @@ public Result GenerateExportfile(string? filename) { return Result.Failure(new Error(ErrorType.ValidationFailed, validationResult.Error!.Message)); } - string? content = null; - var extension = Path.GetExtension(filename); - if (extension?.Equals(FileExtensions.Cuesheet, StringComparison.OrdinalIgnoreCase) == false) - { - filename = $"{filename}{FileExtensions.Cuesheet}"; - } - if (_sessionStateContainer.Cuesheet.Audiofile != null) - { - content = WriteCuesheet(_sessionStateContainer.Cuesheet.Audiofile.Name); - } - return Result.Success(new Exportfile() { Name = filename!, Content = content }); + return Result.Success(new Exportfile() { Name = filename!, Content = WriteCuesheet() }); } - private string WriteCuesheet(string? audiofileName) + string WriteCuesheet() { var builder = new StringBuilder(); if (string.IsNullOrEmpty(_sessionStateContainer.Cuesheet.Cataloguenumber) == false) @@ -79,15 +70,12 @@ private string WriteCuesheet(string? audiofileName) } builder.AppendLine(string.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, _sessionStateContainer.Cuesheet.Title)); builder.AppendLine(string.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, _sessionStateContainer.Cuesheet.Artist)); - builder.AppendLine(string.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, audiofileName, _sessionStateContainer.Cuesheet.Audiofile?.AudioFileType)); - IEnumerable tracks = _sessionStateContainer.Cuesheet.Tracks.OrderBy(x => x.Position); - if (tracks.Any()) + foreach (var audiofile in _sessionStateContainer.Cuesheet.Audiofiles) { - //Position and begin should always start from 0 even with splitpoints - int positionDifference = 1 - Convert.ToInt32(tracks.First().Position); - foreach (var track in tracks) + builder.AppendLine(string.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, audiofile.Name, audiofile.AudioCodec?.FileExtension.Replace(".",string.Empty).ToUpper())); + foreach(var track in audiofile.Tracks) { - builder.AppendLine(string.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position + positionDifference, CuesheetConstants.CuesheetTrackAudio)); + builder.AppendLine(string.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position, CuesheetConstants.CuesheetTrackAudio)); builder.AppendLine(string.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackTitle, track.Title)); builder.AppendLine(string.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackArtist, track.Artist)); if (track.Flags.Any()) diff --git a/AudioCuesheetEditor/Services/IO/CuesheetImportService.cs b/AudioCuesheetEditor/Services/IO/CuesheetImportService.cs index 50975019..8d900df1 100644 --- a/AudioCuesheetEditor/Services/IO/CuesheetImportService.cs +++ b/AudioCuesheetEditor/Services/IO/CuesheetImportService.cs @@ -57,6 +57,7 @@ public static IImportfile Analyse(string fileContent) var regexCDTextfile = new Regex("^" + CuesheetConstants.CuesheetCDTextfile + " \"(?'" + cuesheetCDTextfileGroupName + "'.{0,})\""); var regexCatalogueNumber = new Regex("^" + CuesheetConstants.CuesheetCatalogueNumber + " (?'" + cuesheetCatalogueNumberGroupName + "'.{0,})"); ImportTrack? track = null; + ImportAudiofile? audiofile = null; StringBuilder recognizedContent = new(); foreach (var line in fileContent.Split(Environment.NewLine)) { @@ -100,8 +101,8 @@ public static IImportfile Analyse(string fileContent) var matchGroup = match.Groups.GetValueOrDefault(cuesheetFileNameGroupName); if (matchGroup != null) { - var audioFile = matchGroup.Value; - importfile.AnalyzedCuesheet.Audiofile = audioFile; + audiofile = new() { Name = matchGroup.Value }; + importfile.AnalyzedCuesheet.Audiofiles.Add(audiofile); } else { @@ -203,12 +204,12 @@ public static IImportfile Analyse(string fileContent) var matchGroup = match.Groups.GetValueOrDefault(trackPreGapGroupName); if (matchGroup != null) { - var minutes = int.Parse(matchGroup.Value.Substring(0, matchGroup.Value.IndexOf(':'))); + var minutes = int.Parse(matchGroup.Value[..matchGroup.Value.IndexOf(':')]); var seconds = int.Parse(matchGroup.Value.Substring(matchGroup.Value.IndexOf(':') + 1, 2)); - var frames = int.Parse(matchGroup.Value.Substring(matchGroup.Value.LastIndexOf(':') + 1)); + var frames = int.Parse(matchGroup.Value[(matchGroup.Value.LastIndexOf(':') + 1)..]); if (track != null) { - track.PreGap = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32((frames / 75.0) * 1000)); + track.PreGap = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32(frames / 75.0 * 1000)); } else { @@ -227,12 +228,12 @@ public static IImportfile Analyse(string fileContent) var matchGroup = match.Groups.GetValueOrDefault(trackIndex01GroupName); if (matchGroup != null) { - var minutes = int.Parse(matchGroup.Value.Substring(0, matchGroup.Value.IndexOf(':'))); + var minutes = int.Parse(matchGroup.Value[..matchGroup.Value.IndexOf(':')]); var seconds = int.Parse(matchGroup.Value.Substring(matchGroup.Value.IndexOf(':') + 1, 2)); - var frames = int.Parse(matchGroup.Value.Substring(matchGroup.Value.LastIndexOf(':') + 1)); + var frames = int.Parse(matchGroup.Value[(matchGroup.Value.LastIndexOf(':') + 1)..]); if (track != null) { - track.Begin = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32((frames / 75.0) * 1000)); + track.Begin = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32(frames / 75.0 * 1000)); } else { @@ -243,14 +244,7 @@ public static IImportfile Analyse(string fileContent) { throw new ArgumentException(String.Format("Group '{0}' was null!", trackIndex01GroupName)); } - if (track != null) - { - importfile.AnalyzedCuesheet.Tracks.Add(track); - } - else - { - throw new NullReferenceException(String.Format("Track was null during input {0}", line)); - } + audiofile?.Tracks.Add(track); } if (regexTrackPostGap.IsMatch(line) == true) { @@ -259,12 +253,12 @@ public static IImportfile Analyse(string fileContent) var matchGroup = match.Groups.GetValueOrDefault(trackPostGapGroupName); if (matchGroup != null) { - var minutes = int.Parse(matchGroup.Value.Substring(0, matchGroup.Value.IndexOf(':'))); + var minutes = int.Parse(matchGroup.Value[..matchGroup.Value.IndexOf(':')]); var seconds = int.Parse(matchGroup.Value.Substring(matchGroup.Value.IndexOf(':') + 1, 2)); - var frames = int.Parse(matchGroup.Value.Substring(matchGroup.Value.LastIndexOf(':') + 1)); + var frames = int.Parse(matchGroup.Value[(matchGroup.Value.LastIndexOf(':') + 1)..]); if (track != null) { - track.PostGap = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32((frames / 75.0) * 1000)); + track.PostGap = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32(frames / 75.0 * 1000)); } else { diff --git a/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs b/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs index 4b1e3464..2b7a570b 100644 --- a/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs +++ b/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs @@ -33,7 +33,8 @@ public Result CanGenerateExportfile(Exportprofile exportprofile) List validationMessages = []; validationMessages.AddRange(exportprofile.Validate().ValidationMessages); validationMessages.AddRange(_sessionStateContainer.Cuesheet.Validate().ValidationMessages); - validationMessages.AddRange(_sessionStateContainer.Cuesheet.Tracks.Select(x => x.Validate()).SelectMany(x => x.ValidationMessages)); + validationMessages.AddRange(_sessionStateContainer.Cuesheet.Audiofiles.Select(x => x.Validate()).SelectMany(x => x.ValidationMessages)); + validationMessages.AddRange(_sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).Select(x => x.Validate()).SelectMany(x => x.ValidationMessages)); if (validationMessages.Count != 0) { return Result.Failure(new Error(ErrorType.ValidationFailed, string.Join(Environment.NewLine, validationMessages.Select(x => x.GetMessageLocalized(_localizer))))); @@ -48,15 +49,10 @@ public Result GenerateExportfile(Exportprofile exportprofile) { return Result.Failure(new Error(ErrorType.ValidationFailed, validationResult.Error!.Message)); } - string? content = null; - if (_sessionStateContainer.Cuesheet.Audiofile != null) - { - content = WriteExport(exportprofile, _sessionStateContainer.Cuesheet.Audiofile.Name); - } - return Result.Success(new Exportfile() { Name = exportprofile.Filename, Content = content}); + return Result.Success(new Exportfile() { Name = exportprofile.Filename, Content = WriteExport(exportprofile) }); } - private string WriteExport(Exportprofile exportprofile, string? audiofileName) + private string WriteExport(Exportprofile exportprofile) { var builder = new StringBuilder(); if (exportprofile != null) @@ -64,50 +60,43 @@ private string WriteExport(Exportprofile exportprofile, string? audiofileName) var header = exportprofile.SchemeHead .Replace(Exportprofile.SchemeCuesheetArtist, _sessionStateContainer.Cuesheet.Artist) .Replace(Exportprofile.SchemeCuesheetTitle, _sessionStateContainer.Cuesheet.Title) - .Replace(Exportprofile.SchemeCuesheetAudiofile, audiofileName) .Replace(Exportprofile.SchemeCuesheetCDTextfile, _sessionStateContainer.Cuesheet.CDTextfile?.Name) .Replace(Exportprofile.SchemeCuesheetCatalogueNumber, _sessionStateContainer.Cuesheet.Cataloguenumber) .Replace(Exportprofile.SchemeDate, DateTime.Now.ToShortDateString()) .Replace(Exportprofile.SchemeDateTime, DateTime.Now.ToString()) .Replace(Exportprofile.SchemeTime, DateTime.Now.ToLongTimeString()); builder.AppendLine(header); - IEnumerable tracks = _sessionStateContainer.Cuesheet.Tracks.OrderBy(x => x.Position); - if (tracks.Any()) + foreach (var audiofile in _sessionStateContainer.Cuesheet.Audiofiles) { - //Position, Begin and End should always start from 0 even with splitpoints - int positionDifference = 1 - Convert.ToInt32(tracks.First().Position); - foreach (var track in tracks) + var audiofileLine = exportprofile.SchemeAudiofiles + .Replace(Exportprofile.SchemeAudiofileName, audiofile.Name); + builder.AppendLine(audiofileLine); + IEnumerable tracks = audiofile.Tracks.OrderBy(x => x.Position); + if (tracks.Any()) { - TimeSpan begin; - var end = track.End; - if (track.Begin.HasValue) - { - begin = track.Begin.Value; - } - else + foreach (var track in tracks) { - throw new NullReferenceException(string.Format("{0} may not be null!", nameof(Track.Begin))); + var trackLine = exportprofile.SchemeTracks + .Replace(Exportprofile.SchemeTrackArtist, track.Artist) + .Replace(Exportprofile.SchemeTrackTitle, track.Title) + .Replace(Exportprofile.SchemeTrackPosition, track.Position.ToString()) + .Replace(Exportprofile.SchemeTrackBegin, track.Begin.ToString()) + .Replace(Exportprofile.SchemeTrackEnd, track.End.ToString()) + .Replace(Exportprofile.SchemeTrackLength, track.Length.ToString()) + .Replace(Exportprofile.SchemeTrackFlags, string.Join(" ", track.Flags.Select(x => x.CuesheetLabel))) + .Replace(Exportprofile.SchemeTrackPreGap, track.PreGap != null ? track.PreGap.Value.ToString() : string.Empty) + .Replace(Exportprofile.SchemeTrackPostGap, track.PostGap != null ? track.PostGap.Value.ToString() : string.Empty) + .Replace(Exportprofile.SchemeDate, DateTime.Now.ToShortDateString()) + .Replace(Exportprofile.SchemeDateTime, DateTime.Now.ToString()) + .Replace(Exportprofile.SchemeTime, DateTime.Now.ToLongTimeString()); + builder.AppendLine(trackLine); } - var trackLine = exportprofile.SchemeTracks - .Replace(Exportprofile.SchemeTrackArtist, track.Artist) - .Replace(Exportprofile.SchemeTrackTitle, track.Title) - .Replace(Exportprofile.SchemeTrackPosition, (track.Position + positionDifference).ToString()) - .Replace(Exportprofile.SchemeTrackBegin, begin.ToString()) - .Replace(Exportprofile.SchemeTrackEnd, end.ToString()) - .Replace(Exportprofile.SchemeTrackLength, (end - begin).ToString()) - .Replace(Exportprofile.SchemeTrackFlags, string.Join(" ", track.Flags.Select(x => x.CuesheetLabel))) - .Replace(Exportprofile.SchemeTrackPreGap, track.PreGap != null ? track.PreGap.Value.ToString() : string.Empty) - .Replace(Exportprofile.SchemeTrackPostGap, track.PostGap != null ? track.PostGap.Value.ToString() : string.Empty) - .Replace(Exportprofile.SchemeDate, DateTime.Now.ToShortDateString()) - .Replace(Exportprofile.SchemeDateTime, DateTime.Now.ToString()) - .Replace(Exportprofile.SchemeTime, DateTime.Now.ToLongTimeString()); - builder.AppendLine(trackLine); } } + var footer = exportprofile.SchemeFooter .Replace(Exportprofile.SchemeCuesheetArtist, _sessionStateContainer.Cuesheet.Artist) .Replace(Exportprofile.SchemeCuesheetTitle, _sessionStateContainer.Cuesheet.Title) - .Replace(Exportprofile.SchemeCuesheetAudiofile, audiofileName) .Replace(Exportprofile.SchemeCuesheetCDTextfile, _sessionStateContainer.Cuesheet.CDTextfile?.Name) .Replace(Exportprofile.SchemeCuesheetCatalogueNumber, _sessionStateContainer.Cuesheet.Cataloguenumber) .Replace(Exportprofile.SchemeDate, DateTime.Now.ToShortDateString()) diff --git a/AudioCuesheetEditor/Services/IO/FileInputManager.cs b/AudioCuesheetEditor/Services/IO/FileInputManager.cs index 32836dc8..160a7701 100644 --- a/AudioCuesheetEditor/Services/IO/FileInputManager.cs +++ b/AudioCuesheetEditor/Services/IO/FileInputManager.cs @@ -21,10 +21,9 @@ namespace AudioCuesheetEditor.Services.IO { - public class FileInputManager(IJSRuntime jsRuntime, HttpClient httpClient, ILogger logger) : IFileInputManager + public class FileInputManager(IJSRuntime jsRuntime, ILogger logger) : IFileInputManager { private readonly IJSRuntime _jsRuntime = jsRuntime; - private readonly HttpClient _httpClient = httpClient; private readonly ILogger _logger = logger; public AudioCodec? GetAudioCodec(string? fileContentType, string fileName) @@ -46,6 +45,12 @@ public class FileInputManager(IJSRuntime jsRuntime, HttpClient httpClient, ILogg return foundAudioCodec; } + /// + public async Task GetObjectUrlAsync(string fileInputId) + { + return await _jsRuntime.InvokeAsync("getObjectURLFromMudFileUpload", fileInputId); + } + public bool IsValidAudiofile(string? fileContentType, string fileName) { return GetAudioCodec(fileContentType, fileName) != null; diff --git a/AudioCuesheetEditor/Services/IO/IFileInputManager.cs b/AudioCuesheetEditor/Services/IO/IFileInputManager.cs index f79f88a0..e5f4ff39 100644 --- a/AudioCuesheetEditor/Services/IO/IFileInputManager.cs +++ b/AudioCuesheetEditor/Services/IO/IFileInputManager.cs @@ -26,6 +26,12 @@ public interface IFileInputManager bool IsValidAudiofile(string? fileContentType, string fileName); AudioCodec? GetAudioCodec(string? fileContentType, string fileName); /// + /// Get object url from a mud file upload + /// + /// + /// + Task GetObjectUrlAsync(string fileInputId); + /// /// Checks if a file content type and name matches given parameters /// /// diff --git a/AudioCuesheetEditor/Services/IO/ImportManager.cs b/AudioCuesheetEditor/Services/IO/ImportManager.cs index 1a17a2dc..6637210f 100644 --- a/AudioCuesheetEditor/Services/IO/ImportManager.cs +++ b/AudioCuesheetEditor/Services/IO/ImportManager.cs @@ -13,6 +13,7 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. +using AngleSharp.Media.Dom; using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.AudioCuesheet.Import; using AudioCuesheetEditor.Model.IO; @@ -108,7 +109,7 @@ public void ImportCuesheet() ResetTracing(); if (_sessionStateContainer.ImportCuesheet != null) { - var newCuesheet = _sessionStateContainer.ImportCuesheet; + var newCuesheet = new Cuesheet(); CopyCuesheet(newCuesheet, _sessionStateContainer.ImportCuesheet); var previousValue = _sessionStateContainer.Cuesheet; _sessionStateContainer.Cuesheet = newCuesheet; @@ -163,7 +164,10 @@ public async Task UploadFilesAsync(IEnumerable files) if (_fileInputManager.IsValidAudiofile(file.ContentType, file.Name)) { var audioFile = await _fileInputManager.CreateAudiofileAsync(file); - _sessionStateContainer.ImportAudiofile = audioFile; + if (audioFile != null) + { + _sessionStateContainer.ImportAudiofiles.Add(audioFile); + } } } else @@ -179,33 +183,55 @@ public async Task UploadFilesAsync(IEnumerable files) } } - private void CopyCuesheet(Cuesheet target, ICuesheet cuesheetToCopy) + void CopyCuesheet(Cuesheet target, ICuesheet cuesheetToCopy) { target.Artist = cuesheetToCopy.Artist; target.Title = cuesheetToCopy.Title; target.Cataloguenumber = cuesheetToCopy.Cataloguenumber; - IEnumerable? tracks = null; if (cuesheetToCopy is Cuesheet originCuesheet) { - tracks = originCuesheet.Tracks; - target.Audiofile = originCuesheet.Audiofile; target.CDTextfile = originCuesheet.CDTextfile; - target.Cataloguenumber = originCuesheet.Cataloguenumber; + AttachClonedAudiofiles(target, originCuesheet.Audiofiles); } if (cuesheetToCopy is ImportCuesheet importCuesheet) { - tracks = importCuesheet.Tracks; - if (String.IsNullOrEmpty(importCuesheet.Audiofile) == false) - { - target.Audiofile = new Audiofile(importCuesheet.Audiofile); - } if (String.IsNullOrEmpty(importCuesheet.CDTextfile) == false) { target.CDTextfile = new CDTextfile(importCuesheet.CDTextfile); } + AttachClonedAudiofiles(target, importCuesheet.Audiofiles); } - if (tracks != null) + } + + void AttachClonedAudiofiles(Cuesheet target, IEnumerable audiofiles) + { + foreach (var audiofile in audiofiles) { + Audiofile? targetAudiofile = null; + IEnumerable? tracks = null; + // Map uploaded import audiofiles by name + var importAudiofileFound = _sessionStateContainer.ImportAudiofiles.FirstOrDefault(x => x.Name == audiofile.Name); + if (importAudiofileFound != null) + { + targetAudiofile = new Audiofile(importAudiofileFound.Name, importAudiofileFound.ObjectURL, importAudiofileFound.AudioCodec, importAudiofileFound.Duration); + } + if (audiofile is ImportAudiofile importAudiofile) + { + targetAudiofile ??= new Audiofile() + { + Name = importAudiofile.Name, + }; + tracks = importAudiofile.Tracks; + } + if (audiofile is Audiofile sourceAudiofile) + { + targetAudiofile ??= new Audiofile(sourceAudiofile.Name, sourceAudiofile.ObjectURL, sourceAudiofile.AudioCodec, sourceAudiofile.Duration); + tracks = sourceAudiofile.Tracks; + } + if (targetAudiofile == null || tracks == null) + { + throw new NullReferenceException(); + } IOrderedEnumerable sortedTracks; if (tracks.All(x => x.Position.HasValue)) { @@ -231,56 +257,52 @@ private void CopyCuesheet(Cuesheet target, ICuesheet cuesheetToCopy) { sortedTracks = sortedTracks.ThenByDescending(x => x.End.HasValue).ThenBy(x => x.End); } - List targetTracks = []; TimeSpan? begin = TimeSpan.Zero; ushort position = 1; - foreach (var (importTrack, index) in sortedTracks.Select((track, i) => (track, i))) + for (int i = 0; i < sortedTracks.Count(); i++) { + var track = sortedTracks.ElementAt(i); ITrack? nextTrack = null; - if (index < sortedTracks.Count() - 1) + if (i < sortedTracks.Count() - 1) { - nextTrack = sortedTracks.ElementAt(index + 1); + nextTrack = sortedTracks.ElementAt(i + 1); } - // Copy track - var track = _trackManager.Clone(importTrack); - track.Cuesheet = target; + var clone = _trackManager.Clone(track); + clone.Cuesheet = target; + clone.Audiofile = targetAudiofile; // Special treatment for StartDateTime of ImportTrack - if (importTrack is ImportTrack importTrackReference && importTrackReference.StartDateTime != null && nextTrack is ImportTrack nextImportTrackReference) + if (track is ImportTrack importTrack && importTrack.StartDateTime != null && nextTrack is ImportTrack nextImportTrack) { - var length = nextImportTrackReference.StartDateTime - importTrackReference.StartDateTime; - track.Begin = begin; - track.End = begin + length; + var length = nextImportTrack.StartDateTime - importTrack.StartDateTime; + clone.Begin = begin; + clone.End = begin + length; } // Calculate properties - if (track.Position.HasValue == false) + if (clone.Position.HasValue == false) { - track.Position = position; + clone.Position = position; } - if (track.Begin.HasValue == false) + if (clone.Begin.HasValue == false) { - track.Begin = begin; + clone.Begin = begin; } - if ((track.End.HasValue == false) && (nextTrack?.Begin.HasValue == true)) + if ((clone.End.HasValue == false) && (nextTrack?.Begin.HasValue == true)) { - track.End = nextTrack.Begin; + clone.End = nextTrack.Begin; } - begin = track.End; + begin = clone.End; position++; - targetTracks.Add(track); + targetAudiofile.Tracks.Add(clone); } - target.Tracks = targetTracks; - } - else - { - throw new NullReferenceException(); + target.Audiofiles.Add(targetAudiofile); } } - private void ResetTracing() + void ResetTracing() { if (_sessionStateContainer.ImportCuesheet != null) { - _traceChangeManager.RemoveTracedChanges([_sessionStateContainer.ImportCuesheet, .. _sessionStateContainer.ImportCuesheet.Tracks]); + _traceChangeManager.RemoveTracedChanges([_sessionStateContainer.ImportCuesheet, .. _sessionStateContainer.ImportCuesheet.Audiofiles, .. _sessionStateContainer.ImportCuesheet.Audiofiles.SelectMany(x => x.Tracks)]); } } } diff --git a/AudioCuesheetEditor/Services/IO/TextImportService.cs b/AudioCuesheetEditor/Services/IO/TextImportService.cs index ab975076..e196ca0f 100644 --- a/AudioCuesheetEditor/Services/IO/TextImportService.cs +++ b/AudioCuesheetEditor/Services/IO/TextImportService.cs @@ -31,13 +31,14 @@ public class TextImportService(ILocalStorageOptionsProvider localStorageOptionsP { private readonly ILocalStorageOptionsProvider _localStorageOptionsProvider = localStorageOptionsProvider; + private readonly Dictionary _audiofileStartIndices = []; + public async Task AnalyseAsync(string fileContent) { Importfile importFile = new() { FileContent = fileContent, FileContentRecognized = fileContent, - AnalyzedCuesheet = new ImportCuesheet(), FileType = ImportFileType.Textfile }; try @@ -46,6 +47,7 @@ public async Task AnalyseAsync(string fileContent) var applicationOptions = await _localStorageOptionsProvider.GetOptionsAsync(); var importProfile = importOptions.SelectedImportProfile ?? throw new InvalidOperationException("Selected import profiles is not set!"); SearchForCuesheetData(ref importFile, fileContent, importProfile); + SearchForAudiofileData(ref importFile, fileContent, importProfile); SearchForTrackData(ref importFile, fileContent, importProfile, applicationOptions.DefaultIsLinkedToPreviousTrack); } catch (Exception ex) @@ -57,7 +59,7 @@ public async Task AnalyseAsync(string fileContent) return importFile; } - private static string ApplyRegexAndMarkGroups(object entity, Regex regex, string input, TimeSpanFormat? timeSpanFormat) + static string ApplyRegexAndMarkGroups(object entity, Regex regex, string input, TimeSpanFormat? timeSpanFormat) { return regex.Replace(input, match => { @@ -93,107 +95,180 @@ private static string ApplyRegexAndMarkGroups(object entity, Regex regex, string }); } - private static void SearchForCuesheetData(ref Importfile importFile, string fileContent, Importprofile importProfile) + static void SearchForCuesheetData(ref Importfile importFile, string fileContent, Importprofile importProfile) { + var cuesheet = new ImportCuesheet(); if (string.IsNullOrWhiteSpace(importProfile.SchemeCuesheet) == false) { - var cuesheet = importFile.AnalyzedCuesheet; - Regex regex; - if (importProfile.UseRegularExpression == true) + importFile.FileContentRecognized ??= fileContent; + if (importProfile.UseRegularExpression) { - regex = new Regex(importProfile.SchemeCuesheet, RegexOptions.Multiline); + var regex = new Regex(importProfile.SchemeCuesheet); + importFile.FileContentRecognized = regex.Replace(importFile.FileContentRecognized, + match => + { + string marked = ApplyRegexAndMarkGroups(cuesheet, regex, match.Value, importProfile.TimeSpanFormat); + return marked; + } + ); + importFile.FileContentRecognized = ApplyRegexAndMarkGroups(cuesheet, regex, fileContent, importProfile.TimeSpanFormat); } else { - regex = CreateCuesheetRegexPattern(importProfile.SchemeCuesheet); + var regex = CreateRegexPattern(importProfile.SchemeCuesheet, [ + nameof(ImportCuesheet.Artist), + nameof(ImportCuesheet.Title), + nameof(ImportCuesheet.CDTextfile), + nameof(ImportCuesheet.Cataloguenumber) + ]); + importFile.FileContentRecognized = SearchLineByLineForEntry(importFile.FileContentRecognized, () => new ImportCuesheet(), (position, entity) => + { + cuesheet = (ImportCuesheet)entity; + return true; + }, regex, importProfile); } + } + importFile.AnalyzedCuesheet = cuesheet; + } + void SearchForAudiofileData(ref Importfile importFile, string fileContent, Importprofile importProfile) + { + _audiofileStartIndices.Clear(); + var cuesheet = importFile.AnalyzedCuesheet; + if (string.IsNullOrWhiteSpace(importProfile.SchemeAudiofiles) == false) + { + importFile.FileContentRecognized ??= fileContent; if (importProfile.UseRegularExpression) { - importFile.FileContentRecognized = ApplyRegexAndMarkGroups(cuesheet!, regex, fileContent, importProfile.TimeSpanFormat); + var regex = new Regex(importProfile.SchemeAudiofiles); + importFile.FileContentRecognized = regex.Replace(importFile.FileContentRecognized, + match => + { + var audiofile = new ImportAudiofile(); + string marked = ApplyRegexAndMarkGroups(audiofile, regex, match.Value, importProfile.TimeSpanFormat); + cuesheet!.Audiofiles.Add(audiofile); + _audiofileStartIndices.Add(audiofile, match.Index); + return marked; + } + ); } else { - var sb = new StringBuilder(); - using (var reader = new StringReader(fileContent)) + var regex = CreateRegexPattern(importProfile.SchemeAudiofiles, [nameof(ImportAudiofile.Name)]); + importFile.FileContentRecognized = SearchLineByLineForEntry(importFile.FileContentRecognized, () => new ImportAudiofile(), (position, entity) => { - string? line; - while ((line = reader.ReadLine()) != null) - { - var markedLine = ApplyRegexAndMarkGroups(cuesheet!, regex, line, importProfile.TimeSpanFormat); - sb.AppendLine(markedLine); - if (!string.Equals(markedLine, line)) - { - //We found the first occurrence, break the loop - //Attach the rest of the file to FileContentRecognized - sb.Append(reader.ReadToEnd()); - break; - } - } - } - importFile.FileContentRecognized = sb.ToString(); + var audiofile = (ImportAudiofile)entity; + cuesheet!.Audiofiles.Add(audiofile); + _audiofileStartIndices.Add(audiofile, position); + return false; + }, regex, importProfile); } } + else + { + //Add an empty audiofile to the cuesheet if no scheme is provided, so that tracks can be added to it + var audiofile = new ImportAudiofile(); + _audiofileStartIndices.Add(audiofile, 0); + cuesheet!.Audiofiles.Add(audiofile); + } } - private static void SearchForTrackData(ref Importfile importFile, string fileContent, Importprofile importProfile, bool defaultIsLinkedToPreviousTrack) + void SearchForTrackData(ref Importfile importFile, string fileContent, Importprofile importProfile, bool defaultIsLinkedToPreviousTrack) { if (string.IsNullOrWhiteSpace(importProfile.SchemeTracks) == false) { - Regex regex; - if (importProfile.UseRegularExpression == true) - { - regex = new Regex(importProfile.SchemeTracks, RegexOptions.Multiline); - } - else - { - regex = CreateTrackRegexPattern(importProfile.SchemeTracks); - } var cuesheet = importFile.AnalyzedCuesheet; importFile.FileContentRecognized ??= fileContent; if (importProfile.UseRegularExpression) { + var regex = new Regex(importProfile.SchemeTracks); importFile.FileContentRecognized = regex.Replace(importFile.FileContentRecognized, match => { var track = new ImportTrack() { IsLinkedToPreviousTrack = defaultIsLinkedToPreviousTrack }; - string marked = ApplyRegexAndMarkGroups(track, regex, match.Value, importProfile.TimeSpanFormat); - cuesheet!.Tracks.Add(track); - return marked; + var audiofile = _audiofileStartIndices + .Where(kv => kv.Value <= match.Index) + .OrderBy(kv => kv.Value) + .Select(kv => kv.Key) + .LastOrDefault(); + if (audiofile != null) + { + audiofile.Tracks.Add(track); + return ApplyRegexAndMarkGroups(track, regex, match.Value, importProfile.TimeSpanFormat); + } + return match.Value; } ); } else { - var sb = new StringBuilder(); - using (var reader = new StringReader(importFile.FileContentRecognized)) + var regex = CreateRegexPattern(importProfile.SchemeTracks, + [ + nameof(ImportTrack.Artist), + nameof(ImportTrack.Title), + nameof(ImportTrack.Begin), + nameof(ImportTrack.End), + nameof(ImportTrack.Length), + nameof(ImportTrack.Position), + nameof(ImportTrack.Flags), + nameof(ImportTrack.PreGap), + nameof(ImportTrack.PostGap), + nameof(ImportTrack.StartDateTime) + ]); + if (_audiofileStartIndices.Count > 0) { - string? line; - while ((line = reader.ReadLine()) != null) + importFile.FileContentRecognized = SearchLineByLineForEntry(importFile.FileContentRecognized, () => new ImportTrack() { IsLinkedToPreviousTrack = defaultIsLinkedToPreviousTrack }, (position, entity) => { - // Check if this line is already analyzed - if (line.Contains(CuesheetConstants.MarkHTMLStart) == false) - { - var track = new ImportTrack() { IsLinkedToPreviousTrack = defaultIsLinkedToPreviousTrack }; - var markedLine = ApplyRegexAndMarkGroups(track, regex, line, importProfile.TimeSpanFormat); - if (!string.Equals(markedLine, line)) - { - cuesheet!.Tracks.Add(track); - } - sb.AppendLine(markedLine); - } - else - { - sb.AppendLine(line); - } + var audiofile = _audiofileStartIndices + .Where(kv => kv.Value <= position) + .OrderBy(kv => kv.Value) + .Select(kv => kv.Key) + .LastOrDefault(); + audiofile?.Tracks.Add((ImportTrack)entity); + return false; + }, regex, importProfile); + } + } + } + } + + static string SearchLineByLineForEntry(string fileContent, Func entityCreation, Func ifEntityFound, Regex regex, Importprofile importProfile) + { + var sb = new StringBuilder(); + using (var reader = new StringReader(fileContent)) + { + string? line; + int pos = 0; + while ((line = reader.ReadLine()) != null) + { + Boolean stopSearch = false; + // Check if this line is already analyzed + if (line.Contains(CuesheetConstants.MarkHTMLStart) == false) + { + var entity = entityCreation.Invoke(); + var markedLine = ApplyRegexAndMarkGroups(entity, regex, line, importProfile.TimeSpanFormat); + if (!string.Equals(markedLine, line)) + { + stopSearch = ifEntityFound.Invoke(pos, entity); + } + sb.AppendLine(markedLine); + if (stopSearch) + { + sb.Append(reader.ReadToEnd()); + break; } } - importFile.FileContentRecognized = sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); + else + { + sb.AppendLine(line); + } + pos += line.Length + Environment.NewLine.Length; } } + return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); } - private static void SetValue(object entity, PropertyInfo property, string value, TimeSpanFormat? timeSpanFormat) + static void SetValue(object entity, PropertyInfo property, string value, TimeSpanFormat? timeSpanFormat) { if (property.PropertyType == typeof(TimeSpan?)) { @@ -213,7 +288,7 @@ private static void SetValue(object entity, PropertyInfo property, string value, } if (property.PropertyType == typeof(Audiofile)) { - property.SetValue(entity, new Audiofile(value)); + property.SetValue(entity, new Audiofile() { Name = value }); } if (property.PropertyType == typeof(DateTime?)) { @@ -224,85 +299,8 @@ private static void SetValue(object entity, PropertyInfo property, string value, } } - private static Regex CreateCuesheetRegexPattern(string scheme) - { - string[] fieldNames = - [ - nameof(ImportCuesheet.Artist), - nameof(ImportCuesheet.Title), - nameof(ImportCuesheet.Audiofile), - nameof(ImportCuesheet.CDTextfile), - nameof(ImportCuesheet.Cataloguenumber) - ]; - var parts = new List(); - int idx = 0; - while (idx < scheme.Length) - { - var field = fieldNames.FirstOrDefault(fn => scheme.IndexOf(fn, idx, StringComparison.Ordinal) == idx); - if (field != null) - { - parts.Add(field); - idx += field.Length; - } - else - { - int nextFieldIdx = scheme.Length; - foreach (var fn in fieldNames) - { - int pos = scheme.IndexOf(fn, idx, StringComparison.Ordinal); - if (pos >= 0 && pos < nextFieldIdx) - { - nextFieldIdx = pos; - } - } - string separator = scheme[idx..nextFieldIdx]; - parts.Add(separator); - idx = nextFieldIdx; - } - } - - var regexBuilder = new StringBuilder("^"); - for (int i = 0; i < parts.Count; i++) - { - var part = parts[i]; - if (fieldNames.Contains(part)) - { - bool isLast = i == parts.Count - 1 || parts.Skip(i + 1).All(p => !fieldNames.Contains(p)); - if (isLast) - { - regexBuilder.Append($@"(?<{part}>.+)"); - } - else - { - regexBuilder.Append($@"(?<{part}>.+?)"); - } - } - else - { - string sep = Regex.Escape(part).Replace("\\t", @"\t{1,}"); - regexBuilder.Append(sep); - } - } - regexBuilder.Append('$'); - - return new Regex(regexBuilder.ToString()); - } - - private static Regex CreateTrackRegexPattern(string scheme) + static Regex CreateRegexPattern(string scheme, string[] fieldNames) { - string[] fieldNames = - [ - nameof(ImportTrack.Artist), - nameof(ImportTrack.Title), - nameof(ImportTrack.Begin), - nameof(ImportTrack.End), - nameof(ImportTrack.Length), - nameof(ImportTrack.Position), - nameof(ImportTrack.Flags), - nameof(ImportTrack.PreGap), - nameof(ImportTrack.PostGap), - nameof(ImportTrack.StartDateTime) - ]; var parts = new List(); int idx = 0; while (idx < scheme.Length) diff --git a/AudioCuesheetEditor/Services/UI/DialogManager.cs b/AudioCuesheetEditor/Services/UI/DialogManager.cs index 16b48a45..a98841fa 100644 --- a/AudioCuesheetEditor/Services/UI/DialogManager.cs +++ b/AudioCuesheetEditor/Services/UI/DialogManager.cs @@ -194,7 +194,7 @@ public async Task ShowLoadingDialogAsync() if (_loadingDialog == null) { var options = new DialogOptions() { BackdropClick = false, FullWidth = true, MaxWidth = MaxWidth.ExtraSmall, NoHeader = true }; - _loadingDialog = await _dialogService.ShowAsync(null, options); + _loadingDialog = await _dialogService.ShowAsync(options); await Task.Delay(1); } } diff --git a/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs b/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs index e3f31f3f..24ed1896 100644 --- a/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs +++ b/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs @@ -25,7 +25,7 @@ public interface ISessionStateContainer public event EventHandler? ImportCuesheetChanged; public Cuesheet Cuesheet { get; set; } public Cuesheet? ImportCuesheet { get; set; } - public Audiofile? ImportAudiofile { get; set; } + public IList ImportAudiofiles { get; set; } public IImportfile? Importfile { get; set; } public Boolean ImportIsAnalyzed { get; set; } public void ResetImport(); diff --git a/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs b/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs index f4e00e55..68418a8f 100644 --- a/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs +++ b/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs @@ -63,7 +63,7 @@ public Cuesheet? ImportCuesheet ImportCuesheetChanged?.Invoke(this, EventArgs.Empty); } } - public Audiofile? ImportAudiofile { get; set; } + public IList ImportAudiofiles { get; set; } = []; public IImportfile? Importfile{ get; set; } public Boolean ImportIsAnalyzed { get; set; } = false; @@ -76,7 +76,7 @@ public async Task InitializeAsync() public void ResetImport() { Importfile = null; - ImportAudiofile = null; + ImportAudiofiles = []; ImportCuesheet = null; } diff --git a/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor b/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor index 122d0742..c3fe120a 100644 --- a/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor +++ b/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor @@ -22,54 +22,45 @@ along with Foobar. If not, see @inject PlaybackService _playbackService @inject HotKeys _hotKeys - - - - @_localizer["Playback"] - - - - - @if (_playbackService.CurrentPosition.HasValue) - { - @_playbackService.CurrentPosition.Value.ToString("hh\\:mm\\:ss") - } - else - { - @String.Format("--{0}--{1}--", CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator, CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator) - } - - - @GetSliderTimeValue() - - - @if (_playbackService.TotalTime.HasValue) - { - @_playbackService.TotalTime.Value.ToString("hh\\:mm\\:ss") - } - else - { - @String.Format("--{0}--{1}--", CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator, CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator) - } - - - - - - - - - - - - - - - - - - - + + @if (_playbackService.CurrentPosition.HasValue) + { + @_playbackService.CurrentPosition.Value.ToString("hh\\:mm\\:ss") + } + else + { + @String.Format("--{0}--{1}--", CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator, CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator) + } + + + @GetSliderTimeValue() + + + @if (_playbackService.TotalTime.HasValue) + { + @_playbackService.TotalTime.Value.ToString("hh\\:mm\\:ss") + } + else + { + @String.Format("--{0}--{1}--", CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator, CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator) + } + + + + + + + + + + + + + + + + + @code { double sliderValue; @@ -82,9 +73,9 @@ along with Foobar. If not, see hotKeysContext?.DisposeAsync(); } - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { - base.OnInitialized(); + await base.OnInitializedAsync(); _playbackService.CurrentPositionChanged += PlaybackService_CurrentPositionChanged; hotKeysContext = _hotKeys.CreateContext() .Add(ModKey.Ctrl, Key.p, OnPlayOrPauseClicked) @@ -95,6 +86,7 @@ along with Foobar. If not, see .Add(Key.MediaTrackNext, PlayNextTrackAsync) .Add(Key.MediaTrackPrevious, PlayPreviousTrackAsync) .Add(Key.MediaStop, StopAsync); + await _playbackService.InitializeAsync(); } async Task OnPlayOrPauseClicked() diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.de.resx b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.de.resx new file mode 100644 index 00000000..1735ab5d --- /dev/null +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.de.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Audiodatei + + + Dateiname ändern + + + Geben Sie hier den neuen Dateinamen ein + + + Neuer Dateiname + + + Validierungsfehler + + \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor new file mode 100644 index 00000000..c422360c --- /dev/null +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor @@ -0,0 +1,127 @@ + +@inherits BaseLocalizedComponent + +@inject IStringLocalizer _localizer +@inject IStringLocalizer _validationMessageLocalizer +@inject ISessionStateContainer _sessionStateContainer +@inject ICuesheetManager _cuesheetManager +@inject IAudiofileManager _audiofileManager +@inject IDialogService _dialogService + +@switch (CurrentViewMode) +{ + case ViewMode.DetailView: + case ViewMode.ImportView: + var validationResult = _cuesheet?.Validate(nameof(Cuesheet.Audiofiles)); + if (validationResult?.Status == ValidationStatus.Error) + { + + @_localizer["Validation errors"] + @foreach (var message in validationResult.ValidationMessages) + { + @message.GetMessageLocalized(_validationMessageLocalizer) + } + +
+ } + break; +} + + + @foreach (var file in Files) + { + + + + + @{ + var fileInputId = $"Audiofile_{Guid.NewGuid()}"; + } + x.MimeType))" + FileRenameDisabled="file.Name == null" Error="file.Validate().Status == ValidationStatus.Error" /> + + + + + + + } + + +@code { + Cuesheet? _cuesheet => _sessionStateContainer.GetActiveCuesheet(); + + [Parameter] + [EditorRequired] + public ICollection Files { get; set; } + + [CascadingParameter] + public ViewMode CurrentViewMode { get; set; } + + [Parameter] + public HashSet SelectedFiles { get; set; } = []; + + [Parameter] + public EventCallback> SelectedFilesChanged { get; set; } + + async Task ShowInputDialog(string? initialValue) + { + var parameters = new DialogParameters + { + { x => x.Placeholder, _localizer["Enter the new file name here"] }, + { x => x.Label, _localizer["New file name"] }, + { x => x.InitialValue, initialValue } + }; + var options = new DialogOptions() { CloseOnEscapeKey = true, BackdropClick = false, FullWidth = true, CloseButton = true }; + var dialog = await _dialogService.ShowAsync(_localizer["Change file name"], parameters, options); + var result = await dialog.Result; + var newFileName = result?.Data as string; + if ((result?.Canceled == false) && (String.IsNullOrEmpty(newFileName) == false)) + { + return newFileName; + } + else + { + return null; + } + } + + async Task FileRenameClicked(Audiofile audiofile) + { + var newFileName = await ShowInputDialog(audiofile.Name); + if (String.IsNullOrEmpty(newFileName) == false) + { + _audiofileManager.SetProperty(audiofile, x => x.Name, newFileName); + } + } + + void SelectedChanged(Audiofile audiofile, Boolean selected) + { + if (selected) + { + SelectedFiles.Add(audiofile); + } + else + { + SelectedFiles.Remove(audiofile); + } + SelectedFilesChanged.InvokeAsync(SelectedFiles); + } +} diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.resx b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.resx new file mode 100644 index 00000000..7c5456d4 --- /dev/null +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Audiofile + + + Change file name + + + Enter the new file name here + + + New file name + + + Validation errors + + \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor b/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor index 6de35334..097d8ee1 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor @@ -23,24 +23,21 @@ along with Foobar. If not, see @inject IDialogService _dialogService @inject ISessionStateContainer _sessionStateContainer @inject ICuesheetManager _cuesheetManager -@inject ITrackManager _trackManager -@if (Cuesheet != null) +@if (_cuesheet != null) { - - + - @switch(CurrentViewMode) { case ViewMode.ImportView: case ViewMode.DetailView: - x.MimeType))" OnFileRenameClicked="AudioFileRename" FileRenameDisabled="Cuesheet.Audiofile == null" /> - - + break; @@ -49,24 +46,10 @@ along with Foobar. If not, see } @code { - public Cuesheet? Cuesheet - { - get - { - if (CurrentViewMode == ViewMode.ImportView) - { - return _sessionStateContainer.ImportCuesheet; - } - return _sessionStateContainer.Cuesheet; - } - } - [CascadingParameter] public ViewMode CurrentViewMode { get; set; } - string? _fileInputAudiofileId; - string? _fileInputAudiofileErrorText; - string? _fileInputCDTextfileErrorText; + Cuesheet? _cuesheet => _sessionStateContainer.GetActiveCuesheet(); string? _catalogueNumber; MudForm? _form; MudTextField? _catalogueNumberTextField; @@ -74,10 +57,6 @@ along with Foobar. If not, see protected override void OnInitialized() { base.OnInitialized(); - if (_fileInputAudiofileId == null) - { - _fileInputAudiofileId = $"Input_Audiofile_{Guid.NewGuid()}"; - } TraceChangeManager.UndoDone += TraceChangeManager_UndoDone; TraceChangeManager.RedoDone += TraceChangeManager_RedoDone; _sessionStateContainer.CuesheetChanged += SessionStateContainer_CuesheetChanged; @@ -96,86 +75,29 @@ along with Foobar. If not, see protected override void OnParametersSet() { base.OnParametersSet(); - if (_form?.IsTouched == true) - { - SetAudiofileValidationText(); - } - _catalogueNumber = Cuesheet?.Cataloguenumber; - } - - async Task OnAudiofileSelected(IBrowserFile? browserFile) - { - if (Cuesheet == null) - { - return; - } - _fileInputAudiofileErrorText = null; - try - { - Audiofile? audiofile = null; - if (browserFile != null) - { - var fileUpload = await _fileInputManager.CreateFileUploadsAsync([browserFile], _fileInputAudiofileId); - audiofile = await _fileInputManager.CreateAudiofileAsync(fileUpload.Single()); - } - _cuesheetManager.SetProperty(x => x.Audiofile, audiofile); - } - catch(ArgumentException ae) - { - _fileInputAudiofileErrorText = ae.Message; - } - // Just validate the cuesheet if there is no error already - if (_fileInputAudiofileErrorText == null) - { - SetAudiofileValidationText(); - } - } - - void SetAudiofileValidationText() - { - if (Cuesheet == null) - { - return; - } - var validationMessages = _validationService.Validate(Cuesheet, nameof(Cuesheet.Audiofile)); - if (validationMessages.Count() > 0) - { - _fileInputAudiofileErrorText = String.Join(Environment.NewLine, validationMessages); - } - else - { - _fileInputAudiofileErrorText = null; - } + _catalogueNumber = _cuesheet?.Cataloguenumber; } void OnCDTextfileSelected(IBrowserFile? browserFile) { - if (Cuesheet == null) + if (_cuesheet == null) { return; } - _fileInputCDTextfileErrorText = null; - try + CDTextfile? newValue = null; + if (browserFile != null) { - CDTextfile? newValue = null; - if (browserFile != null) - { - newValue = _fileInputManager.CreateCDTextfile(browserFile.ContentType, browserFile.Name); - } - _cuesheetManager.SetProperty(x => x.CDTextfile, newValue); - } - catch (ArgumentException ae) - { - _fileInputCDTextfileErrorText = ae.Message; + newValue = _fileInputManager.CreateCDTextfile(browserFile.ContentType, browserFile.Name); } + _cuesheetManager.SetProperty(x => x.CDTextfile, newValue); } async Task CDTextFileRename() { - var newFileName = await ShowInputDialog(Cuesheet?.CDTextfile?.Name); + var newFileName = await ShowInputDialog(_cuesheet?.CDTextfile?.Name); if (String.IsNullOrEmpty(newFileName) == false) { - var cdTextFile = Cuesheet?.CDTextfile; + var cdTextFile = _cuesheet?.CDTextfile; if (cdTextFile != null) { cdTextFile.Name = newFileName; @@ -183,19 +105,6 @@ along with Foobar. If not, see } } - async Task AudioFileRename() - { - var newFileName = await ShowInputDialog(Cuesheet?.Audiofile?.Name); - if (String.IsNullOrEmpty(newFileName) == false) - { - var audioFile = Cuesheet?.Audiofile; - if (audioFile != null) - { - audioFile.Name = newFileName; - } - } - } - async Task ShowInputDialog(string? initialValue) { var parameters = new DialogParameters @@ -226,13 +135,13 @@ along with Foobar. If not, see void TraceChangeManager_RedoDone(object? sender, EventArgs e) { - _catalogueNumber = Cuesheet?.Cataloguenumber; + _catalogueNumber = _cuesheet?.Cataloguenumber; _catalogueNumberTextField?.ResetAsync(); } void TraceChangeManager_UndoDone(object? sender, EventArgs e) { - _catalogueNumber = Cuesheet?.Cataloguenumber; + _catalogueNumber = _cuesheet?.Cataloguenumber; _catalogueNumberTextField?.ResetAsync(); } diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx index 13c2296c..41808472 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx @@ -154,7 +154,7 @@ Schema Titel - Geben Sie hier das Spuren-Schema für dieses Profil ein + Geben Sie hier das Titel-Schema für dieses Profil ein Schema Fuß @@ -207,4 +207,10 @@ Kein Exportprofil ausgewählt! + + Schema Audiodateien + + + Geben Sie hier das Audiodatei-Schema für dieses Profil ein + \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor index 7444765e..08c77475 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor @@ -82,6 +82,24 @@ along with Foobar. If not, see } + + + @foreach (var placeholder in Exportprofile.AvailableAudiofileSchemes) + { + + @_localizer[placeholder.Key] + + } + _displayContentDialogVisible = new(); private readonly DialogOptions _displayContentDialogOptions = new() { CloseButton = true, CloseOnEscapeKey = true, FullWidth = true, MaxWidth = MaxWidth.ExtraExtraLarge }; diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx index d3274d80..219748e9 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx @@ -207,4 +207,10 @@ No export profile selected! + + Scheme Audiofiles + + + Enter the audiofiles scheme for this profile here + \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.de.resx b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.de.resx index 48c3b602..c8138d4c 100644 --- a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.de.resx +++ b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.de.resx @@ -123,7 +123,7 @@ Fehler während des Textimports - - Titel + + Dateien \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.razor b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.razor index 60e287c7..99cee2d2 100644 --- a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.razor +++ b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.razor @@ -36,29 +36,25 @@ along with Foobar. If not, see } else { - - - - @_localizer["Common data"] - - - - - - - - @_localizer["Tracks"] - - - - - - + + + @_localizer["Common data"] + + + + + + + + @_localizer["Files"] + + + + + } @code { - Boolean cuesheetDataExpanded = true, cuesheetTracksExpanded = true; - public String? FileContentRecognized => _sessionStateContainer.Importfile?.FileContentRecognized; string SanitizeHTML(string input) diff --git a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.resx b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.resx index f4f133c6..d5f1bf42 100644 --- a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.resx +++ b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.resx @@ -123,7 +123,7 @@ Error during textimport - - Tracks + + Files \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Import/Importprofiles.de.resx b/AudioCuesheetEditor/Shared/Import/Importprofiles.de.resx index 56ba6509..be523a1e 100644 --- a/AudioCuesheetEditor/Shared/Import/Importprofiles.de.resx +++ b/AudioCuesheetEditor/Shared/Import/Importprofiles.de.resx @@ -147,6 +147,9 @@ Ende + + Geben Sie hier das Audiodatei Schema für das Profil an + Geben Sie hier das Allgemeine Informationen Schema für das Profil an @@ -198,6 +201,9 @@ Importprofile zurücksetzen + + Schema Audiodateien + Schema Allgemeine Informationen diff --git a/AudioCuesheetEditor/Shared/Import/Importprofiles.razor b/AudioCuesheetEditor/Shared/Import/Importprofiles.razor index ffbd2063..53c7c68f 100644 --- a/AudioCuesheetEditor/Shared/Import/Importprofiles.razor +++ b/AudioCuesheetEditor/Shared/Import/Importprofiles.razor @@ -1,5 +1,4 @@ -@using System.Linq.Expressions -