2024-11-18 20:59:20 +01:00
using System.Globalization ;
2024-06-27 01:47:44 +02:00
using MediaBrowser.Model.Tasks ;
using Microsoft.Extensions.Logging ;
using MediaBrowser.Controller ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Playlists ;
using MediaBrowser.Controller.Providers ;
using MediaBrowser.Model.IO ;
2024-06-29 18:29:40 +02:00
using Jellyfin.Data.Entities ;
using Jellyfin.Data.Enums ;
2024-10-30 15:20:33 +01:00
using MediaBrowser.Model.Entities ;
2024-06-29 18:29:40 +02:00
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Model.Playlists ;
2024-06-27 01:47:44 +02:00
2024-10-24 23:53:21 +02:00
using Jellyfin.Plugin.SmartPlaylist.Lisp ;
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler ;
2024-11-07 00:48:56 +01:00
using Lisp_Object = Jellyfin . Plugin . SmartPlaylist . Lisp . Object ;
using Lisp_Boolean = Jellyfin . Plugin . SmartPlaylist . Lisp . Boolean ;
2024-10-24 23:53:21 +02:00
2024-06-27 01:47:44 +02:00
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
public class GeneratePlaylist : IScheduledTask {
2024-10-24 23:53:21 +02:00
public static readonly BaseItemKind [ ] AvailableFilterItems = {
2024-10-25 23:37:47 +02:00
BaseItemKind . Audio ,
BaseItemKind . MusicAlbum ,
BaseItemKind . Playlist ,
2024-10-24 23:53:21 +02:00
} ;
2024-06-27 01:47:44 +02:00
private readonly ILogger _logger ;
2024-06-29 18:29:40 +02:00
private readonly ILibraryManager _libraryManager ;
private readonly IUserManager _userManager ;
2024-10-25 02:18:13 +02:00
private readonly IProviderManager _providerManager ;
private readonly IFileSystem _fileSystem ;
2024-10-24 23:53:21 +02:00
private readonly IPlaylistManager _playlistManager ;
private readonly IStore _store ;
2024-06-29 18:29:40 +02:00
2024-06-27 01:47:44 +02:00
public GeneratePlaylist (
2024-06-29 18:29:40 +02:00
ILogger < Plugin > logger ,
ILibraryManager libraryManager ,
2024-10-24 23:53:21 +02:00
IUserManager userManager ,
2024-10-25 02:18:13 +02:00
IProviderManager providerManager ,
IFileSystem fileSystem ,
2024-10-24 23:53:21 +02:00
IPlaylistManager playlistManager ,
IServerApplicationPaths serverApplicationPaths
2024-06-27 01:47:44 +02:00
) {
_logger = logger ;
2024-06-29 18:29:40 +02:00
_libraryManager = libraryManager ;
_userManager = userManager ;
2024-10-25 02:18:13 +02:00
_providerManager = providerManager ;
_fileSystem = fileSystem ;
2024-10-24 23:53:21 +02:00
_playlistManager = playlistManager ;
2024-10-25 02:18:13 +02:00
_store = new Store ( new SmartPlaylistFileSystem ( serverApplicationPaths ) ) ;
2024-06-27 01:47:44 +02:00
}
2024-06-29 18:29:40 +02:00
2024-06-27 01:47:44 +02:00
public string Category = > "Library" ;
public string Name = > "(re)generate Smart Playlists" ;
public string Description = > "Generate or regenerate all Smart Playlists" ;
public string Key = > nameof ( GeneratePlaylist ) ;
2024-06-29 18:29:40 +02:00
2024-06-27 01:47:44 +02:00
public IEnumerable < TaskTriggerInfo > GetDefaultTriggers ( ) {
return new [ ] {
new TaskTriggerInfo {
2024-10-24 23:53:21 +02:00
IntervalTicks = TimeSpan . FromHours ( 24 ) . Ticks ,
2024-06-27 01:47:44 +02:00
Type = TaskTriggerInfo . TriggerInterval ,
}
} ;
}
2024-06-29 18:29:40 +02:00
private void GetUsers ( ) {
foreach ( var user in _userManager . Users ) {
_logger . LogInformation ( "User {0}" , user ) ;
var query = new InternalItemsQuery ( user ) {
2024-10-24 23:53:21 +02:00
IncludeItemTypes = AvailableFilterItems ,
2024-06-29 18:29:40 +02:00
Recursive = true ,
} ;
foreach ( BaseItem item in _libraryManager . GetItemsResult ( query ) . Items ) {
_logger . LogInformation ( "Item {0}" , item ) ;
}
}
}
2024-10-30 19:33:01 +01:00
private PlaylistId CreateNewPlaylist ( string name , UserId userId ) {
2024-10-30 15:20:33 +01:00
_logger . LogDebug ( "Creating playlist '{0}'" , name ) ;
2024-10-24 23:53:21 +02:00
var req = new PlaylistCreationRequest {
2024-10-25 23:37:47 +02:00
Name = name ,
UserId = userId ,
2024-10-30 15:20:33 +01:00
Users = [ new PlaylistUserPermissions ( userId ) ] ,
Public = false ,
2024-10-24 23:53:21 +02:00
} ;
2024-10-25 02:18:13 +02:00
var playlistGuid = Guid . Parse ( _playlistManager . CreatePlaylist ( req ) . Result . Id ) ;
return playlistGuid ;
2024-10-24 23:53:21 +02:00
}
private IEnumerable < Guid > FilterPlaylistItems ( IEnumerable < BaseItem > items , User user , SmartPlaylistDto smartPlaylist ) {
2024-11-19 17:33:33 +01:00
List < BaseItem > results = new List < BaseItem > ( ) ;
Expression expression = new Parser ( StringTokenStream . generate ( smartPlaylist . Program ) ) . parse ( ) ; // parse here, so that we don't repeat the work for each item
2024-10-27 00:54:40 +02:00
Executor executor = new Executor ( new DefaultEnvironment ( ) ) ;
2024-12-17 18:37:36 +01:00
executor . builtins [ "logd" ] = ( x ) = > {
_logger . LogDebug ( ( ( Lisp . String ) x . First ( ) ) . Value ( ) , x . Skip ( 1 ) . ToArray ( ) ) ;
return Lisp_Boolean . TRUE ;
} ;
executor . builtins [ "logi" ] = ( x ) = > {
_logger . LogInformation ( ( ( Lisp . String ) x . First ( ) ) . Value ( ) , x . Skip ( 1 ) . ToArray ( ) ) ;
return Lisp_Boolean . TRUE ;
} ;
executor . builtins [ "logw" ] = ( x ) = > {
_logger . LogWarning ( ( ( Lisp . String ) x . First ( ) ) . Value ( ) , x . Skip ( 1 ) . ToArray ( ) ) ;
return Lisp_Boolean . TRUE ;
} ;
executor . builtins [ "loge" ] = ( x ) = > {
_logger . LogError ( ( ( Lisp . String ) x . First ( ) ) . Value ( ) , x . Skip ( 1 ) . ToArray ( ) ) ;
return Lisp_Boolean . TRUE ;
} ;
2024-11-19 17:33:33 +01:00
executor . environment . Set ( "user" , Lisp_Object . FromBase ( user ) ) ;
2024-11-08 03:09:17 +01:00
if ( Plugin . Instance is not null ) {
executor . eval ( Plugin . Instance . Configuration . InitialProgram ) ;
} else {
throw new ApplicationException ( "Plugin Instance is not yet initialized" ) ;
}
2024-10-24 23:53:21 +02:00
foreach ( var i in items ) {
2024-11-19 17:33:33 +01:00
executor . environment . Set ( "item" , Lisp_Object . FromBase ( i ) ) ;
2024-10-24 23:53:21 +02:00
var r = executor . eval ( expression ) ;
2024-10-25 23:37:47 +02:00
_logger . LogTrace ( "Item {0} evaluated to {1}" , i , r . ToString ( ) ) ;
2024-11-07 00:48:56 +01:00
if ( ( r is not Lisp_Boolean r_bool ) | | ( r_bool . Value ( ) ) ) {
2024-10-26 23:57:02 +02:00
_logger . LogDebug ( "Added '{0}' to Smart Playlist {1}" , i , smartPlaylist . Name ) ;
2024-11-19 17:33:33 +01:00
results . Add ( i ) ;
2024-10-24 23:53:21 +02:00
}
}
2024-11-19 17:33:33 +01:00
executor = new Executor ( new DefaultEnvironment ( ) ) ;
executor . environment . Set ( "user" , Lisp_Object . FromBase ( user ) ) ;
executor . environment . Set ( "items" , Lisp_Object . FromBase ( results ) ) ;
results = new List < BaseItem > ( ) ;
var sort_result = executor . eval ( smartPlaylist . SortProgram ) ;
if ( sort_result is Cons sorted_items ) {
foreach ( var i in sorted_items . ToList ( ) ) {
if ( i is Lisp_Object iObject & & iObject . Value ( ) is BaseItem iBaseItem ) {
results . Add ( iBaseItem ) ;
continue ;
}
throw new ApplicationException ( $"Returned sorted list does contain unexpected items, got {i}" ) ;
}
} else if ( sort_result = = Lisp_Boolean . FALSE ) {
} else {
throw new ApplicationException ( $"Did not return a list of items, returned {sort_result}" ) ;
}
return results . Select ( x = > x . Id ) ;
2024-10-24 23:53:21 +02:00
}
private IEnumerable < BaseItem > GetAllUserMedia ( User user ) {
var req = new InternalItemsQuery ( user ) {
2024-10-25 23:37:47 +02:00
IncludeItemTypes = AvailableFilterItems ,
2024-10-24 23:53:21 +02:00
Recursive = true ,
} ;
return _libraryManager . GetItemsResult ( req ) . Items ;
}
2024-06-27 01:47:44 +02:00
public async Task ExecuteAsync ( IProgress < double > progress , CancellationToken cancellationToken ) {
2024-10-24 23:53:21 +02:00
_logger . LogInformation ( "Started regenerate Smart Playlists" ) ;
2024-12-17 17:54:47 +01:00
_logger . LogDebug ( "Loaded Assemblies:" ) ;
foreach ( var asm in AppDomain . CurrentDomain . GetAssemblies ( ) ) {
_logger . LogDebug ( "- {0}" , asm ) ;
}
2024-12-21 01:12:01 +01:00
var i = 0 ;
2024-10-24 23:53:21 +02:00
foreach ( SmartPlaylistDto dto in await _store . GetAllSmartPlaylistsAsync ( ) ) {
2024-12-21 01:09:02 +01:00
if ( ! dto . Enabled ) {
2024-12-21 01:12:01 +01:00
i + = 1 ;
2024-12-21 01:09:02 +01:00
continue ;
}
2024-10-25 23:37:47 +02:00
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.
2024-10-24 23:53:21 +02:00
await _store . SaveSmartPlaylistAsync ( dto ) ;
}
2024-10-25 23:37:47 +02:00
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 ) ;
}
2024-12-21 01:12:01 +01:00
i + = 1 ;
progress . Report ( 100 * ( ( double ) i ) / dto . Playlists . Count ( ) ) ;
2024-10-24 23:53:21 +02:00
}
}
2024-10-25 23:37:47 +02:00
private async Task ClearPlaylist ( Playlist playlist ) {
2024-10-25 02:18:13 +02:00
// fuck if I know
if ( _libraryManager . GetItemById ( playlist . Id ) is not Playlist playlist_new ) {
throw new ArgumentException ( "" ) ;
}
var existingItems = playlist_new . GetManageableItems ( ) . ToList ( ) ;
2024-11-18 20:59:20 +01:00
await _playlistManager . RemoveItemFromPlaylistAsync ( playlist . Id . ToString ( ) , existingItems . Select ( x = > x . Item1 . ItemId ? . ToString ( "N" , CultureInfo . InvariantCulture ) ) ) ;
2024-06-27 01:47:44 +02:00
}
}
}