365 lines
9.5 KiB
C#
365 lines
9.5 KiB
C#
using Harmonia.Core.Extensions;
|
|
using Harmonia.Core.Models;
|
|
|
|
namespace Harmonia.Core.Playlists;
|
|
|
|
public class Playlist
|
|
{
|
|
private readonly List<PlaylistSong> _songs = [];
|
|
private readonly List<GroupOption> _groupOptions = [];
|
|
private readonly List<SortOption> _sortOptions = [];
|
|
|
|
public string UID { get; init; } = Guid.NewGuid().ToString();
|
|
public string? Name { get; private set; }
|
|
public IReadOnlyList<PlaylistSong> Songs => _songs;
|
|
public IReadOnlyList<GroupOption> GroupOptions => _groupOptions;
|
|
public IReadOnlyList<SortOption> SortOptions => _sortOptions;
|
|
public bool IsLocked { get; private set; }
|
|
|
|
public event EventHandler<PlaylistUpdatedEventArgs>? PlaylistUpdated;
|
|
|
|
public Playlist()
|
|
{
|
|
}
|
|
|
|
public Playlist(string? name)
|
|
{
|
|
Name = name;
|
|
}
|
|
|
|
internal static Playlist Restore(string uid, string? name, IEnumerable<PlaylistSong> songs, IEnumerable<GroupOption> groupOptions, IEnumerable<SortOption> sortOptions, bool isLocked)
|
|
{
|
|
Playlist playlist = new(name)
|
|
{
|
|
UID = uid,
|
|
IsLocked = isLocked
|
|
};
|
|
|
|
playlist._songs.AddRange(songs);
|
|
playlist._groupOptions.AddRange(groupOptions);
|
|
playlist._sortOptions.AddRange(sortOptions);
|
|
|
|
return playlist;
|
|
}
|
|
|
|
public int IndexOf(PlaylistSong playlistSong)
|
|
{
|
|
return _songs.IndexOf(playlistSong);
|
|
}
|
|
|
|
public void SetName(string name)
|
|
{
|
|
if (string.Equals(Name, name, StringComparison.Ordinal))
|
|
return;
|
|
|
|
Name = name;
|
|
|
|
PlaylistUpdatedEventArgs eventArgs = new()
|
|
{
|
|
Action = PlaylistUpdateAction.Rename,
|
|
Index = -1,
|
|
Count = 0,
|
|
Songs = []
|
|
};
|
|
|
|
PlaylistUpdated?.Invoke(this, eventArgs);
|
|
}
|
|
|
|
public void Lock()
|
|
{
|
|
if (IsLocked)
|
|
return;
|
|
|
|
IsLocked = true;
|
|
|
|
PlaylistUpdatedEventArgs eventArgs = new()
|
|
{
|
|
Action = PlaylistUpdateAction.Lock,
|
|
Index = -1,
|
|
Count = 0,
|
|
Songs = []
|
|
};
|
|
|
|
PlaylistUpdated?.Invoke(this, eventArgs);
|
|
}
|
|
|
|
public void Unlock()
|
|
{
|
|
if (IsLocked == false)
|
|
return;
|
|
|
|
IsLocked = false;
|
|
|
|
PlaylistUpdatedEventArgs eventArgs = new()
|
|
{
|
|
Action = PlaylistUpdateAction.Unlock,
|
|
Index = -1,
|
|
Count = 0,
|
|
Songs = []
|
|
};
|
|
|
|
PlaylistUpdated?.Invoke(this, eventArgs);
|
|
}
|
|
|
|
public void AddSong(Song song, int? index = null)
|
|
{
|
|
AddSongs([song], index);
|
|
}
|
|
|
|
public void AddSongs(Song[] songs, int? index = null)
|
|
{
|
|
PlaylistSong[] playlistSongs = [.. songs.Select(song => new PlaylistSong(song))];
|
|
|
|
AddSongs(playlistSongs, index);
|
|
}
|
|
|
|
private void AddSongs(PlaylistSong[] playlistSongs, int? index = null)
|
|
{
|
|
if (IsLocked)
|
|
return;
|
|
|
|
if (playlistSongs.Length == 0)
|
|
return;
|
|
|
|
int insertIndex = index ?? _songs.Count;
|
|
|
|
_songs.InsertRange(insertIndex, playlistSongs);
|
|
|
|
PlaylistUpdatedEventArgs eventArgs = new()
|
|
{
|
|
Action = PlaylistUpdateAction.Add,
|
|
Index = insertIndex,
|
|
Count = playlistSongs.Length,
|
|
Songs = playlistSongs
|
|
};
|
|
|
|
PlaylistUpdated?.Invoke(this, eventArgs);
|
|
}
|
|
|
|
public void MoveSong(PlaylistSong playlistSong, int newIndex)
|
|
{
|
|
int currentIndex = _songs.IndexOf(playlistSong);
|
|
|
|
MoveSong(currentIndex, newIndex);
|
|
}
|
|
|
|
public void MoveSong(int oldIndex, int newIndex)
|
|
{
|
|
if (IsLocked)
|
|
return;
|
|
|
|
if (oldIndex == newIndex)
|
|
return;
|
|
|
|
PlaylistSong playlistSong = _songs[oldIndex];
|
|
|
|
_songs.Remove(playlistSong);
|
|
_songs.Insert(newIndex, playlistSong);
|
|
|
|
PlaylistUpdatedEventArgs eventArgs = new()
|
|
{
|
|
Action = PlaylistUpdateAction.Move,
|
|
Index = oldIndex,
|
|
Count = 1,
|
|
Songs = [playlistSong],
|
|
NewIndex = newIndex
|
|
};
|
|
|
|
PlaylistUpdated?.Invoke(this, eventArgs);
|
|
}
|
|
|
|
public void SortSongs(PlaylistSong[] playlistSongs, SortOption[] sortOptions)
|
|
{
|
|
if (IsLocked)
|
|
return;
|
|
|
|
Dictionary<int, PlaylistSong> oldPlaylistSongs = playlistSongs
|
|
.OrderBy(_songs.IndexOf)
|
|
.ToDictionary(_songs.IndexOf, playlistSong => playlistSong);
|
|
|
|
Song[] songs = [.. playlistSongs.Select(playlistSong => playlistSong.Song)];
|
|
Song[] sortedSongs = [.. songs.SortBy(sortOptions)];
|
|
|
|
int currentSortIndex = 0;
|
|
|
|
foreach (int index in oldPlaylistSongs.Keys)
|
|
{
|
|
PlaylistSong playlistSong = oldPlaylistSongs[index];
|
|
Song sortedSong = sortedSongs[currentSortIndex++];
|
|
PlaylistSong newPlaylistSong = playlistSongs.First(ps => ps.Song == sortedSong);
|
|
|
|
if (newPlaylistSong == playlistSong)
|
|
continue;
|
|
|
|
_songs.RemoveAt(index);
|
|
_songs.Insert(index, newPlaylistSong);
|
|
}
|
|
|
|
PlaylistUpdatedEventArgs eventArgs = new()
|
|
{
|
|
Action = PlaylistUpdateAction.Reset,
|
|
Index = -1,
|
|
Count = playlistSongs.Length,
|
|
Songs = playlistSongs
|
|
};
|
|
|
|
PlaylistUpdated?.Invoke(this, eventArgs);
|
|
}
|
|
|
|
public void Randomize(PlaylistSong[] playlistSongs)
|
|
{
|
|
if (IsLocked)
|
|
return;
|
|
|
|
int[] originalIndexes = [.. playlistSongs.Select(playlistSong => _songs.IndexOf(playlistSong))];
|
|
PlaylistSong[] shuffledSongs = [.. playlistSongs.Shuffle()];
|
|
|
|
for (int i = 0; i < originalIndexes.Length; i++)
|
|
_songs[originalIndexes[i]] = shuffledSongs[i];
|
|
|
|
PlaylistUpdatedEventArgs eventArgs = new()
|
|
{
|
|
Action = PlaylistUpdateAction.Reset,
|
|
Index = -1,
|
|
Count = playlistSongs.Length,
|
|
Songs = playlistSongs
|
|
};
|
|
|
|
PlaylistUpdated?.Invoke(this, eventArgs);
|
|
}
|
|
|
|
public void Reverse(PlaylistSong[] playlistSongs)
|
|
{
|
|
if (IsLocked)
|
|
return;
|
|
|
|
int[] originalIndexes = [.. playlistSongs.Select(playlistSong => _songs.IndexOf(playlistSong))];
|
|
PlaylistSong[] reversedSongs = [.. originalIndexes.Select(i => _songs[i]).Reverse()];
|
|
|
|
for (int i = 0; i < originalIndexes.Length; i++)
|
|
_songs[originalIndexes[i]] = reversedSongs[i];
|
|
|
|
PlaylistUpdatedEventArgs eventArgs = new()
|
|
{
|
|
Action = PlaylistUpdateAction.Reset,
|
|
Index = -1,
|
|
Count = playlistSongs.Length,
|
|
Songs = playlistSongs
|
|
};
|
|
|
|
PlaylistUpdated?.Invoke(this, eventArgs);
|
|
}
|
|
|
|
public void RemoveSong(int index)
|
|
{
|
|
RemoveSongs(index, 1);
|
|
}
|
|
|
|
public void RemoveSongs(int index, int count)
|
|
{
|
|
PlaylistSong[] playlistSongs = [.. _songs.GetRange(index, count)];
|
|
|
|
RemoveSongs(playlistSongs);
|
|
}
|
|
|
|
public void RemoveSong(PlaylistSong playlistSong)
|
|
{
|
|
RemoveSongs([playlistSong]);
|
|
}
|
|
|
|
public void RemoveSongs(PlaylistSong[] playlistSongs)
|
|
{
|
|
if (IsLocked)
|
|
return;
|
|
|
|
List<PlaylistSong> removedSongs = [];
|
|
|
|
foreach (PlaylistSong playlistSong in playlistSongs)
|
|
{
|
|
if (_songs.Remove(playlistSong))
|
|
{
|
|
removedSongs.Add(playlistSong);
|
|
}
|
|
}
|
|
|
|
if (removedSongs.Count == 0)
|
|
return;
|
|
|
|
PlaylistUpdatedEventArgs eventArgs = new()
|
|
{
|
|
Action = PlaylistUpdateAction.Remove,
|
|
Index = -1,
|
|
Count = removedSongs.Count,
|
|
Songs = playlistSongs
|
|
};
|
|
|
|
PlaylistUpdated?.Invoke(this, eventArgs);
|
|
}
|
|
|
|
public void ImportTags(Song[] songs)
|
|
{
|
|
List<PlaylistSong> updatedPlaylistSongs = [];
|
|
|
|
foreach (Song song in songs)
|
|
{
|
|
PlaylistSong[] playlistSongs = [.. Songs.Where(playlistSong =>
|
|
string.Equals(playlistSong.Song.FileName, song.FileName, StringComparison.OrdinalIgnoreCase))];
|
|
|
|
foreach (PlaylistSong playlistSong in playlistSongs)
|
|
{
|
|
playlistSong.Song.Update(song);
|
|
}
|
|
|
|
updatedPlaylistSongs.AddRange(playlistSongs);
|
|
}
|
|
|
|
if (updatedPlaylistSongs.Count == 0)
|
|
return;
|
|
|
|
PlaylistUpdatedEventArgs eventArgs = new()
|
|
{
|
|
Action = PlaylistUpdateAction.Refresh,
|
|
Index = -1,
|
|
Count = updatedPlaylistSongs.Count,
|
|
Songs = [.. updatedPlaylistSongs]
|
|
};
|
|
|
|
PlaylistUpdated?.Invoke(this, eventArgs);
|
|
}
|
|
|
|
public void RemoveMissingSongs()
|
|
{
|
|
PlaylistSong[] missingSongs = [.. Songs.Where(playlistSong =>
|
|
File.Exists(playlistSong.Song.FileName) == false)];
|
|
|
|
if (missingSongs.Length == 0)
|
|
return;
|
|
|
|
RemoveSongs(missingSongs);
|
|
}
|
|
|
|
public void RemoveDuplicateSongs()
|
|
{
|
|
List<PlaylistSong> songsToRemove = [];
|
|
|
|
string[] fileNames = Songs
|
|
.GroupBy(x => x.Song.FileName)
|
|
.Where(x => x.Count() > 1)
|
|
.Select(x => x.Key)
|
|
.ToArray();
|
|
|
|
foreach (string fileName in fileNames)
|
|
{
|
|
List<PlaylistSong> songs = Songs.Where(x =>
|
|
string.Equals(x.Song.FileName, fileName, StringComparison.OrdinalIgnoreCase)).ToList();
|
|
|
|
for (int i = songs.Count - 1; i > 0; i--)
|
|
songsToRemove.Add(songs[i]);
|
|
}
|
|
|
|
if (songsToRemove.Count == 0)
|
|
return;
|
|
|
|
RemoveSongs(songsToRemove.ToArray());
|
|
}
|
|
} |