diff --git a/.github/workflows/deploy_github_pages.yml b/.github/workflows/deploy_github_pages.yml deleted file mode 100644 index c2447627..00000000 --- a/.github/workflows/deploy_github_pages.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Deploy to GitHub Pages - -# Run workflow on every push to the production branch -on: - push: - branches: [ production ] - -jobs: - deploy-to-github-pages: - # use ubuntu-latest image to run steps on - runs-on: ubuntu-latest - steps: - # uses GitHub's checkout action to checkout code from the production branch - - uses: actions/checkout@v3.1.0 - with: - ref: production - - # sets up .NET - # version can be found here https://dotnet.microsoft.com/en-us/download/dotnet/6.0 - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '6.0.401' - - # Only publish when unit tests are ok - - name: Run Unit Tests - run: dotnet test - - # publishes Blazor project to the release-folder - - name: Publish .NET Core Project - run: dotnet publish --no-restore AudioCuesheetEditor/AudioCuesheetEditor.csproj -c Release -o release --nologo - - # changes the base-tag in index.html from '/' to 'AudioCuesheetEditor' to match GitHub Pages repository subdirectory - - name: Change base-tag in index.html from / to AudioCuesheetEditor - run: sed -i 's///g' release/wwwroot/index.html - - # changes the favicon in index.html from '/' to 'AudioCuesheetEditor' to match GitHub Pages repository subdirectory - - name: Change favicon in index.html from / to AudioCuesheetEditor - run: sed -i 's///g' release/wwwroot/index.html - - # copy index.html to 404.html to serve the same file when a file is not found - - name: copy index.html to 404.html - run: cp release/wwwroot/index.html release/wwwroot/404.html - - # add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project. (Allow files and folders starting with an underscore) - - name: Add .nojekyll file - run: touch release/wwwroot/.nojekyll - - - name: Commit wwwroot to GitHub Pages - uses: JamesIves/github-pages-deploy-action@3.7.1 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: gh-pages - FOLDER: release/wwwroot diff --git a/.github/workflows/run_unit_tests.yml b/.github/workflows/run_unit_tests.yml index eba69d9c..e436b851 100644 --- a/.github/workflows/run_unit_tests.yml +++ b/.github/workflows/run_unit_tests.yml @@ -10,14 +10,17 @@ jobs: # use ubuntu-latest image to run steps on runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v4.1.2 # sets up .NET - # version can be found here https://dotnet.microsoft.com/en-us/download/dotnet/6.0 + # version can be found here https://dotnet.microsoft.com/en-us/download/dotnet/7.0 - name: Setup .NET - uses: actions/setup-dotnet@v3.0.1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: '6.0.401' + dotnet-version: '7.0.101' + + - name: Install wasm-tools + run: dotnet workload install wasm-tools-net7 # Only publish when unit tests are ok - name: Run Unit Tests diff --git a/.gitignore b/.gitignore index 0812c246..5b15ab4f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ **/obj **/.vs *.user +*.user diff --git a/AudioCuesheetEditor/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index 6908e074..1f86a34b 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -1,14 +1,15 @@ - net6.0 + net7.0 true - enable - enable + enable + enable https://github.com/NeoCoderMatrix86/AudioCuesheetEditor 3.0 - 3.3.0 + 4.0.0 true + true @@ -20,21 +21,20 @@ + + - - + + - - - @@ -43,6 +43,7 @@ + @@ -59,22 +60,20 @@ + + - - + + - - - - @@ -83,6 +82,8 @@ + + @@ -95,19 +96,19 @@ - + - - + + - + - - - - - + + + + + diff --git a/AudioCuesheetEditor/AudioCuesheetEditor.csproj.user b/AudioCuesheetEditor/AudioCuesheetEditor.csproj.user deleted file mode 100644 index 6081432e..00000000 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj.user +++ /dev/null @@ -1,9 +0,0 @@ - - - - ProjectDebugger - - - IIS Express - - \ No newline at end of file diff --git a/AudioCuesheetEditor/Controller/CuesheetController.cs b/AudioCuesheetEditor/Controller/CuesheetController.cs deleted file mode 100644 index 75a9c077..00000000 --- a/AudioCuesheetEditor/Controller/CuesheetController.cs +++ /dev/null @@ -1,85 +0,0 @@ -//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.Entity; -using AudioCuesheetEditor.Model.IO.Audio; -using AudioCuesheetEditor.Model.Reflection; -using Blazorise; -using Microsoft.AspNetCore.Components.Forms; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace AudioCuesheetEditor.Controller -{ - public class CuesheetController - { - private readonly Dictionary fieldIdentifier = new(); - - public String GetFieldIdentifier(IValidateable validateable, String property) - { - var identifier = fieldIdentifier.FirstOrDefault(x => x.Key.Owner == validateable && x.Key.Property == property); - if (identifier.Equals(default(KeyValuePair))) - { - fieldIdentifier.Add(FieldReference.Create(validateable, property), Guid.NewGuid()); - identifier = fieldIdentifier.FirstOrDefault(x => x.Key.Owner == validateable && x.Key.Property == property); - } - return String.Format("{0}_{1}", identifier.Key.CompleteName, identifier.Value.ToString()); - } - - public String GetFieldIdentifier(FieldReference fieldReference) - { - var identifier = fieldIdentifier.FirstOrDefault(x => x.Key == fieldReference); - if (identifier.Equals(default(KeyValuePair))) - { - fieldIdentifier.Add(fieldReference, Guid.NewGuid()); - identifier = fieldIdentifier.FirstOrDefault(x => x.Key == fieldReference); - } - return String.Format("{0}_{1}", identifier.Key.CompleteName, identifier.Value.ToString()); - } - - public static Boolean CheckFileMimeType(IFileEntry file, String mimeType, String fileExtension) - { - Boolean fileMimeTypeMatches = false; - if ((file != null) && (String.IsNullOrEmpty(mimeType) == false) && (String.IsNullOrEmpty(fileExtension) == false)) - { - if (String.IsNullOrEmpty(file.Type) == false) - { - fileMimeTypeMatches = file.Type.ToLower() == mimeType.ToLower(); - } - else - { - //Try to find by file extension - var extension = Path.GetExtension(file.Name).ToLower(); - fileMimeTypeMatches = extension == fileExtension.ToLower(); - } - } - return fileMimeTypeMatches; - } - - public static Boolean CheckFileMimeType(IFileEntry file, IReadOnlyCollection audioCodecs) - { - Boolean fileMimeTypeMatches = false; - if ((file != null) && (audioCodecs != null)) - { - var extension = Path.GetExtension(file.Name).ToLower(); - var audioCodecsFound = audioCodecs.Where(x => x.MimeType.Equals(file.Type, StringComparison.OrdinalIgnoreCase) || x.FileExtension.Equals(extension, StringComparison.OrdinalIgnoreCase)); - fileMimeTypeMatches = (audioCodecsFound != null) && (audioCodecsFound.Any()); - } - return fileMimeTypeMatches; - } - } -} diff --git a/AudioCuesheetEditor/Data/Options/LocalStorageOptionsProvider.cs b/AudioCuesheetEditor/Data/Options/LocalStorageOptionsProvider.cs index 91e0aefa..53311e85 100644 --- a/AudioCuesheetEditor/Data/Options/LocalStorageOptionsProvider.cs +++ b/AudioCuesheetEditor/Data/Options/LocalStorageOptionsProvider.cs @@ -35,7 +35,7 @@ public LocalStorageOptionsProvider(IJSRuntime jsRuntime) _jsRuntime = jsRuntime; } - public async ValueTask GetOptions() where T : IOptions + public async Task GetOptions() where T : IOptions { var type = typeof(T); IOptions? options = (IOptions?)Activator.CreateInstance(type); @@ -45,14 +45,7 @@ public async ValueTask GetOptions() where T : IOptions try { options = (IOptions?)JsonSerializer.Deserialize(optionsJson, typeof(T)); - if (options != null) - { - options.SetDefaultValues(); - } - else - { - options = (IOptions?)Activator.CreateInstance(typeof(T)); - } + options ??= (IOptions?)Activator.CreateInstance(typeof(T)); } catch (JsonException) { diff --git a/AudioCuesheetEditor/Extensions/SessionStateContainer.cs b/AudioCuesheetEditor/Extensions/SessionStateContainer.cs index 840832d3..d46385f6 100644 --- a/AudioCuesheetEditor/Extensions/SessionStateContainer.cs +++ b/AudioCuesheetEditor/Extensions/SessionStateContainer.cs @@ -13,16 +13,11 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using AudioCuesheetEditor.Controller; using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Model.IO.Import; using AudioCuesheetEditor.Model.Options; using AudioCuesheetEditor.Model.UI; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace AudioCuesheetEditor.Extensions { @@ -38,24 +33,18 @@ public class SessionStateContainer private Cuesheet? importCuesheet; private TextImportfile? textImportFile; private CuesheetImportfile? cuesheetImportFile; - private Audiofile? importAudioFile; + private Audiofile? importAudiofile; public SessionStateContainer(TraceChangeManager traceChangeManager) { - _traceChangeManager = traceChangeManager ?? throw new ArgumentNullException(nameof(traceChangeManager)); - cuesheet = new Cuesheet(); - _traceChangeManager.TraceChanges(Cuesheet); + _traceChangeManager = traceChangeManager; + cuesheet = new Cuesheet(_traceChangeManager); + SetCuesheetReference(cuesheet); } public Cuesheet Cuesheet { get { return cuesheet; } - set - { - cuesheet = value; - _traceChangeManager.Reset(); - _traceChangeManager.TraceChanges(Cuesheet); - CuesheetChanged?.Invoke(this, EventArgs.Empty); - } + set { SetCuesheetReference(value); } } public Cuesheet? ImportCuesheet { @@ -65,9 +54,9 @@ public Cuesheet? ImportCuesheet var previousValue = importCuesheet; importCuesheet = value; //When there is an audiofile from import, we use this file because it has an object url and gets duration, etc. - if ((importCuesheet != null) && (ImportAudioFile != null)) + if ((importCuesheet != null) && (ImportAudiofile != null)) { - importCuesheet.Audiofile = ImportAudioFile; + importCuesheet.Audiofile = ImportAudiofile; } if (Object.Equals(previousValue, importCuesheet) == false) { @@ -115,32 +104,20 @@ public CuesheetImportfile? CuesheetImportFile } } - public Audiofile? ImportAudioFile + public Audiofile? ImportAudiofile { - get => importAudioFile; + get => importAudiofile; set { - importAudioFile = value; - if ((ImportCuesheet != null) && (ImportAudioFile != null)) + importAudiofile = value; + if ((ImportCuesheet != null) && (ImportAudiofile != null)) { - ImportCuesheet.Audiofile = ImportAudioFile; + ImportCuesheet.Audiofile = ImportAudiofile; ImportCuesheetChanged?.Invoke(this, EventArgs.Empty); } } } - private void TextImportScheme_AnalysisFinished(object? sender, EventArgs eventArgs) - { - if (textImportFile != null) - { - ImportCuesheet = textImportFile.Cuesheet; - } - else - { - ImportCuesheet = null; - } - } - public ViewMode CurrentViewMode { get { return currentViewMode; } @@ -155,7 +132,7 @@ public void ResetImport() { TextImportFile = null; CuesheetImportFile = null; - ImportAudioFile = null; + ImportAudiofile = null; } public void StartImportCuesheet(ApplicationOptions applicationOptions) @@ -167,5 +144,30 @@ public void StartImportCuesheet(ApplicationOptions applicationOptions) } ResetImport(); } + + private void SetCuesheetReference(Cuesheet value) + { + cuesheet.CuesheetImported -= Cuesheet_CuesheetImported; + cuesheet = value; + cuesheet.CuesheetImported += Cuesheet_CuesheetImported; + _traceChangeManager.Reset(); + _traceChangeManager.TraceChanges(Cuesheet); + CuesheetChanged?.Invoke(this, EventArgs.Empty); + } + private void TextImportScheme_AnalysisFinished(object? sender, EventArgs eventArgs) + { + if (textImportFile != null) + { + ImportCuesheet = textImportFile.Cuesheet; + } + else + { + ImportCuesheet = null; + } + } + private void Cuesheet_CuesheetImported(object? sender, EventArgs e) + { + CuesheetChanged?.Invoke(this, EventArgs.Empty); + } } } diff --git a/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs b/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs index 441d28dc..0c68e392 100644 --- a/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs +++ b/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs @@ -13,17 +13,10 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using AudioCuesheetEditor.Controller; using AudioCuesheetEditor.Data.Options; using AudioCuesheetEditor.Model.Options; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.JSInterop; -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Threading.Tasks; namespace AudioCuesheetEditor.Extensions { diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/CDTextfile.cs b/AudioCuesheetEditor/Model/AudioCuesheet/CDTextfile.cs index 426fdacb..c4f53e36 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/CDTextfile.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/CDTextfile.cs @@ -25,15 +25,14 @@ public class CDTextfile public const String MimeType = "text/*"; public const String FileExtension = ".cdt"; - public CDTextfile(String fileName) + public CDTextfile(String name) { - if (String.IsNullOrEmpty(fileName)) + if (String.IsNullOrEmpty(name)) { - throw new ArgumentNullException(nameof(fileName)); + throw new ArgumentNullException(nameof(name)); } - FileName = fileName; + Name = name; } - - public String FileName { get; private set; } + public String Name { get; private set; } } } diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/CatalogueNumber.cs b/AudioCuesheetEditor/Model/AudioCuesheet/CatalogueNumber.cs index bdee60b9..fa38b21f 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/CatalogueNumber.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/CatalogueNumber.cs @@ -14,22 +14,12 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.Entity; -using AudioCuesheetEditor.Model.Reflection; using AudioCuesheetEditor.Model.UI; -using Blazorise.Localization; -using System; -using System.Collections.Generic; -using System.Linq; namespace AudioCuesheetEditor.Model.AudioCuesheet { - public class Cataloguenumber : Validateable, IEntityDisplayName, ITraceable + public class Cataloguenumber : Validateable, ITraceable { - public Cataloguenumber() - { - Validate(); - } - private String? value; public event EventHandler? TraceablePropertyChanged; @@ -44,29 +34,30 @@ public String? Value FireEvents(oldValue, propertyName: nameof(Value)); } } - - public String GetDisplayNameLocalized(ITextLocalizer localizer) - { - return localizer[nameof(Cuesheet)]; - } - - protected override void Validate() + protected override ValidationResult Validate(string property) { - if (String.IsNullOrEmpty(Value)) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Value)), ValidationErrorType.Warning, "{0} has no value!", nameof(Cataloguenumber))); - } - else + ValidationStatus validationStatus = ValidationStatus.NoValidation; + List? validationMessages = null; + switch (property) { - if (Value.All(Char.IsDigit) == false) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Value)), ValidationErrorType.Error, "{0} does not only contain numbers.", nameof(Cataloguenumber))); - } - if (Value.Length != 13) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Value)), ValidationErrorType.Error, "{0} has invalid length ({1})!", nameof(Cataloguenumber), 13)); - } + case nameof(Value): + if (String.IsNullOrEmpty(Value) == false) + { + validationStatus = ValidationStatus.Success; + if (Value.All(Char.IsDigit) == false) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} must only contain numbers!", nameof(Value))); + } + if (Value.Length != 13) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has an invalid length. Allowed length is {1}!", nameof(Value), 13)); + } + } + break; } + return ValidationResult.Create(validationStatus, validationMessages); } private void OnTraceablePropertyChanged(object? previousValue, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") @@ -78,7 +69,7 @@ private void OnTraceablePropertyChanged(object? previousValue, [System.Runtime.C /// Method for checking if fire of events should be done /// /// Previous value of the property firing events - /// Fire ValidateablePropertyChanged? + /// Fire OnValidateablePropertyChanged? /// Fire TraceablePropertyChanged? /// Property firing the event /// If propertyName can not be found, an exception is thrown. @@ -92,7 +83,7 @@ private void FireEvents(object? previousValue, Boolean fireValidateablePropertyC { if (fireValidateablePropertyChanged) { - OnValidateablePropertyChanged(); + OnValidateablePropertyChanged(propertyName); } if (fireTraceablePropertyChanged) { diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs index d346caa0..5c868473 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs @@ -13,19 +13,13 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using AudioCuesheetEditor.Controller; using AudioCuesheetEditor.Model.Entity; using AudioCuesheetEditor.Model.IO; using AudioCuesheetEditor.Model.IO.Audio; -using AudioCuesheetEditor.Model.IO.Import; +using AudioCuesheetEditor.Model.IO.Export; using AudioCuesheetEditor.Model.Options; -using AudioCuesheetEditor.Model.Reflection; using AudioCuesheetEditor.Model.UI; -using System; -using System.Collections.Generic; -using System.Linq; using System.Text.Json.Serialization; -using System.Threading.Tasks; namespace AudioCuesheetEditor.Model.AudioCuesheet { @@ -42,34 +36,50 @@ public TrackAddRemoveEventArgs(Track track) Track = track; } - public Track Track { get; private set; } + public Track Track { get; } } - public class Cuesheet : Validateable, ICuesheetEntity, ITraceable + + public class SplitPointAddRemoveEventArgs : EventArgs + { + public SplitPointAddRemoveEventArgs(SplitPoint splitPoint) + { + SplitPoint = splitPoint; + } + + public SplitPoint SplitPoint { get; } + } + + public class Cuesheet : Validateable, ICuesheetEntity, ITraceable { public const String MimeType = "text/*"; public const String FileExtension = ".cue"; private readonly object syncLock = new(); - private List tracks = default!; + private List tracks; private String? artist; private String? title; private Audiofile? audiofile; private CDTextfile? cDTextfile; - private Cataloguenumber catalogueNumber = default!; + private Cataloguenumber catalogueNumber; private DateTime? recordingStart; private readonly List> currentlyHandlingLinkedTrackPropertyChange = new(); + private List splitPoints; public event EventHandler? AudioFileChanged; public event EventHandler? TraceablePropertyChanged; public event EventHandler? TrackAdded; public event EventHandler? TrackRemoved; + public event EventHandler? SplitPointAdded; + public event EventHandler? SplitPointRemoved; + public event EventHandler? CuesheetImported; - public Cuesheet() + public Cuesheet(TraceChangeManager? traceChangeManager = null) { - Tracks = new List(); - Cataloguenumber = new Cataloguenumber(); - OnValidateablePropertyChanged(); + tracks = new(); + catalogueNumber = new(); + splitPoints = new(); + TraceChangeManager = traceChangeManager; } [JsonInclude] @@ -78,7 +88,12 @@ public IReadOnlyCollection Tracks get { return tracks.AsReadOnly(); } private set { - var previousValue = tracks; + foreach (var track in tracks) + { + track.RankPropertyValueChanged -= Track_RankPropertyValueChanged; + track.IsLinkedToPreviousTrackChanged -= Track_IsLinkedToPreviousTrackChanged; + track.Cuesheet = null; + } tracks = value.ToList(); foreach (var track in tracks) { @@ -86,7 +101,6 @@ private set track.RankPropertyValueChanged += Track_RankPropertyValueChanged; track.IsLinkedToPreviousTrackChanged += Track_IsLinkedToPreviousTrackChanged; } - FireEvents(previousValue, false, true, false); } } @@ -143,7 +157,7 @@ public CDTextfile? CDTextfile { var previousValue = cDTextfile; cDTextfile = value; - FireEvents(previousValue, propertyName: nameof(CDTextfile)); + FireEvents(previousValue, fireValidateablePropertyChanged: false, propertyName: nameof(CDTextfile)); } } @@ -152,27 +166,9 @@ public Cataloguenumber Cataloguenumber get => catalogueNumber; set { - if (catalogueNumber != null) - { - catalogueNumber.ValidateablePropertyChanged -= CatalogueNumber_ValidateablePropertyChanged; - } var previousValue = catalogueNumber; catalogueNumber = value; - if (catalogueNumber != null) - { - catalogueNumber.ValidateablePropertyChanged += CatalogueNumber_ValidateablePropertyChanged; - } - FireEvents(previousValue, propertyName: nameof(Cataloguenumber)); - } - } - - [JsonIgnore] - public Boolean CanWriteCuesheetFile - { - get - { - var cuesheetFile = new Cuesheetfile(this); - return cuesheetFile.IsExportable; + FireEvents(previousValue, fireValidateablePropertyChanged: false, propertyName: nameof(Cataloguenumber)); } } @@ -199,6 +195,49 @@ public TimeSpan? RecordingTime [JsonIgnore] public Boolean IsImporting { get; private set; } + + [JsonInclude] + public IReadOnlyCollection SplitPoints + { + get => splitPoints; + private set + { + foreach(var splitpoint in value.Where(x => x.Cuesheet != this)) + { + splitpoint.Cuesheet = this; + } + splitPoints = value.ToList(); + } + } + + [JsonIgnore] + public TraceChangeManager? TraceChangeManager { get; } + + public SplitPoint AddSplitPoint() + { + var previousValue = new List(splitPoints); + var splitPoint = new SplitPoint(this); + splitPoints.Add(splitPoint); + SplitPointAdded?.Invoke(this, new SplitPointAddRemoveEventArgs(splitPoint)); + OnTraceablePropertyChanged(previousValue, nameof(SplitPoints)); + return splitPoint; + } + + public void RemoveSplitPoint(SplitPoint splitPoint) + { + var previousValue = new List(splitPoints); + if (splitPoints.Remove(splitPoint)) + { + OnTraceablePropertyChanged(previousValue, nameof(SplitPoints)); + SplitPointRemoved?.Invoke(this, new SplitPointAddRemoveEventArgs(splitPoint)); + } + } + + public SplitPoint? GetSplitPointAtTrack(Track track) + { + SplitPoint? splitPointAtTrack = SplitPoints?.FirstOrDefault(x => track.Begin < x.Moment && track.End >= x.Moment); + return splitPointAtTrack; + } /// /// Get the previous linked track of a track object @@ -238,16 +277,12 @@ public void AddTrack(Track track, ApplicationOptions? applicationOptions = null) { track.Begin = CalculateTimeSpanWithSensitivity(DateTime.UtcNow - recordingStart.Value, applicationOptions.RecordTimeSensitivity); } - if (applicationOptions.LinkTracksWithPreviousOne.HasValue) - { - track.IsLinkedToPreviousTrack = applicationOptions.LinkTracksWithPreviousOne.Value; - } + track.IsLinkedToPreviousTrack = applicationOptions.LinkTracksWithPreviousOne; } tracks.Add(track); track.Cuesheet = this; ReCalculateTrackProperties(track); track.RankPropertyValueChanged += Track_RankPropertyValueChanged; - OnValidateablePropertyChanged(); OnTraceablePropertyChanged(previousValue, nameof(Tracks)); if (IsImporting == false) { @@ -257,10 +292,6 @@ public void AddTrack(Track track, ApplicationOptions? applicationOptions = null) public void RemoveTrack(Track track) { - if (track == null) - { - throw new ArgumentNullException(nameof(track)); - } var index = tracks.IndexOf(track); Track? nextTrack = null; if ((index + 1) < tracks.Count) @@ -276,7 +307,6 @@ public void RemoveTrack(Track track) track.Cuesheet = null; track.RankPropertyValueChanged -= Track_RankPropertyValueChanged; track.IsLinkedToPreviousTrackChanged -= Track_IsLinkedToPreviousTrackChanged; - OnValidateablePropertyChanged(); //If Tracks are linked, we need to set the linked track again if (nextTrack != null) { @@ -305,10 +335,6 @@ public void RemoveTrack(Track track) /// Selected tracks to remove (can not be null, only empty) public void RemoveTracks(IReadOnlyCollection tracksToRemove) { - if (tracksToRemove == null) - { - throw new ArgumentNullException(nameof(tracksToRemove)); - } var previousValue = new List(); tracks.ForEach(x => previousValue.Add(new Track(x))); tracks.ForEach(x => x.RankPropertyValueChanged -= Track_RankPropertyValueChanged); @@ -333,17 +359,12 @@ public void RemoveTracks(IReadOnlyCollection tracksToRemove) tracks.ForEach(x => x.RankPropertyValueChanged += Track_RankPropertyValueChanged); tracks.ForEach(x => x.IsLinkedToPreviousTrackChanged += Track_IsLinkedToPreviousTrackChanged); RecalculateLastTrackEnd(); - OnValidateablePropertyChanged(); OnTraceablePropertyChanged(previousValue, nameof(Tracks)); } public Boolean MoveTrackPossible(Track track, MoveDirection moveDirection) { Boolean movePossible = false; - if (track == null) - { - throw new ArgumentNullException(nameof(track)); - } lock (syncLock) { var index = Tracks.ToList().IndexOf(track); @@ -367,10 +388,6 @@ public Boolean MoveTrackPossible(Track track, MoveDirection moveDirection) public void MoveTrack(Track track, MoveDirection moveDirection) { - if (track == null) - { - throw new ArgumentNullException(nameof(track)); - } var index = tracks.IndexOf(track); Track? currentTrack = null; switch (moveDirection) @@ -416,6 +433,7 @@ public void Import(Cuesheet cuesheet, ApplicationOptions applicationOptions, Tra } IsImporting = false; } + CuesheetImported?.Invoke(this, EventArgs.Empty); } public void StartRecording() @@ -434,42 +452,78 @@ public void StopRecording(ApplicationOptions applicationOptions) recordingStart = null; } - protected override void Validate() + protected override ValidationResult Validate(string property) { - if (String.IsNullOrEmpty(Artist) == true) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Artist)), ValidationErrorType.Warning, "{0} has no value!", nameof(Artist))); - } - if (String.IsNullOrEmpty(Title) == true) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Title)), ValidationErrorType.Warning, "{0} has no value!", nameof(Title))); - } - if (Audiofile == null) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Audiofile)), ValidationErrorType.Error, "{0} has no value!", nameof(Audiofile))); - } - if (tracks.Count < 1) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Tracks)), ValidationErrorType.Error, "{0} has invalid Count ({1})!", nameof(Tracks), 0)); - } - if (CDTextfile == null) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(CDTextfile)), ValidationErrorType.Warning, "{0} has no value!", nameof(CDTextfile))); - } - if (Cataloguenumber == null) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Cataloguenumber)), ValidationErrorType.Warning, "{0} has no value!", nameof(Cataloguenumber))); - } - else + ValidationStatus validationStatus = ValidationStatus.NoValidation; + List? validationMessages = null; + switch (property) { - _ = Cataloguenumber.IsValid; - validationErrors.AddRange(Cataloguenumber.ValidationErrors); + case nameof(Tracks): + validationStatus = ValidationStatus.Success; + if (Tracks.Count <= 0) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has invalid Count ({1})!", nameof(Tracks), 0)); + } + else + { + //Check track overlapping + var tracksWithSamePosition = Tracks.Where(x => x.IsCloned == false) + .GroupBy(x => x.Position) + .Where(grp => grp.Count() > 1); + if (tracksWithSamePosition.Any()) + { + validationMessages ??= new(); + 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 ??= new(); + 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): + validationStatus = ValidationStatus.Success; + if (Audiofile == null) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Audiofile))); + } + break; + case nameof(Artist): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(Artist)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Artist))); + } + break; + case nameof(Title): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(Title)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Title))); + } + break; } - } - - private void CatalogueNumber_ValidateablePropertyChanged(object? sender, EventArgs e) - { - OnValidateablePropertyChanged(); + return ValidationResult.Create(validationStatus, validationMessages); } private void ReCalculateTrackProperties(Track trackToCalculate) @@ -477,6 +531,7 @@ private void ReCalculateTrackProperties(Track trackToCalculate) if ((Audiofile != null) && (Audiofile.Duration.HasValue) && (trackToCalculate.End.HasValue == false)) { trackToCalculate.End = Audiofile.Duration; + TraceChangeManager?.MergeLastEditWithEdit(x => x.Changes.All(y => y.TraceableObject == this && y.TraceableChange.PropertyName == nameof(Audiofile))); } if (Tracks.Count > 1) { @@ -518,7 +573,7 @@ private void ReCalculateTrackProperties(Track trackToCalculate) { trackToCalculate.Begin = TimeSpan.Zero; } - } + } } /// @@ -539,34 +594,25 @@ private void CopyValues(Cuesheet cuesheet, ApplicationOptions applicationOptions var track = new Track(importTrack, false); AddTrack(track, applicationOptions); } + // Copy splitpoints + foreach (var splitPoint in cuesheet.SplitPoints) + { + var newSplitPoint = AddSplitPoint(); + newSplitPoint.CopyValues(splitPoint); + } } private void Track_RankPropertyValueChanged(object? sender, string e) { - if (sender != null) + if (sender is Track trackRaisedEvent) { - Track trackRaisedEvent = (Track)sender; - switch (e) - { - case nameof(Track.Position): - //Check position and call switchtracks - if (trackRaisedEvent.Position.HasValue) - { - var trackAtPosition = tracks.ElementAtOrDefault((int)trackRaisedEvent.Position.Value - 1); - if ((trackAtPosition != null) && (trackAtPosition != trackRaisedEvent)) - { - SwitchTracks(trackRaisedEvent, trackAtPosition); - } - } - break; - } var item = KeyValuePair.Create(e, trackRaisedEvent); if (currentlyHandlingLinkedTrackPropertyChange.Contains(item) == false) { currentlyHandlingLinkedTrackPropertyChange.Add(item); var linkedPreviousTrack = GetPreviousLinkedTrack(trackRaisedEvent); //Check if raising track has linked previous track - if ((trackRaisedEvent.IsLinkedToPreviousTrack) && (linkedPreviousTrack != null)) + if (trackRaisedEvent.IsLinkedToPreviousTrack && (linkedPreviousTrack != null)) { switch (e) { @@ -748,7 +794,7 @@ private void RecalculateLastTrackEnd() /// /// Previous value of the property firing events /// Fire AudioFileChanged? - /// Fire ValidateablePropertyChanged? + /// Fire OnValidateablePropertyChanged? /// Fire TraceablePropertyChanged? /// Property firing the event /// If propertyName can not be found, an exception is thrown. @@ -766,7 +812,7 @@ private void FireEvents(object? previousValue, Boolean fireAudioFileChanged = fa } if (fireValidateablePropertyChanged) { - OnValidateablePropertyChanged(); + OnValidateablePropertyChanged(propertyName); } if (fireTraceablePropertyChanged) { diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs index d2509dc7..e34dd1d3 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs @@ -14,13 +14,7 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.Entity; -using AudioCuesheetEditor.Model.Reflection; using AudioCuesheetEditor.Model.UI; -using Blazorise.Localization; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using System.Text.Json.Serialization; namespace AudioCuesheetEditor.Model.AudioCuesheet @@ -31,7 +25,7 @@ public enum SetFlagMode Remove } - public class Track : Validateable, ICuesheetEntity, ITraceable + public class Track : Validateable, ICuesheetEntity, ITraceable { public static readonly List AllPropertyNames = new() { nameof(IsLinkedToPreviousTrack), nameof(Position), nameof(Artist), nameof(Title), nameof(Begin), nameof(End), nameof(Flags), nameof(PreGap), nameof(PostGap), nameof(Length) }; @@ -42,7 +36,6 @@ public class Track : Validateable, ICuesheetEntity, ITraceable private TimeSpan? end; private TimeSpan? _length; private List flags = new(); - private Track? clonedFrom = null; private Boolean isLinkedToPreviousTrack; private Cuesheet? cuesheet; private TimeSpan? preGap; @@ -70,12 +63,10 @@ public Track(Track track, Boolean copyCuesheetReference = true) { CopyValues(track, copyCuesheetReference); } - public Track() { - Validate(); + } - public uint? Position { get => position; @@ -90,7 +81,7 @@ public String? Title { get => title; set { var previousValue = title; title = value; FireEvents(previousValue, fireRankPropertyValueChanged: false, propertyName: nameof(Title)); } - } + } public TimeSpan? Begin { get => begin; @@ -99,7 +90,7 @@ public TimeSpan? Begin public TimeSpan? End { get => end; - set { var previousValue = end; end = value; FireEvents(previousValue, propertyName: nameof(End)); } + set { var previousValue = end; end = value; FireEvents(previousValue, propertyName: nameof(End)); } } /// /// If is set, should it be automatically change begin and end? Defaulting to true, because only during edit dialog this should be set to false. @@ -130,6 +121,7 @@ public TimeSpan? Length } set { + var previousValue = Length; if (AutomaticallyCalculateLength) { if ((Begin.HasValue == false) && (End.HasValue == false)) @@ -160,7 +152,7 @@ public TimeSpan? Length { _length = value; } - OnValidateablePropertyChanged(); + FireEvents(previousValue, fireRankPropertyValueChanged: false, fireTraceablePropertyChanged: false); } } [JsonInclude] @@ -180,22 +172,31 @@ private set public Cuesheet? Cuesheet { get => cuesheet; - set { var previousValue = cuesheet; cuesheet = value; FireEvents(previousValue, fireRankPropertyValueChanged:false, propertyName: nameof(Cuesheet)); } + set + { + var previousValue = cuesheet; + Boolean setValue = true; + if (value != null) + { + setValue = value.Tracks.Contains(this); + } + if (setValue) + { + cuesheet = value; + FireEvents(previousValue, fireValidateablePropertyChanged: false, fireRankPropertyValueChanged: false, propertyName: nameof(Cuesheet)); + } + } } /// /// Indicates that this track has been cloned from another track and is a transparent proxy /// [JsonIgnore] - public Boolean IsCloned { get { return clonedFrom != null; } } + public Boolean IsCloned { get { return ClonedFrom != null; } } /// /// Get the original track that this track has been cloned from. Can be null on original objects /// - public Track? ClonedFrom - { - get => clonedFrom; - private set { clonedFrom = value; OnValidateablePropertyChanged(); } - } + public Track? ClonedFrom { get; set; } /// public TimeSpan? PreGap { @@ -217,31 +218,6 @@ public Boolean IsLinkedToPreviousTrack set { var previousValue = IsLinkedToPreviousTrack; isLinkedToPreviousTrack = value; IsLinkedToPreviousTrackChanged?.Invoke(this, EventArgs.Empty); FireEvents(previousValue, fireValidateablePropertyChanged: false, fireRankPropertyValueChanged: false, propertyName: nameof(IsLinkedToPreviousTrack)); } } - public String? GetDisplayNameLocalized(ITextLocalizer localizer) - { - String? identifierString = null; - if (Position != null) - { - identifierString += String.Format("{0}", Position); - } - if (identifierString == null) - { - if (String.IsNullOrEmpty(Artist) == false) - { - identifierString += String.Format("{0}", Artist); - } - if (String.IsNullOrEmpty(Title) == false) - { - if (identifierString != null) - { - identifierString += ","; - } - identifierString += String.Format("{0}", Title); - } - } - return String.Format("{0} ({1})", localizer[nameof(Track)], identifierString); - } - /// /// Performs a deep clone of the current object /// @@ -387,7 +363,6 @@ public void CopyValues(Track track, Boolean setCuesheet = true, Boolean setIsLin IsLinkedToPreviousTrack = track.IsLinkedToPreviousTrack; } } - OnValidateablePropertyChanged(); } /// @@ -416,138 +391,83 @@ public void SetFlags(IEnumerable flags) this.flags.AddRange(flags); } - - public override string ToString() - { - return String.Format("({0} {1},{2} {3},{4} {5},{6} {7},{8} {9},{10} {11})", nameof(Position), Position, nameof(Artist), Artist, nameof(Title), Title, nameof(Begin), Begin, nameof(End), End, nameof(Length), Length); - } - - protected override void Validate() + protected override ValidationResult Validate(string property) { - if (Position == null) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Position)), ValidationErrorType.Error, "{0} has no value!", nameof(Position))); - } - if ((Position != null) && (Position == 0)) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Position)), ValidationErrorType.Error, "{0} has invalid value!", nameof(Position))); - } - if (String.IsNullOrEmpty(Artist) == true) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Artist)), ValidationErrorType.Warning, "{0} has no value!", nameof(Artist))); - } - if (String.IsNullOrEmpty(Title) == true) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Title)), ValidationErrorType.Warning, "{0} has no value!", nameof(Title))); - } - if (Begin == null) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Begin)), ValidationErrorType.Error, "{0} has no value!", nameof(Begin))); - } - else - { - if (Begin < TimeSpan.Zero) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Begin)), ValidationErrorType.Error, "{0} has invalid timespan!", nameof(Begin))); - } - } - if (End == null) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(End)), ValidationErrorType.Error, "{0} has no value!", nameof(End))); - } - else - { - if (End < TimeSpan.Zero) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(End)), ValidationErrorType.Error, "{0} has invalid timespan!", nameof(End))); - } - } - if (Length == null) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Length)), ValidationErrorType.Error, "{0} has no value! Please check {1} and {2}.", nameof(Length), nameof(Begin), nameof(End))); - } - //Check track overlapping - if (Cuesheet != null) + ValidationStatus validationStatus = ValidationStatus.NoValidation; + List? validationMessages = null; + switch (property) { - Cuesheet.Tracks.ToList().ForEach(x => x.RankPropertyValueChanged -= Track_RankPropertyValueChanged); - List tracksToAttachEventHandlerTo = new(); - if (Position.HasValue) - { - IEnumerable tracksWithSamePosition; - if (ClonedFrom != null) + case nameof(Position): + validationStatus = ValidationStatus.Success; + if (Position == null) { - tracksWithSamePosition = Cuesheet.Tracks.Where(x => x.Position == Position && x.Equals(this) == false && (x.Equals(ClonedFrom) == false)); + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Position))); } else { - tracksWithSamePosition = Cuesheet.Tracks.Where(x => x.Position == Position && x.Equals(this) == false); - } - if ((tracksWithSamePosition != null) && (tracksWithSamePosition.Any())) - { - tracksToAttachEventHandlerTo.AddRange(tracksWithSamePosition); - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Position)), ValidationErrorType.Error, "{0} {1} of this track is already in use by track(s) {2}!", nameof(Position), Position, String.Join(", ", tracksWithSamePosition))); - } - if (IsCloned == false) - { - Boolean addValidationError = false; - Track? trackAtPosition = Cuesheet.Tracks.ElementAtOrDefault((int)Position.Value - 1); - if (trackAtPosition != null) + if (Position == 0) { - if (trackAtPosition != this) - { - addValidationError = true; - } + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} may not be 0!", nameof(Position))); } else { - // Only validate the position if the current track belongs to cuesheet since otherwise it gets validated during AddTrack - if ((Cuesheet.Tracks.Contains(this)) && ((Cuesheet.Tracks.ToList().IndexOf(this) + 1) != Position.Value)) + // Check correct track position + if ((IsCloned == false) && (Cuesheet != null)) { - addValidationError = true; + var positionTrackShouldHave = Cuesheet.Tracks.OrderBy(x => x.Begin ?? TimeSpan.MaxValue).ThenBy(x => x.Position).ToList().IndexOf(this) + 1; + if (positionTrackShouldHave != Position) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("Track({0},{1},{2},{3},{4}) does not have the correct position '{5}'!", Position, Artist ?? String.Empty, Title ?? String.Empty, Begin != null ? Begin : String.Empty, End != null ? End : String.Empty, positionTrackShouldHave)); + } } } - if (addValidationError) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Position)), ValidationErrorType.Error, "{0} {1} of this track does not match track position in cuesheet. Please correct the {2} of this track to {3}!", nameof(Position), Position, nameof(Position), Cuesheet.Tracks.ToList().IndexOf(this) + 1)); - } } - } - if (Begin.HasValue) - { - IEnumerable tracksOverlapping; - if (ClonedFrom != null) + break; + case nameof(Begin): + validationStatus = ValidationStatus.Success; + if (Begin == null) { - tracksOverlapping = Cuesheet.Tracks.Where(x => Begin >= x.Begin && Begin < x.End && (x.Equals(this) == false) && (x.Equals(ClonedFrom) == false)); + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Begin))); } else { - tracksOverlapping = Cuesheet.Tracks.Where(x => Begin >= x.Begin && Begin < x.End && (x.Equals(this) == false)); - } - if ((tracksOverlapping != null) && tracksOverlapping.Any()) - { - tracksToAttachEventHandlerTo.AddRange(tracksOverlapping); - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Begin)), ValidationErrorType.Warning, "{0} is overlapping with other track(s) ({1})!", nameof(Begin), String.Join(", ", tracksOverlapping))); + if (Begin < TimeSpan.Zero) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} must be equal or greater zero!", nameof(Begin))); + } } - } - if (End.HasValue) - { - IEnumerable tracksOverlapping; - if (ClonedFrom != null) + break; + case nameof(End): + validationStatus = ValidationStatus.Success; + if (End == null) { - tracksOverlapping = Cuesheet.Tracks.Where(x => x.Begin < End && End <= x.End && (x.Equals(this) == false) && (x.Equals(ClonedFrom) == false)); + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(End))); } else { - tracksOverlapping = Cuesheet.Tracks.Where(x => x.Begin < End && End <= x.End && (x.Equals(this) == false)); + if (End < TimeSpan.Zero) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} must be equal or greater zero!", nameof(End))); + } } - if ((tracksOverlapping != null) && tracksOverlapping.Any()) + break; + case nameof(Length): + validationStatus = ValidationStatus.Success; + if (Length == null) { - tracksToAttachEventHandlerTo.AddRange(tracksOverlapping); - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(End)), ValidationErrorType.Warning, "{0} is overlapping with other track(s) ({1})!", nameof(End), String.Join(", ", tracksOverlapping))); + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Length))); } - } - tracksToAttachEventHandlerTo.ForEach(x => x.RankPropertyValueChanged += Track_RankPropertyValueChanged); + break; } + return ValidationResult.Create(validationStatus, validationMessages); } protected void OnTraceablePropertyChanged(object? previousValue, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") @@ -555,20 +475,6 @@ protected void OnTraceablePropertyChanged(object? previousValue, [System.Runtime TraceablePropertyChanged?.Invoke(this, new TraceablePropertiesChangedEventArgs(new TraceableChange(previousValue, propertyName))); } - private void Track_RankPropertyValueChanged(object? sender, string e) - { - if (sender != null) - { - Track track = (Track)sender; - track.RankPropertyValueChanged -= Track_RankPropertyValueChanged; - OnValidateablePropertyChanged(); - } - else - { - throw new ArgumentNullException(nameof(sender)); - } - } - /// /// Method for checking if fire of events should be done /// @@ -576,8 +482,8 @@ private void Track_RankPropertyValueChanged(object? sender, string e) /// Fire OnValidateablePropertyChanged? /// Fire RankPropertyValueChanged? /// Fire OnTraceablePropertyChanged? - /// Property firing the events /// If propertyName can not be found, an exception is thrown. + /// Property firing the events private void FireEvents(object? previousValue, Boolean fireValidateablePropertyChanged = true, Boolean fireRankPropertyValueChanged = true, Boolean fireTraceablePropertyChanged = true, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") { var propertyInfo = GetType().GetProperty(propertyName); @@ -588,7 +494,7 @@ private void FireEvents(object? previousValue, Boolean fireValidateablePropertyC { if (fireValidateablePropertyChanged) { - OnValidateablePropertyChanged(); + OnValidateablePropertyChanged(propertyName); } if (fireRankPropertyValueChanged) { diff --git a/AudioCuesheetEditor/Model/Entity/IValidateable.cs b/AudioCuesheetEditor/Model/Entity/IValidateable.cs index 60f1d654..eb779b5c 100644 --- a/AudioCuesheetEditor/Model/Entity/IValidateable.cs +++ b/AudioCuesheetEditor/Model/Entity/IValidateable.cs @@ -13,16 +13,63 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. +using AudioCuesheetEditor.Model.AudioCuesheet; using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; namespace AudioCuesheetEditor.Model.Entity { public interface IValidateable { - public Boolean IsValid { get; } - public IReadOnlyCollection ValidationErrors { get; } + /// + /// Validate all properties and return the result of validation. + /// + /// Validation result. + ValidationResult Validate(); + } + public interface IValidateable : IValidateable + { + /// + /// Validate a property and return the result of validation. + /// + /// Property type + /// Property selector + /// Validation result. + ValidationResult Validate(Expression> expression); + + public event EventHandler? ValidateablePropertyChanged; + } + + public enum ValidationStatus + { + NoValidation, + Success, + Error + } + public class ValidationResult + { + private List? validationMessages; + + public static ValidationResult Create(ValidationStatus validationStatus, IEnumerable? validationMessages = null) + { + return new ValidationResult() { Status = validationStatus, ValidationMessages = validationMessages?.ToList() }; + } + + public ValidationStatus Status { get; set; } + public List? ValidationMessages + { + get => validationMessages; + set + { + validationMessages = value; + if ((validationMessages != null) && validationMessages.Any()) + { + Status = ValidationStatus.Error; + } + } + } } } diff --git a/AudioCuesheetEditor/Model/Entity/Validateable.cs b/AudioCuesheetEditor/Model/Entity/Validateable.cs index 095ccd34..6c82a554 100644 --- a/AudioCuesheetEditor/Model/Entity/Validateable.cs +++ b/AudioCuesheetEditor/Model/Entity/Validateable.cs @@ -13,78 +13,61 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using Blazorise.Localization; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json.Serialization; +using AudioCuesheetEditor.Model.AudioCuesheet; +using System.Linq.Expressions; +using System.Reflection; namespace AudioCuesheetEditor.Model.Entity { - public abstract class Validateable : IValidateable + public abstract class Validateable : IValidateable { - protected List validationErrors = new(); - - [JsonIgnore] - public bool IsValid - { - get { return validationErrors.Count == 0; } - } + public event EventHandler? ValidateablePropertyChanged; - [JsonIgnore] - public IReadOnlyCollection ValidationErrors + public ValidationResult Validate(Expression> expression) { - get { return validationErrors.AsReadOnly(); } + if (expression.Body is not MemberExpression body) + { + throw new ArgumentException("'expression' should be a member expression"); + } + return Validate(body.Member.Name); } - public IReadOnlyCollection GetValidationErrorsFiltered(String? property = null, ValidationErrorFilterType validationErrorFilterType = ValidationErrorFilterType.All) + public ValidationResult Validate() { - IReadOnlyCollection returnValue = ValidationErrors; - if (String.IsNullOrEmpty(property) == false) + ValidationResult validationResult = new() { Status = ValidationStatus.NoValidation }; + foreach (var property in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)) { - if (property.Contains('.') == true) + var result = Validate(property.Name); + if (result.ValidationMessages != null) { - returnValue = ValidationErrors.Where(x => x.FieldReference.CompleteName == property).ToList().AsReadOnly(); + validationResult.ValidationMessages ??= new(); + validationResult.ValidationMessages.AddRange(result.ValidationMessages); } - else + switch (validationResult.Status) { - returnValue = ValidationErrors.Where(x => x.FieldReference.Property == property).ToList().AsReadOnly(); + case ValidationStatus.NoValidation: + case ValidationStatus.Success: + switch (result.Status) + { + case ValidationStatus.Success: + case ValidationStatus.Error: + validationResult.Status = result.Status; + break; + } + break; + case ValidationStatus.Error: + //If there was an error, we don't delete it! + break; } } - switch (validationErrorFilterType) - { - case ValidationErrorFilterType.ErrorOnly: - returnValue = returnValue.Where(x => x.Type == ValidationErrorType.Error).ToList().AsReadOnly(); - break; - case ValidationErrorFilterType.WarningOnly: - returnValue = returnValue.Where(x => x.Type == ValidationErrorType.Warning).ToList().AsReadOnly(); - break; - case ValidationErrorFilterType.All: - default: - break; - } - return returnValue; + return validationResult; } - public String? GetValidationErrors(ITextLocalizer localizer, String? property = null, ValidationErrorFilterType validationErrorFilterType = ValidationErrorFilterType.All, String seperator = "
") - { - var errorsFiltered = GetValidationErrorsFiltered(property, validationErrorFilterType); - if (errorsFiltered.Any()) - { - return String.Join(seperator, errorsFiltered.OrderBy(y => y.Type).Select(x => x.Message.GetMessageLocalized(localizer))); - } - return null; - } - - public event EventHandler? ValidateablePropertyChanged; - - protected void OnValidateablePropertyChanged() + protected void OnValidateablePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") { - validationErrors.Clear(); - Validate(); - ValidateablePropertyChanged?.Invoke(this, EventArgs.Empty); + ValidateablePropertyChanged?.Invoke(this, propertyName); } - protected abstract void Validate(); + protected abstract ValidationResult Validate(String property); } } diff --git a/AudioCuesheetEditor/Model/Entity/ValidationError.cs b/AudioCuesheetEditor/Model/Entity/ValidationError.cs deleted file mode 100644 index 2a36fe21..00000000 --- a/AudioCuesheetEditor/Model/Entity/ValidationError.cs +++ /dev/null @@ -1,52 +0,0 @@ -//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.Reflection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace AudioCuesheetEditor.Model.Entity -{ - public enum ValidationErrorFilterType - { - All, - WarningOnly, - ErrorOnly - } - public enum ValidationErrorType - { - Warning, - Error - } - public class ValidationError - { - public ValidationMessage Message { get; private set; } - public ValidationErrorType Type { get; private set; } - public FieldReference FieldReference { get; private set; } - - public ValidationError(FieldReference fieldReference, ValidationErrorType validationErrorType, String message, params object[] messageParameter) - { - if (String.IsNullOrEmpty(message) == true) - { - throw new ArgumentNullException(nameof(message)); - } - Message = new ValidationMessage(message, messageParameter); - Type = validationErrorType; - FieldReference = fieldReference; - } - } -} diff --git a/AudioCuesheetEditor/Model/Entity/ValidationMessage.cs b/AudioCuesheetEditor/Model/Entity/ValidationMessage.cs index d1feb5e5..54f01681 100644 --- a/AudioCuesheetEditor/Model/Entity/ValidationMessage.cs +++ b/AudioCuesheetEditor/Model/Entity/ValidationMessage.cs @@ -14,7 +14,6 @@ //along with Foobar. If not, see //. using Blazorise.Localization; -using System; namespace AudioCuesheetEditor.Model.Entity { @@ -31,7 +30,8 @@ public ValidationMessage(String message, params object[]? args) } public String Message { get; private set; } public object[]? Parameter { get; private set; } - public String GetMessageLocalized(ITextLocalizer localizer) + + public String GetMessageLocalized(ITextLocalizer localizer) { object[]? arguments = null; if (Parameter != null) @@ -41,7 +41,7 @@ public String GetMessageLocalized(ITextLocalizer localizer) } if (arguments != null) { - for (int i = 0; i < arguments.Length;i++) + for (int i = 0; i < arguments.Length; i++) { if ((arguments[i] != null) && (arguments[i].GetType() == typeof(String))) { diff --git a/AudioCuesheetEditor/Model/IO/Audio/AudioCodec.cs b/AudioCuesheetEditor/Model/IO/Audio/AudioCodec.cs index 576f161d..00785d02 100644 --- a/AudioCuesheetEditor/Model/IO/Audio/AudioCodec.cs +++ b/AudioCuesheetEditor/Model/IO/Audio/AudioCodec.cs @@ -13,13 +13,10 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using AudioCuesheetEditor.Model.Entity; -using Blazorise.Localization; -using System; namespace AudioCuesheetEditor.Model.IO.Audio { - public class AudioCodec: IEntityDisplayName + public class AudioCodec { public String MimeType { get; private set; } public String FileExtension { get; private set; } @@ -43,10 +40,5 @@ public AudioCodec(String mimeType, String fileExtension, String name) FileExtension = fileExtension; Name = name; } - - public string GetDisplayNameLocalized(ITextLocalizer localizer) - { - return localizer[Name]; - } } } diff --git a/AudioCuesheetEditor/Model/IO/Audio/AudioFile.cs b/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs similarity index 73% rename from AudioCuesheetEditor/Model/IO/Audio/AudioFile.cs rename to AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs index 5bf299b7..a390feda 100644 --- a/AudioCuesheetEditor/Model/IO/Audio/AudioFile.cs +++ b/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs @@ -1,4 +1,4 @@ -//This file is part of AudioCuesheetEditor. +//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 @@ -13,12 +13,7 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Text.Json.Serialization; -using System.Threading.Tasks; namespace AudioCuesheetEditor.Model.IO.Audio { @@ -45,33 +40,24 @@ public class Audiofile : IDisposable public event EventHandler? ContentStreamLoaded; [JsonConstructor] - public Audiofile(String fileName, Boolean isRecorded = false) + public Audiofile(String name, Boolean isRecorded = false) { - FileName = fileName; + Name = name; IsRecorded = isRecorded; } - public Audiofile(String fileName, String objectURL, AudioCodec audioCodec, System.Net.Http.HttpClient httpClient, Boolean isRecorded = false) : this(fileName, isRecorded) + public Audiofile(String name, String objectURL, AudioCodec? audioCodec, HttpClient httpClient, Boolean isRecorded = false) : this(name, isRecorded) { if (String.IsNullOrEmpty(objectURL)) { throw new ArgumentNullException(nameof(objectURL)); } - if (audioCodec == null) - { - throw new ArgumentNullException(nameof(audioCodec)); - } - if (httpClient == null) - { - throw new ArgumentNullException(nameof(httpClient)); - } ObjectURL = objectURL; AudioCodec = audioCodec; - //Read stream asynchronously in order to be prepared (using large files) - _ = LoadContentStream(httpClient); + HttpClient = httpClient; } - public String FileName { get; private set; } + public String Name { get; private set; } [JsonIgnore] public String? ObjectURL { get; private set; } /// @@ -93,6 +79,8 @@ public Boolean IsContentStreamLoaded /// Duration of the audio file /// public TimeSpan? Duration { get; private set; } + [JsonIgnore] + public HttpClient? HttpClient { get; set; } public AudioCodec? AudioCodec { @@ -100,10 +88,10 @@ public AudioCodec? AudioCodec private set { audioCodec = value; - if ((audioCodec != null) && (FileName.EndsWith(audioCodec.FileExtension) == false)) + if ((audioCodec != null) && (Name.EndsWith(audioCodec.FileExtension) == false)) { //Replace file ending - FileName = String.Format("{0}{1}", Path.GetFileNameWithoutExtension(FileName), audioCodec.FileExtension); + Name = String.Format("{0}{1}", Path.GetFileNameWithoutExtension(Name), audioCodec.FileExtension); } } } @@ -118,11 +106,8 @@ public String? AudioFileType { audioFileType = AudioCodec.FileExtension.Replace(".", "").ToUpper(); } - if (audioFileType == null) - { - //Try to find by file name - audioFileType = Path.GetExtension(FileName).Replace(".", "").ToUpper(); - } + //Try to find by file name + audioFileType ??= Path.GetExtension(Name).Replace(".", "").ToUpper(); return audioFileType; } } @@ -133,7 +118,7 @@ public Boolean PlaybackPossible get { Boolean playbackPossible = false; - if ((String.IsNullOrEmpty(FileName) == false) && (String.IsNullOrEmpty(ObjectURL) == false) && (String.IsNullOrEmpty(AudioFileType) == false) && (AudioCodec != null)) + if ((String.IsNullOrEmpty(Name) == false) && (String.IsNullOrEmpty(ObjectURL) == false) && (String.IsNullOrEmpty(AudioFileType) == false) && (AudioCodec != null)) { playbackPossible = true; } @@ -141,11 +126,11 @@ public Boolean PlaybackPossible } } - private async Task LoadContentStream(System.Net.Http.HttpClient httpClient) + public async Task LoadContentStream() { - if ((String.IsNullOrEmpty(ObjectURL) == false) && (AudioCodec != null)) + if ((ContentStream == null) && (String.IsNullOrEmpty(ObjectURL) == false) && (AudioCodec != null) && (HttpClient != null)) { - ContentStream = await httpClient.GetStreamAsync(ObjectURL); + ContentStream = await HttpClient.GetStreamAsync(ObjectURL); var track = new ATL.Track(ContentStream, AudioCodec.MimeType); Duration = new TimeSpan(0, 0, track.Duration); ContentStreamLoaded?.Invoke(this, EventArgs.Empty); diff --git a/AudioCuesheetEditor/Model/IO/CuesheetFile.cs b/AudioCuesheetEditor/Model/IO/CuesheetFile.cs deleted file mode 100644 index ba23e569..00000000 --- a/AudioCuesheetEditor/Model/IO/CuesheetFile.cs +++ /dev/null @@ -1,107 +0,0 @@ -//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.Controller; -using AudioCuesheetEditor.Model.AudioCuesheet; -using AudioCuesheetEditor.Model.IO.Audio; -using AudioCuesheetEditor.Model.Options; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace AudioCuesheetEditor.Model.IO -{ - public class Cuesheetfile - { - public static readonly String DefaultFileName = "Cuesheet.cue"; - - public Cuesheetfile(Cuesheet cuesheet) - { - if (cuesheet == null) - { - throw new ArgumentNullException(nameof(cuesheet)); - } - Cuesheet = cuesheet; - } - - public Cuesheet Cuesheet { get; private set; } - - public byte[]? GenerateCuesheetFile() - { - if (IsExportable == true) - { - var builder = new StringBuilder(); - if ((Cuesheet.Cataloguenumber != null) && (Cuesheet.Cataloguenumber.IsValid == true)) - { - builder.AppendLine(String.Format("{0} {1}", CuesheetConstants.CuesheetCatalogueNumber, Cuesheet.Cataloguenumber.Value)); - } - if (Cuesheet.CDTextfile != null) - { - builder.AppendLine(String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetCDTextfile, Cuesheet.CDTextfile.FileName)); - } - builder.AppendLine(String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, Cuesheet.Title)); - builder.AppendLine(String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, Cuesheet.Artist)); - builder.AppendLine(String.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, Cuesheet.Audiofile?.FileName, Cuesheet.Audiofile?.AudioFileType)); - foreach (var track in Cuesheet.Tracks) - { - 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.Count > 0) - { - builder.AppendLine(String.Format("{0}{1}{2} {3}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackFlags, String.Join(" ", track.Flags.Select(x => x.CuesheetLabel)))); - } - if (track.PreGap.HasValue) - { - builder.AppendLine(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackPreGap, Math.Floor(track.PreGap.Value.TotalMinutes), track.PreGap.Value.Seconds, (track.PreGap.Value.Milliseconds * 75) / 1000)); - } - if (track.Begin.HasValue) - { - builder.AppendLine(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackIndex01, Math.Floor(track.Begin.Value.TotalMinutes), track.Begin.Value.Seconds, (track.Begin.Value.Milliseconds * 75) / 1000)); - } - if (track.PostGap.HasValue) - { - builder.AppendLine(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackPostGap, Math.Floor(track.PostGap.Value.TotalMinutes), track.PostGap.Value.Seconds, (track.PostGap.Value.Milliseconds * 75) / 1000)); - } - } - return Encoding.UTF8.GetBytes(builder.ToString()); - } - else - { - return null; - } - } - - public Boolean IsExportable - { - get - { - if (Cuesheet.GetValidationErrorsFiltered(validationErrorFilterType: Entity.ValidationErrorFilterType.ErrorOnly).Count > 0) - { - return false; - } - if (Cuesheet.Tracks.Any(x => x.GetValidationErrorsFiltered(validationErrorFilterType: Entity.ValidationErrorFilterType.ErrorOnly).Count > 0) == true) - { - return false; - } - return true; - } - } - } -} diff --git a/AudioCuesheetEditor/Model/IO/Export/ExportProfile.cs b/AudioCuesheetEditor/Model/IO/Export/ExportProfile.cs deleted file mode 100644 index f72b5db5..00000000 --- a/AudioCuesheetEditor/Model/IO/Export/ExportProfile.cs +++ /dev/null @@ -1,86 +0,0 @@ -//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 System; -using System.Text; -using System.Text.Json.Serialization; - -namespace AudioCuesheetEditor.Model.IO.Export -{ - public class Exportprofile - { - public static readonly String DefaultFileName = "Export.txt"; - - public Exportprofile() - { - SchemeHead = new Exportscheme - { - SchemeType = Schemetype.Header - }; - SchemeTracks = new Exportscheme - { - SchemeType = Schemetype.Body - }; - SchemeFooter = new Exportscheme - { - SchemeType = Schemetype.Footer - }; - FileName = DefaultFileName; - var random = new Random(); - Name = String.Format("{0}_{1}", nameof(Exportprofile), random.Next(1, 100)); - } - public String Name { get; set; } - public Exportscheme SchemeHead { get; set; } - public Exportscheme SchemeTracks { get; set; } - public Exportscheme SchemeFooter { get; set; } - public String FileName { get; set; } - [JsonIgnore] - public Boolean IsExportable - { - get - { - return SchemeHead.IsValid && SchemeTracks.IsValid && SchemeFooter.IsValid; - } - } - public byte[]? GenerateExport(Cuesheet cuesheet) - { - if (IsExportable == true) - { - var builder = new StringBuilder(); - if (SchemeHead != null) - { - builder.AppendLine(SchemeHead.GetExportResult(cuesheet)); - } - if (SchemeTracks != null) - { - foreach (var track in cuesheet.Tracks) - { - builder.AppendLine(SchemeTracks.GetExportResult(track)); - } - } - if (SchemeFooter != null) - { - builder.AppendLine(SchemeFooter.GetExportResult(cuesheet)); - } - return Encoding.UTF8.GetBytes(builder.ToString()); - } - else - { - return null; - } - } - } -} diff --git a/AudioCuesheetEditor/Model/Entity/IEntityDisplayName.cs b/AudioCuesheetEditor/Model/IO/Export/Exportfile.cs similarity index 67% rename from AudioCuesheetEditor/Model/Entity/IEntityDisplayName.cs rename to AudioCuesheetEditor/Model/IO/Export/Exportfile.cs index 2bfc956e..154a3af4 100644 --- a/AudioCuesheetEditor/Model/Entity/IEntityDisplayName.cs +++ b/AudioCuesheetEditor/Model/IO/Export/Exportfile.cs @@ -13,13 +13,15 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using Blazorise.Localization; -using System; -namespace AudioCuesheetEditor.Model.Entity +namespace AudioCuesheetEditor.Model.IO.Export { - public interface IEntityDisplayName + public class Exportfile { - public String? GetDisplayNameLocalized(ITextLocalizer localizer); + public static readonly string DefaultCuesheetFilename = "Cuesheet.cue"; + public string Name { get; set; } = String.Empty; + public byte[]? Content { get; set; } + public TimeSpan? Begin { get; set; } + public TimeSpan? End { get; set; } } } diff --git a/AudioCuesheetEditor/Model/IO/Export/ExportfileGenerator.cs b/AudioCuesheetEditor/Model/IO/Export/ExportfileGenerator.cs new file mode 100644 index 00000000..ac448d0d --- /dev/null +++ b/AudioCuesheetEditor/Model/IO/Export/ExportfileGenerator.cs @@ -0,0 +1,396 @@ +//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 AudioCuesheetEditor.Model.Options; +using System.Data; +using System.Diagnostics.Metrics; +using System.Text; +using System.Xml.Linq; + +namespace AudioCuesheetEditor.Model.IO.Export +{ + public enum ExportType + { + Cuesheet = 0, + Exportprofile + } + + public class ExportfileGenerator : Validateable + { + public Cuesheet Cuesheet { get; } + public Exportprofile? Exportprofile { get; set; } + public ApplicationOptions? ApplicationOptions { get; set; } + public ExportType ExportType { get; set; } + + public ExportfileGenerator(ExportType exportType, Cuesheet cuesheet, Exportprofile? exportprofile = null, ApplicationOptions? applicationOptions = null) + { + ExportType = exportType; + Cuesheet = cuesheet; + Exportprofile = exportprofile; + ApplicationOptions = applicationOptions; + } + + public IReadOnlyCollection GenerateExportfiles() + { + List exportfiles = new(); + if (Validate().Status != ValidationStatus.Error) + { + if (Cuesheet.SplitPoints.Count != 0) + { + TimeSpan? previousSplitPointMoment = null; + var begin = Cuesheet.Tracks.Min(x => x.Begin); + var counter = 1; + String? content = null; + String filename = String.Empty; + String? audioFileName = null; + foreach (var splitPoint in Cuesheet.SplitPoints.OrderBy(x => x.Moment)) + { + audioFileName = splitPoint.AudiofileName; + if (splitPoint.Validate().Status == ValidationStatus.Success) + { + switch (ExportType) + { + case ExportType.Cuesheet: + content = WriteCuesheet(audioFileName, previousSplitPointMoment, splitPoint); + filename = String.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(ApplicationOptions?.CuesheetFilename), counter, Cuesheet.FileExtension); + break; + case ExportType.Exportprofile: + if (Exportprofile != null) + { + content = WriteExport(audioFileName, previousSplitPointMoment, splitPoint); + filename = String.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(Exportprofile.Filename), counter, Path.GetExtension(Exportprofile.Filename)); + } + break; + } + if (content != null) + { + if (previousSplitPointMoment != null) + { + begin = previousSplitPointMoment; + } + exportfiles.Add(new Exportfile() { Name = filename, Content = Encoding.UTF8.GetBytes(content), Begin = begin, End = splitPoint.Moment }); + } + previousSplitPointMoment = splitPoint.Moment; + counter++; + } + } + //After a split point attach the last part + audioFileName = String.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(Cuesheet.Audiofile?.Name), counter, Path.GetExtension(Cuesheet.Audiofile?.Name)); + switch (ExportType) + { + case ExportType.Cuesheet: + content = WriteCuesheet(audioFileName, previousSplitPointMoment); + filename = String.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(ApplicationOptions?.CuesheetFilename), counter, Cuesheet.FileExtension); + break; + case ExportType.Exportprofile: + if (Exportprofile != null) + { + content = WriteExport(audioFileName, previousSplitPointMoment); + filename = String.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(Exportprofile.Filename), counter, Path.GetExtension(Exportprofile.Filename)); + } + break; + } + if (content != null) + { + var end = Cuesheet.Tracks.Max(x => x.End); + exportfiles.Add(new Exportfile() { Name = filename, Content = Encoding.UTF8.GetBytes(content), Begin = previousSplitPointMoment, End = end }); + } + } + else + { + String filename = String.Empty; + String? content = null; + switch (ExportType) + { + case ExportType.Cuesheet: + var cuesheetfilename = ApplicationOptions?.CuesheetFilename; + if (String.IsNullOrEmpty(cuesheetfilename) == false) + { + filename = cuesheetfilename; + } + else + { + filename = Exportfile.DefaultCuesheetFilename; + } + if (Cuesheet.Audiofile != null) + { + content = WriteCuesheet(Cuesheet.Audiofile.Name); + } + break; + case ExportType.Exportprofile: + if (Exportprofile != null) + { + filename = Exportprofile.Filename; + if (Cuesheet.Audiofile != null) + { + content = WriteExport(Cuesheet.Audiofile.Name); + } + } + break; + } + if (content != null) + { + var begin = Cuesheet.Tracks.Min(x => x.Begin); + var end = Cuesheet.Tracks.Max(x => x.End); + exportfiles.Add(new Exportfile() { Name = filename, Content = Encoding.UTF8.GetBytes(content), Begin = begin, End = end }); + } + } + } + return exportfiles; + } + + private string WriteCuesheet(String? audiofileName, TimeSpan? from = null, SplitPoint? splitPoint = null) + { + var builder = new StringBuilder(); + if (Cuesheet.Cataloguenumber != null && string.IsNullOrEmpty(Cuesheet.Cataloguenumber.Value) == false && Cuesheet.Cataloguenumber.Validate().Status != ValidationStatus.Error) + { + builder.AppendLine(string.Format("{0} {1}", CuesheetConstants.CuesheetCatalogueNumber, Cuesheet.Cataloguenumber.Value)); + } + if (Cuesheet.CDTextfile != null) + { + builder.AppendLine(string.Format("{0} \"{1}\"", CuesheetConstants.CuesheetCDTextfile, Cuesheet.CDTextfile.Name)); + } + builder.AppendLine(string.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, splitPoint != null ? splitPoint.Title : Cuesheet.Title)); + builder.AppendLine(string.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, splitPoint != null ? splitPoint.Artist : Cuesheet.Artist)); + builder.AppendLine(string.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, audiofileName, Cuesheet.Audiofile?.AudioFileType)); + IEnumerable tracks = Cuesheet.Tracks.OrderBy(x => x.Position); + if (from != null && splitPoint != null) + { + tracks = Cuesheet.Tracks.Where(x => x.Begin <= splitPoint.Moment && x.End >= from).OrderBy(x => x.Position); + } + else + { + if (from != null) + { + tracks = Cuesheet.Tracks.Where(x => x.End >= from).OrderBy(x => x.Position); + } + if (splitPoint != null) + { + tracks = Cuesheet.Tracks.Where(x => x.Begin <= splitPoint.Moment).OrderBy(x => x.Position); + } + } + if (tracks.Any()) + { + //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:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position + positionDifference, 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.Count > 0) + { + builder.AppendLine(string.Format("{0}{1}{2} {3}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackFlags, string.Join(" ", track.Flags.Select(x => x.CuesheetLabel)))); + } + if (track.PreGap.HasValue) + { + builder.AppendLine(string.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackPreGap, Math.Floor(track.PreGap.Value.TotalMinutes), track.PreGap.Value.Seconds, track.PreGap.Value.Milliseconds * 75 / 1000)); + } + if (track.Begin.HasValue) + { + var begin = track.Begin.Value; + if (from != null) + { + if (from >= track.Begin) + { + begin = TimeSpan.Zero; + } + else + { + begin = track.Begin.Value - from.Value; + } + } + builder.AppendLine(string.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackIndex01, Math.Floor(begin.TotalMinutes), begin.Seconds, begin.Milliseconds * 75 / 1000)); + } + else + { + throw new NullReferenceException(string.Format("{0} may not be null!", nameof(Track.Begin))); + } + if (track.PostGap.HasValue) + { + builder.AppendLine(string.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackPostGap, Math.Floor(track.PostGap.Value.TotalMinutes), track.PostGap.Value.Seconds, track.PostGap.Value.Milliseconds * 75 / 1000)); + } + } + } + return builder.ToString(); + } + + private String WriteExport(String? audiofileName, TimeSpan? from = null, SplitPoint? splitPoint = null) + { + var builder = new StringBuilder(); + if (Exportprofile != null) + { + var header = Exportprofile.SchemeHead + .Replace(Exportprofile.SchemeCuesheetArtist, splitPoint != null ? splitPoint.Artist : Cuesheet.Artist) + .Replace(Exportprofile.SchemeCuesheetTitle, splitPoint != null ? splitPoint.Title : Cuesheet.Title) + .Replace(Exportprofile.SchemeCuesheetAudiofile, audiofileName) + .Replace(Exportprofile.SchemeCuesheetCDTextfile, Cuesheet.CDTextfile?.Name) + .Replace(Exportprofile.SchemeCuesheetCatalogueNumber, Cuesheet.Cataloguenumber?.Value) + .Replace(Exportprofile.SchemeDate, DateTime.Now.ToShortDateString()) + .Replace(Exportprofile.SchemeDateTime, DateTime.Now.ToString()) + .Replace(Exportprofile.SchemeTime, DateTime.Now.ToLongTimeString()); + builder.AppendLine(header); + IEnumerable tracks = Cuesheet.Tracks.OrderBy(x => x.Position); + if (from != null && splitPoint != null) + { + tracks = Cuesheet.Tracks.Where(x => x.Begin <= splitPoint.Moment && x.End >= from).OrderBy(x => x.Position); + } + else + { + if (from != null) + { + tracks = Cuesheet.Tracks.Where(x => x.End >= from).OrderBy(x => x.Position); + } + if (splitPoint != null) + { + tracks = Cuesheet.Tracks.Where(x => x.Begin <= splitPoint.Moment).OrderBy(x => x.Position); + } + } + if (tracks.Any()) + { + //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) + { + TimeSpan begin; + var end = track.End; + if (track.Begin.HasValue) + { + begin = track.Begin.Value; + if (from != null) + { + if (from >= track.Begin) + { + begin = TimeSpan.Zero; + } + else + { + begin = track.Begin.Value - from.Value; + } + end = track.End - from.Value; + } + } + else + { + 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 + 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, splitPoint != null ? splitPoint.Artist : Cuesheet.Artist) + .Replace(Exportprofile.SchemeCuesheetTitle, splitPoint != null ? splitPoint.Title : Cuesheet.Title) + .Replace(Exportprofile.SchemeCuesheetAudiofile, audiofileName) + .Replace(Exportprofile.SchemeCuesheetCDTextfile, Cuesheet.CDTextfile?.Name) + .Replace(Exportprofile.SchemeCuesheetCatalogueNumber, Cuesheet.Cataloguenumber?.Value) + .Replace(Exportprofile.SchemeDate, DateTime.Now.ToShortDateString()) + .Replace(Exportprofile.SchemeDateTime, DateTime.Now.ToString()) + .Replace(Exportprofile.SchemeTime, DateTime.Now.ToLongTimeString()); + builder.AppendLine(footer); + } + return builder.ToString(); + } + + protected override ValidationResult Validate(string property) + { + ValidationResult validationResult; + switch (property) + { + case nameof(Cuesheet): + var validationResults = new Dictionary + { + { Cuesheet, Cuesheet.Validate() }, + { Cuesheet.Cataloguenumber, Cuesheet.Cataloguenumber.Validate() } + }; + foreach (var track in Cuesheet.Tracks) + { + validationResults.Add(track, track.Validate()); + } + if (validationResults.Any(x => x.Value.Status == ValidationStatus.Error)) + { + var messages = validationResults.Values.Where(x => x.ValidationMessages != null).SelectMany(x => x.ValidationMessages!); + validationResult = ValidationResult.Create(ValidationStatus.Error, messages); + } + else + { + validationResult = ValidationResult.Create(ValidationStatus.Success); + } + break; + case nameof(ApplicationOptions): + if (ExportType == ExportType.Cuesheet) + { + if (ApplicationOptions == null) + { + var validationMessages = new List() + { + new("{0} has no value!", nameof(ApplicationOptions)) + }; + validationResult = ValidationResult.Create(ValidationStatus.Error, validationMessages); + } + else + { + validationResult = ApplicationOptions.Validate(x => x.CuesheetFilename); + } + } + else + { + validationResult = ValidationResult.Create(ValidationStatus.NoValidation); + } + break; + case nameof(Exportprofile): + if (ExportType == ExportType.Exportprofile) + { + if (Exportprofile != null) + { + validationResult = Exportprofile.Validate(); + } + else + { + var validationMessages = new List() + { + new("{0} has no value!", nameof(Exportprofile)) + }; + validationResult = ValidationResult.Create(ValidationStatus.Error, validationMessages); + } + } + else + { + validationResult = ValidationResult.Create(ValidationStatus.NoValidation); + } + break; + default: + validationResult = ValidationResult.Create(ValidationStatus.NoValidation); + break; + } + return validationResult; + } + } +} diff --git a/AudioCuesheetEditor/Model/IO/Export/ExportScheme.cs b/AudioCuesheetEditor/Model/IO/Export/Exportprofile.cs similarity index 52% rename from AudioCuesheetEditor/Model/IO/Export/ExportScheme.cs rename to AudioCuesheetEditor/Model/IO/Export/Exportprofile.cs index cb7423d2..a9efa451 100644 --- a/AudioCuesheetEditor/Model/IO/Export/ExportScheme.cs +++ b/AudioCuesheetEditor/Model/IO/Export/Exportprofile.cs @@ -1,4 +1,4 @@ -//This file is part of AudioCuesheetEditor. +//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 @@ -15,25 +15,16 @@ //. using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.Entity; -using AudioCuesheetEditor.Model.Reflection; +using AudioCuesheetEditor.Model.IO.Audio; using System; -using System.Collections.Generic; -using System.Linq; +using System.Text; using System.Text.Json.Serialization; namespace AudioCuesheetEditor.Model.IO.Export { - [JsonConverter(typeof(JsonStringEnumConverter))] - public enum Schemetype + public class Exportprofile : Validateable { - Unknown, - Header, - Body, - Footer - } - public class Exportscheme : Validateable - { - public const String SchemeCharacter = "%"; + public static readonly String DefaultFileName = "Export.txt"; public static readonly String SchemeCuesheetArtist; public static readonly String SchemeCuesheetTitle; @@ -56,10 +47,15 @@ public class Exportscheme : Validateable public static readonly Dictionary AvailableCuesheetSchemes; public static readonly Dictionary AvailableTrackSchemes; - private String? scheme; - private Schemetype schemeType; + public const String SchemeCharacter = "%"; + + private String schemeHead; + private String schemeTracks; + private String schemeFooter; + private String filename; + private String name; - static Exportscheme() + static Exportprofile() { SchemeDate = String.Format("{0}Date{1}", SchemeCharacter, SchemeCharacter); SchemeDateTime = String.Format("{0}DateTime{1}", SchemeCharacter, SchemeCharacter); @@ -93,7 +89,7 @@ static Exportscheme() 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); - AvailableTrackSchemes = new Dictionary() + AvailableTrackSchemes = new Dictionary() { { nameof(Track.Position), SchemeTrackPosition }, { nameof(Track.Artist), SchemeTrackArtist }, @@ -107,104 +103,101 @@ static Exportscheme() }; } - public Exportscheme() { } - - public String? Scheme + public Exportprofile() { - get { return scheme; } - set { scheme = value; OnValidateablePropertyChanged(); } + schemeHead = String.Empty; + schemeTracks = String.Empty; + schemeFooter = String.Empty; + filename = DefaultFileName; + var random = new Random(); + name = String.Format("{0}_{1}", nameof(Exportprofile), random.Next(1, 100)); } - public Schemetype SchemeType + public String Name { - get { return schemeType; } - set { schemeType = value; OnValidateablePropertyChanged(); } + get => name; + set { name = value; OnValidateablePropertyChanged(); } } - - public String? GetExportResult(ICuesheetEntity cuesheetEntity) + public String SchemeHead { - String? result = null; - if (String.IsNullOrEmpty(Scheme) == false) - { - switch (SchemeType) - { - case Schemetype.Header: - case Schemetype.Footer: - var cuesheet = (Cuesheet)cuesheetEntity; - result = Scheme - .Replace(SchemeCuesheetArtist, cuesheet.Artist) - .Replace(SchemeCuesheetTitle, cuesheet.Title) - .Replace(SchemeCuesheetAudiofile, cuesheet.Audiofile?.FileName) - .Replace(SchemeCuesheetCDTextfile, cuesheet.CDTextfile?.FileName) - .Replace(SchemeCuesheetCatalogueNumber, cuesheet.Cataloguenumber?.Value) - .Replace(SchemeDate, DateTime.Now.ToShortDateString()) - .Replace(SchemeDateTime, DateTime.Now.ToString()) - .Replace(SchemeTime, DateTime.Now.ToLongTimeString()); - break; - case Schemetype.Body: - var track = (Track)cuesheetEntity; - result = Scheme - .Replace(SchemeTrackArtist, track.Artist) - .Replace(SchemeTrackTitle, track.Title) - .Replace(SchemeTrackPosition, track.Position != null ? track.Position.Value.ToString() : String.Empty) - .Replace(SchemeTrackBegin, track.Begin != null ? track.Begin.Value.ToString() : String.Empty) - .Replace(SchemeTrackEnd, track.End != null ? track.End.Value.ToString() : String.Empty) - .Replace(SchemeTrackLength, track.Length != null ? track.Length.Value.ToString() : String.Empty) - .Replace(SchemeTrackFlags, String.Join(" ", track.Flags.Select(x => x.CuesheetLabel))) - .Replace(SchemeTrackPreGap, track.PreGap != null ? track.PreGap.Value.ToString() : String.Empty) - .Replace(SchemeTrackPostGap, track.PostGap != null ? track.PostGap.Value.ToString() : String.Empty) - .Replace(SchemeDate, DateTime.Now.ToShortDateString()) - .Replace(SchemeDateTime, DateTime.Now.ToString()) - .Replace(SchemeTime, DateTime.Now.ToLongTimeString()); - break; - default: - //Nothing to do - break; - } - } - return result; + get => schemeHead; + set { schemeHead = value; OnValidateablePropertyChanged(); } + } + public String SchemeTracks + { + get => schemeTracks; + set { schemeTracks = value; OnValidateablePropertyChanged(); } + } + public String SchemeFooter + { + get => schemeFooter; + set { schemeFooter = value; OnValidateablePropertyChanged(); } + } + public String Filename + { + get => filename; + set { filename = value; OnValidateablePropertyChanged(); } } - protected override void Validate() + protected override ValidationResult Validate(string property) { - if (String.IsNullOrEmpty(Scheme) == false) + ValidationStatus validationStatus = ValidationStatus.NoValidation; + List? validationMessages = null; + switch (property) { - Boolean addValidationError = false; - switch (SchemeType) - { - case Schemetype.Header: - case Schemetype.Footer: - foreach (var availableScheme in AvailableTrackSchemes) - { - if (Scheme.Contains(availableScheme.Value) == true) - { - addValidationError = true; - break; - } - } - if (addValidationError == true) + case nameof(SchemeHead): + validationStatus = ValidationStatus.Success; + foreach (var availableScheme in AvailableTrackSchemes) + { + if (SchemeHead.Contains(availableScheme.Value) == true) { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Scheme)), ValidationErrorType.Warning, "Scheme contains placeholders that can not be solved!")); + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} contains placeholder '{1}' that can not be resolved!", nameof(SchemeHead), availableScheme.Value)); + break; } - break; - case Schemetype.Body: - foreach (var availableScheme in AvailableCuesheetSchemes) + } + break; + case nameof(SchemeTracks): + validationStatus = ValidationStatus.Success; + foreach (var availableScheme in AvailableCuesheetSchemes) + { + if (SchemeTracks.Contains(availableScheme.Value) == true) { - if (Scheme.Contains(availableScheme.Value) == true) - { - addValidationError = true; - break; - } + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} contains placeholder '{1}' that can not be resolved!", nameof(SchemeTracks), availableScheme.Value)); + break; } - if (addValidationError == true) + } + break; + case nameof(SchemeFooter): + validationStatus = ValidationStatus.Success; + foreach (var availableScheme in AvailableTrackSchemes) + { + if (SchemeFooter.Contains(availableScheme.Value) == true) { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Scheme)), ValidationErrorType.Warning, "Scheme contains placeholders that can not be solved!")); + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} contains placeholder '{1}' that can not be resolved!", nameof(SchemeFooter), availableScheme.Value)); + break; } - break; - case Schemetype.Unknown: - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(SchemeType)), ValidationErrorType.Error, "{0} has invalid value!", nameof(SchemeType))); - break; - } + } + break; + case nameof(Filename): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(Filename)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Filename))); + } + break; + case nameof(Name): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(Name)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Name))); + } + break; } + return ValidationResult.Create(validationStatus, validationMessages); } } } diff --git a/AudioCuesheetEditor/Model/IO/Export/SplitPoint.cs b/AudioCuesheetEditor/Model/IO/Export/SplitPoint.cs new file mode 100644 index 00000000..78e782c7 --- /dev/null +++ b/AudioCuesheetEditor/Model/IO/Export/SplitPoint.cs @@ -0,0 +1,170 @@ +//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.UI; +using System.Text.Json.Serialization; + +namespace AudioCuesheetEditor.Model.IO.Export +{ + public class SplitPoint : Validateable, ITraceable + { + private Cuesheet? cuesheet; + private TimeSpan? moment; + private String? artist; + private String? title; + private String? audiofileName; + + public event EventHandler? TraceablePropertyChanged; + + public SplitPoint(Cuesheet cuesheet) + { + Cuesheet = cuesheet; + artist = Cuesheet.Artist; + title = Cuesheet.Title; + audiofileName = Cuesheet.Audiofile?.Name; + } + + [JsonConstructor] + public SplitPoint() { } + public Cuesheet? Cuesheet + { + get => cuesheet; + set + { + if (cuesheet == null) + { + cuesheet = value; + } + else + { + throw new InvalidOperationException(); + } + } + } + + public String? Artist + { + get => artist; + set + { + var previousValue = artist; + artist = value; + OnValidateablePropertyChanged(nameof(Artist)); + OnTraceablePropertyChanged(previousValue, nameof(Artist)); + } + } + + public String? Title + { + get => title; + set + { + var previousValue = title; + title = value; + OnValidateablePropertyChanged(nameof(Title)); + OnTraceablePropertyChanged(previousValue, nameof(Title)); + } + } + + public TimeSpan? Moment + { + get => moment; + set + { + var previousValue = moment; + moment = value; + OnValidateablePropertyChanged(nameof(Moment)); + OnTraceablePropertyChanged(previousValue, nameof(Moment)); + } + } + + public String? AudiofileName + { + get => audiofileName; + set + { + var previousValue = audiofileName; + audiofileName = value; + OnValidateablePropertyChanged(nameof(AudiofileName)); + OnTraceablePropertyChanged(previousValue, nameof(AudiofileName)); + } + } + + public void CopyValues(SplitPoint splitPoint) + { + Artist = splitPoint.Artist; + Title = splitPoint.Title; + Moment = splitPoint.Moment; + } + + protected override ValidationResult Validate(string property) + { + ValidationStatus validationStatus = ValidationStatus.NoValidation; + List? validationMessages = null; + switch (property) + { + case nameof(Moment): + validationStatus = ValidationStatus.Success; + if (Moment == null) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Moment))); + } + else + { + var maxEnd = Cuesheet?.Tracks.Max(x => x.End); + if (Moment > maxEnd) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} should be equal or less to '{1}'!", nameof(Moment), maxEnd)); + } + } + break; + case nameof(Artist): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(Artist)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Artist))); + } + break; + case nameof(Title): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(Title)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Title))); + } + break; + case nameof(AudiofileName): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(AudiofileName)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(AudiofileName))); + } + break; + } + return ValidationResult.Create(validationStatus, validationMessages); + } + + private void OnTraceablePropertyChanged(object? previousValue, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") + { + TraceablePropertyChanged?.Invoke(this, new TraceablePropertiesChangedEventArgs(new TraceableChange(previousValue, propertyName))); + } + } +} diff --git a/AudioCuesheetEditor/Model/IO/Import/CuesheetImportFile.cs b/AudioCuesheetEditor/Model/IO/Import/CuesheetImportfile.cs similarity index 99% rename from AudioCuesheetEditor/Model/IO/Import/CuesheetImportFile.cs rename to AudioCuesheetEditor/Model/IO/Import/CuesheetImportfile.cs index 05f415df..f9868c04 100644 --- a/AudioCuesheetEditor/Model/IO/Import/CuesheetImportFile.cs +++ b/AudioCuesheetEditor/Model/IO/Import/CuesheetImportfile.cs @@ -1,4 +1,4 @@ -//This file is part of AudioCuesheetEditor. +//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 diff --git a/AudioCuesheetEditor/Model/IO/Import/TextImportScheme.cs b/AudioCuesheetEditor/Model/IO/Import/TextImportScheme.cs index a61ff312..752ba3e1 100644 --- a/AudioCuesheetEditor/Model/IO/Import/TextImportScheme.cs +++ b/AudioCuesheetEditor/Model/IO/Import/TextImportScheme.cs @@ -15,21 +15,19 @@ //. using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.Entity; -using AudioCuesheetEditor.Model.Reflection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Blazorise.Localization; namespace AudioCuesheetEditor.Model.IO.Import { - public class TextImportScheme : Validateable + public class TextImportScheme : Validateable { public const String EnterRegularExpressionHere = "ENTER REGULAR EXPRESSION HERE"; public static readonly IReadOnlyDictionary AvailableSchemeCuesheet; public static readonly IReadOnlyDictionary AvailableSchemesTrack; + public static ITextLocalizer? TextLocalizer { get; set; } + static TextImportScheme() { var schemeCuesheetArtist = String.Format("(?'{0}.{1}'{2})", nameof(Cuesheet), nameof(Cuesheet.Artist), EnterRegularExpressionHere); @@ -72,7 +70,7 @@ static TextImportScheme() } public static readonly String DefaultSchemeCuesheet = @"(?'Cuesheet.Artist'\A.*) - (?'Cuesheet.Title'\w{1,})\t{1,}(?'Cuesheet.Audiofile'.{1,})"; - public static readonly String DefaultSchemeTracks = @"(?'Track.Artist'[a-zA-Z0-9_ .();äöü&:,'*-]{1,}) - (?'Track.Title'[a-zA-Z0-9_ .();äöü&'*-]{1,})\t{0,}(?'Track.End'.{1,})"; + public static readonly String DefaultSchemeTracks = @"(?'Track.Artist'[a-zA-Z0-9_ .();äöü&:,'*-?:]{1,}) - (?'Track.Title'[a-zA-Z0-9_ .();äöü&'*-?:]{1,})\t{0,}(?'Track.End'.{1,})"; public static readonly TextImportScheme DefaultTextImportScheme = new() { @@ -91,8 +89,8 @@ public String? SchemeTracks set { schemeTracks = value; - OnValidateablePropertyChanged(); SchemeChanged?.Invoke(this, nameof(SchemeTracks)); + OnValidateablePropertyChanged(); } } @@ -102,61 +100,59 @@ public String? SchemeCuesheet set { schemeCuesheet = value; - OnValidateablePropertyChanged(); SchemeChanged?.Invoke(this, nameof(SchemeCuesheet)); + OnValidateablePropertyChanged(); } } - - protected override void Validate() + protected override ValidationResult Validate(string property) { - if (String.IsNullOrEmpty(SchemeCuesheet)) + ValidationStatus validationStatus = ValidationStatus.NoValidation; + List? validationMessages = null; + String enterRegularExpression = EnterRegularExpressionHere; + if (TextLocalizer != null) { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(SchemeCuesheet)), ValidationErrorType.Warning, "{0} has no value!", nameof(SchemeCuesheet))); + enterRegularExpression = TextLocalizer[EnterRegularExpressionHere]; } - else + switch (property) { - if (AvailableSchemesTrack != null) - { - List schemesFound = new(); + case nameof(SchemeCuesheet): + validationStatus = ValidationStatus.Success; foreach (var availableScheme in AvailableSchemesTrack) { - if (SchemeCuesheet.Contains(availableScheme.Value.Substring(0, availableScheme.Value.IndexOf(EnterRegularExpressionHere))) == true) + if (SchemeCuesheet?.Contains(availableScheme.Value.Substring(0, availableScheme.Value.IndexOf(EnterRegularExpressionHere))) == true) { var startIndex = SchemeCuesheet.IndexOf(availableScheme.Value.Substring(0, availableScheme.Value.IndexOf(EnterRegularExpressionHere))); var realRegularExpression = SchemeCuesheet.Substring(startIndex, (SchemeCuesheet.IndexOf(")", startIndex) + 1) - startIndex); - schemesFound.Add(realRegularExpression); + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} contains placeholders that can not be solved! Please remove invalid placeholder '{1}'.", nameof(SchemeCuesheet), realRegularExpression)); } } - if (schemesFound.Count > 0) + if (SchemeCuesheet?.Contains(enterRegularExpression) == true) { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(SchemeCuesheet)), ValidationErrorType.Warning, "Scheme contains placeholders that can not be solved! Please remove invalid placeholder '{0}'.", String.Join(",", schemesFound))); + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("Replace '{0}' by a regular expression!", enterRegularExpression)); } - } - } - if (String.IsNullOrEmpty(SchemeTracks)) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(SchemeTracks)), ValidationErrorType.Warning, "{0} has no value!", nameof(SchemeTracks))); - } - else - { - if (AvailableSchemeCuesheet != null) - { - List schemesFound = new(); + break; + case nameof(SchemeTracks): + validationStatus = ValidationStatus.Success; foreach (var availableScheme in AvailableSchemeCuesheet) { - if (SchemeTracks.Contains(availableScheme.Value.Substring(0, availableScheme.Value.IndexOf(EnterRegularExpressionHere))) == true) + if (SchemeTracks?.Contains(availableScheme.Value.Substring(0, availableScheme.Value.IndexOf(EnterRegularExpressionHere))) == true) { var startIndex = SchemeTracks.IndexOf(availableScheme.Value.Substring(0, availableScheme.Value.IndexOf(EnterRegularExpressionHere))); var realRegularExpression = SchemeTracks.Substring(startIndex, (SchemeTracks.IndexOf(")", startIndex) + 1) - startIndex); - schemesFound.Add(realRegularExpression); + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} contains placeholders that can not be solved! Please remove invalid placeholder '{1}'.", nameof(SchemeTracks), realRegularExpression)); } } - if (schemesFound.Count > 0) + if (SchemeTracks?.Contains(enterRegularExpression) == true) { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(SchemeTracks)), ValidationErrorType.Warning, "Scheme contains placeholders that can not be solved! Please remove invalid placeholder '{0}'.", String.Join(",", schemesFound))); + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("Replace '{0}' by a regular expression!", enterRegularExpression)); } - } + break; } + return ValidationResult.Create(validationStatus, validationMessages); } } } diff --git a/AudioCuesheetEditor/Model/IO/Import/TextImportFile.cs b/AudioCuesheetEditor/Model/IO/Import/TextImportfile.cs similarity index 96% rename from AudioCuesheetEditor/Model/IO/Import/TextImportFile.cs rename to AudioCuesheetEditor/Model/IO/Import/TextImportfile.cs index b6cfa266..87829dcd 100644 --- a/AudioCuesheetEditor/Model/IO/Import/TextImportFile.cs +++ b/AudioCuesheetEditor/Model/IO/Import/TextImportfile.cs @@ -1,4 +1,4 @@ -//This file is part of AudioCuesheetEditor. +//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 @@ -239,14 +239,8 @@ private void SetValue(ICuesheetEntity entity, PropertyInfo property, string valu { if (property.PropertyType == typeof(TimeSpan?)) { - if (String.IsNullOrEmpty(TimeSpanFormat.Scheme)) - { - property.SetValue(entity, DateTimeUtility.ParseTimeSpan(value)); - } - else - { - property.SetValue(entity, DateTimeUtility.ParseTimeSpan(value, TimeSpanFormat)); - } + var utility = new DateTimeUtility(TimeSpanFormat); + property.SetValue(entity, utility.ParseTimeSpan(value)); } if (property.PropertyType == typeof(uint?)) { diff --git a/AudioCuesheetEditor/Model/IO/ProjectFile.cs b/AudioCuesheetEditor/Model/IO/Projectfile.cs similarity index 80% rename from AudioCuesheetEditor/Model/IO/ProjectFile.cs rename to AudioCuesheetEditor/Model/IO/Projectfile.cs index af64c2d7..f7b4b2ec 100644 --- a/AudioCuesheetEditor/Model/IO/ProjectFile.cs +++ b/AudioCuesheetEditor/Model/IO/Projectfile.cs @@ -1,4 +1,4 @@ -//This file is part of AudioCuesheetEditor. +//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 @@ -13,14 +13,10 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using AudioCuesheetEditor.Extensions; using AudioCuesheetEditor.Model.AudioCuesheet; -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; using System.Text.Json; -using System.Threading.Tasks; +using System.Text.Json.Serialization; namespace AudioCuesheetEditor.Model.IO { @@ -29,11 +25,12 @@ public class Projectfile public const String MimeType = "text/*"; public const String FileExtension = ".ace"; - public static readonly String DefaultFileName = "Project.ace"; + public static readonly String DefaultFilename = "Project.ace"; public static readonly JsonSerializerOptions Options = new() { - DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + ReferenceHandler = ReferenceHandler.IgnoreCycles }; public static Cuesheet? ImportFile(byte[] fileContent) @@ -59,7 +56,7 @@ public Projectfile(Cuesheet cuesheet) /// Byte array with project file content public byte[] GenerateFile() { - var json = JsonSerializer.Serialize(Cuesheet, Options); + var json = JsonSerializer.Serialize(Cuesheet, Options); return Encoding.UTF8.GetBytes(json); } } diff --git a/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs b/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs index 8180a265..51c0c1fb 100644 --- a/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs +++ b/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs @@ -13,17 +13,13 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using AudioCuesheetEditor.Controller; using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.Entity; using AudioCuesheetEditor.Model.IO; using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Model.IO.Export; -using AudioCuesheetEditor.Model.IO.Import; using AudioCuesheetEditor.Model.Utility; -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Text.Json.Serialization; namespace AudioCuesheetEditor.Model.Options @@ -45,87 +41,24 @@ public enum TimeSensitivityMode Minutes = 2 } - public class ApplicationOptions : IOptions + public class ApplicationOptions : Validateable, IOptions { public const String DefaultCultureName = "en-US"; - private String audioFileNameRecording; - private String? projectFilename; - private String? cuesheetFilename; - public static IReadOnlyCollection AvailableCultures { get { var cultures = new List { - new CultureInfo("en-US"), - new CultureInfo("de-DE") + new("en-US"), + new("de-DE") }; return cultures.AsReadOnly(); } } - -#pragma warning disable CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie die Deklaration als Nullable. - public ApplicationOptions() -#pragma warning restore CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie die Deklaration als Nullable. - { - SetDefaultValues(); - } - - public void SetDefaultValues() - { - //Declare defaults - if (String.IsNullOrEmpty(CuesheetFileName) == true) - { - CuesheetFileName = Cuesheetfile.DefaultFileName; - } - if (String.IsNullOrEmpty(CultureName) == true) - { - CultureName = DefaultCultureName; - } - if (String.IsNullOrEmpty(AudioFileNameRecording) == true) - { - AudioFileNameRecording = Audiofile.RecordingFileName; - } - if (LinkTracksWithPreviousOne.HasValue == false) - { - LinkTracksWithPreviousOne = true; - } - if (String.IsNullOrEmpty(ProjectFileName)) - { - ProjectFileName = Projectfile.DefaultFileName; - } - if (RecordCountdownTimer.HasValue == false) - { - RecordCountdownTimer = 5; - } - - } - public String? CuesheetFileName - { - get => cuesheetFilename; - set - { - if (String.IsNullOrEmpty(value) == false) - { - var extension = Path.GetExtension(value); - if (extension.Equals(Cuesheet.FileExtension, StringComparison.OrdinalIgnoreCase)) - { - cuesheetFilename = value; - } - else - { - cuesheetFilename = String.Format("{0}{1}", Path.GetFileNameWithoutExtension(value), Cuesheet.FileExtension); - } - } - else - { - cuesheetFilename = null; - } - } - } - public String? CultureName { get; set; } + public String? CuesheetFilename { get; set; } = Exportfile.DefaultCuesheetFilename; + public String? CultureName { get; set; } = DefaultCultureName; [JsonIgnore] public CultureInfo Culture { @@ -143,7 +76,7 @@ public CultureInfo Culture } [JsonIgnore] public ViewMode ViewMode { get; set; } - public String? ViewModeName + public String? ViewModename { get { return Enum.GetName(typeof(ViewMode), ViewMode); } set @@ -158,73 +91,106 @@ public String? ViewModeName } } } - public String AudioFileNameRecording + public String RecordedAudiofilename { get; set; } = Audiofile.RecordingFileName; + public Boolean LinkTracksWithPreviousOne { get; set; } = true; + public String? ProjectFilename { get; set; } = Projectfile.DefaultFilename; + public uint RecordCountdownTimer { get; set; } = 5; + [JsonIgnore] + public TimeSensitivityMode RecordTimeSensitivity { get; set; } + public String? RecordTimeSensitivityname { - get { return audioFileNameRecording; } - set + get { return Enum.GetName(typeof(TimeSensitivityMode), RecordTimeSensitivity); } + set { - if (String.IsNullOrEmpty(value) == false) + if (value != null) { - var extension = Path.GetExtension(value); - if ((String.IsNullOrEmpty(extension)) || (extension.Equals(Audiofile.AudioCodecWEBM.FileExtension, StringComparison.OrdinalIgnoreCase) == false)) - { - audioFileNameRecording = String.Format("{0}{1}", Path.GetFileNameWithoutExtension(value), Audiofile.AudioCodecWEBM.FileExtension); - } - else - { - audioFileNameRecording = value; - } + RecordTimeSensitivity = (TimeSensitivityMode)Enum.Parse(typeof(TimeSensitivityMode), value); } else { - audioFileNameRecording = String.Empty; + throw new ArgumentNullException(nameof(value)); } } } - public Boolean? LinkTracksWithPreviousOne { get; set; } - public String? ProjectFileName + public TimeSpanFormat? TimeSpanFormat { get; set; } + + protected override ValidationResult Validate(string property) { - get { return projectFilename; } - set + ValidationStatus validationStatus = ValidationStatus.NoValidation; + List? validationMessages = null; + switch (property) { - if (String.IsNullOrEmpty(value) == false) - { - var extension = Path.GetExtension(value); - if (extension.Equals(Projectfile.FileExtension, StringComparison.OrdinalIgnoreCase)) + case nameof(CuesheetFilename): + validationStatus = ValidationStatus.Success; + if (string.IsNullOrEmpty(CuesheetFilename)) { - projectFilename = value; + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(CuesheetFilename))); } else { - projectFilename = String.Format("{0}{1}", Path.GetFileNameWithoutExtension(value), Projectfile.FileExtension); + var extension = Path.GetExtension(CuesheetFilename); + if (extension.Equals(Cuesheet.FileExtension, StringComparison.OrdinalIgnoreCase) == false) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} must end with '{1}'!", nameof(CuesheetFilename), Cuesheet.FileExtension)); + } + var filenameWithoutExtension = Path.GetFileNameWithoutExtension(CuesheetFilename); + if (string.IsNullOrEmpty(filenameWithoutExtension)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} must have a filename!", nameof(CuesheetFilename))); + } } - } - else - { - projectFilename = null; - } - } - } - public int? RecordCountdownTimer { get; set; } - - [JsonIgnore] - public TimeSensitivityMode RecordTimeSensitivity { get; set; } - public String? RecordTimeSensitivityName - { - get { return Enum.GetName(typeof(TimeSensitivityMode), RecordTimeSensitivity); } - set - { - if (value != null) - { - RecordTimeSensitivity = (TimeSensitivityMode)Enum.Parse(typeof(TimeSensitivityMode), value); - } - else - { - throw new ArgumentNullException(nameof(value)); - } + break; + case nameof(RecordedAudiofilename): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(RecordedAudiofilename)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(RecordedAudiofilename))); + } + else + { + var extension = Path.GetExtension(RecordedAudiofilename); + if (extension.Equals(Audiofile.AudioCodecWEBM.FileExtension, StringComparison.OrdinalIgnoreCase) == false) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} must end with '{1}'!", nameof(RecordedAudiofilename), Audiofile.AudioCodecWEBM.FileExtension)); + } + var filename = Path.GetFileNameWithoutExtension(RecordedAudiofilename); + if (String.IsNullOrEmpty(filename)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} must have a filename!", nameof(RecordedAudiofilename))); + } + } + break; + case nameof(ProjectFilename): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(ProjectFilename)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(ProjectFilename))); + } + else + { + var extension = Path.GetExtension(ProjectFilename); + if (extension.Equals(Projectfile.FileExtension, StringComparison.OrdinalIgnoreCase) == false) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} must end with '{1}'!", nameof(ProjectFilename), Projectfile.FileExtension)); + } + var filename = Path.GetFileNameWithoutExtension(ProjectFilename); + if (String.IsNullOrEmpty(filename)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} must have a filename!", nameof(ProjectFilename))); + } + } + break; } + return ValidationResult.Create(validationStatus, validationMessages); } - - public TimeSpanFormat? TimeSpanFormat { get; set; } } } diff --git a/AudioCuesheetEditor/Model/Options/ExportOptions.cs b/AudioCuesheetEditor/Model/Options/ExportOptions.cs index 7264366c..ec733b00 100644 --- a/AudioCuesheetEditor/Model/Options/ExportOptions.cs +++ b/AudioCuesheetEditor/Model/Options/ExportOptions.cs @@ -19,59 +19,36 @@ namespace AudioCuesheetEditor.Model.Options { public class ExportOptions : IOptions { - public IReadOnlyCollection ExportProfiles { get; set; } - -#pragma warning disable CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie die Deklaration als Nullable. - public ExportOptions() -#pragma warning restore CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie die Deklaration als Nullable. - { - SetDefaultValues(); - } - - public void SetDefaultValues() - { - //Declare defaults - if (ExportProfiles == null) + public static readonly List DefaultExportProfiles = new() + { + new Exportprofile() + { + Filename = "YouTube.txt", + Name = "YouTube", + SchemeHead = "%Cuesheet.Artist% - %Cuesheet.Title%", + SchemeTracks = "%Track.Artist% - %Track.Title% %Track.Begin%" + }, + new Exportprofile() + { + Filename = "Mixcloud.txt", + Name = "Mixcloud", + SchemeTracks = "%Track.Artist% - %Track.Title% %Track.Begin%" + }, + new Exportprofile() + { + Filename = "Export.csv", + Name = "CSV Export", + SchemeHead = "%Cuesheet.Artist%;%Cuesheet.Title%;", + SchemeTracks = "%Track.Position%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%", + SchemeFooter = "Exported at %DateTime% using AudioCuesheetEditor (https://neocodermatrix86.github.io/AudioCuesheetEditor/)" + }, + new Exportprofile() { - var list = new List(); - var exportProfile = new Exportprofile() - { - FileName = "YouTube.txt", - Name = "YouTube" - }; - exportProfile.SchemeHead.Scheme = "%Cuesheet.Artist% - %Cuesheet.Title%"; - exportProfile.SchemeTracks.Scheme = "%Track.Artist% - %Track.Title% %Track.Begin%"; - exportProfile.SchemeFooter.Scheme = String.Empty; - list.Add(exportProfile); - exportProfile = new Exportprofile() - { - FileName = "Mixcloud.txt", - Name = "Mixcloud" - }; - exportProfile.SchemeHead.Scheme = String.Empty; - exportProfile.SchemeTracks.Scheme = "%Track.Artist% - %Track.Title% %Track.Begin%"; - exportProfile.SchemeFooter.Scheme = String.Empty; - list.Add(exportProfile); - exportProfile = new Exportprofile() - { - FileName = "Export.csv", - Name = "CSV Export" - }; - exportProfile.SchemeHead.Scheme = "%Cuesheet.Artist%;%Cuesheet.Title%;"; - exportProfile.SchemeTracks.Scheme = "%Track.Position%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%"; - exportProfile.SchemeFooter.Scheme = "Exported at %DateTime% using AudioCuesheetEditor (https://neocodermatrix86.github.io/AudioCuesheetEditor/)"; - list.Add(exportProfile); - exportProfile = new Exportprofile() - { - FileName = "Tracks.txt", - Name = "Tracks only" - }; - exportProfile.SchemeHead.Scheme = String.Empty; - exportProfile.SchemeTracks.Scheme = "%Track.Position% - %Track.Artist% - %Track.Title% - %Track.Begin% - %Track.End% - %Track.Length%"; - exportProfile.SchemeFooter.Scheme = String.Empty; - list.Add(exportProfile); - ExportProfiles = list.AsReadOnly(); + Filename = "Tracks.txt", + Name = "Tracks only", + SchemeTracks = "%Track.Position% - %Track.Artist% - %Track.Title% - %Track.Begin% - %Track.End% - %Track.Length%", } - } + }; + public IReadOnlyCollection ExportProfiles { get; set; } = DefaultExportProfiles; } } diff --git a/AudioCuesheetEditor/Model/Options/IOptions.cs b/AudioCuesheetEditor/Model/Options/IOptions.cs index b0d852a3..d7cb1a26 100644 --- a/AudioCuesheetEditor/Model/Options/IOptions.cs +++ b/AudioCuesheetEditor/Model/Options/IOptions.cs @@ -17,6 +17,6 @@ namespace AudioCuesheetEditor.Model.Options { public interface IOptions { - public void SetDefaultValues(); + } } diff --git a/AudioCuesheetEditor/Model/Options/ImportOptions.cs b/AudioCuesheetEditor/Model/Options/ImportOptions.cs index 0b596921..59df0ac8 100644 --- a/AudioCuesheetEditor/Model/Options/ImportOptions.cs +++ b/AudioCuesheetEditor/Model/Options/ImportOptions.cs @@ -24,11 +24,10 @@ public class ImportOptions : IOptions public TextImportScheme TextImportScheme { get; set; } public TimeSpanFormat? TimeSpanFormat { get; set; } -#pragma warning disable CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie die Deklaration als Nullable. public ImportOptions() -#pragma warning restore CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie die Deklaration als Nullable. { - SetDefaultValues(); + TextImportScheme = TextImportScheme.DefaultTextImportScheme; + //SetDefaultValues(); } public ImportOptions(TextImportfile textImportfile) @@ -36,12 +35,5 @@ public ImportOptions(TextImportfile textImportfile) TextImportScheme = textImportfile.TextImportScheme; TimeSpanFormat = textImportfile.TimeSpanFormat; } - - public void SetDefaultValues() - { - //Declare defaults - TextImportScheme ??= TextImportScheme.DefaultTextImportScheme; - TimeSpanFormat ??= new TimeSpanFormat(); - } } } diff --git a/AudioCuesheetEditor/Model/Reflection/FieldReference.cs b/AudioCuesheetEditor/Model/Reflection/FieldReference.cs deleted file mode 100644 index 80499900..00000000 --- a/AudioCuesheetEditor/Model/Reflection/FieldReference.cs +++ /dev/null @@ -1,127 +0,0 @@ -//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 System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; - -namespace AudioCuesheetEditor.Model.Reflection -{ - public class FieldReference : IEquatable - { - private readonly PropertyInfo? propertyInfo; - - public static FieldReference Create(object ownerObject, String property) - { - return new FieldReference(ownerObject, property); - } - - #region IEquatable - - public override bool Equals(object? obj) => (obj is FieldReference other) && this.Equals(other); - - public bool Equals(FieldReference? other) - { - if (other is null) - { - return false; - } - - if (Object.ReferenceEquals(this, other)) - { - return true; - } - - if (this.GetType() != other.GetType()) - { - return false; - } - - return (Owner == other.Owner) && (Property == other.Property) && (CompleteName == other.CompleteName); - } - - public static bool operator ==(FieldReference lhs, FieldReference rhs) - { - if (lhs is null) - { - if (rhs is null) - { - return true; - } - - return false; - } - return lhs.Equals(rhs); - } - - public static bool operator !=(FieldReference lhs, FieldReference rhs) - { - return !(lhs == rhs); - } - - public override int GetHashCode() - { - return HashCode.Combine(Owner, Property, CompleteName); - } - - #endregion - - private FieldReference(object ownerObject, String property) - { - if (String.IsNullOrEmpty(property) == true) - { - throw new ArgumentNullException(nameof(property)); - } - Owner = ownerObject; - propertyInfo = Owner.GetType().GetProperty(property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - if (propertyInfo == null) - { - throw new ArgumentException(String.Format("Property {0} could not be found!", property),nameof(property)); - } - } - - public String? CompleteName - { - get - { - if (propertyInfo != null) - { - return String.Format("{0}.{1}", Owner.GetType().Name, propertyInfo.Name); - } - else - { - return null; - } - } - } - public object Owner { get; private set; } - public String? Property - { - get - { - if (propertyInfo != null) - { - return propertyInfo.Name; - } - else - { - return null; - } - } - } - } -} diff --git a/AudioCuesheetEditor/Model/UI/BootstrapUtility.cs b/AudioCuesheetEditor/Model/UI/BootstrapUtility.cs deleted file mode 100644 index 69e0c2c1..00000000 --- a/AudioCuesheetEditor/Model/UI/BootstrapUtility.cs +++ /dev/null @@ -1,57 +0,0 @@ -//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.Entity; - -namespace AudioCuesheetEditor.Model.UI -{ - public class BootstrapUtility - { - public static String GetCSSClassAlert(Validateable? validateable, String property) - { - String cssClass = String.Empty; - if (validateable != null) - { - var validationErrors = validateable.GetValidationErrorsFiltered(property); - if (validationErrors.Count >= 1) - { - if (validationErrors.Count > 1) - { - if (validateable.GetValidationErrorsFiltered(property, ValidationErrorFilterType.ErrorOnly).Count >= 1) - { - cssClass = "alert-danger"; - } - else - { - cssClass = "alert-warning"; - } - } - else - { - if (validationErrors.First().Type == ValidationErrorType.Error) - { - cssClass = "alert-danger"; - } - if (validationErrors.First().Type == ValidationErrorType.Warning) - { - cssClass = "alert-warning"; - } - } - } - } - return cssClass; - } - } -} diff --git a/AudioCuesheetEditor/Model/UI/TraceChangeManager.cs b/AudioCuesheetEditor/Model/UI/TraceChangeManager.cs index 7a929a64..78b5ca1c 100644 --- a/AudioCuesheetEditor/Model/UI/TraceChangeManager.cs +++ b/AudioCuesheetEditor/Model/UI/TraceChangeManager.cs @@ -14,6 +14,8 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Pages; +using Markdig.Extensions.Yaml; using System; using System.Collections; using System.Collections.Generic; @@ -50,14 +52,12 @@ public ITraceable? TraceableObject public class TracedChanges { - private readonly Stack _tracedChanges; - public TracedChanges(IEnumerable changes) { - _tracedChanges = new(changes); + Changes = new(changes); } - public Stack Changes => _tracedChanges; + public List Changes { get; } public Boolean HasTraceableObject { get { return Changes.Any(x => x.TraceableObject != null); } } } @@ -135,9 +135,9 @@ public void Undo() if ((changes != null) && changes.HasTraceableObject) { var redoChanges = new List(); - do + for (int i = changes.Changes.Count - 1; i >= 0; i--) { - var change = changes.Changes.Pop(); + var change = changes.Changes.ElementAt(i); var tracedObject = change?.TraceableObject; var traceAbleChange = change?.TraceableChange; _logger.LogDebug("tracedObject = {tracedObject}, traceAbleChange = {traceAbleChange}", tracedObject, traceAbleChange); @@ -156,8 +156,11 @@ public void Undo() throw new NullReferenceException(String.Format("Property {0} could not be found!", traceAbleChange.PropertyName)); } } + if (change != null) + { + changes.Changes.Remove(change); + } } - while (changes.Changes.Count > 0); //Push the old value to redo stack redoStack.Push(new TracedChanges(redoChanges)); } @@ -184,9 +187,9 @@ public void Redo() if ((changes != null) && changes.HasTraceableObject) { var undoChanges = new List(); - do + for (int i = changes.Changes.Count - 1;i >= 0; i--) { - var change = changes.Changes.Pop(); + var change = changes.Changes.ElementAt(i); var tracedObject = change?.TraceableObject; var traceAbleChange = change?.TraceableChange; _logger.LogDebug("tracedObject = {tracedObject}, traceAbleChange = {traceAbleChange}", tracedObject, traceAbleChange); @@ -205,8 +208,11 @@ public void Redo() throw new NullReferenceException(String.Format("Property {0} could not be found!", traceAbleChange.PropertyName)); } } + if (change != null) + { + changes.Changes.Remove(change); + } } - while (changes.Changes.Count > 0); //Push the old value to redo stack undoStack.Push(new TracedChanges(undoChanges)); } @@ -237,14 +243,35 @@ public Boolean BulkEdit } } + public TracedChanges? LastEdit + { + get + { + return undoStack.Peek(); + } + } + + public void MergeLastEditWithEdit(Func targetEdit) + { + var edit = undoStack.FirstOrDefault(targetEdit); + if (edit != null) + { + if (undoStack.Count > 0) + { + var lastEdits = undoStack.Pop(); + edit.Changes.AddRange(lastEdits.Changes); + UndoDone?.Invoke(this, EventArgs.Empty); + } + } + } + private void ResetStack(Stack stack) { while (stack.Count > 0) { var tracedChange = stack.Pop(); - while (tracedChange.Changes.Count > 0) + foreach (var change in tracedChange.Changes) { - var change = tracedChange.Changes.Pop(); if (change.TraceableObject != null) { change.TraceableObject.TraceablePropertyChanged -= Traceable_TraceablePropertyChanged; @@ -266,7 +293,7 @@ private void Traceable_TraceablePropertyChanged(object? sender, TraceablePropert if (BulkEdit == false) { //Single change - var changes = new TracedChanges(new List() { new TracedChange((ITraceable)sender, e.TraceableChange) }); + var changes = new TracedChanges(new List() { new((ITraceable)sender, e.TraceableChange) }); undoStack.Push(changes); redoStack.Clear(); TracedObjectHistoryChanged?.Invoke(this, EventArgs.Empty); diff --git a/AudioCuesheetEditor/Model/UI/ValidatorUtility.cs b/AudioCuesheetEditor/Model/UI/ValidatorUtility.cs new file mode 100644 index 00000000..14b80de0 --- /dev/null +++ b/AudioCuesheetEditor/Model/UI/ValidatorUtility.cs @@ -0,0 +1,60 @@ +//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.Entity; +using Blazorise; +using Blazorise.Localization; +using System.Linq.Expressions; + +namespace AudioCuesheetEditor.Model.UI +{ + public class ValidatorUtility where T : IValidateable + { + public static Task Validate(ValidatorEventArgs args, T? entity, Expression> expression, ITextLocalizer textLocalizer, CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) + { + if (entity != null) + { + var validationResult = entity.Validate(expression); + if (!cancellationToken.IsCancellationRequested) + { + switch (validationResult.Status) + { + case Entity.ValidationStatus.NoValidation: + args.Status = Blazorise.ValidationStatus.None; + break; + case Entity.ValidationStatus.Success: + args.Status = Blazorise.ValidationStatus.Success; + break; + case Entity.ValidationStatus.Error: + args.Status = Blazorise.ValidationStatus.Error; + if (validationResult.ValidationMessages != null) + { + foreach (var validationMessage in validationResult.ValidationMessages) + { + args.ErrorText += String.Format("{0}{1}", validationMessage.GetMessageLocalized(textLocalizer), Environment.NewLine); + } + } + break; + } + } + } + } + + return Task.CompletedTask; + } + } +} diff --git a/AudioCuesheetEditor/Model/Utility/DateTimeUtility.cs b/AudioCuesheetEditor/Model/Utility/DateTimeUtility.cs index 3eb35879..463c5ac0 100644 --- a/AudioCuesheetEditor/Model/Utility/DateTimeUtility.cs +++ b/AudioCuesheetEditor/Model/Utility/DateTimeUtility.cs @@ -13,22 +13,46 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Data.Options; using AudioCuesheetEditor.Model.Options; -using System.Diagnostics.Metrics; -using System.Globalization; -using System.Text.RegularExpressions; +using System.Linq.Expressions; +using System.Reflection; namespace AudioCuesheetEditor.Model.Utility { - public class DateTimeUtility + public class DateTimeUtility : IDisposable { - public static TimeSpan? ParseTimeSpan(String input, TimeSpanFormat? timespanformat = null) + private readonly LocalStorageOptionsProvider? _localStorageOptionsProvider; + private readonly TimeSpanFormat? _timeFormat; + + private ApplicationOptions? applicationOptions; + private bool disposedValue; + + public DateTimeUtility(LocalStorageOptionsProvider localStorageOptionsProvider) + { + _localStorageOptionsProvider = localStorageOptionsProvider; + _localStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionSaved; + Task.Run(InitAsync); + } + + public DateTimeUtility(TimeSpanFormat timeSpanFormat) + { + _timeFormat = timeSpanFormat; + } + + public void Dispose() + { + // Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in der Methode "Dispose(bool disposing)" ein. + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + public TimeSpan? ParseTimeSpan(String input) { TimeSpan? result = null; if (String.IsNullOrEmpty(input) == false) { - if (timespanformat == null) + if (TimeSpanFormat?.Scheme == null) { if (TimeSpan.TryParse(input, out var parsed)) { @@ -37,10 +61,81 @@ public class DateTimeUtility } else { - result = timespanformat.ParseTimeSpan(input); + result = TimeSpanFormat.ParseTimeSpan(input); } } return result; } + + public async Task TimespanTextChanged(T entity, Expression> expression, String value) + { + if (expression.Body is not MemberExpression memberExpression) + { + throw new ArgumentException("'expression' should be a member expression"); + } + if (applicationOptions == null) + { + await InitAsync(); + } + TimeSpan? result = ParseTimeSpan(value); + switch (memberExpression.Member.MemberType) + { + case MemberTypes.Property: + ((PropertyInfo)memberExpression.Member).SetValue(entity, result); + break; + default: + throw new NotImplementedException(); + } + } + + TimeSpanFormat? TimeSpanFormat + { + get + { + if (applicationOptions != null) + { + return applicationOptions.TimeSpanFormat; + } + if (_timeFormat != null) + { + return _timeFormat; + } + return null; + } + } + + private async Task InitAsync() + { + if (_localStorageOptionsProvider != null) + { + applicationOptions ??= await _localStorageOptionsProvider.GetOptions(); + } + } + + private void LocalStorageOptionsProvider_OptionSaved(object? sender, IOptions options) + { + if (options is ApplicationOptions applicationOption) + { + applicationOptions = applicationOption; + } + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // Verwalteten Zustand (verwaltete Objekte) bereinigen + if (_localStorageOptionsProvider != null) + { + _localStorageOptionsProvider.OptionSaved -= LocalStorageOptionsProvider_OptionSaved; + } + } + + disposedValue = true; + } + } + } } diff --git a/AudioCuesheetEditor/Model/Utility/IOUtility.cs b/AudioCuesheetEditor/Model/Utility/IOUtility.cs new file mode 100644 index 00000000..450d14de --- /dev/null +++ b/AudioCuesheetEditor/Model/Utility/IOUtility.cs @@ -0,0 +1,66 @@ +//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.IO.Audio; +using Blazorise; + +namespace AudioCuesheetEditor.Model.Utility +{ + public class IOUtility + { + public static Boolean CheckFileMimeType(IFileEntry file, String mimeType, String fileExtension) + { + Boolean fileMimeTypeMatches = false; + if ((file != null) && (String.IsNullOrEmpty(mimeType) == false) && (String.IsNullOrEmpty(fileExtension) == false)) + { + if (String.IsNullOrEmpty(file.Type) == false) + { + fileMimeTypeMatches = file.Type.ToLower() == mimeType.ToLower(); + } + else + { + //Try to find by file extension + var extension = Path.GetExtension(file.Name).ToLower(); + fileMimeTypeMatches = extension == fileExtension.ToLower(); + } + } + return fileMimeTypeMatches; + } + + public static Boolean CheckFileMimeTypeForAudioCodec(IFileEntry file) + { + return GetAudioCodec(file) != null; + } + + public static AudioCodec? GetAudioCodec(IFileEntry fileEntry) + { + AudioCodec? foundAudioCodec = null; + var extension = Path.GetExtension(fileEntry.Name).ToLower(); + // First search with mime type and file extension + var audioCodecsFound = Audiofile.AudioCodecs.Where(x => x.MimeType.Equals(fileEntry.Type, StringComparison.OrdinalIgnoreCase) && x.FileExtension.Equals(extension, StringComparison.OrdinalIgnoreCase)); + if (audioCodecsFound.Count() <= 1) + { + foundAudioCodec = audioCodecsFound.FirstOrDefault(); + } + else + { + // Second search with mime type or file extension + audioCodecsFound = Audiofile.AudioCodecs.Where(x => x.MimeType.Equals(fileEntry.Type, StringComparison.OrdinalIgnoreCase) || x.FileExtension.Equals(extension, StringComparison.OrdinalIgnoreCase)); + foundAudioCodec = audioCodecsFound.FirstOrDefault(); + } + return foundAudioCodec; + } + } +} diff --git a/AudioCuesheetEditor/Model/Utility/Timespanformat.cs b/AudioCuesheetEditor/Model/Utility/TimeSpanFormat.cs similarity index 72% rename from AudioCuesheetEditor/Model/Utility/Timespanformat.cs rename to AudioCuesheetEditor/Model/Utility/TimeSpanFormat.cs index 82b88b59..874e3d23 100644 --- a/AudioCuesheetEditor/Model/Utility/Timespanformat.cs +++ b/AudioCuesheetEditor/Model/Utility/TimeSpanFormat.cs @@ -1,4 +1,4 @@ -//This file is part of AudioCuesheetEditor. +//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 @@ -14,12 +14,12 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.Entity; -using AudioCuesheetEditor.Model.Reflection; +using Blazorise.Localization; using System.Text.RegularExpressions; namespace AudioCuesheetEditor.Model.Utility { - public class TimeSpanFormat : Validateable + public class TimeSpanFormat : Validateable { public const String Days = "Days"; public const String Hours = "Hours"; @@ -27,11 +27,11 @@ public class TimeSpanFormat : Validateable public const String Seconds = "Seconds"; public const String Milliseconds = "Milliseconds"; - private string? scheme; - public const String EnterRegularExpressionHere = "ENTER REGULAR EXPRESSION HERE"; public static readonly IReadOnlyDictionary AvailableTimespanScheme; + public static ITextLocalizer? TextLocalizer { get; set; } + static TimeSpanFormat() { var schemeDays = String.Format("(?'{0}.{1}'{2})", nameof(TimeSpanFormat), nameof(Days), EnterRegularExpressionHere); @@ -52,14 +52,16 @@ static TimeSpanFormat() public event EventHandler? SchemeChanged; + private string? scheme; + public String? Scheme { get => scheme; set { scheme = value; - OnValidateablePropertyChanged(); SchemeChanged?.Invoke(this, EventArgs.Empty); + OnValidateablePropertyChanged(); } } @@ -108,27 +110,39 @@ public String? Scheme return timespan; } - protected override void Validate() + protected override ValidationResult Validate(string property) { - if (String.IsNullOrEmpty(Scheme)) + ValidationStatus validationStatus = ValidationStatus.NoValidation; + List? validationMessages = null; + var enterRegularExpression = EnterRegularExpressionHere; + if (TextLocalizer != null) { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Scheme)), ValidationErrorType.Warning, "{0} has no value!", nameof(Scheme))); + enterRegularExpression = TextLocalizer[EnterRegularExpressionHere]; } - else + switch (property) { - if ((Scheme.Contains(Days) == false) - && (Scheme.Contains(Hours) == false) - && (Scheme.Contains(Minutes) == false) - && (Scheme.Contains(Seconds) == false) - && (Scheme.Contains(Milliseconds) == false)) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Scheme)), ValidationErrorType.Warning, "{0} contains no placeholder!", nameof(Scheme))); - } - if (Scheme.Contains(EnterRegularExpressionHere)) - { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(Scheme)), ValidationErrorType.Warning, "Replace {0} by a regular expression!", EnterRegularExpressionHere)); - } + case nameof(Scheme): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(Scheme) == false) + { + if ((Scheme.Contains(Days) == false) + && (Scheme.Contains(Hours) == false) + && (Scheme.Contains(Minutes) == false) + && (Scheme.Contains(Seconds) == false) + && (Scheme.Contains(Milliseconds) == false)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} contains no placeholder!", nameof(Scheme))); + } + if (Scheme.Contains(enterRegularExpression)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("Replace '{0}' by a regular expression!", enterRegularExpression)); + } + } + break; } + return ValidationResult.Create(validationStatus, validationMessages); } } } diff --git a/AudioCuesheetEditor/Pages/EditSplitpoints.razor b/AudioCuesheetEditor/Pages/EditSplitpoints.razor new file mode 100644 index 00000000..c551484b --- /dev/null +++ b/AudioCuesheetEditor/Pages/EditSplitpoints.razor @@ -0,0 +1,305 @@ + +@implements IDisposable + +@inject ITextLocalizer _localizer +@inject SessionStateContainer _sessionStateContainer +@inject ITextLocalizer _validationMessageLocalizer +@inject DateTimeUtility _dateTimeUtility +@inject ITextLocalizerService _localizationService +@inject TraceChangeManager _traceChangeManager +@inject IJSRuntime _jsRuntime + + + @if (Cuesheet != null) + { + @if (_sessionStateContainer.CurrentViewMode == ViewMode.ViewModeFull) + { + + + + + +
+ } + + + + @_localizer["Controls"] + @_localizer["Moment"] + @_localizer["CD artist"] + @_localizer["CD title"] + @_localizer["Audiofile name"] + + + + @foreach (var splitPoint in Cuesheet.SplitPoints) + { + + + +
+ + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @if (String.IsNullOrEmpty(splitPoint.AudiofileName)) + { + x.MimeType))" Changed="(args) => OnSplitpointAudiofileChanged(args, splitPoint)" AutoReset="false"> + + + + + } + else + { + + + + + + + + + + + + + } + + +
+ } +
+
+ } +
+ + + +@code { + Validations? validations; + ModalDialog? modalDialog; + Validation? audiofileValidation; + Dictionary fileEditAudiofileIds = new(); + + public Cuesheet? Cuesheet + { + get + { + Cuesheet? cuesheet; + switch (_sessionStateContainer.CurrentViewMode) + { + case ViewMode.ViewModeImport: + cuesheet = _sessionStateContainer.ImportCuesheet; + break; + default: + cuesheet = _sessionStateContainer.Cuesheet; + break; + } + return cuesheet; + } + } + + public void Dispose() + { + _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; + if (Cuesheet != null) + { + foreach (var track in Cuesheet.Tracks) + { + track.ValidateablePropertyChanged -= Track_ValidateablePropertyChanged; + } + Cuesheet.TrackAdded -= Cuesheet_TrackAdded; + Cuesheet.TrackRemoved -= Cuesheet_TrackRemoved; + } + _traceChangeManager.UndoDone -= TraceChangeManager_UndoDone; + _traceChangeManager.RedoDone -= TraceChangeManager_RedoDone; + _sessionStateContainer.CuesheetChanged -= SessionStateContainer_CuesheetChanged; + _sessionStateContainer.ImportCuesheetChanged -= SessionStateContainer_ImportCuesheetChanged; + } + + protected override void OnInitialized() + { + base.OnInitialized(); + _localizationService.LocalizationChanged += LocalizationService_LocalizationChanged; + if (Cuesheet != null) + { + Cuesheet.TrackAdded += Cuesheet_TrackAdded; + Cuesheet.TrackRemoved += Cuesheet_TrackRemoved; + } + _traceChangeManager.UndoDone += TraceChangeManager_UndoDone; + _traceChangeManager.RedoDone += TraceChangeManager_RedoDone; + _sessionStateContainer.CuesheetChanged += SessionStateContainer_CuesheetChanged; + _sessionStateContainer.ImportCuesheetChanged += SessionStateContainer_ImportCuesheetChanged; + } + + void LocalizationService_LocalizationChanged(object? sender, EventArgs args) + { + StateHasChanged(); + validations?.ValidateAll(); + } + + Task AddSplitPointClicked() + { + if (Cuesheet != null) + { + var splitPoint = Cuesheet.AddSplitPoint(); + _traceChangeManager.TraceChanges(splitPoint); + fileEditAudiofileIds.Add(splitPoint, Guid.NewGuid()); + } + return Task.CompletedTask; + } + + Task DeleteSplitPointClicked(SplitPoint splitPoint) + { + if (Cuesheet != null) + { + Cuesheet.RemoveSplitPoint(splitPoint); + } + return Task.CompletedTask; + } + + void Cuesheet_TrackAdded(object? sender, TrackAddRemoveEventArgs args) + { + args.Track.ValidateablePropertyChanged += Track_ValidateablePropertyChanged; + validations?.ValidateAll().GetAwaiter().GetResult(); + } + + void Cuesheet_TrackRemoved(object? sender, TrackAddRemoveEventArgs args) + { + args.Track.ValidateablePropertyChanged -= Track_ValidateablePropertyChanged; + validations?.ValidateAll().GetAwaiter().GetResult(); + } + + void Track_ValidateablePropertyChanged(object? sender, string property) + { + switch (property) + { + case nameof(Track.Begin): + case nameof(Track.End): + validations?.ValidateAll().GetAwaiter().GetResult(); + break; + } + } + + void TraceChangeManager_UndoDone(object? sender, EventArgs args) + { + StateHasChanged(); + validations?.ValidateAll().GetAwaiter().GetResult(); + } + + void TraceChangeManager_RedoDone(object? sender, EventArgs args) + { + StateHasChanged(); + validations?.ValidateAll().GetAwaiter().GetResult(); + } + + void SessionStateContainer_CuesheetChanged(object? sender, EventArgs args) + { + StateHasChanged(); + } + + void SessionStateContainer_ImportCuesheetChanged(object? sender, EventArgs args) + { + StateHasChanged(); + } + + async Task OnSplitpointAudiofileChangedCliccked(SplitPoint splitPoint) + { + splitPoint.AudiofileName = null; + StateHasChanged(); + await Task.Delay(1); + await _jsRuntime.InvokeVoidAsync("triggerClick", fileEditAudiofileIds[splitPoint]); + } + + async Task OnSplitpointAudiofileChanged(FileChangedEventArgs e, SplitPoint splitPoint) + { + if (e.Files.FirstOrDefault() != null) + { + if (Cuesheet != null) + { + var file = e.Files.First(); + if (IOUtility.CheckFileMimeTypeForAudioCodec(file) == true) + { + splitPoint.AudiofileName = file.Name; + } + else + { + if (modalDialog != null) + { + modalDialog.Title = _localizer["Error"]; + modalDialog.Text = String.Format(_localizer["The file {0} can not be used for operation: {1}. The file is invalid, please use a valid file!"], file.Name, _localizer["Audiofile"]); + modalDialog.ModalSize = ModalSize.Small; + modalDialog.Mode = ModalDialog.DialogMode.Alert; + await modalDialog.ShowModal(); + } + } + } + } + if (audiofileValidation != null) + { + await audiofileValidation.ValidateAsync(); + } + } +} diff --git a/AudioCuesheetEditor/Pages/Help.razor b/AudioCuesheetEditor/Pages/Help.razor index d6ee507b..2ed4e30c 100644 --- a/AudioCuesheetEditor/Pages/Help.razor +++ b/AudioCuesheetEditor/Pages/Help.razor @@ -35,6 +35,8 @@ along with Foobar. If not, see
@_localizer["User Interface"]
+ @_localizer["Shortcuts"] +
@_localizer["Validation"]
@_localizer["Track linking"] @@ -49,6 +51,8 @@ along with Foobar. If not, see
@_localizer["Export of data"]
+ @_localizer["Splitpoints"] +
@_localizer["Recordmode"]
@_localizer["Options"] @@ -77,6 +81,11 @@ along with Foobar. If not, see @_localizer["Scroll to top"] @((MarkupString)_localizer["User Interface helptext"]) + + @_localizer["Shortcuts"] + @_localizer["Scroll to top"] + + @((MarkupString)_localizer["Shortcuts helptext"]) @_localizer["Track linking"] @_localizer["Scroll to top"] @@ -106,6 +115,11 @@ along with Foobar. If not, see @_localizer["Scroll to top"] @((MarkupString)_localizer["Export of data helptext"]) + + @_localizer["Splitpoints"] + @_localizer["Scroll to top"] + + @((MarkupString)_localizer["Splitpoints helptext"]) @_localizer["Recordmode"] @_localizer["Scroll to top"] diff --git a/AudioCuesheetEditor/Pages/Index.razor b/AudioCuesheetEditor/Pages/Index.razor index 0e10b0ba..887d2a1e 100644 --- a/AudioCuesheetEditor/Pages/Index.razor +++ b/AudioCuesheetEditor/Pages/Index.razor @@ -21,7 +21,6 @@ along with Foobar. If not, see @page "/" @inject IJSRuntime _jsRuntime @inject ITextLocalizer _localizer -@inject CuesheetController _cuesheetController @inject NavigationManager _navigationManager @inject LocalStorageOptionsProvider _localStorageOptionsProvider @inject ILogger _logger @@ -40,6 +39,14 @@ along with Foobar. If not, see case ViewMode.ViewModeFull: + + + + + + + + @@ -50,7 +57,7 @@ along with Foobar. If not, see - + @@ -61,16 +68,6 @@ along with Foobar. If not, see
- - - - @_localizer["Processinghints"] - - - - - - break; case ViewMode.ViewModeImport: @@ -89,24 +86,10 @@ along with Foobar. If not, see _sessionStateContainer.CurrentViewModeChanged -= CurrentViewModeChanged; } - public IFluentDisplay DisplayProcessingHints - { - get - { - if (_sessionStateContainer.Cuesheet.GetValidationErrorsFiltered(validationErrorFilterType: ValidationErrorFilterType.All).Count > 0) - { - return Display.Always; - } - else - { - return Display.None; - } - } - } - [CascadingParameter] public MainLayout? mainLayout { get; set; } + Boolean cuesheetSplitPointsVisible = false; Boolean cuesheetDataVisible = true; Boolean cuesheetTracksVisible = true; @@ -114,7 +97,6 @@ along with Foobar. If not, see ModalDialog? modalDialog; AudioPlayer? audioPlayer; - ITextLocalizer validationMessageLocalizer = default!; protected override async Task OnInitializedAsync() { @@ -122,12 +104,11 @@ along with Foobar. If not, see _logger.LogInformation("CultureInfo.CurrentCulture = {0}", CultureInfo.CurrentCulture); _localizationService.LocalizationChanged += LocalizationService_LocalizationChanged; - validationMessageLocalizer = new TextLocalizer(_localizationService); hotKeysContext = _hotKeys.CreateContext() - .Add(ModKeys.None, Keys.Enter, OnEnterKeyDown) - .Add(ModKeys.Ctrl, Keys.Y, () => _traceChangeManager.Undo()) - .Add(ModKeys.Ctrl, Keys.Z, () => _traceChangeManager.Redo()); + .Add(Key.Enter, OnEnterKeyDown) + .Add(ModKey.Ctrl, Key.z, () => _traceChangeManager.Undo()) + .Add(ModKey.Ctrl, Key.y, () => _traceChangeManager.Redo()); _sessionStateContainer.CurrentViewModeChanged += CurrentViewModeChanged; @@ -144,7 +125,7 @@ along with Foobar. If not, see } } - async Task OnEnterKeyDown() + async ValueTask OnEnterKeyDown() { if ((modalDialog != null) && (modalDialog.Visible)) { diff --git a/AudioCuesheetEditor/Pages/ViewModeImport.razor b/AudioCuesheetEditor/Pages/ViewModeImport.razor index 03bf7a88..dcf33b2d 100644 --- a/AudioCuesheetEditor/Pages/ViewModeImport.razor +++ b/AudioCuesheetEditor/Pages/ViewModeImport.razor @@ -26,258 +26,284 @@ along with Foobar. If not, see @inject HotKeys _hotKeys @inject IJSRuntime _jsRuntime @inject HttpClient _httpClient - - - - @_localizer["Select files"] - @_localizer["Validate"] - - - - - @_localizer["Select files for import"] - -
- - - -
- @foreach (var invalidFileName in invalidDropFileNames) - { - - @_localizer["Invalid file"] - @String.Format(_localizer["You dropped an invalid file ({0}) that can not be processed."], invalidFileName) - - - } - - - - @_localizer["Textfile"] - - - - - - @foreach (var invalidFileName in invalidTextImportFileNames) - { - - @_localizer["Invalid file"] - @String.Format(_localizer["You dropped an invalid file ({0}) that can not be processed."], invalidFileName) - - - } - - - @_localizer["Cuesheet"] - - - - - - @foreach (var invalidFileName in invalidCuesheetfileNames) - { - - @_localizer["Invalid file"] - @String.Format(_localizer["You dropped an invalid file ({0}) that can not be processed."], invalidFileName) - - - } - - - @_localizer["Project filename"] - - - - - - @foreach (var invalidFileName in invalidProjectfileNames) - { - - @_localizer["Invalid file"] - @String.Format(_localizer["You dropped an invalid file ({0}) that can not be processed."], invalidFileName) - - - } -
-
-
- - - @_localizer["Validate data for import"] - - - - @_localizer["Recognition of import data finished"] - - @_localizer["Please validate the following data recognized by import assistant. Once you have validated all input, you can confirm import of data."] - - - - - - - @if (FileContentRecognized != null) +@inject ITextLocalizer _validationMessageLocalizer + + + + + @_localizer["Select files"] + @_localizer["Validate"] + + + + + @_localizer["Select files for import"] + +
+ + + +
+ @foreach (var invalidFileName in invalidDropFileNames) { - - - - - - -
-                                            @foreach(var line in FileContentRecognized)
-                                            {
-                                                if (line != null)
+                            
+                                @_localizer["Invalid file"]
+                                @String.Format(_localizer["You dropped an invalid file ({0}) that can not be processed."], invalidFileName)
+                                
+                            
+                        }
+                        
+                        
+                            
+                                @_localizer["Textfile"]
+                                
+                                    
+                                
+                            
+                        
+                        @foreach (var invalidFileName in invalidTextImportFileNames)
+                        {
+                            
+                                @_localizer["Invalid file"]
+                                @String.Format(_localizer["You dropped an invalid file ({0}) that can not be processed."], invalidFileName)
+                                
+                            
+                        }
+                        
+                            
+                                @_localizer["Cuesheet"]
+                                
+                                    
+                                
+                            
+                        
+                        @foreach (var invalidFileName in invalidCuesheetfileNames)
+                        {
+                            
+                                @_localizer["Invalid file"]
+                                @String.Format(_localizer["You dropped an invalid file ({0}) that can not be processed."], invalidFileName)
+                                
+                            
+                        }
+                        
+                            
+                                @_localizer["Project filename"]
+                                
+                                    
+                                
+                            
+                        
+                        @foreach (var invalidFileName in invalidProjectfileNames)
+                        {
+                            
+                                @_localizer["Invalid file"]
+                                @String.Format(_localizer["You dropped an invalid file ({0}) that can not be processed."], invalidFileName)
+                                
+                            
+                        }
+                    
+                
+            
+            
+                
+                    @_localizer["Validate data for import"]
+                    
+                        
+                            
+                                @_localizer["Recognition of import data finished"]
+                            
+                            @_localizer["Please validate the following data recognized by import assistant. Once you have validated all input, you can confirm import of data."]
+                            
+                                
+                                
+                            
+                        
+                        
+                            @if (FileContentRecognized != null)
+                            {
+                                
+                                    
+                                        
+                                    
+                                    
+                                        
+                                            
+                                                @foreach(var line in FileContentRecognized)
                                                 {
-                                                    @((MarkupString)String.Format("{0}
", line)) + if (line != null) + { + @((MarkupString)String.Format("{0}
", line)) + } } - } -
-
+
+
+
+
+ } + @if (_sessionStateContainer.TextImportFile != null) + { + + + + + + + + + + @_localizer["Textimportscheme cuesheet"] + + + + + + + + + + + + + + + @_localizer["Select placeholder"] + + + @foreach (var availableSchemeTrack in TextImportScheme.AvailableSchemeCuesheet) + { + @_localizer[availableSchemeTrack.Key] + } + + + + + + + + + + + + @_localizer["Textimportscheme track"] + + + + + + + + + + + + + + + @_localizer["Select placeholder"] + + + @foreach (var availableSchemeTrack in TextImportScheme.AvailableSchemesTrack) + { + @_localizer[availableSchemeTrack.Key] + } + + + + + + + + + + + + @_localizer["Customized timespan format import"] + + + + + + + + + + + + + + + @_localizer["Select placeholder"] + + + @foreach (var availableFormat in TimeSpanFormat.AvailableTimespanScheme) + { + @_localizer[availableFormat.Key] + } + + + + + + + + + + @_localizer["Reset import options"] + + + + + + + + + @if (_sessionStateContainer.TextImportFile.AnalyseException != null) + { + + + + + + + + @_localizer["Error during textimport"] : @_sessionStateContainer.TextImportFile.AnalyseException.Message + + } + } + @if (displaySplitPoints) + { + + + + + + + + + } + + + + + + - } - @if (_sessionStateContainer.TextImportFile != null) - { - + - + - - - - @_localizer["Textimportscheme cuesheet"] - - - - - - - - - - - - - @_localizer["Select placeholder"] - - - @foreach (var availableSchemeTrack in TextImportScheme.AvailableSchemeCuesheet) - { - @_localizer[availableSchemeTrack.Key] - } - - - - - - - - - - @_localizer["Textimportscheme track"] - - - - - - - - - - - - - @_localizer["Select placeholder"] - - - @foreach (var availableSchemeTrack in TextImportScheme.AvailableSchemesTrack) - { - @_localizer[availableSchemeTrack.Key] - } - - - - - - - - - - @_localizer["Customized timespan format import"] - - - - - - - - - - - - - @_localizer["Select placeholder"] - - - @foreach (var availableFormat in TimeSpanFormat.AvailableTimespanScheme) - { - @_localizer[availableFormat.Key] - } - - - - - - - - - @_localizer["Reset import options"] - - - - - - + - @if (_sessionStateContainer.TextImportFile.AnalyseException != null) - { - - - - - - - - @_localizer["Error during textimport"] : @_sessionStateContainer.TextImportFile.AnalyseException.Message - - } - } - - - - - - - - - - - - - - - - -
-
-
-
-
-
+ + + + + + + @@ -286,10 +312,12 @@ along with Foobar. If not, see String dragNDropUploadFilter = String.Join(',', TextImportfile.MimeType, Cuesheet.FileExtension, Projectfile.FileExtension); Boolean cuesheetDataVisible = true; Boolean cuesheetTracksVisible = true; + Boolean cuesheetSplitPointsVisible = true; Boolean importFileContentVisible = true; Boolean importOptionsVisible = true; Boolean selectFilesCompleted = false; Boolean userChangedSelectedStep = false; + Boolean displaySplitPoints = false; Alert? alertInvalidFile; ModalDialog? modalDialog; List invalidTextImportFileNames = new(); @@ -297,8 +325,8 @@ along with Foobar. If not, see List invalidProjectfileNames = new(); List invalidDropFileNames = new(); - ITextLocalizer validationMessageLocalizer = default!; HotKeysContext? hotKeysContext; + Validations? validations; public IReadOnlyCollection? FileContentRecognized { @@ -327,13 +355,14 @@ along with Foobar. If not, see { _logger.LogDebug("OnInitializedAsync"); - validationMessageLocalizer = new TextLocalizer(_localizationService); - _localizationService.LocalizationChanged += LocalizationService_LocalizationChanged; _sessionStateContainer.ImportCuesheetChanged += SessionStateContainer_ImportCuesheetChanged; hotKeysContext = _hotKeys.CreateContext() - .Add(ModKeys.None, Keys.Enter, OnEnterKeyDown); + .Add(Key.Enter, OnEnterKeyDown); + + TimeSpanFormat.TextLocalizer = _localizer; + TextImportScheme.TextLocalizer = _localizer; return Task.CompletedTask; } @@ -386,7 +415,7 @@ along with Foobar. If not, see if (e.Files.FirstOrDefault() != null) { var file = e.Files.First(); - if (CuesheetController.CheckFileMimeType(file, TextImportfile.MimeType, TextImportfile.FileExtension) == false) + if (IOUtility.CheckFileMimeType(file, TextImportfile.MimeType, TextImportfile.FileExtension) == false) { invalidTextImportFileNames.Add(file.Name); } @@ -404,7 +433,7 @@ along with Foobar. If not, see if (e.Files.FirstOrDefault() != null) { var file = e.Files.First(); - if (CuesheetController.CheckFileMimeType(file, Cuesheet.MimeType, Cuesheet.FileExtension) == false) + if (IOUtility.CheckFileMimeType(file, Cuesheet.MimeType, Cuesheet.FileExtension) == false) { invalidCuesheetfileNames.Add(file.Name); } @@ -422,7 +451,7 @@ along with Foobar. If not, see if (e.Files.FirstOrDefault() != null) { var file = e.Files.First(); - if (CuesheetController.CheckFileMimeType(file, Projectfile.MimeType, Projectfile.FileExtension) == false) + if (IOUtility.CheckFileMimeType(file, Projectfile.MimeType, Projectfile.FileExtension) == false) { invalidProjectfileNames.Add(file.Name); } @@ -439,10 +468,10 @@ along with Foobar. If not, see invalidDropFileNames.Clear(); foreach (var file in e.Files) { - if ((CuesheetController.CheckFileMimeType(file, Projectfile.MimeType, Projectfile.FileExtension) == false) - && (CuesheetController.CheckFileMimeType(file, Cuesheet.MimeType, Cuesheet.FileExtension) == false) - && (CuesheetController.CheckFileMimeType(file, TextImportfile.MimeType, TextImportfile.FileExtension) == false) - && (CuesheetController.CheckFileMimeType(file, Audiofile.AudioCodecs) == false)) + if ((IOUtility.CheckFileMimeType(file, Projectfile.MimeType, Projectfile.FileExtension) == false) + && (IOUtility.CheckFileMimeType(file, Cuesheet.MimeType, Cuesheet.FileExtension) == false) + && (IOUtility.CheckFileMimeType(file, TextImportfile.MimeType, TextImportfile.FileExtension) == false) + && (IOUtility.CheckFileMimeTypeForAudioCodec(file) == false)) { invalidDropFileNames.Add(file.Name); } @@ -457,9 +486,10 @@ along with Foobar. If not, see private async Task OnFileChanged(IReadOnlyCollection files) { _sessionStateContainer.ResetImport(); + displaySplitPoints = false; foreach (var file in files) { - if (CuesheetController.CheckFileMimeType(file, Projectfile.MimeType, Projectfile.FileExtension)) + if (IOUtility.CheckFileMimeType(file, Projectfile.MimeType, Projectfile.FileExtension)) { //We have a valid file here var fileContent = new MemoryStream(); @@ -471,8 +501,9 @@ along with Foobar. If not, see { _sessionStateContainer.ImportCuesheet = cuesheet; } + displaySplitPoints = true; } - if (CuesheetController.CheckFileMimeType(file, Cuesheet.MimeType, Cuesheet.FileExtension) == true) + if (IOUtility.CheckFileMimeType(file, Cuesheet.MimeType, Cuesheet.FileExtension) == true) { var options = await _localStorageOptionsProvider.GetOptions(); var stream = file.OpenReadStream(); @@ -481,20 +512,29 @@ along with Foobar. If not, see stream.Close(); _sessionStateContainer.CuesheetImportFile = new CuesheetImportfile(memoryStream, options); } - if (CuesheetController.CheckFileMimeType(file, TextImportfile.MimeType, TextImportfile.FileExtension) == true) + if (IOUtility.CheckFileMimeType(file, TextImportfile.MimeType, TextImportfile.FileExtension) == true) { var options = await _localStorageOptionsProvider.GetOptions(); var stream = file.OpenReadStream(); MemoryStream memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream); stream.Close(); + if (_sessionStateContainer.TextImportFile != null) + { + _sessionStateContainer.TextImportFile.TimeSpanFormat.ValidateablePropertyChanged -= Timespanformat_ValidateablePropertyChanged; + _sessionStateContainer.TextImportFile.TextImportScheme.ValidateablePropertyChanged -= TextImportScheme_ValidateablePropertyChanged; + } _sessionStateContainer.TextImportFile = new TextImportfile(memoryStream, options); + _sessionStateContainer.TextImportFile.TimeSpanFormat.ValidateablePropertyChanged += Timespanformat_ValidateablePropertyChanged; + _sessionStateContainer.TextImportFile.TextImportScheme.ValidateablePropertyChanged += TextImportScheme_ValidateablePropertyChanged; } - if (CuesheetController.CheckFileMimeType(file, Audiofile.AudioCodecs) == true) + if (IOUtility.CheckFileMimeTypeForAudioCodec(file) == true) { var audioFileObjectURL = await _jsRuntime.InvokeAsync("getObjectURL", "dropFileInput"); - var codec = Audiofile.AudioCodecs.Single(x => x.MimeType.Equals(file.Type, StringComparison.OrdinalIgnoreCase)); - _sessionStateContainer.ImportAudioFile = new Audiofile(file.Name, audioFileObjectURL, codec, _httpClient); + var codec = IOUtility.GetAudioCodec(file); + var audiofile = new Audiofile(file.Name, audioFileObjectURL, codec, _httpClient); + _ = audiofile.LoadContentStream(); + _sessionStateContainer.ImportAudiofile = audiofile; } } await OnSelectedStepChanged("validateData"); @@ -513,7 +553,7 @@ along with Foobar. If not, see } } - async Task OnEnterKeyDown() + async ValueTask OnEnterKeyDown() { if ((modalDialog != null) && (modalDialog.Visible)) { @@ -544,6 +584,9 @@ along with Foobar. If not, see private void LocalizationService_LocalizationChanged(object? sender, EventArgs args) { StateHasChanged(); + TimeSpanFormat.TextLocalizer = _localizer; + TextImportScheme.TextLocalizer = _localizer; + validations?.ValidateAll(); } private void SessionStateContainer_ImportCuesheetChanged(object? sender, EventArgs args) @@ -585,6 +628,8 @@ along with Foobar. If not, see var options = await _localStorageOptionsProvider.GetOptions(); if (_sessionStateContainer.TextImportFile != null) { + _sessionStateContainer.TextImportFile.TimeSpanFormat.ValidateablePropertyChanged -= Timespanformat_ValidateablePropertyChanged; + _sessionStateContainer.TextImportFile.TextImportScheme.ValidateablePropertyChanged -= TextImportScheme_ValidateablePropertyChanged; if (options.TimeSpanFormat != null) { _sessionStateContainer.TextImportFile.TimeSpanFormat = options.TimeSpanFormat; @@ -593,8 +638,30 @@ along with Foobar. If not, see { _sessionStateContainer.TextImportFile.TimeSpanFormat = new TimeSpanFormat(); } + _sessionStateContainer.TextImportFile.TimeSpanFormat.ValidateablePropertyChanged += Timespanformat_ValidateablePropertyChanged; _sessionStateContainer.TextImportFile.TextImportScheme = options.TextImportScheme; + _sessionStateContainer.TextImportFile.TextImportScheme.ValidateablePropertyChanged += TextImportScheme_ValidateablePropertyChanged; + if (validations != null) + { + validations.ValidateAll().GetAwaiter().GetResult(); + } StateHasChanged(); } } + + void TextImportScheme_ValidateablePropertyChanged(object? sender, String property) + { + if (validations != null) + { + validations.ValidateAll().GetAwaiter().GetResult(); + } + } + + void Timespanformat_ValidateablePropertyChanged(object? sender, String property) + { + if (validations != null) + { + validations.ValidateAll().GetAwaiter().GetResult(); + } + } } diff --git a/AudioCuesheetEditor/Pages/ViewModeRecord.razor b/AudioCuesheetEditor/Pages/ViewModeRecord.razor index 54f39a3d..3f9c6638 100644 --- a/AudioCuesheetEditor/Pages/ViewModeRecord.razor +++ b/AudioCuesheetEditor/Pages/ViewModeRecord.razor @@ -104,17 +104,17 @@ along with Foobar. If not, see @_localizer["Artist"] - - @if (context.Item.Disambiguation != null) - { - @String.Format("{0} ({1})", context.Text, context.Item.Disambiguation) - } - else - { - @context.Text - } - - + + @if (context.Item.Disambiguation != null) + { + @String.Format("{0} ({1})", context.Text, context.Item.Disambiguation) + } + else + { + @context.Text + } + + @@ -123,17 +123,17 @@ along with Foobar. If not, see @_localizer["Title"] - - @if (context.Item.Disambiguation != null) - { - @String.Format("{0} ({1})", context.Text, context.Item.Disambiguation) - } - else - { - @context.Text - } - - + + @if (context.Item.Disambiguation != null) + { + @String.Format("{0} ({1})", context.Text, context.Item.Disambiguation) + } + else + { + @context.Text + } + + @@ -145,20 +145,12 @@ along with Foobar. If not, see - + - - - - - - - -
@@ -170,7 +162,7 @@ along with Foobar. If not, see @if ((_sessionStateContainer.Cuesheet.Audiofile != null) && (_sessionStateContainer.Cuesheet.Audiofile.IsRecorded)) { - + } @@ -191,7 +183,9 @@ along with Foobar. If not, see public async Task AudioRecordingFinished(String url) { var options = await _localStorageOptionsProvider.GetOptions(); - _sessionStateContainer.Cuesheet.Audiofile = new Audiofile(options.AudioFileNameRecording, url, Audiofile.AudioCodecWEBM, _httpClient, true); + var audiofile = new Audiofile(options.RecordedAudiofilename, url, Audiofile.AudioCodecWEBM, _httpClient, true); + _ = audiofile.LoadContentStream(); + _sessionStateContainer.Cuesheet.Audiofile = audiofile; StateHasChanged(); } @@ -211,7 +205,6 @@ along with Foobar. If not, see Boolean recordControlVisible = true; Boolean enterNewTrackVisible = true; Boolean cuesheetTracksVisible = true; - Boolean processingHintsVisible = false; IEnumerable? autocompleteArtists; IEnumerable? autocompleteTitles; @@ -241,16 +234,12 @@ along with Foobar. If not, see } }; var options = await _localStorageOptionsProvider.GetOptions(); - - if (options.RecordCountdownTimer.HasValue) + startRecordTimer = new Timer(options.RecordCountdownTimer * 1000); + startRecordTimer.Elapsed += async delegate { - startRecordTimer = new Timer(options.RecordCountdownTimer.Value * 1000); - startRecordTimer.Elapsed += async delegate - { - await StartRecordingClicked(); - startRecordTimer.Stop(); - }; - } + await StartRecordingClicked(); + startRecordTimer.Stop(); + }; } private String GetTimespanAsString(TimeSpan? timeSpan, Boolean removeMilliseconds = false) @@ -306,8 +295,6 @@ along with Foobar. If not, see await _jsRuntime.InvokeVoidAsync("stopAudioRecording"); var options = await _localStorageOptionsProvider.GetOptions(); _sessionStateContainer.Cuesheet.StopRecording(options); - //Open processing hints - processingHintsVisible = true; } async Task OnKeyDownRecordArtist(KeyboardEventArgs args) diff --git a/AudioCuesheetEditor/Program.cs b/AudioCuesheetEditor/Program.cs index e1601da5..45bdad06 100644 --- a/AudioCuesheetEditor/Program.cs +++ b/AudioCuesheetEditor/Program.cs @@ -1,8 +1,23 @@ +//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; -using AudioCuesheetEditor.Controller; using AudioCuesheetEditor.Data.Options; using AudioCuesheetEditor.Data.Services; using AudioCuesheetEditor.Extensions; +using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Model.UI; using AudioCuesheetEditor.Model.Utility; using BlazorDownloadFile; @@ -31,17 +46,17 @@ builder.Services.AddBlazorDownloadFile(); -builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddLogging(); builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); -builder.Services.AddHotKeys(); +builder.Services.AddHotKeys2(); var host = builder.Build(); diff --git a/AudioCuesheetEditor/Resources/Localization/AudioPlayer/de.json b/AudioCuesheetEditor/Resources/Localization/AudioPlayer/de.json index 6444cfd0..f78fa531 100644 --- a/AudioCuesheetEditor/Resources/Localization/AudioPlayer/de.json +++ b/AudioCuesheetEditor/Resources/Localization/AudioPlayer/de.json @@ -2,7 +2,7 @@ "culture": "de", "translations": { "Audioplayer": "Wiedergabe", - "Set playback to begin of previous track": "Wiedergabeposition auf Begin von vorherigen Track setzen", - "Set playback to begin of next track": "Wiedergabeposition auf Begin von nächstem Track setzen" + "Set playback to begin of previous track": "Wiedergabeposition auf Begin von vorherigen Titel setzen", + "Set playback to begin of next track": "Wiedergabeposition auf Begin von nächstem Titel setzen" } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/EditSplitpoints/de.json b/AudioCuesheetEditor/Resources/Localization/EditSplitpoints/de.json new file mode 100644 index 00000000..1ade6c3a --- /dev/null +++ b/AudioCuesheetEditor/Resources/Localization/EditSplitpoints/de.json @@ -0,0 +1,16 @@ +{ + "culture": "de", + "translations": { + "Add new split point": "Neuen Aufteilungspunkt hinzufügen", + "Controls": "Steuerung", + "Moment": "Zeitpunkt", + "Delete split tooltip": "Löscht diesen Aufteilungspunkt", + "CD artist": "CD Künstler", + "CD title": "CD Titel", + "Audiofile name": "Audiodatei", + "Error": "Fehler", + "The file {0} can not be used for operation: {1}. The file is invalid, please use a valid file!": "Die Datei {0} kann nicht für folgende Operation genutzt werden: {1}. Die Datei ist ungültig, bitte verwenden Sie eine gültige Datei!", + "Audiofile": "Audiofile", + "Change": "Ändern" + } +} \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/EditSplitpoints/en.json b/AudioCuesheetEditor/Resources/Localization/EditSplitpoints/en.json new file mode 100644 index 00000000..7256528d --- /dev/null +++ b/AudioCuesheetEditor/Resources/Localization/EditSplitpoints/en.json @@ -0,0 +1,16 @@ +{ + "culture": "en", + "translations": { + "Add new split point": "Add new split point", + "Controls": "Controls", + "Moment": "Moment", + "Delete split tooltip": "Deletes this split point", + "CD artist": "CD artist", + "CD title": "CD title", + "Audiofile name": "Audiofile", + "Error": "Error", + "The file {0} can not be used for operation: {1}. The file is invalid, please use a valid file!": "The file {0} can not be used for operation: {1}. The file is invalid, please use a valid file!", + "Audiofile": "Audiofile", + "Change": "Change" + } +} \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/EditTrackModal/de.json b/AudioCuesheetEditor/Resources/Localization/EditTrackModal/de.json index 1edaa52d..98e51337 100644 --- a/AudioCuesheetEditor/Resources/Localization/EditTrackModal/de.json +++ b/AudioCuesheetEditor/Resources/Localization/EditTrackModal/de.json @@ -6,13 +6,13 @@ "Begin": "Beginn", "End": "Ende", "Length": "Länge", - "Edit track details": "Trackdetails bearbeiten", + "Edit track details": "Titeldetails bearbeiten", "Position": "Position", "Flags": "Markierungen", - "Flag4CHTooltip": "Setzen Sie diese Markierung, wenn der Track 4 Kanal Audio enthält", + "Flag4CHTooltip": "Setzen Sie diese Markierung, wenn der Titel 4 Kanal Audio enthält", "FlagDCPTooltip": "Setzen Sie diese Markierung, wenn digitale Kopien verboten sind", - "FlagPRETooltip": "Setzen Sie diese Markierung, wenn der Track Höhenanhebung hat", - "FlagSCMSTooltip": "Setzen Sie diese Markierung, wenn der Track \"Serial Copy Management System\" hat", + "FlagPRETooltip": "Setzen Sie diese Markierung, wenn der Titel Höhenanhebung hat", + "FlagSCMSTooltip": "Setzen Sie diese Markierung, wenn der Titel \"Serial Copy Management System\" hat", "PreGap": "Vorlücke", "PostGap": "Nachlücke", "Abort": "Abbrechen", diff --git a/AudioCuesheetEditor/Resources/Localization/ExportProfilesDialog/de.json b/AudioCuesheetEditor/Resources/Localization/ExportProfilesDialog/de.json deleted file mode 100644 index 079c9158..00000000 --- a/AudioCuesheetEditor/Resources/Localization/ExportProfilesDialog/de.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "culture": "de", - "translations": { - "Exportprofiles": "Export Profile", - "Select exportprofile": "Exportprofil auswählen", - "Add new exportprofile": "Neues Exportprofil hinzufügen", - "Delete selected exportprofile": "Aktuelles Exportprofil löschen", - "Name": "Name", - "Filename": "Dateiname", - "Enter exportprofile name here": "Geben Sie hier den Namen des Exportprofils an", - "Enter exportprofile filename here": "Geben Sie hier den Dateinamen für das Exportprofil an", - "Enter exportschemehead here tooltip": "Bitte geben Sie hier das Kopf Schema für den Export an. Jeder Platzhalter wird durch seinen zugehörigen Wert ersetzt. Platzhalter beginnen und enden mit %.", - "Exportprofilescheme head": "Kopfschema", - "Enter exportscheme head here": "Bitte geben Sie hier das Kopf Schema für den Export an", - "Exportprofilescheme head validationerrors": "ExportProfil Kopf Schema Validierungsfehler", - "Enter exportscheme track here tooltip": "Bitte geben Sie hier das Track Schema für den Export an. Jeder Platzhalter wird durch seinen zugehörigen Wert ersetzt. Platzhalter beginnen und enden mit %.", - "Exportprofilescheme track": "Tracksschema", - "Enter exportscheme track here": "Bitte geben Sie hier das Track Schema für den Export an", - "Exportprofilescheme track validationerrors": "ExportProfil Track Schema Validierungsfehler", - "Enter exportscheme footer here tooltip": "Bitte geben Sie hier das Fuß Schema für den Export an. Jeder Platzhalter wird durch seinen zugehörigen Wert ersetzt. Platzhalter beginnen und enden mit %.", - "Exportprofilescheme footer": "Fußschema", - "Enter exportscheme footer here": "Bitte geben Sie hier das Fuß Schema für den Export an", - "Exportprofilescheme footer validationerrors": "ExportProfil Fuß Schema Validierungsfehler", - "Exportprofile is not exportable. Please check validationerrors and solve errors in order to download export.": "Export Profil ist nicht exportierbar. Bitte prüfen Sie die Validierungsfehler und beheben Sie diese, um die Exportdatei herunterzuladen.", - "Download export": "Exportdatei herunterladen", - "Select placeholder": "Platzhalter auswählen", - "Close": "Schließen", - "Artist": "Künstler", - "Title": "Titel", - "Audiofile": "Audiodatei", - "CDTextfile": "CD Textdatei", - "Cataloguenumber": "Katalognummer", - "Date": "Datum", - "DateTime": "Datum und Uhrzeit", - "Time": "Uhrzeit", - "Position": "Position", - "Begin": "Beginn", - "End": "Ende", - "Length": "Länge", - "Flags": "Markierungen", - "PreGap": "Vorlücke", - "PostGap": "Nachlücke" - } -} \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/ExportProfilesDialog/en.json b/AudioCuesheetEditor/Resources/Localization/ExportProfilesDialog/en.json deleted file mode 100644 index 08bf992c..00000000 --- a/AudioCuesheetEditor/Resources/Localization/ExportProfilesDialog/en.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "culture": "en", - "translations": { - "Exportprofiles": "Export Profiles", - "Select exportprofile": "Select export profile", - "Add new exportprofile": "Add new exportprofile", - "Delete selected exportprofile": "Delete selected exportprofile", - "Name": "Name", - "Filename": "Filename", - "Enter exportprofile name here": "Enter name of export profile here", - "Enter exportprofile filename here": "Enter name of file for export profile here", - "Enter exportschemehead here tooltip": "Enter scheme for export of header here. Each placeholder will be replaced by the placeholder value. Placeholder start and end with %.", - "Exportprofilescheme head": "Headerscheme", - "Enter exportscheme head here": "Enter scheme for export of header here", - "Exportprofilescheme head validationerrors": "Export profile header scheme validation errors", - "Enter exportscheme track here tooltip": "Enter scheme for export of tracks here. Each placeholder will be replaced by the placeholder value. Placeholder start and end with %.", - "Exportprofilescheme track": "Trackscheme", - "Enter exportscheme track here": "Enter scheme for export of tracks here", - "Exportprofilescheme track validationerrors": "Export profile track scheme validation errors", - "Enter exportscheme footer here tooltip": "Enter scheme for export of footer here. Each placeholder will be replaced by the placeholder value. Placeholder start and end with %.", - "Exportprofilescheme footer": "Footerscheme", - "Enter exportscheme footer here": "Enter scheme for export of footer here", - "Exportprofilescheme footer validationerrors": "Export profile footer scheme validation errors", - "Exportprofile is not exportable. Please check validationerrors and solve errors in order to download export.": "Export profile is not exportable. Please check the validation errors and fix them in order to download the export.", - "Download export": "Download export file", - "Select placeholder": "Select placeholder", - "Close": "Close", - "Artist": "Artist", - "Title": "Title", - "Audiofile": "Audiofile", - "CDTextfile": "CD Textfile", - "Cataloguenumber": "Cataloguenumber", - "Date": "Date", - "DateTime": "Date and Time", - "Time": "Time", - "Position": "Position", - "Begin": "Begin", - "End": "End", - "Length": "Length", - "Flags": "Flags", - "PreGap": "Pregap", - "PostGap": "Postgap" - } -} \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/Help/de.json b/AudioCuesheetEditor/Resources/Localization/Help/de.json index 1c53adf1..077eb8b0 100644 --- a/AudioCuesheetEditor/Resources/Localization/Help/de.json +++ b/AudioCuesheetEditor/Resources/Localization/Help/de.json @@ -17,7 +17,7 @@ "Scroll to top": "Nach oben springen", "What is AudioCuesheetEditor helptext": "AudioCuesheet ist eine Web Applikation für das Schreiben von Audio Cuesheet Dateien. Audio Cuesheet Dateien sind kleine Textdateien, die Informationen über die Audiodatei bereit stellen, wie etwa z.B. Künstler, Titel, aber auch Anfang und Ende. AudioCuesheetEditor ist eine Applikation die Ihnen dabei helfen soll, gültige und korrekte Cuesheets zu erhalten. Sie können außerdem auch fertige Dateien importieren, um ihre Arbeit zu minimieren. Außerdem können Sie die gesammelten Daten auch in verschiedene, individuelle Exportformate überführen.", "Features helptext": "
  • Validierung aller Daten
  • Verschiedene Importmöglichkeiten (Text, Cuesheet, Projektdateien)
  • Verschiedene Export Möglichkeiten (XML/CSV,Text, etc)
  • Audio Wiedergabe
  • Aufnahmemodus
  • Rückgängig/Wiederherstellen von Änderungen
  • Massenänderungen
", - "Validation helptext": "AudioCuesheetEditor arbeitet mit einem validiertem Datenmodell und prüft jede Eingabe. Es gibt folgende Validierungstypen:
Warnung
Warnungen sind Meldungen, die eventuell für Sie wichtig sind, jedoch keine Funktionen blockieren.
Fehler
Fehler sind Meldungen, die unbedingt behoben werden müssen (wie etwa Tracks mit ungültigen Zeiten).
Validierungsmeldungen werden auf der rechten Seite angezeigt und verweisen auf die Datenfelder.
Jede Validierungsmeldung kann angeklickt werden, um zu dem entsprechenden Feld zu springen und das Feld zu korrigieren.
", + "Validation helptext": "AudioCuesheetEditor arbeitet mit einem validiertem Datenmodell und prüft jede Eingabe. In den meisten Fällen werden Validierungsfehler neben den Eingabefeldern angezeigt, um Ihnen eine klare Information zu ermöglichen.", "Track linking helptext": "Es ist möglich einen Track mit dem vorherigen Track zu koppeln. Dies stellt sicher, dass Änderungen an Beginn, Ende und Position automatisch in den anschließenden Track übertragen werden.
Wenn ein Track mit dem vorherigen Track gekoppelt ist, können Sie beispielsweise das Ende verändern und ihre Änderung wird automatisch in den nachfolgenden Track bei Beginn eingetragen. Wenn Sie die Kopplung aufheben möchten, klicken sie einfach auf den orangfarbenenen \"Abkoppeln\" Button. Wenn Sie anschließend die Kopplung wiederherstellen möchten, klicken Sie einfach auf den grünen \"Koppeln\" Button.", "Create cuesheet helptext": "Cuesheets können sehr einfach und schnell mit der vorhandenen Eingabemaske erstellt werden. Dafür füllen Sie einfach CD Künstler und CD Titel in den vorhandenen Feldern aus. Wählen Sie bitte außerdem eine Audio Datei aus indem Sie eine Datei aus ihrem Datei System auf die Ablagefläche ziehen oder die Datei manuell mit dem Auswahldialog auswählen.
Anschließend können Sie einen Track hinzufügen indem Sie den \"Neuen Track hinzufügen\" Button klicken. Ein neuer Track wird angezeigt und sie können seine Eigenschaften wie Künstler, Titel, Beginn, Ende oder Länge bearbeiten. Bitte beachten Sie, dass der Beginn jedes Tracks von seinem vorherigen Ende automatisch berechnet wird. Daher ist es ratsam, einen weiteren Track erst dann hinzuzufügen, wenn alle Daten ausgefüllt sind.
Wenn Sie alle Tracks hinzugefügt haben, die in der Audio Datei vorhanden sind und alle notwendigen Felder ausgefüllt haben, können Sie das Cuesheet herunterladen indem Sie auf \"Export\" klicken und dann auf \"Cuesheet herunterladen\". Außerdem können Sie die Daten auch in anderen Formaten herunterladen, iden Sie auf \"Export\" und dann auf \"Export Profile anzeigen\" klicken.
", "Import of textfiles helptext": "Sie können Textdatein importieren und den Inhalt zeilenweise mit regulären Ausdrücken auswerten. Eine einfache Beispiel Textdatei kann hier gefunden werden.
Jede Zeile repräsentiert einen Track, die erste Zeile jedoch Cuesheet Details und jedes Detail kann aus einer Zeile ausgelesen werden. Die erste Information ist der Track Künstler gefolgt von \" - \" und dem Track Titel. Dann kommen ein paar Tabs und Informationen über das Track Ende. So benötigen wir in diesem Beispiel den Regulären Ausdruck \"(?'Track.Artist'[a-zA-Z0-9_ .();äöü&:,]{1,}) - (?'Track.Title'[a-zA-Z0-9_ .();äöü]{1,})\t{1,}(?'Track.End'.{1,})\" für Tracks und \"(?'Cuesheet.Artist'\\A.*) - (?'Cuesheet.Title'\\w{1,})\t{1,}(?'Cuesheet.Audiofile'.{1,})\" für Cuesheet Details. Jedes Feld kann mit \"Entität.Feldname\" erreicht werden. Sie können den Textimport einfach starten, indem Sie in den Anzeigemodus \"Import Assistent\" wechseln und dort eine Datei aus dem Dateisystem auf die Ablagefläche zieht oder sie manuell über den Dateiauswahldialog auswählt.
Der Assistent wechselt nach der Analyse der Daten in die Detailansicht und sie können den Textimport verändern und so anpassen, dass er zu ihrer Textdatei passt und alle Informationen erfasst werden können.
Änderungen am Import Textschema verändern die Vorschau und spiegeln das Ergebnis wieder.
Sie können alle gültigen Platzhalter eines Tracks erreichen, indem Sie \"Platzhalter auswählen\" anklicken und dann den Platzhalter den Sie benötigen.
Wenn Sie ein ungültiges Import Textschema angeben, wird ihnen ein Validierungsfehler angezeigt.
Geben Sie ein Schema an, dass nicht zur Textdatei passt, wird ihnen ein Fehler angezeigt.
Fehler blockieren immer den Import und müssen korrigiert werden.
Wenn Sie nur Tracks importieren möchten, lassen Sie das Schema für Cuesheet leer und geben Sie eine Textdatei an, die nur Trackdetails enthält. Ein Beispiel kann hier gefunden werden.
Der Import kann nach dem gleichem Muster stattfinden.
", @@ -27,6 +27,10 @@ "Export of data": "Exportmöglichkeiten", "Export of data helptext": "Es ist sehr einfach in AudioCuesheetEditor alle Eingaben zu exportieren. Sie können den Export selbst anpassen und selbstständig eigene Exportprofile anlegen. Wenn Sie ein Cuesheet exportieren möchen, klicken sie einfach auf \"Export Profile anzeigen\" im oberen Bereich im Menü \"Export\".
Es öffnet sich der Exportprofil Dialog bei dem Sie jederzeit zwischen den verschiedenen Exportprofilen wechseln können, sowie alte löschen und neue anlegen. Jede Änderung wird automatisch gespeichert. Sie können einen Namen vergeben, einen Dateinamen für den Download, sowie die Schema für Kopf, Fuß und Tracks verändern.
Alle gültigen Platzhalter können eingefügt werden, indem man sie manuell tippt oder den Button \"Platzhalter auswählen\" und den Platzhalter anklickt den man hinzufügen möchte. Sie können außerdem Text zwischen den Platzhaltern angeben, der nicht ersetzt wird.
Sobald sie das Exportprofil benutzen möchten, klicken sie einfach auf den Button \"Exportdatei herunterladen\" und der Export wird vorgenommen.", "User Interface": "Benutzeroberfläche", - "User Interface helptext": "

Automatische Vervollständigung

AudioCuesheetEditor bietet ihnen die Möglichkeit die Daten von Tracks automatisch vervollständigen zu lassen. Die verwendete Musikdatenbank ist MusicBrainz. Dazu geben Sie einen Track ein und erhalten hier bereits Vorschläge.
Wählen Sie einen gültigen Interpreten aus, so werden Ihnen beim Eingeben des Titels passende Vorschläge gemacht.
Wenn sie einen Titel ausgewählt haben, wird die Länge automatisch übernommen.

Massenänderungen

AudioCuesheetEditor bietet ihnen die Möglichkeit mehrere Tracks gleichzeit zu bearbeiten. Wählen Sie dazu einfach mehrere Tracks in der Übersicht aus (über \"Auswahl für Tracks einblenden\") und klicken auf \"Selektierte Tracks bearbeiten\".
Der Bearbeitungsdialog öffnet sich und sie können wählen, ob die Bearbeitung dynamisch angewendet werden soll (Wert erhöhen oder verringern) oder der eingebene Wert absolut verwendet werden soll (für einige Eigenschaften). Dabei bedeutet \"=\" dass der absolute Wert übernommen werden soll, \"-\" dass der Wert abgezogen werden soll und \"+\" dass der Wert addiert werden soll.
" + "User Interface helptext": "

Automatische Vervollständigung

AudioCuesheetEditor bietet ihnen die Möglichkeit die Daten von Tracks automatisch vervollständigen zu lassen. Die verwendete Musikdatenbank ist MusicBrainz. Dazu geben Sie einen Track ein und erhalten hier bereits Vorschläge.
Wählen Sie einen gültigen Interpreten aus, so werden Ihnen beim Eingeben des Titels passende Vorschläge gemacht.
Wenn sie einen Titel ausgewählt haben, wird die Länge automatisch übernommen.

Massenänderungen

AudioCuesheetEditor bietet ihnen die Möglichkeit mehrere Tracks gleichzeit zu bearbeiten. Wählen Sie dazu einfach mehrere Tracks in der Übersicht aus (über \"Auswahl für Tracks einblenden\") und klicken auf \"Selektierte Tracks bearbeiten\".
Der Bearbeitungsdialog öffnet sich und sie können wählen, ob die Bearbeitung dynamisch angewendet werden soll (Wert erhöhen oder verringern) oder der eingebene Wert absolut verwendet werden soll (für einige Eigenschaften). Dabei bedeutet \"=\" dass der absolute Wert übernommen werden soll, \"-\" dass der Wert abgezogen werden soll und \"+\" dass der Wert addiert werden soll.
", + "Shortcuts": "Tastenkürzel", + "Shortcuts helptext": "Die folgenden Tastenkürzeln sind verfügbar:
  • Strg + p: Startet oder pausiert die Audiowiedergabe
  • Play: Startet oder pausiert die Audiowiedergabe
  • Strg + Left: Vorherigen Track wiedergeben
  • Previous: Vorherigen Track wiedergeben
  • Strg + Right: Nächsten Track wiedergeben
  • Next: Nächsten Track wiedergeben
  • Stop: Stoppt die Audiowiedergabe
  • Strg + z: Letzte Änderung rückgängig machen
  • Strg + y: Letzte Änderung wiederherstellen
  • Strg + h: Navigiere zu dieser Hilfe
  • Strg + e: Öffne die Exportprofile
  • Strg + s: Speicher das Projekt (Download als Datei)
", + "Splitpoints": "Aufteilungspunkte", + "Splitpoints helptext": "
Aufteilungspunkte ermöglichen es ein Cuesheet an einem Zeitpunkt in mehrere Dateien aufzuteilen. Dies ist z.B. beim Import aus einer Textdatei hilfreich, um das Projekt in mehrere Dateien aufzuteilen. Aufteilungspunkte können im Anzeigemodus \"Komplette Bearbeitung\" und \"Aufnahmemodus\" angegeben werden." } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/Help/en.json b/AudioCuesheetEditor/Resources/Localization/Help/en.json index 3c0b337b..9566f41d 100644 --- a/AudioCuesheetEditor/Resources/Localization/Help/en.json +++ b/AudioCuesheetEditor/Resources/Localization/Help/en.json @@ -17,7 +17,7 @@ "Scroll to top": "Scroll to top", "What is AudioCuesheetEditor helptext": "AudioCuesheet is a web application for writting audio cuesheet files. Audio cuesheet files are little text files which provide information about the provided audio file like for example artist, name, but also start and end. AudioCuesheetEditor is an application that helps you writting valid cuesheet files by checking and validation input data. You can also import data from files to minimize your work. Exporting data to individual formats is also possible.", "Features helptext": "
  • Validation for all data
  • Several import options (Text, Cuesheet, Projectfiles)
  • Several export profiles (XML/CSV,Text, etc)
  • Audio playback
  • Live record mode
  • Undo/Redo changes
  • Bulk edit
", - "Validation helptext": "AudioCuesheetEditor works with a validation model and evaluates every input. Possible types of validation are:
Warning
Warnings are validationmessages, that might be important for you, but don't block functions.
Error
Errors are validationmessages that need to be corrected (like tracks with invalid times).
Validation messages are displayed on the right with link to the corresponding field.
Each message can be clicked to jump to the corresponding field and correct the field.
", + "Validation helptext": "AudioCuesheetEditor works with a validation model and evaluates every input. In most cases the validation messages are presented next to the input, giving you a clear instruction what is missing.", "Track linking helptext": "It is possible to link a track with the previous track. This makes shure, that changes to Begin,End and Position are automatically transfered to the next following track. If a track is linked to the previous track, you can change for example the End and the Begin of the following linked track is automatically replaced with the End you entered. If you wish to disable linking, just click on the orange \"Unlink\" Button. If you afterwards want to link the tracks again, just hit the green \"Link\" Button.", "Create cuesheet helptext": "Creation of Cuesheet can be done very easily and fast using the standard GUI without import of files. To do so, enter the CD artist and CD title in the appropriate fields. Also select an audio file on your computer by draging the file from file explorer to the drop zone or manually selecting the file via file chooser.
Afterwards you need to insert a track by clickin the \"Add new track\" button. A new track is added and you can edit its properties like Artist, Title or Begin, End or Length. Please keep in mind, that every tracks begin gets calculated from the end of the previous track. Therefore its adviced to edit one track and fill its data before adding a new one.
When you have added all tracks from the audio file and filled all necessary fields, you can export the cuesheet in the top by clickin \"Export\" and then \"Download cuesheet\". You can also export the result in other formats like XML, CSV or text by clicking \"Export\" and then \"Display Export Profiles\".
", "Import of textfiles helptext": "You can import plain text files and analyse the content of each line by simple regular expressions. A simple sample textfile can be found here.
Each line represents a track, but the first line represents cuesheet details and every detail can be extracted from the line. First information that can be found is the track artist, followed by \" - \" and then track title. Afterwards some tabs come and you can see information about the track end time. So in this example we can work with the regular expression for tracks \"%?'Track.Artist'[a-zA-Z0-9_ .();äöü&:,]{1,}) - (?'Track.Title'[a-zA-Z0-9_ .();äöü]{1,})\t{1,}(?'Track.End'.{1,})\" and with \"(?'Cuesheet.Artist'\\A.*) - (?'Cuesheet.Title'\\w{1,})\t{1,}(?'Cuesheet.Audiofile'.{1,})\" for cuesheet data. Each field can be accessed via \"Entity.Fieldname\".You can start importing a textfile by selecting viewmode \"Import asssistant\" and drag the file from filesystem to the dropzone or by manually selecting it from file chooser dialog.
Changing the import textscheme will result in changes of the import result and the data to import will be shown to you.
You can select the valid fields by pressing the button \"Select placeholder\" and afterwards selecting the placeholder you want to add.
If you enter an invalid text import scheme, a validation error will be displayed.
If the import scheme doesn't match the text file, an error will be displayed to you.
Errors will always block import of text, so you have to correct them at first before import can take place.
If you want to import a tracks only textfile, just leave the cuesheet scheme empty and add a textfile with track properties only. A sample can be found here.
Import can be done the same way.
", @@ -27,6 +27,10 @@ "Export of data": "Export of data", "Export of data helptext": "Exporting all entered data is really easy in AudioCuesheetEditor. You can customize the export yourself and also can add new export profiles by yourself. Once you have a cuesheet you want to export, just click on the \"Display Export Profiles\" Button in the top menu bar inside \"Export\".
The export profiles dialog comes up, where you can easily switch between the current export profiles, delete and add new export profiles. Each change to export profile will get saved automatically. You can enter a name for the profile, a filename for download and the scheme for header, footer and tracks.
All valid placeholders can be added by manually typing them or selecting the \"Select placeholder\" button and click on the placeholder you want to add. You can also add text between placeholders that will not get touched by the export logic.
Once you want to use the export profile, just click on the \"Download export file\" button and the export will be generated for you.", "User Interface": "User interface", - "User Interface helptext": "

Autocomplete

AudioCuesheetEditor has an build autocomplete feature for tracks. The data used come from MusicBrainz. Adding a Track and enter an artist to receive autocomplete suggestions.
Select a valid artist and you will get suggestions for title also.
If you select a title, the length will automatically be set.

Bulk edit

AudioCuesheetEditor offers you the possibility to edit multiple tracks at once. Simply choose the tracks you want to edit in overview (via \"Display selection of tracks\") and click \"Edit selected tracks\".
Edit dialog will appear and you can choose if you want to edit values dynamically (add or subtract value) or statically (entered value will be applied). \"=\" means that the absolute value is applied, \"-\" means that the value is subtracted and \"+\" means that the value is added.
" + "User Interface helptext": "

Autocomplete

AudioCuesheetEditor has an build autocomplete feature for tracks. The data used come from MusicBrainz. Adding a Track and enter an artist to receive autocomplete suggestions.
Select a valid artist and you will get suggestions for title also.
If you select a title, the length will automatically be set.

Bulk edit

AudioCuesheetEditor offers you the possibility to edit multiple tracks at once. Simply choose the tracks you want to edit in overview (via \"Display selection of tracks\") and click \"Edit selected tracks\".
Edit dialog will appear and you can choose if you want to edit values dynamically (add or subtract value) or statically (entered value will be applied). \"=\" means that the absolute value is applied, \"-\" means that the value is subtracted and \"+\" means that the value is added.
", + "Shortcuts": "Shortcuts", + "Shortcuts helptext": "The following shortcuts are available:
  • Ctrl + p: Starts or pauses playback of the audio file
  • Play: Starts or pauses playback of the audio file
  • Ctrl + Left: Play previous track
  • Previous: Play previous track
  • Ctrl + Right: Play next track
  • Next: Play next track
  • Stop: Stops the playback of the audio file
  • Ctrl + z: Undo the last edit
  • Ctrl + y: Redo the last edit
  • Ctrl + h: Navigate to this help
  • Ctrl + e: Open the export profiles
  • Ctrl + s: Save the project (download as project file)
", + "Splitpoints": "Splitpoints", + "Splitpoints helptext": "
Split points allow a cuesheet to be split into multiple files at a time. This is useful, for example, when importing from a text file to split the project into several files. Split points can be specified in \"Full edit mode\" and \"Live record mode\" view modes." } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/Index/de.json b/AudioCuesheetEditor/Resources/Localization/Index/de.json index 2adfb745..32530bad 100644 --- a/AudioCuesheetEditor/Resources/Localization/Index/de.json +++ b/AudioCuesheetEditor/Resources/Localization/Index/de.json @@ -2,28 +2,27 @@ "culture": "de", "translations": { "Cuesheet data": "Cuesheet Daten", - "Cuesheet tracks": "Cuesheet Tracks", - "Unlink this track from previous track": "Diesen Track vom vorherigen Track abkoppeln", - "Link this track with previous track": "Diesen Track an den vorherigen Track koppeln", - "Current track is played": "Dieser Track wird aktuell wiedergegeben", - "Edit track tooltip": "Trackdetails in einem Dialog bearbeiten", - "Copy track tooltip": "Kopiere diesen Track mit allen Werten und füge ihn anschließend zum Cuesheet hinzu.", - "Start playback this track": "Diesen Track wiedergeben", - "Move track up tooltip": "Diesen Track eine Position nach oben schieben", - "Delete track tooltip": "Diesen Track löschen", - "Move track down tooltip": "Diesen Track eine Position nach unten schieben", - "Add new track": "Neuen Track hinzufügen", - "Processinghints": "Bearbeitungshinweise", + "Tracks": "Titel", + "Unlink this track from previous track": "Diesen Titel vom vorherigen Titel abkoppeln", + "Link this track with previous track": "Diesen Titel an den vorherigen Titel koppeln", + "Current track is played": "Dieser Titel wird aktuell wiedergegeben", + "Edit track tooltip": "Titeldetails in einem Dialog bearbeiten", + "Copy track tooltip": "Kopiere diesen Titel mit allen Werten und füge ihn anschließend zum Cuesheet hinzu.", + "Start playback this track": "Diesen Titel wiedergeben", + "Move track up tooltip": "Diesen Titel eine Position nach oben schieben", + "Delete track tooltip": "Diesen Titel löschen", + "Move track down tooltip": "Diesen Titel eine Position nach unten schieben", + "Add new track": "Neuen Titel hinzufügen", "Abort": "Abbrechen", "Name": "Name", "Filename": "Dateiname", "Close": "Schließen", - "Edit track details": "Trackdetails bearbeiten", + "Edit track details": "Titeldetails bearbeiten", "Position": "Position", - "Flag4CHTooltip": "Setzen Sie diese Markierung, wenn der Track 4 Kanal Audio enthält", + "Flag4CHTooltip": "Setzen Sie diese Markierung, wenn der Titel 4 Kanal Audio enthält", "FlagDCPTooltip": "Setzen Sie diese Markierung, wenn digitale Kopien verboten sind", - "FlagPRETooltip": "Setzen Sie diese Markierung, wenn der Track Höhenanhebung hat", - "FlagSCMSTooltip": "Setzen Sie diese Markierung, wenn der Track \"Serial Copy Management System\" hat", + "FlagPRETooltip": "Setzen Sie diese Markierung, wenn der Titel Höhenanhebung hat", + "FlagSCMSTooltip": "Setzen Sie diese Markierung, wenn der Titel \"Serial Copy Management System\" hat", "PreGap": "Vorlücke", "PostGap": "Nachlücke", "The file {0} can not be used for operation: {1}. The file is invalid, please use a valid file!": "Die Datei \"{0}\" kann nicht für die angeforderte Operation verwendet werden: \"{1}\". Bitte nutzen Sie eine gültige, passende Datei!", @@ -36,14 +35,15 @@ "Enter cd title here": "CD Titel hier eingeben", "Enter cataloguenumber here": "Geben Sie hier die Katalognummer des Cuesheets an. Die Katalognummer ist eine 13 stellige Zahl.", "Here all validation messages are displayed. Each message contains a reference to the corresponding field and can clicked to enter the field.": "Hier werden alle Validierungsnachrichten angezeigt. Jede Nachricht enthält eine Referenz auf das ursprüngliche Feld und kann daher angeklickt werden, um zu dem entsprechenden Feld zu gelangen.", - "Display selection of tracks": "Auswahl für Tracks einblenden", + "Display selection of tracks": "Auswahl für Titel einblenden", "Selection": "Auswahl", - "Select this track for multiple track operations": "Diesen Track in Mehrfachauswahl übernehmen", - "Delete selected tracks": "Ausgewählte Tracks löschen", - "Hide selection of tracks": "Auswahl für Tracks ausblenden", + "Select this track for multiple track operations": "Diesen Titel in Mehrfachauswahl übernehmen", + "Delete selected tracks": "Ausgewählte Titel löschen", + "Hide selection of tracks": "Auswahl für Titel ausblenden", "Date": "Datum", "DateTime": "Datum und Uhrzeit", "Time": "Uhrzeit", - "Save changes": "Änderungen speichern" + "Save changes": "Änderungen speichern", + "Cuesheet split points": "Cuesheet Aufteilungspunkte" } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/Index/en.json b/AudioCuesheetEditor/Resources/Localization/Index/en.json index 301a52a0..494ed278 100644 --- a/AudioCuesheetEditor/Resources/Localization/Index/en.json +++ b/AudioCuesheetEditor/Resources/Localization/Index/en.json @@ -2,7 +2,7 @@ "culture": "en", "translations": { "Cuesheet data": "Cuesheet Data", - "Cuesheet tracks": "Cuesheet Tracks", + "Tracks": "Tracks", "Unlink this track from previous track": "Unlink this track from previous track", "Link this track with previous track": "Link this track with previous track", "Current track is played": "Current track is played", @@ -13,7 +13,6 @@ "Delete track tooltip": "Delete this track", "Move track down tooltip": "Move this track one position down", "Add new track": "Add new track", - "Processinghints": "Processinghints", "Abort": "Abort", "Name": "Name", "Filename": "Filename", @@ -44,6 +43,7 @@ "Date": "Date", "DateTime": "Date and Time", "Time": "Time", - "Save changes": "Save changes" + "Save changes": "Save changes", + "Cuesheet split points": "Cuesheet split points" } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/MainLayout/de.json b/AudioCuesheetEditor/Resources/Localization/MainLayout/de.json index 5af3f437..bec6bc09 100644 --- a/AudioCuesheetEditor/Resources/Localization/MainLayout/de.json +++ b/AudioCuesheetEditor/Resources/Localization/MainLayout/de.json @@ -8,13 +8,13 @@ "Undo last change": "Letzte Änderung Rückgängig machen", "Redo last change": "Letzte Änderung wiederherstellen", "Export": "Export", - "Please check processinghints for errors, otherwise the file is not exportable": "Bitte prüfen sie die Bearbeitungshinweise auf Fehler. Andernfalls ist Datei nicht exportierbar.", + "Please check processinghints for errors, otherwise the file is not exportable: {0}": "Bitte prüfen sie die Bearbeitungshinweise auf Fehler. Andernfalls ist Datei nicht exportierbar: {0}", "Download cuesheet": "Cuesheet herunterladen", "Save project": "Projekt speichern", "Open exportprofiles": "Export Profile anzeigen", "Select ViewMode": "Anzeigemodus auswählen", "Reset": "Zurücksetzen", - "Delete all tracks": "Alle Tracks löschen", + "Delete all tracks": "Alle Titel löschen", "Reset cuesheet": "Cuesheet zurücksetzen", "Restart complete application": "Komplette Anwendung neustarten", "Reset complete application": "Komplette Anwendung zurücksetzen", @@ -23,13 +23,52 @@ "Abort": "Abbrechen", "Confirmation required": "Bestätigung erforderlich", "Do you really want to reset the cuesheet? This can not be reversed.": "Möchten Sie wirklich das Cuesheet zurücksetzen? Dies kann nicht rückgangig gemacht werden.", - "Do you really want to delete all tracks? This can not be reversed.": "Möchten Sie wirklich alle Tracks löschen? Dies kann nicht rückgängig gemacht werden.", + "Do you really want to delete all tracks? This can not be reversed.": "Möchten Sie wirklich alle Titel löschen? Dies kann nicht rückgängig gemacht werden.", "Confirm restart of application. All unsaved changes are lost!": "Bitte Bestätigen Sie, dass die komplette Anwendung neugestartet werden soll. Alle ungespeicherten Änderungen werden dadurch verloren!", "Confirm reset of application. All unsaved changes are lost and the application is reloaded!": "Bitte Bestätigen Sie, dass die komplette Anwendung auf Werkseinstellungen zurückgesetzt werden soll. Alle Daten werden gelöscht und die Anwedung neu geladen. Alle nicht gespeicherten Daten sind gelöscht!", "ViewModeFull": "Komplette Bearbeitung", "ViewModeRecord": "Aufnahmemodus", "ViewModeImport": "Import Assistent", "Undo": "Rückgängig", - "Redo": "Wiederherstellen" + "Redo": "Wiederherstellen", + "Tools": "Werkzeuge", + "Split points": "Aufteilungspunkte", + "Exportprofiles": "Export Profile", + "Select exportprofile": "Exportprofil auswählen", + "Add new exportprofile": "Neues Exportprofil hinzufügen", + "Delete selected exportprofile": "Aktuelles Exportprofil löschen", + "Name": "Name", + "Enter exportprofile name here": "Geben Sie hier den Namen des Exportprofils an", + "Enter exportprofile filename here": "Geben Sie hier den Dateinamen für das Exportprofil an", + "Enter exportschemehead here tooltip": "Bitte geben Sie hier das Kopf Schema für den Export an. Jeder Platzhalter wird durch seinen zugehörigen Wert ersetzt. Platzhalter beginnen und enden mit %.", + "Exportprofilescheme head": "Kopfschema", + "Enter exportscheme head here": "Bitte geben Sie hier das Kopf Schema für den Export an", + "Exportprofilescheme head validationerrors": "ExportProfil Kopf Schema Validierungsfehler", + "Enter exportscheme track here tooltip": "Bitte geben Sie hier das Titel Schema für den Export an. Jeder Platzhalter wird durch seinen zugehörigen Wert ersetzt. Platzhalter beginnen und enden mit %.", + "Exportprofilescheme track": "Titelschema", + "Enter exportscheme track here": "Bitte geben Sie hier das Titel Schema für den Export an", + "Exportprofilescheme track validationerrors": "ExportProfil Titel Schema Validierungsfehler", + "Enter exportscheme footer here tooltip": "Bitte geben Sie hier das Fuß Schema für den Export an. Jeder Platzhalter wird durch seinen zugehörigen Wert ersetzt. Platzhalter beginnen und enden mit %.", + "Exportprofilescheme footer": "Fußschema", + "Enter exportscheme footer here": "Bitte geben Sie hier das Fuß Schema für den Export an", + "Exportprofilescheme footer validationerrors": "ExportProfil Fuß Schema Validierungsfehler", + "Exportprofile is not exportable. Please check validationerrors and solve errors in order to download export.": "Export Profil ist nicht exportierbar. Bitte prüfen Sie die Validierungsfehler und beheben Sie diese, um die Exportdatei herunterzuladen.", + "Download export": "Exportdatei herunterladen", + "Select placeholder": "Platzhalter auswählen", + "Artist": "Künstler", + "Title": "Titel", + "Audiofile": "Audiodatei", + "CDTextfile": "CD Textdatei", + "Cataloguenumber": "Katalognummer", + "Date": "Datum", + "DateTime": "Datum und Uhrzeit", + "Time": "Uhrzeit", + "Position": "Position", + "Begin": "Beginn", + "End": "Ende", + "Length": "Länge", + "Flags": "Markierungen", + "PreGap": "Vorlücke", + "PostGap": "Nachlücke" } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/MainLayout/en.json b/AudioCuesheetEditor/Resources/Localization/MainLayout/en.json index 475d3c5e..a3788458 100644 --- a/AudioCuesheetEditor/Resources/Localization/MainLayout/en.json +++ b/AudioCuesheetEditor/Resources/Localization/MainLayout/en.json @@ -8,7 +8,7 @@ "Undo last change": "Undo last change", "Redo last change": "Redo last change", "Export": "Export", - "Please check processinghints for errors, otherwise the file is not exportable": "Please check processing hints for errors. Otherwise the file is not exportable.", + "Please check processinghints for errors, otherwise the file is not exportable: {0}": "Please check processinghints for errors, otherwise the file is not exportable: {0}", "Download cuesheet": "Download cuesheet", "Save project": "Save project", "Open exportprofiles": "Display Export Profiles", @@ -30,6 +30,45 @@ "ViewModeRecord": "Live record mode", "ViewModeImport": "Import assistant mode", "Undo": "Undo", - "Redo": "Redo" + "Redo": "Redo", + "Tools": "Tools", + "Split points": "Split points", + "Exportprofiles": "Export Profiles", + "Select exportprofile": "Select export profile", + "Add new exportprofile": "Add new exportprofile", + "Delete selected exportprofile": "Delete selected exportprofile", + "Name": "Name", + "Enter exportprofile name here": "Enter name of export profile here", + "Enter exportprofile filename here": "Enter name of file for export profile here", + "Enter exportschemehead here tooltip": "Enter scheme for export of header here. Each placeholder will be replaced by the placeholder value. Placeholder start and end with %.", + "Exportprofilescheme head": "Headerscheme", + "Enter exportscheme head here": "Enter scheme for export of header here", + "Exportprofilescheme head validationerrors": "Export profile header scheme validation errors", + "Enter exportscheme track here tooltip": "Enter scheme for export of tracks here. Each placeholder will be replaced by the placeholder value. Placeholder start and end with %.", + "Exportprofilescheme track": "Trackscheme", + "Enter exportscheme track here": "Enter scheme for export of tracks here", + "Exportprofilescheme track validationerrors": "Export profile track scheme validation errors", + "Enter exportscheme footer here tooltip": "Enter scheme for export of footer here. Each placeholder will be replaced by the placeholder value. Placeholder start and end with %.", + "Exportprofilescheme footer": "Footerscheme", + "Enter exportscheme footer here": "Enter scheme for export of footer here", + "Exportprofilescheme footer validationerrors": "Export profile footer scheme validation errors", + "Exportprofile is not exportable. Please check validationerrors and solve errors in order to download export.": "Export profile is not exportable. Please check the validation errors and fix them in order to download the export.", + "Download export": "Download export file", + "Select placeholder": "Select placeholder", + "Artist": "Artist", + "Title": "Title", + "Audiofile": "Audiofile", + "CDTextfile": "CD Textfile", + "Cataloguenumber": "Cataloguenumber", + "Date": "Date", + "DateTime": "Date and Time", + "Time": "Time", + "Position": "Position", + "Begin": "Begin", + "End": "End", + "Length": "Length", + "Flags": "Flags", + "PreGap": "Pregap", + "PostGap": "Postgap" } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/ModalExportdialog/de.json b/AudioCuesheetEditor/Resources/Localization/ModalExportdialog/de.json new file mode 100644 index 00000000..2709c8c3 --- /dev/null +++ b/AudioCuesheetEditor/Resources/Localization/ModalExportdialog/de.json @@ -0,0 +1,18 @@ +{ + "culture": "de", + "translations": { + "Prepare export": "Export vorbereiten", + "Result": "Ergebnis", + "Name": "Name", + "Begin": "Anfang", + "End": "Ende", + "Content": "Inhalt", + "Download this file": "Diese Datei herunterladen", + "Generate export files": "Exportdateien generieren", + "Abort": "Abbrechen", + "Export files can not be generated. Please check validationerrors and solve errors in order to download export: {0}": "Exportdateien können nicht generiert werden. Bitte prüfen Sie die Validierungsfehler und beheben Sie diese, um die Exportdateien herunterladen zu können: {0}", + "Close": "Schließen", + "Download split audio file": "Verarbeitete Audiodatei herunterladen", + "Generation in progress, please stand by ...": "Export wird durchgeführt, bitte warten Sie ...." + } +} \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/ModalExportdialog/en.json b/AudioCuesheetEditor/Resources/Localization/ModalExportdialog/en.json new file mode 100644 index 00000000..973e4b8a --- /dev/null +++ b/AudioCuesheetEditor/Resources/Localization/ModalExportdialog/en.json @@ -0,0 +1,18 @@ +{ + "culture": "en", + "translations": { + "Prepare export": "Prepare export", + "Result": "Result", + "Name": "Name", + "Begin": "Begin", + "End": "End", + "Content": "Content", + "Download this file": "Download this file", + "Generate export files": "Generate export files", + "Abort": "Abort", + "Export files can not be generated. Please check validationerrors and solve errors in order to download export: {0}": "Export files can not be generated. Please check validationerrors and solve errors in order to download export: {0}", + "Close": "Close", + "Download split audio file": "Download split audio file", + "Generation in progress, please stand by ...": "Generation in progress, please stand by ..." + } +} \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/OptionsDialog/de.json b/AudioCuesheetEditor/Resources/Localization/OptionsDialog/de.json index 930ecc13..1931284a 100644 --- a/AudioCuesheetEditor/Resources/Localization/OptionsDialog/de.json +++ b/AudioCuesheetEditor/Resources/Localization/OptionsDialog/de.json @@ -11,11 +11,11 @@ "ViewModeImport": "Import Assistent", "Cuesheet filename": "Cuesheet Dateiname", "Project filename": "Projektdateiname", - "Automatically link tracks": "Tracks standardmäßig koppeln", - "Automatically link tracks with previous": "Tracks standardmäßig an den vorherigen Track koppeln", + "Automatically link tracks": "Titel standardmäßig koppeln", + "Automatically link tracks with previous": "Titel standardmäßig an den vorherigen Titel koppeln", "Textimportscheme cuesheet": "Textschema für den Import von Cuesheeteigenschaften", - "Textimportscheme track": "Textschema für den Import von Trackeigenschaften", - "Filename for recorded audio": "Dateiname für aufgenommene Audiodaten", + "Textimportscheme track": "Textschema für den Import von Titeleigenschaften", + "Filename for recorded audio": "Dateiname für aufgenommenes Audio", "Record countdown timer in seconds": "Aufnahmecountdown in Sekunden", "Record time sensitivity": "Genauigkeit der Aufnahmezeiten", "TimeSensitivityMode.Full": "Alles (inkl. Millisekunden)", diff --git a/AudioCuesheetEditor/Resources/Localization/ProcessingHints/de.json b/AudioCuesheetEditor/Resources/Localization/ProcessingHints/de.json deleted file mode 100644 index 9681c560..00000000 --- a/AudioCuesheetEditor/Resources/Localization/ProcessingHints/de.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "culture": "de", - "translations": { - "Warnings": "Warnungen", - "Errors": "Fehler" - } -} \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/ProcessingHints/en.json b/AudioCuesheetEditor/Resources/Localization/ProcessingHints/en.json deleted file mode 100644 index c6bd13f1..00000000 --- a/AudioCuesheetEditor/Resources/Localization/ProcessingHints/en.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "culture": "en", - "translations": { - "Warnings": "Warning", - "Errors": "Error" - } -} \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/TracksTable/de.json b/AudioCuesheetEditor/Resources/Localization/TracksTable/de.json index 844c893b..3a738f1a 100644 --- a/AudioCuesheetEditor/Resources/Localization/TracksTable/de.json +++ b/AudioCuesheetEditor/Resources/Localization/TracksTable/de.json @@ -1,35 +1,36 @@ { "culture": "de", "translations": { - "Add new track": "Neuen Track hinzufügen", + "Add new track": "Neuen Titel hinzufügen", "Display selection of tracks": "Auswahl einblenden", "Selection": "Auswahl", - "Select this track for multiple track operations": "Diesen Track in Mehrfachauswahl übernehmen", + "Select this track for multiple track operations": "Diesen Titel in Mehrfachauswahl übernehmen", "Select all": "Wählen Sie alle Spuren für Mehrfachspuroperationen aus", "Delete selected tracks": "Ausgewählte löschen", "Hide selection of tracks": "Auswahl ausblenden", - "Delete all tracks": "Alle Tracks löschen", + "Delete all tracks": "Alle Titel löschen", "Controls": "Steuerung", "Artist": "Künstler", "Title": "Titel", "Begin": "Beginn", "End": "Ende", "Length": "Länge", - "Hints": "Hinweise", - "Unlink this track from previous track": "Diesen Track vom vorherigen Track abkoppeln", - "Link this track with previous track": "Diesen Track an den vorherigen Track koppeln", + "Unlink this track from previous track": "Diesen Titel vom vorherigen Titel abkoppeln", + "Link this track with previous track": "Diesen Titel an den vorherigen Titel koppeln", "Current track is played": "Dieser Track wird aktuell wiedergegeben", - "Edit track tooltip": "Trackdetails in einem Dialog bearbeiten", - "Copy track tooltip": "Kopiere diesen Track mit allen Werten und füge ihn anschließend zum Cuesheet hinzu.", - "Start playback this track": "Diesen Track wiedergeben", - "Move track up tooltip": "Diesen Track eine Position nach oben schieben", - "Delete track tooltip": "Diesen Track löschen", - "Move track down tooltip": "Diesen Track eine Position nach unten schieben", + "Edit track tooltip": "Titeldetails in einem Dialog bearbeiten", + "Copy track tooltip": "Kopiere diesen Titel mit allen Werten und füge ihn anschließend zum Cuesheet hinzu.", + "Start playback this track": "Diesen Titel wiedergeben", + "Move track up tooltip": "Diesen Titel eine Position nach oben schieben", + "Delete track tooltip": "Diesen Titel löschen", + "Move track down tooltip": "Diesen Titel eine Position nach unten schieben", "Position": "Position", "Abort": "Abbrechen", "Save changes": "Änderungen speichern", "Confirmation required": "Bestätigung erforderlich", - "Do you really want to delete all tracks?": "Möchten Sie wirklich alle Tracks löschen?", - "Edit selected tracks": "Ausgewählte Tracks bearbeiten" + "Do you really want to delete all tracks?": "Möchten Sie wirklich alle Titel löschen?", + "Edit selected tracks": "Ausgewählte Titel bearbeiten", + "Validation errors": "Validierungsfehler", + "A split point is currently set at the time of this track": "Ein Aufteilungspunkt ist aktuell in dem Zeitfenster dieses Titels gesetzt" } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/TracksTable/en.json b/AudioCuesheetEditor/Resources/Localization/TracksTable/en.json index 9a916314..819ccbf3 100644 --- a/AudioCuesheetEditor/Resources/Localization/TracksTable/en.json +++ b/AudioCuesheetEditor/Resources/Localization/TracksTable/en.json @@ -15,7 +15,6 @@ "Begin": "Begin", "End": "End", "Length": "Length", - "Hints": "Hints", "Unlink this track from previous track": "Unlink this track from previous track", "Link this track with previous track": "Link this track with previous track", "Current track is played": "Current track is played", @@ -30,6 +29,8 @@ "Save changes": "Save changes", "Confirmation required": "Confirmation required", "Do you really want to delete all tracks?": "Do you really want to delete all tracks?", - "Edit selected tracks": "Edit selected tracks" + "Edit selected tracks": "Edit selected tracks", + "Validation errors": "Validation errors", + "A split point is currently set at the time of this track": "A split point is currently set at the time of this track" } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/ValidationMessage/de.json b/AudioCuesheetEditor/Resources/Localization/ValidationMessage/de.json index 517d0b15..afdb290e 100644 --- a/AudioCuesheetEditor/Resources/Localization/ValidationMessage/de.json +++ b/AudioCuesheetEditor/Resources/Localization/ValidationMessage/de.json @@ -2,30 +2,44 @@ "culture": "de", "translations": { "{0} has no value!": "{0} hat keinen Wert!", - "{0} has invalid Count ({1})!": "{0} hat eine ungültige Menge ({1})!", + "{0} may not be 0!": "{0} darf nicht 0 sein!", + "Track({0},{1},{2},{3},{4}) does not have the correct position '{5}'!": "Track({0},{1},{2},{3},{4}) hat nicht die korrekte Position '{5}'!", + "{0} must be equal or greater zero!": "{0} muss größer oder gleich 0 sein!", + "{0} must only contain numbers!": "{0} darf nur Ziffern enthalten!", + "{0} has an invalid length. Allowed length is {1}!": "{0} hat eine ungültige Länge. Erlaubte Länge ist {1}!", + "{0} has invalid Count ({1})!": "{0} hat eine ungültige Anzahl ({1})!", + "{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!": "{0} {1} '{2}' wird auch von {3}({4},{5},{6},{7},{8}) benutzt. Positionen müssen eindeutig sein!", + "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!": "{0}({1},{2},{3},{4},{5}) überschneidet sich mit {0}({6},{7},{8},{9},{10}). Bitte stellen sie sicher, dass das Zeitinterval nur einmal genutzt wird!", "{0} has invalid value!": "{0} hat einen ungültigen Wert!", - "{0} has invalid length ({1})!": "{0} hat eine ungültige Länge ({1})!", - "{0} has invalid timespan!": "{0} ist nicht gültig (muss größer oder gleich 00:00:00 sein). Bitte geben Sie eine gültige Zeit an.", - "{0} has no value! Please check {1} and {2}.": "{0} hat keinen Wert! Bitte prüfen Sie {1} und {2}.", - "{0} {1} of this track is already in use by track(s) {2}!": "{0} {1} von diesem Track ist bereits in Benutzung durch Track(s) {2}!", - "{0} is overlapping with other track(s) ({1})!": "{0} überschneidet sich mit anderen Tracks ({1})!", - "{0} does not only contain numbers.": "{0} enthält nicht nur Zahlen.", - "Artist": "Künstler", - "Title": "Titel", - "Audiofile": "Audiodatei", - "Tracks": "Tracks", - "CDTextfile": "CD Textdatei", - "Cataloguenumber": "Katalognummer", + "{0} contains no placeholder!": "{0} enthält keinen Platzhalter!", + "Replace '{0}' by a regular expression!": "Ersetzen sie '{0}' durch einen regulären Ausdruck!", + "{0} contains placeholder '{1}' that can not be resolved!": "{0} enthält Platzhalter '{1}' die nicht aufgelöst werden können!", + "{0} contains placeholders that can not be solved! Please remove invalid placeholder '{1}'.": "{0} enhält Platzhalter die nicht aufgelöst werden können! Bitte entfernen Sie den ungültigen Platzhalter '{1}'.", "Position": "Position", "Begin": "Beginn", "End": "Ende", "Length": "Länge", - "Schemetype": "Schematyp", - "Scheme contains placeholders that can not be solved! Please remove invalid placeholder '{0}'.": "Schema enthält Platzhalter, die nicht aufgelöst werden können! Bitte entfernen Sie die ungültigen Platzhalter '{0}'.", - "SchemeCuesheet": "Schema Cuesheet", - "SchemeTracks": "Schema Tracks", - "{0} {1} of this track does not match track position in cuesheet. Please correct the {2} of this track to {3}!": "{0} {1} von diesem Track stimmt nicht mit der Position im Cuesheet überein. Bitte korrigieren Sie {2} von diesem Track auf {3}!", - "{0} contains no placeholder!": "{0} enthält keine Platzhalter!", - "Replace {0} by a regular expression!": "Ersetzen sie {0} bitte durch einen regulären Ausdruck!" + "Value": "Wert", + "Tracks": "Titel", + "Audiofile": "Audiodatei", + "SchemeType": "Schematyp", + "Scheme": "Schema", + "ENTER REGULAR EXPRESSION HERE": "Hier regulären Ausdruck eingeben", + "SchemeHead": "Kopfschema", + "SchemeTracks": "Titelschema", + "SchemeFooter": "Fußschema", + "Moment": "Zeitpunkt", + "{0} must end with '{1}'!": "{0} musss mit '{1}' enden!", + "{0} must have a filename!": "{0} muss einen Dateinamen haben!", + "CuesheetFilename": "Cuesheet Dateiname", + "RecordedAudiofilename": "Dateiname für aufgenommenes Audio", + "ProjectFilename": "Projektdateiname", + "{0} should be equal or less to '{1}'!": "{0} sollte kleiner oder gleich '{1}' sein!", + "Filename": "Dateiname", + "Artist": "Künstler", + "Title": "Titel", + "Exportprofile": "Exportprofil", + "ApplicationOptions": "Applikationsoptionen", + "AudiofileName": "Audiodatei" } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/ValidationMessage/en.json b/AudioCuesheetEditor/Resources/Localization/ValidationMessage/en.json index 2d026254..309d5fe4 100644 --- a/AudioCuesheetEditor/Resources/Localization/ValidationMessage/en.json +++ b/AudioCuesheetEditor/Resources/Localization/ValidationMessage/en.json @@ -2,30 +2,44 @@ "culture": "en", "translations": { "{0} has no value!": "{0} has no value!", + "{0} may not be 0!": "{0} may not be 0!", + "Track({0},{1},{2},{3},{4}) does not have the correct position '{5}'!": "Track({0},{1},{2},{3},{4}) does not have the correct position '{5}'!", + "{0} must be equal or greater zero!": "{0} must be equal or greater zero!", + "{0} must only contain numbers!": "{0} must only contain numbers!", + "{0} has an invalid length. Allowed length is {1}!": "{0} has an invalid length. Allowed length is {1}!", "{0} has invalid Count ({1})!": "{0} has invalid Count ({1})!", + "{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!": "{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!", + "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!": "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!", "{0} has invalid value!": "{0} has invalid value!", - "{0} has invalid length ({1})!": "{0} has invalid length ({1})!", - "{0} has invalid timespan!": "{0} is not valid (must be equal or greater than 00:00:00). Please enter a valid time.", - "{0} has no value! Please check {1} and {2}.": "{0} has no value! Please check {1} and {2}.", - "{0} {1} of this track is already in use by track(s) {2}!": "{0} {1} of this track is already in use by track(s) {2}!", - "{0} is overlapping with other track(s) ({1})!": "{0} is overlapping with other track(s) ({1})!", - "{0} does not only contain numbers.": "{0} does not only contain numbers.", - "Artist": "Artist", - "Title": "Title", - "Audiofile": "Audiofile", - "Tracks": "Tracks", - "CDTextfile": "CD Textfile", - "Cataloguenumber": "Cataloguenumber", + "{0} contains no placeholder!": "{0} contains no placeholder!", + "Replace '{0}' by a regular expression!": "Replace '{0}' by a regular expression!", + "{0} contains placeholder '{1}' that can not be resolved!": "{0} contains placeholder '{1}' that can not be resolved!", + "{0} contains placeholders that can not be solved! Please remove invalid placeholder '{1}'.": "{0} contains placeholders that can not be solved! Please remove invalid placeholder '{1}'.", "Position": "Position", "Begin": "Begin", "End": "End", "Length": "Length", - "Schemetype": "Schemetype", - "Scheme contains placeholders that can not be solved! Please remove invalid placeholder '{0}'.": "Scheme contains placeholders that can not be solved! Please remove invalid placeholder '{0}'.", - "SchemeCuesheet": "Scheme Cuesheet", - "SchemeTracks": "Scheme Tracks", - "{0} {1} of this track does not match track position in cuesheet. Please correct the {2} of this track to {3}!": "{0} {1} of this track does not match track position in cuesheet. Please correct the {2} of this track to {3}!", - "{0} contains no placeholder!": "{0} contains no placeholder!", - "Replace {0} by a regular expression!": "Replace {0} by a regular expression!" + "Value": "Value", + "Tracks": "Tracks", + "Audiofile": "Audiofile", + "SchemeType": "Schemetype", + "Scheme": "Scheme", + "ENTER REGULAR EXPRESSION HERE": "Enter regular expression here", + "SchemeHead": "Headerscheme", + "SchemeTracks": "Trackscheme", + "SchemeFooter": "Footerscheme", + "Moment": "Moment", + "{0} must end with '{1}'!": "{0} must end with '{1}'!", + "{0} must have a filename!": "{0} must have a filename!", + "CuesheetFilename": "Cuesheet filename", + "RecordedAudiofilename": "Filename for recorded audio", + "ProjectFilename": "Project filename", + "{0} should be equal or less to '{1}'!": "{0} should be equal or less to '{1}'!", + "Filename": "Filename", + "Artist": "Artist", + "Title": "Title", + "Exportprofile": "Exportprofile", + "ApplicationOptions": "Application options", + "AudiofileName": "Audiofile" } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/ViewModeImport/de.json b/AudioCuesheetEditor/Resources/Localization/ViewModeImport/de.json index 54a53e4a..2e2bd675 100644 --- a/AudioCuesheetEditor/Resources/Localization/ViewModeImport/de.json +++ b/AudioCuesheetEditor/Resources/Localization/ViewModeImport/de.json @@ -18,14 +18,14 @@ "Please validate the following data recognized by import assistant. Once you have validated all input, you can confirm import of data.": "Bitte validieren sie die analysierten und angezeigten Dateien. Anschließend können Sie die Daten bestätigen und den Import durchführen.", "Import the displayed data": "Angezeigte Daten importieren", "Cuesheet data": "Cuesheet Daten", - "Cuesheet tracks": "Cuesheet Tracks", + "Tracks": "Titel", "Textimport assistant": "Importassistent für Textdateien", "Filecontent": "Dateiinhalt", "Enter textimportscheme cuesheet tooltip": "Textschema für den Import von Cuesheeteigenschaften hier eingeben. Die Identifikation wird über reguläre Ausdrücke ausgeführt.", "Enter textimportscheme cuesheet here": "Textschema für den Import von Cuesheeteigenschaften hier eingeben", "Select placeholder": "Platzhalter auswählen", - "Enter textimportscheme track tooltip": "Textschema für den Import von Trackeigenschaften hier eingeben. Die Identifikation wird über reguläre Ausdrücke ausgeführt.", - "Enter textimportscheme track here": "Textschema für den Import von Trackeigenschaften hier eingeben", + "Enter textimportscheme track tooltip": "Textschema für den Import von Titeleigenschaften hier eingeben. Die Identifikation wird über reguläre Ausdrücke ausgeführt.", + "Enter textimportscheme track here": "Textschema für den Import von Titeleigenschaften hier eingeben", "Error during textimport": "Ein Fehler ist während des Textimport aufgetreten", "Flags": "Markierungen", "Import not possible due to textimport errors. Please check errors!": "Import ist nicht möglich, weil der Textimport Fehler aufweist. Bitte prüfen Sie die Fehler!", @@ -45,7 +45,7 @@ "Not possible!": "Ungültig!", "Please select files for import before going to validation!": "Bitte selektieren Sie Dateien bevor Sie zur Validierung wechseln!", "Textimportscheme cuesheet": "Import Schema Cuesheet", - "Textimportscheme track": "Import Schema Track", + "Textimportscheme track": "Import Schema Titel", "Import Options": "Importoptionen", "ENTER REGULAR EXPRESSION HERE": "Hier regulären Ausdruck eingeben", "Enter custom timespan format here": "Hier können Sie bei Bedarf ein angepasstes Format für Zeitspannen eingeben. Details können der Hilfe entnommen werden.", @@ -60,6 +60,7 @@ "Reset import options to defaults": "Optionen auf Standardeinstellung zurücksetzen", "Please confirm": "Bestätigung erforderlich", "Do you really want to reset the import options to default? This can not be undone!": "Möchten Sie wirklich die Import Optionen zurücksetzen? Dies kann nicht rückgängig gemacht werden!", - "Abort import of displayed data": "Import der angezeigten Daten abbrechen" + "Abort import of displayed data": "Import der angezeigten Daten abbrechen", + "Cuesheet split points": "Cuesheet Aufteilungspunkte" } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/ViewModeImport/en.json b/AudioCuesheetEditor/Resources/Localization/ViewModeImport/en.json index 0b517fc8..39c0634f 100644 --- a/AudioCuesheetEditor/Resources/Localization/ViewModeImport/en.json +++ b/AudioCuesheetEditor/Resources/Localization/ViewModeImport/en.json @@ -18,7 +18,7 @@ "Please validate the following data recognized by import assistant. Once you have validated all input, you can confirm import of data.": "Please validate the following data recognized by import assistant. Once you have validated all input, you can confirm import of data.", "Import the displayed data": "Import the displayed data", "Cuesheet data": "Cuesheet Data", - "Cuesheet tracks": "Cuesheet Tracks", + "Tracks": "Tracks", "Textimport assistant": "Import textfile assistant", "Filecontent": "Filecontent", "Enter textimportscheme cuesheet tooltip": "Enter textscheme for cuesheet properties import here. Identification will be done using regular expressions.", @@ -59,6 +59,7 @@ "Reset import options to defaults": "Reset import options to defaults", "Please confirm": "Please confirm", "Do you really want to reset the import options to default? This can not be undone!": "Do you really want to reset the import options to default? This can not be undone!", - "Abort import of displayed data": "Abort import of displayed data" + "Abort import of displayed data": "Abort import of displayed data", + "Cuesheet split points": "Cuesheet split points" } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/ViewModeRecord/de.json b/AudioCuesheetEditor/Resources/Localization/ViewModeRecord/de.json index 6c7168dd..7df97489 100644 --- a/AudioCuesheetEditor/Resources/Localization/ViewModeRecord/de.json +++ b/AudioCuesheetEditor/Resources/Localization/ViewModeRecord/de.json @@ -16,14 +16,14 @@ "Begin": "Beginn", "End": "Ende", "Length": "Länge", - "Add new track": "Neuen Track hinzufügen", - "Enter new track": "Neuen Track eingeben", - "Cuesheet tracks": "Cuesheet Tracks", + "Add new track": "Neuen Titel hinzufügen", + "Enter new track": "Neuen Titel eingeben", + "Tracks": "Titel", "Controls": "Steuerung", "Hints": "Hinweise", "Recording": "Aufnahme", "Download recorded audio": "Aufnahme herunterladen", - "Cuesheet already contains tracks. Recording is not possible, if tracks are present. Please save your work and start with a clean cuesheet.": "Das Cuesheet enthält bereits Tracks. Die Aufnahme ist nicht möglich, wenn bereits Tracks vorhanden sind. Bitte speichern Sie ihre Arbeit und starten Sie anschließend mit einem leeren Cuesheet.", + "Cuesheet already contains tracks. Recording is not possible, if tracks are present. Please save your work and start with a clean cuesheet.": "Das Cuesheet enthält bereits Titel. Die Aufnahme ist nicht möglich, wenn bereits Titel vorhanden sind. Bitte speichern Sie ihre Arbeit und starten Sie anschließend mit einem leeren Cuesheet.", "Error": "Fehler", "Here all validation messages will be displayed. For details switch to full edit mode.": "Hier werden alle Validierungsnachrichten angezeigt. Für Details wechseln Sie bitte in den Bearbeitungsmodus \"Komplette Bearbeitung\".", "Start record timer": "Aufnahmecountdown starten", diff --git a/AudioCuesheetEditor/Resources/Localization/ViewModeRecord/en.json b/AudioCuesheetEditor/Resources/Localization/ViewModeRecord/en.json index 46742c06..b358eda7 100644 --- a/AudioCuesheetEditor/Resources/Localization/ViewModeRecord/en.json +++ b/AudioCuesheetEditor/Resources/Localization/ViewModeRecord/en.json @@ -18,7 +18,7 @@ "Length": "Length", "Add new track": "Add new track", "Enter new track": "Enter new track", - "Cuesheet tracks": "Cuesheet tracks", + "Tracks": "Tracks", "Controls": "Controls", "Hints": "Hints", "Recording": "Recording", diff --git a/AudioCuesheetEditor/Shared/AudioPlayer.razor b/AudioCuesheetEditor/Shared/AudioPlayer.razor index f74cf723..5d0ca5eb 100644 --- a/AudioCuesheetEditor/Shared/AudioPlayer.razor +++ b/AudioCuesheetEditor/Shared/AudioPlayer.razor @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with Foobar. If not, see . --> -@using Toolbelt.Blazor.HotKeys + @implements IDisposable @inject ITextLocalizer _localizer @@ -83,6 +83,7 @@ along with Foobar. If not, see HotKeysContext? hotKeysContext; Boolean paused; Audiofile? audioFile; + Cuesheet? cuesheetAttachedToAudiofileChangedEvent; [Parameter] public EventCallback CurrentlyPlayingTrackChanged { get; set; } @@ -104,6 +105,7 @@ along with Foobar. If not, see _sessionStateContainer.Cuesheet.AudioFileChanged -= Cuesheet_AudioFileChanged; } _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; + _sessionStateContainer.CuesheetChanged -= SessionStateContainer_CuesheetChanged; } public Boolean PlaybackPossible @@ -194,30 +196,19 @@ along with Foobar. If not, see { //Attach shortcuts hotKeysContext = _hotKeys.CreateContext() - .Add(ModKeys.Ctrl, Keys.P, OnPlayClicked) - .Add(ModKeys.Ctrl, Keys.Left, () => OnPlayNextPreviousTrackClicked(true)) - .Add(ModKeys.Ctrl, Keys.Right, () => OnPlayNextPreviousTrackClicked()) - .Add(ModKeys.None, Keys.MediaPlayPause, OnPlayClicked) - .Add(ModKeys.None, Keys.MediaTrackNext, () => OnPlayNextPreviousTrackClicked()) - .Add(ModKeys.None, Keys.MediaTrackPrevious, () => OnPlayNextPreviousTrackClicked(true)) - .Add(ModKeys.None, Keys.MediaStop, () => OnStopClicked()); + .Add(ModKey.Ctrl, Key.p, OnPlayKeyDown) + .Add(ModKey.Ctrl, Key.ArrowLeft, OnPlayNextTrackKeyDown) + .Add(ModKey.Ctrl, Key.ArrowRight, OnPlayPreviousTrackKeyDown) + .Add(Key.MediaPlayPause, OnPlayKeyDown) + .Add(Key.MediaTrackNext, OnPlayNextTrackKeyDown) + .Add(Key.MediaTrackPrevious, OnPlayPreviousTrackKeyDown) + .Add(Key.MediaStop, OnStopKeyDown); _localizationService.LocalizationChanged += LocalizationService_LocalizationChanged; - - //Also reset the duration, since audio file will change also - TotalTime = null; - _sessionStateContainer.Cuesheet.AudioFileChanged += Cuesheet_AudioFileChanged; - if (_sessionStateContainer.Cuesheet.Audiofile != null) - { - if (_sessionStateContainer.Cuesheet.Audiofile.IsContentStreamLoaded == false) - { - _sessionStateContainer.Cuesheet.Audiofile.ContentStreamLoaded += AudioFile_ContentStreamLoaded; - } - else - { - AnalyseAudioFile(); - } - } + _sessionStateContainer.CuesheetChanged += SessionStateContainer_CuesheetChanged; + cuesheetAttachedToAudiofileChangedEvent = _sessionStateContainer.Cuesheet; + cuesheetAttachedToAudiofileChangedEvent.AudioFileChanged += Cuesheet_AudioFileChanged; + AnalyseCuesheet(); //Setup audio timer audioUpdateTimer = new Timer(500); @@ -248,18 +239,18 @@ along with Foobar. If not, see await base.OnInitializedAsync(); } - private void HowlOnPlay(Howler.Blazor.Components.Events.HowlPlayEventArgs args) + void HowlOnPlay(Howler.Blazor.Components.Events.HowlPlayEventArgs args) { paused = false; audioUpdateTimer.Start(); } - private void HowlOnPause(Howler.Blazor.Components.Events.HowlEventArgs args) + void HowlOnPause(Howler.Blazor.Components.Events.HowlEventArgs args) { paused = true; } - private void HowlOnEnd(Howler.Blazor.Components.Events.HowlEventArgs args) + void HowlOnEnd(Howler.Blazor.Components.Events.HowlEventArgs args) { paused = false; audioUpdateTimer.Stop(); @@ -269,7 +260,7 @@ along with Foobar. If not, see StateHasChanged(); } - private void HowlOnStop(Howler.Blazor.Components.Events.HowlEventArgs args) + void HowlOnStop(Howler.Blazor.Components.Events.HowlEventArgs args) { paused = false; audioUpdateTimer.Stop(); @@ -280,7 +271,7 @@ along with Foobar. If not, see StateHasChanged(); } - private async Task OnSliderValueChanged(Double newvalue) + async Task OnSliderValueChanged(Double newvalue) { if (TotalTime.HasValue) { @@ -293,7 +284,7 @@ along with Foobar. If not, see } } - private async Task OnPlayClicked() + async Task OnPlayClicked() { if (_sessionStateContainer.Cuesheet.Audiofile != audioFile) { @@ -331,7 +322,12 @@ along with Foobar. If not, see } } - private async Task OnPlayNextPreviousTrackClicked(Boolean previous = false) + async ValueTask OnPlayKeyDown() + { + await OnPlayClicked(); + } + + async Task OnPlayNextPreviousTrackClicked(Boolean previous = false) { if (CurrentlyPlayingTrack != null) { @@ -349,21 +345,32 @@ along with Foobar. If not, see } } - private async Task OnStopClicked() + async ValueTask OnPlayNextTrackKeyDown() + { + await OnPlayNextPreviousTrackClicked(true); + } + + async ValueTask OnPlayPreviousTrackKeyDown() + { + await OnPlayNextPreviousTrackClicked(); + } + + async Task OnStopClicked() { await _howl.Stop(soundId); } - private void Cuesheet_AudioFileChanged(object? sender, EventArgs args) + async ValueTask OnStopKeyDown() { - if (_sessionStateContainer.Cuesheet.Audiofile != null) - { - _sessionStateContainer.Cuesheet.Audiofile.ContentStreamLoaded += AudioFile_ContentStreamLoaded; - } - TotalTime = null; + await OnStopClicked(); } - private void AudioFile_ContentStreamLoaded(object? sender, EventArgs args) + void Cuesheet_AudioFileChanged(object? sender, EventArgs args) + { + AnalyseCuesheet(); + } + + void AudioFile_ContentStreamLoaded(object? sender, EventArgs args) { if (sender != null) { @@ -373,12 +380,12 @@ along with Foobar. If not, see AnalyseAudioFile(); } - private void LocalizationService_LocalizationChanged(object? sender, EventArgs args) + void LocalizationService_LocalizationChanged(object? sender, EventArgs args) { StateHasChanged(); } - private void AnalyseAudioFile() + void AnalyseAudioFile() { if ((_sessionStateContainer.Cuesheet != null) && (_sessionStateContainer.Cuesheet.Audiofile != null) && (_sessionStateContainer.Cuesheet.Audiofile.Duration.HasValue) && (TotalTime.HasValue == false)) { @@ -386,4 +393,35 @@ along with Foobar. If not, see StateHasChanged(); } } + + void SessionStateContainer_CuesheetChanged(object? sender, EventArgs args) + { + if (cuesheetAttachedToAudiofileChangedEvent != null) + { + cuesheetAttachedToAudiofileChangedEvent.AudioFileChanged -= Cuesheet_AudioFileChanged; + } + AnalyseCuesheet(); + cuesheetAttachedToAudiofileChangedEvent = _sessionStateContainer.Cuesheet; + cuesheetAttachedToAudiofileChangedEvent.AudioFileChanged += Cuesheet_AudioFileChanged; + } + + void AnalyseCuesheet() + { + if (_sessionStateContainer.Cuesheet.Audiofile != null) + { + if (_sessionStateContainer.Cuesheet.Audiofile.IsContentStreamLoaded) + { + AnalyseAudioFile(); + } + else + { + TotalTime = null; + _sessionStateContainer.Cuesheet.Audiofile.ContentStreamLoaded += AudioFile_ContentStreamLoaded; + } + } + else + { + TotalTime = null; + } + } } diff --git a/AudioCuesheetEditor/Shared/CuesheetData.razor b/AudioCuesheetEditor/Shared/CuesheetData.razor index 8504ad64..6d0b9bcf 100644 --- a/AudioCuesheetEditor/Shared/CuesheetData.razor +++ b/AudioCuesheetEditor/Shared/CuesheetData.razor @@ -19,185 +19,152 @@ along with Foobar. If not, see @implements IDisposable @inject ITextLocalizer _localizer -@inject CuesheetController _cuesheetController @inject SessionStateContainer _sessionStateContainer @inject IJSRuntime _jsRuntime @inject ILogger _logger @inject HttpClient _httpClient @inject ITextLocalizerService _localizationService @inject TraceChangeManager _traceChangeManager +@inject ITextLocalizer _validationMessageLocalizer @if (Cuesheet != null) { - - - @_localizer["CD artist"] - - - - - - - - @_localizer["CD title"] - - - - - - @switch (_sessionStateContainer.CurrentViewMode) - { - case ViewMode.ViewModeRecord: - @if (Cuesheet.Audiofile != null) - { - - @_localizer["Audiofile"] - - - @if (Cuesheet.Audiofile.IsRecorded) - { - - - - } - - - - - - - } - break; - case ViewMode.ViewModeFull: - - @_localizer["Audiofile"] + + + + @_localizer["CD artist"] - @if (Cuesheet.Audiofile == null) - { - - x.MimeType))" Changed="OnAudioFileChanged" AutoReset="false"> - - } - else - { - - @if (Cuesheet.Audiofile.IsRecorded) - { - - - - } - - - - - - - - } + + + + + - - - @_localizer["CD textfile"] - - @if (Cuesheet.CDTextfile == null) - { - - } - else - { - - - - - - - - - } - - - - - - @_localizer["Cataloguenumber"] - - - - - - break; - case ViewMode.ViewModeImport: - @if (Cuesheet != null) - { - - @_localizer["Audiofile"] - - @if (Cuesheet.Audiofile == null) - { - - x.MimeType))" Changed="OnAudioFileChanged" AutoReset="false"> - - } - else - { + + + + @_localizer["CD title"] + + + + + + + + + + @switch (_sessionStateContainer.CurrentViewMode) + { + case ViewMode.ViewModeRecord: + @if (Cuesheet.Audiofile != null) + { + + @_localizer["Audiofile"] + @if (Cuesheet.Audiofile.IsRecorded) { - + } - - - - + - } - - - - + + + } + break; + case ViewMode.ViewModeFull: + case ViewMode.ViewModeImport: + + + @_localizer["Audiofile"] + + @if (Cuesheet.Audiofile == null) + { + x.MimeType))" Changed="OnAudioFileChanged" AutoReset="false"> + + + + + } + else + { + + @if (Cuesheet.Audiofile.IsRecorded) + { + + + + } + + + + + + + + + + + + } + + + + + @_localizer["CD textfile"] @if (Cuesheet.CDTextfile == null) { - + + + + + } else { - + - + } - - - + + + @_localizer["Cataloguenumber"] - + + + + + - - } - break; - } + + break; + } + } @code { ModalDialog? modalDialog; - ITextLocalizer validationMessageLocalizer = default!; + Guid fileEditCDTextFileId = Guid.NewGuid(); + Guid fileEditAudiofileId = Guid.NewGuid(); + Validation? audiofileValidation; + Validations? validations; public Cuesheet? Cuesheet { @@ -231,7 +198,6 @@ along with Foobar. If not, see _logger.LogDebug("OnInitializedAsync"); _localizationService.LocalizationChanged += LocalizationService_LocalizationChanged; - validationMessageLocalizer = new TextLocalizer(_localizationService); _sessionStateContainer.CuesheetChanged += SessionStateContainer_CuesheetChanged; _sessionStateContainer.ImportCuesheetChanged += SessionStateContainer_ImportCuesheetChanged; @@ -249,7 +215,7 @@ along with Foobar. If not, see Cuesheet.Audiofile = null; StateHasChanged(); await Task.Delay(1); - await _jsRuntime.InvokeVoidAsync("triggerClick", _cuesheetController.GetFieldIdentifier(Cuesheet, nameof(Cuesheet.Audiofile))); + await _jsRuntime.InvokeVoidAsync("triggerClick", fileEditAudiofileId); } } @@ -261,9 +227,9 @@ along with Foobar. If not, see if (Cuesheet != null) { var file = e.Files.First(); - if (CuesheetController.CheckFileMimeType(file, Audiofile.AudioCodecs) == true) + if (IOUtility.CheckFileMimeTypeForAudioCodec(file) == true) { - await SetAudioFile(file, _cuesheetController.GetFieldIdentifier(Cuesheet, nameof(Cuesheet.Audiofile))); + await SetAudioFile(file); } else { @@ -278,11 +244,15 @@ along with Foobar. If not, see } } } + if (audiofileValidation != null) + { + await audiofileValidation.ValidateAsync(); + } } - private async Task SetAudioFile(IFileEntry file, String inputDomId) + private async Task SetAudioFile(IFileEntry file) { - _logger.LogInformation("SetAudioFile with {0}, {1}", file, inputDomId); + _logger.LogInformation("SetAudioFile with {0}", file); if (Cuesheet != null) { if ((Cuesheet.Audiofile != null) && (Cuesheet.Audiofile.IsRecorded)) @@ -291,9 +261,11 @@ along with Foobar. If not, see } if (file != null) { - var audioFileObjectURL = await _jsRuntime.InvokeAsync("getObjectURL", inputDomId); - var codec = Audiofile.AudioCodecs.Single(x => x.MimeType.Equals(file.Type, StringComparison.OrdinalIgnoreCase)); - Cuesheet.Audiofile = new Audiofile(file.Name, audioFileObjectURL, codec, _httpClient); + var audioFileObjectURL = await _jsRuntime.InvokeAsync("getObjectURL", fileEditAudiofileId); + var codec = IOUtility.GetAudioCodec(file); + var audiofile = new Audiofile(file.Name, audioFileObjectURL, codec, _httpClient); + _ = audiofile.LoadContentStream(); + Cuesheet.Audiofile = audiofile; } else { @@ -310,7 +282,7 @@ along with Foobar. If not, see Cuesheet.CDTextfile = null; StateHasChanged(); await Task.Delay(1); - await _jsRuntime.InvokeVoidAsync("triggerClick", _cuesheetController.GetFieldIdentifier(Cuesheet, nameof(Cuesheet.CDTextfile))); + await _jsRuntime.InvokeVoidAsync("triggerClick", fileEditCDTextFileId); } } @@ -322,7 +294,7 @@ along with Foobar. If not, see if (Cuesheet != null) { var file = e.Files.First(); - if (CuesheetController.CheckFileMimeType(file, CDTextfile.MimeType, CDTextfile.FileExtension) == true) + if (IOUtility.CheckFileMimeType(file, CDTextfile.MimeType, CDTextfile.FileExtension) == true) { Cuesheet.CDTextfile = new CDTextfile(file.Name); } @@ -344,6 +316,7 @@ along with Foobar. If not, see private void LocalizationService_LocalizationChanged(object? sender, EventArgs args) { StateHasChanged(); + validations?.ValidateAll(); } private void SessionStateContainer_CuesheetChanged(object? sender, EventArgs args) @@ -359,10 +332,12 @@ along with Foobar. If not, see private void TraceChangeManager_UndoDone(object? sender, EventArgs args) { StateHasChanged(); + validations?.ValidateAll().GetAwaiter().GetResult(); } private void TraceChangeManager_RedoDone(object? sender, EventArgs args) { StateHasChanged(); + validations?.ValidateAll().GetAwaiter().GetResult(); } } diff --git a/AudioCuesheetEditor/Shared/EditTrackModal.razor b/AudioCuesheetEditor/Shared/EditTrackModal.razor index f0dd5222..b0b83d9c 100644 --- a/AudioCuesheetEditor/Shared/EditTrackModal.razor +++ b/AudioCuesheetEditor/Shared/EditTrackModal.razor @@ -19,12 +19,13 @@ along with Foobar. If not, see @implements IDisposable @inject ITextLocalizer _localizer -@inject ITextLocalizer _validationMessageLocalizer @inject MusicBrainzDataProvider _musicBrainzDataProvider @inject SessionStateContainer _sessionStateContainer @inject LocalStorageOptionsProvider _localStorageOptionsProvider @inject TraceChangeManager _traceChangeManager @inject HotKeys _hotKeys +@inject ITextLocalizer _validationMessageLocalizer +@inject DateTimeUtility _dateTimeUtility @@ -33,29 +34,40 @@ along with Foobar. If not, see - @if (IsMultipleEdit == false) - { - - @_localizer["IsLinkedToPreviousTrack"] - - - @_localizer["IsLinkedToPreviousTrackValue"] - - - - - @_localizer["Position"] - - - - - - - - @_localizer["Artist"] - - - + + @if (IsMultipleEdit == false) + { + + + @_localizer["IsLinkedToPreviousTrack"] + + + + @_localizer["IsLinkedToPreviousTrackValue"] + + + + + + + + + + + @_localizer["Position"] + + + + + + + + + + + @_localizer["Artist"] + + @if (context.Item.Disambiguation != null) { @@ -67,14 +79,12 @@ along with Foobar. If not, see } - - - - - @_localizer["Title"] - - - + + + + @_localizer["Title"] + + @if (context.Item.Disambiguation != null) { @@ -86,118 +96,119 @@ along with Foobar. If not, see } - - - - - @_localizer["Begin"] - - - - - - - - @_localizer["End"] - - - - - - - - @_localizer["Length"] - - - - - - - - @_localizer["Flags"] - - @foreach (var flag in Flag.AvailableFlags) - { - - @flag.Name - - } - - - - @_localizer["PreGap"] - - - - - - - - @_localizer["PostGap"] - - - - - - - } - else - { - - - - @_localizer["Change"] - @_localizer["Property"] - @_localizer["Value"] - - - - - - @_localizer["IsLinkedToPreviousTrack"] - @_localizer["IsLinkedToPreviousTrackValue"] - - - - @_localizer["Position"] - - - - - - - - - - - - - - @_localizer["Artist"] - - - - @if (context.Item.Disambiguation != null) - { - @String.Format("{0} ({1})", context.Text, context.Item.Disambiguation) - } - else - { - @context.Text - } - - - - - - - @_localizer["Title"] + + + + + @_localizer["Begin"] + + + + + + + + + + + + @_localizer["End"] + + + + + + + + + + + + @_localizer["Length"] + + + + + + + + + + + @_localizer["Flags"] + + @foreach (var flag in Flag.AvailableFlags) + { + + @flag.Name + + } + + + + + @_localizer["PreGap"] + + + + + + + + + + + + @_localizer["PostGap"] + + + + + + + + + + } + else + { +
+ + + @_localizer["Change"] + @_localizer["Property"] + @_localizer["Value"] + + + + + + @_localizer["IsLinkedToPreviousTrack"] + @_localizer["IsLinkedToPreviousTrackValue"] + + + + @_localizer["Position"] - + + + + + + + + + + + + + @_localizer["Artist"] + + @if (context.Item.Disambiguation != null) { @@ -210,112 +221,131 @@ along with Foobar. If not, see - - - - @_localizer["Begin"] - - - - - - - - - - - - - - @_localizer["End"] - - - - - - - - - - - - - - @_localizer["Length"] - - - - - - - - - - - - - - @_localizer["Flags"] - - @foreach (var flag in Flag.AvailableFlags) - { - - @flag.Name - - } - - - - - @_localizer["PreGap"] - - - - - - - - - - - - - - @_localizer["PostGap"] - - - - - - - - - - - - -
- } + + + + @_localizer["Title"] + + + + @if (context.Item.Disambiguation != null) + { + @String.Format("{0} ({1})", context.Text, context.Item.Disambiguation) + } + else + { + @context.Text + } + + + + + + + @_localizer["Begin"] + + + + + + + + + + + + + + @_localizer["End"] + + + + + + + + + + + + + + @_localizer["Length"] + + + + + + + + + + + + + + @_localizer["Flags"] + + @foreach (var flag in Flag.AvailableFlags) + { + + @flag.Name + + } + + + + + @_localizer["PreGap"] + + + + + + + + + + + + + + @_localizer["PostGap"] + + + + + + + + + + + + + + } +
@@ -325,12 +355,6 @@ along with Foobar. If not, see
@code { - - public void Dispose() - { - hotKeysContext?.Dispose(); - } - enum DynamicEditValue { EnteredValueEquals = 0, @@ -343,6 +367,7 @@ along with Foobar. If not, see get => _tracksToEdit; set { + editedTrack.ValidateablePropertyChanged -= editedTrack_ValidateablePropertyChanged; _tracksToEdit = value; if (IsMultipleEdit) { @@ -355,6 +380,11 @@ along with Foobar. If not, see editedTrack = _tracksToEdit.First().Clone(); } } + editedTrack.ValidateablePropertyChanged += editedTrack_ValidateablePropertyChanged; + if (validations != null) + { + validations.ValidateAll(); + } } } @@ -387,16 +417,25 @@ along with Foobar. If not, see DynamicEditValue _editModeTrackPostGap; HotKeysContext? hotKeysContext; + Validations? validations; + ApplicationOptions? applicationOptions; + + public void Dispose() + { + hotKeysContext?.Dispose(); + _localStorageOptionsProvider.OptionSaved -= LocalStorageOptionsProvider_OptionSaved; + } - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { hotKeysContext = _hotKeys.CreateContext() - .Add(ModKeys.None, Keys.Enter, EditTrackModalSaveClicked) - .Add(ModKeys.None, Keys.ESC, () => ControlModalDialog(modalTrackEdit, false)); + .Add(Key.Enter, OnEditTrackModalSaveKeyDown) + .Add(Key.Escape, OnHideModalKeyDown); - base.OnInitialized(); - } + applicationOptions = await _localStorageOptionsProvider.GetOptions(); + _localStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionSaved; + } public Boolean EditTrackIsLinkedToPreviousTrack { @@ -698,7 +737,7 @@ along with Foobar. If not, see } else { - track.CopyValues(editedTrack); + track.CopyValues(editedTrack, setCuesheet: false); } } // We need to fire events for IsLinkedToPreviousTrack to work, if we have done a multiple edit. @@ -711,6 +750,11 @@ along with Foobar. If not, see await SaveClicked.InvokeAsync(); } + async ValueTask OnEditTrackModalSaveKeyDown() + { + await EditTrackModalSaveClicked(); + } + async Task ControlModalDialog(Modal? dialog, Boolean show) { if (dialog != null) @@ -726,6 +770,11 @@ along with Foobar. If not, see } } + async ValueTask OnHideModalKeyDown() + { + await ControlModalDialog(modalTrackEdit, false); + } + async Task OnReadDataAutocompleteTrackArtistEditDialog(AutocompleteReadDataEventArgs autocompleteReadDataEventArgs) { if (!autocompleteReadDataEventArgs.CancellationToken.IsCancellationRequested) @@ -795,10 +844,19 @@ along with Foobar. If not, see return resultString; } - async Task OnTimespanTextChanged(Action setAction, String value) + void editedTrack_ValidateablePropertyChanged(object? sender, string property) + { + if (validations != null) + { + validations.ValidateAll().GetAwaiter().GetResult(); + } + } + + void LocalStorageOptionsProvider_OptionSaved(object? sender, IOptions options) { - var options = await _localStorageOptionsProvider.GetOptions(); - TimeSpan? result = DateTimeUtility.ParseTimeSpan(value, options?.TimeSpanFormat); - setAction(result); + if (options is ApplicationOptions) + { + applicationOptions = (ApplicationOptions)options; + } } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/ExportProfilesDialog.razor b/AudioCuesheetEditor/Shared/ExportProfilesDialog.razor deleted file mode 100644 index c0885578..00000000 --- a/AudioCuesheetEditor/Shared/ExportProfilesDialog.razor +++ /dev/null @@ -1,355 +0,0 @@ - - -@implements IDisposable - -@inject ITextLocalizerService _localizationService -@inject ITextLocalizer _localizer -@inject LocalStorageOptionsProvider _localStorageOptionsProvider -@inject ILogger _logger -@inject IBlazorDownloadFileService _blazorDownloadFileService -@inject HotKeys _hotKeys -@inject SessionStateContainer _sessionStateContainer - - - - - - @_localizer["Exportprofiles"] - - - - - - @_localizer["Select exportprofile"] - - - - - - - - @if (SelectedExportProfile != null) - { - - @_localizer["Name"] - - - - - - @_localizer["Filename"] - - - - - @if (SelectedExportProfile != null) - { - - @_localizer["Exportprofilescheme head"] - - - - - - - - - - - - @_localizer["Select placeholder"] - - - @foreach (var availableScheme in Exportscheme.AvailableCuesheetSchemes) - { - @_localizer[availableScheme.Key] - } - - - - - - - @if (SelectedExportProfile.SchemeHead.IsValid == false) - { - @_localizer["Exportprofilescheme head validationerrors"] - @foreach (var validationError in SelectedExportProfile.SchemeHead.ValidationErrors.OrderBy(x => x.Type)) - { - - @validationError.Message.GetMessageLocalized(validationMessageLocalizer) - - } - } - } - @if (SelectedExportProfile != null) - { - - @_localizer["Exportprofilescheme track"] - - - - - - - - - - - - @_localizer["Select placeholder"] - - - @foreach (var availableScheme in Exportscheme.AvailableTrackSchemes) - { - @_localizer[availableScheme.Key] - } - - - - - - - @if (SelectedExportProfile.SchemeTracks.IsValid == false) - { - @_localizer["Exportprofilescheme track validationerrors"] - @foreach (var validationError in SelectedExportProfile.SchemeTracks.ValidationErrors.OrderBy(x => x.Type)) - { - - @validationError.Message.GetMessageLocalized(validationMessageLocalizer) - - } - } - } - @if (SelectedExportProfile != null) - { - - @_localizer["Exportprofilescheme footer"] - - - - - - - - - - - - @_localizer["Select placeholder"] - - - @foreach (var availableScheme in Exportscheme.AvailableCuesheetSchemes) - { - @_localizer[availableScheme.Key] - } - - - - - - - @if (SelectedExportProfile.SchemeFooter.IsValid == false) - { - @_localizer["Exportprofilescheme footer validationerrors"] - @foreach (var validationError in SelectedExportProfile.SchemeFooter.ValidationErrors.OrderBy(x => x.Type)) - { - - @validationError.Message.GetMessageLocalized(validationMessageLocalizer) - - } - } - } - } - - - - - - - - - - -@code { - Modal? modalExportProfiles; - IReadOnlyCollection exportProfileModels = default!; - Guid? selectedExportProfileId; - HotKeysContext? hotKeysContext; - ITextLocalizer validationMessageLocalizer = default!; - Boolean modalExportProfilesVisible = false; - - public class SelectExportProfileModel - { - public Guid Id { get; private set; } - public Exportprofile ExportProfile { get; set; } - - public SelectExportProfileModel(Exportprofile exportProfile) - { - if (exportProfile == null) - { - throw new ArgumentNullException(nameof(exportProfile)); - } - Id = Guid.NewGuid(); - ExportProfile = exportProfile; - } - } - - public Exportprofile? SelectedExportProfile - { - get - { - if ((exportProfileModels != null) && (selectedExportProfileId != Guid.Empty)) - { - var exportProfile = exportProfileModels.FirstOrDefault(x => x.Id == selectedExportProfileId); - if (exportProfile != null) - { - return exportProfile.ExportProfile; - } - } - return null; - } - } - - public void Dispose() - { - hotKeysContext?.Dispose(); - _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; - } - - public async Task Show() - { - if (modalExportProfiles != null) - { - await modalExportProfiles.Show(); - modalExportProfilesVisible = true; - } - } - - protected override async Task OnInitializedAsync() - { - _localizationService.LocalizationChanged += LocalizationService_LocalizationChanged; - - validationMessageLocalizer = new TextLocalizer(_localizationService); - - if (exportProfileModels == null) - { - var options = await _localStorageOptionsProvider.GetOptions(); - exportProfileModels = options.ExportProfiles.Select(x => new SelectExportProfileModel(x)).ToList().AsReadOnly(); - selectedExportProfileId = exportProfileModels.First().Id; - } - - hotKeysContext = _hotKeys.CreateContext() - .Add(ModKeys.None, Keys.Enter, OnEnterKeyDown); - - await base.OnInitializedAsync(); - } - - private async Task OnEnterKeyDown() - { - if (modalExportProfilesVisible) - { - await OnDownloadExportProfileClicked(); - } - } - - async Task ModalExportProfilesCloseClicked() - { - _logger.LogInformation("ModalExportProfilesCloseClicked"); - await SaveExportProfiles(); - if (modalExportProfiles != null) - { - await modalExportProfiles.Hide(); - } - } - - private void OnAddNewExportProfileClicked() - { - _logger.LogInformation("OnAddNewExportProfileClicked"); - var list = exportProfileModels.ToList(); - list.Add(new SelectExportProfileModel(new Exportprofile())); - exportProfileModels = list.AsReadOnly(); - selectedExportProfileId = list.Last().Id; - } - - private void OnDeleteExportProfileClicked() - { - _logger.LogInformation("OnDeleteExportProfileClicked"); - var list = exportProfileModels.ToList(); - list.RemoveAll(x => x.Id == selectedExportProfileId); - exportProfileModels = list.AsReadOnly(); - if (exportProfileModels.Count > 0) - { - selectedExportProfileId = exportProfileModels.First().Id; - } - } - - private async Task SaveExportProfiles() - { - _logger.LogInformation("SaveExportProfiles"); - if (exportProfileModels != null) - { - var options = await _localStorageOptionsProvider.GetOptions(); - options.ExportProfiles = exportProfileModels.Select(x => x.ExportProfile).ToList().AsReadOnly(); - await _localStorageOptionsProvider.SaveOptions(options); - } - } - - async Task OnDownloadExportProfileClicked() - { - _logger.LogInformation("OnDownloadExportProfileClicked with SelectedExportProfile = {0}", SelectedExportProfile); - if ((SelectedExportProfile != null) && (SelectedExportProfile.IsExportable == true)) - { - await SaveExportProfiles(); - await _blazorDownloadFileService.DownloadFile(SelectedExportProfile.FileName, SelectedExportProfile.GenerateExport(_sessionStateContainer.Cuesheet), contentType: "application/octet-stream"); - if (modalExportProfiles != null) - { - await modalExportProfiles.Hide(); - } - } - } - - private void SelectedExportProfileChanged(Guid? newValue) - { - _logger.LogInformation("SelectedExportProfileChanged with {0}", newValue); - selectedExportProfileId = newValue; - StateHasChanged(); - } - - private String? GetLocalizedString(Boolean expressionToCheck, String localizedStringName) - { - if (expressionToCheck == true) - { - return _localizer[localizedStringName]; - } - else - { - return null; - } - } - - private void LocalizationService_LocalizationChanged(object? sender, EventArgs args) - { - StateHasChanged(); - } - - private void ModalExportProfiles_VisibleChanged(Boolean visible) - { - modalExportProfilesVisible = visible; - } -} diff --git a/AudioCuesheetEditor/Shared/MainLayout.razor b/AudioCuesheetEditor/Shared/MainLayout.razor index 141109ce..7ee97645 100644 --- a/AudioCuesheetEditor/Shared/MainLayout.razor +++ b/AudioCuesheetEditor/Shared/MainLayout.razor @@ -30,6 +30,7 @@ along with Foobar. If not, see @inject LocalStorageOptionsProvider _localStorageOptionsProvider @inject SessionStateContainer _sessionStateContainer @inject IBlazorDownloadFileService _blazorDownloadFileService +@inject ITextLocalizer _validationMessageLocalizer @@ -52,7 +53,7 @@ along with Foobar. If not, see
- @if(displayMenuBar) + @if (displayMenuBar) { @@ -60,9 +61,8 @@ along with Foobar. If not, see + + + } + else + { + + } + + + + +@code { + public event EventHandler? GenerateExportfilesClicked; + + [Parameter] + [EditorRequired] + public String? Title { get; set; } + + [Parameter] + [EditorRequired] + public RenderFragment? PrepareExportStepContent { get; set; } + + [Parameter] + [EditorRequired] + public ExportType ExportType { get; set; } + + public Exportprofile? SelectedExportProfile { get; set; } + + public Boolean IsVisible { get; private set; } + public Validations? Validations { get; private set; } + + Modal? modalExportdialog; + String selectedStep = "prepareExport"; + IReadOnlyCollection? exportfiles; + Boolean prepareExportCompleted = false; + HotKeysContext? hotKeysContext; + ApplicationOptions? applicationOptions; + Steps? stepsRef; + + Boolean StepNavigationAllowed + { + get + { + Boolean navigationAllowed = true; + if (Validations != null) + { + navigationAllowed = Validations.ValidateAll().GetAwaiter().GetResult(); + if (navigationAllowed) + { + navigationAllowed = ExportPossible; + } + if (navigationAllowed) + { + navigationAllowed = exportfiles != null; + } + } + return navigationAllowed; + } + } + + String? ExportPossibleTooltip + { + get + { + var generator = new ExportfileGenerator(ExportType, _sessionStateContainer.Cuesheet, SelectedExportProfile, applicationOptions); + var validationResult = generator.Validate(); + if (validationResult?.Status == Model.Entity.ValidationStatus.Error) + { + string? detailText = null; + if (validationResult.ValidationMessages != null) + { + foreach (var validationMessage in validationResult.ValidationMessages) + { + detailText += String.Format("{0}{1}", validationMessage.GetMessageLocalized(_validationMessageLocalizer), Environment.NewLine); + } + } + return _localizer["Export files can not be generated. Please check validationerrors and solve errors in order to download export: {0}", detailText]; + } + return null; + } + } + + Boolean ExportPossible + { + get + { + Boolean exportPossible = false; + if (Validations != null) + { + exportPossible = Validations.ValidateAll().GetAwaiter().GetResult(); + } + return exportPossible && ExportPossibleTooltip == null; + } + } + + protected override async Task OnInitializedAsync() + { + hotKeysContext = _hotKeys.CreateContext() + .Add(Key.Enter, OnEnterKeyDown); + + applicationOptions = await _localStorageOptionsProvider.GetOptions(); + _localStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionSaved; + + await base.OnInitializedAsync(); + } + + public void Dispose() + { + hotKeysContext?.Dispose(); + _localStorageOptionsProvider.OptionSaved -= LocalStorageOptionsProvider_OptionSaved; + } + + public async Task Show() + { + Reset(); + await ControlModalDialog(modalExportdialog, true); + } + + public void Reset() + { + prepareExportCompleted = false; + if (stepsRef != null) + { + stepsRef.SelectStep("prepareExport"); + } + exportfiles = null; + } + + async Task ControlModalDialog(Modal? dialog, Boolean show) + { + if (dialog != null) + { + if (show) + { + await dialog.Show(); + } + else + { + await dialog.Hide(); + } + } + } + + Task GenerateExportfiles_Clicked() + { + _logger.LogDebug("GenerateExportfiles_Clicked called"); + if (ExportPossible) + { + GenerateExportfilesClicked?.Invoke(this, EventArgs.Empty); + var generator = new ExportfileGenerator(ExportType, _sessionStateContainer.Cuesheet, SelectedExportProfile, applicationOptions); + exportfiles = generator.GenerateExportfiles(); + selectedStep = "displayExportResult"; + prepareExportCompleted = true; + } + return Task.CompletedTask; + } + + bool NavigationAllowed(StepNavigationContext context) + { + return StepNavigationAllowed; + } + + void ModalExportdialog_VisibleChanged(Boolean visible) + { + IsVisible = visible; + } + + async ValueTask OnEnterKeyDown() + { + if (IsVisible) + { + await GenerateExportfiles_Clicked(); + } + } + + void LocalStorageOptionsProvider_OptionSaved(object? sender, IOptions options) + { + if (options is ApplicationOptions) + { + applicationOptions = (ApplicationOptions)options; + } + } +} diff --git a/AudioCuesheetEditor/Shared/OptionsDialog.razor b/AudioCuesheetEditor/Shared/OptionsDialog.razor index e820cf71..d1e23330 100644 --- a/AudioCuesheetEditor/Shared/OptionsDialog.razor +++ b/AudioCuesheetEditor/Shared/OptionsDialog.razor @@ -25,144 +25,160 @@ along with Foobar. If not, see @inject NavigationManager _navigationManager @inject HotKeys _hotKeys @inject ITextLocalizerService _localizationService +@inject ITextLocalizer _validationMessageLocalizer @if (applicationOptions != null) { - + @_localizer["Options"] - - - - - @_localizer["Common settings"] - @_localizer["Record settings"] - - - - - - - - @_localizer["Culture setting"] - - - - - - @_localizer["Default viewmode"] - - - - - - @_localizer["Cuesheet filename"] - - - - - - @_localizer["Project filename"] - - - - - - @_localizer["Automatically link tracks"] - - @_localizer["Automatically link tracks with previous"] - - - - - - @_localizer["Customized timespan format"] - - - - - - @if ((applicationOptions != null) && (applicationOptions.TimeSpanFormat != null)) - { - - + + + + + + @_localizer["Common settings"] + @_localizer["Record settings"] + + + + + + + + @_localizer["Culture setting"] + + + + + + @_localizer["Default viewmode"] + + + + + + + @_localizer["Cuesheet filename"] + + + + + - } - else - { - - + + + + + + @_localizer["Project filename"] + + + + + - } - - - - - @_localizer["Select placeholder"] - - - @foreach (var availableFormat in TimeSpanFormat.AvailableTimespanScheme) - { - @_localizer[availableFormat.Key] - } - - - - - - - - - - @_localizer["Filename for recorded audio"] - - - - - - @_localizer["Record countdown timer in seconds"] - - - - - - @_localizer["Record time sensitivity"] - - - - - - - - - - - - - - - - - + + + + + @_localizer["Automatically link tracks"] + + @_localizer["Automatically link tracks with previous"] + + + + + + + @_localizer["Customized timespan format"] + + + + + + + + + + + + + + + @_localizer["Select placeholder"] + + + @foreach (var availableFormat in TimeSpanFormat.AvailableTimespanScheme) + { + @_localizer[availableFormat.Key] + } + + + + + + + + + + + + @_localizer["Filename for recorded audio"] + + + + + + + + + + + @_localizer["Record countdown timer in seconds"] + + + + + + @_localizer["Record time sensitivity"] + + + + + + + + + + + + + + + + + + } @code { @@ -173,7 +189,7 @@ along with Foobar. If not, see Modal? modalOptions; HotKeysContext? hotKeysContext; - ITextLocalizer validationMessageLocalizer = default!; + Validation? timespanformatValidation; public void Dispose() { @@ -194,18 +210,18 @@ along with Foobar. If not, see protected override async Task OnInitializedAsync() { hotKeysContext = _hotKeys.CreateContext() - .Add(ModKeys.None, Keys.Enter, OnEnterKeyDown); + .Add(Key.Enter, OnEnterKeyDown); _localizationService.LocalizationChanged += LocalizationService_LocalizationChanged; _localStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionSaved; applicationOptions = await _localStorageOptionsProvider.GetOptions(); - validationMessageLocalizer = new TextLocalizer(_localizationService); + TimeSpanFormat.TextLocalizer = _localizer; await base.OnInitializedAsync(); } - private async Task OnEnterKeyDown() + async ValueTask OnEnterKeyDown() { if (modalOptionsVisible) { @@ -224,6 +240,7 @@ along with Foobar. If not, see private void LocalizationService_LocalizationChanged(object? sender, EventArgs args) { StateHasChanged(); + TimeSpanFormat.TextLocalizer = _localizer; } private Task OnCultureSelectionChanged(String value) @@ -299,6 +316,7 @@ along with Foobar. If not, see if (applicationOptions.TimeSpanFormat == null) { applicationOptions.TimeSpanFormat = new TimeSpanFormat(); + applicationOptions.TimeSpanFormat.ValidateablePropertyChanged += Timespanformat_ValidateablePropertyChanged; } applicationOptions.TimeSpanFormat.Scheme = value; } @@ -306,6 +324,10 @@ along with Foobar. If not, see { if (applicationOptions != null) { + if (applicationOptions.TimeSpanFormat != null) + { + applicationOptions.TimeSpanFormat.ValidateablePropertyChanged -= Timespanformat_ValidateablePropertyChanged; + } applicationOptions.TimeSpanFormat = null; } } @@ -319,6 +341,7 @@ along with Foobar. If not, see if (applicationOptions.TimeSpanFormat == null) { applicationOptions.TimeSpanFormat = new TimeSpanFormat(); + applicationOptions.TimeSpanFormat.ValidateablePropertyChanged += Timespanformat_ValidateablePropertyChanged; } applicationOptions.TimeSpanFormat.Scheme += value.ToString()?.Replace(TimeSpanFormat.EnterRegularExpressionHere, _localizer[TimeSpanFormat.EnterRegularExpressionHere]); } @@ -326,9 +349,21 @@ along with Foobar. If not, see { if (applicationOptions != null) { + if (applicationOptions.TimeSpanFormat != null) + { + applicationOptions.TimeSpanFormat.ValidateablePropertyChanged -= Timespanformat_ValidateablePropertyChanged; + } applicationOptions.TimeSpanFormat = null; } } return Task.CompletedTask; } + + void Timespanformat_ValidateablePropertyChanged(object? sender, String property) + { + if (timespanformatValidation != null) + { + timespanformatValidation.ValidateAsync().GetAwaiter().GetResult(); + } + } } diff --git a/AudioCuesheetEditor/Shared/ProcessingHints.razor b/AudioCuesheetEditor/Shared/ProcessingHints.razor deleted file mode 100644 index dbde5493..00000000 --- a/AudioCuesheetEditor/Shared/ProcessingHints.razor +++ /dev/null @@ -1,213 +0,0 @@ - - -@implements IDisposable - -@inject ITextLocalizer _localizer -@inject IJSRuntime _jsRuntime -@inject CuesheetController _cuesheetController -@inject ITextLocalizerService _localizationService -@inject SessionStateContainer _sessionStateContainer - - - @Header - - @if (GetValidationErrors(ValidationErrorFilterType.WarningOnly).Count() > 0) - { - - - - - - @foreach (var validationError in GetValidationErrors(ValidationErrorFilterType.WarningOnly)) - { - if (ActivateLinksToFields == true) - { - var functionName = "focusElement"; - - - @if (validationError.FieldReference.Owner is IEntityDisplayName entityDisplayName) - { - @entityDisplayName.GetDisplayNameLocalized(_localizer) : @validationError.Message.GetMessageLocalized(validationMessageLocalizer) - } - else - { - @_localizer[validationError.FieldReference.Owner.GetType().Name] : @validationError.Message.GetMessageLocalized(validationMessageLocalizer) - } - - - } - else - { - - @if (validationError.FieldReference.Owner is IEntityDisplayName entityDisplayName) - { - @entityDisplayName.GetDisplayNameLocalized(_localizer) : @validationError.Message.GetMessageLocalized(validationMessageLocalizer) - } - else - { - @_localizer[validationError.FieldReference.Owner.GetType().Name] : @validationError.Message.GetMessageLocalized(validationMessageLocalizer) - } - - } - } - - - } - @if (GetValidationErrors(ValidationErrorFilterType.ErrorOnly).Count() > 0) - { - - - - - - @foreach (var validationError in GetValidationErrors(ValidationErrorFilterType.ErrorOnly)) - { - if (ActivateLinksToFields == true) - { - var functionName = "focusElement"; - - - @if (validationError.FieldReference.Owner is IEntityDisplayName entityDisplayName) - { - @entityDisplayName.GetDisplayNameLocalized(_localizer) : @validationError.Message.GetMessageLocalized(validationMessageLocalizer) - } - else - { - @_localizer[validationError.FieldReference.Owner.GetType().Name] : @validationError.Message.GetMessageLocalized(validationMessageLocalizer) - } - - - } - else - { - - @if (validationError.FieldReference.Owner is IEntityDisplayName entityDisplayName) - { - @entityDisplayName.GetDisplayNameLocalized(_localizer) : @validationError.Message.GetMessageLocalized(validationMessageLocalizer) - } - else - { - @_localizer[validationError.FieldReference.Owner.GetType().Name] : @validationError.Message.GetMessageLocalized(validationMessageLocalizer) - } - - } - } - - - } - - - -@code { - [Parameter] - public String Header { get; set; } = default!; - - [Parameter] - public Boolean ActivateLinksToFields { get; set; } - - Boolean processingHintsWarningVisible = true; - Boolean processingHintsErrorVisible = true; - ITextLocalizer validationMessageLocalizer = default!; - - public void Dispose() - { - _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; - _sessionStateContainer.CuesheetChanged -= SessionStateContainer_CuesheetChanged; - ConnectOrDisconnectValidationRefresh(true); - } - - protected override Task OnInitializedAsync() - { - _localizationService.LocalizationChanged += LocalizationService_LocalizationChanged; - - validationMessageLocalizer = new TextLocalizer(_localizationService); - //When cuesheet is changed, we need to call statehaschanged! - _sessionStateContainer.CuesheetChanged += SessionStateContainer_CuesheetChanged; - ConnectOrDisconnectValidationRefresh(); - - return base.OnInitializedAsync(); - } - - private IReadOnlyCollection GetValidationErrors(ValidationErrorFilterType validationErrorFilterType) - { - List validationErrors = _sessionStateContainer.Cuesheet.GetValidationErrorsFiltered(validationErrorFilterType: validationErrorFilterType).ToList(); - foreach (var track in _sessionStateContainer.Cuesheet.Tracks) - { - validationErrors.AddRange(track.GetValidationErrorsFiltered(validationErrorFilterType: validationErrorFilterType)); - } - return validationErrors.AsReadOnly(); - } - - private void ConnectOrDisconnectValidationRefresh(Boolean disconnect = false) - { - if (disconnect == true) - { - _sessionStateContainer.Cuesheet.ValidateablePropertyChanged -= Cuesheet_ValidateablePropertyChanged; - foreach (var track in _sessionStateContainer.Cuesheet.Tracks) - { - track.ValidateablePropertyChanged -= Track_ValidateablePropertyChanged; - } - _sessionStateContainer.Cuesheet.TrackAdded -= Cuesheet_TrackAdded; - _sessionStateContainer.Cuesheet.TrackRemoved -= Cuesheet_TrackRemoved; - } - else - { - //When a validatable property has been changed we need to call statehaschanged! - _sessionStateContainer.Cuesheet.ValidateablePropertyChanged += Cuesheet_ValidateablePropertyChanged; - foreach (var track in _sessionStateContainer.Cuesheet.Tracks) - { - track.ValidateablePropertyChanged += Track_ValidateablePropertyChanged; - } - _sessionStateContainer.Cuesheet.TrackAdded += Cuesheet_TrackAdded; - _sessionStateContainer.Cuesheet.TrackRemoved += Cuesheet_TrackRemoved; - } - } - - private void SessionStateContainer_CuesheetChanged(object? sender, EventArgs args) - { - ConnectOrDisconnectValidationRefresh(true); - ConnectOrDisconnectValidationRefresh(); - StateHasChanged(); - } - - private void LocalizationService_LocalizationChanged(object? sender, EventArgs args) - { - StateHasChanged(); - } - - private void Cuesheet_ValidateablePropertyChanged(object? sender, EventArgs args) - { - StateHasChanged(); - } - - private void Track_ValidateablePropertyChanged(object? sender, EventArgs args) - { - StateHasChanged(); - } - - private void Cuesheet_TrackAdded(object? sender, TrackAddRemoveEventArgs args) - { - args.Track.ValidateablePropertyChanged += Track_ValidateablePropertyChanged; - } - - private void Cuesheet_TrackRemoved(object? sender, TrackAddRemoveEventArgs args) - { - args.Track.ValidateablePropertyChanged -= Track_ValidateablePropertyChanged; - } -} diff --git a/AudioCuesheetEditor/Shared/TracksTable.razor b/AudioCuesheetEditor/Shared/TracksTable.razor index 07bda878..8c97bc92 100644 --- a/AudioCuesheetEditor/Shared/TracksTable.razor +++ b/AudioCuesheetEditor/Shared/TracksTable.razor @@ -19,22 +19,35 @@ along with Foobar. If not, see @implements IDisposable @inject ITextLocalizer _localizer -@inject CuesheetController _cuesheetController @inject SessionStateContainer _sessionStateContainer @inject LocalStorageOptionsProvider _localStorageOptionsProvider @inject TraceChangeManager _traceChangeManager @inject ILogger _logger @inject ITextLocalizerService _localizationService @inject MusicBrainzDataProvider _musicBrainzDataProvider +@inject ITextLocalizer _validationMessageLocalizer +@inject DateTimeUtility _dateTimeUtility -@if (Cuesheet != null) -{ + @if (_sessionStateContainer.CurrentViewMode == ViewMode.ViewModeFull) { + var validationResult = Cuesheet?.Validate(x => x.Tracks); + + + @_localizer["Validation errors"] + + @if (validationResult?.ValidationMessages != null) + { + @foreach(var message in validationResult.ValidationMessages) + { + @message.GetMessageLocalized(_validationMessageLocalizer) + } + } + - - } - else - { - - } - - - } - - @if (TrackSelectionVisible) - { - - - - - - } - @switch (_sessionStateContainer.CurrentViewMode) + + @if ((track != Cuesheet?.Tracks.FirstOrDefault()) && (_sessionStateContainer.CurrentViewMode == ViewMode.ViewModeFull)) { - case ViewMode.ViewModeRecord: - - - - @track.Position - break; - case ViewMode.ViewModeFull: - case ViewMode.ViewModeImport: - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
+ + + @if (track.IsLinkedToPreviousTrack) + { + + } + else + { + + } + + } + + @if (TrackSelectionVisible) + { - - + + - break; - } - - - - + } + @switch (_sessionStateContainer.CurrentViewMode) + { + case ViewMode.ViewModeRecord: + + + + @track.Position + break; + case ViewMode.ViewModeFull: + case ViewMode.ViewModeImport: + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + break; + } + + + @if (context.Item.Disambiguation != null) { @@ -289,12 +301,10 @@ along with Foobar. If not, see -
-
- - - - + + + + @if (context.Item.Disambiguation != null) { @@ -307,80 +317,85 @@ along with Foobar. If not, see -
- - @switch (_sessionStateContainer.CurrentViewMode) - { - case ViewMode.ViewModeRecord: - @track.Begin - @track.End - @track.Length - break; - case ViewMode.ViewModeFull: - case ViewMode.ViewModeImport: - - - - - - - - - - - - - - - - - - + + @switch (_sessionStateContainer.CurrentViewMode) + { + case ViewMode.ViewModeRecord: + @track.Begin + @track.End + @track.Length + break; + case ViewMode.ViewModeFull: + case ViewMode.ViewModeImport: + +
+ @if (Cuesheet?.GetSplitPointAtTrack(track) != null) + { + + + + + + + + } + + + + + + + + + +
+
+ + + + + + + + + - - - @if (_sessionStateContainer.CurrentViewMode == ViewMode.ViewModeFull) - { + - @((MarkupString)GetMarkupString(track.GetValidationErrors(validationMessageLocalizer))) + + + + + + + - } - break; - } - + break; + } + + } -} +
@code { - //TODO: Validation for autocomplete and focus for autocomplete doesn't currently work ModalDialog? modalDialog; EditTrackModal? modalTrackEdit; List selectedTracks = new(); - ITextLocalizer validationMessageLocalizer = default!; - Track? lastTrack; IEnumerable? autocompleteTrackArtists; IEnumerable? autocompleteTrackTitles; Boolean _trackSelectionVisible = false; //Saved locally because of performance (do not load everytime something is edited) ApplicationOptions? applicationOptions; - - [Parameter] - public Boolean TrackSelectionVisible - { - get => _trackSelectionVisible; - set - { - _trackSelectionVisible = value; - selectedTracks = new(); - } - } + Validations? validations; + Boolean revalidate = false; + List TracksAttachedToValidateablePropertyChanged = new(); [Parameter] public AudioPlayer? AudioPlayer { get; set; } @@ -403,6 +418,16 @@ along with Foobar. If not, see } } + public Boolean TrackSelectionVisible + { + get => _trackSelectionVisible; + set + { + _trackSelectionVisible = value; + selectedTracks = new(); + } + } + public void Dispose() { _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; @@ -411,12 +436,10 @@ along with Foobar. If not, see _sessionStateContainer.ImportCuesheetChanged -= SessionStateContainer_ImportCuesheetChanged; _traceChangeManager.UndoDone -= TraceChangeManager_UndoDone; _traceChangeManager.RedoDone -= TraceChangeManager_RedoDone; - _sessionStateContainer.Cuesheet.TrackAdded -= Cuesheet_TrackAdded; _sessionStateContainer.Cuesheet.TrackRemoved -= Cuesheet_TrackRemoved; - if (lastTrack != null) - { - lastTrack.ValidateablePropertyChanged -= Cuesheet_LastTrack_ValidateablePropertyChanged; - } + _sessionStateContainer.Cuesheet.TrackAdded -= Cuesheet_TrackAdded; + DetachTrackFromValidateablePropertyChanged(); + DetachCuesheetFromSplitPointsAddedRemoved(); } protected override async Task OnInitializedAsync() @@ -424,7 +447,6 @@ along with Foobar. If not, see _logger.LogDebug("OnInitializedAsync"); _localizationService.LocalizationChanged += LocalizationService_LocalizationChanged; - validationMessageLocalizer = new TextLocalizer(_localizationService); applicationOptions = await _localStorageOptionsProvider.GetOptions(); _localStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionSaved; @@ -437,7 +459,20 @@ along with Foobar. If not, see _sessionStateContainer.Cuesheet.TrackAdded += Cuesheet_TrackAdded; _sessionStateContainer.Cuesheet.TrackRemoved += Cuesheet_TrackRemoved; - AttachToLastTrackValidateablePropertyChanged(); + + AttachTracksToValidateablePropertyChanged(); + AttachCuesheetToSplitPointsAddedRemoved(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + _logger.LogDebug("OnAfterRenderAsync({firstRender})", firstRender); + if ((revalidate) && (validations != null)) + { + await validations.ValidateAll(); + revalidate = false; + } } Task OnAddTrackClicked() @@ -564,13 +599,6 @@ along with Foobar. If not, see await EditTrackModal(copy); } - Task OnTimespanTextChanged(Action setAction, String value) - { - TimeSpan? result = DateTimeUtility.ParseTimeSpan(value, applicationOptions?.TimeSpanFormat); - setAction(result); - return Task.CompletedTask; - } - private async Task OnPlayTrackClicked(Track track) { if (AudioPlayer != null) @@ -582,6 +610,7 @@ along with Foobar. If not, see private void LocalizationService_LocalizationChanged(object? sender, EventArgs args) { StateHasChanged(); + validations?.ValidateAll(); } private MarkupString GetMarkupString(String? stringValue) @@ -594,28 +623,31 @@ along with Foobar. If not, see return result; } - private void AttachToLastTrackValidateablePropertyChanged() - { - if (lastTrack != null) - { - lastTrack.ValidateablePropertyChanged -= Cuesheet_LastTrack_ValidateablePropertyChanged; - } - lastTrack = Cuesheet?.Tracks.LastOrDefault(); - if (lastTrack != null) - { - lastTrack.ValidateablePropertyChanged += Cuesheet_LastTrack_ValidateablePropertyChanged; - } - } - private void SessionStateContainer_CuesheetChanged(object? sender, EventArgs args) { + DetachTrackFromValidateablePropertyChanged(); + DetachCuesheetFromSplitPointsAddedRemoved(); selectedTracks.Clear(); StateHasChanged(); + AttachTracksToValidateablePropertyChanged(); + AttachCuesheetToSplitPointsAddedRemoved(); } private void SessionStateContainer_ImportCuesheetChanged(object? sender, EventArgs args) { - AttachToLastTrackValidateablePropertyChanged(); + // Unsubscribe to previous attached events + DetachCuesheetFromSplitPointsAddedRemoved(); + var tracks = TracksAttachedToValidateablePropertyChanged.Except(_sessionStateContainer.Cuesheet.Tracks); + for (int i = tracks.Count() - 1; i >= 0; i--) + { + var track = tracks.ElementAt(i); + DetachTrackFromValidateablePropertyChanged(track); + TracksAttachedToValidateablePropertyChanged.Remove(track); + } + // Reattach if needed + AttachTracksToValidateablePropertyChanged(); + AttachCuesheetToSplitPointsAddedRemoved(); + revalidate = true; StateHasChanged(); } @@ -629,20 +661,17 @@ along with Foobar. If not, see StateHasChanged(); } - private void Cuesheet_TrackAdded(object? sender, EventArgs args) + void Cuesheet_TrackAdded(object? sender, TrackAddRemoveEventArgs args) { - AttachToLastTrackValidateablePropertyChanged(); StateHasChanged(); + AttachTracksToValidateablePropertyChanged(); + revalidate = true; } - private void Cuesheet_TrackRemoved(object? sender, EventArgs args) + void Cuesheet_TrackRemoved(object? sender, TrackAddRemoveEventArgs args) { - AttachToLastTrackValidateablePropertyChanged(); - } - - private void Cuesheet_LastTrack_ValidateablePropertyChanged(object? sender, EventArgs args) - { - StateHasChanged(); + DetachTrackFromValidateablePropertyChanged(args.Track); + revalidate = true; } async Task OnReadDataAutocompleteTrackArtist(AutocompleteReadDataEventArgs autocompleteReadDataEventArgs) @@ -695,4 +724,81 @@ along with Foobar. If not, see applicationOptions = (ApplicationOptions)options; } } + + void AttachTracksToValidateablePropertyChanged() + { + if (Cuesheet != null) + { + foreach (var track in Cuesheet.Tracks) + { + if (TracksAttachedToValidateablePropertyChanged.Contains(track) == false) + { + track.ValidateablePropertyChanged += Track_ValidateablePropertyChanged; + TracksAttachedToValidateablePropertyChanged.Add(track); + } + } + } + } + + void DetachTrackFromValidateablePropertyChanged(Track? track = null) + { + if (track == null) + { + foreach (var trackCurrentlyAttached in TracksAttachedToValidateablePropertyChanged) + { + trackCurrentlyAttached.ValidateablePropertyChanged -= Track_ValidateablePropertyChanged; + } + } + else + { + track.ValidateablePropertyChanged -= Track_ValidateablePropertyChanged; + } + } + + void Track_ValidateablePropertyChanged(object? sender, string property) + { + if (validations != null) + { + validations.ValidateAll().GetAwaiter().GetResult(); + } + StateHasChanged(); + } + + void AttachCuesheetToSplitPointsAddedRemoved() + { + if (Cuesheet != null) + { + Cuesheet.SplitPointAdded += Cuesheet_SplitPointAdded; + Cuesheet.SplitPointRemoved += Cuesheet_SplitPointRemoved; + } + } + + void DetachCuesheetFromSplitPointsAddedRemoved() + { + if (Cuesheet != null) + { + Cuesheet.SplitPointAdded -= Cuesheet_SplitPointAdded; + Cuesheet.SplitPointRemoved -= Cuesheet_SplitPointRemoved; + } + } + + void Cuesheet_SplitPointAdded(object? sender, SplitPointAddRemoveEventArgs args) + { + args.SplitPoint.ValidateablePropertyChanged += SplitPoint_ValidateablePropertyChanged; + } + + void Cuesheet_SplitPointRemoved(object? sender, SplitPointAddRemoveEventArgs args) + { + args.SplitPoint.ValidateablePropertyChanged -= SplitPoint_ValidateablePropertyChanged; + } + + void SplitPoint_ValidateablePropertyChanged(object? sender, string property) + { + switch (property) + { + case nameof(SplitPoint.Moment): + StateHasChanged(); + break; + } + } } diff --git a/AudioCuesheetEditor/_Imports.razor b/AudioCuesheetEditor/_Imports.razor index 04899a0f..f00ca655 100644 --- a/AudioCuesheetEditor/_Imports.razor +++ b/AudioCuesheetEditor/_Imports.razor @@ -6,16 +6,17 @@ @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop @using AudioCuesheetEditor +@using AudioCuesheetEditor.Pages @using AudioCuesheetEditor.Shared @using AudioCuesheetEditor.Model.AudioCuesheet @using AudioCuesheetEditor.Model.IO @using AudioCuesheetEditor.Model.IO.Export @using AudioCuesheetEditor.Model.IO.Import @using AudioCuesheetEditor.Model.IO.Audio -@using AudioCuesheetEditor.Controller @using AudioCuesheetEditor.Model.Entity @using AudioCuesheetEditor.Model.Options @using AudioCuesheetEditor.Extensions @@ -30,4 +31,4 @@ @using BlazorDownloadFile @using Howler.Blazor.Components @using Markdig -@using Toolbelt.Blazor.HotKeys \ No newline at end of file +@using Toolbelt.Blazor.HotKeys2 \ No newline at end of file diff --git a/AudioCuesheetEditor/web.config b/AudioCuesheetEditor/web.config new file mode 100644 index 00000000..1d890d18 --- /dev/null +++ b/AudioCuesheetEditor/web.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/AudioCuesheetEditor/wwwroot/CHANGELOG.md b/AudioCuesheetEditor/wwwroot/CHANGELOG.md index dee90c99..9775139e 100644 --- a/AudioCuesheetEditor/wwwroot/CHANGELOG.md +++ b/AudioCuesheetEditor/wwwroot/CHANGELOG.md @@ -1,5 +1,58 @@ # Changelog +## [v4.0.0](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/tree/v4.0.0) (03.04.2024) + +[Full Changelog](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/compare/v3.3.0...v4.0.0) + +**Implemented enhancements:** + +- Add detailed message why an export can not be done [\#313](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/313) +- Change Undo/Redo icon to filled [\#293](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/293) +- Remove processing hints and include the validation into GUI [\#250](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/250) +- Add possibility to automatically split audio file [\#63](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/63) + +**Fixed bugs:** + +- Modal export \(Cuesheet and ExportProfile\) opens with the last generated files [\#327](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/327) +- Out of memory exception when using large mp3 files [\#323](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/323) +- Splitpoints doesn't work with imported cuesheet [\#320](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/320) +- Last track length not calculated automatically during import [\#312](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/312) +- Validation messages are not relocalized after switching language [\#303](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/303) +- Revalidation after Textimport scheme change not displayed [\#302](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/302) +- Audio file not recognized during import [\#301](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/301) +- Undo doesn't work if using Audiofile [\#299](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/299) +- Ogg audiofile can not be used [\#298](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/298) +- Reset of cuesheet doesn't reset the audio player [\#291](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/291) +- Increase rendering performance with much tracks. [\#277](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/277) + +**Closed issues:** + +- Describe Shortcuts in Help [\#288](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/288) +- Update Documentation for v4.0 [\#251](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/251) + +**Merged pull requests:** + +- Added splitpoints help [\#331](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/331) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Modal export cuesheet and exportprofile opens with the last generated files [\#330](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/330) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Out of memory exception when using large mp3 files [\#329](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/329) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Add detailed message why an export can not be done [\#325](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/325) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Update documentation for v40 [\#324](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/324) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Delete .github/workflows/deploy\_github\_pages.yml [\#319](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/319) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Added shortcuts to help [\#318](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/318) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Bugfix for localizing validation messages [\#317](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/317) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Ogg audiofile can not be used [\#316](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/316) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Last track length not calculated automatically during import [\#315](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/315) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Update AudioPlayer.razor [\#311](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/311) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Initial correction for setting track end after audio file analysis [\#310](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/310) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- 302 revalidation after textimport scheme change not displayed [\#309](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/309) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- 301 audio file not recognized during import [\#307](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/307) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- 63 add possibility to automatically split audio file [\#304](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/304) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Update from master [\#295](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/295) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Changed undo/redo icons to filled counterparts [\#294](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/294) ([RadekKocka](https://github.com/RadekKocka)) +- 277 increase rendering performance with much tracks [\#290](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/290) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- Update version [\#287](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/287) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) +- 250 remove processing hints and include the validation into gui [\#286](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/286) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) + ## [v3.3.0](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/tree/v3.3.0) (2022-11-29) [Full Changelog](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/compare/v3.2.0...v3.3.0) @@ -17,6 +70,7 @@ **Merged pull requests:** +- Updated Changelog [\#282](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/282) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) - 279 import mode overwrites current changes before committed [\#281](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/281) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) - Hotkey redesigned [\#280](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/280) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) - 269 increase performance [\#278](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/pull/278) ([NeoCoderMatrix86](https://github.com/NeoCoderMatrix86)) diff --git a/AudioCuesheetEditor/wwwroot/images/CreateCuesheet1_de.png b/AudioCuesheetEditor/wwwroot/images/CreateCuesheet1_de.png index 7c628034..e0021a6d 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/CreateCuesheet1_de.png and b/AudioCuesheetEditor/wwwroot/images/CreateCuesheet1_de.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/CreateCuesheet1_en.png b/AudioCuesheetEditor/wwwroot/images/CreateCuesheet1_en.png index 1480e037..efd48c6a 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/CreateCuesheet1_en.png and b/AudioCuesheetEditor/wwwroot/images/CreateCuesheet1_en.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/CreateCuesheet2_de.png b/AudioCuesheetEditor/wwwroot/images/CreateCuesheet2_de.png index 75a0bbdb..cf0eec95 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/CreateCuesheet2_de.png and b/AudioCuesheetEditor/wwwroot/images/CreateCuesheet2_de.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/CreateCuesheet2_en.png b/AudioCuesheetEditor/wwwroot/images/CreateCuesheet2_en.png index 4e74aa7d..5926ad5d 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/CreateCuesheet2_en.png and b/AudioCuesheetEditor/wwwroot/images/CreateCuesheet2_en.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/ImportCuesheet2_de.png b/AudioCuesheetEditor/wwwroot/images/ImportCuesheet2_de.png index fc47be3e..e1250406 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/ImportCuesheet2_de.png and b/AudioCuesheetEditor/wwwroot/images/ImportCuesheet2_de.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/ImportCuesheet2_en.png b/AudioCuesheetEditor/wwwroot/images/ImportCuesheet2_en.png index 31e928d7..7433ce38 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/ImportCuesheet2_en.png and b/AudioCuesheetEditor/wwwroot/images/ImportCuesheet2_en.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/ImportCuesheet3_de.png b/AudioCuesheetEditor/wwwroot/images/ImportCuesheet3_de.png index 5e74414d..906156f8 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/ImportCuesheet3_de.png and b/AudioCuesheetEditor/wwwroot/images/ImportCuesheet3_de.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/ImportCuesheet3_en.png b/AudioCuesheetEditor/wwwroot/images/ImportCuesheet3_en.png index fc6cf2d4..9a096496 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/ImportCuesheet3_en.png and b/AudioCuesheetEditor/wwwroot/images/ImportCuesheet3_en.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/RecordMode1_de.png b/AudioCuesheetEditor/wwwroot/images/RecordMode1_de.png index 12a50de7..ad78a298 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/RecordMode1_de.png and b/AudioCuesheetEditor/wwwroot/images/RecordMode1_de.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/RecordMode1_en.png b/AudioCuesheetEditor/wwwroot/images/RecordMode1_en.png index a800a2e6..64c4896d 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/RecordMode1_en.png and b/AudioCuesheetEditor/wwwroot/images/RecordMode1_en.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/RecordMode5_de.png b/AudioCuesheetEditor/wwwroot/images/RecordMode5_de.png index add4eaab..6660420e 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/RecordMode5_de.png and b/AudioCuesheetEditor/wwwroot/images/RecordMode5_de.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/RecordMode5_en.png b/AudioCuesheetEditor/wwwroot/images/RecordMode5_en.png index 1e5621a9..3a22855c 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/RecordMode5_en.png and b/AudioCuesheetEditor/wwwroot/images/RecordMode5_en.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/Splitpoints1_de.png b/AudioCuesheetEditor/wwwroot/images/Splitpoints1_de.png new file mode 100644 index 00000000..9e4c93f0 Binary files /dev/null and b/AudioCuesheetEditor/wwwroot/images/Splitpoints1_de.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/Splitpoints1_en.png b/AudioCuesheetEditor/wwwroot/images/Splitpoints1_en.png new file mode 100644 index 00000000..db2d5659 Binary files /dev/null and b/AudioCuesheetEditor/wwwroot/images/Splitpoints1_en.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/TrackLinking1_de.png b/AudioCuesheetEditor/wwwroot/images/TrackLinking1_de.png index d9c7e9b1..99042c03 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/TrackLinking1_de.png and b/AudioCuesheetEditor/wwwroot/images/TrackLinking1_de.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/TrackLinking1_en.png b/AudioCuesheetEditor/wwwroot/images/TrackLinking1_en.png index 7842a42d..72b57917 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/TrackLinking1_en.png and b/AudioCuesheetEditor/wwwroot/images/TrackLinking1_en.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/UserInterface_BulkEdit1_de.png b/AudioCuesheetEditor/wwwroot/images/UserInterface_BulkEdit1_de.png index eca3b5c3..f6296644 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/UserInterface_BulkEdit1_de.png and b/AudioCuesheetEditor/wwwroot/images/UserInterface_BulkEdit1_de.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/UserInterface_BulkEdit1_en.png b/AudioCuesheetEditor/wwwroot/images/UserInterface_BulkEdit1_en.png index 793b3dad..2d21354a 100644 Binary files a/AudioCuesheetEditor/wwwroot/images/UserInterface_BulkEdit1_en.png and b/AudioCuesheetEditor/wwwroot/images/UserInterface_BulkEdit1_en.png differ diff --git a/AudioCuesheetEditor/wwwroot/images/Validation1_de.png b/AudioCuesheetEditor/wwwroot/images/Validation1_de.png deleted file mode 100644 index da00659a..00000000 Binary files a/AudioCuesheetEditor/wwwroot/images/Validation1_de.png and /dev/null differ diff --git a/AudioCuesheetEditor/wwwroot/images/Validation1_en.png b/AudioCuesheetEditor/wwwroot/images/Validation1_en.png deleted file mode 100644 index 551c207c..00000000 Binary files a/AudioCuesheetEditor/wwwroot/images/Validation1_en.png and /dev/null differ diff --git a/AudioCuesheetEditor/wwwroot/images/Validation2_de.png b/AudioCuesheetEditor/wwwroot/images/Validation2_de.png deleted file mode 100644 index 8ae35191..00000000 Binary files a/AudioCuesheetEditor/wwwroot/images/Validation2_de.png and /dev/null differ diff --git a/AudioCuesheetEditor/wwwroot/images/Validation2_en.png b/AudioCuesheetEditor/wwwroot/images/Validation2_en.png deleted file mode 100644 index 5b5c1b6d..00000000 Binary files a/AudioCuesheetEditor/wwwroot/images/Validation2_en.png and /dev/null differ diff --git a/AudioCuesheetEditorTests/AudioCuesheetEditorTests.csproj b/AudioCuesheetEditorTests/AudioCuesheetEditorTests.csproj index 17f4404d..2df2704d 100644 --- a/AudioCuesheetEditorTests/AudioCuesheetEditorTests.csproj +++ b/AudioCuesheetEditorTests/AudioCuesheetEditorTests.csproj @@ -1,16 +1,16 @@ - net6.0 - + net7.0 + enable false - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/AudioCuesheetEditorTests/Controller/CuesheetControllerTests.cs b/AudioCuesheetEditorTests/Controller/CuesheetControllerTests.cs deleted file mode 100644 index 494df8af..00000000 --- a/AudioCuesheetEditorTests/Controller/CuesheetControllerTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -//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 Microsoft.VisualStudio.TestTools.UnitTesting; -using AudioCuesheetEditor.Controller; -using System; -using System.Collections.Generic; -using System.Text; -using AudioCuesheetEditorTests.Utility; -using AudioCuesheetEditor.Model.AudioCuesheet; -using AudioCuesheetEditor.Model.Reflection; - -namespace AudioCuesheetEditor.Controller.Tests -{ - [TestClass()] - public class CuesheetControllerTests - { - [TestMethod()] - public void GetFieldIdentifierTest() - { - var testHelper = new TestHelper(); - var cuesheetController = testHelper.CuesheetController; - var cuesheet = new Cuesheet(); - Assert.IsNotNull(cuesheet); - var identifier = cuesheetController.GetFieldIdentifier(cuesheet, nameof(Cuesheet.Artist)); - Assert.IsTrue(identifier.StartsWith(String.Format("{0}.{1}", nameof(Cuesheet), nameof(Cuesheet.Artist)))); - Assert.AreEqual(cuesheetController.GetFieldIdentifier(cuesheet, nameof(Cuesheet.Artist)), identifier); - var fieldReference = FieldReference.Create(cuesheet, nameof(Cuesheet.Artist)); - Assert.AreEqual(cuesheetController.GetFieldIdentifier(fieldReference), identifier); - } - } -} \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Extensions/SessionStateContainerTests.cs b/AudioCuesheetEditorTests/Extensions/SessionStateContainerTests.cs index 90aa7c37..87b97a64 100644 --- a/AudioCuesheetEditorTests/Extensions/SessionStateContainerTests.cs +++ b/AudioCuesheetEditorTests/Extensions/SessionStateContainerTests.cs @@ -30,5 +30,21 @@ public void SessionStateContainerTest() container.ImportCuesheet = new Cuesheet(); Assert.IsTrue(importCuesheetChangedFired); } + + [TestMethod()] + public void SessionStateContainerFireCuesheetChangedTest() + { + var helper = new TestHelper(); + var manager = new TraceChangeManager(TestHelper.CreateLogger()); + var container = new SessionStateContainer(manager); + var cuesheetChangedFired = false; + container.CuesheetChanged += delegate + { + cuesheetChangedFired = true; + }; + Assert.IsFalse(cuesheetChangedFired); + container.Cuesheet.Import(new Cuesheet(), helper.ApplicationOptions); + Assert.IsTrue(cuesheetChangedFired); + } } } \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/AudioCuesheet/CatalogueNumberTests.cs b/AudioCuesheetEditorTests/Model/AudioCuesheet/CataloguenumberTests.cs similarity index 75% rename from AudioCuesheetEditorTests/Model/AudioCuesheet/CatalogueNumberTests.cs rename to AudioCuesheetEditorTests/Model/AudioCuesheet/CataloguenumberTests.cs index 26e46c90..aa7a8198 100644 --- a/AudioCuesheetEditorTests/Model/AudioCuesheet/CatalogueNumberTests.cs +++ b/AudioCuesheetEditorTests/Model/AudioCuesheet/CataloguenumberTests.cs @@ -1,4 +1,4 @@ -//This file is part of AudioCuesheetEditor. +//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 @@ -31,14 +31,13 @@ public class CataloguenumberTests public void CatalogueNumberTest() { var catalogueNumber = new Cataloguenumber(); - Assert.IsFalse(catalogueNumber.IsValid); - Assert.IsTrue(catalogueNumber.ValidationErrors.Count == 1); + Assert.AreEqual(Entity.ValidationStatus.NoValidation, catalogueNumber.Validate().Status); catalogueNumber.Value = "Testvalue"; - Assert.IsFalse(catalogueNumber.IsValid); + Assert.AreEqual(Entity.ValidationStatus.Error, catalogueNumber.Validate().Status); catalogueNumber.Value = "01234567891234567890"; - Assert.IsFalse(catalogueNumber.IsValid); + Assert.AreEqual(Entity.ValidationStatus.Error, catalogueNumber.Validate().Status); catalogueNumber.Value = "1234567890123"; - Assert.IsTrue(catalogueNumber.IsValid); + Assert.AreEqual(Entity.ValidationStatus.Success, catalogueNumber.Validate().Status); } } } \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/AudioCuesheet/CuesheetTests.cs b/AudioCuesheetEditorTests/Model/AudioCuesheet/CuesheetTests.cs index aea7dfe7..8eb7f310 100644 --- a/AudioCuesheetEditorTests/Model/AudioCuesheet/CuesheetTests.cs +++ b/AudioCuesheetEditorTests/Model/AudioCuesheet/CuesheetTests.cs @@ -1,5 +1,4 @@ -using AudioCuesheetEditor.Model.AudioCuesheet; -//This file is part of AudioCuesheetEditor. +//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 @@ -14,16 +13,18 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using AudioCuesheetEditorTests.Utility; -using System.Linq; +using AudioCuesheetEditor.Model.Entity; +using AudioCuesheetEditor.Model.IO; using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Model.IO.Import; -using System.IO; -using System.Text; using AudioCuesheetEditorTests.Properties; +using AudioCuesheetEditorTests.Utility; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; namespace AudioCuesheetEditor.Model.AudioCuesheet.Tests { @@ -52,11 +53,11 @@ public void CuesheetTest() var testHelper = new TestHelper(); var cuesheet = new Cuesheet(); Assert.IsNull(cuesheet.Audiofile); - var validationErrorAudioFile = cuesheet.GetValidationErrorsFiltered(String.Format("{0}.{1}", nameof(Cuesheet), nameof(Cuesheet.Audiofile))).FirstOrDefault(); - Assert.IsNotNull(validationErrorAudioFile); + var validationErrorAudioFile = cuesheet.Validate(x => x.Audiofile); + Assert.AreEqual(ValidationStatus.Error, validationErrorAudioFile.Status); cuesheet.Audiofile = new Audiofile("AudioFile01.ogg"); - validationErrorAudioFile = cuesheet.GetValidationErrorsFiltered(nameof(Cuesheet.Audiofile)).FirstOrDefault(); - Assert.IsNull(validationErrorAudioFile); + validationErrorAudioFile = cuesheet.Validate(x => x.Audiofile); + Assert.AreEqual(ValidationStatus.Success, validationErrorAudioFile.Status); } [TestMethod()] @@ -65,11 +66,11 @@ public void EmptyCuesheetTracksValidationTest() var testHelper = new TestHelper(); var cuesheet = new Cuesheet(); Assert.AreEqual(cuesheet.Tracks.Count, 0); - var validationErrorTracks = cuesheet.GetValidationErrorsFiltered(String.Format("{0}.{1}", nameof(Cuesheet), nameof(Cuesheet.Tracks))).FirstOrDefault(); - Assert.IsNotNull(validationErrorTracks); + var validationErrorTracks = cuesheet.Validate(x => x.Tracks); + Assert.AreEqual(ValidationStatus.Error, validationErrorTracks.Status); cuesheet.AddTrack(new Track(), testHelper.ApplicationOptions); - validationErrorTracks = cuesheet.GetValidationErrorsFiltered(nameof(Cuesheet.Tracks)).FirstOrDefault(); - Assert.IsNull(validationErrorTracks); + validationErrorTracks = cuesheet.Validate(x => x.Tracks); + Assert.AreEqual(ValidationStatus.Success, validationErrorTracks.Status); } [TestMethod()] @@ -84,13 +85,13 @@ public void MoveTrackTest() var track3 = new Track(); cuesheet.AddTrack(track3, testHelper.ApplicationOptions); Assert.AreEqual(cuesheet.Tracks.Count, 3); - Assert.IsTrue(track1.Position.Value == 1); + Assert.AreEqual((uint)1, track1.Position); cuesheet.MoveTrack(track1, MoveDirection.Up); - Assert.IsTrue(track1.Position.Value == 1); - Assert.IsTrue(track3.Position.Value == 3); + Assert.AreEqual((uint)1, track1.Position); + Assert.AreEqual((uint)3, track3.Position); cuesheet.MoveTrack(track3, MoveDirection.Down); - Assert.IsTrue(track3.Position.Value == 3); - Assert.IsTrue(track2.Position.Value == 2); + Assert.AreEqual((uint)3, track3.Position); + Assert.AreEqual((uint)2, track2.Position); cuesheet.MoveTrack(track2, MoveDirection.Up); Assert.AreEqual(track2, cuesheet.Tracks.ElementAt(0)); Assert.AreEqual(track1, cuesheet.Tracks.ElementAt(1)); @@ -205,8 +206,8 @@ public void MoveAndDeleteTrackTest() cuesheet.MoveTrack(track1, MoveDirection.Down); Assert.AreEqual(track1, cuesheet.Tracks.ElementAt(1)); Assert.AreEqual(track3, cuesheet.GetPreviousLinkedTrack(track1)); - Assert.AreEqual((uint)2, track1.Position.Value); - Assert.AreEqual((uint)3, track5.Position.Value); + Assert.AreEqual((uint)2, track1.Position); + Assert.AreEqual((uint)3, track5.Position); Assert.AreEqual(track1, cuesheet.GetPreviousLinkedTrack(track5)); Assert.AreEqual(track1End, track3.End); } @@ -234,6 +235,7 @@ public void ImportTest() textImportFile.TextImportScheme.SchemeCuesheet = TextImportScheme.DefaultSchemeCuesheet; textImportFile.TextImportScheme.SchemeTracks = TextImportScheme.DefaultSchemeTracks; Assert.IsNull(textImportFile.AnalyseException); + Assert.IsNotNull(textImportFile.Cuesheet); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 8); Assert.IsTrue(textImportFile.IsValid); @@ -242,15 +244,15 @@ public void ImportTest() cuesheet.Import(textImportFile.Cuesheet, testHelper.ApplicationOptions); Assert.IsNull(cuesheet.CDTextfile); - Assert.AreEqual(2, cuesheet.ValidationErrors.Count); - Assert.IsTrue(cuesheet.Tracks.ElementAt(0).IsValid); - Assert.IsTrue(cuesheet.Tracks.ElementAt(1).IsValid); - Assert.IsTrue(cuesheet.Tracks.ElementAt(2).IsValid); - Assert.IsTrue(cuesheet.Tracks.ElementAt(3).IsValid); - Assert.IsTrue(cuesheet.Tracks.ElementAt(4).IsValid); - Assert.IsTrue(cuesheet.Tracks.ElementAt(5).IsValid); - Assert.IsTrue(cuesheet.Tracks.ElementAt(6).IsValid); - Assert.IsTrue(cuesheet.Tracks.ElementAt(7).IsValid); + Assert.AreEqual(ValidationStatus.Success, cuesheet.Validate().Status); + Assert.AreEqual(ValidationStatus.Success, cuesheet.Tracks.ElementAt(0).Validate().Status); + Assert.AreEqual(ValidationStatus.Success, cuesheet.Tracks.ElementAt(1).Validate().Status); + Assert.AreEqual(ValidationStatus.Success, cuesheet.Tracks.ElementAt(2).Validate().Status); + Assert.AreEqual(ValidationStatus.Success, cuesheet.Tracks.ElementAt(3).Validate().Status); + Assert.AreEqual(ValidationStatus.Success, cuesheet.Tracks.ElementAt(4).Validate().Status); + Assert.AreEqual(ValidationStatus.Success, cuesheet.Tracks.ElementAt(5).Validate().Status); + Assert.AreEqual(ValidationStatus.Success, cuesheet.Tracks.ElementAt(6).Validate().Status); + Assert.AreEqual(ValidationStatus.Success, cuesheet.Tracks.ElementAt(7).Validate().Status); File.Delete(tempFile); } @@ -262,6 +264,7 @@ public void ImportTestCalculateEndCorrectly() var textImportFile = new TextImportfile(new MemoryStream(Resources.Textimport_Bug_54)); textImportFile.TextImportScheme.SchemeCuesheet = String.Empty; Assert.IsNull(textImportFile.AnalyseException); + Assert.IsNotNull(textImportFile.Cuesheet); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 39); Assert.IsTrue(textImportFile.IsValid); var cuesheet = new Cuesheet(); @@ -306,8 +309,9 @@ public void RecordTest() Assert.AreNotEqual(TimeSpan.Zero, track.End); Assert.AreEqual(0, track.End.Value.Milliseconds); cuesheet.StopRecording(testHelper.ApplicationOptions); - Assert.AreEqual(track.End.Value, track2.Begin.Value); - Assert.AreEqual(0, track2.End.Value.Milliseconds); + Assert.IsNotNull(track.End); + Assert.AreEqual(track.End, track2.Begin); + Assert.AreEqual(0, track2.End?.Milliseconds); } [TestMethod()] @@ -321,9 +325,9 @@ public void TrackRecalculationTest() cuesheet.AddTrack(track1, testHelper.ApplicationOptions); cuesheet.AddTrack(track2, testHelper.ApplicationOptions); cuesheet.AddTrack(track3, testHelper.ApplicationOptions); - Assert.AreEqual(track1.Position.Value, (uint)1); - Assert.AreEqual(track2.Position.Value, (uint)2); - Assert.AreEqual(track3.Position.Value, (uint)3); + Assert.AreEqual((uint)1, track1.Position); + Assert.AreEqual((uint)2, track2.Position); + Assert.AreEqual((uint)3, track3.Position); Assert.AreEqual(track1.Begin, TimeSpan.Zero); Assert.IsNull(track1.End); Assert.IsNull(track2.Begin); @@ -351,9 +355,11 @@ public void TrackOverlappingTest() cuesheet.AddTrack(track1, testHelper.ApplicationOptions); cuesheet.AddTrack(track2, testHelper.ApplicationOptions); cuesheet.AddTrack(track3, testHelper.ApplicationOptions); - Assert.AreEqual(track1.Position.Value, (uint)1); - Assert.AreEqual(track2.Position.Value, (uint)2); - Assert.AreEqual(track3.Position.Value, (uint)3); + Assert.AreEqual((uint)1, track1.Position); + Assert.AreEqual((uint)2, track2.Position); + Assert.AreEqual((uint)3, track3.Position); + var validationResult = cuesheet.Validate(x => x.Tracks); + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); track1.Position = 1; track2.Position = 1; track3.Position = 1; @@ -362,31 +368,36 @@ public void TrackOverlappingTest() track2.End = new TimeSpan(0, 5, 30); track3.Begin = new TimeSpan(0, 4, 54); track3.End = new TimeSpan(0, 8, 12); - var validationErrors = track1.GetValidationErrorsFiltered(nameof(Track.Position)); - Assert.IsTrue(validationErrors.Count >= 1); - validationErrors = track2.GetValidationErrorsFiltered(nameof(Track.Position)); - Assert.IsTrue(validationErrors.Count >= 1); - validationErrors = track3.GetValidationErrorsFiltered(nameof(Track.Position)); - Assert.IsTrue(validationErrors.Count >= 1); - validationErrors = track2.GetValidationErrorsFiltered(nameof(Track.Begin)); - Assert.IsTrue(validationErrors.Count >= 1); - validationErrors = track3.GetValidationErrorsFiltered(nameof(Track.Begin)); - Assert.IsTrue(validationErrors.Count >= 1); + validationResult = cuesheet.Validate(x => x.Tracks); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual(7, validationResult.ValidationMessages?.Count); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!" && x.Parameter != null && x.Parameter[2].Equals(track1.Position) && x.Parameter[7].Equals(track1.Begin) && x.Parameter[8].Equals(track1.End))); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!" && x.Parameter != null && x.Parameter[2].Equals(track2.Position) && x.Parameter[7].Equals(track2.Begin) && x.Parameter[8].Equals(track2.End))); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!" && x.Parameter != null && x.Parameter[2].Equals(track3.Position) && x.Parameter[7].Equals(track3.Begin) && x.Parameter[8].Equals(track3.End))); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!" && x.Parameter != null && x.Parameter[1].Equals(track1.Position) && x.Parameter[4].Equals(track1.Begin) && x.Parameter[5].Equals(track1.End) && x.Parameter[6].Equals(track2.Position) && x.Parameter[9].Equals(track2.Begin) && x.Parameter[10].Equals(track2.End))); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!" && x.Parameter != null && x.Parameter[1].Equals(track2.Position) && x.Parameter[4].Equals(track2.Begin) && x.Parameter[5].Equals(track2.End) && x.Parameter[6].Equals(track1.Position) && x.Parameter[9].Equals(track1.Begin) && x.Parameter[10].Equals(track1.End))); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!" && x.Parameter != null && x.Parameter[1].Equals(track2.Position) && x.Parameter[4].Equals(track2.Begin) && x.Parameter[5].Equals(track2.End) && x.Parameter[6].Equals(track3.Position) && x.Parameter[9].Equals(track3.Begin) && x.Parameter[10].Equals(track3.End))); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!" && x.Parameter != null && x.Parameter[1].Equals(track3.Position) && x.Parameter[4].Equals(track3.Begin) && x.Parameter[5].Equals(track3.End) && x.Parameter[6].Equals(track2.Position) && x.Parameter[9].Equals(track2.Begin) && x.Parameter[10].Equals(track2.End))); track2.End = new TimeSpan(0, 5, 15); - validationErrors = track2.GetValidationErrorsFiltered(nameof(Track.End)); - Assert.IsTrue(validationErrors.Count >= 1); + validationResult = cuesheet.Validate(x => x.Tracks); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!" && x.Parameter != null && x.Parameter[1].Equals(track2.Position) && x.Parameter[4].Equals(track2.Begin) && x.Parameter[5].Equals(track2.End) && x.Parameter[6].Equals(track3.Position) && x.Parameter[9].Equals(track3.Begin) && x.Parameter[10].Equals(track3.End))); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!" && x.Parameter != null && x.Parameter[1].Equals(track3.Position) && x.Parameter[4].Equals(track3.Begin) && x.Parameter[5].Equals(track3.End) && x.Parameter[6].Equals(track2.Position) && x.Parameter[9].Equals(track2.Begin) && x.Parameter[10].Equals(track2.End))); track1.Position = 1; track2.Position = 2; track3.Position = 3; - var clone = track1.Clone(); - validationErrors = clone.GetValidationErrorsFiltered(nameof(Track.Position)); - Assert.IsTrue(validationErrors.Count == 0); - clone.Position = 2; - validationErrors = clone.GetValidationErrorsFiltered(nameof(Track.Position)); - Assert.IsTrue(validationErrors.Count == 1); - clone.Position = 4; - validationErrors = clone.GetValidationErrorsFiltered(nameof(Track.Position)); - Assert.IsTrue(validationErrors.Count == 0); + validationResult = cuesheet.Validate(x => x.Tracks); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.AreEqual(4, validationResult.ValidationMessages?.Count); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!" && x.Parameter != null && x.Parameter[1].Equals(track1.Position) && x.Parameter[4].Equals(track1.Begin) && x.Parameter[5].Equals(track1.End) && x.Parameter[6].Equals(track2.Position) && x.Parameter[9].Equals(track2.Begin) && x.Parameter[10].Equals(track2.End))); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!" && x.Parameter != null && x.Parameter[1].Equals(track2.Position) && x.Parameter[4].Equals(track2.Begin) && x.Parameter[5].Equals(track2.End) && x.Parameter[6].Equals(track1.Position) && x.Parameter[9].Equals(track1.Begin) && x.Parameter[10].Equals(track1.End))); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!" && x.Parameter != null && x.Parameter[1].Equals(track2.Position) && x.Parameter[4].Equals(track2.Begin) && x.Parameter[5].Equals(track2.End) && x.Parameter[6].Equals(track3.Position) && x.Parameter[9].Equals(track3.Begin) && x.Parameter[10].Equals(track3.End))); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!" && x.Parameter != null && x.Parameter[1].Equals(track3.Position) && x.Parameter[4].Equals(track3.Begin) && x.Parameter[5].Equals(track3.End) && x.Parameter[6].Equals(track2.Position) && x.Parameter[9].Equals(track2.Begin) && x.Parameter[10].Equals(track2.End))); + track2.Begin = new TimeSpan(0, 2, 30); + track3.Begin = new TimeSpan(0, 5, 15); + validationResult = cuesheet.Validate(x => x.Tracks); + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); + } [TestMethod()] @@ -412,9 +423,9 @@ public void RemoveTrackTest() track5.End = new TimeSpan(0, 25, 0); Assert.AreEqual(5, cuesheet.Tracks.Count); cuesheet.RemoveTrack(track2); - Assert.AreEqual((uint)2, track3.Position.Value); - Assert.AreEqual((uint)3, track4.Position.Value); - Assert.AreEqual((uint)4, track5.Position.Value); + Assert.AreEqual((uint)2, track3.Position); + Assert.AreEqual((uint)3, track4.Position); + Assert.AreEqual((uint)4, track5.Position); testHelper = new TestHelper(); testHelper.ApplicationOptions.LinkTracksWithPreviousOne = true; cuesheet = new Cuesheet(); @@ -511,8 +522,8 @@ public void TrackPositionChangedTest() Assert.AreEqual(track2, cuesheet.Tracks.First()); Assert.AreEqual(track1, cuesheet.Tracks.Last()); track1.Position = 1; - Assert.AreEqual(track1, cuesheet.Tracks.First()); - Assert.AreEqual(track2, cuesheet.Tracks.Last()); + Assert.AreEqual(track2, cuesheet.Tracks.First()); + Assert.AreEqual(track1, cuesheet.Tracks.Last()); } [TestMethod()] @@ -541,6 +552,7 @@ public void ImportSamplesTest() textImportFile.TextImportScheme.SchemeCuesheet = TextImportScheme.DefaultSchemeCuesheet; textImportFile.TextImportScheme.SchemeTracks = TextImportScheme.DefaultSchemeTracks; Assert.IsNull(textImportFile.AnalyseException); + Assert.IsNotNull(textImportFile.Cuesheet); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 8); Assert.IsTrue(textImportFile.IsValid); @@ -564,6 +576,7 @@ public void ImportSamples2Test() textImportFile.TextImportScheme.SchemeCuesheet = null; textImportFile.TextImportScheme.SchemeTracks = TextImportScheme.DefaultSchemeTracks; Assert.IsNull(textImportFile.AnalyseException); + Assert.IsNotNull(textImportFile.Cuesheet); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 8); Assert.IsTrue(textImportFile.IsValid); @@ -579,5 +592,31 @@ public void ImportSamples2Test() Assert.AreEqual(8, cuesheet.Tracks.Count); Assert.AreEqual(new TimeSpan(1, 15, 54), cuesheet.Tracks.Last().End); } + + [TestMethod()] + public void ValidateTest() + { + var cuesheet = new Cuesheet(); + Assert.AreEqual(ValidationStatus.Error, cuesheet.Validate(x => x.Artist).Status); + cuesheet.Artist = "Testartist"; + Assert.AreEqual(ValidationStatus.Success, cuesheet.Validate(x => x.Artist).Status); + Assert.AreEqual(ValidationStatus.Error, cuesheet.Validate(x => x.Title).Status); + cuesheet.Title = "Testtitle"; + Assert.AreEqual(ValidationStatus.Success, cuesheet.Validate(x => x.Title).Status); + } + + [TestMethod()] + public void ImportProjectfileTest() + { + var fileContent = Encoding.UTF8.GetBytes("{\"Tracks\":[{\"Position\":1,\"Artist\":\"Artist 1\",\"Title\":\"Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:01:01\",\"Flags\":[\"4CH\"],\"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\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":8,\"Artist\":\"Artist 8\",\"Title\":\"Title 8\",\"Begin\":\"00:28:28\",\"End\":\"00:36:36\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":9,\"Artist\":\"Artist 9\",\"Title\":\"Title 9\",\"Begin\":\"00:36:36\",\"End\":\"00:45:45\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":10,\"Artist\":\"Artist 10\",\"Title\":\"Title 10\",\"Begin\":\"00:45:45\",\"End\":\"00:55:55\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true}],\"Artist\":\"CuesheetArtist\",\"Title\":\"CuesheetTitle\",\"Audiofile\":{\"Name\":\"AudioFile.mp3\"},\"CDTextfile\":{\"Name\":\"CDTextfile.cdt\"},\"Cataloguenumber\":{\"Value\":\"A123\"},\"SplitPoints\":[{\"Artist\":\"CuesheetArtist\",\"Title\":\"CuesheetTitle\",\"Moment\":\"00:30:00\"},{\"Artist\":\"CuesheetArtist\",\"Title\":\"CuesheetTitle\",\"Moment\":\"01:00:00\"}]}"); + var projectFileCuesheet = Projectfile.ImportFile(fileContent); + Assert.IsNotNull(projectFileCuesheet); + Assert.AreEqual(2, projectFileCuesheet.SplitPoints.Count); + var cuesheet = new Cuesheet(); + var testHelper = new TestHelper(); + cuesheet.Import(projectFileCuesheet, testHelper.ApplicationOptions); + Assert.AreEqual(2, cuesheet.SplitPoints.Count); + Assert.IsTrue(cuesheet.SplitPoints.All(x => x.Cuesheet == cuesheet)); + } } } \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/AudioCuesheet/TrackTests.cs b/AudioCuesheetEditorTests/Model/AudioCuesheet/TrackTests.cs index 023f4a57..c0451026 100644 --- a/AudioCuesheetEditorTests/Model/AudioCuesheet/TrackTests.cs +++ b/AudioCuesheetEditorTests/Model/AudioCuesheet/TrackTests.cs @@ -13,12 +13,11 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. +using AudioCuesheetEditor.Model.Entity; +using AudioCuesheetEditorTests.Utility; using Microsoft.VisualStudio.TestTools.UnitTesting; -using AudioCuesheetEditor.Model.AudioCuesheet; using System; using System.Collections.Generic; -using System.Text; -using AudioCuesheetEditorTests.Utility; using System.Linq; namespace AudioCuesheetEditor.Model.AudioCuesheet.Tests @@ -104,11 +103,51 @@ public void CheckPositionInCuesheetTest() Position = 3, End = new TimeSpan(0, 5, 0) }; + var track2 = new Track + { + Position = 2, + Begin = track1.End, + End = new TimeSpan(0, 8, 23) + }; cuesheet.AddTrack(track1, testHelper.ApplicationOptions); - var validationErrors = track1.GetValidationErrorsFiltered(nameof(Track.Position)); - Assert.IsTrue(validationErrors.Any(x => x.Message.Message.Contains(" of this track does not match track position in cuesheet. Please correct the"))); + cuesheet.AddTrack(track2, testHelper.ApplicationOptions); + var validationResult = track1.Validate(x => x.Position); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "Track({0},{1},{2},{3},{4}) does not have the correct position '{5}'!" + && x.Parameter != null && x.Parameter[0].Equals(track1.Position) && x.Parameter[3].Equals(track1.Begin) && x.Parameter[4].Equals(track1.End) && x.Parameter[5].Equals(1))); track1.Position = 1; - Assert.IsTrue(track1.GetValidationErrorsFiltered(nameof(Track.Position)).Count == 0); + validationResult = track1.Validate(x => x.Position); + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); + validationResult = track2.Validate(x => x.Position); + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); + track1.Position = 3; + track2.Position = 5; + validationResult = track1.Validate(x => x.Position); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "Track({0},{1},{2},{3},{4}) does not have the correct position '{5}'!" + && x.Parameter != null && x.Parameter[0].Equals(track1.Position) && x.Parameter[3].Equals(track1.Begin) && x.Parameter[4].Equals(track1.End) && x.Parameter[5].Equals(1))); + validationResult = track2.Validate(x => x.Position); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Message == "Track({0},{1},{2},{3},{4}) does not have the correct position '{5}'!" + && x.Parameter != null && x.Parameter[0].Equals(track2.Position) && x.Parameter[3].Equals(track2.Begin) && x.Parameter[4].Equals(track2.End) && x.Parameter[5].Equals(2))); + cuesheet = new Cuesheet(); + track1 = new Track() + { + Artist = "Testartist 1", + Title = "Testtitle 1" + }; + cuesheet.AddTrack(track1, testHelper.ApplicationOptions); + track2 = new Track() + { + Artist = "Testartist 2", + Title = "Testtitle 2" + }; + cuesheet.AddTrack(track2, testHelper.ApplicationOptions); + Assert.IsNotNull(track1.Begin); + Assert.IsNull(track1.End); + Assert.IsNull(track2.Begin); + Assert.IsNull(track2.End); + validationResult = track1.Validate(x => x.Position); + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); + validationResult = track2.Validate(x => x.Position); + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); } [TestMethod()] @@ -149,5 +188,40 @@ public void CopyValuesTest() Assert.IsTrue(destinationTracks.All(x => x.PreGap == sourceTrack.PreGap)); Assert.IsTrue(destinationTracks.All(x => x.PostGap == sourceTrack.PostGap)); } + + [TestMethod()] + public void CloneTest() + { + var track = new Track() + { + Begin = TimeSpan.Zero, + Artist = "Testartist", + Title = "Testttitle" + }; + var cuesheet = new Cuesheet(); + cuesheet.AddTrack(track); + var validationResult = track.Validate(); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Parameter != null && x.Parameter.Contains(nameof(Track.End)))); + var clone = track.Clone(); + clone.End = new TimeSpan(0, 2, 32); + validationResult = clone.Validate(); + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); + Assert.IsNull(track.End); + } + + [TestMethod()] + public void SetCuesheetTest() + { + var track = new Track(); + Assert.IsNull(track.Cuesheet); + var cuesheet = new Cuesheet(); + cuesheet.AddTrack(track); + Assert.AreEqual(cuesheet, track.Cuesheet); + track.Cuesheet = new Cuesheet(); + Assert.AreEqual(cuesheet, track.Cuesheet); + track.Cuesheet = null; + Assert.IsNull(track.Cuesheet); + } } } \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/Entity/FieldReferenceTests.cs b/AudioCuesheetEditorTests/Model/Entity/FieldReferenceTests.cs deleted file mode 100644 index f371728b..00000000 --- a/AudioCuesheetEditorTests/Model/Entity/FieldReferenceTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -//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 Microsoft.VisualStudio.TestTools.UnitTesting; -using AudioCuesheetEditor.Model.Entity; -using System; -using System.Collections.Generic; -using System.Text; - -namespace AudioCuesheetEditor.Model.Reflection.Tests -{ - [TestClass()] - public class FieldReferenceTests - { - public class FieldReferenceTestClass - { - public String Property1 { get; set; } - public int Property2 { get; set; } - } - - [TestMethod()] - public void CreateTest() - { - var testObject = new FieldReferenceTestClass(); - var fieldReference = FieldReference.Create(testObject, nameof(FieldReferenceTestClass.Property1)); - Assert.AreEqual(fieldReference.CompleteName, "FieldReferenceTestClass.Property1"); - fieldReference = FieldReference.Create(testObject, nameof(FieldReferenceTestClass.Property2)); - Assert.AreEqual(fieldReference.CompleteName, "FieldReferenceTestClass.Property2"); - } - } -} \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/Entity/ValidateableTests.cs b/AudioCuesheetEditorTests/Model/Entity/ValidateableTests.cs index 9cb54106..1488cc14 100644 --- a/AudioCuesheetEditorTests/Model/Entity/ValidateableTests.cs +++ b/AudioCuesheetEditorTests/Model/Entity/ValidateableTests.cs @@ -13,48 +13,69 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. +using AudioCuesheetEditorTests.Utility; using Microsoft.VisualStudio.TestTools.UnitTesting; -using AudioCuesheetEditor.Model.Entity; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using AudioCuesheetEditor.Model.Reflection; -using AudioCuesheetEditorTests.Utility; namespace AudioCuesheetEditor.Model.Entity.Tests { - public class ValidateableTestClass : Validateable + public class ValidateableTestClass : Validateable { - private String testProperty; - public String TestProperty + private String? testProperty; + private int? testProperty2; + + public String? TestProperty { - get { return testProperty; } - set { testProperty = value; OnValidateablePropertyChanged(); } + get => testProperty; + set + { + testProperty = value; + OnValidateablePropertyChanged(); + } + } + public int? TestProperty2 + { + get => testProperty2; + set + { + testProperty2 = value; + OnValidateablePropertyChanged(); + } } - protected override void Validate() + protected override ValidationResult Validate(string property) { - if (String.IsNullOrEmpty(TestProperty)) + ValidationStatus validationStatus = ValidationStatus.NoValidation; + List? validationMessages = null; + switch (property) { - validationErrors.Add(new ValidationError(FieldReference.Create(this, nameof(TestProperty)), ValidationErrorType.Warning, "Testmessage")); + case nameof(TestProperty): + validationStatus = ValidationStatus.Success; + if (String.IsNullOrEmpty(TestProperty)) + { + validationMessages ??= new(); + validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(TestProperty))); + } + break; } + return ValidationResult.Create(validationStatus, validationMessages); } } [TestClass()] public class ValidateableTests { [TestMethod()] - public void GetValidationErrorsTest() + public void ValidateTest() { var testObject = new ValidateableTestClass { TestProperty = String.Empty }; - var testhelper = new TestHelper(); - Assert.IsNull(testObject.GetValidationErrors(testhelper.Localizer, validationErrorFilterType: ValidationErrorFilterType.ErrorOnly)); - Assert.IsTrue(testObject.ValidationErrors.Count > 0); - Assert.IsNotNull(testObject.GetValidationErrors(testhelper.Localizer, nameof(ValidateableTestClass.TestProperty))); + Assert.AreEqual(ValidationStatus.Error, testObject.Validate().Status); + Assert.IsNotNull(testObject.Validate().ValidationMessages); + Assert.IsTrue(testObject.Validate().ValidationMessages?.Count == 1); + testObject.TestProperty = "Test"; + Assert.AreEqual(ValidationStatus.Success, testObject.Validate().Status); } } } \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/Entity/ValidationResultTests.cs b/AudioCuesheetEditorTests/Model/Entity/ValidationResultTests.cs new file mode 100644 index 00000000..c0b1e4e3 --- /dev/null +++ b/AudioCuesheetEditorTests/Model/Entity/ValidationResultTests.cs @@ -0,0 +1,70 @@ +//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 Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; + +namespace AudioCuesheetEditor.Model.Entity.Tests +{ + [TestClass()] + public class ValidationResultTests + { + [TestMethod()] + public void CreateTest() + { + var validationResult = ValidationResult.Create(ValidationStatus.NoValidation); + Assert.AreEqual(ValidationStatus.NoValidation, validationResult.Status); + Assert.IsNull(validationResult.ValidationMessages); + validationResult = ValidationResult.Create(ValidationStatus.Success); + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); + Assert.IsNull(validationResult.ValidationMessages); + validationResult = ValidationResult.Create(ValidationStatus.Error); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.IsNull(validationResult.ValidationMessages); + var validationMessages = new List() { new ValidationMessage("Testmessage!") }; + validationResult = ValidationResult.Create(ValidationStatus.Success, validationMessages); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.IsNotNull(validationResult.ValidationMessages); + validationResult = ValidationResult.Create(ValidationStatus.NoValidation, validationMessages); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.IsNotNull(validationResult.ValidationMessages); + } + + [TestMethod()] + public void StatusTest() + { + var result = new ValidationResult(); + Assert.IsNull(result.ValidationMessages); + Assert.AreEqual(ValidationStatus.NoValidation, result.Status); + result.Status = ValidationStatus.Success; + Assert.IsNull(result.ValidationMessages); + Assert.AreEqual(ValidationStatus.Success, result.Status); + } + + [TestMethod()] + public void ErrorTest() + { + var result = new ValidationResult(); + Assert.IsNull(result.ValidationMessages); + Assert.AreEqual(ValidationStatus.NoValidation, result.Status); + result.Status = ValidationStatus.Success; + Assert.IsNull(result.ValidationMessages); + Assert.AreEqual(ValidationStatus.Success, result.Status); + result.ValidationMessages = new List() { new ValidationMessage("Unit Test error1"), new ValidationMessage("Unit Test error2") }; + Assert.IsNotNull(result.ValidationMessages); + Assert.AreEqual(ValidationStatus.Error, result.Status); + } + } +} \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/IO/Audio/AudioFileTests.cs b/AudioCuesheetEditorTests/Model/IO/Audio/AudiofileTests.cs similarity index 81% rename from AudioCuesheetEditorTests/Model/IO/Audio/AudioFileTests.cs rename to AudioCuesheetEditorTests/Model/IO/Audio/AudiofileTests.cs index 60c699d1..1da1b44e 100644 --- a/AudioCuesheetEditorTests/Model/IO/Audio/AudioFileTests.cs +++ b/AudioCuesheetEditorTests/Model/IO/Audio/AudiofileTests.cs @@ -1,4 +1,4 @@ -//This file is part of AudioCuesheetEditor. +//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 @@ -31,22 +31,22 @@ public void AudioFileTest() var audioFile = new Audiofile("test.mp3"); Assert.IsNull(audioFile.ContentStream); Assert.IsFalse(audioFile.IsContentStreamLoaded); - Assert.IsNotNull(audioFile.FileName); + Assert.IsNotNull(audioFile.Name); Assert.AreEqual(audioFile.AudioFileType, "MP3"); audioFile = new Audiofile("Test"); Assert.AreEqual(audioFile.AudioFileType, String.Empty); - Assert.IsNotNull(audioFile.FileName); + Assert.IsNotNull(audioFile.Name); var codec = Audiofile.AudioCodecs.Single(x => x.FileExtension == ".ogg"); var httpClient = new System.Net.Http.HttpClient(); audioFile = new Audiofile("test", "TestobjectURL", codec, httpClient); - Assert.IsNotNull(audioFile.FileName); - Assert.AreEqual("test.ogg", audioFile.FileName); + Assert.IsNotNull(audioFile.Name); + Assert.AreEqual("test.ogg", audioFile.Name); Assert.AreEqual(audioFile.AudioFileType, "OGG"); Assert.IsNotNull(audioFile.ObjectURL); Assert.IsTrue(audioFile.PlaybackPossible); codec = Audiofile.AudioCodecs.Single(x => x.FileExtension == ".mp3"); - var audioFile2 = new Audiofile(audioFile.FileName, "TestObjectURL2", codec, httpClient); - Assert.AreEqual("test.mp3", audioFile2.FileName); + var audioFile2 = new Audiofile(audioFile.Name, "TestObjectURL2", codec, httpClient); + Assert.AreEqual("test.mp3", audioFile2.Name); } } } \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/IO/CuesheetFileTests.cs b/AudioCuesheetEditorTests/Model/IO/CuesheetFileTests.cs deleted file mode 100644 index 5eef0e44..00000000 --- a/AudioCuesheetEditorTests/Model/IO/CuesheetFileTests.cs +++ /dev/null @@ -1,253 +0,0 @@ -//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 AudioCuesheetEditorTests.Properties; -using AudioCuesheetEditorTests.Utility; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.IO; -using System.Linq; -using System.Text; - -namespace AudioCuesheetEditor.Model.IO.Tests -{ - [TestClass()] - public class CuesheetfileTests - { - [TestMethod()] - public void GenerateCuesheetFileTest() - { - var testHelper = new TestHelper(); - Cuesheet cuesheet = new() - { - Artist = "Demo Artist", - Title = "Demo Title", - Audiofile = new Audiofile("Testfile.mp3") - }; - var begin = TimeSpan.Zero; - for (int i = 1; i < 25; i++) - { - var track = new Track - { - Artist = String.Format("Demo Track Artist {0}", i), - Title = String.Format("Demo Track Title {0}", i), - Begin = begin - }; - begin = begin.Add(new TimeSpan(0, i, i)); - track.End = begin; - cuesheet.AddTrack(track, testHelper.ApplicationOptions); - } - var cuesheetFile = new Cuesheetfile(cuesheet); - var generatedFile = cuesheetFile.GenerateCuesheetFile(); - Assert.IsNotNull(generatedFile); - var fileName = Path.GetTempFileName(); - File.WriteAllBytes(fileName, generatedFile); - var fileContent = File.ReadAllLines(fileName); - Assert.AreEqual(fileContent[0], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, cuesheet.Title)); - Assert.AreEqual(fileContent[1], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, cuesheet.Artist)); - Assert.AreEqual(fileContent[2], String.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, cuesheet.Audiofile.FileName, cuesheet.Audiofile.AudioFileType)); - var position = 1; - for (int i = 3; i < fileContent.Length; i += 4) - { - var track = cuesheet.Tracks.Single(x => x.Position == position); - position++; - Assert.AreEqual(fileContent[i], String.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position, CuesheetConstants.CuesheetTrackAudio)); - Assert.AreEqual(fileContent[i + 1], String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackTitle, track.Title)); - Assert.AreEqual(fileContent[i + 2], String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackArtist, track.Artist)); - Assert.AreEqual(fileContent[i + 3], String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackIndex01, Math.Floor(track.Begin.Value.TotalMinutes), track.Begin.Value.Seconds, track.Begin.Value.Milliseconds / 75)); - } - File.Delete(fileName); - cuesheet.CDTextfile = new CDTextfile("Testfile.cdt"); - cuesheet.Cataloguenumber.Value = "0123456789123"; - generatedFile = cuesheetFile.GenerateCuesheetFile(); - Assert.IsNotNull(generatedFile); - fileName = Path.GetTempFileName(); - File.WriteAllBytes(fileName, generatedFile); - fileContent = File.ReadAllLines(fileName); - Assert.AreEqual(fileContent[0], String.Format("{0} {1}", CuesheetConstants.CuesheetCatalogueNumber, cuesheet.Cataloguenumber.Value)); - Assert.AreEqual(fileContent[1], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetCDTextfile, cuesheet.CDTextfile.FileName)); - File.Delete(fileName); - cuesheet.CDTextfile = new CDTextfile("Testfile.cdt"); - cuesheet.Cataloguenumber.Value = "Testvalue"; - generatedFile = cuesheetFile.GenerateCuesheetFile(); - Assert.IsNull(generatedFile); - } - - [TestMethod()] - public void TestExportWithPreGapAndPostGap() - { - var testHelper = new TestHelper(); - Cuesheet cuesheet = new() - { - Artist = "Demo Artist", - Title = "Demo Title", - Audiofile = new Audiofile("Testfile.mp3") - }; - var begin = TimeSpan.Zero; - for (int i = 1; i < 25; i++) - { - var track = new Track - { - Artist = String.Format("Demo Track Artist {0}", i), - Title = String.Format("Demo Track Title {0}", i), - Begin = begin - }; - begin = begin.Add(new TimeSpan(0, i, i)); - track.End = begin; - var rand = new Random(); - var flagsToAdd = rand.Next(1, 3); - for (int x = 0; x < flagsToAdd; x++) - { - track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); - } - track.PostGap = new TimeSpan(0, 0, 2); - track.PreGap = new TimeSpan(0, 0, 3); - cuesheet.AddTrack(track, testHelper.ApplicationOptions); - } - var cuesheetFile = new Cuesheetfile(cuesheet); - var generatedFile = cuesheetFile.GenerateCuesheetFile(); - Assert.IsNotNull(generatedFile); - var fileName = Path.GetTempFileName(); - File.WriteAllBytes(fileName, generatedFile); - var fileContent = File.ReadAllLines(fileName); - Assert.AreEqual(fileContent[0], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, cuesheet.Title)); - Assert.AreEqual(fileContent[1], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, cuesheet.Artist)); - Assert.AreEqual(fileContent[2], String.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, cuesheet.Audiofile.FileName, cuesheet.Audiofile.AudioFileType)); - var position = 1; - for (int i = 3; i < fileContent.Length; i += 7) - { - var track = cuesheet.Tracks.Single(x => x.Position == position); - position++; - Assert.AreEqual(String.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position, CuesheetConstants.CuesheetTrackAudio), fileContent[i]); - Assert.AreEqual(String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackTitle, track.Title), fileContent[i + 1]); - Assert.AreEqual(String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackArtist, track.Artist), fileContent[i + 2]); - Assert.AreEqual(String.Format("{0}{1}{2} {3}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackFlags, String.Join(" ", track.Flags.Select(x => x.CuesheetLabel))), fileContent[i + 3]); - Assert.AreEqual(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackPreGap, Math.Floor(track.PreGap.Value.TotalMinutes), track.PreGap.Value.Seconds, track.PreGap.Value.Milliseconds / 75), fileContent[i + 4]); - Assert.AreEqual(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackIndex01, Math.Floor(track.Begin.Value.TotalMinutes), track.Begin.Value.Seconds, track.Begin.Value.Milliseconds / 75), fileContent[i + 5]); - Assert.AreEqual(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackPostGap, Math.Floor(track.PostGap.Value.TotalMinutes), track.PostGap.Value.Seconds, track.PostGap.Value.Milliseconds / 75), fileContent[i + 6]); - } - File.Delete(fileName); - } - - [TestMethod()] - public void TestExportFlags() - { - var testHelper = new TestHelper(); - Cuesheet cuesheet = new() - { - Artist = "Demo Artist", - Title = "Demo Title", - Audiofile = new Audiofile("Testfile.mp3") - }; - var begin = TimeSpan.Zero; - for (int i = 1; i < 25; i++) - { - var track = new Track - { - Artist = String.Format("Demo Track Artist {0}", i), - Title = String.Format("Demo Track Title {0}", i), - Begin = begin - }; - begin = begin.Add(new TimeSpan(0, i, i)); - track.End = begin; - var rand = new Random(); - var flagsToAdd = rand.Next(1, 3); - for (int x = 0; x < flagsToAdd; x++) - { - track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); - } - cuesheet.AddTrack(track, testHelper.ApplicationOptions); - } - var cuesheetFile = new Cuesheetfile(cuesheet); - var generatedFile = cuesheetFile.GenerateCuesheetFile(); - Assert.IsNotNull(generatedFile); - var fileName = Path.GetTempFileName(); - File.WriteAllBytes(fileName, generatedFile); - var fileContent = File.ReadAllLines(fileName); - Assert.AreEqual(fileContent[0], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, cuesheet.Title)); - Assert.AreEqual(fileContent[1], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, cuesheet.Artist)); - Assert.AreEqual(fileContent[2], String.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, cuesheet.Audiofile.FileName, cuesheet.Audiofile.AudioFileType)); - var position = 1; - for (int i = 3; i < fileContent.Length; i += 5) - { - var track = cuesheet.Tracks.Single(x => x.Position == position); - position++; - Assert.AreEqual(fileContent[i], String.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position, CuesheetConstants.CuesheetTrackAudio)); - Assert.AreEqual(fileContent[i + 1], String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackTitle, track.Title)); - Assert.AreEqual(fileContent[i + 2], String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackArtist, track.Artist)); - Assert.AreEqual(fileContent[i + 3], String.Format("{0}{1}{2} {3}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackFlags, String.Join(" ", track.Flags.Select(x => x.CuesheetLabel)))); - Assert.AreEqual(fileContent[i + 4], String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackIndex01, Math.Floor(track.Begin.Value.TotalMinutes), track.Begin.Value.Seconds, track.Begin.Value.Milliseconds / 75)); - } - File.Delete(fileName); - } - - [TestMethod()] - public void TestExportWithIncorrectPositions() - { - var testHelper = new TestHelper(); - Cuesheet cuesheet = new() - { - Artist = "Demo Artist", - Title = "Demo Title", - Audiofile = new Audiofile("Testfile.mp3") - }; - var begin = TimeSpan.Zero; - var random = new Random(); - for (int i = 1; i < 6; i++) - { - var track = new Track - { - Artist = String.Format("Demo Track Artist {0}", i), - Title = String.Format("Demo Track Title {0}", i), - Begin = begin, - Position = (uint)(i + random.Next(1, 10)) - }; - begin = begin.Add(new TimeSpan(0, i, i)); - track.End = begin; - cuesheet.AddTrack(track, testHelper.ApplicationOptions); - } - var cuesheetFile = new Cuesheetfile(cuesheet); - Assert.IsFalse(cuesheetFile.IsExportable); - //Rearrange positions - cuesheet.Tracks.ElementAt(0).Position = 1; - cuesheet.Tracks.ElementAt(1).Position = 2; - cuesheet.Tracks.ElementAt(2).Position = 3; - cuesheet.Tracks.ElementAt(3).Position = 4; - cuesheet.Tracks.ElementAt(4).Position = 5; - Assert.IsTrue(cuesheetFile.IsExportable); - var generatedFile = cuesheetFile.GenerateCuesheetFile(); - Assert.IsNotNull(generatedFile); - var fileName = Path.GetTempFileName(); - File.WriteAllBytes(fileName, generatedFile); - var fileContent = File.ReadAllLines(fileName); - Assert.AreEqual(fileContent[0], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, cuesheet.Title)); - Assert.AreEqual(fileContent[1], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, cuesheet.Artist)); - Assert.AreEqual(fileContent[2], String.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, cuesheet.Audiofile.FileName, cuesheet.Audiofile.AudioFileType)); - var position = 1; - for (int i = 3; i < fileContent.Length; i += 4) - { - var track = cuesheet.Tracks.ElementAt(position - 1); - Assert.AreEqual(String.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, position, CuesheetConstants.CuesheetTrackAudio), fileContent[i]); - Assert.AreEqual(String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackTitle, track.Title), fileContent[i + 1]); - Assert.AreEqual(String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackArtist, track.Artist), fileContent[i + 2]); - Assert.AreEqual(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackIndex01, Math.Floor(track.Begin.Value.TotalMinutes), track.Begin.Value.Seconds, track.Begin.Value.Milliseconds / 75), fileContent[i + 3]); - position++; - } - File.Delete(fileName); - } - } -} \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/IO/Export/ExportProfileTests.cs b/AudioCuesheetEditorTests/Model/IO/Export/ExportProfileTests.cs deleted file mode 100644 index 5e45d892..00000000 --- a/AudioCuesheetEditorTests/Model/IO/Export/ExportProfileTests.cs +++ /dev/null @@ -1,236 +0,0 @@ -//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 Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using AudioCuesheetEditorTests.Utility; -using AudioCuesheetEditor.Model.AudioCuesheet; -using System.IO; -using AudioCuesheetEditor.Model.IO.Audio; - -namespace AudioCuesheetEditor.Model.IO.Export.Tests -{ - [TestClass()] - public class ExportProfileTests - { - [TestMethod()] - public void ExportprofileTest() - { - var testHelper = new TestHelper(); - //Prepare cuesheet - Cuesheet cuesheet = new() - { - Artist = "Demo Artist", - Title = "Demo Title", - Audiofile = new Audiofile("Testfile.mp3") - }; - var begin = TimeSpan.Zero; - for (int i = 1; i < 25; i++) - { - var track = new Track - { - Artist = String.Format("Demo Track Artist {0}", i), - Title = String.Format("Demo Track Title {0}", i), - Begin = begin - }; - begin = begin.Add(new TimeSpan(0, i, i)); - track.End = begin; - cuesheet.AddTrack(track, testHelper.ApplicationOptions); - var rand = new Random(); - var flagsToAdd = rand.Next(0, 3); - if (flagsToAdd > 0) - { - for (int x = 0; x < flagsToAdd; x++) - { - track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); - } - } - } - - cuesheet.Cataloguenumber.Value = "Testcatalognumber"; - cuesheet.CDTextfile = new CDTextfile("Testfile.cdt"); - - //Test class - var exportProfile = new Exportprofile(); - exportProfile.SchemeHead.Scheme = "%Cuesheet.Artist%;%Cuesheet.Title%;%Cuesheet.Cataloguenumber%;%Cuesheet.CDTextfile%"; - Assert.IsTrue(exportProfile.SchemeHead.IsValid); - exportProfile.SchemeTracks.Scheme = "%Track.Position%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%"; - Assert.IsTrue(exportProfile.SchemeTracks.IsValid); - exportProfile.SchemeFooter.Scheme = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor"; - Assert.IsTrue(exportProfile.SchemeFooter.IsValid); - Assert.IsTrue(exportProfile.IsExportable); - var fileContent = exportProfile.GenerateExport(cuesheet); - Assert.IsNotNull(fileContent); - var tempFile = Path.GetTempFileName(); - File.WriteAllBytes(tempFile, fileContent); - var content = File.ReadAllLines(tempFile); - Assert.AreEqual(content[0], "Demo Artist;Demo Title;Testcatalognumber;Testfile.cdt"); - for (int i = 1; i < content.Length - 1;i++) - { - Assert.IsFalse(String.IsNullOrEmpty(content[i])); - Assert.AreNotEqual(content[i], ";;;;;"); - Assert.IsTrue(content[i].StartsWith(cuesheet.Tracks.ToList()[i - 1].Position + ";")); - } - Assert.AreEqual(content[^1], "Exported Demo Title from Demo Artist using AudioCuesheetEditor"); - - File.Delete(tempFile); - - exportProfile.SchemeHead.Scheme = "%Track.Position%;%Cuesheet.Artist%;"; - Assert.IsFalse(exportProfile.SchemeHead.IsValid); - Assert.AreEqual(exportProfile.SchemeHead.ValidationErrors.Count, 1); - - //Check multiline export - - exportProfile = new Exportprofile(); - exportProfile.SchemeHead.Scheme = "%Cuesheet.Artist%;%Cuesheet.Title%"; - Assert.IsTrue(exportProfile.SchemeHead.IsValid); - exportProfile.SchemeTracks.Scheme = String.Format("%Track.Position%{0}%Track.Artist%{1}%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%", Environment.NewLine, Environment.NewLine); - Assert.IsTrue(exportProfile.SchemeTracks.IsValid); - exportProfile.SchemeFooter.Scheme = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor"; - Assert.IsTrue(exportProfile.SchemeFooter.IsValid); - Assert.IsTrue(exportProfile.IsExportable); - fileContent = exportProfile.GenerateExport(cuesheet); - Assert.IsNotNull(fileContent); - tempFile = Path.GetTempFileName(); - File.WriteAllBytes(tempFile, fileContent); - content = File.ReadAllLines(tempFile); - Assert.AreEqual(content[0], "Demo Artist;Demo Title"); - var trackPosition = 0; - for (int i = 1; i < content.Length - 1; i += 3) - { - Assert.IsFalse(String.IsNullOrEmpty(content[i])); - Assert.AreNotEqual(content[i], ";;;;;"); - Assert.IsTrue(content[i].StartsWith(cuesheet.Tracks.ToList()[trackPosition].Position.Value.ToString())); - trackPosition++; - } - Assert.AreEqual(content[^1], "Exported Demo Title from Demo Artist using AudioCuesheetEditor"); - - File.Delete(tempFile); - - //Test flags - exportProfile = new Exportprofile(); - exportProfile.SchemeHead.Scheme = "%Cuesheet.Artist%;%Cuesheet.Title%;%Cuesheet.Cataloguenumber%;%Cuesheet.CDTextfile%"; - Assert.IsTrue(exportProfile.SchemeHead.IsValid); - exportProfile.SchemeTracks.Scheme = "%Track.Position%;%Track.Flags%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%"; - Assert.IsTrue(exportProfile.SchemeTracks.IsValid); - exportProfile.SchemeFooter.Scheme = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor"; - Assert.IsTrue(exportProfile.SchemeFooter.IsValid); - Assert.IsTrue(exportProfile.IsExportable); - fileContent = exportProfile.GenerateExport(cuesheet); - Assert.IsNotNull(fileContent); - tempFile = Path.GetTempFileName(); - File.WriteAllBytes(tempFile, fileContent); - content = File.ReadAllLines(tempFile); - Assert.AreEqual(content[0], "Demo Artist;Demo Title;Testcatalognumber;Testfile.cdt"); - for (int i = 1; i < content.Length - 1; i++) - { - Assert.IsFalse(String.IsNullOrEmpty(content[i])); - Assert.AreNotEqual(content[i], ";;;;;"); - Assert.IsTrue(content[i].StartsWith(cuesheet.Tracks.ToList()[i - 1].Position + ";")); - if (cuesheet.Tracks.ElementAt(i - 1).Flags.Count > 0) - { - var flags = cuesheet.Tracks.ElementAt(i - 1).Flags; - Assert.IsTrue(content[i].Contains(String.Join(" ", flags.Select(x => x.CuesheetLabel)))); - } - } - Assert.AreEqual(content[^1], "Exported Demo Title from Demo Artist using AudioCuesheetEditor"); - - File.Delete(tempFile); - } - - [TestMethod()] - public void TestExportWithPregapAndPostgap() - { - var testHelper = new TestHelper(); - //Prepare cuesheet - Cuesheet cuesheet = new() - { - Artist = "Demo Artist", - Title = "Demo Title", - Audiofile = new Audiofile("Testfile.mp3") - }; - var begin = TimeSpan.Zero; - for (int i = 1; i < 25; i++) - { - var track = new Track - { - Artist = String.Format("Demo Track Artist {0}", i), - Title = String.Format("Demo Track Title {0}", i), - Begin = begin, - PostGap = new TimeSpan(0, 0, 1), - PreGap = new TimeSpan(0, 0, 3) - }; - begin = begin.Add(new TimeSpan(0, i, i)); - track.End = begin; - cuesheet.AddTrack(track, testHelper.ApplicationOptions); - var rand = new Random(); - var flagsToAdd = rand.Next(0, 3); - if (flagsToAdd > 0) - { - for (int x = 0; x < flagsToAdd; x++) - { - track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); - } - } - } - - cuesheet.Cataloguenumber.Value = "Testcatalognumber"; - cuesheet.CDTextfile = new CDTextfile("Testfile.cdt"); - - var exportProfile = new Exportprofile(); - exportProfile.SchemeHead.Scheme = "%Cuesheet.Artist%;%Cuesheet.Title%;%Cuesheet.Cataloguenumber%;%Cuesheet.CDTextfile%"; - Assert.IsTrue(exportProfile.SchemeHead.IsValid); - exportProfile.SchemeTracks.Scheme = "%Track.Position%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%;%Track.PreGap%;%Track.PostGap%"; - Assert.IsTrue(exportProfile.SchemeTracks.IsValid); - exportProfile.SchemeFooter.Scheme = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor at %Date%"; - Assert.IsTrue(exportProfile.SchemeFooter.IsValid); - Assert.IsTrue(exportProfile.IsExportable); - var fileContent = exportProfile.GenerateExport(cuesheet); - Assert.IsNotNull(fileContent); - var tempFile = Path.GetTempFileName(); - File.WriteAllBytes(tempFile, fileContent); - var content = File.ReadAllLines(tempFile); - Assert.AreEqual(content[0], "Demo Artist;Demo Title;Testcatalognumber;Testfile.cdt"); - for (int i = 1; i < content.Length - 1; i++) - { - Assert.IsFalse(String.IsNullOrEmpty(content[i])); - Assert.AreNotEqual(content[i], ";;;;;"); - Assert.IsTrue(content[i].StartsWith(cuesheet.Tracks.ToList()[i - 1].Position + ";")); - } - Assert.AreEqual(content[^1], String.Format("Exported Demo Title from Demo Artist using AudioCuesheetEditor at {0}", DateTime.Now.ToShortDateString())); - File.Delete(tempFile); - } - - [TestMethod()] - public void IsExportableTest() - { - var exportProfile = new Exportprofile(); - exportProfile.SchemeHead.Scheme = "%Cuesheet.Artist%;%Cuesheet.Title%;%Cuesheet.Cataloguenumber%;%Cuesheet.CDTextfile%"; - Assert.IsTrue(exportProfile.SchemeHead.IsValid); - exportProfile.SchemeTracks.Scheme = "%Track.Position%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%;%Track.PreGap%;%Track.PostGap%"; - Assert.IsTrue(exportProfile.SchemeTracks.IsValid); - exportProfile.SchemeFooter.Scheme = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor at %Date%"; - Assert.IsTrue(exportProfile.SchemeFooter.IsValid); - Assert.IsTrue(exportProfile.IsExportable); - exportProfile.SchemeFooter.Scheme = "Exported %Track.Title% from %Cuesheet.Artist% using AudioCuesheetEditor at %Date%"; - Assert.IsFalse(exportProfile.SchemeFooter.IsValid); - Assert.IsFalse(exportProfile.IsExportable); - } - } -} \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/IO/Export/ExportfileGeneratorTests.cs b/AudioCuesheetEditorTests/Model/IO/Export/ExportfileGeneratorTests.cs new file mode 100644 index 00000000..a06551f6 --- /dev/null +++ b/AudioCuesheetEditorTests/Model/IO/Export/ExportfileGeneratorTests.cs @@ -0,0 +1,725 @@ +//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 Microsoft.VisualStudio.TestTools.UnitTesting; +using AudioCuesheetEditor.Model.IO.Export; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AudioCuesheetEditorTests.Utility; +using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO.Audio; +using System.IO; +using AudioCuesheetEditor.Model.Entity; +using System.Diagnostics.Metrics; + +namespace AudioCuesheetEditor.Model.IO.Export.Tests +{ + [TestClass()] + public class ExportfileGeneratorTests + { + + [TestMethod()] + public void GenerateCuesheetFilesTest() + { + var testHelper = new TestHelper(); + Cuesheet cuesheet = new() + { + Artist = "Demo Artist", + Title = "Demo Title", + Audiofile = new Audiofile("Testfile.mp3") + }; + var begin = TimeSpan.Zero; + for (int i = 1; i < 25; i++) + { + var track = new Track + { + Artist = String.Format("Demo Track Artist {0}", i), + Title = String.Format("Demo Track Title {0}", i), + Begin = begin + }; + begin = begin.Add(new TimeSpan(0, i, i)); + track.End = begin; + cuesheet.AddTrack(track, testHelper.ApplicationOptions); + } + var generator = new ExportfileGenerator(ExportType.Cuesheet, cuesheet, applicationOptions: testHelper.ApplicationOptions); + var generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(1, generatedFiles.Count); + Assert.AreEqual(Exportfile.DefaultCuesheetFilename, generatedFiles.First().Name); + var content = generatedFiles.First().Content; + Assert.IsNotNull(content); + var fileName = Path.GetTempFileName(); + File.WriteAllBytes(fileName, content); + var fileContent = File.ReadAllLines(fileName); + Assert.AreEqual(fileContent[0], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, cuesheet.Title)); + Assert.AreEqual(fileContent[1], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, cuesheet.Artist)); + Assert.AreEqual(fileContent[2], String.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, cuesheet.Audiofile.Name, cuesheet.Audiofile.AudioFileType)); + var position = 1; + for (int i = 3; i < fileContent.Length; i += 4) + { + var track = cuesheet.Tracks.Single(x => x.Position == position); + position++; + Assert.AreEqual(fileContent[i], String.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position, CuesheetConstants.CuesheetTrackAudio)); + Assert.AreEqual(fileContent[i + 1], String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackTitle, track.Title)); + Assert.AreEqual(fileContent[i + 2], String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackArtist, track.Artist)); + var trackBegin = track.Begin; + Assert.IsNotNull(trackBegin); + Assert.AreEqual(fileContent[i + 3], String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackIndex01, Math.Floor(trackBegin.Value.TotalMinutes), trackBegin.Value.Seconds, trackBegin.Value.Milliseconds / 75)); + } + File.Delete(fileName); + cuesheet.CDTextfile = new CDTextfile("Testfile.cdt"); + cuesheet.Cataloguenumber.Value = "0123456789123"; + generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(1, generatedFiles.Count); + content = generatedFiles.First().Content; + Assert.IsNotNull(content); + fileName = Path.GetTempFileName(); + File.WriteAllBytes(fileName, content); + fileContent = File.ReadAllLines(fileName); + Assert.AreEqual(fileContent[0], String.Format("{0} {1}", CuesheetConstants.CuesheetCatalogueNumber, cuesheet.Cataloguenumber.Value)); + Assert.AreEqual(fileContent[1], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetCDTextfile, cuesheet.CDTextfile.Name)); + File.Delete(fileName); + cuesheet.CDTextfile = new CDTextfile("Testfile.cdt"); + cuesheet.Cataloguenumber.Value = "Testvalue"; + Assert.AreEqual(ValidationStatus.Error, generator.Validate().Status); + generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(0, generatedFiles.Count); + } + + [TestMethod()] + public void GenerateCuesheetFilesWithPreGapAndPostGapTest() + { + var testHelper = new TestHelper(); + Cuesheet cuesheet = new() + { + Artist = "Demo Artist", + Title = "Demo Title", + Audiofile = new Audiofile("Testfile.mp3") + }; + var begin = TimeSpan.Zero; + for (int i = 1; i < 25; i++) + { + var track = new Track + { + Artist = String.Format("Demo Track Artist {0}", i), + Title = String.Format("Demo Track Title {0}", i), + Begin = begin + }; + begin = begin.Add(new TimeSpan(0, i, i)); + track.End = begin; + var rand = new Random(); + var flagsToAdd = rand.Next(1, 3); + for (int x = 0; x < flagsToAdd; x++) + { + track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); + } + track.PostGap = new TimeSpan(0, 0, 2); + track.PreGap = new TimeSpan(0, 0, 3); + cuesheet.AddTrack(track, testHelper.ApplicationOptions); + } + var generator = new ExportfileGenerator(ExportType.Cuesheet, cuesheet, applicationOptions: testHelper.ApplicationOptions); + var generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(1, generatedFiles.Count); + var content = generatedFiles.First().Content; + Assert.IsNotNull(content); + var fileName = Path.GetTempFileName(); + File.WriteAllBytes(fileName, content); + var fileContent = File.ReadAllLines(fileName); + Assert.AreEqual(fileContent[0], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, cuesheet.Title)); + Assert.AreEqual(fileContent[1], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, cuesheet.Artist)); + Assert.AreEqual(fileContent[2], String.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, cuesheet.Audiofile.Name, cuesheet.Audiofile.AudioFileType)); + var position = 1; + for (int i = 3; i < fileContent.Length; i += 7) + { + var track = cuesheet.Tracks.Single(x => x.Position == position); + position++; + Assert.AreEqual(String.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position, CuesheetConstants.CuesheetTrackAudio), fileContent[i]); + Assert.AreEqual(String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackTitle, track.Title), fileContent[i + 1]); + Assert.AreEqual(String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackArtist, track.Artist), fileContent[i + 2]); + Assert.AreEqual(String.Format("{0}{1}{2} {3}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackFlags, String.Join(" ", track.Flags.Select(x => x.CuesheetLabel))), fileContent[i + 3]); + var preGap = track.PreGap; + var trackBegin = track.Begin; + var postGap = track.PostGap; + Assert.IsNotNull(preGap); + Assert.IsNotNull(trackBegin); + Assert.IsNotNull(postGap); + Assert.AreEqual(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackPreGap, Math.Floor(preGap.Value.TotalMinutes), preGap.Value.Seconds, preGap.Value.Milliseconds / 75), fileContent[i + 4]); + Assert.AreEqual(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackIndex01, Math.Floor(trackBegin.Value.TotalMinutes), trackBegin.Value.Seconds, trackBegin.Value.Milliseconds / 75), fileContent[i + 5]); + Assert.AreEqual(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackPostGap, Math.Floor(postGap.Value.TotalMinutes), postGap.Value.Seconds, postGap.Value.Milliseconds / 75), fileContent[i + 6]); + } + File.Delete(fileName); + } + + [TestMethod()] + public void GenerateCuesheetFilesWithTrackFlagsTest() + { + var testHelper = new TestHelper(); + Cuesheet cuesheet = new() + { + Artist = "Demo Artist", + Title = "Demo Title", + Audiofile = new Audiofile("Testfile.mp3") + }; + var begin = TimeSpan.Zero; + for (int i = 1; i < 25; i++) + { + var track = new Track + { + Artist = String.Format("Demo Track Artist {0}", i), + Title = String.Format("Demo Track Title {0}", i), + Begin = begin + }; + begin = begin.Add(new TimeSpan(0, i, i)); + track.End = begin; + var rand = new Random(); + var flagsToAdd = rand.Next(1, 3); + for (int x = 0; x < flagsToAdd; x++) + { + track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); + } + cuesheet.AddTrack(track, testHelper.ApplicationOptions); + } + var generator = new ExportfileGenerator(ExportType.Cuesheet, cuesheet, applicationOptions: testHelper.ApplicationOptions); + var generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(1, generatedFiles.Count); + var content = generatedFiles.First().Content; + Assert.IsNotNull(content); + var fileName = Path.GetTempFileName(); + File.WriteAllBytes(fileName, content); + var fileContent = File.ReadAllLines(fileName); + Assert.AreEqual(fileContent[0], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, cuesheet.Title)); + Assert.AreEqual(fileContent[1], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, cuesheet.Artist)); + Assert.AreEqual(fileContent[2], String.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, cuesheet.Audiofile.Name, cuesheet.Audiofile.AudioFileType)); + var position = 1; + for (int i = 3; i < fileContent.Length; i += 5) + { + var track = cuesheet.Tracks.Single(x => x.Position == position); + position++; + Assert.AreEqual(fileContent[i], String.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position, CuesheetConstants.CuesheetTrackAudio)); + Assert.AreEqual(fileContent[i + 1], String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackTitle, track.Title)); + Assert.AreEqual(fileContent[i + 2], String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackArtist, track.Artist)); + Assert.AreEqual(fileContent[i + 3], String.Format("{0}{1}{2} {3}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackFlags, String.Join(" ", track.Flags.Select(x => x.CuesheetLabel)))); + var trackBegin = track.Begin; + Assert.IsNotNull(trackBegin); + Assert.AreEqual(fileContent[i + 4], String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackIndex01, Math.Floor(trackBegin.Value.TotalMinutes), trackBegin.Value.Seconds, trackBegin.Value.Milliseconds / 75)); + } + File.Delete(fileName); + } + + [TestMethod()] + public void GenerateCuesheetFilesWithIncorrectTrackPositionsTest() + { + var testHelper = new TestHelper(); + Cuesheet cuesheet = new() + { + Artist = "Demo Artist", + Title = "Demo Title", + Audiofile = new Audiofile("Testfile.mp3") + }; + var begin = TimeSpan.Zero; + var random = new Random(); + for (int i = 1; i < 6; i++) + { + var track = new Track + { + Artist = String.Format("Demo Track Artist {0}", i), + Title = String.Format("Demo Track Title {0}", i), + Begin = begin, + Position = (uint)(i + random.Next(1, 10)) + }; + begin = begin.Add(new TimeSpan(0, i, i)); + track.End = begin; + cuesheet.AddTrack(track, testHelper.ApplicationOptions); + } + var generator = new ExportfileGenerator(ExportType.Cuesheet, cuesheet, applicationOptions: testHelper.ApplicationOptions); + Assert.AreEqual(ValidationStatus.Error, generator.Validate().Status); + //Rearrange positions + cuesheet.Tracks.ElementAt(0).Position = 1; + cuesheet.Tracks.ElementAt(1).Position = 2; + cuesheet.Tracks.ElementAt(2).Position = 3; + cuesheet.Tracks.ElementAt(3).Position = 4; + cuesheet.Tracks.ElementAt(4).Position = 5; + Assert.AreEqual(ValidationStatus.Success, generator.Validate().Status); + var generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(1, generatedFiles.Count); + var content = generatedFiles.First().Content; + Assert.IsNotNull(content); + var fileName = Path.GetTempFileName(); + File.WriteAllBytes(fileName, content); + var fileContent = File.ReadAllLines(fileName); + Assert.AreEqual(fileContent[0], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, cuesheet.Title)); + Assert.AreEqual(fileContent[1], String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, cuesheet.Artist)); + Assert.AreEqual(fileContent[2], String.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, cuesheet.Audiofile.Name, cuesheet.Audiofile.AudioFileType)); + var position = 1; + for (int i = 3; i < fileContent.Length; i += 4) + { + var track = cuesheet.Tracks.ElementAt(position - 1); + Assert.AreEqual(String.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, position, CuesheetConstants.CuesheetTrackAudio), fileContent[i]); + Assert.AreEqual(String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackTitle, track.Title), fileContent[i + 1]); + Assert.AreEqual(String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackArtist, track.Artist), fileContent[i + 2]); + var trackBegin = track.Begin; + Assert.IsNotNull(trackBegin); + Assert.AreEqual(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackIndex01, Math.Floor(trackBegin.Value.TotalMinutes), trackBegin.Value.Seconds, trackBegin.Value.Milliseconds / 75), fileContent[i + 3]); + position++; + } + File.Delete(fileName); + } + + [TestMethod()] + public void GenerateCuesheetFilesWithSplitPointsTest() + { + var testHelper = new TestHelper(); + Cuesheet cuesheet = new() + { + Artist = "Demo Artist", + Title = "Demo Title", + Audiofile = new Audiofile("Testfile.mp3") + }; + var begin = TimeSpan.Zero; + for (int i = 1; i < 25; i++) + { + var track = new Track + { + Artist = String.Format("Demo Track Artist {0}", i), + Title = String.Format("Demo Track Title {0}", i), + Begin = begin + }; + begin = begin.Add(new TimeSpan(0, i, i)); + track.End = begin; + cuesheet.AddTrack(track, testHelper.ApplicationOptions); + } + var splitPoint = cuesheet.AddSplitPoint(); + splitPoint.Moment = new TimeSpan(2, 0, 0); + splitPoint.Title = "Last part"; + splitPoint.AudiofileName = "Last part.mp3"; + splitPoint = cuesheet.AddSplitPoint(); + splitPoint.Moment = new TimeSpan(0, 30, 0); + splitPoint.AudiofileName = "First part.mp3"; + splitPoint = cuesheet.AddSplitPoint(); + splitPoint.Moment = new TimeSpan(1, 0, 0); + splitPoint.Artist = "Demo Artist Part2"; + splitPoint = cuesheet.AddSplitPoint(); + splitPoint.Artist = "Artist 3"; + splitPoint.Title = "Title 3"; + splitPoint.AudiofileName = "Part 3.mp3"; + splitPoint.Moment = new TimeSpan(1, 30, 0); + testHelper.ApplicationOptions.CuesheetFilename = "Unit test.cue"; + var generator = new ExportfileGenerator(ExportType.Cuesheet, cuesheet, applicationOptions: testHelper.ApplicationOptions); + Assert.AreEqual(ValidationStatus.Success, generator.Validate().Status); + var generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(5, generatedFiles.Count); + var position = 1; + var counter = 1; + //Check split according to split points + Assert.AreEqual(cuesheet.Tracks.Min(x => x.Begin), generatedFiles.First().Begin); + Assert.AreEqual(new TimeSpan(0, 30, 0), generatedFiles.First().End); + Assert.AreEqual(new TimeSpan(0, 30, 0), generatedFiles.ElementAt(1).Begin); + Assert.AreEqual(new TimeSpan(1, 0, 0), generatedFiles.ElementAt(1).End); + Assert.AreEqual(new TimeSpan(1, 0, 0), generatedFiles.ElementAt(2).Begin); + Assert.AreEqual(new TimeSpan(1, 30, 0), generatedFiles.ElementAt(2).End); + Assert.AreEqual(new TimeSpan(1, 30, 0), generatedFiles.ElementAt(3).Begin); + Assert.AreEqual(new TimeSpan(2, 0, 0), generatedFiles.ElementAt(3).End); + Assert.AreEqual(new TimeSpan(2, 0, 0), generatedFiles.ElementAt(4).Begin); + Assert.AreEqual(cuesheet.Tracks.Max(x => x.End), generatedFiles.Last().End); + foreach (var generatedFile in generatedFiles) + { + Assert.AreEqual(String.Format("Unit test({0}).cue", counter), generatedFile.Name); + counter++; + var content = generatedFile.Content; + Assert.IsNotNull(content); + var fileName = Path.GetTempFileName(); + File.WriteAllBytes(fileName, content); + var fileContent = File.ReadAllLines(fileName); + File.Delete(fileName); + int positionDifference = 1 - position; + // Check cuesheet header for splitpoint artist and title + var splitPointForThisFile = cuesheet.SplitPoints.FirstOrDefault(x => x.Moment == generatedFile.End); + if (splitPointForThisFile != null) + { + Assert.AreEqual(String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, splitPointForThisFile.Title), fileContent.First()); + Assert.AreEqual(String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, splitPointForThisFile.Artist), fileContent[1]); + Assert.AreEqual(String.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, splitPointForThisFile.AudiofileName, cuesheet.Audiofile?.AudioFileType), fileContent[2]); + } + else + { + Assert.AreEqual(String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, cuesheet.Title), fileContent.First()); + Assert.AreEqual(String.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, cuesheet.Artist), fileContent[1]); + var cuesheetFileName = String.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(cuesheet.Audiofile?.Name), counter - 1, Path.GetExtension(cuesheet.Audiofile?.Name)); + Assert.AreEqual(String.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, cuesheetFileName, cuesheet.Audiofile?.AudioFileType), fileContent[2]); + } + //Check for start from position 1 and begin = 00:00:00 + for (int i = 3; i < fileContent.Length; i += 4) + { + var track = cuesheet.Tracks.Single(x => x.Position == position); + position++; + Assert.AreEqual(String.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position + positionDifference, CuesheetConstants.CuesheetTrackAudio), fileContent[i]); + Assert.AreEqual(String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackTitle, track.Title), fileContent[i + 1]); + Assert.AreEqual(String.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackArtist, track.Artist), fileContent[i + 2]); + var trackBegin = track.Begin; + if (generatedFile.Begin != null) + { + if (generatedFile.Begin >= track.Begin) + { + trackBegin = TimeSpan.Zero; + } + else + { + trackBegin = track.Begin - generatedFile.Begin; + } + } + Assert.IsNotNull(trackBegin); + Assert.AreEqual(String.Format("{0}{1}{2} {3:00}:{4:00}:{5:00}", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackIndex01, Math.Floor(trackBegin.Value.TotalMinutes), trackBegin.Value.Seconds, trackBegin.Value.Milliseconds / 75), fileContent[i + 3]); + } + position--; + } + } + + [TestMethod()] + public void GenerateExportfilesTest() + { + var testHelper = new TestHelper(); + //Prepare cuesheet + Cuesheet cuesheet = new() + { + Artist = "Demo Artist", + Title = "Demo Title", + Audiofile = new Audiofile("Testfile.mp3") + }; + var begin = TimeSpan.Zero; + for (int i = 1; i < 25; i++) + { + var track = new Track + { + Artist = String.Format("Demo Track Artist {0}", i), + Title = String.Format("Demo Track Title {0}", i), + Begin = begin + }; + begin = begin.Add(new TimeSpan(0, i, i)); + track.End = begin; + cuesheet.AddTrack(track, testHelper.ApplicationOptions); + var rand = new Random(); + var flagsToAdd = rand.Next(0, 3); + if (flagsToAdd > 0) + { + for (int x = 0; x < flagsToAdd; x++) + { + track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); + } + } + } + + cuesheet.Cataloguenumber.Value = "0123456789123"; + cuesheet.CDTextfile = new CDTextfile("Testfile.cdt"); + + //Test class + var exportProfile = new Exportprofile + { + SchemeHead = "%Cuesheet.Artist%;%Cuesheet.Title%;%Cuesheet.Cataloguenumber%;%Cuesheet.CDTextfile%", + SchemeTracks = "%Track.Position%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%", + SchemeFooter = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor" + }; + Assert.AreEqual(ValidationStatus.Success, exportProfile.Validate().Status); + var generator = new ExportfileGenerator(ExportType.Exportprofile, cuesheet, exportprofile: exportProfile); + var generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(1, generatedFiles.Count); + Assert.AreEqual(exportProfile.Filename, generatedFiles.First().Name); + var fileContent = generatedFiles.First().Content; + Assert.IsNotNull(fileContent); + var tempFile = Path.GetTempFileName(); + File.WriteAllBytes(tempFile, fileContent); + var content = File.ReadAllLines(tempFile); + Assert.AreEqual("Demo Artist;Demo Title;0123456789123;Testfile.cdt", content[0]); + for (int i = 1; i < content.Length - 1; i++) + { + Assert.IsFalse(String.IsNullOrEmpty(content[i])); + Assert.AreNotEqual(content[i], ";;;;;"); + Assert.IsTrue(content[i].StartsWith(cuesheet.Tracks.ToList()[i - 1].Position + ";")); + } + Assert.AreEqual(content[^1], "Exported Demo Title from Demo Artist using AudioCuesheetEditor"); + + File.Delete(tempFile); + + exportProfile.SchemeHead = "%Track.Position%;%Cuesheet.Artist%;"; + var validationResult = exportProfile.Validate(x => x.SchemeHead); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Parameter != null && x.Parameter.Contains("%Track.Position%"))); + + //Check multiline export + exportProfile = new Exportprofile + { + SchemeHead = "%Cuesheet.Artist%;%Cuesheet.Title%", + SchemeTracks = String.Format("%Track.Position%{0}%Track.Artist%{1}%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%", Environment.NewLine, Environment.NewLine), + SchemeFooter = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor" + }; + Assert.AreEqual(ValidationStatus.Success, exportProfile.Validate().Status); + generator.Exportprofile = exportProfile; + generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(1, generatedFiles.Count); + Assert.AreEqual(exportProfile.Filename, generatedFiles.First().Name); + fileContent = generatedFiles.First().Content; + Assert.IsNotNull(fileContent); + tempFile = Path.GetTempFileName(); + File.WriteAllBytes(tempFile, fileContent); + content = File.ReadAllLines(tempFile); + Assert.AreEqual(content[0], "Demo Artist;Demo Title"); + var trackPosition = 0; + for (int i = 1; i < content.Length - 1; i += 3) + { + Assert.IsFalse(String.IsNullOrEmpty(content[i])); + Assert.AreNotEqual(content[i], ";;;;;"); + var track = cuesheet.Tracks.ToList()[trackPosition]; + Assert.IsNotNull(track.Position); + var position = track.Position.ToString(); + Assert.IsNotNull(position); + Assert.IsTrue(content[i].StartsWith(position)); + trackPosition++; + } + Assert.AreEqual(content[^1], "Exported Demo Title from Demo Artist using AudioCuesheetEditor"); + + File.Delete(tempFile); + + //Test flags + exportProfile = new Exportprofile + { + SchemeHead = "%Cuesheet.Artist%;%Cuesheet.Title%;%Cuesheet.Cataloguenumber%;%Cuesheet.CDTextfile%", + SchemeTracks = "%Track.Position%;%Track.Flags%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%", + SchemeFooter = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor" + }; + Assert.AreEqual(ValidationStatus.Success, exportProfile.Validate().Status); + generator.Exportprofile = exportProfile; + generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(1, generatedFiles.Count); + Assert.AreEqual(exportProfile.Filename, generatedFiles.First().Name); + fileContent = generatedFiles.First().Content; + Assert.IsNotNull(fileContent); + tempFile = Path.GetTempFileName(); + File.WriteAllBytes(tempFile, fileContent); + content = File.ReadAllLines(tempFile); + Assert.AreEqual("Demo Artist;Demo Title;0123456789123;Testfile.cdt", content[0]); + for (int i = 1; i < content.Length - 1; i++) + { + Assert.IsFalse(String.IsNullOrEmpty(content[i])); + Assert.AreNotEqual(content[i], ";;;;;"); + Assert.IsTrue(content[i].StartsWith(cuesheet.Tracks.ToList()[i - 1].Position + ";")); + if (cuesheet.Tracks.ElementAt(i - 1).Flags.Count > 0) + { + var flags = cuesheet.Tracks.ElementAt(i - 1).Flags; + Assert.IsTrue(content[i].Contains(String.Join(" ", flags.Select(x => x.CuesheetLabel)))); + } + } + Assert.AreEqual(content[^1], "Exported Demo Title from Demo Artist using AudioCuesheetEditor"); + File.Delete(tempFile); + } + + [TestMethod()] + public void GenerateExportfilesWithPregapAndPostgapTest() + { + var testHelper = new TestHelper(); + //Prepare cuesheet + Cuesheet cuesheet = new() + { + Artist = "Demo Artist", + Title = "Demo Title", + Audiofile = new Audiofile("Testfile.mp3") + }; + var begin = TimeSpan.Zero; + for (int i = 1; i < 25; i++) + { + var track = new Track + { + Artist = String.Format("Demo Track Artist {0}", i), + Title = String.Format("Demo Track Title {0}", i), + Begin = begin, + PostGap = new TimeSpan(0, 0, 1), + PreGap = new TimeSpan(0, 0, 3) + }; + begin = begin.Add(new TimeSpan(0, i, i)); + track.End = begin; + cuesheet.AddTrack(track, testHelper.ApplicationOptions); + var rand = new Random(); + var flagsToAdd = rand.Next(0, 3); + if (flagsToAdd > 0) + { + for (int x = 0; x < flagsToAdd; x++) + { + track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); + } + } + } + + cuesheet.Cataloguenumber.Value = "0123456789123"; + cuesheet.CDTextfile = new CDTextfile("Testfile.cdt"); + + var exportProfile = new Exportprofile + { + SchemeHead = "%Cuesheet.Artist%;%Cuesheet.Title%;%Cuesheet.Cataloguenumber%;%Cuesheet.CDTextfile%", + SchemeTracks = "%Track.Position%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%;%Track.PreGap%;%Track.PostGap%", + SchemeFooter = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor at %Date%" + }; + Assert.AreEqual(ValidationStatus.Success, exportProfile.Validate().Status); + var generator = new ExportfileGenerator(ExportType.Exportprofile, cuesheet, exportprofile: exportProfile); + var generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(1, generatedFiles.Count); + Assert.AreEqual(exportProfile.Filename, generatedFiles.First().Name); + var fileContent = generatedFiles.First().Content; + Assert.IsNotNull(fileContent); + var tempFile = Path.GetTempFileName(); + File.WriteAllBytes(tempFile, fileContent); + var content = File.ReadAllLines(tempFile); + Assert.AreEqual("Demo Artist;Demo Title;0123456789123;Testfile.cdt", content[0]); + for (int i = 1; i < content.Length - 1; i++) + { + Assert.IsFalse(String.IsNullOrEmpty(content[i])); + Assert.AreNotEqual(content[i], ";;;;;"); + Assert.IsTrue(content[i].StartsWith(cuesheet.Tracks.ToList()[i - 1].Position + ";")); + } + Assert.AreEqual(content[^1], String.Format("Exported Demo Title from Demo Artist using AudioCuesheetEditor at {0}", DateTime.Now.ToShortDateString())); + File.Delete(tempFile); + } + + [TestMethod()] + public void GenerateExportfilesWithSplitPointsTest() + { + var testHelper = new TestHelper(); + //Prepare cuesheet + Cuesheet cuesheet = new() + { + Artist = "Demo Artist", + Title = "Demo Title", + Audiofile = new Audiofile("Testfile.mp3") + }; + var begin = TimeSpan.Zero; + for (int i = 1; i < 25; i++) + { + var track = new Track + { + Artist = String.Format("Demo Track Artist {0}", i), + Title = String.Format("Demo Track Title {0}", i), + Begin = begin + }; + begin = begin.Add(new TimeSpan(0, i, i)); + track.End = begin; + cuesheet.AddTrack(track, testHelper.ApplicationOptions); + var rand = new Random(); + var flagsToAdd = rand.Next(0, 3); + if (flagsToAdd > 0) + { + for (int x = 0; x < flagsToAdd; x++) + { + track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); + } + } + } + + cuesheet.Cataloguenumber.Value = "0123456789123"; + cuesheet.CDTextfile = new CDTextfile("Testfile.cdt"); + var splitPoint = cuesheet.AddSplitPoint(); + splitPoint.Moment = new TimeSpan(2, 0, 0); + splitPoint.Title = "Last part"; + splitPoint.AudiofileName = "Last part.mp3"; + splitPoint = cuesheet.AddSplitPoint(); + splitPoint.Moment = new TimeSpan(0, 30, 0); + splitPoint.AudiofileName = "First part.mp3"; + splitPoint = cuesheet.AddSplitPoint(); + splitPoint.Moment = new TimeSpan(1, 0, 0); + splitPoint.Artist = "Demo Artist Part2"; + splitPoint = cuesheet.AddSplitPoint(); + splitPoint.Artist = "Artist 3"; + splitPoint.Title = "Title 3"; + splitPoint.AudiofileName = "Part 3.mp3"; + splitPoint.Moment = new TimeSpan(1, 30, 0); + //Test export + var exportProfile = new Exportprofile + { + SchemeHead = "%Cuesheet.Artist%;%Cuesheet.Title%;%Cuesheet.Audiofile%;%Cuesheet.Cataloguenumber%;%Cuesheet.CDTextfile%", + SchemeTracks = "%Track.Position%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%", + SchemeFooter = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor" + }; + var generator = new ExportfileGenerator(ExportType.Exportprofile, cuesheet, exportProfile); + Assert.AreEqual(ValidationStatus.Success, generator.Validate().Status); + var generatedFiles = generator.GenerateExportfiles(); + Assert.AreEqual(5, generatedFiles.Count); + + //Check split according to split points + Assert.AreEqual(cuesheet.Tracks.Min(x => x.Begin), generatedFiles.First().Begin); + Assert.AreEqual(new TimeSpan(0, 30, 0), generatedFiles.First().End); + Assert.AreEqual(new TimeSpan(0, 30, 0), generatedFiles.ElementAt(1).Begin); + Assert.AreEqual(new TimeSpan(1, 0, 0), generatedFiles.ElementAt(1).End); + Assert.AreEqual(new TimeSpan(1, 0, 0), generatedFiles.ElementAt(2).Begin); + Assert.AreEqual(new TimeSpan(1, 30, 0), generatedFiles.ElementAt(2).End); + Assert.AreEqual(new TimeSpan(1, 30, 0), generatedFiles.ElementAt(3).Begin); + Assert.AreEqual(new TimeSpan(2, 0, 0), generatedFiles.ElementAt(3).End); + Assert.AreEqual(new TimeSpan(2, 0, 0), generatedFiles.ElementAt(4).Begin); + Assert.AreEqual(cuesheet.Tracks.Max(x => x.End), generatedFiles.Last().End); + var counter = 1; + var position = 1; + foreach (var generatedFile in generatedFiles) + { + Assert.AreEqual(String.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(exportProfile.Filename), counter, Path.GetExtension(exportProfile.Filename)), generatedFile.Name); + counter++; + var content = generatedFile.Content; + Assert.IsNotNull(content); + var fileName = Path.GetTempFileName(); + File.WriteAllBytes(fileName, content); + var fileContent = File.ReadAllLines(fileName); + File.Delete(fileName); + int positionDifference = 1 - position; + //Check cuesheet header for splitpoint artist and title + var splitPointForThisFile = cuesheet.SplitPoints.FirstOrDefault(x => x.Moment == generatedFile.End); + if (splitPointForThisFile != null) + { + Assert.AreEqual(String.Format("{0};{1};{2};0123456789123;Testfile.cdt", splitPointForThisFile.Artist, splitPointForThisFile.Title, splitPointForThisFile.AudiofileName), fileContent[0]); + } + else + { + var audiofileName = String.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(cuesheet.Audiofile?.Name), counter - 1, Path.GetExtension(cuesheet.Audiofile?.Name)); + Assert.AreEqual(String.Format("{0};{1};{2};0123456789123;Testfile.cdt", cuesheet.Artist, cuesheet.Title, audiofileName), fileContent[0]); + } + //Check for start from position 1 and begin = 00:00:00 + for (int i = 1; i < fileContent.Length - 1; i++) + { + var track = cuesheet.Tracks.Single(x => x.Position == position); + position++; + var trackBegin = track.Begin; + var trackEnd = track.End; + if (generatedFile.Begin != null) + { + if (generatedFile.Begin >= track.Begin) + { + trackBegin = TimeSpan.Zero; + } + else + { + trackBegin = track.Begin - generatedFile.Begin; + } + trackEnd = track.End - generatedFile.Begin; + } + Assert.AreEqual(String.Format("{0};{1};{2};{3};{4};{5}", track.Position + positionDifference, track.Artist, track.Title, trackBegin, trackEnd, trackEnd - trackBegin), fileContent[i]); + } + if (splitPointForThisFile != null) + { + Assert.AreEqual(String.Format("Exported {0} from {1} using AudioCuesheetEditor", splitPointForThisFile.Title, splitPointForThisFile.Artist), fileContent.Last()); + } + else + { + Assert.AreEqual(String.Format("Exported {0} from {1} using AudioCuesheetEditor", cuesheet.Title, cuesheet.Artist), fileContent.Last()); + } + position--; + } + } + } +} \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/IO/Export/ExportprofileTests.cs b/AudioCuesheetEditorTests/Model/IO/Export/ExportprofileTests.cs new file mode 100644 index 00000000..c78f6a37 --- /dev/null +++ b/AudioCuesheetEditorTests/Model/IO/Export/ExportprofileTests.cs @@ -0,0 +1,54 @@ +using AudioCuesheetEditor.Model.IO.Export; +//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 Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AudioCuesheetEditorTests.Utility; +using AudioCuesheetEditor.Model.AudioCuesheet; +using System.IO; +using AudioCuesheetEditor.Model.IO.Audio; +using AudioCuesheetEditor.Model.Entity; + +namespace AudioCuesheetEditor.Model.IO.Export.Tests +{ + [TestClass()] + public class ExportprofileTests + { + [TestMethod()] + public void ValidateTest() + { + var exportprofile = new Exportprofile + { + Filename = String.Empty + }; + Assert.AreEqual(ValidationStatus.Error, exportprofile.Validate(x => x.Filename).Status); + exportprofile.Filename = "Test123"; + Assert.AreEqual(ValidationStatus.Success, exportprofile.Validate(x => x.Filename).Status); + exportprofile.SchemeHead = "%Cuesheet.Artist%;%Cuesheet.Title%;%Cuesheet.Cataloguenumber%;%Cuesheet.CDTextfile%"; + Assert.AreEqual(ValidationStatus.Success, exportprofile.Validate(x => x.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(x => x.SchemeTracks).Status); + exportprofile.SchemeFooter = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor at %Date%"; + Assert.AreEqual(ValidationStatus.Success, exportprofile.Validate(x => x.SchemeFooter).Status); + exportprofile.SchemeFooter = "Exported %Track.Title% from %Cuesheet.Artist% using AudioCuesheetEditor at %Date%"; + Assert.AreEqual(ValidationStatus.Error, exportprofile.Validate(x => x.SchemeFooter).Status); + } + } +} \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/IO/Export/SplitPointTests.cs b/AudioCuesheetEditorTests/Model/IO/Export/SplitPointTests.cs new file mode 100644 index 00000000..d4dc78e2 --- /dev/null +++ b/AudioCuesheetEditorTests/Model/IO/Export/SplitPointTests.cs @@ -0,0 +1,36 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using AudioCuesheetEditor.Model.IO.Export; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AudioCuesheetEditor.Model.Entity; +using AudioCuesheetEditor.Model.AudioCuesheet; + +namespace AudioCuesheetEditor.Model.IO.Export.Tests +{ + [TestClass()] + public class SplitPointTests + { + [TestMethod()] + public void ValidationTest() + { + var cuesheet = new Cuesheet(); + var splitPoint = new SplitPoint(cuesheet); + Assert.IsNull(splitPoint.Moment); + var validationResult = splitPoint.Validate(x => x.Moment); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + splitPoint.Moment = new TimeSpan(0, 30, 0); + validationResult = splitPoint.Validate(x => x.Moment); + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); + cuesheet.AddTrack(new Track() { Position = 1}); + cuesheet.AddTrack(new Track() { Position = 2, Begin = new TimeSpan(0, 8, 43), End = new TimeSpan(0, 15, 43) }); + validationResult = splitPoint.Validate(x => x.Moment); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + splitPoint.Moment = new TimeSpan(0, 15, 43); + validationResult = splitPoint.Validate(x => x.Moment); + Assert.AreEqual(ValidationStatus.Success, validationResult.Status); + } + } +} \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/IO/Import/CuesheetImportFileTests.cs b/AudioCuesheetEditorTests/Model/IO/Import/CuesheetImportfileTests.cs similarity index 92% rename from AudioCuesheetEditorTests/Model/IO/Import/CuesheetImportFileTests.cs rename to AudioCuesheetEditorTests/Model/IO/Import/CuesheetImportfileTests.cs index d0db44a1..b401112a 100644 --- a/AudioCuesheetEditorTests/Model/IO/Import/CuesheetImportFileTests.cs +++ b/AudioCuesheetEditorTests/Model/IO/Import/CuesheetImportfileTests.cs @@ -1,4 +1,4 @@ -//This file is part of AudioCuesheetEditor. +//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 @@ -24,11 +24,12 @@ using System.IO; using AudioCuesheetEditorTests.Properties; using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.Entity; namespace AudioCuesheetEditor.Model.IO.Import.Tests { [TestClass()] - public class CuesheetImportFileTests + public class CuesheetImportfileTests { [TestMethod()] public void CuesheetImportFileTest() @@ -80,25 +81,25 @@ public void CuesheetImportFileTest() var importFile = new CuesheetImportfile(new MemoryStream(File.ReadAllBytes(tempFile)), testHelper.ApplicationOptions); Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.Cuesheet); - Assert.IsTrue(importFile.Cuesheet.IsValid); + Assert.AreEqual(ValidationStatus.Success, importFile.Cuesheet.Validate().Status); Assert.AreEqual(importFile.Cuesheet.Tracks.Count, 8); Assert.IsNotNull(importFile.Cuesheet.CDTextfile); - Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "PERFORMER \"Sample CD Artist\""), importFile.FileContentRecognized.ElementAt(0)); - Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "TITLE \"Sample CD Title\""), importFile.FileContentRecognized.ElementAt(1)); - Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "FILE \"AC DC - TNT.mp3\" MP3"), importFile.FileContentRecognized.ElementAt(2)); - Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "CDTEXTFILE \"Testfile.cdt\""), importFile.FileContentRecognized.ElementAt(3)); - Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "CATALOG 0123456789012"), importFile.FileContentRecognized.ElementAt(4)); - Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "TRACK 01 AUDIO"), importFile.FileContentRecognized.ElementAt(5)); - Assert.AreEqual(String.Format(" {0}", String.Format(CuesheetConstants.RecognizedMarkHTML, "PERFORMER \"Sample Artist 1\"")), importFile.FileContentRecognized.ElementAt(6)); - Assert.AreEqual(String.Format(" {0}", String.Format(CuesheetConstants.RecognizedMarkHTML, "TITLE \"Sample Title 1\"")), importFile.FileContentRecognized.ElementAt(7)); - Assert.AreEqual(String.Format(" {0}", String.Format(CuesheetConstants.RecognizedMarkHTML, "INDEX 01 00:00:00")), importFile.FileContentRecognized.ElementAt(8)); + Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "PERFORMER \"Sample CD Artist\""), importFile.FileContentRecognized?.ElementAt(0)); + Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "TITLE \"Sample CD Title\""), importFile.FileContentRecognized?.ElementAt(1)); + Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "FILE \"AC DC - TNT.mp3\" MP3"), importFile.FileContentRecognized?.ElementAt(2)); + Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "CDTEXTFILE \"Testfile.cdt\""), importFile.FileContentRecognized?.ElementAt(3)); + Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "CATALOG 0123456789012"), importFile.FileContentRecognized?.ElementAt(4)); + Assert.AreEqual(String.Format(CuesheetConstants.RecognizedMarkHTML, "TRACK 01 AUDIO"), importFile.FileContentRecognized?.ElementAt(5)); + Assert.AreEqual(String.Format(" {0}", String.Format(CuesheetConstants.RecognizedMarkHTML, "PERFORMER \"Sample Artist 1\"")), importFile.FileContentRecognized?.ElementAt(6)); + Assert.AreEqual(String.Format(" {0}", String.Format(CuesheetConstants.RecognizedMarkHTML, "TITLE \"Sample Title 1\"")), importFile.FileContentRecognized?.ElementAt(7)); + Assert.AreEqual(String.Format(" {0}", String.Format(CuesheetConstants.RecognizedMarkHTML, "INDEX 01 00:00:00")), importFile.FileContentRecognized?.ElementAt(8)); File.Delete(tempFile); importFile = new CuesheetImportfile(new MemoryStream(Resources.Playlist_Bug_30), testHelper.ApplicationOptions); Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.Cuesheet); - Assert.IsNull(importFile.Cuesheet.GetValidationErrors(testHelper.Localizer, validationErrorFilterType: Entity.ValidationErrorFilterType.ErrorOnly)); + Assert.AreEqual(ValidationStatus.Success, importFile.Cuesheet.Validate().Status); importFile = new CuesheetImportfile(new MemoryStream(Resources.Playlist_Bug_57), testHelper.ApplicationOptions); Assert.IsNull(importFile.AnalyseException); @@ -162,9 +163,9 @@ public void CuesheetImportFileTest() Assert.IsNull(importFile.AnalyseException); Assert.IsNotNull(importFile.Cuesheet); - Assert.AreEqual(String.Format(" {0}", String.Format(CuesheetConstants.RecognizedMarkHTML, "FLAGS 4CH DCP PRE SCMS")), importFile.FileContentRecognized.ElementAt(8)); - Assert.AreEqual(String.Format(" {0}", String.Format(CuesheetConstants.RecognizedMarkHTML, "PREGAP 00:04:00")), importFile.FileContentRecognized.ElementAt(35)); - Assert.IsTrue(importFile.Cuesheet.IsValid); + Assert.AreEqual(String.Format(" {0}", String.Format(CuesheetConstants.RecognizedMarkHTML, "FLAGS 4CH DCP PRE SCMS")), importFile.FileContentRecognized?.ElementAt(8)); + Assert.AreEqual(String.Format(" {0}", String.Format(CuesheetConstants.RecognizedMarkHTML, "PREGAP 00:04:00")), importFile.FileContentRecognized?.ElementAt(35)); + Assert.AreEqual(ValidationStatus.Success, importFile.Cuesheet.Validate().Status); Assert.AreEqual(importFile.Cuesheet.Tracks.Count, 8); Assert.IsNotNull(importFile.Cuesheet.CDTextfile); Assert.IsTrue(importFile.Cuesheet.Tracks.ElementAt(0).Flags.Count == 4); diff --git a/AudioCuesheetEditorTests/Model/IO/Import/TextImportSchemeTests.cs b/AudioCuesheetEditorTests/Model/IO/Import/TextImportSchemeTests.cs index 125c71b4..2efbed05 100644 --- a/AudioCuesheetEditorTests/Model/IO/Import/TextImportSchemeTests.cs +++ b/AudioCuesheetEditorTests/Model/IO/Import/TextImportSchemeTests.cs @@ -13,6 +13,7 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. +using AudioCuesheetEditor.Model.Entity; using AudioCuesheetEditor.Model.IO.Import; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -34,18 +35,20 @@ public void TextImportSchemeValidationTest() SchemeTracks = String.Empty, SchemeCuesheet = String.Empty }; - Assert.IsFalse(importScheme.IsValid); + Assert.AreEqual(ValidationStatus.Success, importScheme.Validate().Status); importScheme.SchemeCuesheet = "(?'Track.Begin'\\w{1,}) - (?'Cuesheet.Artist'\\w{1})"; - var validationErrors = importScheme.GetValidationErrorsFiltered(String.Format("{0}.{1}", nameof(TextImportScheme), nameof(TextImportScheme.SchemeCuesheet))); - Assert.IsTrue(validationErrors.Count == 1); - Assert.IsTrue(validationErrors.First().Message.Parameter.First().ToString() == "(?'Track.Begin'\\w{1,})"); + var validationResult = importScheme.Validate(x => x.SchemeCuesheet); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Parameter != null && x.Parameter.Last().Equals("(?'Track.Begin'\\w{1,})"))); importScheme.SchemeCuesheet = "(?'Track.Begin'\\w{1,}) - (?'Cuesheet.Artist'\\w{1}) - (?'Track.Artist'[A-z0-9]{1,})"; - validationErrors = importScheme.GetValidationErrorsFiltered(String.Format("{0}.{1}", nameof(TextImportScheme), nameof(TextImportScheme.SchemeCuesheet))); - Assert.IsTrue(validationErrors.Count == 1); - Assert.AreEqual("(?'Track.Artist'[A-z0-9]{1,}),(?'Track.Begin'\\w{1,})", validationErrors.First().Message.Parameter.First().ToString()); + validationResult = importScheme.Validate(x => x.SchemeCuesheet); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.IsTrue(validationResult.ValidationMessages?.Count == 2); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Parameter != null && x.Parameter.Last().Equals("(?'Track.Begin'\\w{1,})"))); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Parameter != null && x.Parameter.Last().Equals("(?'Track.Artist'[A-z0-9]{1,})"))); Boolean eventFiredCorrect = false; - importScheme.SchemeChanged += delegate (object sender, String property) - { + importScheme.SchemeChanged += delegate (object? sender, String property) + { if (property == nameof(TextImportScheme.SchemeTracks)) { eventFiredCorrect = true; @@ -53,12 +56,12 @@ public void TextImportSchemeValidationTest() }; importScheme.SchemeTracks = "(?'Cuesheet.Title'[a-zA-Z0-9_ .;äöü&:,]{1,}) - (?'Track.End'\\w{1,})"; Assert.AreEqual(true, eventFiredCorrect); - validationErrors = importScheme.GetValidationErrorsFiltered(String.Format("{0}.{1}", nameof(TextImportScheme), nameof(TextImportScheme.SchemeTracks))); - Assert.IsTrue(validationErrors.Count == 1); - Assert.AreEqual("(?'Cuesheet.Title'[a-zA-Z0-9_ .;äöü&:,]{1,})", validationErrors.First().Message.Parameter.First().ToString()); + validationResult = importScheme.Validate(x => x.SchemeTracks); + Assert.AreEqual(ValidationStatus.Error, validationResult.Status); + Assert.IsTrue(validationResult.ValidationMessages?.Any(x => x.Parameter != null && x.Parameter.Last().Equals("(?'Cuesheet.Title'[a-zA-Z0-9_ .;äöü&:,]{1,})"))); importScheme.SchemeCuesheet = "(?'Cuesheet.Artist'\\w{1,}) - (?'Cuesheet.AudioFile'[a-zA-Z0-9_. ;äöü&:,\\]{1,})"; importScheme.SchemeTracks = "(?'Track.Artist'[A-z0-9]{1,}) - (?'Track.Title'.{1,})"; - Assert.IsTrue(importScheme.IsValid); + Assert.AreEqual(ValidationStatus.Success, importScheme.Validate().Status); } } } diff --git a/AudioCuesheetEditorTests/Model/IO/Import/TextImportFileTests.cs b/AudioCuesheetEditorTests/Model/IO/Import/TextImportfileTests.cs similarity index 96% rename from AudioCuesheetEditorTests/Model/IO/Import/TextImportFileTests.cs rename to AudioCuesheetEditorTests/Model/IO/Import/TextImportfileTests.cs index c6fa0693..e5f10684 100644 --- a/AudioCuesheetEditorTests/Model/IO/Import/TextImportFileTests.cs +++ b/AudioCuesheetEditorTests/Model/IO/Import/TextImportfileTests.cs @@ -1,4 +1,4 @@ -//This file is part of AudioCuesheetEditor. +//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 @@ -59,7 +59,7 @@ public void TextImportFileTest() Assert.IsNotNull(textImportFile.Cuesheet); Assert.AreEqual("CuesheetArtist", textImportFile.Cuesheet.Artist); Assert.AreEqual("CuesheetTitle", textImportFile.Cuesheet.Title); - Assert.AreEqual("c:\\tmp\\Testfile.mp3", textImportFile.Cuesheet.Audiofile.FileName); + Assert.AreEqual("c:\\tmp\\Testfile.mp3", textImportFile.Cuesheet.Audiofile?.Name); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 8); Assert.AreEqual(textImportFile.Cuesheet.Tracks.ToArray()[0].Artist, "Sample Artist 1"); Assert.AreEqual(textImportFile.Cuesheet.Tracks.ToArray()[0].Title, "Sample Title 1"); @@ -92,9 +92,10 @@ public void TextImportFileTest() textImportFile.TextImportScheme.SchemeTracks = @"(?'Track.Position'\d{1,})[|](?'Track.Artist'.{1,}) - (?'Track.Title'[a-zA-Z0-9_ ]{1,})\t{1,}(?'Track.End'.{1,})"; Assert.IsNull(textImportFile.AnalyseException); + Assert.IsNotNull(textImportFile.Cuesheet); Assert.AreEqual("CuesheetArtist", textImportFile.Cuesheet.Artist); Assert.AreEqual("CuesheetTitle", textImportFile.Cuesheet.Title); - Assert.AreEqual("c:\\tmp\\TestTextFile.cdt", textImportFile.Cuesheet.CDTextfile.FileName); + Assert.AreEqual("c:\\tmp\\TestTextFile.cdt", textImportFile.Cuesheet.CDTextfile?.Name); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 8); Assert.IsTrue(textImportFile.Cuesheet.Tracks.ToArray()[5].Position == 6); Assert.AreEqual(textImportFile.Cuesheet.Tracks.ToArray()[0].Artist, "Sample Artist 1"); @@ -136,9 +137,10 @@ public void TextImportFileTest() textImportFile.TextImportScheme.SchemeTracks = @"(?'Track.Position'.{1,})[|](?'Track.Artist'.{1,}) - (?'Track.Title'[a-zA-Z0-9_ ]{1,})\t{1,}(?'Track.End'.{1,})"; Assert.IsNull(textImportFile.AnalyseException); + Assert.IsNotNull(textImportFile.Cuesheet); Assert.AreEqual("CuesheetArtist", textImportFile.Cuesheet.Artist); Assert.AreEqual("CuesheetTitle", textImportFile.Cuesheet.Title); - Assert.AreEqual("c:\\tmp\\TestTextFile.cdt", textImportFile.Cuesheet.CDTextfile.FileName); + Assert.AreEqual("c:\\tmp\\TestTextFile.cdt", textImportFile.Cuesheet.CDTextfile?.Name); Assert.AreEqual("A83412346734", textImportFile.Cuesheet.Cataloguenumber.Value); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 8); Assert.IsTrue(textImportFile.Cuesheet.Tracks.ToArray()[5].Position == 6); @@ -171,6 +173,7 @@ public void TextImportFileTest() textImportFile.TextImportScheme.SchemeTracks = TextImportScheme.DefaultSchemeTracks; Assert.IsNull(textImportFile.AnalyseException); + Assert.IsNotNull(textImportFile.Cuesheet); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 9); Assert.AreEqual(textImportFile.Cuesheet.Tracks.ToArray()[0].Artist, "Sample Artist 1"); Assert.AreEqual(textImportFile.Cuesheet.Tracks.ToArray()[0].Title, "Sample Title 1"); @@ -178,12 +181,12 @@ public void TextImportFileTest() File.Delete(tempFile); - var importOptions = new ImportOptions(); - importOptions.TimeSpanFormat.Scheme = "(?'TimeSpanFormat.Minutes'\\d{1,})[:](?'TimeSpanFormat.Seconds'\\d{1,})"; + var importOptions = new ImportOptions() { TimeSpanFormat = new TimeSpanFormat() { Scheme = "(?'TimeSpanFormat.Minutes'\\d{1,})[:](?'TimeSpanFormat.Seconds'\\d{1,})" } }; textImportFile = new TextImportfile(new MemoryStream(Resources.Textimport_Bug_213), importOptions); textImportFile.TextImportScheme.SchemeCuesheet = "(?'Track.Artist'[a-zA-Z0-9_ .();äöü&:,]{1,}) - (?'Track.Title'[a-zA-Z0-9_ .();äöü]{1,})\t{1,}(?'Track.End'.{1,})"; textImportFile.TextImportScheme.SchemeCuesheet = String.Empty; Assert.IsNull(textImportFile.AnalyseException); + Assert.IsNotNull(textImportFile.Cuesheet); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 4); Assert.AreEqual(new TimeSpan(2, 3, 23), textImportFile.Cuesheet.Tracks.ToArray()[3].End); } @@ -211,6 +214,7 @@ public void TextImportFileTestFlags() textImportFile.TextImportScheme.SchemeTracks = "(?'Track.Artist'[a-zA-Z0-9_ .();äöü&:,]{1,}) - (?'Track.Title'[a-zA-Z0-9_ .();äöü]{1,})\t{1,}(?'Track.End'[0-9]{2}[:][0-9]{2}[:][0-9]{2})\t{1,}(?'Track.Flags'[a-zA-Z 0-9,]{1,})"; Assert.IsNull(textImportFile.AnalyseException); + Assert.IsNotNull(textImportFile.Cuesheet); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 8); Assert.IsTrue(textImportFile.Cuesheet.Tracks.ElementAt(0).Flags.Contains(AudioCuesheet.Flag.DCP)); Assert.IsTrue(textImportFile.Cuesheet.Tracks.ElementAt(2).Flags.Contains(AudioCuesheet.Flag.DCP)); @@ -250,6 +254,7 @@ public void TextImportFileTestPreGapAndPostGap() textImportFile.TextImportScheme.SchemeTracks = "(?'Track.Artist'[a-zA-Z0-9_ .();äöü&:,]{1,}) - (?'Track.Title'[a-zA-Z0-9_ .();äöü]{1,})\t{1,}(?'Track.PreGap'[0-9]{2}[:][0-9]{2}[:][0-9]{2})\t{1,}(?'Track.End'[0-9]{2}[:][0-9]{2}[:][0-9]{2})\t{1,}(?'Track.PostGap'[0-9]{2}[:][0-9]{2}[:][0-9]{2})"; Assert.IsNull(textImportFile.AnalyseException); + Assert.IsNotNull(textImportFile.Cuesheet); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 8); Assert.AreEqual(new TimeSpan(0, 0, 2), textImportFile.Cuesheet.Tracks.ElementAt(0).PreGap); Assert.AreEqual(new TimeSpan(0, 0, 0), textImportFile.Cuesheet.Tracks.ElementAt(0).PostGap); @@ -302,7 +307,7 @@ public void FileContentRecognizedTests() String.Format(CuesheetConstants.RecognizedMarkHTML, "c:\\tmp\\Testfile.mp3")), textImportFile.FileContentRecognized.First()); Assert.AreEqual("CuesheetArtist", textImportFile.Cuesheet.Artist); Assert.AreEqual("CuesheetTitle", textImportFile.Cuesheet.Title); - Assert.AreEqual("c:\\tmp\\Testfile.mp3", textImportFile.Cuesheet.Audiofile.FileName); + Assert.AreEqual("c:\\tmp\\Testfile.mp3", textImportFile.Cuesheet.Audiofile?.Name); Assert.IsTrue(textImportFile.Cuesheet.Tracks.Count == 0); Assert.AreEqual("Sample Artist 1 - Sample Title 1 00:05:00", textImportFile.FileContentRecognized.ElementAt(1)); Assert.AreEqual("Sample Artist 2 - Sample Title 2 00:09:23", textImportFile.FileContentRecognized.ElementAt(2)); diff --git a/AudioCuesheetEditorTests/Model/IO/ProjectFileTests.cs b/AudioCuesheetEditorTests/Model/IO/ProjectFileTests.cs deleted file mode 100644 index 236baf42..00000000 --- a/AudioCuesheetEditorTests/Model/IO/ProjectFileTests.cs +++ /dev/null @@ -1,101 +0,0 @@ -//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 Microsoft.VisualStudio.TestTools.UnitTesting; -using AudioCuesheetEditor.Model.IO; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using AudioCuesheetEditor.Model.AudioCuesheet; -using System.IO; -using AudioCuesheetEditor.Model.IO.Audio; -using System.Text.Json; -using AudioCuesheetEditor.Extensions; - -namespace AudioCuesheetEditor.Model.IO.Tests -{ - [TestClass()] - public class ProjectfileTests - { - [TestMethod()] - public void GenerateFileTest() - { - var cuesheet = new Cuesheet - { - Artist = "CuesheetArtist", - Title = "CuesheetTitle", - Audiofile = new Audiofile("AudioFile.mp3"), - CDTextfile = new CDTextfile("CDTextfile.cdt"), - }; - cuesheet.Cataloguenumber.Value = "A123"; - var begin = TimeSpan.Zero; - for (int i = 1; i <= 10; i++) - { - var track = new Track - { - Position = (uint)i, - Artist = String.Format("Artist {0}", i), - Title = String.Format("Title {0}", i), - Begin = begin - }; - var rand = new Random(); - var flagsToAdd = rand.Next(1, 3); - for (int x = 0; x < flagsToAdd; x++) - { - track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); - } - begin = begin.Add(new TimeSpan(0, i, i)); - track.End = begin; - cuesheet.AddTrack(track, new Options.ApplicationOptions() { LinkTracksWithPreviousOne = true }); - } - var projectFile = new Projectfile(cuesheet); - var generatedFile = projectFile.GenerateFile(); - Assert.IsNotNull(generatedFile); - var fileName = Path.GetTempFileName(); - File.WriteAllBytes(fileName, generatedFile); - var fileContent = File.ReadAllLines(fileName); - var json = JsonSerializer.Serialize(cuesheet, Projectfile.Options); - Assert.AreEqual(json, fileContent.FirstOrDefault()); - File.Delete(fileName); - } - - [TestMethod()] - public void ImportFileTest() - { - var fileContent = Encoding.UTF8.GetBytes("{\"Artist\":\"CuesheetArtist\",\"Title\":\"CuesheetTitle\",\"Audiofile\":{\"FileName\":\"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\":{\"FileName\":\"CDTextfile.cdt\"},\"Cataloguenumber\":{\"Value\":\"A123\"}}"); - var cuesheet = Projectfile.ImportFile(fileContent); - Assert.IsTrue(cuesheet.Tracks.All(x => x.Cuesheet == cuesheet)); - Assert.AreEqual("CuesheetArtist", cuesheet.Artist); - Assert.AreEqual("CuesheetTitle", cuesheet.Title); - Assert.AreEqual("AudioFile.mp3", cuesheet.Audiofile.FileName); - Assert.IsFalse(cuesheet.Audiofile.IsRecorded); - Assert.AreEqual("A123", cuesheet.Cataloguenumber.Value); - Assert.IsTrue(cuesheet.Cataloguenumber.ValidationErrors.Count == 2); - Assert.IsTrue(cuesheet.Tracks.Count == 10); - 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.IsTrue(Object.ReferenceEquals(cuesheet.Tracks.First(), cuesheet.GetPreviousLinkedTrack(cuesheet.Tracks.ElementAt(1)))); - Assert.AreEqual(cuesheet.Tracks.First(), cuesheet.GetPreviousLinkedTrack(cuesheet.Tracks.ElementAt(1))); - cuesheet.Tracks.First().Position = 3; - Assert.AreEqual("Artist 1", cuesheet.Tracks.ElementAt(2).Artist); - Assert.AreEqual("Artist 3", cuesheet.Tracks.First().Artist); - Assert.AreEqual((uint)10, cuesheet.Tracks.Last().Position.Value); - } - } -} \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/IO/ProjectfileTests.cs b/AudioCuesheetEditorTests/Model/IO/ProjectfileTests.cs new file mode 100644 index 00000000..022cec94 --- /dev/null +++ b/AudioCuesheetEditorTests/Model/IO/ProjectfileTests.cs @@ -0,0 +1,167 @@ +//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.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; + +namespace AudioCuesheetEditor.Model.IO.Tests +{ + [TestClass()] + public class ProjectfileTests + { + [TestMethod()] + public void GenerateFileTest() + { + var cuesheet = new Cuesheet + { + Artist = "CuesheetArtist", + Title = "CuesheetTitle", + Audiofile = new Audiofile("AudioFile.mp3"), + CDTextfile = new CDTextfile("CDTextfile.cdt"), + }; + cuesheet.Cataloguenumber.Value = "A123"; + var begin = TimeSpan.Zero; + for (int i = 1; i <= 10; i++) + { + var track = new Track + { + Position = (uint)i, + Artist = String.Format("Artist {0}", i), + Title = String.Format("Title {0}", i), + Begin = begin + }; + var rand = new Random(); + var flagsToAdd = rand.Next(1, 3); + for (int x = 0; x < flagsToAdd; x++) + { + track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); + } + begin = begin.Add(new TimeSpan(0, i, i)); + track.End = begin; + cuesheet.AddTrack(track, new Options.ApplicationOptions() { LinkTracksWithPreviousOne = true }); + } + var projectFile = new Projectfile(cuesheet); + var generatedFile = projectFile.GenerateFile(); + Assert.IsNotNull(generatedFile); + var fileName = Path.GetTempFileName(); + File.WriteAllBytes(fileName, generatedFile); + var fileContent = File.ReadAllLines(fileName); + var json = JsonSerializer.Serialize(cuesheet, Projectfile.Options); + Assert.AreEqual(json, fileContent.FirstOrDefault()); + File.Delete(fileName); + } + + [TestMethod()] + public void GenerateFileWithSplitPointsTest() + { + var cuesheet = new Cuesheet + { + Artist = "CuesheetArtist", + Title = "CuesheetTitle", + Audiofile = new Audiofile("AudioFile.mp3"), + CDTextfile = new CDTextfile("CDTextfile.cdt"), + }; + cuesheet.Cataloguenumber.Value = "A123"; + var begin = TimeSpan.Zero; + for (int i = 1; i <= 10; i++) + { + var track = new Track + { + Position = (uint)i, + Artist = String.Format("Artist {0}", i), + Title = String.Format("Title {0}", i), + Begin = begin + }; + var rand = new Random(); + var flagsToAdd = rand.Next(1, 3); + for (int x = 0; x < flagsToAdd; x++) + { + track.SetFlag(Flag.AvailableFlags.ElementAt(x), SetFlagMode.Add); + } + begin = begin.Add(new TimeSpan(0, i, i)); + track.End = begin; + cuesheet.AddTrack(track, new Options.ApplicationOptions() { LinkTracksWithPreviousOne = true }); + } + var splitPoint = cuesheet.AddSplitPoint(); + splitPoint.Moment = new TimeSpan(0, 30, 0); + splitPoint = cuesheet.AddSplitPoint(); + splitPoint.Moment = new TimeSpan(1, 0, 0); + var projectFile = new Projectfile(cuesheet); + var generatedFile = projectFile.GenerateFile(); + Assert.IsNotNull(generatedFile); + var fileName = Path.GetTempFileName(); + File.WriteAllBytes(fileName, generatedFile); + var fileContent = File.ReadAllLines(fileName); + var json = JsonSerializer.Serialize(cuesheet, Projectfile.Options); + Assert.AreEqual(json, fileContent.FirstOrDefault()); + File.Delete(fileName); + } + + [TestMethod()] + public void ImportFileTest() + { + var fileContent = Encoding.UTF8.GetBytes("{\"Artist\":\"CuesheetArtist\",\"Title\":\"CuesheetTitle\",\"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\"},\"Cataloguenumber\":{\"Value\":\"A123\"}}"); + var cuesheet = Projectfile.ImportFile(fileContent); + Assert.IsNotNull(cuesheet); + Assert.IsTrue(cuesheet.Tracks.All(x => x.Cuesheet == cuesheet)); + Assert.AreEqual("CuesheetArtist", cuesheet.Artist); + Assert.AreEqual("CuesheetTitle", cuesheet.Title); + Assert.AreEqual("AudioFile.mp3", cuesheet.Audiofile?.Name); + Assert.IsFalse(cuesheet.Audiofile?.IsRecorded); + Assert.AreEqual("A123", cuesheet.Cataloguenumber.Value); + Assert.IsTrue(cuesheet.Cataloguenumber.Validate().ValidationMessages?.Count == 2); + Assert.IsTrue(cuesheet.Tracks.Count == 10); + 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.IsTrue(Object.ReferenceEquals(cuesheet.Tracks.First(), cuesheet.GetPreviousLinkedTrack(cuesheet.Tracks.ElementAt(1)))); + Assert.AreEqual(cuesheet.Tracks.First(), cuesheet.GetPreviousLinkedTrack(cuesheet.Tracks.ElementAt(1))); + Assert.AreEqual((uint)10, cuesheet.Tracks.Last().Position); + } + + [TestMethod()] + public void ImportFileWithSplitPointsTest() + { + var fileContent = Encoding.UTF8.GetBytes("{\"Tracks\":[{\"Position\":1,\"Artist\":\"Artist 1\",\"Title\":\"Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:01:01\",\"Flags\":[\"4CH\"],\"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\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":8,\"Artist\":\"Artist 8\",\"Title\":\"Title 8\",\"Begin\":\"00:28:28\",\"End\":\"00:36:36\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":9,\"Artist\":\"Artist 9\",\"Title\":\"Title 9\",\"Begin\":\"00:36:36\",\"End\":\"00:45:45\",\"Flags\":[\"4CH\"],\"IsLinkedToPreviousTrack\":true},{\"Position\":10,\"Artist\":\"Artist 10\",\"Title\":\"Title 10\",\"Begin\":\"00:45:45\",\"End\":\"00:55:55\",\"Flags\":[\"4CH\",\"DCP\"],\"IsLinkedToPreviousTrack\":true}],\"Artist\":\"CuesheetArtist\",\"Title\":\"CuesheetTitle\",\"Audiofile\":{\"Name\":\"AudioFile.mp3\"},\"CDTextfile\":{\"Name\":\"CDTextfile.cdt\"},\"Cataloguenumber\":{\"Value\":\"A123\"},\"SplitPoints\":[{\"Artist\":\"CuesheetArtist\",\"Title\":\"CuesheetTitle\",\"Moment\":\"00:30:00\"},{\"Artist\":\"CuesheetArtist\",\"Title\":\"CuesheetTitle\",\"Moment\":\"01:00:00\"}]}"); + var cuesheet = Projectfile.ImportFile(fileContent); + Assert.IsNotNull(cuesheet); + Assert.IsTrue(cuesheet.Tracks.All(x => x.Cuesheet == cuesheet)); + Assert.AreEqual("CuesheetArtist", cuesheet.Artist); + Assert.AreEqual("CuesheetTitle", cuesheet.Title); + Assert.AreEqual("AudioFile.mp3", cuesheet.Audiofile?.Name); + Assert.IsFalse(cuesheet.Audiofile?.IsRecorded); + Assert.AreEqual("A123", cuesheet.Cataloguenumber.Value); + Assert.IsTrue(cuesheet.Cataloguenumber.Validate().ValidationMessages?.Count == 2); + Assert.IsTrue(cuesheet.Tracks.Count == 10); + 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.IsTrue(Object.ReferenceEquals(cuesheet.Tracks.First(), cuesheet.GetPreviousLinkedTrack(cuesheet.Tracks.ElementAt(1)))); + Assert.AreEqual(cuesheet.Tracks.First(), cuesheet.GetPreviousLinkedTrack(cuesheet.Tracks.ElementAt(1))); + Assert.AreEqual((uint)10, cuesheet.Tracks.Last().Position); + Assert.AreEqual(2, cuesheet.SplitPoints.Count); + Assert.AreEqual(new TimeSpan(0, 30, 0), cuesheet.SplitPoints.First().Moment); + Assert.AreEqual(new TimeSpan(1, 0, 0), cuesheet.SplitPoints.Last().Moment); + } + } +} \ No newline at end of file diff --git a/AudioCuesheetEditorTests/Model/UI/TraceChangeManagerTests.cs b/AudioCuesheetEditorTests/Model/UI/TraceChangeManagerTests.cs index 9ec1d320..7b86a8d3 100644 --- a/AudioCuesheetEditorTests/Model/UI/TraceChangeManagerTests.cs +++ b/AudioCuesheetEditorTests/Model/UI/TraceChangeManagerTests.cs @@ -10,6 +10,7 @@ using AudioCuesheetEditor.Model.IO.Import; using System.IO; using AudioCuesheetEditorTests.Properties; +using AudioCuesheetEditor.Model.Entity; namespace AudioCuesheetEditor.Model.UI.Tests { @@ -217,10 +218,10 @@ public void RemoveTracksTest() cuesheet.AddTrack(track2, testhelper.ApplicationOptions); cuesheet.AddTrack(track3, testhelper.ApplicationOptions); cuesheet.AddTrack(track4, testhelper.ApplicationOptions); - Assert.IsTrue(track1.IsValid); - Assert.IsTrue(track2.IsValid); - Assert.IsTrue(track3.IsValid); - Assert.IsTrue(track4.IsValid); + Assert.AreEqual(ValidationStatus.Success, track1.Validate().Status); + Assert.AreEqual(ValidationStatus.Success, track2.Validate().Status); + Assert.AreEqual(ValidationStatus.Success, track3.Validate().Status); + Assert.AreEqual(ValidationStatus.Success, track4.Validate().Status); var tracksToRemove = new List { track2, @@ -230,10 +231,15 @@ public void RemoveTracksTest() Assert.IsTrue(manager.CanUndo); manager.Undo(); Assert.AreEqual(4, cuesheet.Tracks.Count); - Assert.IsTrue(track1.IsValid); - Assert.IsTrue(track2.IsValid); - Assert.IsTrue(track3.IsValid); - Assert.IsTrue(track4.IsValid); + //We need to set object references back to cuesheet tracks since TraceChangeManager creates new objects + track1 = cuesheet.Tracks.ElementAt(0); + track2 = cuesheet.Tracks.ElementAt(1); + track3 = cuesheet.Tracks.ElementAt(2); + track4 = cuesheet.Tracks.ElementAt(3); + Assert.AreEqual(ValidationStatus.Success, track1.Validate().Status); + Assert.AreEqual(ValidationStatus.Success, track2.Validate().Status); + Assert.AreEqual(ValidationStatus.Success, track3.Validate().Status); + Assert.AreEqual(ValidationStatus.Success, track4.Validate().Status); } [TestMethod()] diff --git a/AudioCuesheetEditorTests/Model/Utility/DateTimeUtilityTests.cs b/AudioCuesheetEditorTests/Model/Utility/DateTimeUtilityTests.cs index 446b8cb0..4da6ac1f 100644 --- a/AudioCuesheetEditorTests/Model/Utility/DateTimeUtilityTests.cs +++ b/AudioCuesheetEditorTests/Model/Utility/DateTimeUtilityTests.cs @@ -1,11 +1,20 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using AudioCuesheetEditor.Model.Utility; +//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 Microsoft.VisualStudio.TestTools.UnitTesting; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using AudioCuesheetEditorTests.Utility; namespace AudioCuesheetEditor.Model.Utility.Tests { @@ -15,38 +24,40 @@ public class DateTimeUtilityTests [TestMethod()] public void ParseTimeSpanTest() { - var timespan = DateTimeUtility.ParseTimeSpan("01:23:45"); + var utility = new DateTimeUtility(new TimeSpanFormat()); + var timespan = utility.ParseTimeSpan("01:23:45"); Assert.IsNotNull(timespan); Assert.AreEqual(new TimeSpan(1, 23, 45), timespan); var format = new TimeSpanFormat() { Scheme = "(?'Minutes'\\d{1,})[:](?'Seconds'\\d{1,})" }; - timespan = DateTimeUtility.ParseTimeSpan("3:12", format); + utility = new DateTimeUtility(format); + timespan = utility.ParseTimeSpan("3:12"); Assert.IsNotNull(timespan); Assert.AreEqual(new TimeSpan(0, 3, 12), timespan); - timespan = DateTimeUtility.ParseTimeSpan("63:12", format); + timespan = utility.ParseTimeSpan("63:12"); Assert.IsNotNull(timespan); Assert.AreEqual(new TimeSpan(1, 3, 12), timespan); format.Scheme = "(?'Hours'\\d{1,})[:](?'Minutes'\\d{1,})"; - timespan = DateTimeUtility.ParseTimeSpan("23:12", format); + timespan = utility.ParseTimeSpan("23:12"); Assert.IsNotNull(timespan); Assert.AreEqual(new TimeSpan(23, 12, 0), timespan); format.Scheme = "(?'Hours'\\d{1,})[:](?'Minutes'\\d{1,})[:](?'Seconds'\\d{1,})"; - timespan = DateTimeUtility.ParseTimeSpan("23:45:56", format); + timespan = utility.ParseTimeSpan("23:45:56"); Assert.IsNotNull(timespan); Assert.AreEqual(new TimeSpan(0, 23, 45, 56), timespan); format.Scheme = "(?'Days'\\d{1,})[.](?'Hours'\\d{1,})[:](?'Minutes'\\d{1,})[:](?'Seconds'\\d{1,})"; - timespan = DateTimeUtility.ParseTimeSpan("2.23:45:56", format); + timespan = utility.ParseTimeSpan("2.23:45:56"); Assert.IsNotNull(timespan); Assert.AreEqual(new TimeSpan(2, 23, 45, 56), timespan); format.Scheme = "(?'Hours'\\d{1,})[:](?'TimeSpanFormat.Minutes'\\d{1,})[:](?'TimeSpanFormat.Seconds'\\d{1,})[.](?'TimeSpanFormat.Milliseconds'\\d{1,})"; - timespan = DateTimeUtility.ParseTimeSpan("23:45:56.599", format); + timespan = utility.ParseTimeSpan("23:45:56.599"); Assert.IsNotNull(timespan); Assert.AreEqual(new TimeSpan(0, 23, 45, 56, 599), timespan); - timespan = DateTimeUtility.ParseTimeSpan("1.2e:45:87.h3a", format); + timespan = utility.ParseTimeSpan("1.2e:45:87.h3a"); Assert.IsNull(timespan); - timespan = DateTimeUtility.ParseTimeSpan("Test", format); + timespan = utility.ParseTimeSpan("Test"); Assert.IsNull(timespan); format.Scheme = "this is a test"; - timespan = DateTimeUtility.ParseTimeSpan("Test", format); + timespan = utility.ParseTimeSpan("Test"); Assert.IsNull(timespan); } } diff --git a/AudioCuesheetEditorTests/Utility/TestHelper.cs b/AudioCuesheetEditorTests/Utility/TestHelper.cs index ddbc5a1b..e5776f9c 100644 --- a/AudioCuesheetEditorTests/Utility/TestHelper.cs +++ b/AudioCuesheetEditorTests/Utility/TestHelper.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.Controller; using AudioCuesheetEditor.Model.Entity; +using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Model.Options; using Blazorise.Localization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System; namespace AudioCuesheetEditorTests.Utility { @@ -26,17 +27,12 @@ internal class TestHelper { public TestHelper() { - var localizerService = new TextLocalizerService(); - Localizer = new TextLocalizer(localizerService); - CuesheetController = new CuesheetController(); ApplicationOptions = new ApplicationOptions { LinkTracksWithPreviousOne = false }; } - public ITextLocalizer Localizer { get; private set; } - public CuesheetController CuesheetController { get; private set; } public ApplicationOptions ApplicationOptions { get; private set; } public static ILogger CreateLogger() { @@ -44,8 +40,7 @@ public static ILogger CreateLogger() .AddLogging() .BuildServiceProvider(); - var factory = serviceProvider.GetService(); - + var factory = serviceProvider.GetService() ?? throw new NullReferenceException(); return factory.CreateLogger(); } } diff --git a/Readme.md b/Readme.md index cc480ab2..fbcc7a23 100644 --- a/Readme.md +++ b/Readme.md @@ -8,7 +8,14 @@ Basically it is a program for handling audio cuesheet files and everything aroun AudioCuesheetEditor is a Blazor based web application for writing audio cuesheets. There is much validation that helps the user to write a valid cuesheet. You can import external data (like text files, xml files, etc.) and analyse them directly in GUI. There are also much export variations like CSV, but you can customize export freely. ## Usage -Simply open the link https://neocodermatrix86.github.io/AudioCuesheetEditor/ on any browser +Simply open the link https://audiocuesheeteditor.netlify.app/ on any browser + +## Environments +### Production +The current stable version can be found here: https://audiocuesheeteditor.netlify.app/ +### Preview +The next release candidate version can be found here: https://preview-audiocuesheeteditor.netlify.app/ + ## Contributing You can contribute to the project by opening up issues or adding feature requests. To do so, you simply open https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues and add a new issue. If you want, you can also contribute by developing a feature or fixing a bug. More can be found here: https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/blob/master/CONTRIBUTING.md diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 00000000..ff1c0508 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,4 @@ +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 \ No newline at end of file diff --git a/netlify/build.sh b/netlify/build.sh new file mode 100644 index 00000000..7e646326 --- /dev/null +++ b/netlify/build.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -e + +## install latest .NET 7.0 release +pushd /tmp +wget https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh +chmod u+x /tmp/dotnet-install.sh +/tmp/dotnet-install.sh --channel 7.0 +popd + +## Install wasm-tools +dotnet workload install wasm-tools + +## Run Unit Test +dotnet test + +## publish project to known location for subsequent deployment by Netlify +dotnet publish -c Release -o release \ No newline at end of file