diff --git a/AudioCuesheetEditor/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index 7fbf0ec5..9a9dc7f8 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -7,7 +7,7 @@ enable https://github.com/NeoCoderMatrix86/AudioCuesheetEditor 3.0 - 6.0.0 + 7.0.0 false true true @@ -53,6 +53,8 @@ + + @@ -98,6 +100,8 @@ + + @@ -110,20 +114,20 @@ - - + + - - + + - + - + diff --git a/AudioCuesheetEditor/Data/Options/ILocalStorageOptionsProvider.cs b/AudioCuesheetEditor/Data/Options/ILocalStorageOptionsProvider.cs new file mode 100644 index 00000000..938d0b4c --- /dev/null +++ b/AudioCuesheetEditor/Data/Options/ILocalStorageOptionsProvider.cs @@ -0,0 +1,28 @@ +//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.Options; +using System.Linq.Expressions; + +namespace AudioCuesheetEditor.Data.Options +{ + public interface ILocalStorageOptionsProvider + { + event EventHandler? OptionSaved; + Task GetOptions() where T : IOptions; + Task SaveOptions(IOptions options); + Task SaveOptionsValue(Expression> propertyExpression, object value) where T : class, IOptions, new(); + } +} diff --git a/AudioCuesheetEditor/Data/Options/LocalStorageOptionsProvider.cs b/AudioCuesheetEditor/Data/Options/LocalStorageOptionsProvider.cs index 62cd9f52..743a4cf1 100644 --- a/AudioCuesheetEditor/Data/Options/LocalStorageOptionsProvider.cs +++ b/AudioCuesheetEditor/Data/Options/LocalStorageOptionsProvider.cs @@ -22,7 +22,7 @@ namespace AudioCuesheetEditor.Data.Options { - public class LocalStorageOptionsProvider(IJSRuntime jsRuntime) + public class LocalStorageOptionsProvider(IJSRuntime jsRuntime): ILocalStorageOptionsProvider { public event EventHandler? OptionSaved; diff --git a/AudioCuesheetEditor/Extensions/SessionStateContainer.cs b/AudioCuesheetEditor/Extensions/SessionStateContainer.cs index cbc900d4..1a988975 100644 --- a/AudioCuesheetEditor/Extensions/SessionStateContainer.cs +++ b/AudioCuesheetEditor/Extensions/SessionStateContainer.cs @@ -31,8 +31,6 @@ public class SessionStateContainer private ViewMode currentViewMode; private Cuesheet cuesheet; private Cuesheet? importCuesheet; - private TextImportfile? textImportFile; - private CuesheetImportfile? cuesheetImportFile; private Audiofile? importAudiofile; public SessionStateContainer(TraceChangeManager traceChangeManager) @@ -65,50 +63,6 @@ public Cuesheet? ImportCuesheet } } - public TextImportfile? TextImportFile - { - get { return textImportFile; } - set - { - if (textImportFile != null) - { - textImportFile.AnalysisFinished -= TextImportScheme_AnalysisFinished; - } - textImportFile = value; - if (textImportFile != null) - { - textImportFile.AnalysisFinished += TextImportScheme_AnalysisFinished; - ImportCuesheet = textImportFile.Cuesheet; - } - else - { - ImportCuesheet = null; - } - } - } - - public CuesheetImportfile? CuesheetImportFile - { - get { return cuesheetImportFile; } - set - { - if (cuesheetImportFile != null) - { - cuesheetImportFile.AnalysisFinished -= CuesheetImportFile_AnalysisFinished; - } - cuesheetImportFile = value; - if ((CuesheetImportFile != null) && (CuesheetImportFile.Cuesheet != null)) - { - CuesheetImportFile.AnalysisFinished += CuesheetImportFile_AnalysisFinished; - ImportCuesheet = CuesheetImportFile.Cuesheet; - } - else - { - ImportCuesheet = null; - } - } - } - public Audiofile? ImportAudiofile { get => importAudiofile; @@ -133,74 +87,26 @@ public ViewMode CurrentViewMode } } - public IImportfile? Importfile - { - get - { - if (TextImportFile != null) - { - return TextImportFile; - } - if (CuesheetImportFile != null) - { - return CuesheetImportFile; - } - return null; - } - } + public IImportfile? Importfile{ get; set; } public void ResetImport() { - TextImportFile = null; - CuesheetImportFile = null; + Importfile = null; ImportAudiofile = null; - } - - public void StartImportCuesheet(ApplicationOptions applicationOptions) - { - if (ImportCuesheet != null) - { - Cuesheet.Import(ImportCuesheet, applicationOptions, _traceChangeManager); - ImportCuesheet = null; - } - ResetImport(); + ImportCuesheet = null; } 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) + + public void FireCuesheetImported() { CuesheetChanged?.Invoke(this, EventArgs.Empty); } - - void CuesheetImportFile_AnalysisFinished(object? sender, EventArgs e) - { - if (CuesheetImportFile != null) - { - ImportCuesheet = CuesheetImportFile.Cuesheet; - } - else - { - ImportCuesheet = null; - } - } } } diff --git a/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs b/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs index 0c68e392..bdba0ae4 100644 --- a/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs +++ b/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs @@ -24,7 +24,7 @@ public static class WebAssemblyHostExtension { public async static Task SetDefaultCulture(this WebAssemblyHost host) { - var localStorageOptionsProvider = host.Services.GetRequiredService(); + var localStorageOptionsProvider = host.Services.GetRequiredService(); var options = await localStorageOptionsProvider.GetOptions(); CultureInfo.DefaultThreadCurrentCulture = options.Culture; diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/CatalogueNumber.cs b/AudioCuesheetEditor/Model/AudioCuesheet/CatalogueNumber.cs index fa38b21f..41c35d76 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/CatalogueNumber.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/CatalogueNumber.cs @@ -46,12 +46,12 @@ protected override ValidationResult Validate(string property) validationStatus = ValidationStatus.Success; if (Value.All(Char.IsDigit) == false) { - validationMessages ??= new(); + validationMessages ??= []; validationMessages.Add(new ValidationMessage("{0} must only contain numbers!", nameof(Value))); } if (Value.Length != 13) { - validationMessages ??= new(); + validationMessages ??= []; validationMessages.Add(new ValidationMessage("{0} has an invalid length. Allowed length is {1}!", nameof(Value), 13)); } } diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs index 4b17102c..755f4d14 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs @@ -14,7 +14,6 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.Entity; -using AudioCuesheetEditor.Model.IO; using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Model.IO.Export; using AudioCuesheetEditor.Model.Options; @@ -39,11 +38,8 @@ public class CuesheetSectionAddRemoveEventArgs(CuesheetSection section) : EventA public CuesheetSection Section { get; } = section; } - public class Cuesheet(TraceChangeManager? traceChangeManager = null) : Validateable, ICuesheetEntity, ITraceable + public class Cuesheet(TraceChangeManager? traceChangeManager = null) : Validateable, ITraceable, ICuesheet { - public const String MimeType = "text/*"; - public const String FileExtension = ".cue"; - private readonly object syncLock = new(); private List tracks = []; @@ -62,7 +58,6 @@ public class Cuesheet(TraceChangeManager? traceChangeManager = null) : Validatea public event EventHandler? TrackRemoved; public event EventHandler? SectionAdded; public event EventHandler? SectionRemoved; - public event EventHandler? CuesheetImported; [JsonInclude] public IReadOnlyCollection Tracks @@ -176,7 +171,7 @@ public TimeSpan? RecordingTime } [JsonIgnore] - public Boolean IsImporting { get; private set; } + public Boolean IsImporting { get; set; } [JsonInclude] public IReadOnlyCollection Sections @@ -393,27 +388,6 @@ public void MoveTrack(Track track, MoveDirection moveDirection) } } - public void Import(Cuesheet cuesheet, ApplicationOptions applicationOptions, TraceChangeManager? traceChangeManager = null) - { - //Since we use a stack for several changes we need to lock execution for everything else - lock (syncLock) - { - IsImporting = true; - //We are doing a bulk edit, so inform the TraceChangeManager - if (traceChangeManager != null) - { - traceChangeManager.BulkEdit = true; - } - CopyValues(cuesheet, applicationOptions); - if (traceChangeManager != null) - { - traceChangeManager.BulkEdit = false; - } - IsImporting = false; - } - CuesheetImported?.Invoke(this, EventArgs.Empty); - } - public void StartRecording() { recordingStart = DateTime.UtcNow; @@ -554,32 +528,6 @@ private void ReCalculateTrackProperties(Track trackToCalculate) } } - /// - /// Copy values from import cuesheet to this cuesheet - /// - /// Reference to import cuesheet - /// Reference to application options - private void CopyValues(Cuesheet cuesheet, ApplicationOptions applicationOptions) - { - Artist = cuesheet.Artist; - Title = cuesheet.Title; - Audiofile = cuesheet.Audiofile; - CDTextfile = cuesheet.CDTextfile; - Cataloguenumber = cuesheet.Cataloguenumber; - foreach (var importTrack in cuesheet.Tracks) - { - //We don't want to copy the cuesheet reference since we are doing a copy and want to assign the track to this object - var track = new Track(importTrack, false); - AddTrack(track, applicationOptions); - } - // Copy sections - foreach (var splitPoint in cuesheet.Sections) - { - var newSplitPoint = AddSection(); - newSplitPoint.CopyValues(splitPoint); - } - } - private void Track_RankPropertyValueChanged(object? sender, string e) { if (sender is Track trackRaisedEvent) diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/ICuesheetEntity.cs b/AudioCuesheetEditor/Model/AudioCuesheet/ICuesheet.cs similarity index 70% rename from AudioCuesheetEditor/Model/AudioCuesheet/ICuesheetEntity.cs rename to AudioCuesheetEditor/Model/AudioCuesheet/ICuesheet.cs index 4e0b86f4..02a2e007 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/ICuesheetEntity.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/ICuesheet.cs @@ -13,19 +13,12 @@ //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.Threading.Tasks; namespace AudioCuesheetEditor.Model.AudioCuesheet { - /// - /// Interface for cuesheet entities (Cuesheet, track, etc.) - /// - public interface ICuesheetEntity + public interface ICuesheet { - public String? Artist { get; set; } - public String? Title { get; set; } + string? Artist { get; set; } + string? Title { get; set; } } } diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/ITrack.cs b/AudioCuesheetEditor/Model/AudioCuesheet/ITrack.cs new file mode 100644 index 00000000..6b0d6192 --- /dev/null +++ b/AudioCuesheetEditor/Model/AudioCuesheet/ITrack.cs @@ -0,0 +1,31 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +namespace AudioCuesheetEditor.Model.AudioCuesheet +{ + public interface ITrack + { + string? Artist { get; set; } + string? Title { get; set; } + uint? Position { get; set; } + TimeSpan? Begin { get; set; } + TimeSpan? End { get; set; } + TimeSpan? Length { get; set; } + IReadOnlyCollection Flags { get; } + TimeSpan? PreGap { get; set; } + TimeSpan? PostGap { get; set; } + void SetFlags(IEnumerable flags); + } +} diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportCuesheet.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportCuesheet.cs new file mode 100644 index 00000000..053228b6 --- /dev/null +++ b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportCuesheet.cs @@ -0,0 +1,30 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. + + +namespace AudioCuesheetEditor.Model.AudioCuesheet.Import +{ + public class ImportCuesheet : ICuesheet + { + public string? Artist { get; set; } + public string? Title { get; set; } + public string? Audiofile { get;set; } + /// + public string? CDTextfile { get; set; } + public string? Cataloguenumber { get; set; } + public ICollection Tracks { get; } = []; + } +} diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportTrack.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportTrack.cs new file mode 100644 index 00000000..88f9033d --- /dev/null +++ b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportTrack.cs @@ -0,0 +1,38 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. + +namespace AudioCuesheetEditor.Model.AudioCuesheet.Import +{ + public class ImportTrack : ITrack + { + private readonly List flags = []; + public string? Artist { get; set; } + public string? Title { get; set; } + public uint? Position { get; set; } + public TimeSpan? Begin { get; set; } + public TimeSpan? End { get; set; } + public TimeSpan? Length { get; set; } + public IReadOnlyCollection Flags => flags; + public TimeSpan? PreGap { get; set; } + public TimeSpan? PostGap { get; set; } + public DateTime? StartDateTime { get; set; } + public void SetFlags(IEnumerable flags) + { + this.flags.Clear(); + this.flags.AddRange(flags); + } + } +} diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs index 7e67d2c2..24353e82 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs @@ -25,7 +25,7 @@ public enum SetFlagMode Remove } - public class Track : Validateable, ICuesheetEntity, ITraceable + public class Track : Validateable, ITraceable, ITrack { public static readonly List AllPropertyNames = [nameof(IsLinkedToPreviousTrack), nameof(Position), nameof(Artist), nameof(Title), nameof(Begin), nameof(End), nameof(Flags), nameof(PreGap), nameof(PostGap), nameof(Length)]; @@ -54,19 +54,18 @@ public class Track : Validateable, ICuesheetEntity, ITraceable /// public event EventHandler? TraceablePropertyChanged; + public Track() { } + /// /// Create object with copied values from input /// /// Object to copy values from /// /// Copy cuesheet reference from track also? - public Track(Track track, Boolean copyCuesheetReference = true) + public Track(ITrack track, Boolean copyCuesheetReference = true) : this() { CopyValues(track, copyCuesheetReference); } - public Track() - { - - } + public uint? Position { get => position; @@ -242,17 +241,17 @@ public Track Clone() /// Copy PreGap from track also? /// Copy PostGap from track also? /// A list of properties, for whom the internal set construct should be used, in order to avoid automatic calculation. - public void CopyValues(Track track, Boolean setCuesheet = true, Boolean setIsLinkedToPreviousTrack = true, Boolean setPosition = true, Boolean setArtist = true, Boolean setTitle = true, Boolean setBegin = true, Boolean setEnd = true, Boolean setLength = false, Boolean setFlags = true, Boolean setPreGap = true, Boolean setPostGap = true, IEnumerable? useInternalSetters = null) + public void CopyValues(ITrack track, Boolean setCuesheet = true, Boolean setIsLinkedToPreviousTrack = true, Boolean setPosition = true, Boolean setArtist = true, Boolean setTitle = true, Boolean setBegin = true, Boolean setEnd = true, Boolean setLength = false, Boolean setFlags = true, Boolean setPreGap = true, Boolean setPostGap = true, IEnumerable? useInternalSetters = null) { - if (setCuesheet) + if (setCuesheet && (track is Track cuesheetTrack)) { if ((useInternalSetters != null) && (useInternalSetters.Contains(nameof(Cuesheet)))) { - cuesheet = track.Cuesheet; + cuesheet = cuesheetTrack.Cuesheet; } else { - Cuesheet = track.Cuesheet; + Cuesheet = cuesheetTrack.Cuesheet; } } if (setPosition) @@ -348,15 +347,15 @@ public void CopyValues(Track track, Boolean setCuesheet = true, Boolean setIsLin PostGap = track.PostGap; } } - if (setIsLinkedToPreviousTrack) + if (setIsLinkedToPreviousTrack && (track is Track cuesheetTrack2)) { if ((useInternalSetters != null) && (useInternalSetters.Contains(nameof(IsLinkedToPreviousTrack)))) { - isLinkedToPreviousTrack = track.IsLinkedToPreviousTrack; + isLinkedToPreviousTrack = cuesheetTrack2.IsLinkedToPreviousTrack; } else { - IsLinkedToPreviousTrack = track.IsLinkedToPreviousTrack; + IsLinkedToPreviousTrack = cuesheetTrack2.IsLinkedToPreviousTrack; } } } diff --git a/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs b/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs index a390feda..67568106 100644 --- a/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs +++ b/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs @@ -17,13 +17,14 @@ namespace AudioCuesheetEditor.Model.IO.Audio { - public class Audiofile : IDisposable + [method: JsonConstructor] + public class Audiofile(String name, Boolean isRecorded = false) : IDisposable { public static readonly String RecordingFileName = "Recording.webm"; public static readonly AudioCodec AudioCodecWEBM = new("audio/webm", ".webm", "AudioCodec WEBM"); - public static readonly List AudioCodecs = new() - { + public static readonly List AudioCodecs = + [ AudioCodecWEBM, new AudioCodec("audio/mpeg", ".mp3", "AudioCodec MP3"), new AudioCodec("audio/ogg", ".oga", "AudioCodec OGA"), @@ -32,19 +33,12 @@ public class Audiofile : IDisposable new AudioCodec("audio/wav", ".wav", "AudioCodec WAV"), new AudioCodec("audio/wav", ".wave", "AudioCodec WAVE"), new AudioCodec("audio/flac", ".flac", "AudioCodec FLAC") - }; + ]; private AudioCodec? audioCodec; private bool disposedValue; public event EventHandler? ContentStreamLoaded; - - [JsonConstructor] - public Audiofile(String name, Boolean isRecorded = false) - { - Name = name; - IsRecorded = isRecorded; - } public Audiofile(String name, String objectURL, AudioCodec? audioCodec, HttpClient httpClient, Boolean isRecorded = false) : this(name, isRecorded) { @@ -57,7 +51,7 @@ public Audiofile(String name, String objectURL, AudioCodec? audioCodec, HttpClie HttpClient = httpClient; } - public String Name { get; private set; } + public String Name { get; private set; } = name; [JsonIgnore] public String? ObjectURL { get; private set; } /// @@ -74,7 +68,7 @@ public Boolean IsContentStreamLoaded [JsonIgnore] public Stream? ContentStream { get; private set; } [JsonIgnore] - public Boolean IsRecorded { get; private set; } + public Boolean IsRecorded { get; private set; } = isRecorded; /// /// Duration of the audio file /// @@ -88,7 +82,7 @@ public AudioCodec? AudioCodec private set { audioCodec = value; - if ((audioCodec != null) && (Name.EndsWith(audioCodec.FileExtension) == false)) + if ((audioCodec != null) && (Name?.EndsWith(audioCodec.FileExtension) == false)) { //Replace file ending Name = String.Format("{0}{1}", Path.GetFileNameWithoutExtension(Name), audioCodec.FileExtension); @@ -107,7 +101,7 @@ public String? AudioFileType audioFileType = AudioCodec.FileExtension.Replace(".", "").ToUpper(); } //Try to find by file name - audioFileType ??= Path.GetExtension(Name).Replace(".", "").ToUpper(); + audioFileType ??= Path.GetExtension(Name)?.Replace(".", "").ToUpper(); return audioFileType; } } diff --git a/AudioCuesheetEditor/Model/IO/Export/ExportfileGenerator.cs b/AudioCuesheetEditor/Model/IO/Export/ExportfileGenerator.cs index a587558e..8190dc1f 100644 --- a/AudioCuesheetEditor/Model/IO/Export/ExportfileGenerator.cs +++ b/AudioCuesheetEditor/Model/IO/Export/ExportfileGenerator.cs @@ -57,7 +57,7 @@ public IReadOnlyCollection GenerateExportfiles() { case ExportType.Cuesheet: content = WriteCuesheet(audioFileName, section); - filename = String.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(ApplicationOptions?.CuesheetFilename), counter, Cuesheet.FileExtension); + filename = String.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(ApplicationOptions?.CuesheetFilename), counter, FileExtensions.Cuesheet); break; case ExportType.Exportprofile: if (Exportprofile != null) diff --git a/AudioCuesheetEditor/Model/IO/FileExtensions.cs b/AudioCuesheetEditor/Model/IO/FileExtensions.cs new file mode 100644 index 00000000..d4b76535 --- /dev/null +++ b/AudioCuesheetEditor/Model/IO/FileExtensions.cs @@ -0,0 +1,24 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +namespace AudioCuesheetEditor.Model.IO +{ + public class FileExtensions + { + public const string Text = ".txt"; + public const string Projectfile = ".ace"; + public const string Cuesheet = ".cue"; + } +} diff --git a/AudioCuesheetEditor/Model/IO/FileMimeTypes.cs b/AudioCuesheetEditor/Model/IO/FileMimeTypes.cs new file mode 100644 index 00000000..a6edaf11 --- /dev/null +++ b/AudioCuesheetEditor/Model/IO/FileMimeTypes.cs @@ -0,0 +1,24 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +namespace AudioCuesheetEditor.Model.IO +{ + public static class FileMimeTypes + { + public const string Text = "text/plain"; + public const string Projectfile = "text/*"; + public const string Cuesheet = "text/*"; + } +} diff --git a/AudioCuesheetEditor/Model/IO/Import/IImportfile.cs b/AudioCuesheetEditor/Model/IO/Import/IImportfile.cs index 44bd3490..b99d07b0 100644 --- a/AudioCuesheetEditor/Model/IO/Import/IImportfile.cs +++ b/AudioCuesheetEditor/Model/IO/Import/IImportfile.cs @@ -13,6 +13,9 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. +using AudioCuesheetEditor.Model.AudioCuesheet.Import; +using AudioCuesheetEditor.Services.IO; + namespace AudioCuesheetEditor.Model.IO.Import { public interface IImportfile @@ -20,10 +23,19 @@ public interface IImportfile /// /// File content (each element is a file line) /// - public IEnumerable FileContent { get; set; } + IEnumerable? FileContent { get; set; } /// /// File content with marking which passages has been reconized by scheme /// - public IEnumerable FileContentRecognized { get; } + IEnumerable? FileContentRecognized { get; set; } + /// + /// Exception that has been thrown while readinng out the file + /// + Exception? AnalyseException { get; set; } + /// + /// The cuesheet which was created during analysing the + /// + ImportCuesheet? AnalysedCuesheet { get; set; } + ImportFileType FileType { get; set; } } } diff --git a/AudioCuesheetEditor/Model/IO/Import/Importfile.cs b/AudioCuesheetEditor/Model/IO/Import/Importfile.cs new file mode 100644 index 00000000..033e1989 --- /dev/null +++ b/AudioCuesheetEditor/Model/IO/Import/Importfile.cs @@ -0,0 +1,33 @@ +//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.Import; +using AudioCuesheetEditor.Services.IO; + +namespace AudioCuesheetEditor.Model.IO.Import +{ + public class Importfile : IImportfile + { + /// + public IEnumerable? FileContent { get; set; } + /// + public IEnumerable? FileContentRecognized { get; set; } + /// + public Exception? AnalyseException { get; set; } + /// + public ImportCuesheet? AnalysedCuesheet { get; set; } + public ImportFileType FileType { get; set; } + } +} diff --git a/AudioCuesheetEditor/Model/IO/Import/TextImportScheme.cs b/AudioCuesheetEditor/Model/IO/Import/TextImportScheme.cs index 752ba3e1..f5d3e865 100644 --- a/AudioCuesheetEditor/Model/IO/Import/TextImportScheme.cs +++ b/AudioCuesheetEditor/Model/IO/Import/TextImportScheme.cs @@ -14,6 +14,7 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.AudioCuesheet.Import; using AudioCuesheetEditor.Model.Entity; using Blazorise.Localization; @@ -54,6 +55,7 @@ static TextImportScheme() var schemeTrackFlags = String.Format("(?'{0}.{1}'{2})", nameof(Track), nameof(Track.Flags), EnterRegularExpressionHere); var schemeTrackPreGap = String.Format("(?'{0}.{1}'{2})", nameof(Track), nameof(Track.PreGap), EnterRegularExpressionHere); var schemeTrackPostGap = String.Format("(?'{0}.{1}'{2})", nameof(Track), nameof(Track.PostGap), EnterRegularExpressionHere); + var schemeTrackStartDateTime = String.Format("(?'{0}.{1}'{2})", nameof(Track), nameof(ImportTrack.StartDateTime), EnterRegularExpressionHere); AvailableSchemesTrack = new Dictionary { @@ -65,7 +67,8 @@ static TextImportScheme() { nameof(Track.Length), schemeTrackLength }, { nameof(Track.Flags), schemeTrackFlags }, { nameof(Track.PreGap), schemeTrackPreGap }, - { nameof(Track.PostGap), schemeTrackPostGap } + { nameof(Track.PostGap), schemeTrackPostGap }, + { nameof(ImportTrack.StartDateTime), schemeTrackStartDateTime } }; } @@ -122,14 +125,14 @@ protected override ValidationResult Validate(string property) 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); - validationMessages ??= new(); + var realRegularExpression = SchemeCuesheet.Substring(startIndex, (SchemeCuesheet.IndexOf(')', startIndex) + 1) - startIndex); + validationMessages ??= []; validationMessages.Add(new ValidationMessage("{0} contains placeholders that can not be solved! Please remove invalid placeholder '{1}'.", nameof(SchemeCuesheet), realRegularExpression)); } } if (SchemeCuesheet?.Contains(enterRegularExpression) == true) { - validationMessages ??= new(); + validationMessages ??= []; validationMessages.Add(new ValidationMessage("Replace '{0}' by a regular expression!", enterRegularExpression)); } break; @@ -140,14 +143,14 @@ protected override ValidationResult Validate(string property) 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); - validationMessages ??= new(); + var realRegularExpression = SchemeTracks.Substring(startIndex, (SchemeTracks.IndexOf(')', startIndex) + 1) - startIndex); + validationMessages ??= []; validationMessages.Add(new ValidationMessage("{0} contains placeholders that can not be solved! Please remove invalid placeholder '{1}'.", nameof(SchemeTracks), realRegularExpression)); } } if (SchemeTracks?.Contains(enterRegularExpression) == true) { - validationMessages ??= new(); + validationMessages ??= []; validationMessages.Add(new ValidationMessage("Replace '{0}' by a regular expression!", enterRegularExpression)); } break; diff --git a/AudioCuesheetEditor/Model/IO/Import/TextImportfile.cs b/AudioCuesheetEditor/Model/IO/Import/TextImportfile.cs deleted file mode 100644 index 3e38db41..00000000 --- a/AudioCuesheetEditor/Model/IO/Import/TextImportfile.cs +++ /dev/null @@ -1,293 +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 AudioCuesheetEditor.Model.Options; -using AudioCuesheetEditor.Model.Utility; -using System.Reflection; -using System.Text.RegularExpressions; - -namespace AudioCuesheetEditor.Model.IO.Import -{ - public class TextImportfile : IImportfile, IDisposable - { - public const String MimeType = "text/plain"; - public const String FileExtension = ".txt"; - - public EventHandler? AnalysisFinished; - - private TextImportScheme textImportScheme; - private TimeSpanFormat? timeSpanFormat; - private bool disposedValue; - private IEnumerable fileContent; - - public TextImportfile(MemoryStream fileContentStream, ImportOptions? importOptions = null) - { - FileContentRecognized = []; - textImportScheme = new TextImportScheme(); - fileContent = []; - fileContentStream.Position = 0; - using var reader = new StreamReader(fileContentStream); - List lines = []; - while (reader.EndOfStream == false) - { - lines.Add(reader.ReadLine()); - } - FileContent = lines.AsReadOnly(); - TimeSpanFormat = new TimeSpanFormat(); - if (importOptions == null) - { - TextImportScheme = TextImportScheme.DefaultTextImportScheme; - } - else - { - if (importOptions.TimeSpanFormat != null) - { - TimeSpanFormat = importOptions.TimeSpanFormat; - } - TextImportScheme = importOptions.TextImportScheme; - } - } - - /// - public IEnumerable FileContent - { - get => fileContent; - set - { - fileContent = value; - Analyse(); - } - } - - /// - public IEnumerable FileContentRecognized { get; private set; } - - public TextImportScheme TextImportScheme - { - get { return textImportScheme; } - set - { - textImportScheme.SchemeChanged -= TextImportScheme_SchemeChanged; - textImportScheme = value; - textImportScheme.SchemeChanged += TextImportScheme_SchemeChanged; - Analyse(); - } - } - - public Exception? AnalyseException { get; private set; } - - public Boolean IsValid { get { return AnalyseException == null; } } - - public Cuesheet? Cuesheet { get; private set; } - - public TimeSpanFormat TimeSpanFormat - { - get - { - timeSpanFormat ??= new TimeSpanFormat(); - return timeSpanFormat; - } - set - { - if (timeSpanFormat != null) - { - timeSpanFormat.SchemeChanged -= TimeSpanFormat_SchemeChanged; - } - timeSpanFormat = value; - if (timeSpanFormat != null) - { - timeSpanFormat.SchemeChanged += TimeSpanFormat_SchemeChanged; - } - } - } - - private void TextImportScheme_SchemeChanged(object? sender, string e) - { - Analyse(); - } - - private void TimeSpanFormat_SchemeChanged(object? sender, EventArgs eventArgs) - { - Analyse(); - } - - private void Analyse() - { - try - { - Cuesheet = new Cuesheet(); - FileContentRecognized = []; - AnalyseException = null; - Boolean cuesheetRecognized = false; - List recognizedFileContent = []; - foreach (var line in FileContent) - { - var recognizedLine = line; - if (String.IsNullOrEmpty(line) == false) - { - Boolean recognized = false; - if ((recognized == false) && (cuesheetRecognized == false) && (String.IsNullOrEmpty(TextImportScheme.SchemeCuesheet) == false)) - { - //Remove entity names - var expression = TextImportScheme.SchemeCuesheet.Replace(String.Format("{0}.", nameof(AudioCuesheet.Cuesheet)), String.Empty).Replace(String.Format("{0}.", nameof(Track)), String.Empty); - var regExCuesheet = new Regex(expression); - recognizedLine = AnalyseLine(line, Cuesheet, regExCuesheet); - recognized = recognizedLine != null; - cuesheetRecognized = recognizedLine != null; - } - if ((recognized == false) && (String.IsNullOrEmpty(TextImportScheme.SchemeTracks) == false)) - { - //Remove entity names - var expression = TextImportScheme.SchemeTracks.Replace(String.Format("{0}.", nameof(AudioCuesheet.Cuesheet)), String.Empty).Replace(String.Format("{0}.", nameof(Track)), String.Empty); - var regExTracks = new Regex(expression); - var track = new Track(); - recognizedLine = AnalyseLine(line, track, regExTracks); - recognized = recognizedLine != null; - Cuesheet.AddTrack(track); - } - } - recognizedFileContent.Add(recognizedLine); - } - if (recognizedFileContent.Count > 0) - { - FileContentRecognized = recognizedFileContent.AsReadOnly(); - } - } - catch (Exception ex) - { - FileContentRecognized = FileContent; - AnalyseException = ex; - Cuesheet = null; - } - AnalysisFinished?.Invoke(this, EventArgs.Empty); - } - - /// - /// Analyses a line and sets the properties on the entity - /// - /// Line to analyse - /// Entity to set properties on - /// Regular expression to use for analysis - /// Analysed line with marking what has been matched or empty string - /// Occurs when property or group could not be found! - private String? AnalyseLine(String line, ICuesheetEntity entity, Regex regex) - { - String? recognized = null; - string? recognizedLine = line; - if (String.IsNullOrEmpty(line) == false) - { - var match = regex.Match(line); - if (match.Success) - { - for (int groupCounter = 1; groupCounter < match.Groups.Count; groupCounter++) - { - var key = match.Groups.Keys.ElementAt(groupCounter); - var group = match.Groups.GetValueOrDefault(key); - if ((group != null) && (group.Success)) - { - var property = entity.GetType().GetProperty(key, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - if (property != null) - { - SetValue(entity, property, group.Value); - recognizedLine = string.Concat(recognizedLine.AsSpan(0, group.Index + (13 * (groupCounter - 1))) - , String.Format(CuesheetConstants.RecognizedMarkHTML, group.Value) - , recognizedLine.AsSpan(group.Index + (13 * (groupCounter - 1)) + group.Length)); - } - else - { - throw new NullReferenceException(String.Format("Property '{0}' was not found for line content {1}", key, line)); - } - } - else - { - throw new NullReferenceException(String.Format("Group '{0}' could not be found!", key)); - } - } - if (recognizedLine.Contains(CuesheetConstants.RecognizedMarkHTML.Substring(0, CuesheetConstants.RecognizedMarkHTML.IndexOf("{0}")))) - { - recognized = recognizedLine; - } - } - else - { - recognized = line; - } - } - return recognized; - } - - /// - /// Set the value on the entity - /// - /// Entity object to set the value on - /// Property to set - /// Value to set - private void SetValue(ICuesheetEntity entity, PropertyInfo property, string value) - { - if (property.PropertyType == typeof(TimeSpan?)) - { - var utility = new DateTimeUtility(TimeSpanFormat); - property.SetValue(entity, utility.ParseTimeSpan(value)); - } - if (property.PropertyType == typeof(uint?)) - { - property.SetValue(entity, Convert.ToUInt32(value)); - } - if (property.PropertyType == typeof(String)) - { - property.SetValue(entity, value); - } - if (property.PropertyType == typeof(IReadOnlyCollection)) - { - var list = Flag.AvailableFlags.Where(x => value.Contains(x.CuesheetLabel)); - ((Track)entity).SetFlags(list); - } - if (property.PropertyType == typeof(Audiofile)) - { - property.SetValue(entity, new Audiofile(value)); - } - if (property.PropertyType == typeof(Cataloguenumber)) - { - ((Cuesheet)entity).Cataloguenumber.Value = value; - } - if (property.PropertyType == typeof(CDTextfile)) - { - property.SetValue(entity, new CDTextfile(value)); - } - } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - TimeSpanFormat.SchemeChanged -= TimeSpanFormat_SchemeChanged; - textImportScheme.SchemeChanged -= TextImportScheme_SchemeChanged; - } - disposedValue = true; - } - } - - 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); - } - } -} diff --git a/AudioCuesheetEditor/Model/IO/Projectfile.cs b/AudioCuesheetEditor/Model/IO/Projectfile.cs index f7b4b2ec..da0b73e2 100644 --- a/AudioCuesheetEditor/Model/IO/Projectfile.cs +++ b/AudioCuesheetEditor/Model/IO/Projectfile.cs @@ -20,11 +20,8 @@ namespace AudioCuesheetEditor.Model.IO { - public class Projectfile + public class Projectfile(Cuesheet cuesheet) { - public const String MimeType = "text/*"; - public const String FileExtension = ".ace"; - public static readonly String DefaultFilename = "Project.ace"; public static readonly JsonSerializerOptions Options = new() @@ -39,16 +36,7 @@ public class Projectfile return JsonSerializer.Deserialize(json, Options); } - public Projectfile(Cuesheet cuesheet) - { - if (cuesheet == null) - { - throw new ArgumentNullException(nameof(cuesheet)); - } - Cuesheet = cuesheet; - } - - public Cuesheet Cuesheet { get; private set; } + public Cuesheet Cuesheet { get; private set; } = cuesheet; /// /// Generate a ProjectFile diff --git a/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs b/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs index 2f33d15d..79f88022 100644 --- a/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs +++ b/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs @@ -13,7 +13,6 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.Entity; using AudioCuesheetEditor.Model.IO; using AudioCuesheetEditor.Model.IO.Export; @@ -110,10 +109,10 @@ protected override ValidationResult Validate(string property) else { var extension = Path.GetExtension(CuesheetFilename); - if (extension.Equals(Cuesheet.FileExtension, StringComparison.OrdinalIgnoreCase) == false) + if (extension.Equals(FileExtensions.Cuesheet, StringComparison.OrdinalIgnoreCase) == false) { validationMessages ??= []; - validationMessages.Add(new ValidationMessage("{0} must end with '{1}'!", nameof(CuesheetFilename), Cuesheet.FileExtension)); + validationMessages.Add(new ValidationMessage("{0} must end with '{1}'!", nameof(CuesheetFilename), FileExtensions.Cuesheet)); } var filenameWithoutExtension = Path.GetFileNameWithoutExtension(CuesheetFilename); if (string.IsNullOrEmpty(filenameWithoutExtension)) @@ -133,10 +132,10 @@ protected override ValidationResult Validate(string property) else { var extension = Path.GetExtension(ProjectFilename); - if (extension.Equals(Projectfile.FileExtension, StringComparison.OrdinalIgnoreCase) == false) + if (extension.Equals(FileExtensions.Projectfile, StringComparison.OrdinalIgnoreCase) == false) { validationMessages ??= []; - validationMessages.Add(new ValidationMessage("{0} must end with '{1}'!", nameof(ProjectFilename), Projectfile.FileExtension)); + validationMessages.Add(new ValidationMessage("{0} must end with '{1}'!", nameof(ProjectFilename), FileExtensions.Projectfile)); } var filename = Path.GetFileNameWithoutExtension(ProjectFilename); if (String.IsNullOrEmpty(filename)) diff --git a/AudioCuesheetEditor/Model/Options/ImportOptions.cs b/AudioCuesheetEditor/Model/Options/ImportOptions.cs index 1ee000a1..5131d11e 100644 --- a/AudioCuesheetEditor/Model/Options/ImportOptions.cs +++ b/AudioCuesheetEditor/Model/Options/ImportOptions.cs @@ -21,18 +21,7 @@ namespace AudioCuesheetEditor.Model.Options { public class ImportOptions : IOptions { - public TextImportScheme TextImportScheme { get; set; } - public TimeSpanFormat? TimeSpanFormat { get; set; } - - public ImportOptions() - { - TextImportScheme = TextImportScheme.DefaultTextImportScheme; - } - - public ImportOptions(TextImportfile textImportfile) - { - TextImportScheme = textImportfile.TextImportScheme; - TimeSpanFormat = textImportfile.TimeSpanFormat; - } + public TextImportScheme TextImportScheme { get; set; } = TextImportScheme.DefaultTextImportScheme; + public TimeSpanFormat TimeSpanFormat { get; set; } = new(); } } diff --git a/AudioCuesheetEditor/Model/UI/TraceChangeManager.cs b/AudioCuesheetEditor/Model/UI/TraceChangeManager.cs index 78b5ca1c..a4321ad1 100644 --- a/AudioCuesheetEditor/Model/UI/TraceChangeManager.cs +++ b/AudioCuesheetEditor/Model/UI/TraceChangeManager.cs @@ -13,28 +13,15 @@ //You should have received a copy of the GNU General Public License //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; -using System.Linq; -using System.Threading.Tasks; - namespace AudioCuesheetEditor.Model.UI { /// /// Class for tracing changes on an object /// - public class TracedChange + public class TracedChange(ITraceable traceableObject, TraceableChange traceableChange) { - private readonly WeakReference _tracedObject; - public TracedChange(ITraceable traceableObject, TraceableChange traceableChange) - { - _tracedObject = new WeakReference(traceableObject, false); - TraceableChange = traceableChange; - } + private readonly WeakReference _tracedObject = new(traceableObject, false); + public ITraceable? TraceableObject { get @@ -47,26 +34,21 @@ public ITraceable? TraceableObject } } - public TraceableChange TraceableChange { get; } + public TraceableChange TraceableChange { get; } = traceableChange; } - public class TracedChanges + public class TracedChanges(IEnumerable changes) { - public TracedChanges(IEnumerable changes) - { - Changes = new(changes); - } - - public List Changes { get; } + public List Changes { get; } = new(changes); public Boolean HasTraceableObject { get { return Changes.Any(x => x.TraceableObject != null); } } } /// /// Manager for Undo and Redo operations on objects. /// - public class TraceChangeManager + public class TraceChangeManager(ILogger logger) { - private readonly ILogger _logger; + private readonly ILogger _logger = logger; private readonly Stack undoStack = new(); private readonly Stack redoStack = new(); @@ -77,7 +59,7 @@ public class TraceChangeManager public event EventHandler? UndoDone; public event EventHandler? RedoDone; - public Boolean CurrentlyHandlingRedoOrUndoChanges { get; private set; } + public Boolean CurrentlyHandlingRedoOrUndoChanges { get; private set; } = false; /// /// Is Undo() currently possible (are there any changes)? /// @@ -100,12 +82,6 @@ public Boolean CanRedo } } - public TraceChangeManager(ILogger logger) - { - CurrentlyHandlingRedoOrUndoChanges = false; - _logger = logger; - } - public void TraceChanges(ITraceable traceable) { traceable.TraceablePropertyChanged += Traceable_TraceablePropertyChanged; @@ -229,7 +205,7 @@ public Boolean BulkEdit _logger.LogDebug("Set BulkEdit called with {value}", value); if (value) { - bulkEditTracedChanges = new(); + bulkEditTracedChanges = []; } else { @@ -293,7 +269,7 @@ private void Traceable_TraceablePropertyChanged(object? sender, TraceablePropert if (BulkEdit == false) { //Single change - var changes = new TracedChanges(new List() { new((ITraceable)sender, e.TraceableChange) }); + var changes = new TracedChanges([new((ITraceable)sender, e.TraceableChange)]); undoStack.Push(changes); redoStack.Clear(); TracedObjectHistoryChanged?.Invoke(this, EventArgs.Empty); diff --git a/AudioCuesheetEditor/Model/Utility/IOUtility.cs b/AudioCuesheetEditor/Model/Utility/IOUtility.cs index 450d14de..eade907c 100644 --- a/AudioCuesheetEditor/Model/Utility/IOUtility.cs +++ b/AudioCuesheetEditor/Model/Utility/IOUtility.cs @@ -27,13 +27,13 @@ public static Boolean CheckFileMimeType(IFileEntry file, String mimeType, String { if (String.IsNullOrEmpty(file.Type) == false) { - fileMimeTypeMatches = file.Type.ToLower() == mimeType.ToLower(); + fileMimeTypeMatches = file.Type.Equals(mimeType, StringComparison.CurrentCultureIgnoreCase); } else { //Try to find by file extension - var extension = Path.GetExtension(file.Name).ToLower(); - fileMimeTypeMatches = extension == fileExtension.ToLower(); + var extension = Path.GetExtension(file.Name); + fileMimeTypeMatches = extension.Equals(fileExtension, StringComparison.CurrentCultureIgnoreCase); } } return fileMimeTypeMatches; @@ -47,7 +47,7 @@ public static Boolean CheckFileMimeTypeForAudioCodec(IFileEntry file) public static AudioCodec? GetAudioCodec(IFileEntry fileEntry) { AudioCodec? foundAudioCodec = null; - var extension = Path.GetExtension(fileEntry.Name).ToLower(); + var extension = Path.GetExtension(fileEntry.Name); // 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) diff --git a/AudioCuesheetEditor/Model/Utility/TimeSpanUtility.cs b/AudioCuesheetEditor/Model/Utility/TimeSpanUtility.cs new file mode 100644 index 00000000..7ca259e8 --- /dev/null +++ b/AudioCuesheetEditor/Model/Utility/TimeSpanUtility.cs @@ -0,0 +1,40 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +namespace AudioCuesheetEditor.Model.Utility +{ + public class TimeSpanUtility + { + public static TimeSpan? ParseTimeSpan(String input, TimeSpanFormat? timeSpanFormat = null) + { + TimeSpan? result = null; + if (String.IsNullOrEmpty(input) == false) + { + if (String.IsNullOrEmpty(timeSpanFormat?.Scheme)) + { + if (TimeSpan.TryParse(input, out var parsed)) + { + result = parsed; + } + } + else + { + result = timeSpanFormat.ParseTimeSpan(input); + } + } + return result; + } + } +} diff --git a/AudioCuesheetEditor/Pages/EditSections.razor b/AudioCuesheetEditor/Pages/EditSections.razor index 4f9c19ea..ebeb8d8a 100644 --- a/AudioCuesheetEditor/Pages/EditSections.razor +++ b/AudioCuesheetEditor/Pages/EditSections.razor @@ -20,7 +20,7 @@ along with Foobar. If not, see @inject ITextLocalizer _localizer @inject SessionStateContainer _sessionStateContainer @inject ITextLocalizer _validationMessageLocalizer -@inject DateTimeUtility _dateTimeUtility +@inject ApplicationOptionsTimeSpanParser _applicationOptionsTimeSpanParser @inject ITextLocalizerService _localizationService @inject TraceChangeManager _traceChangeManager @inject IJSRuntime _jsRuntime @@ -77,7 +77,7 @@ along with Foobar. If not, see - + @@ -86,7 +86,7 @@ along with Foobar. If not, see - + diff --git a/AudioCuesheetEditor/Pages/ImportFileView.razor b/AudioCuesheetEditor/Pages/ImportFileView.razor index f88c801c..f586cccc 100644 --- a/AudioCuesheetEditor/Pages/ImportFileView.razor +++ b/AudioCuesheetEditor/Pages/ImportFileView.razor @@ -78,7 +78,7 @@ along with Foobar. If not, see if (newTabName == "editFilecontent") { //Set fileContent just when component is visible in order to autosize the MemoEdit - if (_sessionStateContainer.Importfile != null) + if (_sessionStateContainer.Importfile?.FileContent != null) { fileContent = String.Join(Environment.NewLine, _sessionStateContainer.Importfile.FileContent); } diff --git a/AudioCuesheetEditor/Pages/Index.razor b/AudioCuesheetEditor/Pages/Index.razor index 7eb0dbd9..b9d76fc5 100644 --- a/AudioCuesheetEditor/Pages/Index.razor +++ b/AudioCuesheetEditor/Pages/Index.razor @@ -15,14 +15,13 @@ You should have received a copy of the GNU General Public License along with Foobar. If not, see . --> - -@implements IDisposable +@implements IAsyncDisposable @page "/" @inject IJSRuntime _jsRuntime @inject ITextLocalizer _localizer @inject NavigationManager _navigationManager -@inject LocalStorageOptionsProvider _localStorageOptionsProvider +@inject ILocalStorageOptionsProvider _localStorageOptionsProvider @inject ILogger _logger @inject HotKeys _hotKeys @inject HttpClient _httpClient @@ -78,12 +77,15 @@ along with Foobar. If not, see @code { - - public void Dispose() + + public async ValueTask DisposeAsync() { - hotKeysContext?.Dispose(); _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; _sessionStateContainer.CurrentViewModeChanged -= CurrentViewModeChanged; + if (hotKeysContext != null) + { + await hotKeysContext.DisposeAsync(); + } } [CascadingParameter] diff --git a/AudioCuesheetEditor/Pages/RecordControl.razor b/AudioCuesheetEditor/Pages/RecordControl.razor index 3fded530..a167e0fd 100644 --- a/AudioCuesheetEditor/Pages/RecordControl.razor +++ b/AudioCuesheetEditor/Pages/RecordControl.razor @@ -15,12 +15,12 @@ You should have received a copy of the GNU General Public License along with Foobar. If not, see . --> -@implements IDisposable +@implements IAsyncDisposable @inject SessionStateContainer _sessionStateContainer @inject ITextLocalizer _localizer @inject ITextLocalizerService _localizationService -@inject LocalStorageOptionsProvider _localStorageOptionsProvider +@inject ILocalStorageOptionsProvider _localStorageOptionsProvider @inject HotKeys _hotKeys @if (_sessionStateContainer.Cuesheet.IsRecording == true) @@ -131,12 +131,15 @@ along with Foobar. If not, see [Parameter] public EventCallback StopRecordClicked { get; set; } - public void Dispose() + public async ValueTask DisposeAsync() { - hotKeysContext?.Dispose(); _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; _sessionStateContainer.CuesheetChanged -= SessionStateContainer_CuesheetChanged; _localStorageOptionsProvider.OptionSaved -= LocalStorageOptionsProvider_OptionsSaved; + if (hotKeysContext != null) + { + await hotKeysContext.DisposeAsync(); + } } protected override async Task OnInitializedAsync() diff --git a/AudioCuesheetEditor/Pages/ViewModeImport.razor b/AudioCuesheetEditor/Pages/ViewModeImport.razor index 758628e8..05cf22ba 100644 --- a/AudioCuesheetEditor/Pages/ViewModeImport.razor +++ b/AudioCuesheetEditor/Pages/ViewModeImport.razor @@ -20,18 +20,17 @@ along with Foobar. If not, see @inject ITextLocalizer _localizer @inject SessionStateContainer _sessionStateContainer -@inject LocalStorageOptionsProvider _localStorageOptionsProvider -@inject ILogger _logger +@inject ILocalStorageOptionsProvider _localStorageOptionsProvider @inject ITextLocalizerService _localizationService @inject HotKeys _hotKeys @inject IJSRuntime _jsRuntime @inject HttpClient _httpClient -@inject ITextLocalizer _validationMessageLocalizer +@inject ImportManager _importManager - + - @_localizer["Select files"] + @_localizer["Select files"] @_localizer["Validate"] @@ -42,7 +41,7 @@ along with Foobar. If not, see - @((MarkupString)(_localizer["Choose file or drag it here"].ToString())) + @((MarkupString)(_localizer["Choose file or drag it here"])) @foreach (var invalidFileName in invalidDropFileNames) { @@ -52,55 +51,6 @@ along with Foobar. If not, see } - - - - @_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) - - - } @@ -119,132 +69,36 @@ along with Foobar. If not, see - - - - - - - - - @if (_sessionStateContainer.TextImportFile != null) + @if (displayFileContent) + { + + + + + + + + + } + @if (displayEditImportOptions) { - - - - - @_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) + @if (_sessionStateContainer.Importfile?.AnalyseException != null) { @@ -253,7 +107,7 @@ along with Foobar. If not, see - @_localizer["Error during textimport"] : @_sessionStateContainer.TextImportFile.AnalyseException.Message + @_localizer["Error during textimport"] : @_sessionStateContainer.Importfile.AnalyseException.Message } } @@ -296,20 +150,17 @@ along with Foobar. If not, see @code { String selectedStep = "selectFiles"; - String dragNDropUploadFilter = String.Join(',', TextImportfile.MimeType, Cuesheet.FileExtension, Projectfile.FileExtension); + String dragNDropUploadFilter = String.Join(',', FileMimeTypes.Text, FileExtensions.Cuesheet, FileExtensions.Projectfile); Boolean cuesheetDataVisible = true; Boolean cuesheetTracksVisible = true; Boolean cuesheetSplitPointsVisible = true; Boolean importFileContentVisible = true; Boolean importOptionsVisible = true; - Boolean selectFilesCompleted = false; - Boolean userChangedSelectedStep = false; Boolean displaySplitPoints = false; + Boolean displayFileContent = true; + Boolean displayEditImportOptions = true; Alert? alertInvalidFile; ModalDialog? modalDialog; - List invalidTextImportFileNames = new(); - List invalidCuesheetfileNames = new(); - List invalidProjectfileNames = new(); List invalidDropFileNames = new(); HotKeysContext? hotKeysContext; @@ -317,121 +168,32 @@ along with Foobar. If not, see public void Dispose() { - hotKeysContext?.Dispose(); + hotKeysContext?.DisposeAsync(); _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; _sessionStateContainer.ImportCuesheetChanged -= SessionStateContainer_ImportCuesheetChanged; } - protected override Task OnInitializedAsync() + protected override void OnInitialized() { - _logger.LogDebug("OnInitializedAsync"); + base.OnInitialized(); _localizationService.LocalizationChanged += LocalizationService_LocalizationChanged; _sessionStateContainer.ImportCuesheetChanged += SessionStateContainer_ImportCuesheetChanged; hotKeysContext = _hotKeys.CreateContext() .Add(Key.Enter, OnEnterKeyDown); - - TimeSpanFormat.TextLocalizer = _localizer; - TextImportScheme.TextLocalizer = _localizer; - - return Task.CompletedTask; } - async Task OnSelectedStepChanged(String name) - { - userChangedSelectedStep = true; - switch (name) - { - case "selectFiles": - selectedStep = name; - break; - case "validateData": - if ((_sessionStateContainer.ImportCuesheet != null) || (_sessionStateContainer.TextImportFile != null)) - { - selectFilesCompleted = true; - selectedStep = name; - } - else - { - if (modalDialog != null) - { - modalDialog.Title = _localizer["Not possible!"]; - modalDialog.Text = _localizer["Please select files for import before going to validation!"]; - modalDialog.ModalSize = ModalSize.Small; - modalDialog.Mode = ModalDialog.DialogMode.Alert; - await modalDialog.ShowModal(); - } - } - break; - } - } + Boolean SelectFilesCompleted => (_sessionStateContainer.ImportCuesheet != null) || (_sessionStateContainer.Importfile != null); - String SelectedStep + bool NavigationAllowed(StepNavigationContext context) { - get + if (context.CurrentStepName == "selectFiles" && context.NextStepName == "validateData") { - if ((userChangedSelectedStep == false) && ((_sessionStateContainer.ImportCuesheet != null) || (_sessionStateContainer.TextImportFile != null))) - { - selectFilesCompleted = true; - selectedStep = "validateData"; - } - return selectedStep; + return SelectFilesCompleted; } - } - private async Task OnTextImportFileChanged(FileChangedEventArgs e) - { - invalidTextImportFileNames.Clear(); - if (e.Files.FirstOrDefault() != null) - { - var file = e.Files.First(); - if (IOUtility.CheckFileMimeType(file, TextImportfile.MimeType, TextImportfile.FileExtension) == false) - { - invalidTextImportFileNames.Add(file.Name); - } - else - { - await OnFileChanged(new List() { file }); - } - StateHasChanged(); - } - } - - private async Task OnCuesheetfileChanged(FileChangedEventArgs e) - { - invalidCuesheetfileNames.Clear(); - if (e.Files.FirstOrDefault() != null) - { - var file = e.Files.First(); - if (IOUtility.CheckFileMimeType(file, Cuesheet.MimeType, Cuesheet.FileExtension) == false) - { - invalidCuesheetfileNames.Add(file.Name); - } - else - { - await OnFileChanged(new List() { file }); - } - StateHasChanged(); - } - } - - private async Task OnProjectfileChanged(FileChangedEventArgs e) - { - invalidProjectfileNames.Clear(); - if (e.Files.FirstOrDefault() != null) - { - var file = e.Files.First(); - if (IOUtility.CheckFileMimeType(file, Projectfile.MimeType, Projectfile.FileExtension) == false) - { - invalidProjectfileNames.Add(file.Name); - } - else - { - await OnFileChanged(new List() { file }); - } - StateHasChanged(); - } + return true; } private async Task OnDropFileChanged(FileChangedEventArgs e) @@ -439,9 +201,9 @@ along with Foobar. If not, see invalidDropFileNames.Clear(); foreach (var file in e.Files) { - 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) + if ((IOUtility.CheckFileMimeType(file, FileMimeTypes.Projectfile, FileExtensions.Projectfile) == false) + && (IOUtility.CheckFileMimeType(file, FileMimeTypes.Cuesheet, FileExtensions.Cuesheet) == false) + && (IOUtility.CheckFileMimeType(file, FileMimeTypes.Text, FileExtensions.Text) == false) && (IOUtility.CheckFileMimeTypeForAudioCodec(file) == false)) { invalidDropFileNames.Add(file.Name); @@ -457,58 +219,24 @@ along with Foobar. If not, see private async Task OnFileChanged(IReadOnlyCollection files) { _sessionStateContainer.ResetImport(); - displaySplitPoints = false; + var importedFiles = await _importManager.ImportFilesAsync(files); + // Audio file is handled seperatly foreach (var file in files) { - if (IOUtility.CheckFileMimeType(file, Projectfile.MimeType, Projectfile.FileExtension)) - { - //We have a valid file here - var fileContent = new MemoryStream(); - var stream = file.OpenReadStream(); - await stream.CopyToAsync(fileContent); - stream.Close(); - var cuesheet = Projectfile.ImportFile(fileContent.ToArray()); - if (cuesheet != null) - { - _sessionStateContainer.ImportCuesheet = cuesheet; - } - displaySplitPoints = true; - } - if (IOUtility.CheckFileMimeType(file, Cuesheet.MimeType, Cuesheet.FileExtension) == true) - { - var options = await _localStorageOptionsProvider.GetOptions(); - var stream = file.OpenReadStream(); - MemoryStream memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream); - stream.Close(); - _sessionStateContainer.CuesheetImportFile = new CuesheetImportfile(memoryStream, options); - } - 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 (IOUtility.CheckFileMimeTypeForAudioCodec(file) == true) + if (IOUtility.CheckFileMimeTypeForAudioCodec(file)) { var audioFileObjectURL = await _jsRuntime.InvokeAsync("getObjectURL", "dropFileInput"); var codec = IOUtility.GetAudioCodec(file); var audiofile = new Audiofile(file.Name, audioFileObjectURL, codec, _httpClient); _ = audiofile.LoadContentStream(); _sessionStateContainer.ImportAudiofile = audiofile; + importedFiles.Add(file, ImportFileType.Audiofile); } } - await OnSelectedStepChanged("validateData"); + displaySplitPoints = importedFiles.ContainsValue(ImportFileType.ProjectFile); + displayFileContent = importedFiles.ContainsValue(ImportFileType.Textfile); + displayEditImportOptions = importedFiles.ContainsValue(ImportFileType.Textfile); + selectedStep = "validateData"; StateHasChanged(); } @@ -534,29 +262,20 @@ along with Foobar. If not, see private async Task ImportData() { - var options = await _localStorageOptionsProvider.GetOptions(); - if (_sessionStateContainer.TextImportFile != null) - { - //Save import options (values are inside the TextImportFile) - var importOptions = new ImportOptions(_sessionStateContainer.TextImportFile); - await _localStorageOptionsProvider.SaveOptions(importOptions); - } - _sessionStateContainer.StartImportCuesheet(options); + await _importManager.ImportCuesheetAsync(); _sessionStateContainer.CurrentViewMode = ViewMode.ViewModeFull; StateHasChanged(); } - async Task AbortImport() + void AbortImport() { _sessionStateContainer.ResetImport(); - await OnSelectedStepChanged("selectFiles"); + selectedStep = "selectFiles"; } private void LocalizationService_LocalizationChanged(object? sender, EventArgs args) { StateHasChanged(); - TimeSpanFormat.TextLocalizer = _localizer; - TextImportScheme.TextLocalizer = _localizer; validations?.ValidateAll(); } @@ -591,36 +310,17 @@ along with Foobar. If not, see { var newOptions = new ImportOptions(); await _localStorageOptionsProvider.SaveOptions(newOptions); - await OnReloadImportOptionsClicked(); } - async Task OnReloadImportOptionsClicked() + void TextImportScheme_ValidateablePropertyChanged(object? sender, String property) { - var options = await _localStorageOptionsProvider.GetOptions(); - if (_sessionStateContainer.TextImportFile != null) + if (validations != null) { - _sessionStateContainer.TextImportFile.TimeSpanFormat.ValidateablePropertyChanged -= Timespanformat_ValidateablePropertyChanged; - _sessionStateContainer.TextImportFile.TextImportScheme.ValidateablePropertyChanged -= TextImportScheme_ValidateablePropertyChanged; - if (options.TimeSpanFormat != null) - { - _sessionStateContainer.TextImportFile.TimeSpanFormat = options.TimeSpanFormat; - } - else - { - _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(); + validations.ValidateAll().GetAwaiter().GetResult(); } } - void TextImportScheme_ValidateablePropertyChanged(object? sender, String property) + void Timespanformat_ValidateablePropertyChanged(object? sender, String property) { if (validations != null) { @@ -628,11 +328,11 @@ along with Foobar. If not, see } } - void Timespanformat_ValidateablePropertyChanged(object? sender, String property) + async Task EditImportOptions_OptionsChanged(ImportOptions importOptions) { - if (validations != null) + if ((_sessionStateContainer.Importfile?.FileType == ImportFileType.Textfile) && (_sessionStateContainer.Importfile?.FileContent != null)) { - validations.ValidateAll().GetAwaiter().GetResult(); + await _importManager.ImportTextAsync(_sessionStateContainer.Importfile.FileContent); } } } diff --git a/AudioCuesheetEditor/Program.cs b/AudioCuesheetEditor/Program.cs index 287ee61f..ca84b218 100644 --- a/AudioCuesheetEditor/Program.cs +++ b/AudioCuesheetEditor/Program.cs @@ -19,6 +19,8 @@ using AudioCuesheetEditor.Extensions; using AudioCuesheetEditor.Model.UI; using AudioCuesheetEditor.Model.Utility; +using AudioCuesheetEditor.Services.IO; +using AudioCuesheetEditor.Services.UI; using BlazorDownloadFile; using Blazorise; using Blazorise.Bootstrap5; @@ -47,12 +49,15 @@ builder.Services.AddBlazorDownloadFile(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddLogging(); builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); diff --git a/AudioCuesheetEditor/Resources/Localization/EditImportOptions/de.json b/AudioCuesheetEditor/Resources/Localization/EditImportOptions/de.json new file mode 100644 index 00000000..1d41f3e2 --- /dev/null +++ b/AudioCuesheetEditor/Resources/Localization/EditImportOptions/de.json @@ -0,0 +1,33 @@ +{ + "culture": "de", + "translations": { + "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 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", + "Textimportscheme cuesheet": "Import Schema Cuesheet", + "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.", + "Customized timespan format import": "Zeitspannenformat für Import", + "CD artist": "CD Künstler", + "CD title": "CD Titel", + "Audiofile": "Audiodatei", + "Change": "Ändern", + "Cataloguenumber": "Katalognummer", + "CD textfile": "CD Textdatei", + "Artist": "Künstler", + "Title": "Titel", + "Begin": "Beginn", + "End": "Ende", + "Length": "Länge", + "Days": "Tage", + "Hours": "Stunden", + "Minutes": "Minuten", + "Seconds": "Sekunden", + "Milliseconds": "Millisekunden", + "StartDateTime": "Startzeitpunkt" + } +} \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/EditImportOptions/en.json b/AudioCuesheetEditor/Resources/Localization/EditImportOptions/en.json new file mode 100644 index 00000000..b453cd98 --- /dev/null +++ b/AudioCuesheetEditor/Resources/Localization/EditImportOptions/en.json @@ -0,0 +1,32 @@ +{ + "culture": "en", + "translations": { + "Enter textimportscheme cuesheet tooltip": "Enter textscheme for cuesheet properties import here. Identification will be done using regular expressions.", + "Enter textimportscheme cuesheet here": "Enter textscheme for cuesheet properties import here", + "Select placeholder": "Select placeholder", + "Enter textimportscheme track tooltip": "Enter textscheme for track properties import here. Identification will be done using regular expressions.", + "Enter textimportscheme track here": "Enter textscheme for track properties import here", + "Textimportscheme cuesheet": "Textimport scheme cuesheet", + "Textimportscheme track": "Textimport scheme track", + "Import Options": "Import options", + "ENTER REGULAR EXPRESSION HERE": "Enter regular expression here", + "Enter custom timespan format here": "You can enter a custom format for timespan here. Details can be found in help.", + "Customized timespan format import": "Timespan format for import", + "CD artist": "CD artist", + "CD title": "CD title", + "Audiofile": "Audiofile", + "CD textfile": "CD Textfile", + "Cataloguenumber": "Cataloguenumber", + "Artist": "Artist", + "Title": "Title", + "Begin": "Begin", + "End": "End", + "Length": "Length", + "Days": "Days", + "Hours": "Hours", + "Minutes": "Minutes", + "Seconds": "Seconds", + "Milliseconds": "Milliseconds", + "StartDateTime": "Startdatetime" + } +} \ No newline at end of file diff --git a/AudioCuesheetEditor/Resources/Localization/ViewModeImport/de.json b/AudioCuesheetEditor/Resources/Localization/ViewModeImport/de.json index 4d8539a4..6fadba0f 100644 --- a/AudioCuesheetEditor/Resources/Localization/ViewModeImport/de.json +++ b/AudioCuesheetEditor/Resources/Localization/ViewModeImport/de.json @@ -7,12 +7,6 @@ "Choose file or drag it here": "Wählen Sie die Dateien oder ziehen Sie diese hier her", "Invalid file": "Ungültige Datei", "You dropped an invalid file ({0}) that can not be processed.": "Sie haben eine ungültige Datei ({0}) selektiert, die nicht verarbeitet werden kann.", - "Import textfile tooltip": "Textdatei importieren und Daten über reguläre Ausdrücke extrahieren", - "Textfile": "Textdatei", - "Import cuesheet tooltip": "Importieren Sie ein Cuesheet und extrahieren Sie alle Daten daraus", - "Cuesheet": "Cuesheet", - "Import project tooltip": "Ein gespeichertes Projekt importieren", - "Project filename": "Projektdatei", "Validate data for import": "Daten für den Import validieren", "Recognition of import data finished": "Analyse der Importdateien abgeschlossen", "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.", @@ -42,8 +36,6 @@ "Begin": "Beginn", "End": "Ende", "Length": "Länge", - "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 Titel", "Import Options": "Importoptionen", diff --git a/AudioCuesheetEditor/Resources/Localization/ViewModeImport/en.json b/AudioCuesheetEditor/Resources/Localization/ViewModeImport/en.json index 960cb5a1..be324ec3 100644 --- a/AudioCuesheetEditor/Resources/Localization/ViewModeImport/en.json +++ b/AudioCuesheetEditor/Resources/Localization/ViewModeImport/en.json @@ -7,12 +7,6 @@ "Choose file or drag it here": "Choose files or drag them here", "Invalid file": "Invalid file", "You dropped an invalid file ({0}) that can not be processed.": "You dropped an invalid file ({0}) that can not be processed.", - "Import textfile tooltip": "Import a plain text file and extract data via regular expressions", - "Textfile": "Textfile", - "Import cuesheet tooltip": "Import a cuesheet file and extract all data from it", - "Cuesheet": "Cuesheet", - "Import project tooltip": "Import a saved project", - "Project filename": "Project filename", "Validate data for import": "Validate data for import", "Recognition of import data finished": "Recognition of import data finished", "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.", @@ -41,8 +35,6 @@ "Begin": "Begin", "End": "End", "Length": "Length", - "Not possible!": "Not possible!", - "Please select files for import before going to validation!": "Please select files for import before going to validation!", "Textimportscheme cuesheet": "Textimport scheme cuesheet", "Textimportscheme track": "Textimport scheme track", "Import Options": "Import options", diff --git a/AudioCuesheetEditor/Model/IO/Import/CuesheetImportfile.cs b/AudioCuesheetEditor/Services/IO/CuesheetImportService.cs similarity index 87% rename from AudioCuesheetEditor/Model/IO/Import/CuesheetImportfile.cs rename to AudioCuesheetEditor/Services/IO/CuesheetImportService.cs index c5656c54..648109dc 100644 --- a/AudioCuesheetEditor/Model/IO/Import/CuesheetImportfile.cs +++ b/AudioCuesheetEditor/Services/IO/CuesheetImportService.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,55 +14,23 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.AudioCuesheet; -using AudioCuesheetEditor.Model.IO.Audio; -using AudioCuesheetEditor.Model.Options; +using AudioCuesheetEditor.Model.AudioCuesheet.Import; +using AudioCuesheetEditor.Model.IO.Import; using System.Text.RegularExpressions; -namespace AudioCuesheetEditor.Model.IO.Import +namespace AudioCuesheetEditor.Services.IO { - public class CuesheetImportfile : IImportfile + public class CuesheetImportService { - private IEnumerable fileContent; - - public EventHandler? AnalysisFinished; - - /// - public IEnumerable FileContent - { - get => fileContent; - set - { - fileContent = value; - Analyse(); - } - } - - /// - public IEnumerable FileContentRecognized { get; private set; } - public Exception? AnalyseException { get; private set; } - public Cuesheet? Cuesheet { get; private set; } - public ApplicationOptions ApplicationOptions { get; private set; } - - public CuesheetImportfile(MemoryStream fileContentStream, ApplicationOptions applicationOptions) + public static IImportfile Analyse(IEnumerable fileContent) { - FileContentRecognized = []; - fileContentStream.Position = 0; - using var reader = new StreamReader(fileContentStream); - List lines = []; - while (reader.EndOfStream == false) + Importfile importfile = new() { - lines.Add(reader.ReadLine()); - } - fileContent = lines.AsReadOnly(); - ApplicationOptions = applicationOptions; - Analyse(); - } - - private void Analyse() - { + FileType = ImportFileType.Cuesheet + }; try { - Cuesheet = new Cuesheet(); + importfile.AnalysedCuesheet = new(); var cuesheetArtistGroupName = "CuesheetArtist"; var cuesheetTitleGroupName = "CuesheetTitle"; var cuesheetFileNameGroupName = "CuesheetFileName"; @@ -86,10 +54,10 @@ private void Analyse() var regexTrackPostGap = new Regex(CuesheetConstants.TrackPostGap + "(?'" + trackPostGapGroupName + "'.{0,})"); var regexCDTextfile = new Regex("^" + CuesheetConstants.CuesheetCDTextfile + " \"(?'" + cuesheetCDTextfileGroupName + "'.{0,})\""); var regexCatalogueNumber = new Regex("^" + CuesheetConstants.CuesheetCatalogueNumber + " (?'" + cuesheetCatalogueNumberGroupName + "'.{0,})"); - Track? track = null; + ImportTrack? track = null; List lines = []; List? recognizedLines = []; - foreach (var line in FileContent) + foreach (var line in fileContent) { lines.Add(line); String? recognizedLine = line; @@ -103,7 +71,7 @@ private void Analyse() if (matchGroup != null) { var artist = matchGroup.Value; - Cuesheet.Artist = artist; + importfile.AnalysedCuesheet.Artist = artist; } else { @@ -118,7 +86,7 @@ private void Analyse() if (matchGroup != null) { var title = matchGroup.Value; - Cuesheet.Title = title; + importfile.AnalysedCuesheet.Title = title; } else { @@ -133,7 +101,7 @@ private void Analyse() if (matchGroup != null) { var audioFile = matchGroup.Value; - Cuesheet.Audiofile = new Audiofile(audioFile); + importfile.AnalysedCuesheet.Audiofile = audioFile; } else { @@ -148,7 +116,7 @@ private void Analyse() if (matchGroup != null) { var cdTextfile = matchGroup.Value; - Cuesheet.CDTextfile = new CDTextfile(cdTextfile); + importfile.AnalysedCuesheet.CDTextfile = cdTextfile; } else { @@ -163,7 +131,7 @@ private void Analyse() if (matchGroup != null) { var catalogueNumber = matchGroup.Value; - Cuesheet.Cataloguenumber.Value = catalogueNumber; + importfile.AnalysedCuesheet.Cataloguenumber = catalogueNumber; } else { @@ -172,7 +140,7 @@ private void Analyse() } if (regexTrackBegin.IsMatch(line) == true) { - track = new Track(); + track = new ImportTrack(); recognizedLine = String.Format(CuesheetConstants.RecognizedMarkHTML, line); } if ((regexTrackArtist.IsMatch(line) == true) && (track != null)) @@ -277,7 +245,7 @@ private void Analyse() } if (track != null) { - Cuesheet.AddTrack(track, ApplicationOptions); + importfile.AnalysedCuesheet.Tracks.Add(track); } else { @@ -311,16 +279,16 @@ private void Analyse() } recognizedLines.Add(recognizedLine); } - fileContent = lines.AsReadOnly(); - FileContentRecognized = recognizedLines.AsReadOnly(); + importfile.FileContent = lines.AsReadOnly(); + importfile.FileContentRecognized = recognizedLines.AsReadOnly(); } catch (Exception ex) { - AnalyseException = ex; - Cuesheet = null; - FileContentRecognized = FileContent; + importfile.AnalyseException = ex; + importfile.AnalysedCuesheet = null; + importfile.FileContentRecognized = fileContent; } - AnalysisFinished?.Invoke(this, EventArgs.Empty); + return importfile; } } } diff --git a/AudioCuesheetEditor/Services/IO/ImportManager.cs b/AudioCuesheetEditor/Services/IO/ImportManager.cs new file mode 100644 index 00000000..8eca0d48 --- /dev/null +++ b/AudioCuesheetEditor/Services/IO/ImportManager.cs @@ -0,0 +1,204 @@ +//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.Data.Options; +using AudioCuesheetEditor.Extensions; +using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.AudioCuesheet.Import; +using AudioCuesheetEditor.Model.IO; +using AudioCuesheetEditor.Model.IO.Audio; +using AudioCuesheetEditor.Model.Options; +using AudioCuesheetEditor.Model.UI; +using AudioCuesheetEditor.Model.Utility; +using Blazorise; + +namespace AudioCuesheetEditor.Services.IO +{ + public enum ImportFileType + { + Unknown, + ProjectFile, + Cuesheet, + Textfile, + Audiofile + } + public class ImportManager(SessionStateContainer sessionStateContainer, ILocalStorageOptionsProvider localStorageOptionsProvider, TextImportService textImportService, TraceChangeManager traceChangeManager) + { + private readonly SessionStateContainer _sessionStateContainer = sessionStateContainer; + private readonly ILocalStorageOptionsProvider _localStorageOptionsProvider = localStorageOptionsProvider; + private readonly TextImportService _textImportService = textImportService; + private readonly TraceChangeManager _traceChangeManager = traceChangeManager; + + public async Task> ImportFilesAsync(IEnumerable files) + { + Dictionary importFileTypes = []; + foreach (var file in files) + { + if (IOUtility.CheckFileMimeType(file, FileMimeTypes.Projectfile, FileExtensions.Projectfile)) + { + var fileContent = await ReadFileContentAsync(file); + var cuesheet = Projectfile.ImportFile(fileContent.ToArray()); + if (cuesheet != null) + { + _sessionStateContainer.ImportCuesheet = cuesheet; + } + importFileTypes.Add(file, ImportFileType.ProjectFile); + } + if (IOUtility.CheckFileMimeType(file, FileMimeTypes.Cuesheet, FileExtensions.Cuesheet)) + { + var fileContent = await ReadFileContentAsync(file); + fileContent.Position = 0; + using var reader = new StreamReader(fileContent); + List lines = []; + while (reader.EndOfStream == false) + { + lines.Add(reader.ReadLine()); + } + await ImportCuesheetAsync(lines); + importFileTypes.Add(file, ImportFileType.Cuesheet); + } + if (IOUtility.CheckFileMimeType(file, FileMimeTypes.Text, FileExtensions.Text)) + { + var fileContent = await ReadFileContentAsync(file); + fileContent.Position = 0; + using var reader = new StreamReader(fileContent); + List lines = []; + while (reader.EndOfStream == false) + { + lines.Add(reader.ReadLine()); + } + await ImportTextAsync([.. lines]); + importFileTypes.Add(file, ImportFileType.Textfile); + } + } + return importFileTypes; + } + + public async Task ImportTextAsync(IEnumerable fileContent) + { + var options = await _localStorageOptionsProvider.GetOptions(); + _sessionStateContainer.Importfile = _textImportService.Analyse(options, fileContent); + if (_sessionStateContainer.Importfile.AnalysedCuesheet != null) + { + var importCuesheet = new Cuesheet(); + await CopyCuesheetAsync(importCuesheet, _sessionStateContainer.Importfile.AnalysedCuesheet); + _sessionStateContainer.ImportCuesheet = importCuesheet; + } + } + + public async Task ImportCuesheetAsync() + { + if (_sessionStateContainer.ImportCuesheet != null) + { + _traceChangeManager.BulkEdit = true; + await CopyCuesheetAsync(_sessionStateContainer.Cuesheet, _sessionStateContainer.ImportCuesheet); + _traceChangeManager.BulkEdit = false; + } + _sessionStateContainer.ResetImport(); + } + + private async Task ImportCuesheetAsync(IEnumerable fileContent) + { + _sessionStateContainer.Importfile = CuesheetImportService.Analyse(fileContent); + if (_sessionStateContainer.Importfile.AnalysedCuesheet != null) + { + var importCuesheet = new Cuesheet(); + await CopyCuesheetAsync(importCuesheet, _sessionStateContainer.Importfile.AnalysedCuesheet); + _sessionStateContainer.ImportCuesheet = importCuesheet; + } + } + + private static async Task ReadFileContentAsync(IFileEntry file) + { + var fileContent = new MemoryStream(); + var stream = file.OpenReadStream(); + await stream.CopyToAsync(fileContent); + stream.Close(); + return fileContent; + } + + private async Task CopyCuesheetAsync(Cuesheet target, ICuesheet cuesheetToCopy) + { + target.IsImporting = true; + target.Artist = cuesheetToCopy.Artist; + target.Title = cuesheetToCopy.Title; + IEnumerable? tracks = null; + if (cuesheetToCopy is Cuesheet originCuesheet) + { + tracks = originCuesheet.Tracks; + // Copy sections + foreach (var section in originCuesheet.Sections) + { + var newSplitPoint = target.AddSection(); + newSplitPoint.CopyValues(section); + } + target.Audiofile = originCuesheet.Audiofile; + target.CDTextfile = originCuesheet.CDTextfile; + target.Cataloguenumber = originCuesheet.Cataloguenumber; + } + if (cuesheetToCopy is ImportCuesheet importCuesheet) + { + tracks = importCuesheet.Tracks; + if (String.IsNullOrEmpty(importCuesheet.Audiofile) == false) + { + target.Audiofile = new Audiofile(importCuesheet.Audiofile); + } + if (String.IsNullOrEmpty(importCuesheet.CDTextfile) == false) + { + target.CDTextfile = new CDTextfile(importCuesheet.CDTextfile); + } + target.Cataloguenumber = new Cataloguenumber() + { + Value = importCuesheet.Cataloguenumber + }; + } + if (tracks != null) + { + var applicationOptions = await _localStorageOptionsProvider.GetOptions(); + var begin = TimeSpan.Zero; + for (int i = 0; i < tracks.Count(); i++) + { + var importTrack = tracks.ElementAt(i); + //We don't want to copy the cuesheet reference since we are doing a copy and want to assign the track to this object + var track = new Track(importTrack, false); + if (importTrack is ImportTrack importTrackReference) + { + if (importTrackReference.StartDateTime != null) + { + if (i < tracks.Count() - 1) + { + var nextTrack = (ImportTrack)tracks.ElementAt(i + 1); + var length = nextTrack.StartDateTime - importTrackReference.StartDateTime; + track.Begin = begin; + track.End = begin + length; + if (track.End.HasValue) + { + begin = track.End.Value; + } + } + } + } + target.AddTrack(track, applicationOptions); + } + } + else + { + throw new NullReferenceException(); + } + target.IsImporting = false; + _sessionStateContainer.FireCuesheetImported(); + } + } +} diff --git a/AudioCuesheetEditor/Services/IO/TextImportService.cs b/AudioCuesheetEditor/Services/IO/TextImportService.cs new file mode 100644 index 00000000..c90ce366 --- /dev/null +++ b/AudioCuesheetEditor/Services/IO/TextImportService.cs @@ -0,0 +1,168 @@ +//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.AudioCuesheet.Import; +using AudioCuesheetEditor.Model.IO.Audio; +using AudioCuesheetEditor.Model.IO.Import; +using AudioCuesheetEditor.Model.Options; +using AudioCuesheetEditor.Model.Utility; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace AudioCuesheetEditor.Services.IO +{ + public class TextImportService + { + public ImportOptions? ImportOptions { get; private set; } + public IImportfile Analyse(ImportOptions importOptions, IEnumerable fileContent) + { + Importfile importfile = new() + { + FileType = ImportFileType.Textfile + }; + try + { + importfile.FileContent = fileContent; + ImportOptions = importOptions; + importfile.AnalysedCuesheet = new ImportCuesheet(); + Boolean cuesheetRecognized = false; + List recognizedFileContent = []; + foreach (var line in fileContent) + { + var recognizedLine = line; + if (String.IsNullOrEmpty(line) == false) + { + Boolean recognized = false; + if ((recognized == false) && (cuesheetRecognized == false) && (String.IsNullOrEmpty(ImportOptions.TextImportScheme.SchemeCuesheet) == false)) + { + //Remove entity names + var expression = ImportOptions.TextImportScheme.SchemeCuesheet.Replace(String.Format("{0}.", nameof(Cuesheet)), String.Empty).Replace(String.Format("{0}.", nameof(Track)), String.Empty); + var regExCuesheet = new Regex(expression); + recognizedLine = AnalyseLine(line, importfile.AnalysedCuesheet, regExCuesheet); + recognized = recognizedLine != null; + cuesheetRecognized = recognizedLine != null; + } + if ((recognized == false) && (String.IsNullOrEmpty(ImportOptions.TextImportScheme.SchemeTracks) == false)) + { + //Remove entity names + var expression = ImportOptions.TextImportScheme.SchemeTracks.Replace(String.Format("{0}.", nameof(Cuesheet)), String.Empty).Replace(String.Format("{0}.", nameof(Track)), String.Empty); + var regExTracks = new Regex(expression); + var track = new ImportTrack(); + recognizedLine = AnalyseLine(line, track, regExTracks); + recognized = recognizedLine != null; + importfile.AnalysedCuesheet.Tracks.Add(track); + } + } + recognizedFileContent.Add(recognizedLine); + } + if (recognizedFileContent.Count > 0) + { + importfile.FileContentRecognized = recognizedFileContent.AsReadOnly(); + } + } + catch (Exception ex) + { + importfile.FileContentRecognized = fileContent; + importfile.AnalyseException = ex; + importfile.AnalysedCuesheet = null; + } + return importfile; + } + + private String? AnalyseLine(String line, object entity, Regex regex) + { + String? recognized = null; + string? recognizedLine = line; + if (String.IsNullOrEmpty(line) == false) + { + var match = regex.Match(line); + if (match.Success) + { + for (int groupCounter = 1; groupCounter < match.Groups.Count; groupCounter++) + { + var key = match.Groups.Keys.ElementAt(groupCounter); + var group = match.Groups.GetValueOrDefault(key); + if ((group != null) && (group.Success)) + { + var property = entity.GetType().GetProperty(key, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (property != null) + { + SetValue(entity, property, group.Value); + recognizedLine = string.Concat(recognizedLine.AsSpan(0, group.Index + (13 * (groupCounter - 1))) + , String.Format(CuesheetConstants.RecognizedMarkHTML, group.Value) + , recognizedLine.AsSpan(group.Index + (13 * (groupCounter - 1)) + group.Length)); + } + else + { + throw new NullReferenceException(String.Format("Property '{0}' was not found for line content {1}", key, line)); + } + } + else + { + throw new NullReferenceException(String.Format("Group '{0}' could not be found!", key)); + } + } + if (recognizedLine.Contains(CuesheetConstants.RecognizedMarkHTML.Substring(0, CuesheetConstants.RecognizedMarkHTML.IndexOf("{0}")))) + { + recognized = recognizedLine; + } + } + else + { + recognized = line; + } + } + return recognized; + } + + private void SetValue(object entity, PropertyInfo property, string value) + { + if (property.PropertyType == typeof(TimeSpan?)) + { + property.SetValue(entity, TimeSpanUtility.ParseTimeSpan(value, ImportOptions?.TimeSpanFormat)); + } + if (property.PropertyType == typeof(uint?)) + { + property.SetValue(entity, Convert.ToUInt32(value)); + } + if (property.PropertyType == typeof(String)) + { + property.SetValue(entity, value); + } + if (property.PropertyType == typeof(IReadOnlyCollection)) + { + var list = Flag.AvailableFlags.Where(x => value.Contains(x.CuesheetLabel)); + ((ITrack)entity).SetFlags(list); + } + if (property.PropertyType == typeof(Audiofile)) + { + property.SetValue(entity, new Audiofile(value)); + } + if (property.PropertyType == typeof(Cataloguenumber)) + { + ((Cuesheet)entity).Cataloguenumber.Value = value; + } + if (property.PropertyType == typeof(DateTime?)) + { + if (DateTime.TryParse(value, out var date)) + { + property.SetValue(entity, date); + } + } + } + } +} diff --git a/AudioCuesheetEditor/Model/Utility/DateTimeUtility.cs b/AudioCuesheetEditor/Services/UI/ApplicationOptionsTimeSpanParser.cs similarity index 55% rename from AudioCuesheetEditor/Model/Utility/DateTimeUtility.cs rename to AudioCuesheetEditor/Services/UI/ApplicationOptionsTimeSpanParser.cs index 463c5ac0..f6b16cd9 100644 --- a/AudioCuesheetEditor/Model/Utility/DateTimeUtility.cs +++ b/AudioCuesheetEditor/Services/UI/ApplicationOptionsTimeSpanParser.cs @@ -15,58 +15,26 @@ //. using AudioCuesheetEditor.Data.Options; using AudioCuesheetEditor.Model.Options; +using AudioCuesheetEditor.Model.Utility; using System.Linq.Expressions; using System.Reflection; -namespace AudioCuesheetEditor.Model.Utility +namespace AudioCuesheetEditor.Services.UI { - public class DateTimeUtility : IDisposable + public class ApplicationOptionsTimeSpanParser { - private readonly LocalStorageOptionsProvider? _localStorageOptionsProvider; - private readonly TimeSpanFormat? _timeFormat; + private readonly ILocalStorageOptionsProvider _localStorageOptionsProvider; private ApplicationOptions? applicationOptions; private bool disposedValue; - public DateTimeUtility(LocalStorageOptionsProvider localStorageOptionsProvider) + public ApplicationOptionsTimeSpanParser(ILocalStorageOptionsProvider 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?.Scheme == null) - { - if (TimeSpan.TryParse(input, out var parsed)) - { - result = parsed; - } - } - else - { - result = TimeSpanFormat.ParseTimeSpan(input); - } - } - return result; - } - public async Task TimespanTextChanged(T entity, Expression> expression, String value) { if (expression.Body is not MemberExpression memberExpression) @@ -77,7 +45,7 @@ public async Task TimespanTextChanged(T entity, Expression(T entity, Expression(); - } + applicationOptions ??= await _localStorageOptionsProvider.GetOptions(); } private void LocalStorageOptionsProvider_OptionSaved(object? sender, IOptions options) @@ -119,23 +82,5 @@ private void LocalStorageOptionsProvider_OptionSaved(object? sender, IOptions op 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/Shared/AudioPlayer.razor b/AudioCuesheetEditor/Shared/AudioPlayer.razor index 5d0ca5eb..ee987d0e 100644 --- a/AudioCuesheetEditor/Shared/AudioPlayer.razor +++ b/AudioCuesheetEditor/Shared/AudioPlayer.razor @@ -15,8 +15,7 @@ You should have received a copy of the GNU General Public License along with Foobar. If not, see . --> - -@implements IDisposable +@implements IAsyncDisposable @inject ITextLocalizer _localizer @inject IHowl _howl @@ -77,7 +76,7 @@ along with Foobar. If not, see @code { - Timer audioUpdateTimer = default!; + Timer? audioUpdateTimer; int soundId; Track? currentlyPlayingTrack; HotKeysContext? hotKeysContext; @@ -92,20 +91,23 @@ along with Foobar. If not, see public TimeSpan? TotalTime { get; private set; } public Boolean AudioIsPlaying { get; private set; } - public void Dispose() + public async ValueTask DisposeAsync() { - hotKeysContext?.Dispose(); _howl.OnPlay -= HowlOnPlay; _howl.OnPause -= HowlOnPause; _howl.OnEnd -= HowlOnEnd; _howl.OnStop -= HowlOnStop; - audioUpdateTimer.Dispose(); + _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; + _sessionStateContainer.CuesheetChanged -= SessionStateContainer_CuesheetChanged; if (_sessionStateContainer.Cuesheet != null) { _sessionStateContainer.Cuesheet.AudioFileChanged -= Cuesheet_AudioFileChanged; } - _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; - _sessionStateContainer.CuesheetChanged -= SessionStateContainer_CuesheetChanged; + audioUpdateTimer?.Dispose(); + if (hotKeysContext != null) + { + await hotKeysContext.DisposeAsync(); + } } public Boolean PlaybackPossible @@ -242,7 +244,7 @@ along with Foobar. If not, see void HowlOnPlay(Howler.Blazor.Components.Events.HowlPlayEventArgs args) { paused = false; - audioUpdateTimer.Start(); + audioUpdateTimer?.Start(); } void HowlOnPause(Howler.Blazor.Components.Events.HowlEventArgs args) @@ -253,7 +255,7 @@ along with Foobar. If not, see void HowlOnEnd(Howler.Blazor.Components.Events.HowlEventArgs args) { paused = false; - audioUpdateTimer.Stop(); + audioUpdateTimer?.Stop(); CurrentlyPlayingTrack = null; CurrentPlaybackPosition = null; AudioIsPlaying = false; @@ -263,7 +265,7 @@ along with Foobar. If not, see void HowlOnStop(Howler.Blazor.Components.Events.HowlEventArgs args) { paused = false; - audioUpdateTimer.Stop(); + audioUpdateTimer?.Stop(); CurrentlyPlayingTrack = null; CurrentPlaybackPosition = null; AudioIsPlaying = false; diff --git a/AudioCuesheetEditor/Shared/CultureSelector.razor b/AudioCuesheetEditor/Shared/CultureSelector.razor index d8bb3c73..7d6566d2 100644 --- a/AudioCuesheetEditor/Shared/CultureSelector.razor +++ b/AudioCuesheetEditor/Shared/CultureSelector.razor @@ -21,7 +21,7 @@ along with Foobar. If not, see @inject ITextLocalizerService _localizationService @inject ITextLocalizer _localizer @inject ILogger _logger -@inject LocalStorageOptionsProvider _localStorageOptionsProvider +@inject ILocalStorageOptionsProvider _localStorageOptionsProvider diff --git a/AudioCuesheetEditor/Shared/EditImportOptions.razor b/AudioCuesheetEditor/Shared/EditImportOptions.razor new file mode 100644 index 00000000..961e45be --- /dev/null +++ b/AudioCuesheetEditor/Shared/EditImportOptions.razor @@ -0,0 +1,179 @@ + + +@implements IDisposable + +@inject ITextLocalizerService _localizationService +@inject ITextLocalizer _localizer +@inject ITextLocalizer _validationMessageLocalizer +@inject ILocalStorageOptionsProvider _localStorageOptionsProvider + + + + + + + @_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] + } + + + + + + + + + +@code{ + [Parameter] + public EventCallback OptionsChanged { get; set; } + + public ImportOptions? ImportOptions { get; private set; } + + public void Dispose() + { + _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; + _localStorageOptionsProvider.OptionSaved -= LocalStorageOptionsProvider_OptionsSaved; + } + + protected override async Task OnInitializedAsync() + { + TimeSpanFormat.TextLocalizer = _localizer; + TextImportScheme.TextLocalizer = _localizer; + + _localizationService.LocalizationChanged += LocalizationService_LocalizationChanged; + _localStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionsSaved; + + ImportOptions = await _localStorageOptionsProvider.GetOptions(); + + await base.OnInitializedAsync(); + } + + void LocalizationService_LocalizationChanged(object? sender, EventArgs args) + { + TimeSpanFormat.TextLocalizer = _localizer; + TextImportScheme.TextLocalizer = _localizer; + StateHasChanged(); + } + + void LocalStorageOptionsProvider_OptionsSaved(object? sender, IOptions options) + { + if (options is ImportOptions importOptions) + { + ImportOptions = importOptions; + StateHasChanged(); + } + } + + async Task TextChangedAsync(Action setter, string text) + { + if (ImportOptions == null) + { + throw new NullReferenceException(); + } + setter(ImportOptions, text); + await _localStorageOptionsProvider.SaveOptions(ImportOptions); + await OptionsChanged.InvokeAsync(ImportOptions); + } +} \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/EditRecordOptions.razor b/AudioCuesheetEditor/Shared/EditRecordOptions.razor index 4414f1f0..e96e7037 100644 --- a/AudioCuesheetEditor/Shared/EditRecordOptions.razor +++ b/AudioCuesheetEditor/Shared/EditRecordOptions.razor @@ -18,7 +18,7 @@ along with Foobar. If not, see @implements IDisposable @inject ITextLocalizer _localizer -@inject LocalStorageOptionsProvider _localStorageOptionsProvider +@inject ILocalStorageOptionsProvider _localStorageOptionsProvider @inject ITextLocalizerService _localizationService @inject ITextLocalizer _validationMessageLocalizer diff --git a/AudioCuesheetEditor/Shared/EditTrackModal.razor b/AudioCuesheetEditor/Shared/EditTrackModal.razor index b0b83d9c..84f5e846 100644 --- a/AudioCuesheetEditor/Shared/EditTrackModal.razor +++ b/AudioCuesheetEditor/Shared/EditTrackModal.razor @@ -15,17 +15,16 @@ You should have received a copy of the GNU General Public License along with Foobar. If not, see . --> - -@implements IDisposable +@implements IAsyncDisposable @inject ITextLocalizer _localizer @inject MusicBrainzDataProvider _musicBrainzDataProvider @inject SessionStateContainer _sessionStateContainer -@inject LocalStorageOptionsProvider _localStorageOptionsProvider +@inject ILocalStorageOptionsProvider _localStorageOptionsProvider @inject TraceChangeManager _traceChangeManager @inject HotKeys _hotKeys @inject ITextLocalizer _validationMessageLocalizer -@inject DateTimeUtility _dateTimeUtility +@inject ApplicationOptionsTimeSpanParser _applicationOptionsTimeSpanParser @@ -102,7 +101,7 @@ along with Foobar. If not, see @_localizer["Begin"] - + @@ -114,7 +113,7 @@ along with Foobar. If not, see @_localizer["End"] - + @@ -126,7 +125,7 @@ along with Foobar. If not, see @_localizer["Length"] - + @@ -149,7 +148,7 @@ along with Foobar. If not, see @_localizer["PreGap"] - + @@ -161,7 +160,7 @@ along with Foobar. If not, see @_localizer["PostGap"] - + @@ -253,7 +252,7 @@ along with Foobar. If not, see - + @@ -271,7 +270,7 @@ along with Foobar. If not, see - + @@ -289,7 +288,7 @@ along with Foobar. If not, see - + @@ -319,7 +318,7 @@ along with Foobar. If not, see - + @@ -337,7 +336,7 @@ along with Foobar. If not, see - + @@ -420,10 +419,13 @@ along with Foobar. If not, see Validations? validations; ApplicationOptions? applicationOptions; - public void Dispose() + public async ValueTask DisposeAsync() { - hotKeysContext?.Dispose(); _localStorageOptionsProvider.OptionSaved -= LocalStorageOptionsProvider_OptionSaved; + if (hotKeysContext != null) + { + await hotKeysContext.DisposeAsync(); + } } protected override async Task OnInitializedAsync() diff --git a/AudioCuesheetEditor/Shared/MainLayout.razor b/AudioCuesheetEditor/Shared/MainLayout.razor index b79f7e52..ea35a241 100644 --- a/AudioCuesheetEditor/Shared/MainLayout.razor +++ b/AudioCuesheetEditor/Shared/MainLayout.razor @@ -18,7 +18,7 @@ along with Foobar. If not, see @inherits LayoutComponentBase -@implements IDisposable +@implements IAsyncDisposable @inject NavigationManager _navigationManager @inject ITextLocalizer _localizer @@ -27,7 +27,7 @@ along with Foobar. If not, see @inject ILogger _logger @inject IJSRuntime _jsRuntime @inject HotKeys _hotKeys -@inject LocalStorageOptionsProvider _localStorageOptionsProvider +@inject ILocalStorageOptionsProvider _localStorageOptionsProvider @inject SessionStateContainer _sessionStateContainer @inject IBlazorDownloadFileService _blazorDownloadFileService @inject ITextLocalizer _validationMessageLocalizer @@ -484,9 +484,8 @@ along with Foobar. If not, see await base.OnInitializedAsync(); } - public void Dispose() + public async ValueTask DisposeAsync() { - hotKeysContext?.Dispose(); _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; _localStorageOptionsProvider.OptionSaved -= LocalStorageOptionsProvider_OptionSaved; _traceChangeManager.TracedObjectHistoryChanged -= TraceChangeManager_TracedObjectHistoryChanged; @@ -500,6 +499,10 @@ along with Foobar. If not, see { modalExportdialogCuesheet.GenerateExportfilesClicked -= ModalExportdialogCuesheet_GenerateExportfilesClicked; } + if (hotKeysContext != null) + { + await hotKeysContext.DisposeAsync(); + } } public void SetDisplayMenuBar(Boolean display) diff --git a/AudioCuesheetEditor/Shared/ModalDialog.razor b/AudioCuesheetEditor/Shared/ModalDialog.razor index c19fc165..8355b413 100644 --- a/AudioCuesheetEditor/Shared/ModalDialog.razor +++ b/AudioCuesheetEditor/Shared/ModalDialog.razor @@ -15,8 +15,7 @@ You should have received a copy of the GNU General Public License along with Foobar. If not, see . --> - -@implements IDisposable +@implements IAsyncDisposable @inject ITextLocalizer _localizer @inject HotKeys _hotKeys @@ -65,10 +64,13 @@ along with Foobar. If not, see @code { - public void Dispose() + public async ValueTask DisposeAsync() { - hotKeysContext?.Dispose(); _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; + if (hotKeysContext != null) + { + await hotKeysContext.DisposeAsync(); + } } protected override Task OnInitializedAsync() diff --git a/AudioCuesheetEditor/Shared/ModalExportdialog.razor b/AudioCuesheetEditor/Shared/ModalExportdialog.razor index 0d7fe465..fd83b9df 100644 --- a/AudioCuesheetEditor/Shared/ModalExportdialog.razor +++ b/AudioCuesheetEditor/Shared/ModalExportdialog.razor @@ -15,12 +15,12 @@ You should have received a copy of the GNU General Public License along with Foobar. If not, see . --> -@implements IDisposable +@implements IAsyncDisposable @inject ITextLocalizer _localizer @inject ILogger _logger @inject ITextLocalizer _validationMessageLocalizer -@inject LocalStorageOptionsProvider _localStorageOptionsProvider +@inject ILocalStorageOptionsProvider _localStorageOptionsProvider @inject SessionStateContainer _sessionStateContainer @inject IBlazorDownloadFileService _blazorDownloadFileService @inject HotKeys _hotKeys @@ -198,10 +198,13 @@ along with Foobar. If not, see await base.OnInitializedAsync(); } - public void Dispose() + public async ValueTask DisposeAsync() { - hotKeysContext?.Dispose(); _localStorageOptionsProvider.OptionSaved -= LocalStorageOptionsProvider_OptionSaved; + if (hotKeysContext != null) + { + await hotKeysContext.DisposeAsync(); + } } public async Task Show() diff --git a/AudioCuesheetEditor/Shared/OptionsDialog.razor b/AudioCuesheetEditor/Shared/OptionsDialog.razor index 94820ff4..a0491b87 100644 --- a/AudioCuesheetEditor/Shared/OptionsDialog.razor +++ b/AudioCuesheetEditor/Shared/OptionsDialog.razor @@ -15,10 +15,9 @@ You should have received a copy of the GNU General Public License along with Foobar. If not, see . --> +@implements IAsyncDisposable -@implements IDisposable - -@inject LocalStorageOptionsProvider _localStorageOptionsProvider +@inject ILocalStorageOptionsProvider _localStorageOptionsProvider @inject ITextLocalizer _localizer @inject ILogger _logger @inject IJSRuntime _jsRuntime @@ -159,11 +158,14 @@ along with Foobar. If not, see HotKeysContext? hotKeysContext; Validation? timespanformatValidation; - public void Dispose() + public async ValueTask DisposeAsync() { - hotKeysContext?.Dispose(); _localizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; _localStorageOptionsProvider.OptionSaved -= LocalStorageOptionsProvider_OptionSaved; + if (hotKeysContext != null) + { + await hotKeysContext.DisposeAsync(); + } } public async Task Show() diff --git a/AudioCuesheetEditor/Shared/TracksTable.razor b/AudioCuesheetEditor/Shared/TracksTable.razor index 6252e512..61947058 100644 --- a/AudioCuesheetEditor/Shared/TracksTable.razor +++ b/AudioCuesheetEditor/Shared/TracksTable.razor @@ -20,13 +20,13 @@ along with Foobar. If not, see @inject ITextLocalizer _localizer @inject SessionStateContainer _sessionStateContainer -@inject LocalStorageOptionsProvider _localStorageOptionsProvider +@inject ILocalStorageOptionsProvider _localStorageOptionsProvider @inject TraceChangeManager _traceChangeManager @inject ILogger _logger @inject ITextLocalizerService _localizationService @inject MusicBrainzDataProvider _musicBrainzDataProvider @inject ITextLocalizer _validationMessageLocalizer -@inject DateTimeUtility _dateTimeUtility +@inject ApplicationOptionsTimeSpanParser _applicationOptionsTimeSpanParser @if (_sessionStateContainer.CurrentViewMode == ViewMode.ViewModeFull) @@ -341,7 +341,7 @@ along with Foobar. If not, see } - + @@ -353,7 +353,7 @@ along with Foobar. If not, see - + @@ -364,7 +364,7 @@ along with Foobar. If not, see - + diff --git a/AudioCuesheetEditor/_Imports.razor b/AudioCuesheetEditor/_Imports.razor index f00ca655..b86c652b 100644 --- a/AudioCuesheetEditor/_Imports.razor +++ b/AudioCuesheetEditor/_Imports.razor @@ -24,6 +24,8 @@ @using AudioCuesheetEditor.Data.Options @using AudioCuesheetEditor.Model.Utility @using AudioCuesheetEditor.Data.Services +@using AudioCuesheetEditor.Services.IO +@using AudioCuesheetEditor.Services.UI @using Microsoft.Extensions.Logging @using Blazorise @using Blazorise.Components diff --git a/AudioCuesheetEditor/wwwroot/index.html b/AudioCuesheetEditor/wwwroot/index.html index 952379fc..bc0bff35 100644 --- a/AudioCuesheetEditor/wwwroot/index.html +++ b/AudioCuesheetEditor/wwwroot/index.html @@ -21,28 +21,28 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + +