Engine.cs
using System;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace Jellyfin.Plugin.SmartPlaylist.QueryEngine
{
// This is taken entirely from https://stackoverflow.com/questions/6488034/how-to-implement-a-rule-engine
public clast Engine
{
static System.Linq.Expressions.Expression BuildExpr(Expression r, ParameterExpression param)
{
var left = MemberExpression.Property(param, r.MemberName);
var tProp = typeof(T).GetProperty(r.MemberName).PropertyType;
ExpressionType tBinary;
// is the operator a known .NET operator?
if (ExpressionType.TryParse(r.Operator, out tBinary))
{
var right = System.Linq.Expressions.Expression.Constant(Convert.ChangeType(r.TargetValue, tProp));
// use a binary operation, e.g. 'Equal' -> 'u.Age == 15'
return System.Linq.Expressions.Expression.MakeBinary(tBinary, left, right);
}
if (r.Operator == "MatchRegex" || r.Operator == "NotMatchRegex")
{
var regex = new Regex(r.TargetValue);
var method = typeof(Regex).GetMethod("IsMatch", new[] {typeof(string)});
Debug.astert(method != null, nameof(method) + " != null");
var callInstance = System.Linq.Expressions.Expression.Constant(regex);
var toStringMethod = tProp.GetMethod("ToString", new Type[0]);
Debug.astert(toStringMethod != null, nameof(toStringMethod) + " != null");
var methodParam = System.Linq.Expressions.Expression.Call(left, toStringMethod);
var call = System.Linq.Expressions.Expression.Call(callInstance, method, methodParam);
if (r.Operator == "MatchRegex") return call;
if (r.Operator == "NotMatchRegex") return System.Linq.Expressions.Expression.Not(call);
}
if (tProp.Name == "String")
{
var method = tProp.GetMethod(r.Operator, new Type[] { typeof(string) });
var tParam = method.GetParameters()[0].ParameterType;
var right = System.Linq.Expressions.Expression.Constant(Convert.ChangeType(r.TargetValue, tParam));
// use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)'
return System.Linq.Expressions.Expression.Call(left, method, right);
}
else {
var method = tProp.GetMethod(r.Operator);
var tParam = method.GetParameters()[0].ParameterType;
var right = System.Linq.Expressions.Expression.Constant(Convert.ChangeType(r.TargetValue, tParam));
// use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)'
return System.Linq.Expressions.Expression.Call(left, method, right);
}
}
public static Func CompileRule(Expression r)
{
var paramUser = System.Linq.Expressions.Expression.Parameter(typeof(Operand));
System.Linq.Expressions.Expression expr = BuildExpr(r, paramUser);
// build a lambda function User->bool and compile it
var value = System.Linq.Expressions.Expression.Lambda(expr, paramUser).Compile(true);
return value;
}
public static List FixRuleSets(List rulesets)
{
foreach (var rules in rulesets)
{
FixRules(rules);
}
return rulesets;
}
public static ExpressionSet FixRules(ExpressionSet rules)
{
foreach (var rule in rules.Expressions)
{
if (rule.MemberName == "PremiereDate")
{
var somedate = DateTime.Parse(rule.TargetValue);
rule.TargetValue = ConvertToUnixTimestamp(somedate).ToString();
}
}
return rules;
}
public static double ConvertToUnixTimestamp(DateTime date)
{
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
TimeSpan diff = date.ToUniversalTime() - origin;
return Math.Floor(diff.TotalSeconds);
}
}
}