Cleanup and allow calling methods with arguments.

This commit is contained in:
redxef 2024-10-25 02:18:13 +02:00
parent 6208c9c070
commit faca13d393
Signed by: redxef
GPG key ID: 7DAC3AA211CBD921
6 changed files with 103 additions and 42 deletions

View file

@ -31,8 +31,8 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
E Equals(T other); E Equals(T other);
} }
public abstract class Expression: IFormattable, IComparable<Expression, bool> { public abstract class Expression: IComparable<Expression, bool> {
public abstract string ToString(string? format, IFormatProvider? provider); public override abstract string ToString();
public abstract override int GetHashCode(); public abstract override int GetHashCode();
public abstract bool Equals(Expression other); public abstract bool Equals(Expression other);
public override bool Equals(object? other) { public override bool Equals(object? other) {
@ -47,6 +47,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
public static bool operator !=(Expression left, Expression right) { public static bool operator !=(Expression left, Expression right) {
return !left.Equals(right); return !left.Equals(right);
} }
public abstract object Inner();
} }
public abstract class Atom : Expression {} public abstract class Atom : Expression {}
public class Symbol : Atom { public class Symbol : Atom {
@ -67,7 +68,10 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
} }
return false; return false;
} }
public override string ToString(string? format, IFormatProvider? provider) { public override string ToString() {
return _name;
}
public override object Inner() {
return _name; return _name;
} }
} }
@ -90,9 +94,12 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
} }
return false; return false;
} }
public override string ToString(string? format, IFormatProvider? provider) { public override string ToString() {
return _value? "t" : "nil"; return _value? "t" : "nil";
} }
public override object Inner() {
return _value;
}
} }
public class Integer : Atom, IAddable<Integer>, ISubtractable<Integer>, IMultiplicatable<Integer>, IDivisible<Integer>, ISortable<Integer, Boolean> { public class Integer : Atom, IAddable<Integer>, ISubtractable<Integer>, IMultiplicatable<Integer>, IDivisible<Integer>, ISortable<Integer, Boolean> {
@ -113,9 +120,8 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
} }
return false; return false;
} }
public override string ToString(string? format, IFormatProvider? provider) { public override string ToString() {
return _value.ToString(); return _value.ToString();
//return _value.ToString("0", provider);
} }
public static Integer operator +(Integer a, Integer b) { public static Integer operator +(Integer a, Integer b) {
return new Integer(a.value + b.value); return new Integer(a.value + b.value);
@ -150,6 +156,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
public static Boolean operator !=(Integer a, Integer b) { public static Boolean operator !=(Integer a, Integer b) {
return new Boolean(a.value != b.value); return new Boolean(a.value != b.value);
} }
public override object Inner() {
return _value;
}
} }
public class String : Atom, IAddable<String> { public class String : Atom, IAddable<String> {
@ -170,12 +179,15 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
} }
return false; return false;
} }
public override string ToString(string? format, IFormatProvider? provider) { public override string ToString() {
return "\"" + _value + "\""; return "\"" + _value + "\"";
} }
public static String operator +(String a, String b) { public static String operator +(String a, String b) {
return new String (a.value + b.value); return new String (a.value + b.value);
} }
public override object Inner() {
return _value;
}
} }
public class Object : Atom { public class Object : Atom {
@ -196,7 +208,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
} }
return false; return false;
} }
public override string ToString(string? format, IFormatProvider? provider) { public override string ToString() {
return _value.ToString(); return _value.ToString();
} }
public static Atom FromBase(object o) { public static Atom FromBase(object o) {
@ -211,6 +223,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
return new Object(o); return new Object(o);
} }
} }
public override object Inner() {
return _value;
}
} }
public class List : Expression { public class List : Expression {
@ -233,8 +248,8 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
} }
return false; return false;
} }
public override string ToString(string? format, IFormatProvider? provider) { public override string ToString() {
return "(" + string.Join(" ", _expressions.Select(x => x.ToString("0", provider))) + ")"; return "(" + string.Join(" ", _expressions.Select(x => x.ToString())) + ")";
} }
public static List operator +(List a, List b) { public static List operator +(List a, List b) {
List<Expression> r = new List<Expression>(); List<Expression> r = new List<Expression>();
@ -242,6 +257,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
r.AddRange(b.expressions); r.AddRange(b.expressions);
return new List(r); return new List(r);
} }
public override object Inner() {
return _expressions.Select(x => x.Inner()).ToArray();
}
} }
public class Parser { public class Parser {

View file

@ -30,6 +30,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
this["length"] = _length; this["length"] = _length;
this["haskeys"] = _haskeys; this["haskeys"] = _haskeys;
this["getitems"] = _getitems; this["getitems"] = _getitems;
this["invoke"] = _invoke;
//this[new Symbol("!=")] = _ne; //this[new Symbol("!=")] = _ne;
} }
@ -202,20 +203,27 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
r.Add(Compiler.Object.FromBase(pi.GetValue(o.value))); r.Add(Compiler.Object.FromBase(pi.GetValue(o.value)));
continue; continue;
} }
MethodInfo? mi = o.value.GetType().GetMethod(s.value);
if (mi != null) {
r.Add(Compiler.Object.FromBase(mi.Invoke(o.value, null)));
continue;
}
FieldInfo? fi = o.value.GetType().GetField(s.value); FieldInfo? fi = o.value.GetType().GetField(s.value);
if (fi != null) { if (fi != null) {
r.Add(Compiler.Object.FromBase(fi.GetValue(o.value))); r.Add(Compiler.Object.FromBase(fi.GetValue(o.value)));
continue; continue;
} }
throw new ApplicationException(); throw new ApplicationException($"{o.value} has no property or field {s.value}");
} }
return new Compiler.List(r); return new Compiler.List(r);
} }
private static Expression _invoke(IList<Expression> args) {
Compiler.Object o = (Compiler.Object) args[0];
Compiler.String s = (Compiler.String) args[1];
Compiler.List l = (Compiler.List) args[2];
IList<Expression> r = new List<Expression>();
MethodInfo? mi = o.value.GetType().GetMethod(s.value);
if (mi == null) {
throw new ApplicationException($"{o.value} has not method {s.value}");
}
return Compiler.Object.FromBase(mi.Invoke(o.value, (object?[]?) l.Inner()));
}
} }
public class BuiltinsLater : Dictionary<string, FunctionLater> { public class BuiltinsLater : Dictionary<string, FunctionLater> {

View file

@ -38,6 +38,8 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem;
private readonly IPlaylistManager _playlistManager; private readonly IPlaylistManager _playlistManager;
private readonly IStore _store; private readonly IStore _store;
@ -46,15 +48,19 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
ILogger<Plugin> logger, ILogger<Plugin> logger,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IUserManager userManager, IUserManager userManager,
IProviderManager providerManager,
IFileSystem fileSystem,
IPlaylistManager playlistManager, IPlaylistManager playlistManager,
IServerApplicationPaths serverApplicationPaths IServerApplicationPaths serverApplicationPaths
) { ) {
_logger = logger; _logger = logger;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_userManager = userManager; _userManager = userManager;
_providerManager = providerManager;
_fileSystem = fileSystem;
_playlistManager = playlistManager; _playlistManager = playlistManager;
_store = new Store(new FileSystem(serverApplicationPaths)); _store = new Store(new SmartPlaylistFileSystem(serverApplicationPaths));
} }
public string Category => "Library"; public string Category => "Library";
@ -84,24 +90,29 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
} }
} }
private SmartPlaylistId CreateNewPlaylist(SmartPlaylistDto dto, User user) { private SmartPlaylistId CreateNewPlaylist(SmartPlaylistDto dto) {
var req = new PlaylistCreationRequest { var req = new PlaylistCreationRequest {
Name = dto.Name, Name = dto.Name,
UserId = user.Id UserId = dto.User
}; };
return Guid.Parse(_playlistManager.CreatePlaylist(req).Result.Id); var playlistGuid = Guid.Parse(_playlistManager.CreatePlaylist(req).Result.Id);
return playlistGuid;
} }
private IEnumerable<Guid> FilterPlaylistItems(IEnumerable<BaseItem> items, User user, SmartPlaylistDto smartPlaylist) { private IEnumerable<Guid> FilterPlaylistItems(IEnumerable<BaseItem> items, User user, SmartPlaylistDto smartPlaylist) {
List<Guid> results = new List<Guid>(); List<Guid> results = new List<Guid>();
Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse(); Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse();
Executor executor = new Executor(); Executor executor = new Executor();
executor.environment["user"] = new Lisp_Object(user);
foreach (var i in items) { foreach (var i in items) {
executor.environment["item"] = new Lisp_Object(i); executor.environment["item"] = new Lisp_Object(i);
var r = executor.eval(expression); var r = executor.eval(expression);
_logger.LogInformation("Item {0} evaluated to {1}", i, r.ToString()); _logger.LogDebug("Item {0} evaluated to {1}", i, r.ToString());
if (r is Lisp_Boolean r_bool) { if (r is Lisp_Boolean r_bool) {
if (r_bool.value) { results.Add(i.Id); } if (r_bool.value) {
_logger.LogDebug("Added "{0}" to Smart Playlist {1}", i, smartPlaylist.Name);
results.Add(i.Id);
}
} else { } else {
_logger.LogInformation("Program did not return a boolean, returned {0}", r.ToString()); _logger.LogInformation("Program did not return a boolean, returned {0}", r.ToString());
} }
@ -124,8 +135,8 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
List<Playlist> playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList(); List<Playlist> playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList();
if ((dto.Id == null) || !playlists.Any()) { if ((dto.Id == null) || !playlists.Any()) {
_logger.LogInformation("Generating new smart playlist (dto.Id = {0}, playlists.Any() = {1})", dto.Id, playlists.Any()); _logger.LogInformation("Generating new smart playlist (dto.Id = {0}, playlists.Any() = {1})", dto.Id, playlists.Any());
_store.DeleteSmartPlaylist(dto.Id); _store.DeleteSmartPlaylist(dto);
dto.Id = CreateNewPlaylist(dto, user); dto.Id = CreateNewPlaylist(dto);
await _store.SaveSmartPlaylistAsync(dto); await _store.SaveSmartPlaylistAsync(dto);
playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList(); playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList();
} }
@ -137,13 +148,12 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
} }
private async Task ClearPlaylist(SmartPlaylistDto smartPlaylist, Playlist playlist, User user) { private async Task ClearPlaylist(SmartPlaylistDto smartPlaylist, Playlist playlist, User user) {
var req = new InternalItemsQuery(user) // fuck if I know
{ if (_libraryManager.GetItemById(playlist.Id) is not Playlist playlist_new) {
IncludeItemTypes = AvailableFilterItems, throw new ArgumentException("");
Recursive = true }
}; var existingItems = playlist_new.GetManageableItems().ToList();
var existingItems = playlist.GetChildren(user, false, req).Select(x => x.Id.ToString()).ToList(); await _playlistManager.RemoveItemFromPlaylistAsync(playlist.Id.ToString(), existingItems.Select(x => x.Item1.Id));
await _playlistManager.RemoveItemFromPlaylistAsync(playlist.Id.ToString(), existingItems);
} }
} }
} }

View file

@ -1,11 +1,28 @@
using System.Runtime.Serialization;
namespace Jellyfin.Plugin.SmartPlaylist { namespace Jellyfin.Plugin.SmartPlaylist {
[Serializable] [Serializable]
public class SmartPlaylistDto { public class SmartPlaylistDto {
private string DEFAULT_PROGRAM = "(begin (invoke item 'IsFavoriteOrLiked' (user)))";
public SmartPlaylistId Id { get; set; } public SmartPlaylistId Id { get; set; }
public string Name { get; set; } public string? Name { get; set; }
public string FileName { get; set; }
public UserId User { get; set; } public UserId User { get; set; }
public string Program { get; set; } public string? Program { get; set; }
public int MaxItems { get; set; } public string? Filename { get; set; }
public int MaxItems { get; set; } = -1;
public void Fill(string filename) {
if (Id == Guid.Empty) {
Id = Guid.NewGuid();
}
if (Name == null) {
Name = Id.ToString();
}
if (Program == null) {
Program = DEFAULT_PROGRAM;
}
if (Filename == null) {
Filename = filename;
}
}
} }
} }

View file

@ -9,8 +9,8 @@ namespace Jellyfin.Plugin.SmartPlaylist {
public string[] FindAllSmartPlaylistFilePaths(); public string[] FindAllSmartPlaylistFilePaths();
} }
public class FileSystem : ISmartPlaylistFileSystem { public class SmartPlaylistFileSystem : ISmartPlaylistFileSystem {
public FileSystem(IServerApplicationPaths serverApplicationPaths) { public SmartPlaylistFileSystem(IServerApplicationPaths serverApplicationPaths) {
StoragePath = Path.Combine(serverApplicationPaths.DataPath, "smartplaylists"); StoragePath = Path.Combine(serverApplicationPaths.DataPath, "smartplaylists");
if (!Directory.Exists(StoragePath)) { Directory.CreateDirectory(StoragePath); } if (!Directory.Exists(StoragePath)) { Directory.CreateDirectory(StoragePath); }
} }

View file

@ -5,7 +5,7 @@ namespace Jellyfin.Plugin.SmartPlaylist {
Task<SmartPlaylistDto?> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId); Task<SmartPlaylistDto?> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId);
Task<SmartPlaylistDto[]> GetAllSmartPlaylistsAsync(); Task<SmartPlaylistDto[]> GetAllSmartPlaylistsAsync();
Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist); Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist);
void DeleteSmartPlaylist(SmartPlaylistId smartPlaylistId); void DeleteSmartPlaylist(SmartPlaylistDto smartPlaylist);
} }
public class Store : IStore { public class Store : IStore {
@ -15,7 +15,9 @@ namespace Jellyfin.Plugin.SmartPlaylist {
} }
private async Task<SmartPlaylistDto?> LoadPlaylistAsync(string filename) { private async Task<SmartPlaylistDto?> LoadPlaylistAsync(string filename) {
await using var r = File.OpenRead(filename); await using var r = File.OpenRead(filename);
return await JsonSerializer.DeserializeAsync<SmartPlaylistDto>(r).ConfigureAwait(false); var dto = (await JsonSerializer.DeserializeAsync<SmartPlaylistDto>(r).ConfigureAwait(false));
dto.Fill(filename);
return dto;
} }
public async Task<SmartPlaylistDto?> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId) { public async Task<SmartPlaylistDto?> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId) {
string filename = _fileSystem.FindSmartPlaylistFilePath(smartPlaylistId); string filename = _fileSystem.FindSmartPlaylistFilePath(smartPlaylistId);
@ -31,9 +33,15 @@ namespace Jellyfin.Plugin.SmartPlaylist {
await using var w = File.Create(filename); await using var w = File.Create(filename);
await JsonSerializer.SerializeAsync(w, smartPlaylist).ConfigureAwait(false); await JsonSerializer.SerializeAsync(w, smartPlaylist).ConfigureAwait(false);
} }
public void DeleteSmartPlaylist(SmartPlaylistId smartPlaylistId) { private void DeleteSmartPlaylistById(SmartPlaylistId smartPlaylistId) {
try {
string filename = _fileSystem.FindSmartPlaylistFilePath(smartPlaylistId); string filename = _fileSystem.FindSmartPlaylistFilePath(smartPlaylistId);
if (File.Exists(filename)) { File.Delete(filename); } if (File.Exists(filename)) { File.Delete(filename); }
} catch (System.InvalidOperationException) {}
}
public void DeleteSmartPlaylist(SmartPlaylistDto smartPlaylist) {
if (File.Exists(smartPlaylist.Filename)) { File.Delete(smartPlaylist.Filename); }
DeleteSmartPlaylistById(smartPlaylist.Id);
} }
} }
} }