diff --git a/amp.Database/ExtensionClasses/DbContextExtensions.cs b/amp.Database/ExtensionClasses/DbContextExtensions.cs index 3efa3c52..bb953b1c 100644 --- a/amp.Database/ExtensionClasses/DbContextExtensions.cs +++ b/amp.Database/ExtensionClasses/DbContextExtensions.cs @@ -24,8 +24,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ #endregion +using System.Reflection; +using amp.Shared.Interfaces; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; namespace amp.Database.ExtensionClasses; @@ -35,36 +36,47 @@ namespace amp.Database.ExtensionClasses; public static class DbContextExtensions { /// - /// Updates the specified range of entities, saves the possible changes and stops tracking the if the is set to true. + /// Upserts the specified entity range into the database and saves the changes. /// - /// The type of entity being operated on by this set. - /// The database context the entities belong to. - /// The entities to update. - /// if set to true the is called. - /// A task that represents the asynchronous save operation. The task result contains the number of state entries written to the database. - public static async Task UpdateRangeAndSave(this DbContext context, IEnumerable entities, bool clearTracking = true) where TEntity : class + /// The type of the entity. + /// The database context. + /// The entities to upsert. + public static async Task UpsertRange(this DbContext context, params TEntity[] entities) + where TEntity : class, IEntity { - return await context.UpdateRangeAndSave(entities.ToArray(), clearTracking); + var toInsert = entities.Where(f => f.Id == 0).ToList(); + var toUpdate = entities.Where(f => f.Id != 0).ToList(); + + foreach (var entity in toUpdate) + { + var update = await context.Set().FirstOrDefaultAsync(f => f.Id == entity.Id); + if (update != null) + { + UpdateEntity(update, entity); + await context.SaveChangesAsync(); + } + } + + if (toInsert.Count > 0) + { + context.Set().AddRange(toInsert); + await context.SaveChangesAsync(); + } } - /// - /// Updates the specified range of entities, saves the possible changes and stops tracking the if the is set to true. - /// - /// The type of entity being operated on by this set. - /// The database context the entities belong to. - /// The entities to update. - /// if set to true the is called. - /// A task that represents the asynchronous save operation. The task result contains the number of state entries written to the database. - public static async Task UpdateRangeAndSave(this DbContext context, TEntity[] entities, bool clearTracking = true) where TEntity : class + private static void UpdateEntity(IEntity destination, IEntity source) { - context.Set().UpdateRange(entities); - var result = await context.SaveChangesAsync(); + var propertyInfos = destination.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); - if (clearTracking) + foreach (var propertyInfo in propertyInfos) { - context.ChangeTracker.Clear(); - } + if (destination.Id != 0 && propertyInfo.Name == nameof(IEntity.Id) || propertyInfo.Name == nameof(IRowVersionEntity.RowVersion)) + { + continue; + } - return result; + var sourceValue = propertyInfo.GetValue(source); + propertyInfo.SetValue(destination, sourceValue); + } } } \ No newline at end of file diff --git a/amp.EtoForms/Classes/AlbumTrackMethods.cs b/amp.EtoForms/Classes/AlbumTrackMethods.cs index 8a0e46f7..fe69c1c6 100644 --- a/amp.EtoForms/Classes/AlbumTrackMethods.cs +++ b/amp.EtoForms/Classes/AlbumTrackMethods.cs @@ -26,7 +26,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System.Collections.ObjectModel; using amp.Database; -using amp.EtoForms.Models; +using amp.EtoForms.DtoClasses; using Eto.Forms; using EtoForms.Controls.Custom.Utilities; using Microsoft.EntityFrameworkCore; diff --git a/amp.EtoForms/Dialogs/DialogModifySavedQueue.cs b/amp.EtoForms/Dialogs/DialogModifySavedQueue.cs index 90739b17..6aa80c22 100644 --- a/amp.EtoForms/Dialogs/DialogModifySavedQueue.cs +++ b/amp.EtoForms/Dialogs/DialogModifySavedQueue.cs @@ -26,8 +26,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System.Collections.ObjectModel; using amp.Database; -using amp.Database.DataModel; -using amp.Database.ExtensionClasses; +using amp.EtoForms.DtoClasses; using amp.EtoForms.Utilities; using amp.Shared.Localization; using Eto.Drawing; @@ -103,14 +102,14 @@ public DialogModifySavedQueue(AmpContext context, long queueId) new GridColumn { DataCell = new TextBoxCell(nameof(QueueTrack.QueueIndex)), - HeaderText = UI.QueueCreated, + HeaderText = UI.QueueIndex, }, new GridColumn { DataCell = new TextBoxCell { Binding = Binding - .Property((QueueTrack qs) => TrackDisplayNameGenerate.GetAudioTrackName(qs.AudioTrack!)) + .Property((QueueTrack qs) => TrackDisplayNameGenerate.GetAudioTrackName(qs.AudioTrack)) .Convert(s => s) .Cast(), }, Expand = true, @@ -211,7 +210,7 @@ private async Task ListQueue() { queueTracks.Clear(); foreach (var queueTrack in await context.QueueTracks.Include(f => f.AudioTrack).Where(f => f.QueueSnapshotId == queueId) - .OrderBy(f => f.QueueIndex).AsNoTracking().ToListAsync()) + .OrderBy(f => f.QueueIndex).Select(f => Globals.AutoMapper.Map(f)).ToListAsync()) { queueTracks.Add(queueTrack); } @@ -225,6 +224,7 @@ private async void DialogModifySavedQueue_Shown(object? sender, EventArgs e) private async void BtnSaveAndClose_Click(object? sender, EventArgs e) { + context.ChangeTracker.Clear(); await using var transaction = await context.Database.BeginTransactionAsync(); await Globals.LoggerSafeInvokeAsync(async () => @@ -233,12 +233,30 @@ await Globals.LoggerSafeInvokeAsync(async () => context.QueueTracks.RemoveRange(itemsToDelete); await context.SaveChangesAsync(); - await context.UpdateRangeAndSave(queueTracks); + foreach (var queueTrack in queueTracks) + { + await UpdateTrack(context, queueTrack); + } + + await context.SaveChangesAsync(); + await transaction.CommitAsync(); }, async (_) => await transaction.RollbackAsync()); Close(true); } + private static async Task UpdateTrack(AmpContext context, QueueTrack queueTrack) + { + var track = await context.QueueTracks.FirstAsync(f => f.Id == queueTrack.Id); + track.AudioTrackId = queueTrack.AudioTrackId; + track.CreatedAtUtc = queueTrack.CreatedAtUtc; + track.ModifiedAtUtc = queueTrack.ModifiedAtUtc; + track.QueueSnapshotId = queueTrack.QueueSnapshotId; + track.QueueIndex = queueTrack.QueueIndex; + + await context.SaveChangesAsync(); + } + private void BtnCancel_Click(object? sender, EventArgs e) { Close(false); diff --git a/amp.EtoForms/Dialogs/DialogUpdateTagData.cs b/amp.EtoForms/Dialogs/DialogUpdateTagData.cs index 688fb97a..85be7488 100644 --- a/amp.EtoForms/Dialogs/DialogUpdateTagData.cs +++ b/amp.EtoForms/Dialogs/DialogUpdateTagData.cs @@ -25,7 +25,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #endregion using amp.Database; -using amp.EtoForms.Models; +using amp.EtoForms.DtoClasses; using amp.Shared.Classes; using amp.Shared.Localization; using Eto.Drawing; diff --git a/amp.EtoForms/Models/Album.cs b/amp.EtoForms/DtoClasses/Album.cs similarity index 91% rename from amp.EtoForms/Models/Album.cs rename to amp.EtoForms/DtoClasses/Album.cs index ff03f738..2351fea9 100644 --- a/amp.EtoForms/Models/Album.cs +++ b/amp.EtoForms/DtoClasses/Album.cs @@ -28,7 +28,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System.Runtime.CompilerServices; using amp.Shared.Interfaces; -namespace amp.EtoForms.Models; +namespace amp.EtoForms.DtoClasses; /// /// A class for album. @@ -56,7 +56,7 @@ public DateTime? ModifiedAtUtc if (modifiedAtUtc != value) { modifiedAtUtc = value; - OnPropertyChanged(nameof(ModifiedAtUtc)); + OnPropertyChanged(); } } } @@ -71,7 +71,7 @@ public DateTime CreatedAtUtc if (createdAtUtc != value) { createdAtUtc = value; - OnPropertyChanged(nameof(CreatedAtUtc)); + OnPropertyChanged(); } } } @@ -86,7 +86,7 @@ public string AlbumName if (albumName != value) { albumName = value; - OnPropertyChanged(nameof(AlbumName)); + OnPropertyChanged(); } } } diff --git a/amp.EtoForms/Models/AlbumTrack.cs b/amp.EtoForms/DtoClasses/AlbumTrack.cs similarity index 88% rename from amp.EtoForms/Models/AlbumTrack.cs rename to amp.EtoForms/DtoClasses/AlbumTrack.cs index a7827d96..3277b9f6 100644 --- a/amp.EtoForms/Models/AlbumTrack.cs +++ b/amp.EtoForms/DtoClasses/AlbumTrack.cs @@ -29,7 +29,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using amp.EtoForms.Utilities; using amp.Shared.Interfaces; -namespace amp.EtoForms.Models; +namespace amp.EtoForms.DtoClasses; /// /// A class for album tracks. @@ -78,7 +78,7 @@ public DateTime? ModifiedAtUtc if (modifiedAtUtc != value) { modifiedAtUtc = value; - OnPropertyChanged(nameof(ModifiedAtUtc)); + OnPropertyChanged(); } } } @@ -93,7 +93,7 @@ public DateTime CreatedAtUtc if (createdAtUtc != value) { createdAtUtc = value; - OnPropertyChanged(nameof(CreatedAtUtc)); + OnPropertyChanged(); } } } @@ -108,7 +108,7 @@ public long AlbumId if (albumId != value) { albumId = value; - OnPropertyChanged(nameof(AlbumId)); + OnPropertyChanged(); } } @@ -124,7 +124,7 @@ public long AudioTrackId if (trackId != value) { trackId = value; - OnPropertyChanged(nameof(AudioTrackId)); + OnPropertyChanged(); } } } @@ -139,7 +139,7 @@ public int QueueIndex if (queueIndex != value) { queueIndex = value; - OnPropertyChanged(nameof(QueueIndex)); + OnPropertyChanged(); } } @@ -155,7 +155,7 @@ public int QueueIndexAlternate if (queueIndexAlternate != value) { queueIndexAlternate = value; - OnPropertyChanged(nameof(QueueIndexAlternate)); + OnPropertyChanged(); } } @@ -171,7 +171,7 @@ public AudioTrack? AudioTrack if (track != value) { track = value; - OnPropertyChanged(nameof(AudioTrack)); + OnPropertyChanged(); } } } @@ -186,7 +186,7 @@ public Album? Album if (album != value) { album = value; - OnPropertyChanged(nameof(Album)); + OnPropertyChanged(); } } } diff --git a/amp.EtoForms/Models/AudioTrack.cs b/amp.EtoForms/DtoClasses/AudioTrack.cs similarity index 85% rename from amp.EtoForms/Models/AudioTrack.cs rename to amp.EtoForms/DtoClasses/AudioTrack.cs index dcd99254..6db41b9c 100644 --- a/amp.EtoForms/Models/AudioTrack.cs +++ b/amp.EtoForms/DtoClasses/AudioTrack.cs @@ -30,7 +30,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using amp.Shared.Enumerations; using amp.Shared.Interfaces; -namespace amp.EtoForms.Models; +namespace amp.EtoForms.DtoClasses; /// /// The track entity-independent representation. @@ -110,7 +110,7 @@ public DateTime? ModifiedAtUtc if (modifiedAtUtc != value) { modifiedAtUtc = value; - OnPropertyChanged(nameof(ModifiedAtUtc)); + OnPropertyChanged(); } } } @@ -125,7 +125,7 @@ public DateTime CreatedAtUtc if (createdAtUtc != value) { createdAtUtc = value; - OnPropertyChanged(nameof(CreatedAtUtc)); + OnPropertyChanged(); } } } @@ -140,7 +140,7 @@ public int? PlayedByRandomize if (playedByRandomize != value) { playedByRandomize = value; - OnPropertyChanged(nameof(PlayedByRandomize)); + OnPropertyChanged(); } } } @@ -155,7 +155,7 @@ public int? PlayedByUser if (playedByUser != value) { playedByUser = value; - OnPropertyChanged(nameof(PlayedByUser)); + OnPropertyChanged(); } } } @@ -170,7 +170,7 @@ public int? SkippedEarlyCount if (skippedEarlyCount != value) { skippedEarlyCount = value; - OnPropertyChanged(nameof(SkippedEarlyCount)); + OnPropertyChanged(); } } } @@ -185,7 +185,7 @@ public string FileName if (fileName != value) { fileName = value; - OnPropertyChanged(nameof(FileName)); + OnPropertyChanged(); } } } @@ -200,7 +200,7 @@ public string? Artist if (artist != value) { artist = value; - OnPropertyChanged(nameof(Artist)); + OnPropertyChanged(); } } } @@ -215,7 +215,7 @@ public string? Album if (album != value) { album = value; - OnPropertyChanged(nameof(Album)); + OnPropertyChanged(); } } } @@ -230,7 +230,7 @@ public string? Track if (track != value) { track = value; - OnPropertyChanged(nameof(Track)); + OnPropertyChanged(); } } } @@ -245,7 +245,7 @@ public string? Year if (year != value) { year = value; - OnPropertyChanged(nameof(Year)); + OnPropertyChanged(); } } } @@ -260,7 +260,7 @@ public string? Lyrics if (lyrics != value) { lyrics = value; - OnPropertyChanged(nameof(Lyrics)); + OnPropertyChanged(); } } } @@ -275,7 +275,7 @@ public int? Rating if (rating != value) { rating = value; - OnPropertyChanged(nameof(Rating)); + OnPropertyChanged(); } } } @@ -290,7 +290,7 @@ public long? FileSizeBytes if (fileSizeBytes != value) { fileSizeBytes = value; - OnPropertyChanged(nameof(FileSizeBytes)); + OnPropertyChanged(); } } } @@ -305,7 +305,7 @@ public double PlaybackVolume if (Math.Abs(playbackVolume - value) > Globals.FloatingPointTolerance) { playbackVolume = value; - OnPropertyChanged(nameof(PlaybackVolume)); + OnPropertyChanged(); } } } @@ -320,7 +320,7 @@ public string? OverrideName if (overrideName != value) { overrideName = value; - OnPropertyChanged(nameof(OverrideName)); + OnPropertyChanged(); } } } @@ -335,7 +335,7 @@ public string? TagFindString if (tagFindString != value) { tagFindString = value; - OnPropertyChanged(nameof(TagFindString)); + OnPropertyChanged(); } } } @@ -350,7 +350,7 @@ public bool? TagRead if (tagRead != value) { tagRead = value; - OnPropertyChanged(nameof(TagRead)); + OnPropertyChanged(); } } } @@ -365,7 +365,7 @@ public string? FileNameNoPath if (fileNameNoPath != value) { fileNameNoPath = value; - OnPropertyChanged(nameof(FileNameNoPath)); + OnPropertyChanged(); } } } @@ -380,7 +380,7 @@ public string? Title if (title != value) { title = value; - OnPropertyChanged(nameof(Title)); + OnPropertyChanged(); } } } @@ -395,7 +395,7 @@ public byte[]? TrackImageData if (trackImageData != value) { trackImageData = value; - OnPropertyChanged(nameof(TrackImageData)); + OnPropertyChanged(); } } } @@ -410,7 +410,7 @@ public MusicFileType MusicFileType if (musicFileType != value) { musicFileType = value; - OnPropertyChanged(nameof(MusicFileType)); + OnPropertyChanged(); } } } diff --git a/amp.EtoForms/DtoClasses/QueueSnapshot.cs b/amp.EtoForms/DtoClasses/QueueSnapshot.cs new file mode 100644 index 00000000..7c97fdbc --- /dev/null +++ b/amp.EtoForms/DtoClasses/QueueSnapshot.cs @@ -0,0 +1,144 @@ +#region License +/* +MIT License + +Copyright(c) 2022 Petteri Kautonen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion + +using System.ComponentModel; +using System.Runtime.CompilerServices; +using amp.Shared.Interfaces; + +namespace amp.EtoForms.DtoClasses; + +/// +/// An entity to save queues into the database. +/// Implements the +/// +/// +public class QueueSnapshot : IQueueSnapshot, INotifyPropertyChanged +{ + private long id; + private long albumId; + private string snapshotName = string.Empty; + private DateTime snapshotDate; + private DateTime? modifiedAtUtc; + private DateTime createdAtUtc; + private IList? queueTracks; + private Album? album; + + /// + public long Id + { + get => id; + set => SetField(ref id, value); + } + + /// + public long AlbumId + { + get => albumId; + set => SetField(ref albumId, value); + } + + /// + public string SnapshotName + { + get => snapshotName; + set => SetField(ref snapshotName, value); + } + + /// + public DateTime SnapshotDate + { + get => snapshotDate; + set => SetField(ref snapshotDate, value); + } + + /// + public DateTime? ModifiedAtUtc + { + get => modifiedAtUtc; + set => SetField(ref modifiedAtUtc, value); + } + + /// + public DateTime CreatedAtUtc + { + get => createdAtUtc; + set => SetField(ref createdAtUtc, value); + } + + /// + /// Gets or sets the queue tracks belonging to this queue snapshot. + /// + /// The queued tracks. + public IList? QueueTracks + { + get => queueTracks; + set => SetField(ref queueTracks, value); + } + + /// + /// Gets or sets the album of the queue snapshot. + /// + /// The album of the queue snapshot. + public Album? Album + { + get => album; + set => SetField(ref album, value); + } + + /// + /// Occurs when a property value changes. + /// + public event PropertyChangedEventHandler? PropertyChanged; + + /// + /// Called when property value changes. + /// + /// Name of the property. + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + /// Sets the property backing field value and calls the for the field property. + /// + /// The type of the field. + /// The field which value to set. + /// The value set for the field. + /// The name of the property. + /// true if the field value was changed and set, false otherwise. + protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null) + { + if (EqualityComparer.Default.Equals(field, value)) + { + return false; + } + + field = value; + OnPropertyChanged(propertyName); + return true; + } +} \ No newline at end of file diff --git a/amp.EtoForms/DtoClasses/QueueTrack.cs b/amp.EtoForms/DtoClasses/QueueTrack.cs new file mode 100644 index 00000000..f2e2cb0c --- /dev/null +++ b/amp.EtoForms/DtoClasses/QueueTrack.cs @@ -0,0 +1,133 @@ +#region License +/* +MIT License + +Copyright(c) 2022 Petteri Kautonen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion + +using amp.Shared.Interfaces; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace amp.EtoForms.DtoClasses; + +/// +/// A DTO class for queue track data. +/// Implements the +/// +/// +public class QueueTrack : IQueueTrack, INotifyPropertyChanged +{ + private long id; + private long audioTrackId; + private long queueSnapshotId; + private int queueIndex; + private DateTime? modifiedAtUtc; + private DateTime createdAtUtc; + private AudioTrack audioTrack = new(); + + /// + public long Id + { + get => id; + set => SetField(ref id, value); + } + + /// + public long AudioTrackId + { + get => audioTrackId; + set => SetField(ref audioTrackId, value); + } + + /// + public long QueueSnapshotId + { + get => queueSnapshotId; + set => SetField(ref queueSnapshotId, value); + } + + /// + public int QueueIndex + { + get => queueIndex; + set => SetField(ref queueIndex, value); + } + + /// + public DateTime? ModifiedAtUtc + { + get => modifiedAtUtc; + set => SetField(ref modifiedAtUtc, value); + } + + /// + public DateTime CreatedAtUtc + { + get => createdAtUtc; + set => SetField(ref createdAtUtc, value); + } + + /// + /// Gets or sets the audio track of this queue track. + /// + /// The audio track of this queue track. + public AudioTrack AudioTrack + { + get => audioTrack; + set => SetField(ref audioTrack, value); + } + + /// + /// Occurs when a property value changes. + /// + public event PropertyChangedEventHandler? PropertyChanged; + + /// + /// Called when [property changed]. + /// + /// Name of the property. + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + /// Sets the property backing field value and calls the for the field property. + /// + /// The type of the field. + /// The field which value to set. + /// The value set for the field. + /// The name of the property. + /// true if the field value was changed and set, false otherwise. + protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null) + { + if (EqualityComparer.Default.Equals(field, value)) + { + return false; + } + + field = value; + OnPropertyChanged(propertyName); + return true; + } +} \ No newline at end of file diff --git a/amp.EtoForms/FormMain.Events.cs b/amp.EtoForms/FormMain.Events.cs index bcf6ab83..528db878 100644 --- a/amp.EtoForms/FormMain.Events.cs +++ b/amp.EtoForms/FormMain.Events.cs @@ -46,7 +46,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using EtoForms.Controls.Custom.UserIdle; using EtoForms.Controls.Custom.Utilities; using Microsoft.EntityFrameworkCore; -using AudioTrack = amp.EtoForms.Models.AudioTrack; +using AlbumTrack = amp.EtoForms.DtoClasses.AlbumTrack; +using AudioTrack = amp.EtoForms.DtoClasses.AudioTrack; namespace amp.EtoForms; @@ -104,7 +105,7 @@ await playbackOrder.MoveToQueueTopOrBottom(tracks, shift, e.Key == Keys.PageUp, { if (gvAudioTracks.SelectedItem != null) { - var albumTrack = (Models.AlbumTrack)gvAudioTracks.SelectedItem; + var albumTrack = (AlbumTrack)gvAudioTracks.SelectedItem; await playbackManager.PlayAudioTrack(albumTrack, true); e.Handled = true; return; @@ -192,9 +193,9 @@ private void FormMain_Closing(object? sender, CancelEventArgs e) idleChecker.Dispose(); } - private async Task GetNextAudioTrackFunc() + private async Task GetNextAudioTrackFunc() { - Models.AlbumTrack? result = null; + AlbumTrack? result = null; await Application.Instance.InvokeAsync(async () => { var nextTrackData = await playbackOrder.NextTrack(tracks); @@ -224,6 +225,7 @@ await Application.Instance.InvokeAsync(() => { var track = tracks.FirstOrDefault(f => f.AudioTrackId == e.AudioTrackId); lbTracksTitle.Text = track?.GetAudioTrackName() ?? string.Empty; + currentTrackId = track != null ? e.AudioTrackId : 0; btnPlayPause.CheckedChange -= PlayPauseToggle; btnPlayPause.Checked = e.PlaybackState == PlaybackState.Playing; btnPlayPause.CheckedChange += PlayPauseToggle; @@ -231,6 +233,30 @@ await Application.Instance.InvokeAsync(() => }); } + private void LbTracksTitle_MouseDown(object? sender, MouseEventArgs e) + { + if (e.Buttons == MouseButtons.Primary && currentTrackId != 0) + { + var index = tracks.FindIndex(f => f.AudioTrackId == currentTrackId); + if (index != -1) + { + var indexFiltered = filteredTracks.FindIndex(f => f.AudioTrackId == currentTrackId); + + if (indexFiltered != -1) + { + var dataSource = gvAudioTracks.DataStore.Cast().ToList(); + var displayTrack = dataSource.FindIndex(f => f.AudioTrackId == currentTrackId); + if (displayTrack != -1) + { + gvAudioTracks.SelectedRow = displayTrack; + gvAudioTracks.ScrollToRow(displayTrack); + gvAudioTracks.Focus(); + } + } + } + } + } + private void PlaybackManagerTrackChanged(object? sender, TrackChangedArgs e) { Application.Instance.Invoke(() => @@ -243,8 +269,9 @@ private void PlaybackManagerTrackChanged(object? sender, TrackChangedArgs e) trackVolumeSlider.SuspendEventInvocation = false; trackRatingSlider.SuspendEventInvocation = false; lbTracksTitle.Text = track?.GetAudioTrackName() ?? string.Empty; + currentTrackId = track != null ? e.AudioTrackId : 0; - var dataSource = gvAudioTracks.DataStore.Cast().ToList(); + var dataSource = gvAudioTracks.DataStore.Cast().ToList(); var displayTrack = dataSource.FindIndex(f => f.AudioTrackId == e.AudioTrackId); if (displayTrack != -1) { @@ -315,9 +342,9 @@ private async void PlayNextAudioTrackClick(object? sender, EventArgs e) await playbackManager.PlayNextTrack(true); } - private async Task GetTrackById(long trackId) + private async Task GetTrackById(long trackId) { - return await Application.Instance.InvokeAsync(Models.AlbumTrack? () => + return await Application.Instance.InvokeAsync(AlbumTrack? () => { return tracks.FirstOrDefault(f => f.AudioTrackId == trackId); }); @@ -428,7 +455,8 @@ private async void PlayPreviousClick(object? sender, EventArgs e) private void ManageSavedQueues_Executed(object? sender, EventArgs e) { - new FormSavedQueues(context, LoadOrAppendQueue).ShowModal(this); + using var form = new FormSavedQueues(context, LoadOrAppendQueue); + form.ShowModal(this); } private async void SaveQueueCommand_Executed(object? sender, EventArgs e) @@ -571,7 +599,7 @@ private void BtnStackQueueToggle_CheckedChange(object? sender, CheckedChangeEven private void TrackInfoCommand_Executed(object? sender, EventArgs e) { - var track = (Models.AlbumTrack?)gvAudioTracks.SelectedItem; + var track = (AlbumTrack?)gvAudioTracks.SelectedItem; if (track != null) { using var dialog = new FormDialogTrackInfo(track.AudioTrack!, AudioTrackChanged); @@ -598,11 +626,11 @@ private async void AudioTrackChanged(object? sender, AudioTrackChangedEventArgs } } - private async void QueryDivider_QueryCompleted(object? sender, QueryCompletedEventArgs e) + private async void QueryDivider_QueryCompleted(object? sender, QueryCompletedEventArgs e) { - tracks = new ObservableCollection(e.ResultList); + tracks = new ObservableCollection(e.ResultList); - tracks = new ObservableCollection(tracks.OrderBy(f => f.DisplayName)); + tracks = new ObservableCollection(tracks.OrderBy(f => f.DisplayName)); await Application.Instance.InvokeAsync(() => { @@ -611,7 +639,7 @@ await Application.Instance.InvokeAsync(() => if (!string.IsNullOrWhiteSpace(tbSearch.Text)) { filteredTracks = - new ObservableCollection(tracks + new ObservableCollection(tracks .Where(f => f.AudioTrack!.Match(tbSearch.Text)) .ToList()); } diff --git a/amp.EtoForms/FormMain.Fields.cs b/amp.EtoForms/FormMain.Fields.cs index 03a85e88..982afbe7 100644 --- a/amp.EtoForms/FormMain.Fields.cs +++ b/amp.EtoForms/FormMain.Fields.cs @@ -25,8 +25,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #endregion using amp.Database.QueryHelpers; +using amp.EtoForms.DtoClasses; using amp.EtoForms.Forms; -using amp.EtoForms.Models; using amp.Shared.Localization; using Eto.Drawing; using Eto.Forms; @@ -109,6 +109,7 @@ partial class FormMain private WindowState previousWindowState; private readonly FormAlbumImage formAlbumImage = new(); private QueryDivider? queryDivider; + private long currentTrackId; // About private readonly AboutDialog aboutDialog = new(); diff --git a/amp.EtoForms/FormMain.Layout.cs b/amp.EtoForms/FormMain.Layout.cs index 16f7e858..bb83218e 100644 --- a/amp.EtoForms/FormMain.Layout.cs +++ b/amp.EtoForms/FormMain.Layout.cs @@ -26,7 +26,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using amp.EtoForms.Models; +using amp.EtoForms.DtoClasses; using amp.EtoForms.Properties; using amp.Shared.Localization; using Eto.Drawing; @@ -245,6 +245,9 @@ private StackLayout CreateMainContent() btnClearSearch = new ImageOnlyButton(ClearSearchClick, Size20.ic_fluent_eraser_20_filled) { ImageColor = Color.Parse(Globals.ColorConfiguration.ClearSearchButtonColor), Size = Globals.SmallImageButtonDefaultSize, ToolTip = UI.ClearSearch, }; + lbTracksTitle.Cursor = Cursors.Pointer; + lbTracksTitle.MouseDown += LbTracksTitle_MouseDown; + var result = new StackLayout { Items = diff --git a/amp.EtoForms/FormMain.Methods.cs b/amp.EtoForms/FormMain.Methods.cs index bdd6ce09..30fe5d24 100644 --- a/amp.EtoForms/FormMain.Methods.cs +++ b/amp.EtoForms/FormMain.Methods.cs @@ -29,9 +29,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System.Reflection; using amp.Database.QueryHelpers; using amp.EtoForms.Dialogs; +using amp.EtoForms.DtoClasses; using amp.EtoForms.ExtensionClasses; using amp.EtoForms.Forms.Enumerations; -using amp.EtoForms.Models; using amp.EtoForms.Properties; using amp.EtoForms.Utilities; using amp.Playback.Classes; diff --git a/amp.EtoForms/FormMain.Properties.cs b/amp.EtoForms/FormMain.Properties.cs index 550cfbc9..a62d5af4 100644 --- a/amp.EtoForms/FormMain.Properties.cs +++ b/amp.EtoForms/FormMain.Properties.cs @@ -24,6 +24,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ #endregion +using amp.EtoForms.DtoClasses; + namespace amp.EtoForms; public partial class FormMain @@ -63,7 +65,7 @@ private long SelectedAlbumTrackId { if (gvAudioTracks.SelectedItem != null) { - var albumTrackId = ((Models.AlbumTrack)gvAudioTracks.SelectedItem).Id; + var albumTrackId = ((AlbumTrack)gvAudioTracks.SelectedItem).Id; return albumTrackId; } @@ -77,7 +79,7 @@ private IEnumerable SelectedAlbumTrackIds { foreach (var selectedItem in gvAudioTracks.SelectedItems) { - var trackId = ((Models.AlbumTrack)selectedItem).Id; + var trackId = ((AlbumTrack)selectedItem).Id; yield return trackId; } } @@ -89,7 +91,7 @@ private bool QueuedItemsInSelection { foreach (var selectedItem in gvAudioTracks.SelectedItems) { - return ((Models.AlbumTrack)selectedItem).QueueIndex > 0; + return ((AlbumTrack)selectedItem).QueueIndex > 0; } return false; @@ -102,7 +104,7 @@ private bool AlternateQueuedItemsInSelection { foreach (var selectedItem in gvAudioTracks.SelectedItems) { - return ((Models.AlbumTrack)selectedItem).QueueIndexAlternate > 0; + return ((AlbumTrack)selectedItem).QueueIndexAlternate > 0; } return false; diff --git a/amp.EtoForms/FormMain.cs b/amp.EtoForms/FormMain.cs index 761e8d67..79233fc8 100644 --- a/amp.EtoForms/FormMain.cs +++ b/amp.EtoForms/FormMain.cs @@ -34,8 +34,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using Eto.Drawing; using Eto.Forms; using EtoForms.Controls.Custom.UserIdle; -using AlbumTrack = amp.EtoForms.Models.AlbumTrack; -using AudioTrack = amp.EtoForms.Models.AudioTrack; +using AlbumTrack = amp.EtoForms.DtoClasses.AlbumTrack; +using AudioTrack = amp.EtoForms.DtoClasses.AudioTrack; namespace amp.EtoForms; @@ -62,7 +62,7 @@ public FormMain() positionSaveLoad = new FormSaveLoadPosition(this); - playbackOrder = new PlaybackOrder(Globals.Settings, + playbackOrder = new PlaybackOrder(Globals.Settings, Globals.Settings.StackQueueRandomPercentage, UpdateQueueFunc); // ReSharper disable once StringLiteralTypo @@ -76,7 +76,7 @@ public FormMain() Database.Globals.ConnectionString = $"Data Source={databaseFile}"; - playbackManager = new PlaybackManager(Globals.Logger, GetNextAudioTrackFunc, GetTrackById, + playbackManager = new PlaybackManager(Globals.Logger, GetNextAudioTrackFunc, GetTrackById, () => Application.Instance.RunIteration(), Globals.Settings.PlaybackRetryCount); context = new AmpContext(); @@ -112,9 +112,9 @@ private void TestStuff_Executed(object? sender, EventArgs e) private ObservableCollection tracks = new(); private ObservableCollection filteredTracks = new(); - private readonly PlaybackManager playbackManager; - private QuietHourHandler quietHourHandler; - private readonly PlaybackOrder playbackOrder; + private readonly PlaybackManager playbackManager; + private QuietHourHandler quietHourHandler; + private readonly PlaybackOrder playbackOrder; private readonly AmpContext context; private readonly UserIdleChecker idleChecker; private readonly System.Timers.Timer tmMessageQueueTimer = new(1000); diff --git a/amp.EtoForms/Forms/EventArguments/AudioTrackChangedEventArgs.cs b/amp.EtoForms/Forms/EventArguments/AudioTrackChangedEventArgs.cs index 71d2cf8d..2a4a7aad 100644 --- a/amp.EtoForms/Forms/EventArguments/AudioTrackChangedEventArgs.cs +++ b/amp.EtoForms/Forms/EventArguments/AudioTrackChangedEventArgs.cs @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ #endregion -using amp.EtoForms.Models; +using amp.EtoForms.DtoClasses; namespace amp.EtoForms.Forms.EventArguments; diff --git a/amp.EtoForms/Forms/FormAlbums.cs b/amp.EtoForms/Forms/FormAlbums.cs index 2288991e..2b775ad7 100644 --- a/amp.EtoForms/Forms/FormAlbums.cs +++ b/amp.EtoForms/Forms/FormAlbums.cs @@ -151,7 +151,9 @@ await Globals.LoggerSafeInvokeAsync(async () => var toUpdate = dataSource!.Where(f => existingIds.Contains(f.Id)).ToList(); - context.Albums.UpdateRange(toUpdate); + + + await context.UpsertRange(toUpdate.Cast().ToArray()); await context.SaveChangesAsync(); await transaction.CommitAsync(); context.ChangeTracker.Clear(); diff --git a/amp.EtoForms/Forms/FormDialogTrackInfo.cs b/amp.EtoForms/Forms/FormDialogTrackInfo.cs index 84c1c523..75e22094 100644 --- a/amp.EtoForms/Forms/FormDialogTrackInfo.cs +++ b/amp.EtoForms/Forms/FormDialogTrackInfo.cs @@ -24,8 +24,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ #endregion +using amp.EtoForms.DtoClasses; using amp.EtoForms.Forms.EventArguments; -using amp.EtoForms.Models; using amp.Shared.Classes; using amp.Shared.Localization; using ATL; diff --git a/amp.EtoForms/Forms/FormSavedQueues.cs b/amp.EtoForms/Forms/FormSavedQueues.cs index 23875d0b..08d40800 100644 --- a/amp.EtoForms/Forms/FormSavedQueues.cs +++ b/amp.EtoForms/Forms/FormSavedQueues.cs @@ -26,8 +26,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System.Collections.ObjectModel; using amp.Database; -using amp.Database.DataModel; +using amp.Database.ExtensionClasses; using amp.EtoForms.Dialogs; +using amp.EtoForms.DtoClasses; using amp.EtoForms.Forms.Enumerations; using amp.EtoForms.Layout; using amp.EtoForms.Utilities; @@ -38,6 +39,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using EtoForms.Controls.Custom.Utilities; using FluentIcons.Resources.Filled; using Microsoft.EntityFrameworkCore; +using Album = amp.EtoForms.DtoClasses.Album; namespace amp.EtoForms.Forms; @@ -133,7 +135,7 @@ public FormSavedQueues(AmpContext context, Func, long, Que DataCell = new TextBoxCell { Binding = Binding - .Property((QueueTrack qs) => TrackDisplayNameGenerate.GetAudioTrackName(qs.AudioTrack!)) + .Property((QueueTrack qs) => TrackDisplayNameGenerate.GetAudioTrackName(qs.AudioTrack)) .Convert(s => s) .Cast(), }, Expand = true, @@ -144,9 +146,9 @@ public FormSavedQueues(AmpContext context, Func, long, Que DataStore = queueTracks, }; - var panel1 = new Panel {Content = gvAlbumQueues, Size = new Size(700, 250),}; - var panel2 = new Panel {Content = gvAlbumQueueTracks, Size = new Size(700, 250),}; - + var panel1 = new Panel { Content = gvAlbumQueues, Size = new Size(700, 250), }; + var panel2 = new Panel { Content = gvAlbumQueueTracks, Size = new Size(700, 250), }; + Content = new TableLayout { Rows = @@ -182,7 +184,7 @@ public FormSavedQueues(AmpContext context, Func, long, Que private async void BtnLoadQueueClick(object? sender, EventArgs e) { - var albumId = ((Models.Album?)(cmbAlbumSelect.SelectedValue))?.Id; + var albumId = ((Album?)(cmbAlbumSelect.SelectedValue))?.Id; if (SelectedQueueId != 0 && albumId != null) { var queueData = new Dictionary(context.QueueTracks @@ -199,7 +201,7 @@ private async void BtnLoadQueueClick(object? sender, EventArgs e) private void CopyToFolderClick(object? sender, EventArgs e) { - var fileNames = queueTracks.Select(f => f.AudioTrack!.FileName).ToList(); + var fileNames = queueTracks.Select(f => f.AudioTrack.FileName).ToList(); if (selectFolderDialog.ShowDialog(this) == DialogResult.Ok) { Globals.LoggerSafeInvoke(() => @@ -232,6 +234,7 @@ private async Task Save() await using var transaction = await context.Database.BeginTransactionAsync(); await Globals.LoggerSafeInvokeAsync(async () => { + context.ChangeTracker.Clear(); var removeTracks = await context.QueueTracks.Where(f => queuesToDelete.Contains(f.QueueSnapshotId)) .ToListAsync(); context.QueueTracks.RemoveRange(removeTracks); @@ -242,7 +245,9 @@ await Globals.LoggerSafeInvokeAsync(async () => context.QueueSnapshots.RemoveRange(removeQueues); await context.SaveChangesAsync(); - context.QueueSnapshots.UpdateRange(queueSnapshots); + var updateData = queueSnapshots.Select(f => Globals.AutoMapper.Map(f)).ToArray(); + + await context.UpsertRange(updateData); await context.SaveChangesAsync(); await transaction.CommitAsync(); @@ -265,19 +270,19 @@ private void GvAlbumQueues_SelectionChanged(object? sender, EventArgs e) foreach (var queueSnapshot in context.QueueTracks.Include(f => f.AudioTrack).Where(f => f.QueueSnapshotId == queueId) .OrderBy(f => f.QueueIndex).AsNoTracking()) { - queueTracks.Add(queueSnapshot); + queueTracks.Add(Globals.AutoMapper.Map(queueSnapshot)); } } private void RefreshQueueSnapshots(long? arg) { - arg ??= ((Models.Album?)(cmbAlbumSelect.SelectedValue))?.Id; + arg ??= ((Album?)(cmbAlbumSelect.SelectedValue))?.Id; queueSnapshots.Clear(); foreach (var queueSnapshot in context.QueueSnapshots.Where(f => !queuesToDelete.Contains(f.Id) && f.AlbumId == arg).OrderBy(f => f.SnapshotName).AsNoTracking()) { - queueSnapshots.Add(queueSnapshot); + queueSnapshots.Add(Globals.AutoMapper.Map(queueSnapshot)); } } @@ -289,7 +294,8 @@ private Task SelectedValueChanged(long? arg) private void EditClick(object? sender, EventArgs e) { - new DialogModifySavedQueue(context, SelectedQueueId).ShowModal(this); + using var dialog = new DialogModifySavedQueue(context, SelectedQueueId); + dialog.ShowModal(this); } private long SelectedQueueId => ((QueueSnapshot?)gvAlbumQueues.SelectedItem)?.Id ?? 0; diff --git a/amp.EtoForms/Globals.cs b/amp.EtoForms/Globals.cs index 411ef44c..303070a8 100644 --- a/amp.EtoForms/Globals.cs +++ b/amp.EtoForms/Globals.cs @@ -328,12 +328,17 @@ internal static IMapper AutoMapper { mapperConfiguration ??= new MapperConfiguration(cfg => { - cfg.CreateMap(); - cfg.CreateMap(); - cfg.CreateMap(); - cfg.CreateMap(); - cfg.CreateMap(); - cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); + + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); }); mapper ??= mapperConfiguration.CreateMapper(); diff --git a/amp.EtoForms/Layout/ReusableControls.cs b/amp.EtoForms/Layout/ReusableControls.cs index 7654626a..30135b34 100644 --- a/amp.EtoForms/Layout/ReusableControls.cs +++ b/amp.EtoForms/Layout/ReusableControls.cs @@ -26,6 +26,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using amp.Database; using amp.Database.ExtensionClasses; +using amp.EtoForms.DtoClasses; using Eto.Forms; namespace amp.EtoForms.Layout; @@ -38,7 +39,7 @@ public static ComboBox CreateAlbumSelectCombo(Func? selectedValueCh cmbAlbumSelect.SelectedValueChanged += async (_, _) => { - var id = ((Models.Album?)cmbAlbumSelect.SelectedValue)?.Id; + var id = ((Album?)cmbAlbumSelect.SelectedValue)?.Id; if (selectedValueChanged != null) { @@ -55,7 +56,7 @@ private static async Task UpdateAlbumDataSource(ComboBox cmbAlbumSelect, AmpCont { var albumsEntity = await context.Albums.GetUnTrackedList(f => f.AlbumName, new long[] { 1, }); - var albums = albumsEntity.Select(f => Globals.AutoMapper.Map(f)).ToList(); + var albums = albumsEntity.Select(f => Globals.AutoMapper.Map(f)).ToList(); cmbAlbumSelect.DataStore = albums; if (albums.Any(f => f.Id == currentAlbumId)) diff --git a/amp.EtoForms/Utilities/FileUtils.cs b/amp.EtoForms/Utilities/FileUtils.cs new file mode 100644 index 00000000..1078e1b4 --- /dev/null +++ b/amp.EtoForms/Utilities/FileUtils.cs @@ -0,0 +1,51 @@ +#region License +/* +MIT License + +Copyright(c) 2022 Petteri Kautonen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion + +namespace amp.EtoForms.Utilities; + +/// +/// Some file and folder utilities. +/// +public static class FileUtils +{ + /// + /// Gets the first existing file from the specified list of files. + /// + /// The files. + /// A file name. + public static string FirstExistingFile(params string[] files) + { + foreach (var file in files) + { + if (File.Exists(file)) + { + return file; + } + } + + return files.LastOrDefault() ?? string.Empty; + } +} \ No newline at end of file diff --git a/amp.EtoForms/Utilities/Help.cs b/amp.EtoForms/Utilities/Help.cs index 21c824bd..4d53fa8b 100644 --- a/amp.EtoForms/Utilities/Help.cs +++ b/amp.EtoForms/Utilities/Help.cs @@ -25,6 +25,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #endregion using System.Reflection; +using amp.Shared.Classes; using Eto.Forms; namespace amp.EtoForms.Utilities; @@ -86,18 +87,22 @@ internal static void LaunchHelp() /// Launches the help from the folder specified in the settings. /// /// The parent for a dialog in case the help file is not found. - /// true if the web browser for the help file was successfully launched, false otherwise. - internal static bool LaunchHelpFromSettings(Control parent) + internal static void LaunchHelpFromSettings(Control parent) { - var helpFile = Path.Combine(Globals.Settings.HelpFolder, "index.html"); + + var helpFileFallback = Path.Combine(Globals.Settings.HelpFolder, $"amp-en-{UtilityOS.OsNameLowerCase}", "index.html"); + var helpFileCurrentLocale = Path.Combine(Globals.Settings.HelpFolder, $"amp-{Globals.Settings.Locale}-{UtilityOS.OsNameLowerCase}", "index.html"); + var indexHtml = Path.Combine(Globals.Settings.HelpFolder, "index.html"); + + var helpFile = FileUtils.FirstExistingFile(helpFileCurrentLocale, helpFileFallback, indexHtml); + if (File.Exists(helpFile)) { var uri = new Uri(helpFile).AbsoluteUri; Application.Instance.Open(uri); - return true; + return; } MessageBox.Show(parent, Shared.Localization.Messages.PleaseSetTheHelpPathFromTheSettings, Shared.Localization.Messages.Information); - return false; } } \ No newline at end of file diff --git a/amp.EtoForms/amp.EtoForms.csproj b/amp.EtoForms/amp.EtoForms.csproj index db115a86..a6452788 100644 --- a/amp.EtoForms/amp.EtoForms.csproj +++ b/amp.EtoForms/amp.EtoForms.csproj @@ -22,6 +22,12 @@ OSX + + + $(ProjectDir)\FormMain.cs + + + @@ -64,11 +70,6 @@ - - - - - True diff --git a/amp.Shared/Classes/UtilityOS.cs b/amp.Shared/Classes/UtilityOS.cs index 5a259b76..204399fc 100644 --- a/amp.Shared/Classes/UtilityOS.cs +++ b/amp.Shared/Classes/UtilityOS.cs @@ -99,4 +99,42 @@ public static T GetValueForOSNotNull(T windowsValue, T linuxValue, T macValue /// true if the current operating system is Linux; otherwise, false. // ReSharper disable once InconsistentNaming, OS is upper case public static bool IsLinuxOS => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + /// + /// The macOS operating system name in lower case ("macos"). + /// + public const string MacOSNameLowerCase = "macos"; + + /// + /// The Windows operating system name in lower case ("windows"). + /// + public const string WindowsNameLowerCase = "windows"; + + /// + /// The Linux operating system name in lower case ("linux"). + /// + public const string LinuxNameLowerCase = "linux"; + + + /// + /// Gets the operating system name in lower case. E.g. windows, linux, macos. + /// + /// The operating system name in lower case. + public static string OsNameLowerCase + { + get + { + if (IsMacOS) + { + return MacOSNameLowerCase; + } + + if (IsLinuxOS) + { + return LinuxNameLowerCase; + } + + return WindowsNameLowerCase; + } + } } \ No newline at end of file diff --git a/amp.Shared/Localization/UI.Designer.cs b/amp.Shared/Localization/UI.Designer.cs index fabbd590..39fbdee3 100644 --- a/amp.Shared/Localization/UI.Designer.cs +++ b/amp.Shared/Localization/UI.Designer.cs @@ -907,6 +907,15 @@ public static string QueueEntries { } } + /// + /// Looks up a localized string similar to Queue index. + /// + public static string QueueIndex { + get { + return ResourceManager.GetString("QueueIndex", resourceCulture); + } + } + /// /// Looks up a localized string similar to Queue name. /// diff --git a/amp.Shared/Localization/UI.fi.resx b/amp.Shared/Localization/UI.fi.resx index 96cbb763..596f37a5 100644 --- a/amp.Shared/Localization/UI.fi.resx +++ b/amp.Shared/Localization/UI.fi.resx @@ -533,4 +533,7 @@ lopputulokseen, jos muuttujalla on arvo esim. Artisti. ({@Ar /}). Aputiedostokansio + + Jonotusnumero + \ No newline at end of file diff --git a/amp.Shared/Localization/UI.resx b/amp.Shared/Localization/UI.resx index 0f17b6c4..215bf6a9 100644 --- a/amp.Shared/Localization/UI.resx +++ b/amp.Shared/Localization/UI.resx @@ -533,4 +533,7 @@ result if the actual variable e.g. Artist exists. ({@Ar /}). Help folder + + Queue index + \ No newline at end of file