diff --git a/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs b/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs index 088ba18..34925d6 100644 --- a/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs +++ b/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs @@ -32,7 +32,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { public class GeneratePlaylist : IScheduledTask { public static readonly BaseItemKind[] AvailableFilterItems = { - BaseItemKind.Audio + BaseItemKind.Audio, + BaseItemKind.MusicAlbum, + BaseItemKind.Playlist, }; private readonly ILogger _logger; @@ -90,10 +92,10 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { } } - private SmartPlaylistId CreateNewPlaylist(SmartPlaylistDto dto) { + private SmartPlaylistId CreateNewPlaylist(string name, UserId userId) { var req = new PlaylistCreationRequest { - Name = dto.Name, - UserId = dto.User + Name = name, + UserId = userId, }; var playlistGuid = Guid.Parse(_playlistManager.CreatePlaylist(req).Result.Id); return playlistGuid; @@ -107,7 +109,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { foreach (var i in items) { executor.environment["item"] = new Lisp_Object(i); var r = executor.eval(expression); - _logger.LogDebug("Item {0} evaluated to {1}", i, r.ToString()); + _logger.LogTrace("Item {0} evaluated to {1}", i, r.ToString()); if (r is Lisp_Boolean r_bool) { if (r_bool.value) { _logger.LogDebug("Added '{0}' to Smart Playlist {1}", i, smartPlaylist.Name); @@ -122,7 +124,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { private IEnumerable GetAllUserMedia(User user) { var req = new InternalItemsQuery(user) { - IncludeItemTypes = new[] {BaseItemKind.Audio}, + IncludeItemTypes = AvailableFilterItems, Recursive = true, }; return _libraryManager.GetItemsResult(req).Items; @@ -131,23 +133,44 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { _logger.LogInformation("Started regenerate Smart Playlists"); foreach (SmartPlaylistDto dto in await _store.GetAllSmartPlaylistsAsync()) { - var user = _userManager.GetUserById(dto.User); - List playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList(); - if ((dto.Id == null) || !playlists.Any()) { - _logger.LogInformation("Generating new smart playlist (dto.Id = {0}, playlists.Any() = {1})", dto.Id, playlists.Any()); - _store.DeleteSmartPlaylist(dto); - dto.Id = CreateNewPlaylist(dto); - await _store.SaveSmartPlaylistAsync(dto); - playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList(); + var changedDto = false; + if (dto.Playlists.Length == 0) { + dto.Playlists = _userManager.UsersIds.Select(x => new SmartPlaylistLinkDto { + UserId = x, + PlaylistId = CreateNewPlaylist(dto.Name, x), + }).ToArray(); + changedDto = true; + } + foreach (SmartPlaylistLinkDto playlistLink in dto.Playlists) { + if (playlistLink.PlaylistId == Guid.Empty) { + // not initialized + playlistLink.PlaylistId = CreateNewPlaylist(dto.Name, playlistLink.UserId); + changedDto = true; + } else if (_playlistManager.GetPlaylists(playlistLink.UserId).Where(x => x.Id == playlistLink.PlaylistId).ToArray().Length == 0) { + // somehow the corresponding playlist doesnt + // exist anymore, did the user delete it? + playlistLink.PlaylistId = CreateNewPlaylist(dto.Name, playlistLink.UserId); + changedDto = true; + } + } + if (changedDto) { + _store.DeleteSmartPlaylist(dto); // delete in case the file was not the canonical one. + await _store.SaveSmartPlaylistAsync(dto); + } + foreach (SmartPlaylistLinkDto playlistLink in dto.Playlists) { + User? user = _userManager.GetUserById(playlistLink.UserId); + if (user == null) { + continue; + } + var insertItems = FilterPlaylistItems(GetAllUserMedia(user), user, dto).ToArray(); + var playlist = _playlistManager.GetPlaylists(playlistLink.UserId).Where(x => x.Id == playlistLink.PlaylistId).First(); + await ClearPlaylist(playlist); + await _playlistManager.AddItemToPlaylistAsync(playlist.Id, insertItems, playlistLink.UserId); } - var insertItems = FilterPlaylistItems(GetAllUserMedia(user), user, dto); - Playlist playlist = playlists.First(); - await ClearPlaylist(dto, playlist, user); - await _playlistManager.AddItemToPlaylistAsync(playlist.Id, insertItems.ToArray(), user.Id); } } - private async Task ClearPlaylist(SmartPlaylistDto smartPlaylist, Playlist playlist, User user) { + private async Task ClearPlaylist(Playlist playlist) { // fuck if I know if (_libraryManager.GetItemById(playlist.Id) is not Playlist playlist_new) { throw new ArgumentException(""); diff --git a/Jellyfin.Plugin.SmartPlaylist/SmartPlaylistDto.cs b/Jellyfin.Plugin.SmartPlaylist/SmartPlaylistDto.cs index ca024dd..273eecf 100644 --- a/Jellyfin.Plugin.SmartPlaylist/SmartPlaylistDto.cs +++ b/Jellyfin.Plugin.SmartPlaylist/SmartPlaylistDto.cs @@ -1,28 +1,103 @@ using System.Runtime.Serialization; namespace Jellyfin.Plugin.SmartPlaylist { - [Serializable] - public class SmartPlaylistDto { - private string DEFAULT_PROGRAM = "(begin (invoke item 'IsFavoriteOrLiked' (user)))"; - public SmartPlaylistId Id { get; set; } - public string? Name { get; set; } - public UserId User { get; set; } - public string? Program { get; set; } - public string? Filename { get; set; } - public int MaxItems { get; set; } = -1; - public void Fill(string filename) { - if (Id == Guid.Empty) { + class GuidDeserializer { + public static Guid deserialize(string v) { + if (v.Length == 32) { + return Guid.ParseExact(v, "N"); + } + return Guid.Parse(v); + } + } + + [Serializable] + public class SmartPlaylistLinkDto : ISerializable { + public PlaylistId PlaylistId { get; set; } + public UserId UserId { get; set; } + + public SmartPlaylistLinkDto() { + PlaylistId = Guid.Empty; + UserId = Guid.Empty; + } + + protected SmartPlaylistLinkDto(SerializationInfo info, StreamingContext context) { + if (info.GetValue("PlaylistId", typeof(PlaylistId)) is PlaylistId _PlaylistId) { + PlaylistId = _PlaylistId; + } else { + PlaylistId = Guid.Empty; + } + if (info.GetValue("UserId", typeof(UserId)) is UserId _UserId) { + UserId = _UserId; + } else { + UserId = Guid.Empty; + } + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) { + info.AddValue("PlaylistId", PlaylistId); + info.AddValue("UserId", UserId); + } + } + + [Serializable] + public class SmartPlaylistDto : ISerializable { + private static string DEFAULT_PROGRAM = "(begin (invoke item 'IsFavoriteOrLiked' (user)))"; + public SmartPlaylistId Id { get; set; } + public SmartPlaylistLinkDto[] Playlists { get; set; } + public string Name { get; set; } + public string Program { get; set; } + public string? Filename { get; set; } + public bool Enabled { get; set; } + + public SmartPlaylistDto() { + Id = Guid.NewGuid(); + Playlists = []; + Name = Id.ToString(); + Program = DEFAULT_PROGRAM; + Filename = null; + Enabled = true; + } + + protected SmartPlaylistDto(SerializationInfo info, StreamingContext context) { + if (info.GetValue("Id", typeof(SmartPlaylistId)) is SmartPlaylistId _Id) { + Id = _Id; + } else { Id = Guid.NewGuid(); } - if (Name == null) { + if (info.GetValue("Playlists", typeof(SmartPlaylistLinkDto[])) is SmartPlaylistLinkDto[] _Playlists) { + Playlists = _Playlists; + } else { + Playlists = []; + } + if (info.GetValue("Name", typeof(string)) is string _Name) { + Name = _Name; + } else { Name = Id.ToString(); } - if (Program == null) { + if (info.GetValue("Program", typeof(string)) is string _Program) { + Program = _Program; + } else { Program = DEFAULT_PROGRAM; } - if (Filename == null) { - Filename = filename; + if (info.GetValue("Filename", typeof(string)) is string _Filename) { + Filename = _Filename; + } else { + Filename = null; } + if (info.GetValue("Enabled", typeof(bool)) is bool _Enabled) { + Enabled = _Enabled; + } else { + Enabled = true; + } + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) { + info.AddValue("Id", Id); + info.AddValue("Playlists", Playlists); + info.AddValue("Name", Name); + info.AddValue("Program", Program); + info.AddValue("Filename", Filename); + info.AddValue("Enabled", Enabled); } } } diff --git a/Jellyfin.Plugin.SmartPlaylist/Store.cs b/Jellyfin.Plugin.SmartPlaylist/Store.cs index 2a9b609..417592a 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Store.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Store.cs @@ -2,7 +2,7 @@ using System.Text.Json; namespace Jellyfin.Plugin.SmartPlaylist { public interface IStore { - Task GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId); + Task GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId); Task GetAllSmartPlaylistsAsync(); Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist); void DeleteSmartPlaylist(SmartPlaylistDto smartPlaylist); @@ -13,13 +13,16 @@ namespace Jellyfin.Plugin.SmartPlaylist { public Store(ISmartPlaylistFileSystem fileSystem) { _fileSystem = fileSystem; } - private async Task LoadPlaylistAsync(string filename) { + private async Task LoadPlaylistAsync(string filename) { await using var r = File.OpenRead(filename); var dto = (await JsonSerializer.DeserializeAsync(r).ConfigureAwait(false)); - dto.Fill(filename); + if (dto == null) { + throw new ApplicationException(""); + } + dto.Filename = filename; return dto; } - public async Task GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId) { + public async Task GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId) { string filename = _fileSystem.FindSmartPlaylistFilePath(smartPlaylistId); return await LoadPlaylistAsync(filename).ConfigureAwait(false); } diff --git a/Jellyfin.Plugin.SmartPlaylist/Types.cs b/Jellyfin.Plugin.SmartPlaylist/Types.cs index 060a796..4f17310 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Types.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Types.cs @@ -1,4 +1,5 @@ global using System; global using UserId = System.Guid; +global using PlaylistId = System.Guid; global using SmartPlaylistId = System.Guid;