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.

This commit is contained in:
2026-07-24 09:49:58 -04:00
parent 0acbab1215
commit 63938b0139
14 changed files with 181 additions and 96 deletions

View File

@@ -8,10 +8,10 @@ public abstract class FileRepository<TObject> : IRepository<TObject> where TObje
protected abstract string Extension { get; } protected abstract string Extension { get; }
protected abstract string GetNewFileName(); protected abstract string GetNewFileName();
protected abstract string Serialize(TObject playlist); protected abstract Task<string> SerializeAsync(TObject playlist);
protected abstract TObject Deserialize(Stream stream); protected abstract Task<TObject> DeserializeAsync(Stream stream);
public FileRepository() public async Task InitializeAsync()
{ {
if (string.IsNullOrWhiteSpace(DirectoryName)) if (string.IsNullOrWhiteSpace(DirectoryName))
return; return;
@@ -23,7 +23,7 @@ public abstract class FileRepository<TObject> : IRepository<TObject> where TObje
return; return;
List<string> fileNames = GetAllFileNames(); List<string> fileNames = GetAllFileNames();
LoadFileNamesIntoMap(fileNames); await LoadFileNamesIntoMapAsync(fileNames);
} }
private List<string> GetAllFileNames() private List<string> GetAllFileNames()
@@ -36,7 +36,7 @@ public abstract class FileRepository<TObject> : IRepository<TObject> where TObje
return [.. fileInfoList.Select(fileInfo => fileInfo.FullName)]; return [.. fileInfoList.Select(fileInfo => fileInfo.FullName)];
} }
private void LoadFileNamesIntoMap(List<string> fileNames) private async Task LoadFileNamesIntoMapAsync(List<string> fileNames)
{ {
foreach (var fileName in fileNames) foreach (var fileName in fileNames)
{ {
@@ -44,7 +44,7 @@ public abstract class FileRepository<TObject> : IRepository<TObject> where TObje
try try
{ {
TObject obj = Deserialize(textReader.BaseStream); TObject obj = await DeserializeAsync(textReader.BaseStream);
_fileNameMap.Add(obj, fileName); _fileNameMap.Add(obj, fileName);
} }
catch (Exception) catch (Exception)
@@ -59,9 +59,9 @@ public abstract class FileRepository<TObject> : IRepository<TObject> where TObje
return [.. _fileNameMap.Keys]; 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 fileName = Path.Combine(DirectoryName, GetFileName(obj));
string? path = Path.GetDirectoryName(fileName); string? path = Path.GetDirectoryName(fileName);
@@ -72,8 +72,10 @@ public abstract class FileRepository<TObject> : IRepository<TObject> where TObje
if (Directory.Exists(path) == false) if (Directory.Exists(path) == false)
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
using TextWriter textWriter = new StreamWriter(fileName); //using TextWriter textWriter = new StreamWriter(fileName);
textWriter.Write(serializedObject); //await textWriter.WriteAsync(serializedObject);
await File.WriteAllTextAsync(fileName, serializedObject);
} }
private string GetFileName(TObject obj) private string GetFileName(TObject obj)
@@ -91,6 +93,8 @@ public abstract class FileRepository<TObject> : IRepository<TObject> where TObje
{ {
string fileName = Path.Combine(DirectoryName, GetFileName(obj)); string fileName = Path.Combine(DirectoryName, GetFileName(obj));
_fileNameMap.Remove(obj);
if (File.Exists(fileName)) if (File.Exists(fileName))
File.Delete(fileName); File.Delete(fileName);
} }

View File

@@ -2,7 +2,8 @@
public interface IRepository<TObject> public interface IRepository<TObject>
{ {
Task InitializeAsync();
List<TObject> Get(); List<TObject> Get();
void Save(TObject value); Task SaveAsync(TObject value);
void Delete(TObject value); void Delete(TObject value);
} }

View File

@@ -14,13 +14,19 @@ public abstract class JsonFileRepository<TObject> : FileRepository<TObject> wher
protected override string Extension => "json"; protected override string Extension => "json";
protected override TObject Deserialize(Stream stream) protected override async Task<TObject> DeserializeAsync(Stream stream)
{ {
return JsonSerializer.Deserialize<TObject>(stream) ?? new(); return await JsonSerializer.DeserializeAsync<TObject>(stream) ?? new();
} }
protected override string Serialize(TObject obj) protected override async Task<string> 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();
} }
} }

View File

@@ -2,20 +2,20 @@
namespace Harmonia.Core.Data; namespace Harmonia.Core.Data;
public abstract class XMLFileRepository<TObject> : FileRepository<TObject> where TObject : notnull, new() public abstract class XmlFileRepository<TObject> : FileRepository<TObject> where TObject : notnull, new()
{ {
private readonly XmlSerializer _serializer = new(typeof(TObject)); private readonly XmlSerializer _serializer = new(typeof(TObject));
protected override string Extension => "xml"; protected override string Extension => "xml";
protected override TObject Deserialize(Stream stream) protected override async Task<TObject> DeserializeAsync(Stream stream)
{ {
using TextReader textReader = new StreamReader(stream); using TextReader textReader = new StreamReader(stream);
return (TObject?)_serializer.Deserialize(textReader) ?? new(); return (TObject?)_serializer.Deserialize(textReader) ?? new();
} }
protected override string Serialize(TObject obj) protected override async Task<string> SerializeAsync(TObject obj)
{ {
using TextWriter textWriter = new StringWriter(); using TextWriter textWriter = new StringWriter();

View File

@@ -25,3 +25,15 @@ public static class ServiceCollectionExtensions
return services; return services;
} }
} }
public static class ServiceProviderExtensions
{
public static async Task InitializeHarmoniaAsync(this IServiceProvider serviceProvider)
{
IPlaylistRepository playlistRepository = serviceProvider.GetRequiredService<IPlaylistRepository>();
await playlistRepository.InitializeAsync();
IPlaylistManager playlistManager = serviceProvider.GetRequiredService<IPlaylistManager>();
await playlistManager.InitializeAsync();
}
}

View File

@@ -7,7 +7,7 @@ namespace Harmonia.Core.Player;
public class AudioPlayer : IAudioPlayer public class AudioPlayer : IAudioPlayer
{ {
private readonly IAudioEngine _audioEngine; private readonly IAudioEngine _audioEngine;
private readonly IPlaylistRepository _playlistRepository; private readonly IPlaylistManager _playlistManager;
private Playlist? _playlist; private Playlist? _playlist;
public Playlist? Playlist public Playlist? Playlist
@@ -117,13 +117,13 @@ public class AudioPlayer : IAudioPlayer
public event EventHandler? PlayingSongChanged; public event EventHandler? PlayingSongChanged;
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
public AudioPlayer(IAudioEngine audioEngine, IPlaylistRepository playlistRepository) public AudioPlayer(IAudioEngine audioEngine, IPlaylistManager playlistManager)
{ {
_audioEngine = audioEngine; _audioEngine = audioEngine;
_audioEngine.StreamFinished += OnAudioEngineStreamFinished; _audioEngine.StreamFinished += OnAudioEngineStreamFinished;
_audioEngine.StateChanged += OnMusicEngineStateChanged; _audioEngine.StateChanged += OnMusicEngineStateChanged;
_playlistRepository = playlistRepository; _playlistManager = playlistManager;
} }
private async void OnAudioEngineStreamFinished(object? sender, EventArgs e) private async void OnAudioEngineStreamFinished(object? sender, EventArgs e)
@@ -231,10 +231,7 @@ public class AudioPlayer : IAudioPlayer
{ {
if (Playlist == null || Playlist.Songs.Contains(song) == false) if (Playlist == null || Playlist.Songs.Contains(song) == false)
{ {
//Playlist? newPlaylist = _playlistRepository.GetPlaylist(song); Playlist? newPlaylist = _playlistManager.GetPlaylist(song);
Playlist? newPlaylist = _playlistRepository.Get().FirstOrDefault(playlist =>
playlist.Songs.Contains(song));
if (newPlaylist == null) if (newPlaylist == null)
return false; return false;

View File

@@ -2,10 +2,13 @@
public interface IPlaylistManager public interface IPlaylistManager
{ {
IReadOnlyList<Playlist> Playlists { get; }
Playlist? CurrentPlaylist { get; set; } Playlist? CurrentPlaylist { get; set; }
void AddPlaylist(); Task InitializeAsync();
Task<Playlist> AddPlaylistAsync();
void RemovePlaylist(Playlist playlist); void RemovePlaylist(Playlist playlist);
Playlist? GetPlaylist(PlaylistSong playlistSong);
event EventHandler? CurrentPlaylistChanged; event EventHandler? CurrentPlaylistChanged;
event EventHandler<PlaylistAddedEventArgs> PlaylistAdded; event EventHandler<PlaylistAddedEventArgs> PlaylistAdded;

View File

@@ -5,9 +5,9 @@ namespace Harmonia.Core.Playlists;
public interface IPlaylistRepository : IRepository<Playlist> public interface IPlaylistRepository : IRepository<Playlist>
{ {
Playlist? GetPlaylist(PlaylistSong playlistSong); Playlist? GetPlaylist(PlaylistSong playlistSong);
void AddPlaylist(); //void AddPlaylist();
void RemovePlaylist(Playlist playlist); //void RemovePlaylist(Playlist playlist);
event EventHandler<PlaylistAddedEventArgs> PlaylistAdded; //event EventHandler<PlaylistAddedEventArgs> PlaylistAdded;
event EventHandler<PlaylistRemovedEventArgs> PlaylistRemoved; //event EventHandler<PlaylistRemovedEventArgs> PlaylistRemoved;
} }

View File

@@ -1,7 +1,12 @@
namespace Harmonia.Core.Playlists; namespace Harmonia.Core.Playlists;
public class PlaylistManager(IPlaylistRepository playlistRepository) : IPlaylistManager public class PlaylistManager : IPlaylistManager
{ {
private readonly IPlaylistRepository playlistRepository;
private readonly List<Playlist> _playlists = [];
public IReadOnlyList<Playlist> Playlists => _playlists;
private Playlist? _currentPlaylist; private Playlist? _currentPlaylist;
public Playlist? CurrentPlaylist public Playlist? CurrentPlaylist
{ {
@@ -20,22 +25,60 @@ public class PlaylistManager(IPlaylistRepository playlistRepository) : IPlaylist
public event EventHandler<PlaylistAddedEventArgs>? PlaylistAdded; public event EventHandler<PlaylistAddedEventArgs>? PlaylistAdded;
public event EventHandler<PlaylistRemovedEventArgs>? PlaylistRemoved; public event EventHandler<PlaylistRemovedEventArgs>? 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<Playlist> AddPlaylistAsync()
{ {
Playlist playlist = new() Playlist playlist = new()
{ {
Name = "New Playlist" Name = "New Playlist"
}; };
playlistRepository.Save(playlist); playlist.PlaylistUpdated += OnPlaylistUpdated;
_playlists.Add(playlist);
await playlistRepository.SaveAsync(playlist);
PlaylistAdded?.Invoke(this, new(playlist)); PlaylistAdded?.Invoke(this, new(playlist));
return playlist;
} }
public void RemovePlaylist(Playlist playlist) public void RemovePlaylist(Playlist playlist)
{ {
playlist.PlaylistUpdated -= OnPlaylistUpdated;
_playlists.Remove(playlist);
playlistRepository.Delete(playlist); playlistRepository.Delete(playlist);
PlaylistRemoved?.Invoke(this, new(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);
}
} }

View File

@@ -6,26 +6,26 @@ public class PlaylistRepository : JsonFileRepository<Playlist>, IPlaylistReposit
{ {
protected override string DirectoryName => Path.Combine("Playlists"); protected override string DirectoryName => Path.Combine("Playlists");
public PlaylistRepository() //public PlaylistRepository()
{ //{
List<Playlist> playlists = Get(); // List<Playlist> playlists = Get();
foreach (Playlist playlist in playlists) // foreach (Playlist playlist in playlists)
{ // {
playlist.PlaylistUpdated += OnPlaylistUpdated; // playlist.PlaylistUpdated += OnPlaylistUpdated;
} // }
if (playlists.Count == 0) // if (playlists.Count == 0)
AddPlaylist(); // AddPlaylist();
} //}
private void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e) //private void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e)
{ //{
if (sender is not Playlist playlist) // if (sender is not Playlist playlist)
return; // return;
Save(playlist); // Save(playlist);
} //}
public Playlist? GetPlaylist(PlaylistSong playlistSong) public Playlist? GetPlaylist(PlaylistSong playlistSong)
{ {
@@ -46,27 +46,27 @@ public class PlaylistRepository : JsonFileRepository<Playlist>, IPlaylistReposit
throw new Exception("Unable to determine new fileName"); throw new Exception("Unable to determine new fileName");
} }
public event EventHandler<PlaylistAddedEventArgs>? PlaylistAdded; //public event EventHandler<PlaylistAddedEventArgs>? PlaylistAdded;
public event EventHandler<PlaylistRemovedEventArgs>? PlaylistRemoved; //public event EventHandler<PlaylistRemovedEventArgs>? PlaylistRemoved;
public void AddPlaylist() //public void AddPlaylist()
{ //{
Playlist playlist = new() // Playlist playlist = new()
{ // {
Name = "New Playlist" // Name = "New Playlist"
}; // };
playlist.PlaylistUpdated += OnPlaylistUpdated; // playlist.PlaylistUpdated += OnPlaylistUpdated;
Save(playlist); // Save(playlist);
PlaylistAdded?.Invoke(this, new(playlist)); // PlaylistAdded?.Invoke(this, new(playlist));
} //}
public void RemovePlaylist(Playlist playlist) //public void RemovePlaylist(Playlist playlist)
{ //{
playlist.PlaylistUpdated -= OnPlaylistUpdated; // playlist.PlaylistUpdated -= OnPlaylistUpdated;
Delete(playlist); // Delete(playlist);
PlaylistRemoved?.Invoke(this, new(playlist)); // PlaylistRemoved?.Invoke(this, new(playlist));
} //}
} }

View File

@@ -8,8 +8,8 @@ using Shouldly;
namespace Harmonia.Tests; namespace Harmonia.Tests;
internal class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistRepository playlistRepository) internal class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistManager playlistManager)
: AudioPlayer(audioEngine, playlistRepository) : AudioPlayer(audioEngine, playlistManager)
{ {
internal void SetPlaylist(Playlist playlist) internal void SetPlaylist(Playlist playlist)
{ {
@@ -28,7 +28,7 @@ internal class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistRepository pla
public class AudioPlayerTests public class AudioPlayerTests
{ {
private readonly IAudioEngine _audioEngine; private readonly IAudioEngine _audioEngine;
private readonly IPlaylistRepository _playlistRepository; private readonly IPlaylistManager _playlistManager;
private readonly PlaylistSong[] _playlistSongs; private readonly PlaylistSong[] _playlistSongs;
private readonly TestAudioPlayer _audioPlayer; private readonly TestAudioPlayer _audioPlayer;
@@ -63,10 +63,11 @@ public class AudioPlayerTests
_playlistSongs = [.. playlist.Songs]; _playlistSongs = [.. playlist.Songs];
_playlistRepository = Substitute.For<IPlaylistRepository>(); _playlistManager = Substitute.For<IPlaylistManager>();
_playlistRepository.Get().Returns([playlist]); _playlistManager.Playlists.Returns([playlist]);
_playlistManager.GetPlaylist(Arg.Any<PlaylistSong>()).Returns(playlist);
_audioPlayer = new TestAudioPlayer(_audioEngine, _playlistRepository); _audioPlayer = new TestAudioPlayer(_audioEngine, _playlistManager);
_audioPlayer.SetPlaylist(playlist); _audioPlayer.SetPlaylist(playlist);
} }

View File

@@ -211,21 +211,21 @@ public partial class PlaybackBarViewModel : ViewModelBase, IDisposable
_timer = new(TimeSpan.FromMilliseconds(100), DispatcherPriority.Default, TickTock); _timer = new(TimeSpan.FromMilliseconds(100), DispatcherPriority.Default, TickTock);
Task.Run(() => PlayDemoSong(playlistRepository)); //Task.Run(() => PlayDemoSong(playlistRepository));
} }
private async Task PlayDemoSong(IPlaylistRepository playlistRepository) //private async Task PlayDemoSong(IPlaylistManager playlistManager)
{ //{
if (playlistRepository.Get().Count == 0) // if (playlistRepository.Get().Count == 0)
{ // {
playlistRepository.AddPlaylist(); // playlistRepository.AddPlaylist();
} // }
Playlist playlist = playlistRepository.Get().First(); // Playlist playlist = playlistRepository.Get().First();
if (playlist.Songs.Count > 0) // if (playlist.Songs.Count > 0)
await _audioPlayer.LoadAsync(playlist.Songs[0], PlaybackMode.LoadOnly); // await _audioPlayer.LoadAsync(playlist.Songs[0], PlaybackMode.LoadOnly);
} //}
private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e) private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e)
{ {

View File

@@ -1,4 +1,5 @@
using Harmonia.Core.Extensions; using Harmonia.Core.Extensions;
using Harmonia.Core.Playlists;
using Harmonia.WinUI.Caching; using Harmonia.WinUI.Caching;
using Harmonia.WinUI.Storage; using Harmonia.WinUI.Storage;
using Harmonia.WinUI.ViewModels; using Harmonia.WinUI.ViewModels;
@@ -38,8 +39,10 @@ public partial class App : Application
InitializeComponent(); InitializeComponent();
} }
protected override void OnLaunched(LaunchActivatedEventArgs args) protected override async void OnLaunched(LaunchActivatedEventArgs args)
{ {
await ServiceProvider.InitializeHarmoniaAsync();
_mainWindow = ServiceProvider.GetRequiredService<MainWindow>(); _mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
_mainWindow.Activate(); _mainWindow.Activate();
} }

View File

@@ -30,6 +30,7 @@ namespace Harmonia.WinUI.ViewModels;
public partial class PlaylistViewModel : ViewModelBase public partial class PlaylistViewModel : ViewModelBase
{ {
private readonly IPlaylistManager _playlistManager;
private readonly IAudioPlayer _audioPlayer; private readonly IAudioPlayer _audioPlayer;
private readonly IAudioImageCache _audioImageCache; private readonly IAudioImageCache _audioImageCache;
private readonly IAudioBitmapImageCache _audioBitmapImageCache; private readonly IAudioBitmapImageCache _audioBitmapImageCache;
@@ -133,8 +134,11 @@ public partial class PlaylistViewModel : ViewModelBase
IAudioFileScanner audioFileScanner, IAudioFileScanner audioFileScanner,
IAudioEngine audioEngine, IAudioEngine audioEngine,
IStorageProvider storageProvider, IStorageProvider storageProvider,
IPlaylistRepository playlistRepository) IPlaylistManager playlistManager)
{ {
_playlistManager = playlistManager;
//_playlistManager.CurrentPlaylistChanged += OnPlaylistChanged;
_audioPlayer = audioPlayer; _audioPlayer = audioPlayer;
_audioPlayer.PlaylistChanged += OnPlaylistChanged; _audioPlayer.PlaylistChanged += OnPlaylistChanged;
_audioPlayer.PlayingSongChanged += OnPlayingSongChanged; _audioPlayer.PlayingSongChanged += OnPlayingSongChanged;
@@ -148,8 +152,11 @@ public partial class PlaylistViewModel : ViewModelBase
FilteredPlaylistSongs.CollectionChanged += OnFilteredPlaylistSongsCollectionChanged; FilteredPlaylistSongs.CollectionChanged += OnFilteredPlaylistSongsCollectionChanged;
Playlist = _playlistManager.CurrentPlaylist; // Testing
UpdatePlaylistSongs(Playlist);
// Testing // Testing
Task.Run(() => PlayDemoSong(playlistRepository)); //Task.Run(() => PlayDemoSong(playlistRepository));
} }
private void OnFilteredPlaylistSongsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) private void OnFilteredPlaylistSongsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
@@ -160,17 +167,25 @@ public partial class PlaylistViewModel : ViewModelBase
int x = 1; int x = 1;
} }
private async Task PlayDemoSong(IPlaylistRepository playlistRepository) //private async Task PlayDemoSong(IPlaylistRepository playlistRepository)
{ //{
if (playlistRepository.Get().Count == 0) // if (playlistRepository.Get().Count == 0)
{ // {
playlistRepository.AddPlaylist(); // playlistRepository.AddPlaylist();
} // }
Playlist playlist = playlistRepository.Get().First(); // Playlist playlist = playlistRepository.Get().First();
if (playlist.Songs.Count > 0) // if (playlist.Songs.Count > 0)
await _audioPlayer.LoadAsync(playlist.Songs[0], PlaybackMode.LoadOnly); // await _audioPlayer.LoadAsync(playlist.Songs[0], PlaybackMode.LoadOnly);
//}
private void UpdatePlaylistSongs(Playlist? playlist)
{
PlaylistSong[] playlistSongs = playlist?.Songs.ToArray() ?? [];
PlaylistSongs = [.. playlistSongs];
UpdateFilteredSongs();
} }
private void OnPlaylistChanged(object? sender, EventArgs e) private void OnPlaylistChanged(object? sender, EventArgs e)