Implemented tag refresh on a playlist. Made audio file scanning truly asynchronous. Added basic playlist sorting functionality.

This commit is contained in:
2026-08-02 23:34:04 -04:00
parent 7aaf36f7b2
commit 99978c62c4
7 changed files with 191 additions and 36 deletions

View File

@@ -15,13 +15,13 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.ApplicationModel.DataTransfer;
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
using DispatcherQueueTimer = Microsoft.UI.Dispatching.DispatcherQueueTimer;
@@ -103,22 +103,50 @@ public partial class PlaylistDetailViewModel : ViewModelBase
set
{
SetProperty(ref _selectedPlaylistSongs, value);
NotifySelectionDependentCommands();
}
}
public ICommand PlaySongCommand => new AsyncRelayCommand(PlaySongAsync, AreSongsSelected);
public ICommand NewPlaylistCommand => new AsyncRelayCommand(NewPlaylistAsync);
public ICommand AddFilesCommand => new AsyncRelayCommand(AddFilesAsync);
public ICommand AddFolderCommand => new AsyncRelayCommand(AddFolderAsync);
public ICommand RemoveSongsCommand => new RelayCommand(RemoveSongs, AreSongsSelected);
public ICommand CutSongsCommand => new RelayCommand(CutSongs, AreSongsSelected);
public ICommand CopySongsCommand => new RelayCommand(CopySongs, AreSongsSelected);
public ICommand PasteSongsCommand => new AsyncRelayCommand(PasteSongsAsync, CanPasteSongs);
public ICommand OpenFileLocationCommand => new RelayCommand(OpenFileLocation, AreSongsSelected);
public ICommand RefreshTagsCommand => new RelayCommand(RefreshTags);
public ICommand RemoveMissingSongsCommand => new RelayCommand(RemoveMissingSongs);
public ICommand RemoveDuplicateSongsCommand => new RelayCommand(RemoveDuplicateSongs);
public ICommand DeletePlaylistCommand => new AsyncRelayCommand(DeletePlaylistAsync);
public SortItem[] SortItems { get; } =
[
new SortItem
{
Name = "Album - Disc # - Track # - Title - File Name",
SortOptions =
[
new(nameof(Song.Album), ListSortDirection.Ascending),
new(nameof(Song.DiscNumber), ListSortDirection.Ascending),
new(nameof(Song.TrackNumber), ListSortDirection.Ascending),
new(nameof(Song.Title), ListSortDirection.Ascending),
new(nameof(Song.FileName), ListSortDirection.Ascending)
]
},
new SortItem
{
Name = "Title - File Name",
SortOptions =
[
new(nameof(Song.Title), ListSortDirection.Ascending),
new(nameof(Song.FileName), ListSortDirection.Ascending)
]
}
];
public IAsyncRelayCommand PlaySongCommand { get; }
public IAsyncRelayCommand NewPlaylistCommand { get; }
public IAsyncRelayCommand AddFilesCommand { get; }
public IAsyncRelayCommand AddFolderCommand { get; }
public IRelayCommand RemoveSongsCommand { get; }
public IRelayCommand CutSongsCommand { get; }
public IRelayCommand CopySongsCommand { get; }
public IAsyncRelayCommand PasteSongsCommand { get; }
public IRelayCommand OpenFileLocationCommand { get; }
public IAsyncRelayCommand RefreshTagsCommand { get; }
public IRelayCommand RemoveMissingSongsCommand { get; }
public IRelayCommand RemoveDuplicateSongsCommand { get; }
public IAsyncRelayCommand DeletePlaylistCommand { get; }
public IRelayCommand<SortItem> SortAllCommand { get; }
public IRelayCommand<SortItem> SortSelectedCommand { get; }
public bool IsUserUpdating { get; set; }
private bool _isUserInitiatingSongChange;
@@ -152,12 +180,39 @@ public partial class PlaylistDetailViewModel : ViewModelBase
_messageService = messageService;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
PlaySongCommand = new AsyncRelayCommand(PlaySongAsync, AreSongsSelected);
NewPlaylistCommand = new AsyncRelayCommand(NewPlaylistAsync);
AddFilesCommand = new AsyncRelayCommand(AddFilesAsync);
AddFolderCommand = new AsyncRelayCommand(AddFolderAsync);
RemoveSongsCommand = new RelayCommand(RemoveSongs, AreSongsSelected);
CutSongsCommand = new RelayCommand(CutSongs, AreSongsSelected);
CopySongsCommand = new RelayCommand(CopySongs, AreSongsSelected);
PasteSongsCommand = new AsyncRelayCommand(PasteSongsAsync, CanPasteSongs);
OpenFileLocationCommand = new RelayCommand(OpenFileLocation, AreSongsSelected);
RefreshTagsCommand = new AsyncRelayCommand(RefreshTagsAsync);
RemoveMissingSongsCommand = new RelayCommand(RemoveMissingSongs);
RemoveDuplicateSongsCommand = new RelayCommand(RemoveDuplicateSongs);
DeletePlaylistCommand = new AsyncRelayCommand(DeletePlaylistAsync);
SortAllCommand = new RelayCommand<SortItem>(SortAllSongs);
SortSelectedCommand = new RelayCommand<SortItem>(SortSelectedSongs, _ => AreMultipleSongsSelected());
FilteredPlaylistSongs.CollectionChanged += OnFilteredPlaylistSongsCollectionChanged;
Playlist = _playlistManager.CurrentPlaylist; // Testing
UpdateFilteredSongs();
}
private void NotifySelectionDependentCommands()
{
PlaySongCommand.NotifyCanExecuteChanged();
RemoveSongsCommand.NotifyCanExecuteChanged();
CutSongsCommand.NotifyCanExecuteChanged();
CopySongsCommand.NotifyCanExecuteChanged();
PasteSongsCommand.NotifyCanExecuteChanged();
OpenFileLocationCommand.NotifyCanExecuteChanged();
SortSelectedCommand.NotifyCanExecuteChanged();
}
private void OnFilteredPlaylistSongsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (IsUserUpdating == false)
@@ -212,6 +267,7 @@ public partial class PlaylistDetailViewModel : ViewModelBase
{
case PlaylistUpdateAction.Add:
case PlaylistUpdateAction.Remove:
case PlaylistUpdateAction.Refresh:
_dispatcherQueue.TryEnqueue(UpdateFilteredSongs);
break;
case PlaylistUpdateAction.Move:
@@ -381,6 +437,11 @@ public partial class PlaylistDetailViewModel : ViewModelBase
return SelectedPlaylistSongs.Count > 0;
}
private bool AreMultipleSongsSelected()
{
return SelectedPlaylistSongs.Count > 1;
}
private async Task NewPlaylistAsync(CancellationToken cancellationToken)
{
Playlist playlist = await _playlistManager.AddPlaylistAsync();
@@ -538,9 +599,23 @@ public partial class PlaylistDetailViewModel : ViewModelBase
Process.Start("explorer.exe", argument);
}
private void RefreshTags()
private async Task RefreshTagsAsync(CancellationToken cancellationToken)
{
//Playlist?.RefreshTags();
if (Playlist is null)
return;
// TODO: Have different method for selected songs versus all songs in the playlist
PlaylistSong[] targets = SelectedPlaylistSongs.Count > 0
? [.. SelectedPlaylistSongs]
: [.. Playlist.Songs];
string[] fileNames = [.. targets
.Select(ps => ps.Song.FileName)
.Distinct(StringComparer.OrdinalIgnoreCase)];
Song[] songs = await _audioFileScanner.GetSongsAsync(fileNames, cancellationToken);
Playlist.ImportTags(songs);
}
private void RemoveMissingSongs()
@@ -578,5 +653,30 @@ public partial class PlaylistDetailViewModel : ViewModelBase
}
}
private void SortAllSongs(SortItem? sortItem)
{
if (Playlist == null || sortItem == null)
return;
Playlist.SortSongs([.. Playlist.Songs], [.. sortItem.SortOptions]);
}
private void SortSelectedSongs(SortItem? sortItem)
{
if (Playlist == null || sortItem == null)
return;
if (SelectedPlaylistSongs.Count < 2)
return;
Playlist.SortSongs([.. SelectedPlaylistSongs], [.. sortItem.SortOptions]);
}
#endregion
}
public class SortItem
{
public string Name { get; set; } = string.Empty;
public IReadOnlyCollection<SortOption> SortOptions { get; set; } = [];
}