From 63938b0139f961db9217b38f7a82676b2351d533 Mon Sep 17 00:00:00 2001 From: Brian Bicknell Date: Fri, 24 Jul 2026 09:49:58 -0400 Subject: [PATCH] Made repository methods asynchronous. Simplified playlist repository. Use playlist manager in the app, and left playlist repository to only be used by the playlist manager. --- Harmonia.Core/Data/FileRepository.cs | 24 ++++--- Harmonia.Core/Data/IRepository.cs | 3 +- Harmonia.Core/Data/JsonFileRepository.cs | 14 ++-- Harmonia.Core/Data/XMLFileRepository.cs | 6 +- .../Extensions/ServiceCollectionExtensions.cs | 12 ++++ Harmonia.Core/Player/AudioPlayer.cs | 11 ++- Harmonia.Core/Playlists/IPlaylistManager.cs | 5 +- .../Playlists/IPlaylistRepository.cs | 8 +-- Harmonia.Core/Playlists/PlaylistManager.cs | 49 ++++++++++++- Harmonia.Core/Playlists/PlaylistRepository.cs | 68 +++++++++---------- Harmonia.Tests/AudioPlayerTests.cs | 13 ++-- .../ViewModels/PlaybackBarViewModel.cs | 22 +++--- Harmonia.WinUI/App.xaml.cs | 5 +- .../ViewModels/PlaylistViewModel.cs | 37 +++++++--- 14 files changed, 181 insertions(+), 96 deletions(-) diff --git a/Harmonia.Core/Data/FileRepository.cs b/Harmonia.Core/Data/FileRepository.cs index 23e58e5..70789d9 100644 --- a/Harmonia.Core/Data/FileRepository.cs +++ b/Harmonia.Core/Data/FileRepository.cs @@ -8,10 +8,10 @@ public abstract class FileRepository : IRepository where TObje protected abstract string Extension { get; } protected abstract string GetNewFileName(); - protected abstract string Serialize(TObject playlist); - protected abstract TObject Deserialize(Stream stream); + protected abstract Task SerializeAsync(TObject playlist); + protected abstract Task DeserializeAsync(Stream stream); - public FileRepository() + public async Task InitializeAsync() { if (string.IsNullOrWhiteSpace(DirectoryName)) return; @@ -23,7 +23,7 @@ public abstract class FileRepository : IRepository where TObje return; List fileNames = GetAllFileNames(); - LoadFileNamesIntoMap(fileNames); + await LoadFileNamesIntoMapAsync(fileNames); } private List GetAllFileNames() @@ -36,7 +36,7 @@ public abstract class FileRepository : IRepository where TObje return [.. fileInfoList.Select(fileInfo => fileInfo.FullName)]; } - private void LoadFileNamesIntoMap(List fileNames) + private async Task LoadFileNamesIntoMapAsync(List fileNames) { foreach (var fileName in fileNames) { @@ -44,7 +44,7 @@ public abstract class FileRepository : IRepository where TObje try { - TObject obj = Deserialize(textReader.BaseStream); + TObject obj = await DeserializeAsync(textReader.BaseStream); _fileNameMap.Add(obj, fileName); } catch (Exception) @@ -59,9 +59,9 @@ public abstract class FileRepository : IRepository where TObje return [.. _fileNameMap.Keys]; } - public void Save(TObject obj) + public async Task SaveAsync(TObject obj) { - string serializedObject = Serialize(obj); + string serializedObject = await SerializeAsync(obj); string fileName = Path.Combine(DirectoryName, GetFileName(obj)); string? path = Path.GetDirectoryName(fileName); @@ -72,8 +72,10 @@ public abstract class FileRepository : IRepository where TObje if (Directory.Exists(path) == false) Directory.CreateDirectory(path); - using TextWriter textWriter = new StreamWriter(fileName); - textWriter.Write(serializedObject); + //using TextWriter textWriter = new StreamWriter(fileName); + //await textWriter.WriteAsync(serializedObject); + + await File.WriteAllTextAsync(fileName, serializedObject); } private string GetFileName(TObject obj) @@ -91,6 +93,8 @@ public abstract class FileRepository : IRepository where TObje { string fileName = Path.Combine(DirectoryName, GetFileName(obj)); + _fileNameMap.Remove(obj); + if (File.Exists(fileName)) File.Delete(fileName); } diff --git a/Harmonia.Core/Data/IRepository.cs b/Harmonia.Core/Data/IRepository.cs index c7a09ba..68aa3a2 100644 --- a/Harmonia.Core/Data/IRepository.cs +++ b/Harmonia.Core/Data/IRepository.cs @@ -2,7 +2,8 @@ public interface IRepository { + Task InitializeAsync(); List Get(); - void Save(TObject value); + Task SaveAsync(TObject value); void Delete(TObject value); } \ No newline at end of file diff --git a/Harmonia.Core/Data/JsonFileRepository.cs b/Harmonia.Core/Data/JsonFileRepository.cs index 9b655fc..ba463cc 100644 --- a/Harmonia.Core/Data/JsonFileRepository.cs +++ b/Harmonia.Core/Data/JsonFileRepository.cs @@ -14,13 +14,19 @@ public abstract class JsonFileRepository : FileRepository wher protected override string Extension => "json"; - protected override TObject Deserialize(Stream stream) + protected override async Task DeserializeAsync(Stream stream) { - return JsonSerializer.Deserialize(stream) ?? new(); + return await JsonSerializer.DeserializeAsync(stream) ?? new(); } - protected override string Serialize(TObject obj) + protected override async Task SerializeAsync(TObject obj) { - return JsonSerializer.Serialize(obj, _options); + using MemoryStream memoryStream = new(); + await JsonSerializer.SerializeAsync(memoryStream, obj, _options); + + memoryStream.Position = 0; + + using StreamReader reader = new(memoryStream); + return await reader.ReadToEndAsync(); } } \ No newline at end of file diff --git a/Harmonia.Core/Data/XMLFileRepository.cs b/Harmonia.Core/Data/XMLFileRepository.cs index 0b84afd..fb347b3 100644 --- a/Harmonia.Core/Data/XMLFileRepository.cs +++ b/Harmonia.Core/Data/XMLFileRepository.cs @@ -2,20 +2,20 @@ namespace Harmonia.Core.Data; -public abstract class XMLFileRepository : FileRepository where TObject : notnull, new() +public abstract class XmlFileRepository : FileRepository where TObject : notnull, new() { private readonly XmlSerializer _serializer = new(typeof(TObject)); protected override string Extension => "xml"; - protected override TObject Deserialize(Stream stream) + protected override async Task DeserializeAsync(Stream stream) { using TextReader textReader = new StreamReader(stream); return (TObject?)_serializer.Deserialize(textReader) ?? new(); } - protected override string Serialize(TObject obj) + protected override async Task SerializeAsync(TObject obj) { using TextWriter textWriter = new StringWriter(); diff --git a/Harmonia.Core/Extensions/ServiceCollectionExtensions.cs b/Harmonia.Core/Extensions/ServiceCollectionExtensions.cs index f1348a0..9ba35ab 100644 --- a/Harmonia.Core/Extensions/ServiceCollectionExtensions.cs +++ b/Harmonia.Core/Extensions/ServiceCollectionExtensions.cs @@ -24,4 +24,16 @@ public static class ServiceCollectionExtensions return services; } +} + +public static class ServiceProviderExtensions +{ + public static async Task InitializeHarmoniaAsync(this IServiceProvider serviceProvider) + { + IPlaylistRepository playlistRepository = serviceProvider.GetRequiredService(); + await playlistRepository.InitializeAsync(); + + IPlaylistManager playlistManager = serviceProvider.GetRequiredService(); + await playlistManager.InitializeAsync(); + } } \ No newline at end of file diff --git a/Harmonia.Core/Player/AudioPlayer.cs b/Harmonia.Core/Player/AudioPlayer.cs index c56c65d..0ce870c 100644 --- a/Harmonia.Core/Player/AudioPlayer.cs +++ b/Harmonia.Core/Player/AudioPlayer.cs @@ -7,7 +7,7 @@ namespace Harmonia.Core.Player; public class AudioPlayer : IAudioPlayer { private readonly IAudioEngine _audioEngine; - private readonly IPlaylistRepository _playlistRepository; + private readonly IPlaylistManager _playlistManager; private Playlist? _playlist; public Playlist? Playlist @@ -117,13 +117,13 @@ public class AudioPlayer : IAudioPlayer public event EventHandler? PlayingSongChanged; public event PropertyChangedEventHandler? PropertyChanged; - public AudioPlayer(IAudioEngine audioEngine, IPlaylistRepository playlistRepository) + public AudioPlayer(IAudioEngine audioEngine, IPlaylistManager playlistManager) { _audioEngine = audioEngine; _audioEngine.StreamFinished += OnAudioEngineStreamFinished; _audioEngine.StateChanged += OnMusicEngineStateChanged; - _playlistRepository = playlistRepository; + _playlistManager = playlistManager; } private async void OnAudioEngineStreamFinished(object? sender, EventArgs e) @@ -231,10 +231,7 @@ public class AudioPlayer : IAudioPlayer { if (Playlist == null || Playlist.Songs.Contains(song) == false) { - //Playlist? newPlaylist = _playlistRepository.GetPlaylist(song); - - Playlist? newPlaylist = _playlistRepository.Get().FirstOrDefault(playlist => - playlist.Songs.Contains(song)); + Playlist? newPlaylist = _playlistManager.GetPlaylist(song); if (newPlaylist == null) return false; diff --git a/Harmonia.Core/Playlists/IPlaylistManager.cs b/Harmonia.Core/Playlists/IPlaylistManager.cs index 2d2f35e..5b46de3 100644 --- a/Harmonia.Core/Playlists/IPlaylistManager.cs +++ b/Harmonia.Core/Playlists/IPlaylistManager.cs @@ -2,10 +2,13 @@ public interface IPlaylistManager { + IReadOnlyList Playlists { get; } Playlist? CurrentPlaylist { get; set; } - void AddPlaylist(); + Task InitializeAsync(); + Task AddPlaylistAsync(); void RemovePlaylist(Playlist playlist); + Playlist? GetPlaylist(PlaylistSong playlistSong); event EventHandler? CurrentPlaylistChanged; event EventHandler PlaylistAdded; diff --git a/Harmonia.Core/Playlists/IPlaylistRepository.cs b/Harmonia.Core/Playlists/IPlaylistRepository.cs index af93dcd..724881f 100644 --- a/Harmonia.Core/Playlists/IPlaylistRepository.cs +++ b/Harmonia.Core/Playlists/IPlaylistRepository.cs @@ -5,9 +5,9 @@ namespace Harmonia.Core.Playlists; public interface IPlaylistRepository : IRepository { Playlist? GetPlaylist(PlaylistSong playlistSong); - void AddPlaylist(); - void RemovePlaylist(Playlist playlist); + //void AddPlaylist(); + //void RemovePlaylist(Playlist playlist); - event EventHandler PlaylistAdded; - event EventHandler PlaylistRemoved; + //event EventHandler PlaylistAdded; + //event EventHandler PlaylistRemoved; } \ No newline at end of file diff --git a/Harmonia.Core/Playlists/PlaylistManager.cs b/Harmonia.Core/Playlists/PlaylistManager.cs index 84ea04f..41158e8 100644 --- a/Harmonia.Core/Playlists/PlaylistManager.cs +++ b/Harmonia.Core/Playlists/PlaylistManager.cs @@ -1,7 +1,12 @@ namespace Harmonia.Core.Playlists; -public class PlaylistManager(IPlaylistRepository playlistRepository) : IPlaylistManager +public class PlaylistManager : IPlaylistManager { + private readonly IPlaylistRepository playlistRepository; + private readonly List _playlists = []; + + public IReadOnlyList Playlists => _playlists; + private Playlist? _currentPlaylist; public Playlist? CurrentPlaylist { @@ -20,22 +25,60 @@ public class PlaylistManager(IPlaylistRepository playlistRepository) : IPlaylist public event EventHandler? PlaylistAdded; public event EventHandler? PlaylistRemoved; - public void AddPlaylist() + public PlaylistManager(IPlaylistRepository playlistRepository) + { + this.playlistRepository = playlistRepository; + } + + public async Task InitializeAsync() + { + _playlists.AddRange(playlistRepository.Get()); + + foreach (Playlist playlist in _playlists) + { + playlist.PlaylistUpdated += OnPlaylistUpdated; + } + + CurrentPlaylist = _playlists.Count > 0 ? _playlists[0] : await AddPlaylistAsync(); + } + + public async Task AddPlaylistAsync() { Playlist playlist = new() { Name = "New Playlist" }; - playlistRepository.Save(playlist); + playlist.PlaylistUpdated += OnPlaylistUpdated; + _playlists.Add(playlist); + + await playlistRepository.SaveAsync(playlist); PlaylistAdded?.Invoke(this, new(playlist)); + + return playlist; } public void RemovePlaylist(Playlist playlist) { + playlist.PlaylistUpdated -= OnPlaylistUpdated; + _playlists.Remove(playlist); + playlistRepository.Delete(playlist); PlaylistRemoved?.Invoke(this, new(playlist)); } + + public Playlist? GetPlaylist(PlaylistSong playlistSong) + { + return _playlists.FirstOrDefault(playlist => playlist.Songs.Any(song => song.UID == playlistSong.UID)); + } + + private async void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e) + { + if (sender is not Playlist playlist) + return; + + await playlistRepository.SaveAsync(playlist); + } } \ No newline at end of file diff --git a/Harmonia.Core/Playlists/PlaylistRepository.cs b/Harmonia.Core/Playlists/PlaylistRepository.cs index a0094d3..800ce37 100644 --- a/Harmonia.Core/Playlists/PlaylistRepository.cs +++ b/Harmonia.Core/Playlists/PlaylistRepository.cs @@ -6,26 +6,26 @@ public class PlaylistRepository : JsonFileRepository, IPlaylistReposit { protected override string DirectoryName => Path.Combine("Playlists"); - public PlaylistRepository() - { - List playlists = Get(); + //public PlaylistRepository() + //{ + // List playlists = Get(); - foreach (Playlist playlist in playlists) - { - playlist.PlaylistUpdated += OnPlaylistUpdated; - } + // foreach (Playlist playlist in playlists) + // { + // playlist.PlaylistUpdated += OnPlaylistUpdated; + // } - if (playlists.Count == 0) - AddPlaylist(); - } + // if (playlists.Count == 0) + // AddPlaylist(); + //} - private void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e) - { - if (sender is not Playlist playlist) - return; + //private void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e) + //{ + // if (sender is not Playlist playlist) + // return; - Save(playlist); - } + // Save(playlist); + //} public Playlist? GetPlaylist(PlaylistSong playlistSong) { @@ -46,27 +46,27 @@ public class PlaylistRepository : JsonFileRepository, IPlaylistReposit throw new Exception("Unable to determine new fileName"); } - public event EventHandler? PlaylistAdded; - public event EventHandler? PlaylistRemoved; + //public event EventHandler? PlaylistAdded; + //public event EventHandler? PlaylistRemoved; - public void AddPlaylist() - { - Playlist playlist = new() - { - Name = "New Playlist" - }; + //public void AddPlaylist() + //{ + // Playlist playlist = new() + // { + // Name = "New Playlist" + // }; - playlist.PlaylistUpdated += OnPlaylistUpdated; + // playlist.PlaylistUpdated += OnPlaylistUpdated; - Save(playlist); - PlaylistAdded?.Invoke(this, new(playlist)); - } + // Save(playlist); + // PlaylistAdded?.Invoke(this, new(playlist)); + //} - public void RemovePlaylist(Playlist playlist) - { - playlist.PlaylistUpdated -= OnPlaylistUpdated; + //public void RemovePlaylist(Playlist playlist) + //{ + // playlist.PlaylistUpdated -= OnPlaylistUpdated; - Delete(playlist); - PlaylistRemoved?.Invoke(this, new(playlist)); - } + // Delete(playlist); + // PlaylistRemoved?.Invoke(this, new(playlist)); + //} } \ No newline at end of file diff --git a/Harmonia.Tests/AudioPlayerTests.cs b/Harmonia.Tests/AudioPlayerTests.cs index 7ed12d9..a66d3e8 100644 --- a/Harmonia.Tests/AudioPlayerTests.cs +++ b/Harmonia.Tests/AudioPlayerTests.cs @@ -8,8 +8,8 @@ using Shouldly; namespace Harmonia.Tests; -internal class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistRepository playlistRepository) - : AudioPlayer(audioEngine, playlistRepository) +internal class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistManager playlistManager) + : AudioPlayer(audioEngine, playlistManager) { internal void SetPlaylist(Playlist playlist) { @@ -28,7 +28,7 @@ internal class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistRepository pla public class AudioPlayerTests { private readonly IAudioEngine _audioEngine; - private readonly IPlaylistRepository _playlistRepository; + private readonly IPlaylistManager _playlistManager; private readonly PlaylistSong[] _playlistSongs; private readonly TestAudioPlayer _audioPlayer; @@ -63,10 +63,11 @@ public class AudioPlayerTests _playlistSongs = [.. playlist.Songs]; - _playlistRepository = Substitute.For(); - _playlistRepository.Get().Returns([playlist]); + _playlistManager = Substitute.For(); + _playlistManager.Playlists.Returns([playlist]); + _playlistManager.GetPlaylist(Arg.Any()).Returns(playlist); - _audioPlayer = new TestAudioPlayer(_audioEngine, _playlistRepository); + _audioPlayer = new TestAudioPlayer(_audioEngine, _playlistManager); _audioPlayer.SetPlaylist(playlist); } diff --git a/Harmonia.UI/ViewModels/PlaybackBarViewModel.cs b/Harmonia.UI/ViewModels/PlaybackBarViewModel.cs index e072c54..3f1a4e6 100644 --- a/Harmonia.UI/ViewModels/PlaybackBarViewModel.cs +++ b/Harmonia.UI/ViewModels/PlaybackBarViewModel.cs @@ -211,21 +211,21 @@ public partial class PlaybackBarViewModel : ViewModelBase, IDisposable _timer = new(TimeSpan.FromMilliseconds(100), DispatcherPriority.Default, TickTock); - Task.Run(() => PlayDemoSong(playlistRepository)); + //Task.Run(() => PlayDemoSong(playlistRepository)); } - private async Task PlayDemoSong(IPlaylistRepository playlistRepository) - { - if (playlistRepository.Get().Count == 0) - { - playlistRepository.AddPlaylist(); - } + //private async Task PlayDemoSong(IPlaylistManager playlistManager) + //{ + // if (playlistRepository.Get().Count == 0) + // { + // playlistRepository.AddPlaylist(); + // } - Playlist playlist = playlistRepository.Get().First(); + // Playlist playlist = playlistRepository.Get().First(); - if (playlist.Songs.Count > 0) - await _audioPlayer.LoadAsync(playlist.Songs[0], PlaybackMode.LoadOnly); - } + // if (playlist.Songs.Count > 0) + // await _audioPlayer.LoadAsync(playlist.Songs[0], PlaybackMode.LoadOnly); + //} private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e) { diff --git a/Harmonia.WinUI/App.xaml.cs b/Harmonia.WinUI/App.xaml.cs index b2e55c9..c1df5b9 100644 --- a/Harmonia.WinUI/App.xaml.cs +++ b/Harmonia.WinUI/App.xaml.cs @@ -1,4 +1,5 @@ using Harmonia.Core.Extensions; +using Harmonia.Core.Playlists; using Harmonia.WinUI.Caching; using Harmonia.WinUI.Storage; using Harmonia.WinUI.ViewModels; @@ -38,8 +39,10 @@ public partial class App : Application InitializeComponent(); } - protected override void OnLaunched(LaunchActivatedEventArgs args) + protected override async void OnLaunched(LaunchActivatedEventArgs args) { + await ServiceProvider.InitializeHarmoniaAsync(); + _mainWindow = ServiceProvider.GetRequiredService(); _mainWindow.Activate(); } diff --git a/Harmonia.WinUI/ViewModels/PlaylistViewModel.cs b/Harmonia.WinUI/ViewModels/PlaylistViewModel.cs index b01a0ed..5320bef 100644 --- a/Harmonia.WinUI/ViewModels/PlaylistViewModel.cs +++ b/Harmonia.WinUI/ViewModels/PlaylistViewModel.cs @@ -30,6 +30,7 @@ namespace Harmonia.WinUI.ViewModels; public partial class PlaylistViewModel : ViewModelBase { + private readonly IPlaylistManager _playlistManager; private readonly IAudioPlayer _audioPlayer; private readonly IAudioImageCache _audioImageCache; private readonly IAudioBitmapImageCache _audioBitmapImageCache; @@ -133,8 +134,11 @@ public partial class PlaylistViewModel : ViewModelBase IAudioFileScanner audioFileScanner, IAudioEngine audioEngine, IStorageProvider storageProvider, - IPlaylistRepository playlistRepository) + IPlaylistManager playlistManager) { + _playlistManager = playlistManager; + //_playlistManager.CurrentPlaylistChanged += OnPlaylistChanged; + _audioPlayer = audioPlayer; _audioPlayer.PlaylistChanged += OnPlaylistChanged; _audioPlayer.PlayingSongChanged += OnPlayingSongChanged; @@ -148,8 +152,11 @@ public partial class PlaylistViewModel : ViewModelBase FilteredPlaylistSongs.CollectionChanged += OnFilteredPlaylistSongsCollectionChanged; + Playlist = _playlistManager.CurrentPlaylist; // Testing + UpdatePlaylistSongs(Playlist); + // Testing - Task.Run(() => PlayDemoSong(playlistRepository)); + //Task.Run(() => PlayDemoSong(playlistRepository)); } private void OnFilteredPlaylistSongsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -160,17 +167,25 @@ public partial class PlaylistViewModel : ViewModelBase int x = 1; } - private async Task PlayDemoSong(IPlaylistRepository playlistRepository) + //private async Task PlayDemoSong(IPlaylistRepository playlistRepository) + //{ + // if (playlistRepository.Get().Count == 0) + // { + // playlistRepository.AddPlaylist(); + // } + + // Playlist playlist = playlistRepository.Get().First(); + + // if (playlist.Songs.Count > 0) + // await _audioPlayer.LoadAsync(playlist.Songs[0], PlaybackMode.LoadOnly); + //} + + private void UpdatePlaylistSongs(Playlist? playlist) { - if (playlistRepository.Get().Count == 0) - { - playlistRepository.AddPlaylist(); - } + PlaylistSong[] playlistSongs = playlist?.Songs.ToArray() ?? []; - Playlist playlist = playlistRepository.Get().First(); - - if (playlist.Songs.Count > 0) - await _audioPlayer.LoadAsync(playlist.Songs[0], PlaybackMode.LoadOnly); + PlaylistSongs = [.. playlistSongs]; + UpdateFilteredSongs(); } private void OnPlaylistChanged(object? sender, EventArgs e)