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

View file

@ -30,6 +30,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
this["length"] = _length;
this["haskeys"] = _haskeys;
this["getitems"] = _getitems;
this["invoke"] = _invoke;
//this[new Symbol("!=")] = _ne;
}
@ -202,20 +203,27 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
r.Add(Compiler.Object.FromBase(pi.GetValue(o.value)));
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);
if (fi != null) {
r.Add(Compiler.Object.FromBase(fi.GetValue(o.value)));
continue;
}
throw new ApplicationException();
throw new ApplicationException($"{o.value} has no property or field {s.value}");
}
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> {

View file

@ -38,6 +38,8 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem;
private readonly IPlaylistManager _playlistManager;
private readonly IStore _store;
@ -46,15 +48,19 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
ILogger<Plugin> logger,
ILibraryManager libraryManager,
IUserManager userManager,
IProviderManager providerManager,
IFileSystem fileSystem,
IPlaylistManager playlistManager,
IServerApplicationPaths serverApplicationPaths
) {
_logger = logger;
_libraryManager = libraryManager;
_userManager = userManager;
_providerManager = providerManager;
_fileSystem = fileSystem;
_playlistManager = playlistManager;
_store = new Store(new FileSystem(serverApplicationPaths));
_store = new Store(new SmartPlaylistFileSystem(serverApplicationPaths));
}
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 {
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) {
List<Guid> results = new List<Guid>();
Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse();
Executor executor = new Executor();
executor.environment["user"] = new Lisp_Object(user);
foreach (var i in items) {
executor.environment["item"] = new Lisp_Object(i);
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_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 {
_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();
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.Id);
dto.Id = CreateNewPlaylist(dto, user);
_store.DeleteSmartPlaylist(dto);
dto.Id = CreateNewPlaylist(dto);
await _store.SaveSmartPlaylistAsync(dto);
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) {
var req = new InternalItemsQuery(user)
{
IncludeItemTypes = AvailableFilterItems,
Recursive = true
};
var existingItems = playlist.GetChildren(user, false, req).Select(x => x.Id.ToString()).ToList();
await _playlistManager.RemoveItemFromPlaylistAsync(playlist.Id.ToString(), existingItems);
// fuck if I know
if (_libraryManager.GetItemById(playlist.Id) is not Playlist playlist_new) {
throw new ArgumentException("");
}
var existingItems = playlist_new.GetManageableItems().ToList();
await _playlistManager.RemoveItemFromPlaylistAsync(playlist.Id.ToString(), existingItems.Select(x => x.Item1.Id));
}
}
}

View file

@ -1,11 +1,28 @@
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 string FileName { get; set; }
public string? Name { get; set; }
public UserId User { get; set; }
public string Program { get; set; }
public int MaxItems { 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) {
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 class FileSystem : ISmartPlaylistFileSystem {
public FileSystem(IServerApplicationPaths serverApplicationPaths) {
public class SmartPlaylistFileSystem : ISmartPlaylistFileSystem {
public SmartPlaylistFileSystem(IServerApplicationPaths serverApplicationPaths) {
StoragePath = Path.Combine(serverApplicationPaths.DataPath, "smartplaylists");
if (!Directory.Exists(StoragePath)) { Directory.CreateDirectory(StoragePath); }
}

View file

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