Compare commits
6 commits
3c3ddc9e83
...
f41485cecf
Author | SHA1 | Date | |
---|---|---|---|
f41485cecf | |||
4537a3aee3 | |||
396384fd71 | |||
6ee9bd7f67 | |||
4e5cb8e64e | |||
906bfb9eeb |
11 changed files with 150 additions and 77 deletions
|
@ -29,6 +29,10 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
E Equals(T other);
|
E Equals(T other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IInner {
|
||||||
|
public object Inner();
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class Expression: IComparable<Expression, bool> {
|
public abstract class Expression: IComparable<Expression, bool> {
|
||||||
public override abstract string? ToString();
|
public override abstract string? ToString();
|
||||||
public abstract override int GetHashCode();
|
public abstract override int GetHashCode();
|
||||||
|
@ -48,7 +52,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class Atom : Expression {}
|
public abstract class Atom : Expression {}
|
||||||
public class Scalar<V> : Atom where V : notnull {
|
public class Scalar<V> : Atom, IInner where V : notnull {
|
||||||
protected V _value;
|
protected V _value;
|
||||||
public Scalar(V value) {
|
public Scalar(V value) {
|
||||||
_value = value;
|
_value = value;
|
||||||
|
@ -68,6 +72,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
public V Value() {
|
public V Value() {
|
||||||
return _value;
|
return _value;
|
||||||
}
|
}
|
||||||
|
public object Inner() {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public class Symbol : Atom {
|
public class Symbol : Atom {
|
||||||
private string _name;
|
private string _name;
|
||||||
|
@ -276,18 +283,24 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
Executor new_e = new Executor(new SubEnvironment(e.environment), e.builtins, e.builtinsLater);
|
Executor new_e = new Executor(new SubEnvironment(e.environment), e.builtins, e.builtinsLater);
|
||||||
var _params = _parameters.Select(x => x.Name()).ToArray();
|
var _params = _parameters.Select(x => x.Name()).ToArray();
|
||||||
var idx_rest = -1;
|
var idx_rest = -1;
|
||||||
|
|
||||||
|
IList<(string, Expression)> name_args = new List<(string, Expression)>();
|
||||||
for (var i = 0; i < _parameters.Count(); i++) {
|
for (var i = 0; i < _parameters.Count(); i++) {
|
||||||
var name = _params[i];
|
var name = _params[i];
|
||||||
if (name.Equals(".")) {
|
if (name.Equals(".")) {
|
||||||
idx_rest = i + 1;
|
idx_rest = i + 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
new_e.environment.Set(name, _eval(e, args[i]));
|
name_args.Add((name, _eval(e, args[i])));
|
||||||
}
|
}
|
||||||
if (idx_rest > 0) {
|
if (idx_rest > 0) {
|
||||||
new_e.environment.Set(_params[idx_rest], Cons.FromList(args.Skip(idx_rest - 1).Select(x => _eval(e, x))));
|
name_args.Add((_params[idx_rest], Cons.FromList(args.Skip(idx_rest - 1).Select(x => _eval(e, x)))));
|
||||||
}
|
}
|
||||||
return new_e.eval(_body);
|
foreach (var na in name_args) {
|
||||||
|
new_e.environment.Set(na.Item1, na.Item2);
|
||||||
|
}
|
||||||
|
var r = new_e.eval(_body);
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,15 +39,13 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
public class DefaultEnvironment: Environment {
|
public class DefaultEnvironment: Environment {
|
||||||
public DefaultEnvironment() {
|
public DefaultEnvironment() {
|
||||||
var e = new Executor();
|
var e = new Executor();
|
||||||
this["if"] = e.eval("(lambda* (condition a b) ( cond ((eval condition) (eval a)) (t (eval b))))");
|
|
||||||
this["null"] = new Symbol("not");
|
this["null"] = new Symbol("not");
|
||||||
this["list"] = e.eval("(lambda (. args) args)");
|
this["list"] = e.eval("(lambda (. args) args)");
|
||||||
this["find"] = e.eval("(lambda (item list_) (if (null list_) nil (if (= item (car list_)) (car list_) (find item (cdr list_)))))");
|
this["find"] = e.eval("(lambda (item list_) (if (null list_) nil (if (= item (car list_)) (car list_) (find item (cdr list_)))))");
|
||||||
this["map"] = e.eval("(lambda (fc l) (if (null l) nil (cons (fc (car l)) (map fc (cdr l)))))");
|
this["map"] = e.eval("(lambda (fc l) (if (null l) nil (cons (fc (car l)) (map fc (cdr l)))))");
|
||||||
this["and"] = e.eval("(lambda (l) (if (null l) t (if (car l) (and (cdr l)) nil)))");
|
this["fold"] = e.eval("(lambda (fc i l) (if (null l) i (fold fc (fc i (car l)) (cdr l))))");
|
||||||
this["or"] = e.eval("(lambda (l) (if (null l) nil (if (car l) t (or (cdr l)))))");
|
this["any"] = e.eval("(lambda (fc l) (apply or (map fc l)))");
|
||||||
this["any"] = e.eval("(lambda (fc l) (or (map fc l)))");
|
this["all"] = e.eval("(lambda (fc l) (apply and (map fc l)))");
|
||||||
this["all"] = e.eval("(lambda (fc l) (and (map fc l)))");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +102,10 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
this[">"] = (x) => _cmp((Integer a, Integer b) => a > b, x);
|
this[">"] = (x) => _cmp((Integer a, Integer b) => a > b, x);
|
||||||
this[">="] = (x) => _cmp((Integer a, Integer b) => a >= b, x);
|
this[">="] = (x) => _cmp((Integer a, Integer b) => a >= b, x);
|
||||||
this["!="] = (x) => _cmp((Integer a, Integer b) => a != b, x);
|
this["!="] = (x) => _cmp((Integer a, Integer b) => a != b, x);
|
||||||
this["not"] = (x) => (x.First() == Boolean.FALSE) ? Boolean.TRUE : Boolean.FALSE;
|
this["not"] = (x) => {
|
||||||
|
return (x.First() == Boolean.FALSE) ? Boolean.TRUE : Boolean.FALSE;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
this["haskeys"] = _haskeys;
|
this["haskeys"] = _haskeys;
|
||||||
this["getitems"] = _getitems;
|
this["getitems"] = _getitems;
|
||||||
|
@ -160,7 +161,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
return Boolean.TRUE;
|
return Boolean.TRUE;
|
||||||
}
|
}
|
||||||
private static Expression _getitems(IEnumerable<Expression> args) {
|
private static Expression _getitems(IEnumerable<Expression> args) {
|
||||||
Object o = (Object) args.First();
|
Object o = new Object(((IInner) args.First()).Inner());
|
||||||
IList<Expression> r = new List<Expression>();
|
IList<Expression> r = new List<Expression>();
|
||||||
foreach (var e in args.Skip(1)) {
|
foreach (var e in args.Skip(1)) {
|
||||||
String s = (String) e;
|
String s = (String) e;
|
||||||
|
@ -180,7 +181,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Expression _invoke(IEnumerable<Expression> args) {
|
private static Expression _invoke(IEnumerable<Expression> args) {
|
||||||
Object o = (Object) args.First();
|
Object o = new Object(((IInner) args.First()).Inner());
|
||||||
String s = (String) args.Skip(1).First();
|
String s = (String) args.Skip(1).First();
|
||||||
IEnumerable<Expression> l;
|
IEnumerable<Expression> l;
|
||||||
if (args.Skip(2).First() is Boolean lb && lb == Boolean.FALSE) {
|
if (args.Skip(2).First() is Boolean lb && lb == Boolean.FALSE) {
|
||||||
|
@ -190,25 +191,31 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
} else {
|
} else {
|
||||||
throw new ApplicationException($"Expected a list of arguments, got {args.Skip(2).First()}");
|
throw new ApplicationException($"Expected a list of arguments, got {args.Skip(2).First()}");
|
||||||
}
|
}
|
||||||
|
object[]? l_ = l.Select<Expression, object>(x => {
|
||||||
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()}");
|
|
||||||
}
|
|
||||||
object?[]? l_ = l.Select<Expression, object?>(x => {
|
|
||||||
switch (x) {
|
switch (x) {
|
||||||
case Integer s:
|
case Integer s:
|
||||||
return s.Value();
|
return s.Value();
|
||||||
case Boolean b:
|
case Boolean b:
|
||||||
return b.Value();
|
return b.Value();
|
||||||
|
case String s:
|
||||||
|
return s.Value();
|
||||||
case Object o:
|
case Object o:
|
||||||
return o.Value();
|
return o.Value();
|
||||||
case Cons c:
|
case Cons c:
|
||||||
return c.ToList().ToList();
|
return c.ToList().ToList();
|
||||||
}
|
}
|
||||||
return null;
|
throw new ApplicationException($"Unhandled value {x} (type {x.GetType()})");
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
Type[] l_types = l_.Select( x => {
|
||||||
|
return x.GetType();
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
IList<Expression> r = new List<Expression>();
|
||||||
|
MethodInfo? mi = o.Value().GetType().GetMethod(s.Value(), l_types);
|
||||||
|
if (mi == null) {
|
||||||
|
throw new ApplicationException($"{o.Value()} has not method {s.Value()}");
|
||||||
|
}
|
||||||
|
|
||||||
return Object.FromBase(mi.Invoke(o.Value(), l_));
|
return Object.FromBase(mi.Invoke(o.Value(), l_));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,11 +225,36 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
this["quote"] = _quote;
|
this["quote"] = _quote;
|
||||||
this["eval"] = _eval;
|
this["eval"] = _eval;
|
||||||
this["cond"] = _cond;
|
this["cond"] = _cond;
|
||||||
|
this["if"] = _if;
|
||||||
this["define"] = _define;
|
this["define"] = _define;
|
||||||
this["let"] = _let;
|
this["let"] = _let;
|
||||||
this["let*"] = _let_star;
|
this["let*"] = _let_star;
|
||||||
this["lambda"] = _lambda;
|
this["lambda"] = _lambda;
|
||||||
this["lambda*"] = _lambda_star;
|
this["lambda*"] = _lambda_star;
|
||||||
|
this["apply"] = _apply;
|
||||||
|
|
||||||
|
this["and"] = (e, x) => {
|
||||||
|
Expression? r = null;
|
||||||
|
foreach (var xi in x) {
|
||||||
|
r = e.eval(xi);
|
||||||
|
if (r == Boolean.FALSE) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (r is null) {
|
||||||
|
return Boolean.FALSE;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
this["or"] = (e, x) => {
|
||||||
|
foreach (var xi in x) {
|
||||||
|
var r = e.eval(xi);
|
||||||
|
if (r != Boolean.FALSE) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Boolean.FALSE;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
private static Expression _quote(Executor e, IEnumerable<Expression> args) {
|
private static Expression _quote(Executor e, IEnumerable<Expression> args) {
|
||||||
return args.First();
|
return args.First();
|
||||||
|
@ -243,6 +275,12 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
}
|
}
|
||||||
return Boolean.FALSE;
|
return Boolean.FALSE;
|
||||||
}
|
}
|
||||||
|
private static Expression _if(Executor e, IEnumerable<Expression> args) {
|
||||||
|
if (e.eval(args.First()).Equals(Boolean.FALSE)) {
|
||||||
|
return e.eval(args.Skip(2).First());
|
||||||
|
}
|
||||||
|
return e.eval(args.Skip(1).First());
|
||||||
|
}
|
||||||
private static Expression _define(Executor e, IEnumerable<Expression> args) {
|
private static Expression _define(Executor e, IEnumerable<Expression> args) {
|
||||||
Symbol refname = (Symbol) args.First();
|
Symbol refname = (Symbol) args.First();
|
||||||
e.environment.Parent(true).Set(refname.Name(), args.Skip(1).Select(x => e.eval(x)).First());
|
e.environment.Parent(true).Set(refname.Name(), args.Skip(1).Select(x => e.eval(x)).First());
|
||||||
|
@ -269,7 +307,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
}
|
}
|
||||||
Symbol refname = (Symbol) pair_cons.Item1;
|
Symbol refname = (Symbol) pair_cons.Item1;
|
||||||
Expression exp_ = ((Cons) pair_cons.Item2).Item1;
|
Expression exp_ = ((Cons) pair_cons.Item2).Item1;
|
||||||
vars.Add((refname, exp_));
|
vars.Add((refname, e.eval(exp_)));
|
||||||
}
|
}
|
||||||
foreach (var pair in vars) {
|
foreach (var pair in vars) {
|
||||||
new_e.environment.Set(pair.Item1.Name(), pair.Item2);
|
new_e.environment.Set(pair.Item1.Name(), pair.Item2);
|
||||||
|
@ -294,6 +332,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
}
|
}
|
||||||
return new Procedure(proc_args, args.Skip(1).First(), false);
|
return new Procedure(proc_args, args.Skip(1).First(), false);
|
||||||
}
|
}
|
||||||
|
private static Expression _apply(Executor e, IEnumerable<Expression> args) {
|
||||||
|
return e.eval(new Cons(args.First(), e.eval(args.Skip(1).First())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Executor {
|
public class Executor {
|
||||||
|
|
|
@ -2,8 +2,19 @@ using MediaBrowser.Model.Plugins;
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.SmartPlaylist {
|
namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
public class PluginConfiguration : BasePluginConfiguration {
|
public class PluginConfiguration : BasePluginConfiguration {
|
||||||
public PluginConfiguration(
|
public PluginConfiguration() {
|
||||||
) {
|
InitialProgram = """
|
||||||
}
|
(begin
|
||||||
|
(define lower (lambda (s) (invoke s "ToLower" nil)))
|
||||||
|
(define is-genre (lambda (g g-list) (any (lambda (x) (invoke (lower x) "Contains" (list (lower g)))) g-list)))
|
||||||
|
(define is-genre-exact (lambda (g g-list) (find g g-list)))
|
||||||
|
(define genre-list (lambda nil (let (_g (getitems item "Genres")) (if (null _g) nil (car _g)))))
|
||||||
|
(define is-favorite (lambda nil (invoke item "IsFavoriteOrLiked" (list user)))))
|
||||||
|
|
||||||
|
|
||||||
|
(define is-favourite is-favorite)
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
public string InitialProgram { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,11 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse();
|
Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse();
|
||||||
Executor executor = new Executor(new DefaultEnvironment());
|
Executor executor = new Executor(new DefaultEnvironment());
|
||||||
executor.environment.Set("user", new Lisp_Object(user));
|
executor.environment.Set("user", new Lisp_Object(user));
|
||||||
|
if (Plugin.Instance is not null) {
|
||||||
|
executor.eval(Plugin.Instance.Configuration.InitialProgram);
|
||||||
|
} else {
|
||||||
|
throw new ApplicationException("Plugin Instance is not yet initialized");
|
||||||
|
}
|
||||||
foreach (var i in items) {
|
foreach (var i in items) {
|
||||||
executor.environment.Set("item", new Lisp_Object(i));
|
executor.environment.Set("item", new Lisp_Object(i));
|
||||||
var r = executor.eval(expression);
|
var r = executor.eval(expression);
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class SmartPlaylistDto : ISerializable {
|
public class SmartPlaylistDto : ISerializable {
|
||||||
private static string DEFAULT_PROGRAM = "(begin (invoke item 'IsFavoriteOrLiked' (user)))";
|
private static string DEFAULT_PROGRAM = "(begin (invoke item \"IsFavoriteOrLiked\" (list user)))";
|
||||||
public SmartPlaylistId Id { get; set; }
|
public SmartPlaylistId Id { get; set; }
|
||||||
public SmartPlaylistLinkDto[] Playlists { get; set; }
|
public SmartPlaylistLinkDto[] Playlists { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
|
@ -16,13 +16,21 @@ namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
}
|
}
|
||||||
public string StoragePath { get; }
|
public string StoragePath { get; }
|
||||||
public string GetSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) {
|
public string GetSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) {
|
||||||
return Path.Combine(StoragePath, $"{smartPlaylistId}.json");
|
return Path.Combine(StoragePath, $"{smartPlaylistId}.yaml");
|
||||||
}
|
}
|
||||||
public string FindSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) {
|
public string FindSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) {
|
||||||
return Directory.GetFiles(StoragePath, $"{smartPlaylistId}.json", SearchOption.AllDirectories).First();
|
return Directory.GetFiles(StoragePath, $"{smartPlaylistId}.yaml", SearchOption.AllDirectories).Concat(
|
||||||
|
Directory.GetFiles(StoragePath, $"{smartPlaylistId}.yml", SearchOption.AllDirectories)
|
||||||
|
).Concat(
|
||||||
|
Directory.GetFiles(StoragePath, $"{smartPlaylistId}.json", SearchOption.AllDirectories)
|
||||||
|
).First();
|
||||||
}
|
}
|
||||||
public string[] FindAllSmartPlaylistFilePaths() {
|
public string[] FindAllSmartPlaylistFilePaths() {
|
||||||
return Directory.GetFiles(StoragePath, "*.json", SearchOption.AllDirectories);
|
return Directory.GetFiles(StoragePath, "*.yaml", SearchOption.AllDirectories).Concat(
|
||||||
|
Directory.GetFiles(StoragePath, "*.yml", SearchOption.AllDirectories)
|
||||||
|
).Concat(
|
||||||
|
Directory.GetFiles(StoragePath, "*.json", SearchOption.AllDirectories)
|
||||||
|
).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Text.Json;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.SmartPlaylist {
|
namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
public interface IStore {
|
public interface IStore {
|
||||||
|
@ -14,14 +14,21 @@ namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
}
|
}
|
||||||
private async Task<SmartPlaylistDto> LoadPlaylistAsync(string filename) {
|
private async Task<SmartPlaylistDto> LoadPlaylistAsync(string filename) {
|
||||||
await using var r = File.OpenRead(filename);
|
var r = File.ReadAllText(filename);
|
||||||
var dto = (await JsonSerializer.DeserializeAsync<SmartPlaylistDto>(r).ConfigureAwait(false));
|
if (r.Equals("")) {
|
||||||
if (dto == null) {
|
r = "{}";
|
||||||
|
}
|
||||||
|
var dto = new DeserializerBuilder().Build().Deserialize<SmartPlaylistDto>(r);
|
||||||
|
if (dto == null)
|
||||||
|
{
|
||||||
throw new ApplicationException("");
|
throw new ApplicationException("");
|
||||||
}
|
}
|
||||||
if (dto.Id == Path.GetFileNameWithoutExtension(filename)) {
|
if (dto.Id != Path.GetFileNameWithoutExtension(filename)) {
|
||||||
dto.Id = Path.GetFileNameWithoutExtension(filename);
|
dto.Id = Path.GetFileNameWithoutExtension(filename);
|
||||||
}
|
}
|
||||||
|
if (dto.Name != Path.GetFileNameWithoutExtension(filename)) {
|
||||||
|
dto.Name = Path.GetFileNameWithoutExtension(filename);
|
||||||
|
}
|
||||||
if (dto.Filename != filename) {
|
if (dto.Filename != filename) {
|
||||||
dto.Filename = filename;
|
dto.Filename = filename;
|
||||||
}
|
}
|
||||||
|
@ -38,8 +45,8 @@ namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
}
|
}
|
||||||
public async Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist) {
|
public async Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist) {
|
||||||
string filename = _fileSystem.GetSmartPlaylistFilePath(smartPlaylist.Id);
|
string filename = _fileSystem.GetSmartPlaylistFilePath(smartPlaylist.Id);
|
||||||
await using var w = File.Create(filename);
|
var text = new SerializerBuilder().Build().Serialize(smartPlaylist);
|
||||||
await JsonSerializer.SerializeAsync(w, smartPlaylist).ConfigureAwait(false);
|
File.WriteAllText(filename, text);
|
||||||
}
|
}
|
||||||
private void DeleteSmartPlaylistById(SmartPlaylistId smartPlaylistId) {
|
private void DeleteSmartPlaylistById(SmartPlaylistId smartPlaylistId) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -12,5 +12,7 @@ description: |
|
||||||
category: "General"
|
category: "General"
|
||||||
artifacts:
|
artifacts:
|
||||||
- jellyfin-smart-playlist.dll
|
- jellyfin-smart-playlist.dll
|
||||||
|
- YamlDotNet.dll
|
||||||
changelog: |
|
changelog: |
|
||||||
|
- Switch to yaml loading.
|
||||||
- Initial Alpha release.
|
- Initial Alpha release.
|
||||||
|
|
|
@ -5,32 +5,14 @@
|
||||||
<title>Template</title>
|
<title>Template</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="TemplateConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox">
|
<div id="SmartPlaylistConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox">
|
||||||
<div data-role="content">
|
<div data-role="content">
|
||||||
<div class="content-primary">
|
<div class="content-primary">
|
||||||
<form id="TemplateConfigForm">
|
<form id="SmartPlaylistConfigForm">
|
||||||
<div class="selectContainer">
|
|
||||||
<label class="selectLabel" for="Options">Several Options</label>
|
|
||||||
<select is="emby-select" id="Options" name="Options" class="emby-select-withcolor emby-select">
|
|
||||||
<option id="optOneOption" value="OneOption">One Option</option>
|
|
||||||
<option id="optAnotherOption" value="AnotherOption">Another Option</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<label class="inputLabel inputLabelUnfocused" for="AnInteger">An Integer</label>
|
<label class="inputLabel inputLabelUnfocused" for="InitialProgram">Initial Program</label>
|
||||||
<input id="AnInteger" name="AnInteger" type="number" is="emby-input" min="0" />
|
<div class="fieldDescription">A program which can set up the environment</div>
|
||||||
<div class="fieldDescription">A Description</div>
|
<input id="InitialProgram" name="InitialProgram" type="text" is="emby-input" />
|
||||||
</div>
|
|
||||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
|
||||||
<label class="emby-checkbox-label">
|
|
||||||
<input id="TrueFalseSetting" name="TrueFalseCheckBox" type="checkbox" is="emby-checkbox" />
|
|
||||||
<span>A Checkbox</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="inputContainer">
|
|
||||||
<label class="inputLabel inputLabelUnfocused" for="AString">A String</label>
|
|
||||||
<input id="AString" name="AString" type="text" is="emby-input" />
|
|
||||||
<div class="fieldDescription">Another Description</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
|
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
|
||||||
|
@ -41,31 +23,25 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var TemplateConfig = {
|
var SmartPlaylistConfig = {
|
||||||
pluginUniqueId: 'dd2326e3-4d3e-4bfc-80e6-28502c1131df'
|
pluginUniqueId: 'dd2326e3-4d3e-4bfc-80e6-28502c1131df'
|
||||||
};
|
};
|
||||||
|
|
||||||
document.querySelector('#TemplateConfigPage')
|
document.querySelector('#SmartPlaylistConfigPage')
|
||||||
.addEventListener('pageshow', function() {
|
.addEventListener('pageshow', function() {
|
||||||
Dashboard.showLoadingMsg();
|
Dashboard.showLoadingMsg();
|
||||||
ApiClient.getPluginConfiguration(TemplateConfig.pluginUniqueId).then(function (config) {
|
ApiClient.getPluginConfiguration(SmartPlaylistConfig.pluginUniqueId).then(function (config) {
|
||||||
document.querySelector('#Options').value = config.Options;
|
document.querySelector('#InitialProgram').value = config.InitialProgram;
|
||||||
document.querySelector('#AnInteger').value = config.AnInteger;
|
|
||||||
document.querySelector('#TrueFalseSetting').checked = config.TrueFalseSetting;
|
|
||||||
document.querySelector('#AString').value = config.AString;
|
|
||||||
Dashboard.hideLoadingMsg();
|
Dashboard.hideLoadingMsg();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector('#TemplateConfigForm')
|
document.querySelector('#SmartPlaylistConfigForm')
|
||||||
.addEventListener('submit', function(e) {
|
.addEventListener('submit', function(e) {
|
||||||
Dashboard.showLoadingMsg();
|
Dashboard.showLoadingMsg();
|
||||||
ApiClient.getPluginConfiguration(TemplateConfig.pluginUniqueId).then(function (config) {
|
ApiClient.getPluginConfiguration(SmartPlaylistConfig.pluginUniqueId).then(function (config) {
|
||||||
config.Options = document.querySelector('#Options').value;
|
config.InitialProgram = document.querySelector('#InitialProgram').value;
|
||||||
config.AnInteger = document.querySelector('#AnInteger').value;
|
ApiClient.updatePluginConfiguration(SmartPlaylistConfig.pluginUniqueId, config).then(function (result) {
|
||||||
config.TrueFalseSetting = document.querySelector('#TrueFalseSetting').checked;
|
|
||||||
config.AString = document.querySelector('#AString').value;
|
|
||||||
ApiClient.updatePluginConfiguration(TemplateConfig.pluginUniqueId, config).then(function (result) {
|
|
||||||
Dashboard.processPluginConfigurationUpdateResult(result);
|
Dashboard.processPluginConfigurationUpdateResult(result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<RootNamespace>jellyfin_smart_playlist</RootNamespace>
|
<RootNamespace>Jellyfin.Plugin.SmartPlaylist</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.1.1.0</Version>
|
<Version>0.1.1.0</Version>
|
||||||
|
@ -11,6 +11,12 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Jellyfin.Controller" Version="10.10.0" />
|
<PackageReference Include="Jellyfin.Controller" Version="10.10.0" />
|
||||||
<PackageReference Include="Jellyfin.Model" Version="10.10.0" />
|
<PackageReference Include="Jellyfin.Model" Version="10.10.0" />
|
||||||
|
<PackageReference Include="YamlDotNet" Version="16.1.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="configPage.html"/>
|
||||||
|
<EmbeddedResource Include="configPage.html"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -192,7 +192,6 @@ namespace Tests
|
||||||
(define null (lambda* (x) (cond ((eval x) nil) (t t))))
|
(define null (lambda* (x) (cond ((eval x) nil) (t t))))
|
||||||
(null (quote (1 2))))
|
(null (quote (1 2))))
|
||||||
""").ToString());
|
""").ToString());
|
||||||
|
|
||||||
}
|
}
|
||||||
[Fact]
|
[Fact]
|
||||||
public static void ObjectTest() {
|
public static void ObjectTest() {
|
||||||
|
@ -212,18 +211,23 @@ namespace Tests
|
||||||
Executor e = new Executor(new DefaultEnvironment());
|
Executor e = new Executor(new DefaultEnvironment());
|
||||||
Assert.Equal("1", e.eval("(if nil 0 1)").ToString());
|
Assert.Equal("1", e.eval("(if nil 0 1)").ToString());
|
||||||
Assert.Equal("0", e.eval("(if t 0 1)").ToString());
|
Assert.Equal("0", e.eval("(if t 0 1)").ToString());
|
||||||
|
Assert.Equal("5", e.eval("(if t (if t 5 nil) nil)").ToString());
|
||||||
|
Assert.Equal("nil", e.eval("(if t (if nil 5 nil) nil)").ToString());
|
||||||
Assert.Equal("(1 2 3)", e.eval("(list 1 2 3)").ToString());
|
Assert.Equal("(1 2 3)", e.eval("(list 1 2 3)").ToString());
|
||||||
Assert.Equal("3", e.eval("(find 3 (list 1 2 3 4))").ToString());
|
Assert.Equal("3", e.eval("(find 3 (list 1 2 3 4))").ToString());
|
||||||
Assert.Equal("nil", e.eval("(find 0 (list 1 2 3 4))").ToString());
|
Assert.Equal("nil", e.eval("(find 0 (list 1 2 3 4))").ToString());
|
||||||
Assert.Equal("(2 4 6)", e.eval("(map (lambda (x) (* x 2)) (quote (1 2 3)))").ToString());
|
Assert.Equal("(2 4 6)", e.eval("(map (lambda (x) (* x 2)) (quote (1 2 3)))").ToString());
|
||||||
Assert.Equal("nil", e.eval("(and (quote (1 2 3 nil)))").ToString());
|
Assert.Equal("nil", e.eval("(and 1 2 3 nil)").ToString());
|
||||||
Assert.Equal("t", e.eval("(and (quote (1 2 3 4)))").ToString());
|
Assert.Equal("t", e.eval("(and t t t t)").ToString());
|
||||||
Assert.Equal("t", e.eval("(or (quote (nil nil 1 nil)))").ToString());
|
Assert.Equal("t", e.eval("(or nil nil t nil)").ToString());
|
||||||
Assert.Equal("nil", e.eval("(or (quote (nil nil nil nil)))").ToString());
|
Assert.Equal("nil", e.eval("(or nil nil nil nil)").ToString());
|
||||||
Assert.Equal("t", e.eval("(any (lambda (x) (= x 2)) (list 1 2 3 4 5 6))").ToString());
|
Assert.Equal("t", e.eval("(any (lambda (x) (= x 2)) (list 1 2 3 4 5 6))").ToString());
|
||||||
Assert.Equal("nil", e.eval("(any (lambda (x) (= x 2)) (list 1 3 4 5 6))").ToString());
|
Assert.Equal("nil", e.eval("(any (lambda (x) (= x 2)) (list 1 3 4 5 6))").ToString());
|
||||||
|
Assert.Equal("nil", e.eval("(any (lambda (x) (= x 2)) nil)").ToString());
|
||||||
Assert.Equal("t", e.eval("(all (lambda (x) (= 1 (% x 2))) (list 1 3 5))").ToString());
|
Assert.Equal("t", e.eval("(all (lambda (x) (= 1 (% x 2))) (list 1 3 5))").ToString());
|
||||||
Assert.Equal("nil", e.eval("(all (lambda (x) (= 1 (% x 2))) (list 1 3 4 5))").ToString());
|
Assert.Equal("nil", e.eval("(all (lambda (x) (= 1 (% x 2))) (list 1 3 4 5))").ToString());
|
||||||
|
Assert.Equal("nil", e.eval("(all (lambda (x) (= x 2)) nil)").ToString());
|
||||||
|
Assert.Equal("10", e.eval("(fold (lambda (a b) (+ a b)) 0 (list 1 2 3 4))").ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue