From be5af99158cd4a36bf1f068520997cbd8fa7985b Mon Sep 17 00:00:00 2001 From: da3dsoul Date: Mon, 2 Oct 2023 16:32:33 -0400 Subject: [PATCH] API for expression listing --- .../API/v3/Controllers/FilterController.cs | 100 +++++++++++++++ Shoko.Server/API/v3/Models/Shoko/Filter.cs | 116 ++++++++++++++++++ .../Files/HasAudioLanguageExpression.cs | 3 + .../Files/HasSharedAudioLanguageExpression.cs | 3 + .../HasSharedSubtitleLanguageExpression.cs | 3 + .../Files/HasSharedVideoSourceExpression.cs | 12 ++ .../Files/HasSubtitleLanguageExpression.cs | 3 + .../Filters/Files/HasVideoSourceExpression.cs | 12 ++ Shoko.Server/Filters/FilterExpression.cs | 8 +- Shoko.Server/Filters/FilterExtensions.cs | 12 +- Shoko.Server/Filters/Filterable.cs | 19 +++ .../Filters/Functions/DateAddFunction.cs | 1 + .../Filters/Functions/DateDiffFunction.cs | 1 + .../Filters/Functions/TodayFunction.cs | 1 + .../Filters/Info/HasAnimeTypeExpression.cs | 11 ++ .../Filters/Info/HasCustomTagExpression.cs | 3 +- .../HasMissingEpisodesCollectingExpression.cs | 1 + .../Info/HasMissingEpisodesExpression.cs | 1 + .../Filters/Info/HasNameExpression.cs | 1 + .../Filters/Info/HasTMDbLinkExpression.cs | 1 + Shoko.Server/Filters/Info/HasTagExpression.cs | 1 + .../Filters/Info/HasTraktLinkExpression.cs | 1 + .../Filters/Info/HasTvDBLinkExpression.cs | 1 + .../Filters/Info/InSeasonExpression.cs | 8 +- Shoko.Server/Filters/Info/InYearExpression.cs | 1 + .../Filters/Info/IsFinishedExpression.cs | 1 + .../Filters/Info/MissingTMDbLinkExpression.cs | 1 + .../Info/MissingTraktLinkExpression.cs | 1 + .../Filters/Info/MissingTvDBLinkExpression.cs | 1 + .../Filters/Interfaces/IFilterExpression.cs | 1 + .../Filters/Interfaces/IFilterable.cs | 7 +- .../Legacy/LegacyConditionConverter.cs | 7 +- Shoko.Server/Filters/Legacy/LegacyMappings.cs | 8 +- .../Logic/DateTimes/DateEqualsExpression.cs | 1 + .../DateGreaterThanEqualsExpression.cs | 1 + .../DateTimes/DateGreaterThanExpression.cs | 1 + .../DateTimes/DateLessThanEqualsExpression.cs | 1 + .../Logic/DateTimes/DateLessThanExpression.cs | 1 + .../DateTimes/DateNotEqualsExpression.cs | 1 + .../Logic/{ => Expressions}/AndExpression.cs | 3 +- .../Logic/{ => Expressions}/NotExpression.cs | 3 +- .../Logic/{ => Expressions}/OrExpression.cs | 3 +- .../Logic/{ => Expressions}/XorExpression.cs | 3 +- .../Logic/Numbers/NumberEqualsExpression.cs | 1 + .../NumberGreaterThanEqualsExpression.cs | 1 + .../Numbers/NumberGreaterThanExpression.cs | 1 + .../Numbers/NumberLessThanEqualsExpression.cs | 1 + .../Logic/Numbers/NumberLessThanExpression.cs | 1 + .../Numbers/NumberNotEqualsExpression.cs | 1 + .../Logic/Strings/StringContainsExpression.cs | 1 + .../Logic/Strings/StringEqualsExpression.cs | 1 + .../Strings/StringNotEqualsExpression.cs | 1 + .../Filters/Selectors/AddedDateSelector.cs | 1 + .../Filters/Selectors/AirDateSelector.cs | 1 + .../Selectors/AudioLanguageCountSelector.cs | 1 + .../Selectors/AverageAniDBRatingSelector.cs | 56 +++++++++ .../Filters/Selectors/EpisodeCountSelector.cs | 1 + .../Selectors/HighestAniDBRatingSelector.cs | 1 + .../Selectors/HighestUserRatingSelector.cs | 1 + .../Selectors/LastAddedDateSelector.cs | 1 + .../Filters/Selectors/LastAirDateSelector.cs | 1 + .../Selectors/LastWatchedDateSelector.cs | 1 + .../Selectors/LowestAniDBRatingSelector.cs | 1 + .../Selectors/LowestUserRatingSelector.cs | 1 + .../MissingEpisodeCollectingCountSelector.cs | 55 +++++++++ .../Selectors/MissingEpisodeCountSelector.cs | 55 +++++++++ .../Filters/Selectors/NameSelector.cs | 55 +++++++++ .../Filters/Selectors/SeriesCountSelector.cs | 1 + .../SubtitleLanguageCountSelector.cs | 1 + .../Selectors/TotalEpisodeCountSelector.cs | 1 + .../UnwatchedEpisodeCountSelector.cs | 55 +++++++++ .../Filters/Selectors/WatchedDateSelector.cs | 1 + .../Selectors/WatchedEpisodeCountSelector.cs | 55 +++++++++ .../AddedDateSortingSelector.cs | 1 + .../AirDateSortingSelector.cs | 1 + .../AudioLanguageCountSortingSelector.cs | 1 + .../AverageAniDBRatingSortingSelector.cs | 16 +++ .../EpisodeCountSortingSelector.cs | 1 + .../HighestAniDBRatingSortingSelector.cs | 1 + .../HighestUserRatingSortingSelector.cs | 1 + .../LastAddedDateSortingSelector.cs | 1 + .../LastAirDateSortingSelector.cs | 1 + .../LastWatchedDateSortingSelector.cs | 1 + .../LowestAniDBRatingSortingSelector.cs | 1 + .../LowestUserRatingSortingSelector.cs | 1 + ...ngEpisodeCollectingCountSortingSelector.cs | 1 + .../MissingEpisodeCountSortingSelector.cs | 1 + .../SortingSelectors/NameSortingSelector.cs | 1 + .../SeriesCountSortingSelector.cs | 1 + .../SortingNameSortingSelector.cs | 1 + .../SubtitleLanguageCountSortingSelector.cs | 1 + .../TotalEpisodeCountSortingSelector.cs | 1 + ...> UnwatchedEpisodeCountSortingSelector.cs} | 3 +- .../WatchedDateSortingSelector.cs | 1 + ... => WatchedEpisodeCountSortingSelector.cs} | 3 +- .../User/HasPermanentUserVotesExpression.cs | 1 + .../User/HasUnwatchedEpisodesExpression.cs | 1 + .../Filters/User/HasUserVotesExpression.cs | 1 + .../User/HasWatchedEpisodesExpression.cs | 1 + .../Filters/User/IsFavoriteExpression.cs | 1 + .../MissingPermanentUserVotesExpression.cs | 1 + Shoko.Server/Models/SVR_AniDB_File.cs | 88 +++++++++++++ .../Cached/FilterPresetRepository.cs | 1 + Shoko.Tests/Shoko.Tests/FilterTests.cs | 1 + .../Shoko.Tests/LegacyFilterConditionTests.cs | 1 + Shoko.Tests/Shoko.Tests/LegacyFilterTests.cs | 1 + Shoko.Tests/Shoko.Tests/TestFilterable.cs | 1 + 107 files changed, 840 insertions(+), 24 deletions(-) rename Shoko.Server/Filters/Logic/{ => Expressions}/AndExpression.cs (91%) rename Shoko.Server/Filters/Logic/{ => Expressions}/NotExpression.cs (90%) rename Shoko.Server/Filters/Logic/{ => Expressions}/OrExpression.cs (91%) rename Shoko.Server/Filters/Logic/{ => Expressions}/XorExpression.cs (90%) create mode 100644 Shoko.Server/Filters/Selectors/AverageAniDBRatingSelector.cs create mode 100644 Shoko.Server/Filters/Selectors/MissingEpisodeCollectingCountSelector.cs create mode 100644 Shoko.Server/Filters/Selectors/MissingEpisodeCountSelector.cs create mode 100644 Shoko.Server/Filters/Selectors/NameSelector.cs create mode 100644 Shoko.Server/Filters/Selectors/UnwatchedEpisodeCountSelector.cs create mode 100644 Shoko.Server/Filters/Selectors/WatchedEpisodeCountSelector.cs create mode 100644 Shoko.Server/Filters/SortingSelectors/AverageAniDBRatingSortingSelector.cs rename Shoko.Server/Filters/SortingSelectors/{UnwatchedCountSortingSelector.cs => UnwatchedEpisodeCountSortingSelector.cs} (56%) rename Shoko.Server/Filters/SortingSelectors/{WatchedCountSortingSelector.cs => WatchedEpisodeCountSortingSelector.cs} (57%) diff --git a/Shoko.Server/API/v3/Controllers/FilterController.cs b/Shoko.Server/API/v3/Controllers/FilterController.cs index 4470a99c1..f46f1b444 100644 --- a/Shoko.Server/API/v3/Controllers/FilterController.cs +++ b/Shoko.Server/API/v3/Controllers/FilterController.cs @@ -13,6 +13,7 @@ using Shoko.Server.API.v3.Models.Common; using Shoko.Server.API.v3.Models.Shoko; using Shoko.Server.Filters; +using Shoko.Server.Filters.Interfaces; using Shoko.Server.Models; using Shoko.Server.Repositories; using Shoko.Server.Settings; @@ -34,6 +35,8 @@ public class FilterController : BaseController private readonly FilterFactory _factory; private readonly SeriesFactory _seriesFactory; private readonly FilterEvaluator _filterEvaluator; + private static Filter.FilterExpressionHelp[] _expressionTypes; + private static Filter.SortingCriteriaHelp[] _sortingTypes; #region Existing Filters @@ -87,6 +90,103 @@ public ActionResult AddNewFilter(Filter.Input.CreateOrUpdateFilterBody b return filter; } + /// + /// Lists the available expressions. + /// The word "Filterable" is used a lot. It is a generic word for a series or group, depending on what the filter is set to apply to. + /// Expression: The identifier used to create the expression. eg. And, Not, HasTag. + /// Type: Parameters have a type, and this is the type that needs to match. + /// Left, Right, Parameter, and SecondParameter show what type the expression supports as parameters. + /// Left and Right are Expressions or Selectors. Parameters are constants. + /// + [HttpGet("Expressions")] + public ActionResult GetExpressions() + { + // get all classes that derive from FilterExpression, but not SortingExpression + _expressionTypes ??= AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()) + .Where(a => a != typeof(FilterExpression) && !a.IsGenericType && typeof(FilterExpression).IsAssignableFrom(a) && !typeof(SortingExpression).IsAssignableFrom(a)) + .OrderBy(a => a.FullName).Select(a => + { + var expression = (FilterExpression)Activator.CreateInstance(a); + if (expression == null) return null; + Filter.FilterExpressionHelp.FilterExpressionParameterType? left = expression switch + { + IWithExpressionParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.Expression, + IWithDateSelectorParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.DateSelector, + IWithNumberSelectorParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.NumberSelector, + IWithStringSelectorParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.StringSelector, + _ => null + }; + Filter.FilterExpressionHelp.FilterExpressionParameterType? right = expression switch + { + IWithSecondExpressionParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.Expression, + IWithSecondDateSelectorParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.DateSelector, + IWithSecondNumberSelectorParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.NumberSelector, + IWithSecondStringSelectorParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.StringSelector, + _ => null + }; + Filter.FilterExpressionHelp.FilterExpressionParameterType? parameter = expression switch + { + IWithDateParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.Date, + IWithNumberParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.Number, + IWithStringParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.String, + IWithTimeSpanParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.TimeSpan, + _ => null + }; + Filter.FilterExpressionHelp.FilterExpressionParameterType? secondParameter = expression switch + { + IWithSecondStringParameter => Filter.FilterExpressionHelp.FilterExpressionParameterType.String, + _ => null + }; + var type = expression switch + { + FilterExpression => Filter.FilterExpressionHelp.FilterExpressionParameterType.Expression, + FilterExpression => Filter.FilterExpressionHelp.FilterExpressionParameterType.DateSelector, + FilterExpression => Filter.FilterExpressionHelp.FilterExpressionParameterType.NumberSelector, + FilterExpression => Filter.FilterExpressionHelp.FilterExpressionParameterType.StringSelector, + _ => throw new Exception($"Expression {a.Name} is not a handled type for Filter Expression Help") + }; + return new Filter.FilterExpressionHelp + { + Expression = a.Name.Replace("Expression", ""), + Description = expression.HelpDescription, + PossibleParameters = expression.HelpPossibleParameters, + PossibleSecondParameters = expression.HelpPossibleSecondParameters, + Left = left, + Right = right, + Parameter = parameter, + SecondParameter = secondParameter, + Type = type + }; + }).Where(a => a != null).ToArray(); + return _expressionTypes; + } + + /// + /// Lists the available sorting expressions. These are basically selectors that the filter system uses to sort. + /// The word "Filterable" is used a lot. It is a generic word for a series or group, depending on what the filter is set to apply to. + /// Type: The identifier used to create the expression. eg. AddedDate. + /// IsInverted: Whether the sorting should be in descending order. + /// Next: If the expression returns equal values, it defers to the next expression to sort more predictably. + /// For example, MissingEpisodeCount,Descending -> AirDate, Descending would have thing with the most missing episodes, then the last aired first. + /// + [HttpGet("SortingCriteria")] + public ActionResult GetSortingCriteria() + { + // get all classes that derive from FilterExpression, but not SortingExpression + _sortingTypes ??= AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(a => + a != typeof(FilterExpression) && !a.IsAbstract && !a.IsGenericType && typeof(SortingExpression).IsAssignableFrom(a)).OrderBy(a => a.FullName) + .Select(a => + { + var criteria = (SortingExpression)Activator.CreateInstance(a); + if (criteria == null) return null; + return new Filter.SortingCriteriaHelp + { + Type = a.Name.Replace("SortingSelector", ""), Description = criteria.HelpDescription + }; + }).Where(a => a != null).ToArray(); + return _sortingTypes; + } + /// /// Get the for the given . /// diff --git a/Shoko.Server/API/v3/Models/Shoko/Filter.cs b/Shoko.Server/API/v3/Models/Shoko/Filter.cs index d73055bb8..09670030c 100644 --- a/Shoko.Server/API/v3/Models/Shoko/Filter.cs +++ b/Shoko.Server/API/v3/Models/Shoko/Filter.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using Shoko.Server.API.v3.Models.Common; // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global @@ -61,6 +62,7 @@ public class FilterIDs : IDs /// /// The of the parent , if it has one. /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public int? ParentFilter { get; set; } } @@ -106,6 +108,119 @@ public class FilterCondition public string? SecondParameter { get; set; } } + public class FilterExpressionHelp + { + /// + /// The internal type name of the FilterExpression + /// This is what you give the API, not actually the internal type (it is the internal type without the word Expression) + /// + [Required] + public string Expression { get; init; } + + /// + /// A description of what the expression is doing, comparing, etc + /// + [Required] + public string Description { get; init; } + + /// + /// This is what the expression would be considered for parameters, for example, Air Date is a Date Selector + /// + [Required] + [JsonConverter(typeof(StringEnumConverter))] + public FilterExpressionParameterType Type { get; init; } + + /// + /// The parameter type that the property requires + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(StringEnumConverter))] + public FilterExpressionParameterType? Left { get; init; } + + /// + /// The parameter types that the property requires + /// If multiple are given, then at least one is required + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(StringEnumConverter))] + public FilterExpressionParameterType? Right { get; init; } + + /// + /// The parameter type that the property requires. + /// This will always be a string for simplicity in type safety, but the type is what it expects + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(StringEnumConverter))] + public FilterExpressionParameterType? Parameter { get; init; } + + /// + /// This will list the possible parameters, usually with the most common ones first. + /// + public string[]? PossibleParameters { get; init; } + + /// + /// This will list the possible parameters, usually with the most common ones first. + /// + public string[]? PossibleSecondParameters { get; init; } + + /// + /// The parameter type that the property requires + /// This will always be a string for simplicity in type safety, but the type is what it expects + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(StringEnumConverter))] + public FilterExpressionParameterType? SecondParameter { get; init; } + + /// + /// Magical Json.Net stuff + /// + public bool ShouldSerializePossibleParameters() + { + return PossibleParameters?.Length > 0; + } + + /// + /// Magical Json.Net stuff + /// + public bool ShouldSerializePossibleSecondParameters() + { + return PossibleSecondParameters?.Length > 0; + } + + /// + /// The type of the parameter. Expressions return a boolean, Selectors return the type of their name, and the rest are values from the user. + /// Dates are in yyyy-MM-dd format + /// TimeSpans are in d:HH:mm:ss.ffff format (f is milliseconds) + /// + public enum FilterExpressionParameterType + { + Expression, + DateSelector, + NumberSelector, + StringSelector, + Date, + Number, + String, + TimeSpan + } + } + + public class SortingCriteriaHelp + { + /// + /// The internal type name of the FilterExpression + /// This is what you give the API, not actually the internal type (it is the internal type without the word Expression) + /// + [Required] + public string Type { get; init; } + + /// + /// A description of what the expression is doing, comparing, etc + /// + [Required] + public string Description { get; init; } + } + /// /// Sorting Criteria hold info on how Group Filters sort their items. /// It is in a List to follow an OrderBy().ThenBy().ThenBy(), allowing @@ -124,6 +239,7 @@ public class SortingCriteria /// /// The next expression to fall back on when the SortingExpression is equal or invalid, for example, sort by Episode Count descending then by Name /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public SortingCriteria? Next { get; set; } /// diff --git a/Shoko.Server/Filters/Files/HasAudioLanguageExpression.cs b/Shoko.Server/Filters/Files/HasAudioLanguageExpression.cs index d4fdbba87..bb35a4418 100644 --- a/Shoko.Server/Filters/Files/HasAudioLanguageExpression.cs +++ b/Shoko.Server/Filters/Files/HasAudioLanguageExpression.cs @@ -1,5 +1,6 @@ using System; using Shoko.Server.Filters.Interfaces; +using Shoko.Server.Models; namespace Shoko.Server.Filters.Files; @@ -14,6 +15,8 @@ public HasAudioLanguageExpression() { } public string Parameter { get; set; } public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the files have the audio language provided in the parameter"; + public override string[] HelpPossibleParameters => SVR_AniDB_File.GetPossibleAudioLanguages(); public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Files/HasSharedAudioLanguageExpression.cs b/Shoko.Server/Filters/Files/HasSharedAudioLanguageExpression.cs index d20510e8d..5e8b31e0a 100644 --- a/Shoko.Server/Filters/Files/HasSharedAudioLanguageExpression.cs +++ b/Shoko.Server/Filters/Files/HasSharedAudioLanguageExpression.cs @@ -1,5 +1,6 @@ using System; using Shoko.Server.Filters.Interfaces; +using Shoko.Server.Models; namespace Shoko.Server.Filters.Files; @@ -14,6 +15,8 @@ public HasSharedAudioLanguageExpression() { } public string Parameter { get; set; } public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if all of the files have the audio language provided in the parameter"; + public override string[] HelpPossibleParameters => SVR_AniDB_File.GetPossibleAudioLanguages(); public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Files/HasSharedSubtitleLanguageExpression.cs b/Shoko.Server/Filters/Files/HasSharedSubtitleLanguageExpression.cs index a117d77d2..51219dd7d 100644 --- a/Shoko.Server/Filters/Files/HasSharedSubtitleLanguageExpression.cs +++ b/Shoko.Server/Filters/Files/HasSharedSubtitleLanguageExpression.cs @@ -1,5 +1,6 @@ using System; using Shoko.Server.Filters.Interfaces; +using Shoko.Server.Models; namespace Shoko.Server.Filters.Files; @@ -14,6 +15,8 @@ public HasSharedSubtitleLanguageExpression() { } public string Parameter { get; set; } public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if all of the files have the subtitle language provided in the parameter"; + public override string[] HelpPossibleParameters => SVR_AniDB_File.GetPossibleSubtitleLanguages(); public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Files/HasSharedVideoSourceExpression.cs b/Shoko.Server/Filters/Files/HasSharedVideoSourceExpression.cs index 6e1f67ea2..7d82786b5 100644 --- a/Shoko.Server/Filters/Files/HasSharedVideoSourceExpression.cs +++ b/Shoko.Server/Filters/Files/HasSharedVideoSourceExpression.cs @@ -1,5 +1,6 @@ using System; using Shoko.Server.Filters.Interfaces; +using Shoko.Server.Providers.AniDB; namespace Shoko.Server.Filters.Files; @@ -14,6 +15,17 @@ public HasSharedVideoSourceExpression() { } public string Parameter { get; set; } public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if all of the files have the video source provided in the parameter"; + public override string[] HelpPossibleParameters => new[] + { + GetFile_Source.BluRay.ToString(), GetFile_Source.DVD.ToString(), + GetFile_Source.Web.ToString(), GetFile_Source.TV.ToString(), + GetFile_Source.HDTV.ToString(), GetFile_Source.Unknown.ToString(), + GetFile_Source.Camcorder.ToString(), GetFile_Source.DTV.ToString(), + GetFile_Source.VCD.ToString(), GetFile_Source.VHS.ToString(), + GetFile_Source.SVCD.ToString(), GetFile_Source.HDDVD.ToString(), + GetFile_Source.HKDVD.ToString(), GetFile_Source.LaserDisc.ToString() + }; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Files/HasSubtitleLanguageExpression.cs b/Shoko.Server/Filters/Files/HasSubtitleLanguageExpression.cs index 25ada250f..f82d482df 100644 --- a/Shoko.Server/Filters/Files/HasSubtitleLanguageExpression.cs +++ b/Shoko.Server/Filters/Files/HasSubtitleLanguageExpression.cs @@ -1,5 +1,6 @@ using System; using Shoko.Server.Filters.Interfaces; +using Shoko.Server.Models; namespace Shoko.Server.Filters.Files; @@ -14,6 +15,8 @@ public HasSubtitleLanguageExpression() { } public string Parameter { get; set; } public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the files have the subtitle language provided in the parameter"; + public override string[] HelpPossibleParameters => SVR_AniDB_File.GetPossibleSubtitleLanguages(); public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Files/HasVideoSourceExpression.cs b/Shoko.Server/Filters/Files/HasVideoSourceExpression.cs index 73ad0ba5c..25ef23df1 100644 --- a/Shoko.Server/Filters/Files/HasVideoSourceExpression.cs +++ b/Shoko.Server/Filters/Files/HasVideoSourceExpression.cs @@ -1,5 +1,6 @@ using System; using Shoko.Server.Filters.Interfaces; +using Shoko.Server.Providers.AniDB; namespace Shoko.Server.Filters.Files; @@ -14,6 +15,17 @@ public HasVideoSourceExpression() { } public string Parameter { get; set; } public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the files have the video source provided in the parameter"; + public override string[] HelpPossibleParameters => new[] + { + GetFile_Source.BluRay.ToString(), GetFile_Source.DVD.ToString(), + GetFile_Source.Web.ToString(), GetFile_Source.TV.ToString(), + GetFile_Source.HDTV.ToString(), GetFile_Source.Unknown.ToString(), + GetFile_Source.Camcorder.ToString(), GetFile_Source.DTV.ToString(), + GetFile_Source.VCD.ToString(), GetFile_Source.VHS.ToString(), + GetFile_Source.SVCD.ToString(), GetFile_Source.HDDVD.ToString(), + GetFile_Source.HKDVD.ToString(), GetFile_Source.LaserDisc.ToString() + }; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/FilterExpression.cs b/Shoko.Server/Filters/FilterExpression.cs index 6453ccb89..27036c588 100644 --- a/Shoko.Server/Filters/FilterExpression.cs +++ b/Shoko.Server/Filters/FilterExpression.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.Serialization; using Newtonsoft.Json; using Shoko.Server.Filters.Interfaces; @@ -6,8 +7,11 @@ namespace Shoko.Server.Filters; public class FilterExpression : IFilterExpression { - [IgnoreDataMember][JsonIgnore] public virtual bool TimeDependent => false; - [IgnoreDataMember][JsonIgnore] public virtual bool UserDependent => false; + [IgnoreDataMember] [JsonIgnore] public virtual bool TimeDependent => false; + [IgnoreDataMember] [JsonIgnore] public virtual bool UserDependent => false; + [IgnoreDataMember] [JsonIgnore] public virtual string HelpDescription => string.Empty; + [IgnoreDataMember] [JsonIgnore] public virtual string[] HelpPossibleParameters => Array.Empty(); + [IgnoreDataMember] [JsonIgnore] public virtual string[] HelpPossibleSecondParameters => Array.Empty(); protected virtual bool Equals(FilterExpression other) { diff --git a/Shoko.Server/Filters/FilterExtensions.cs b/Shoko.Server/Filters/FilterExtensions.cs index 2fb756452..8bd486c58 100644 --- a/Shoko.Server/Filters/FilterExtensions.cs +++ b/Shoko.Server/Filters/FilterExtensions.cs @@ -48,6 +48,7 @@ public static Filterable ToFilterable(this SVR_AnimeSeries series) TotalEpisodeCountDelegate = () => anime?.EpisodeCount ?? 0, LowestAniDBRatingDelegate = () => decimal.Round(Convert.ToDecimal(anime?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero), HighestAniDBRatingDelegate = () => decimal.Round(Convert.ToDecimal(anime?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero), + AverageAniDBRatingDelegate = () => decimal.Round(Convert.ToDecimal(anime?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero), AnimeTypesDelegate = () => anime == null ? new HashSet() : new HashSet(StringComparer.InvariantCultureIgnoreCase) @@ -114,6 +115,7 @@ public static UserDependentFilterable ToUserDependentFilterable(this SVR_AnimeSe TotalEpisodeCountDelegate = () => anime?.EpisodeCount ?? 0, LowestAniDBRatingDelegate = () => decimal.Round(Convert.ToDecimal(anime?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero), HighestAniDBRatingDelegate = () => decimal.Round(Convert.ToDecimal(anime?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero), + AverageAniDBRatingDelegate = () => decimal.Round(Convert.ToDecimal(anime?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero), AnimeTypesDelegate = () => anime == null ? new HashSet() : new HashSet(StringComparer.InvariantCultureIgnoreCase) @@ -263,6 +265,7 @@ public static Filterable ToFilterable(this SVR_AnimeGroup group) TotalEpisodeCountDelegate = () => series.Sum(a => a.GetAnime()?.EpisodeCount ?? 0), LowestAniDBRatingDelegate = () => anime.Select(a => decimal.Round(Convert.ToDecimal(a?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero)).DefaultIfEmpty().Min(), HighestAniDBRatingDelegate = () => anime.Select(a => decimal.Round(Convert.ToDecimal(a?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero)).DefaultIfEmpty().Max(), + AverageAniDBRatingDelegate = () => anime.Select(a => decimal.Round(Convert.ToDecimal(a?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero)).DefaultIfEmpty().Average(), AnimeTypesDelegate = () => new HashSet(anime.Select(a => ((AnimeType)a.AnimeType).ToString()), StringComparer.InvariantCultureIgnoreCase), VideoSourcesDelegate = () => group.Contract?.Stat_AllVideoQuality ?? new HashSet(), SharedVideoSourcesDelegate = () => group.Contract?.Stat_AllVideoQuality_Episodes ?? new HashSet(), @@ -331,12 +334,9 @@ public static Filterable ToUserDependentFilterable(this SVR_AnimeGroup group, in LastAddedDateDelegate = () => series.SelectMany(a => a.GetVideoLocals()).Select(a => a.DateTimeCreated).DefaultIfEmpty().Max(), EpisodeCountDelegate = () => series.Sum(a => a.GetAnime()?.EpisodeCountNormal ?? 0), TotalEpisodeCountDelegate = () => series.Sum(a => a.GetAnime()?.EpisodeCount ?? 0), - LowestAniDBRatingDelegate = () => - anime.Select(a => decimal.Round(Convert.ToDecimal(a?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero)).DefaultIfEmpty() - .Min(), - HighestAniDBRatingDelegate = () => - anime.Select(a => decimal.Round(Convert.ToDecimal(a?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero)).DefaultIfEmpty() - .Max(), + LowestAniDBRatingDelegate = () => anime.Select(a => decimal.Round(Convert.ToDecimal(a?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero)).DefaultIfEmpty().Min(), + HighestAniDBRatingDelegate = () => anime.Select(a => decimal.Round(Convert.ToDecimal(a?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero)).DefaultIfEmpty().Max(), + AverageAniDBRatingDelegate = () => anime.Select(a => decimal.Round(Convert.ToDecimal(a?.Rating ?? 0) / 100, 1, MidpointRounding.AwayFromZero)).DefaultIfEmpty().Average(), AnimeTypesDelegate = () => new HashSet(anime.Select(a => ((AnimeType)a.AnimeType).ToString()), StringComparer.InvariantCultureIgnoreCase), VideoSourcesDelegate = () => group.Contract?.Stat_AllVideoQuality ?? new HashSet(), SharedVideoSourcesDelegate = () => group.Contract?.Stat_AllVideoQuality_Episodes ?? new HashSet(), diff --git a/Shoko.Server/Filters/Filterable.cs b/Shoko.Server/Filters/Filterable.cs index cd5cf9f27..5e053e0a7 100644 --- a/Shoko.Server/Filters/Filterable.cs +++ b/Shoko.Server/Filters/Filterable.cs @@ -20,6 +20,9 @@ public class Filterable : IFilterable private readonly Lazy> _audioLanguages; private readonly Func> _audioLanguagesDelegate; + + private readonly Lazy _averageAniDBRating; + private readonly Func _averageAniDBRatingDelegate; private readonly Lazy> _customTags; private readonly Func> _customTagsDelegate; @@ -486,6 +489,22 @@ public Func HighestAniDBRatingDelegate } } + public decimal AverageAniDBRating + { + get => _averageAniDBRating.Value; + init => throw new NotSupportedException(); + } + + public Func AverageAniDBRatingDelegate + { + get => _averageAniDBRatingDelegate; + init + { + _averageAniDBRatingDelegate = value; + _averageAniDBRating = new Lazy(_averageAniDBRatingDelegate, LazyThreadSafetyMode.ExecutionAndPublication); + } + } + public IReadOnlySet VideoSources { get => _videoSources.Value; diff --git a/Shoko.Server/Filters/Functions/DateAddFunction.cs b/Shoko.Server/Filters/Functions/DateAddFunction.cs index fb0be3cc4..8d1a00fc7 100644 --- a/Shoko.Server/Filters/Functions/DateAddFunction.cs +++ b/Shoko.Server/Filters/Functions/DateAddFunction.cs @@ -20,6 +20,7 @@ public DateAddFunction(FilterExpression selector, TimeSpan parameter) public override bool TimeDependent => Selector.TimeDependent; public override bool UserDependent => Selector.UserDependent; + public override string HelpDescription => "This adds a timespan to a date selector"; public FilterExpression Left { diff --git a/Shoko.Server/Filters/Functions/DateDiffFunction.cs b/Shoko.Server/Filters/Functions/DateDiffFunction.cs index 669f3bf35..e9d9150fb 100644 --- a/Shoko.Server/Filters/Functions/DateDiffFunction.cs +++ b/Shoko.Server/Filters/Functions/DateDiffFunction.cs @@ -17,6 +17,7 @@ public DateDiffFunction() { } public override bool TimeDependent => Selector.TimeDependent; public override bool UserDependent => Selector.UserDependent; + public override string HelpDescription => "This subtracts a timespan from a date selector."; public FilterExpression Left { diff --git a/Shoko.Server/Filters/Functions/TodayFunction.cs b/Shoko.Server/Filters/Functions/TodayFunction.cs index 615266491..e3c6dedfa 100644 --- a/Shoko.Server/Filters/Functions/TodayFunction.cs +++ b/Shoko.Server/Filters/Functions/TodayFunction.cs @@ -7,6 +7,7 @@ public class TodayFunction : FilterExpression { public override bool TimeDependent => true; public override bool UserDependent => false; + public override string HelpDescription => "This returns the current date, at midnight (00:00:00.0000)"; public override DateTime? Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Info/HasAnimeTypeExpression.cs b/Shoko.Server/Filters/Info/HasAnimeTypeExpression.cs index 3f10e1adb..6d6950556 100644 --- a/Shoko.Server/Filters/Info/HasAnimeTypeExpression.cs +++ b/Shoko.Server/Filters/Info/HasAnimeTypeExpression.cs @@ -1,4 +1,5 @@ using System; +using Shoko.Plugin.Abstractions.DataModels; using Shoko.Server.Filters.Interfaces; namespace Shoko.Server.Filters.Info; @@ -14,6 +15,16 @@ public HasAnimeTypeExpression() { } public string Parameter { get; set; } public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime in a filterable are of the type given in the parameter"; + public override string[] HelpPossibleParameters => new[] + { + AnimeType.TVSeries.ToString(), + AnimeType.Movie.ToString(), + AnimeType.OVA.ToString(), + AnimeType.Web.ToString(), + AnimeType.TVSpecial.ToString(), + AnimeType.Other.ToString(), + }; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/HasCustomTagExpression.cs b/Shoko.Server/Filters/Info/HasCustomTagExpression.cs index 5d7690dcf..26e1ed6ec 100644 --- a/Shoko.Server/Filters/Info/HasCustomTagExpression.cs +++ b/Shoko.Server/Filters/Info/HasCustomTagExpression.cs @@ -13,7 +13,8 @@ public HasCustomTagExpression() { } public string Parameter { get; set; } public override bool TimeDependent => false; - public override bool UserDependent => true; + public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime have a custom tag that matches the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/HasMissingEpisodesCollectingExpression.cs b/Shoko.Server/Filters/Info/HasMissingEpisodesCollectingExpression.cs index 0265ad948..259cbb358 100644 --- a/Shoko.Server/Filters/Info/HasMissingEpisodesCollectingExpression.cs +++ b/Shoko.Server/Filters/Info/HasMissingEpisodesCollectingExpression.cs @@ -6,6 +6,7 @@ public class HasMissingEpisodesCollectingExpression : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime are missing episodes from a release group that is currently in the collection"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/HasMissingEpisodesExpression.cs b/Shoko.Server/Filters/Info/HasMissingEpisodesExpression.cs index 8ac6091c4..78c874c8e 100644 --- a/Shoko.Server/Filters/Info/HasMissingEpisodesExpression.cs +++ b/Shoko.Server/Filters/Info/HasMissingEpisodesExpression.cs @@ -6,6 +6,7 @@ public class HasMissingEpisodesExpression : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime have missing episodes from any known release group"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/HasNameExpression.cs b/Shoko.Server/Filters/Info/HasNameExpression.cs index 89eee6721..729721d62 100644 --- a/Shoko.Server/Filters/Info/HasNameExpression.cs +++ b/Shoko.Server/Filters/Info/HasNameExpression.cs @@ -14,6 +14,7 @@ public HasNameExpression() { } public string Parameter { get; set; } public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if the name of the series or group matches the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/HasTMDbLinkExpression.cs b/Shoko.Server/Filters/Info/HasTMDbLinkExpression.cs index 8839975d7..c4cf083e8 100644 --- a/Shoko.Server/Filters/Info/HasTMDbLinkExpression.cs +++ b/Shoko.Server/Filters/Info/HasTMDbLinkExpression.cs @@ -6,6 +6,7 @@ public class HasTMDbLinkExpression : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime have a TMDb link"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/HasTagExpression.cs b/Shoko.Server/Filters/Info/HasTagExpression.cs index 34cbeb4b6..264dc602b 100644 --- a/Shoko.Server/Filters/Info/HasTagExpression.cs +++ b/Shoko.Server/Filters/Info/HasTagExpression.cs @@ -14,6 +14,7 @@ public HasTagExpression() { } public string Parameter { get; set; } public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime have a tag that matches the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/HasTraktLinkExpression.cs b/Shoko.Server/Filters/Info/HasTraktLinkExpression.cs index 171d08bb3..5bd1a39bd 100644 --- a/Shoko.Server/Filters/Info/HasTraktLinkExpression.cs +++ b/Shoko.Server/Filters/Info/HasTraktLinkExpression.cs @@ -6,6 +6,7 @@ public class HasTraktLinkExpression : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime have a Trakt link"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/HasTvDBLinkExpression.cs b/Shoko.Server/Filters/Info/HasTvDBLinkExpression.cs index 9c819f591..cb50c6c3a 100644 --- a/Shoko.Server/Filters/Info/HasTvDBLinkExpression.cs +++ b/Shoko.Server/Filters/Info/HasTvDBLinkExpression.cs @@ -6,6 +6,7 @@ public class HasTvDBLinkExpression : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime have a TvDB link"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/InSeasonExpression.cs b/Shoko.Server/Filters/Info/InSeasonExpression.cs index c0c90522c..45deea3e2 100644 --- a/Shoko.Server/Filters/Info/InSeasonExpression.cs +++ b/Shoko.Server/Filters/Info/InSeasonExpression.cs @@ -17,7 +17,13 @@ public InSeasonExpression() { } public AnimeSeason Season { get; set; } public override bool TimeDependent => false; public override bool UserDependent => false; - + public override string HelpDescription => "This passes if any of the anime aired in the season given in the parameters"; + public override string[] HelpPossibleSecondParameters => new[] + { + AnimeSeason.Winter.ToString(), AnimeSeason.Spring.ToString(), + AnimeSeason.Summer.ToString(), AnimeSeason.Fall.ToString(), + }; + double IWithNumberParameter.Parameter { get => Year; diff --git a/Shoko.Server/Filters/Info/InYearExpression.cs b/Shoko.Server/Filters/Info/InYearExpression.cs index 96194b880..536f6a422 100644 --- a/Shoko.Server/Filters/Info/InYearExpression.cs +++ b/Shoko.Server/Filters/Info/InYearExpression.cs @@ -14,6 +14,7 @@ public InYearExpression() { } public int Parameter { get; set; } public override bool TimeDependent => true; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime aired in the year given in the parameters"; double IWithNumberParameter.Parameter { diff --git a/Shoko.Server/Filters/Info/IsFinishedExpression.cs b/Shoko.Server/Filters/Info/IsFinishedExpression.cs index d2711fcaa..716736799 100644 --- a/Shoko.Server/Filters/Info/IsFinishedExpression.cs +++ b/Shoko.Server/Filters/Info/IsFinishedExpression.cs @@ -6,6 +6,7 @@ public class IsFinishedExpression : FilterExpression { public override bool TimeDependent => true; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime have finished"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/MissingTMDbLinkExpression.cs b/Shoko.Server/Filters/Info/MissingTMDbLinkExpression.cs index 9fd79cf7f..921bf59b2 100644 --- a/Shoko.Server/Filters/Info/MissingTMDbLinkExpression.cs +++ b/Shoko.Server/Filters/Info/MissingTMDbLinkExpression.cs @@ -9,6 +9,7 @@ public class MissingTMDbLinkExpression : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime should have a TMDb link but does not have one"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/MissingTraktLinkExpression.cs b/Shoko.Server/Filters/Info/MissingTraktLinkExpression.cs index 7e1efc6bf..ab6bc7c05 100644 --- a/Shoko.Server/Filters/Info/MissingTraktLinkExpression.cs +++ b/Shoko.Server/Filters/Info/MissingTraktLinkExpression.cs @@ -9,6 +9,7 @@ public class MissingTraktLinkExpression : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime should have a Trakt link but does not have one"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Info/MissingTvDBLinkExpression.cs b/Shoko.Server/Filters/Info/MissingTvDBLinkExpression.cs index 8a3c804a5..c329b43d3 100644 --- a/Shoko.Server/Filters/Info/MissingTvDBLinkExpression.cs +++ b/Shoko.Server/Filters/Info/MissingTvDBLinkExpression.cs @@ -9,6 +9,7 @@ public class MissingTvDBLinkExpression : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This passes if any of the anime should have a TvDB link but does not have one"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Interfaces/IFilterExpression.cs b/Shoko.Server/Filters/Interfaces/IFilterExpression.cs index 07341ed18..837308ae6 100644 --- a/Shoko.Server/Filters/Interfaces/IFilterExpression.cs +++ b/Shoko.Server/Filters/Interfaces/IFilterExpression.cs @@ -4,6 +4,7 @@ public interface IFilterExpression { bool TimeDependent { get; } bool UserDependent { get; } + string HelpDescription { get; } } public interface IFilterExpression diff --git a/Shoko.Server/Filters/Interfaces/IFilterable.cs b/Shoko.Server/Filters/Interfaces/IFilterable.cs index ae3b87e41..127534f2c 100644 --- a/Shoko.Server/Filters/Interfaces/IFilterable.cs +++ b/Shoko.Server/Filters/Interfaces/IFilterable.cs @@ -125,7 +125,12 @@ public interface IFilterable /// Highest AniDB Rating (0-10) /// decimal HighestAniDBRating { get; init; } - + + /// + /// Average AniDB Rating (0-10) + /// + decimal AverageAniDBRating { get; init; } + /// /// The sources that the video came from, such as TV, Web, DVD, Blu-ray, etc. /// diff --git a/Shoko.Server/Filters/Legacy/LegacyConditionConverter.cs b/Shoko.Server/Filters/Legacy/LegacyConditionConverter.cs index 19719c461..92f1e9ad7 100644 --- a/Shoko.Server/Filters/Legacy/LegacyConditionConverter.cs +++ b/Shoko.Server/Filters/Legacy/LegacyConditionConverter.cs @@ -9,6 +9,7 @@ using Shoko.Server.Filters.Interfaces; using Shoko.Server.Filters.Logic; using Shoko.Server.Filters.Logic.DateTimes; +using Shoko.Server.Filters.Logic.Expressions; using Shoko.Server.Filters.Logic.Numbers; using Shoko.Server.Filters.Selectors; using Shoko.Server.Filters.SortingSelectors; @@ -568,7 +569,7 @@ private static bool IsEpisodeWatchedDate(FilterExpression expression, out object private static bool IsAniDBRating(FilterExpression expression, out object parameter, out GroupFilterOperator gfOperator) { - return TryParseComparator(expression, typeof(HighestAniDBRatingSelector), out parameter, out gfOperator); + return TryParseComparator(expression, typeof(AverageAniDBRatingSelector), out parameter, out gfOperator); } private static bool IsUserRating(FilterExpression expression, out object parameter, out GroupFilterOperator gfOperator) @@ -702,7 +703,7 @@ public static List GetSortingCriteriaList(FilterPres sortType = GroupFilterSorting.Year; else if (type == typeof(SeriesCountSortingSelector)) sortType = GroupFilterSorting.SeriesCount; - else if (type == typeof(UnwatchedCountSortingSelector)) + else if (type == typeof(UnwatchedEpisodeCountSortingSelector)) sortType = GroupFilterSorting.UnwatchedEpisodeCount; else if (type == typeof(MissingEpisodeCountSortingSelector)) sortType = GroupFilterSorting.MissingEpisodeCount; @@ -885,7 +886,7 @@ public static SortingExpression GetSortingExpression(List new UnwatchedCountSortingSelector + GroupFilterSorting.UnwatchedEpisodeCount => new UnwatchedEpisodeCountSortingSelector { Descending = criteria.SortDirection == GroupFilterSortDirection.Desc }, diff --git a/Shoko.Server/Filters/Legacy/LegacyMappings.cs b/Shoko.Server/Filters/Legacy/LegacyMappings.cs index f3108241b..eabbd0071 100644 --- a/Shoko.Server/Filters/Legacy/LegacyMappings.cs +++ b/Shoko.Server/Filters/Legacy/LegacyMappings.cs @@ -6,6 +6,7 @@ using Shoko.Server.Filters.Info; using Shoko.Server.Filters.Logic; using Shoko.Server.Filters.Logic.DateTimes; +using Shoko.Server.Filters.Logic.Expressions; using Shoko.Server.Filters.Logic.Numbers; using Shoko.Server.Filters.Selectors; using Shoko.Server.Filters.User; @@ -341,9 +342,9 @@ public static FilterExpression GetRatingExpression(GroupFilterOperator op, { // These are reversed because we would consider that parameter is greater than the rating, but the expression takes a constant as the second operand case GroupFilterOperator.GreaterThan: - return new NumberLessThanExpression(new HighestAniDBRatingSelector(), rating); + return new NumberLessThanExpression(new AverageAniDBRatingSelector(), rating); case GroupFilterOperator.LessThan: - return new NumberGreaterThanExpression(new HighestAniDBRatingSelector(), rating); + return new NumberGreaterThanExpression(new AverageAniDBRatingSelector(), rating); default: return suppressErrors ? null : throw new ArgumentOutOfRangeException(nameof(op), $@"ConditionOperator {op} not applicable for Rating"); } @@ -371,11 +372,10 @@ public static FilterExpression GetEpisodeCountExpression(GroupFilterOperat return suppressErrors ? null : throw new ArgumentException($@"Parameter {parameter} is not a number", nameof(parameter)); switch (op) { - // These are reversed because we would consider that parameter is greater than the rating, but the expression takes a constant as the second operand case GroupFilterOperator.GreaterThan: return new NumberLessThanExpression(new EpisodeCountSelector(), count); case GroupFilterOperator.LessThan: - return new NumberGreaterThanExpression(new HighestUserRatingSelector(), count); + return new NumberGreaterThanExpression(new EpisodeCountSelector(), count); default: return suppressErrors ? null : throw new ArgumentOutOfRangeException(nameof(op), $@"ConditionOperator {op} not applicable for Episode Count"); } diff --git a/Shoko.Server/Filters/Logic/DateTimes/DateEqualsExpression.cs b/Shoko.Server/Filters/Logic/DateTimes/DateEqualsExpression.cs index 1405c7ef3..1a2b23076 100644 --- a/Shoko.Server/Filters/Logic/DateTimes/DateEqualsExpression.cs +++ b/Shoko.Server/Filters/Logic/DateTimes/DateEqualsExpression.cs @@ -22,6 +22,7 @@ public DateEqualsExpression() { } public DateTime Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector equals either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/DateTimes/DateGreaterThanEqualsExpression.cs b/Shoko.Server/Filters/Logic/DateTimes/DateGreaterThanEqualsExpression.cs index 0b1e64ea0..9ad73c534 100644 --- a/Shoko.Server/Filters/Logic/DateTimes/DateGreaterThanEqualsExpression.cs +++ b/Shoko.Server/Filters/Logic/DateTimes/DateGreaterThanEqualsExpression.cs @@ -22,6 +22,7 @@ public DateGreaterThanEqualsExpression() { } public DateTime Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector is greater than or equal to either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/DateTimes/DateGreaterThanExpression.cs b/Shoko.Server/Filters/Logic/DateTimes/DateGreaterThanExpression.cs index 1c10fa2be..884a7ebce 100644 --- a/Shoko.Server/Filters/Logic/DateTimes/DateGreaterThanExpression.cs +++ b/Shoko.Server/Filters/Logic/DateTimes/DateGreaterThanExpression.cs @@ -22,6 +22,7 @@ public DateGreaterThanExpression() { } public DateTime Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector is greater than either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/DateTimes/DateLessThanEqualsExpression.cs b/Shoko.Server/Filters/Logic/DateTimes/DateLessThanEqualsExpression.cs index c4419741b..fa2b2fd43 100644 --- a/Shoko.Server/Filters/Logic/DateTimes/DateLessThanEqualsExpression.cs +++ b/Shoko.Server/Filters/Logic/DateTimes/DateLessThanEqualsExpression.cs @@ -22,6 +22,7 @@ public DateLessThanEqualsExpression() { } public DateTime Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector is less than or equal to either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/DateTimes/DateLessThanExpression.cs b/Shoko.Server/Filters/Logic/DateTimes/DateLessThanExpression.cs index 55f01d196..61ae4c63a 100644 --- a/Shoko.Server/Filters/Logic/DateTimes/DateLessThanExpression.cs +++ b/Shoko.Server/Filters/Logic/DateTimes/DateLessThanExpression.cs @@ -22,6 +22,7 @@ public DateLessThanExpression() { } public DateTime Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector is less than either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/DateTimes/DateNotEqualsExpression.cs b/Shoko.Server/Filters/Logic/DateTimes/DateNotEqualsExpression.cs index 00b0b1c49..f70d9dd90 100644 --- a/Shoko.Server/Filters/Logic/DateTimes/DateNotEqualsExpression.cs +++ b/Shoko.Server/Filters/Logic/DateTimes/DateNotEqualsExpression.cs @@ -22,6 +22,7 @@ public DateNotEqualsExpression() { } public DateTime Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector is not equal to either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/AndExpression.cs b/Shoko.Server/Filters/Logic/Expressions/AndExpression.cs similarity index 91% rename from Shoko.Server/Filters/Logic/AndExpression.cs rename to Shoko.Server/Filters/Logic/Expressions/AndExpression.cs index 7180a7429..ae1ec3824 100644 --- a/Shoko.Server/Filters/Logic/AndExpression.cs +++ b/Shoko.Server/Filters/Logic/Expressions/AndExpression.cs @@ -1,7 +1,7 @@ using System; using Shoko.Server.Filters.Interfaces; -namespace Shoko.Server.Filters.Logic; +namespace Shoko.Server.Filters.Logic.Expressions; public class AndExpression : FilterExpression, IWithExpressionParameter, IWithSecondExpressionParameter { @@ -15,6 +15,7 @@ public AndExpression() { } public override bool TimeDependent => Left.TimeDependent || Right.TimeDependent; public override bool UserDependent => Left.UserDependent || Right.UserDependent; + public override string HelpDescription => "This passes if both the left expression and the right expression pass"; public FilterExpression Left { get; set; } public FilterExpression Right { get; set; } diff --git a/Shoko.Server/Filters/Logic/NotExpression.cs b/Shoko.Server/Filters/Logic/Expressions/NotExpression.cs similarity index 90% rename from Shoko.Server/Filters/Logic/NotExpression.cs rename to Shoko.Server/Filters/Logic/Expressions/NotExpression.cs index 9686bbc37..85576c305 100644 --- a/Shoko.Server/Filters/Logic/NotExpression.cs +++ b/Shoko.Server/Filters/Logic/Expressions/NotExpression.cs @@ -1,7 +1,7 @@ using System; using Shoko.Server.Filters.Interfaces; -namespace Shoko.Server.Filters.Logic; +namespace Shoko.Server.Filters.Logic.Expressions; public class NotExpression : FilterExpression, IWithExpressionParameter { @@ -13,6 +13,7 @@ public NotExpression(FilterExpression left) public NotExpression() { } public override bool TimeDependent => Left.TimeDependent; public override bool UserDependent => Left.UserDependent; + public override string HelpDescription => "This passes if the left expression does not pass, e.g. an inverse"; public FilterExpression Left { get; set; } diff --git a/Shoko.Server/Filters/Logic/OrExpression.cs b/Shoko.Server/Filters/Logic/Expressions/OrExpression.cs similarity index 91% rename from Shoko.Server/Filters/Logic/OrExpression.cs rename to Shoko.Server/Filters/Logic/Expressions/OrExpression.cs index 1ea617c9b..0eb78c180 100644 --- a/Shoko.Server/Filters/Logic/OrExpression.cs +++ b/Shoko.Server/Filters/Logic/Expressions/OrExpression.cs @@ -1,7 +1,7 @@ using System; using Shoko.Server.Filters.Interfaces; -namespace Shoko.Server.Filters.Logic; +namespace Shoko.Server.Filters.Logic.Expressions; public class OrExpression : FilterExpression, IWithExpressionParameter, IWithSecondExpressionParameter { @@ -15,6 +15,7 @@ public OrExpression() { } public override bool TimeDependent => Left.TimeDependent || Right.TimeDependent; public override bool UserDependent => Left.UserDependent || Right.UserDependent; + public override string HelpDescription => "This passes if either the left expression or the right expression pass"; public FilterExpression Left { get; set; } public FilterExpression Right { get; set; } diff --git a/Shoko.Server/Filters/Logic/XorExpression.cs b/Shoko.Server/Filters/Logic/Expressions/XorExpression.cs similarity index 90% rename from Shoko.Server/Filters/Logic/XorExpression.cs rename to Shoko.Server/Filters/Logic/Expressions/XorExpression.cs index 50e014b78..103ff7db1 100644 --- a/Shoko.Server/Filters/Logic/XorExpression.cs +++ b/Shoko.Server/Filters/Logic/Expressions/XorExpression.cs @@ -1,7 +1,7 @@ using System; using Shoko.Server.Filters.Interfaces; -namespace Shoko.Server.Filters.Logic; +namespace Shoko.Server.Filters.Logic.Expressions; public class XorExpression : FilterExpression, IWithExpressionParameter, IWithSecondExpressionParameter { @@ -15,6 +15,7 @@ public XorExpression() { } public override bool TimeDependent => Left.TimeDependent || Right.TimeDependent; public override bool UserDependent => Left.UserDependent || Right.UserDependent; + public override string HelpDescription => "This passes if either the left expression or the right expression pass, but not both"; public FilterExpression Left { get; set; } public FilterExpression Right { get; set; } diff --git a/Shoko.Server/Filters/Logic/Numbers/NumberEqualsExpression.cs b/Shoko.Server/Filters/Logic/Numbers/NumberEqualsExpression.cs index 1fba5ac52..6e3fc1ec8 100644 --- a/Shoko.Server/Filters/Logic/Numbers/NumberEqualsExpression.cs +++ b/Shoko.Server/Filters/Logic/Numbers/NumberEqualsExpression.cs @@ -22,6 +22,7 @@ public NumberEqualsExpression() { } public double Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector equals either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/Numbers/NumberGreaterThanEqualsExpression.cs b/Shoko.Server/Filters/Logic/Numbers/NumberGreaterThanEqualsExpression.cs index 762aa1cda..b70371fd5 100644 --- a/Shoko.Server/Filters/Logic/Numbers/NumberGreaterThanEqualsExpression.cs +++ b/Shoko.Server/Filters/Logic/Numbers/NumberGreaterThanEqualsExpression.cs @@ -22,6 +22,7 @@ public NumberGreaterThanEqualsExpression() { } public double Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector is greater than or equal to either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/Numbers/NumberGreaterThanExpression.cs b/Shoko.Server/Filters/Logic/Numbers/NumberGreaterThanExpression.cs index da2d67873..87e5a1f99 100644 --- a/Shoko.Server/Filters/Logic/Numbers/NumberGreaterThanExpression.cs +++ b/Shoko.Server/Filters/Logic/Numbers/NumberGreaterThanExpression.cs @@ -22,6 +22,7 @@ public NumberGreaterThanExpression() { } public double Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector is greater than either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/Numbers/NumberLessThanEqualsExpression.cs b/Shoko.Server/Filters/Logic/Numbers/NumberLessThanEqualsExpression.cs index 6b72be3dc..06e297660 100644 --- a/Shoko.Server/Filters/Logic/Numbers/NumberLessThanEqualsExpression.cs +++ b/Shoko.Server/Filters/Logic/Numbers/NumberLessThanEqualsExpression.cs @@ -22,6 +22,7 @@ public NumberLessThanEqualsExpression() { } public double Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector is less than or equal to either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/Numbers/NumberLessThanExpression.cs b/Shoko.Server/Filters/Logic/Numbers/NumberLessThanExpression.cs index b53da6fa8..a4a3ba373 100644 --- a/Shoko.Server/Filters/Logic/Numbers/NumberLessThanExpression.cs +++ b/Shoko.Server/Filters/Logic/Numbers/NumberLessThanExpression.cs @@ -22,6 +22,7 @@ public NumberLessThanExpression() { } public double Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector is less than either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/Numbers/NumberNotEqualsExpression.cs b/Shoko.Server/Filters/Logic/Numbers/NumberNotEqualsExpression.cs index 23f07d9a6..4a2f7b86e 100644 --- a/Shoko.Server/Filters/Logic/Numbers/NumberNotEqualsExpression.cs +++ b/Shoko.Server/Filters/Logic/Numbers/NumberNotEqualsExpression.cs @@ -22,6 +22,7 @@ public NumberNotEqualsExpression() { } public double Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector is not equal to either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/Strings/StringContainsExpression.cs b/Shoko.Server/Filters/Logic/Strings/StringContainsExpression.cs index 42f59c9d0..4e4458497 100644 --- a/Shoko.Server/Filters/Logic/Strings/StringContainsExpression.cs +++ b/Shoko.Server/Filters/Logic/Strings/StringContainsExpression.cs @@ -24,6 +24,7 @@ public StringContainsExpression() { } public string Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector contains either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/Strings/StringEqualsExpression.cs b/Shoko.Server/Filters/Logic/Strings/StringEqualsExpression.cs index 2e3e1bfcd..c5bf7e301 100644 --- a/Shoko.Server/Filters/Logic/Strings/StringEqualsExpression.cs +++ b/Shoko.Server/Filters/Logic/Strings/StringEqualsExpression.cs @@ -22,6 +22,7 @@ public StringEqualsExpression() { } public string Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector equals either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Logic/Strings/StringNotEqualsExpression.cs b/Shoko.Server/Filters/Logic/Strings/StringNotEqualsExpression.cs index a578807eb..4e41f5c48 100644 --- a/Shoko.Server/Filters/Logic/Strings/StringNotEqualsExpression.cs +++ b/Shoko.Server/Filters/Logic/Strings/StringNotEqualsExpression.cs @@ -22,6 +22,7 @@ public StringNotEqualsExpression() { } public string Parameter { get; set; } public override bool TimeDependent => Left.TimeDependent || (Right?.TimeDependent ?? false); public override bool UserDependent => Left.UserDependent || (Right?.UserDependent ?? false); + public override string HelpDescription => "This passes if the left selector is not equal to either the right selector or the parameter"; public override bool Evaluate(IFilterable filterable) { diff --git a/Shoko.Server/Filters/Selectors/AddedDateSelector.cs b/Shoko.Server/Filters/Selectors/AddedDateSelector.cs index b52a97085..de10c8bd2 100644 --- a/Shoko.Server/Filters/Selectors/AddedDateSelector.cs +++ b/Shoko.Server/Filters/Selectors/AddedDateSelector.cs @@ -7,6 +7,7 @@ public class AddedDateSelector : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This returns the date that a filterable was created"; public override DateTime? Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/AirDateSelector.cs b/Shoko.Server/Filters/Selectors/AirDateSelector.cs index cd92486c3..8530b26fe 100644 --- a/Shoko.Server/Filters/Selectors/AirDateSelector.cs +++ b/Shoko.Server/Filters/Selectors/AirDateSelector.cs @@ -7,6 +7,7 @@ public class AirDateSelector : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This returns the first date that a filterable aired"; public override DateTime? Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/AudioLanguageCountSelector.cs b/Shoko.Server/Filters/Selectors/AudioLanguageCountSelector.cs index b1440d9e9..438baeda0 100644 --- a/Shoko.Server/Filters/Selectors/AudioLanguageCountSelector.cs +++ b/Shoko.Server/Filters/Selectors/AudioLanguageCountSelector.cs @@ -6,6 +6,7 @@ public class AudioLanguageCountSelector : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This returns how many distinct audio languages are present in all of the files in a filterable"; public override double Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/AverageAniDBRatingSelector.cs b/Shoko.Server/Filters/Selectors/AverageAniDBRatingSelector.cs new file mode 100644 index 000000000..e72c4b63d --- /dev/null +++ b/Shoko.Server/Filters/Selectors/AverageAniDBRatingSelector.cs @@ -0,0 +1,56 @@ +using System; +using Shoko.Server.Filters.Interfaces; + +namespace Shoko.Server.Filters.Selectors; + +public class AverageAniDBRatingSelector : FilterExpression +{ + public override bool TimeDependent => false; + public override bool UserDependent => false; + public override string HelpDescription => "This returns the average AniDB rating in a filterable"; + + public override double Evaluate(IFilterable f) + { + return Convert.ToDouble(f.AverageAniDBRating); + } + + protected bool Equals(AverageAniDBRatingSelector other) + { + return base.Equals(other); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return Equals((AverageAniDBRatingSelector)obj); + } + + public override int GetHashCode() + { + return GetType().FullName!.GetHashCode(); + } + + public static bool operator ==(AverageAniDBRatingSelector left, AverageAniDBRatingSelector right) + { + return Equals(left, right); + } + + public static bool operator !=(AverageAniDBRatingSelector left, AverageAniDBRatingSelector right) + { + return !Equals(left, right); + } +} diff --git a/Shoko.Server/Filters/Selectors/EpisodeCountSelector.cs b/Shoko.Server/Filters/Selectors/EpisodeCountSelector.cs index 103942d2a..f6814f368 100644 --- a/Shoko.Server/Filters/Selectors/EpisodeCountSelector.cs +++ b/Shoko.Server/Filters/Selectors/EpisodeCountSelector.cs @@ -6,6 +6,7 @@ public class EpisodeCountSelector : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This returns the total number of episodes in a filterable"; public override double Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/HighestAniDBRatingSelector.cs b/Shoko.Server/Filters/Selectors/HighestAniDBRatingSelector.cs index 195059410..48c7e0e6f 100644 --- a/Shoko.Server/Filters/Selectors/HighestAniDBRatingSelector.cs +++ b/Shoko.Server/Filters/Selectors/HighestAniDBRatingSelector.cs @@ -7,6 +7,7 @@ public class HighestAniDBRatingSelector : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This returns the highest AniDB rating in a filterable"; public override double Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/HighestUserRatingSelector.cs b/Shoko.Server/Filters/Selectors/HighestUserRatingSelector.cs index 5fdc87660..1c1b765be 100644 --- a/Shoko.Server/Filters/Selectors/HighestUserRatingSelector.cs +++ b/Shoko.Server/Filters/Selectors/HighestUserRatingSelector.cs @@ -7,6 +7,7 @@ public class HighestUserRatingSelector : UserDependentFilterExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This returns the highest user rating in a filterable"; public override double Evaluate(IUserDependentFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/LastAddedDateSelector.cs b/Shoko.Server/Filters/Selectors/LastAddedDateSelector.cs index 982378940..bca8a80f6 100644 --- a/Shoko.Server/Filters/Selectors/LastAddedDateSelector.cs +++ b/Shoko.Server/Filters/Selectors/LastAddedDateSelector.cs @@ -7,6 +7,7 @@ public class LastAddedDateSelector : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This returns the last date that any episode was added in a filterable"; public override DateTime? Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/LastAirDateSelector.cs b/Shoko.Server/Filters/Selectors/LastAirDateSelector.cs index ca6ae648b..1e43770e5 100644 --- a/Shoko.Server/Filters/Selectors/LastAirDateSelector.cs +++ b/Shoko.Server/Filters/Selectors/LastAirDateSelector.cs @@ -7,6 +7,7 @@ public class LastAirDateSelector : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This returns the last date that a filterable aired"; public override DateTime? Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/LastWatchedDateSelector.cs b/Shoko.Server/Filters/Selectors/LastWatchedDateSelector.cs index e1054fca4..993b20378 100644 --- a/Shoko.Server/Filters/Selectors/LastWatchedDateSelector.cs +++ b/Shoko.Server/Filters/Selectors/LastWatchedDateSelector.cs @@ -7,6 +7,7 @@ public class LastWatchedDateSelector : UserDependentFilterExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This returns the last date that a filterable was watched by the current user"; public override DateTime? Evaluate(IUserDependentFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/LowestAniDBRatingSelector.cs b/Shoko.Server/Filters/Selectors/LowestAniDBRatingSelector.cs index 40db18d91..3412689eb 100644 --- a/Shoko.Server/Filters/Selectors/LowestAniDBRatingSelector.cs +++ b/Shoko.Server/Filters/Selectors/LowestAniDBRatingSelector.cs @@ -7,6 +7,7 @@ public class LowestAniDBRatingSelector : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This returns the lowest AniDB rating in a filterable"; public override double Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/LowestUserRatingSelector.cs b/Shoko.Server/Filters/Selectors/LowestUserRatingSelector.cs index 6026592e6..bb39ba089 100644 --- a/Shoko.Server/Filters/Selectors/LowestUserRatingSelector.cs +++ b/Shoko.Server/Filters/Selectors/LowestUserRatingSelector.cs @@ -7,6 +7,7 @@ public class LowestUserRatingSelector : UserDependentFilterExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This returns the lowest user rating in a filterable"; public override double Evaluate(IUserDependentFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/MissingEpisodeCollectingCountSelector.cs b/Shoko.Server/Filters/Selectors/MissingEpisodeCollectingCountSelector.cs new file mode 100644 index 000000000..31f3eb91d --- /dev/null +++ b/Shoko.Server/Filters/Selectors/MissingEpisodeCollectingCountSelector.cs @@ -0,0 +1,55 @@ +using Shoko.Server.Filters.Interfaces; + +namespace Shoko.Server.Filters.Selectors; + +public class MissingEpisodeCollectingCountSelector : FilterExpression +{ + public override bool TimeDependent => false; + public override bool UserDependent => false; + public override string HelpDescription => "This returns the number of missing episodes in a filterable that are from a release group that is already in the filterable"; + + public override double Evaluate(IFilterable f) + { + return f.MissingEpisodesCollecting; + } + + protected bool Equals(MissingEpisodeCollectingCountSelector other) + { + return base.Equals(other); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return Equals((MissingEpisodeCollectingCountSelector)obj); + } + + public override int GetHashCode() + { + return GetType().FullName!.GetHashCode(); + } + + public static bool operator ==(MissingEpisodeCollectingCountSelector left, MissingEpisodeCollectingCountSelector right) + { + return Equals(left, right); + } + + public static bool operator !=(MissingEpisodeCollectingCountSelector left, MissingEpisodeCollectingCountSelector right) + { + return !Equals(left, right); + } +} diff --git a/Shoko.Server/Filters/Selectors/MissingEpisodeCountSelector.cs b/Shoko.Server/Filters/Selectors/MissingEpisodeCountSelector.cs new file mode 100644 index 000000000..ec7c7dd9e --- /dev/null +++ b/Shoko.Server/Filters/Selectors/MissingEpisodeCountSelector.cs @@ -0,0 +1,55 @@ +using Shoko.Server.Filters.Interfaces; + +namespace Shoko.Server.Filters.Selectors; + +public class MissingEpisodeCountSelector : FilterExpression +{ + public override bool TimeDependent => false; + public override bool UserDependent => false; + public override string HelpDescription => "This returns the number of missing episodes from any release group in a filterable"; + + public override double Evaluate(IFilterable f) + { + return f.MissingEpisodes; + } + + protected bool Equals(MissingEpisodeCountSelector other) + { + return base.Equals(other); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return Equals((MissingEpisodeCountSelector)obj); + } + + public override int GetHashCode() + { + return GetType().FullName!.GetHashCode(); + } + + public static bool operator ==(MissingEpisodeCountSelector left, MissingEpisodeCountSelector right) + { + return Equals(left, right); + } + + public static bool operator !=(MissingEpisodeCountSelector left, MissingEpisodeCountSelector right) + { + return !Equals(left, right); + } +} diff --git a/Shoko.Server/Filters/Selectors/NameSelector.cs b/Shoko.Server/Filters/Selectors/NameSelector.cs new file mode 100644 index 000000000..40e022558 --- /dev/null +++ b/Shoko.Server/Filters/Selectors/NameSelector.cs @@ -0,0 +1,55 @@ +using Shoko.Server.Filters.Interfaces; + +namespace Shoko.Server.Filters.Selectors; + +public class NameSelector : FilterExpression +{ + public override bool TimeDependent => false; + public override bool UserDependent => false; + public override string HelpDescription => "This returns the name of a filterable"; + + public override string Evaluate(IFilterable f) + { + return f.Name; + } + + protected bool Equals(NameSelector other) + { + return base.Equals(other); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return Equals((NameSelector)obj); + } + + public override int GetHashCode() + { + return GetType().FullName!.GetHashCode(); + } + + public static bool operator ==(NameSelector left, NameSelector right) + { + return Equals(left, right); + } + + public static bool operator !=(NameSelector left, NameSelector right) + { + return !Equals(left, right); + } +} diff --git a/Shoko.Server/Filters/Selectors/SeriesCountSelector.cs b/Shoko.Server/Filters/Selectors/SeriesCountSelector.cs index c1b78cad4..f44799caf 100644 --- a/Shoko.Server/Filters/Selectors/SeriesCountSelector.cs +++ b/Shoko.Server/Filters/Selectors/SeriesCountSelector.cs @@ -6,6 +6,7 @@ public class SeriesCountSelector : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This returns the number of series in a filterable"; public override double Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/SubtitleLanguageCountSelector.cs b/Shoko.Server/Filters/Selectors/SubtitleLanguageCountSelector.cs index 07f6b6f0c..1160bd3ad 100644 --- a/Shoko.Server/Filters/Selectors/SubtitleLanguageCountSelector.cs +++ b/Shoko.Server/Filters/Selectors/SubtitleLanguageCountSelector.cs @@ -6,6 +6,7 @@ public class SubtitleLanguageCountSelector : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This returns how many distinct subtitle languages are present in all of the files in a filterable"; public override double Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/TotalEpisodeCountSelector.cs b/Shoko.Server/Filters/Selectors/TotalEpisodeCountSelector.cs index 60560f0b9..f65217f81 100644 --- a/Shoko.Server/Filters/Selectors/TotalEpisodeCountSelector.cs +++ b/Shoko.Server/Filters/Selectors/TotalEpisodeCountSelector.cs @@ -6,6 +6,7 @@ public class TotalEpisodeCountSelector : FilterExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This returns the total number of episodes in a filterable"; public override double Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/UnwatchedEpisodeCountSelector.cs b/Shoko.Server/Filters/Selectors/UnwatchedEpisodeCountSelector.cs new file mode 100644 index 000000000..9dd7b0f2c --- /dev/null +++ b/Shoko.Server/Filters/Selectors/UnwatchedEpisodeCountSelector.cs @@ -0,0 +1,55 @@ +using Shoko.Server.Filters.Interfaces; + +namespace Shoko.Server.Filters.Selectors; + +public class UnwatchedEpisodeCountSelector : UserDependentFilterExpression +{ + public override bool TimeDependent => false; + public override bool UserDependent => true; + public override string HelpDescription => "This returns the number of episodes in a filterable that have not been watched by the current user"; + + public override double Evaluate(IUserDependentFilterable f) + { + return f.UnwatchedEpisodes; + } + + protected bool Equals(UnwatchedEpisodeCountSelector other) + { + return base.Equals(other); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return Equals((UnwatchedEpisodeCountSelector)obj); + } + + public override int GetHashCode() + { + return GetType().FullName!.GetHashCode(); + } + + public static bool operator ==(UnwatchedEpisodeCountSelector left, UnwatchedEpisodeCountSelector right) + { + return Equals(left, right); + } + + public static bool operator !=(UnwatchedEpisodeCountSelector left, UnwatchedEpisodeCountSelector right) + { + return !Equals(left, right); + } +} diff --git a/Shoko.Server/Filters/Selectors/WatchedDateSelector.cs b/Shoko.Server/Filters/Selectors/WatchedDateSelector.cs index 20cbcd23b..5fb7a5bba 100644 --- a/Shoko.Server/Filters/Selectors/WatchedDateSelector.cs +++ b/Shoko.Server/Filters/Selectors/WatchedDateSelector.cs @@ -7,6 +7,7 @@ public class WatchedDateSelector : UserDependentFilterExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This returns the first date that a filterable was watched by the current user"; public override DateTime? Evaluate(IUserDependentFilterable f) { diff --git a/Shoko.Server/Filters/Selectors/WatchedEpisodeCountSelector.cs b/Shoko.Server/Filters/Selectors/WatchedEpisodeCountSelector.cs new file mode 100644 index 000000000..d19c84855 --- /dev/null +++ b/Shoko.Server/Filters/Selectors/WatchedEpisodeCountSelector.cs @@ -0,0 +1,55 @@ +using Shoko.Server.Filters.Interfaces; + +namespace Shoko.Server.Filters.Selectors; + +public class WatchedEpisodeCountSelector : UserDependentFilterExpression +{ + public override bool TimeDependent => false; + public override bool UserDependent => true; + public override string HelpDescription => "This returns the number of episodes in a filterable that have been watched by the current user"; + + public override double Evaluate(IUserDependentFilterable f) + { + return f.WatchedEpisodes; + } + + protected bool Equals(WatchedEpisodeCountSelector other) + { + return base.Equals(other); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return Equals((WatchedEpisodeCountSelector)obj); + } + + public override int GetHashCode() + { + return GetType().FullName!.GetHashCode(); + } + + public static bool operator ==(WatchedEpisodeCountSelector left, WatchedEpisodeCountSelector right) + { + return Equals(left, right); + } + + public static bool operator !=(WatchedEpisodeCountSelector left, WatchedEpisodeCountSelector right) + { + return !Equals(left, right); + } +} diff --git a/Shoko.Server/Filters/SortingSelectors/AddedDateSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/AddedDateSortingSelector.cs index 248eec808..1d804efdc 100644 --- a/Shoko.Server/Filters/SortingSelectors/AddedDateSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/AddedDateSortingSelector.cs @@ -6,6 +6,7 @@ public class AddedDateSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the date that a filterable was created"; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/AirDateSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/AirDateSortingSelector.cs index b999b0786..d7db5ac63 100644 --- a/Shoko.Server/Filters/SortingSelectors/AirDateSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/AirDateSortingSelector.cs @@ -7,6 +7,7 @@ public class AirDateSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the date that a filterable first aired"; public DateTime DefaultValue { get; set; } public override object Evaluate(IFilterable f) diff --git a/Shoko.Server/Filters/SortingSelectors/AudioLanguageCountSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/AudioLanguageCountSortingSelector.cs index f29c4951a..53d89ef9e 100644 --- a/Shoko.Server/Filters/SortingSelectors/AudioLanguageCountSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/AudioLanguageCountSortingSelector.cs @@ -6,6 +6,7 @@ public class AudioLanguageCountSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by how many distinct audio languages are present in all of the files in a filterable"; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/AverageAniDBRatingSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/AverageAniDBRatingSortingSelector.cs new file mode 100644 index 000000000..3028630f4 --- /dev/null +++ b/Shoko.Server/Filters/SortingSelectors/AverageAniDBRatingSortingSelector.cs @@ -0,0 +1,16 @@ +using System; +using Shoko.Server.Filters.Interfaces; + +namespace Shoko.Server.Filters.SortingSelectors; + +public class AverageAniDBRatingSortingSelector : SortingExpression +{ + public override bool TimeDependent => false; + public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the average AniDB rating in a filterable"; + + public override object Evaluate(IFilterable f) + { + return Convert.ToDouble(f.AverageAniDBRating); + } +} diff --git a/Shoko.Server/Filters/SortingSelectors/EpisodeCountSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/EpisodeCountSortingSelector.cs index 63fd55819..ca72bac31 100644 --- a/Shoko.Server/Filters/SortingSelectors/EpisodeCountSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/EpisodeCountSortingSelector.cs @@ -6,6 +6,7 @@ public class EpisodeCountSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the total number of episodes in a filterable"; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/HighestAniDBRatingSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/HighestAniDBRatingSortingSelector.cs index 6f49b0387..fdf969e81 100644 --- a/Shoko.Server/Filters/SortingSelectors/HighestAniDBRatingSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/HighestAniDBRatingSortingSelector.cs @@ -7,6 +7,7 @@ public class HighestAniDBRatingSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the highest AniDB rating in a filterable"; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/HighestUserRatingSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/HighestUserRatingSortingSelector.cs index 0656e729d..28b9433aa 100644 --- a/Shoko.Server/Filters/SortingSelectors/HighestUserRatingSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/HighestUserRatingSortingSelector.cs @@ -7,6 +7,7 @@ public class HighestUserRatingSortingSelector : UserDependentSortingExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This sorts by the highest user rating in a filterable"; public override object Evaluate(IUserDependentFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/LastAddedDateSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/LastAddedDateSortingSelector.cs index 5c38f6364..fa25e3e09 100644 --- a/Shoko.Server/Filters/SortingSelectors/LastAddedDateSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/LastAddedDateSortingSelector.cs @@ -6,6 +6,7 @@ public class LastAddedDateSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the last date that any episode was added in a filterable"; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/LastAirDateSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/LastAirDateSortingSelector.cs index 3d8d49317..5d59bc187 100644 --- a/Shoko.Server/Filters/SortingSelectors/LastAirDateSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/LastAirDateSortingSelector.cs @@ -7,6 +7,7 @@ public class LastAirDateSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the last date that a filterable aired"; public DateTime DefaultValue { get; set; } public override object Evaluate(IFilterable f) diff --git a/Shoko.Server/Filters/SortingSelectors/LastWatchedDateSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/LastWatchedDateSortingSelector.cs index b14ae0eaf..bdcd07f2c 100644 --- a/Shoko.Server/Filters/SortingSelectors/LastWatchedDateSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/LastWatchedDateSortingSelector.cs @@ -7,6 +7,7 @@ public class LastWatchedDateSortingSelector : UserDependentSortingExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This sorts by the last date that a filterable was watched by the current user"; public DateTime DefaultValue { get; set; } public override object Evaluate(IUserDependentFilterable f) diff --git a/Shoko.Server/Filters/SortingSelectors/LowestAniDBRatingSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/LowestAniDBRatingSortingSelector.cs index d8f479cb2..f979eb52c 100644 --- a/Shoko.Server/Filters/SortingSelectors/LowestAniDBRatingSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/LowestAniDBRatingSortingSelector.cs @@ -7,6 +7,7 @@ public class LowestAniDBRatingSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the lowest AniDB rating in a filterable"; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/LowestUserRatingSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/LowestUserRatingSortingSelector.cs index 6fe4db933..1d4798aeb 100644 --- a/Shoko.Server/Filters/SortingSelectors/LowestUserRatingSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/LowestUserRatingSortingSelector.cs @@ -7,6 +7,7 @@ public class LowestUserRatingSortingSelector : UserDependentSortingExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This sorts by the lowest user rating in a filterable"; public override object Evaluate(IUserDependentFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/MissingEpisodeCollectingCountSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/MissingEpisodeCollectingCountSortingSelector.cs index dee2b5ca5..81eb68d7f 100644 --- a/Shoko.Server/Filters/SortingSelectors/MissingEpisodeCollectingCountSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/MissingEpisodeCollectingCountSortingSelector.cs @@ -6,6 +6,7 @@ public class MissingEpisodeCollectingCountSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the number of missing episodes in a filterable that are from a release group that is already in the filterable"; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/MissingEpisodeCountSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/MissingEpisodeCountSortingSelector.cs index 02a079504..e19225f90 100644 --- a/Shoko.Server/Filters/SortingSelectors/MissingEpisodeCountSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/MissingEpisodeCountSortingSelector.cs @@ -6,6 +6,7 @@ public class MissingEpisodeCountSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the number of missing episodes from any release group in a filterable"; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/NameSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/NameSortingSelector.cs index 19cbb8ccf..1f5e29747 100644 --- a/Shoko.Server/Filters/SortingSelectors/NameSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/NameSortingSelector.cs @@ -6,6 +6,7 @@ public class NameSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the name of a filterable"; public override string Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/SeriesCountSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/SeriesCountSortingSelector.cs index 73e91c6f4..f963e7929 100644 --- a/Shoko.Server/Filters/SortingSelectors/SeriesCountSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/SeriesCountSortingSelector.cs @@ -6,6 +6,7 @@ public class SeriesCountSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the number of series in a filterable"; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/SortingNameSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/SortingNameSortingSelector.cs index eb9bcef22..4a26e33e5 100644 --- a/Shoko.Server/Filters/SortingSelectors/SortingNameSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/SortingNameSortingSelector.cs @@ -6,6 +6,7 @@ public class SortingNameSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by a filterable's name, excluding common words like A, The, etc."; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/SubtitleLanguageCountSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/SubtitleLanguageCountSortingSelector.cs index 72e327c89..a770c899c 100644 --- a/Shoko.Server/Filters/SortingSelectors/SubtitleLanguageCountSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/SubtitleLanguageCountSortingSelector.cs @@ -6,6 +6,7 @@ public class SubtitleLanguageCountSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by how many distinct subtitle languages are present in all of the files in a filterable"; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/TotalEpisodeCountSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/TotalEpisodeCountSortingSelector.cs index c5d32dde6..ba3ae56db 100644 --- a/Shoko.Server/Filters/SortingSelectors/TotalEpisodeCountSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/TotalEpisodeCountSortingSelector.cs @@ -6,6 +6,7 @@ public class TotalEpisodeCountSortingSelector : SortingExpression { public override bool TimeDependent => false; public override bool UserDependent => false; + public override string HelpDescription => "This sorts by the total number of episodes in a filterable"; public override object Evaluate(IFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/UnwatchedCountSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/UnwatchedEpisodeCountSortingSelector.cs similarity index 56% rename from Shoko.Server/Filters/SortingSelectors/UnwatchedCountSortingSelector.cs rename to Shoko.Server/Filters/SortingSelectors/UnwatchedEpisodeCountSortingSelector.cs index fa1fa1207..77f18f554 100644 --- a/Shoko.Server/Filters/SortingSelectors/UnwatchedCountSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/UnwatchedEpisodeCountSortingSelector.cs @@ -2,10 +2,11 @@ namespace Shoko.Server.Filters.SortingSelectors; -public class UnwatchedCountSortingSelector : UserDependentSortingExpression +public class UnwatchedEpisodeCountSortingSelector : UserDependentSortingExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This returns the number of episodes in a filterable that have not been watched by the current user"; public override object Evaluate(IUserDependentFilterable f) { diff --git a/Shoko.Server/Filters/SortingSelectors/WatchedDateSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/WatchedDateSortingSelector.cs index 84c6670a4..b99eddae6 100644 --- a/Shoko.Server/Filters/SortingSelectors/WatchedDateSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/WatchedDateSortingSelector.cs @@ -7,6 +7,7 @@ public class WatchedDateSortingSelector : UserDependentSortingExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This sorts by the first date that a filterable was watched by the current user"; public DateTime DefaultValue { get; set; } public override object Evaluate(IUserDependentFilterable f) diff --git a/Shoko.Server/Filters/SortingSelectors/WatchedCountSortingSelector.cs b/Shoko.Server/Filters/SortingSelectors/WatchedEpisodeCountSortingSelector.cs similarity index 57% rename from Shoko.Server/Filters/SortingSelectors/WatchedCountSortingSelector.cs rename to Shoko.Server/Filters/SortingSelectors/WatchedEpisodeCountSortingSelector.cs index ffae07bfe..5c04f1696 100644 --- a/Shoko.Server/Filters/SortingSelectors/WatchedCountSortingSelector.cs +++ b/Shoko.Server/Filters/SortingSelectors/WatchedEpisodeCountSortingSelector.cs @@ -2,10 +2,11 @@ namespace Shoko.Server.Filters.SortingSelectors; -public class WatchedCountSortingSelector : UserDependentSortingExpression +public class WatchedEpisodeCountSortingSelector : UserDependentSortingExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This returns the number of episodes in a filterable that have been watched by the current user"; public override object Evaluate(IUserDependentFilterable f) { diff --git a/Shoko.Server/Filters/User/HasPermanentUserVotesExpression.cs b/Shoko.Server/Filters/User/HasPermanentUserVotesExpression.cs index 7306d4061..6e957e5ec 100644 --- a/Shoko.Server/Filters/User/HasPermanentUserVotesExpression.cs +++ b/Shoko.Server/Filters/User/HasPermanentUserVotesExpression.cs @@ -6,6 +6,7 @@ public class HasPermanentUserVotesExpression : UserDependentFilterExpression false; public override bool UserDependent => true; + public override string HelpDescription => "This passes if the filterable has a user vote that is of the permanent vote type"; public override bool Evaluate(IUserDependentFilterable filterable) { diff --git a/Shoko.Server/Filters/User/HasUnwatchedEpisodesExpression.cs b/Shoko.Server/Filters/User/HasUnwatchedEpisodesExpression.cs index f3bdda9b4..04da19310 100644 --- a/Shoko.Server/Filters/User/HasUnwatchedEpisodesExpression.cs +++ b/Shoko.Server/Filters/User/HasUnwatchedEpisodesExpression.cs @@ -6,6 +6,7 @@ public class HasUnwatchedEpisodesExpression : UserDependentFilterExpression false; public override bool UserDependent => true; + public override string HelpDescription => "This passes if the current user has any unwatched episodes in the filterable"; public override bool Evaluate(IUserDependentFilterable filterable) { diff --git a/Shoko.Server/Filters/User/HasUserVotesExpression.cs b/Shoko.Server/Filters/User/HasUserVotesExpression.cs index 436cbb519..b2d933ceb 100644 --- a/Shoko.Server/Filters/User/HasUserVotesExpression.cs +++ b/Shoko.Server/Filters/User/HasUserVotesExpression.cs @@ -6,6 +6,7 @@ public class HasUserVotesExpression : UserDependentFilterExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This passes if the filterable has a user vote"; public override bool Evaluate(IUserDependentFilterable filterable) { diff --git a/Shoko.Server/Filters/User/HasWatchedEpisodesExpression.cs b/Shoko.Server/Filters/User/HasWatchedEpisodesExpression.cs index 328b9690f..407379f98 100644 --- a/Shoko.Server/Filters/User/HasWatchedEpisodesExpression.cs +++ b/Shoko.Server/Filters/User/HasWatchedEpisodesExpression.cs @@ -6,6 +6,7 @@ public class HasWatchedEpisodesExpression : UserDependentFilterExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This passes if the current user has any watched episodes in the filterable"; public override bool Evaluate(IUserDependentFilterable filterable) { diff --git a/Shoko.Server/Filters/User/IsFavoriteExpression.cs b/Shoko.Server/Filters/User/IsFavoriteExpression.cs index 38a585196..52500b73b 100644 --- a/Shoko.Server/Filters/User/IsFavoriteExpression.cs +++ b/Shoko.Server/Filters/User/IsFavoriteExpression.cs @@ -6,6 +6,7 @@ public class IsFavoriteExpression : UserDependentFilterExpression { public override bool TimeDependent => false; public override bool UserDependent => true; + public override string HelpDescription => "This passes if the filterable is marked as Favorite. This exists for backwards compatibility. Custom Tags are a better way to do this."; public override bool Evaluate(IUserDependentFilterable filterable) { diff --git a/Shoko.Server/Filters/User/MissingPermanentUserVotesExpression.cs b/Shoko.Server/Filters/User/MissingPermanentUserVotesExpression.cs index 8d149e981..4437d26b7 100644 --- a/Shoko.Server/Filters/User/MissingPermanentUserVotesExpression.cs +++ b/Shoko.Server/Filters/User/MissingPermanentUserVotesExpression.cs @@ -6,6 +6,7 @@ public class MissingPermanentUserVotesExpression : UserDependentFilterExpression { public override bool TimeDependent => true; public override bool UserDependent => true; + public override string HelpDescription => "This passes if the filterable is missing a user vote that is of the permanent vote type. This has logic for if the filterable should have a vote"; public override bool Evaluate(IUserDependentFilterable filterable) { diff --git a/Shoko.Server/Models/SVR_AniDB_File.cs b/Shoko.Server/Models/SVR_AniDB_File.cs index 4479ee6e3..20823645b 100644 --- a/Shoko.Server/Models/SVR_AniDB_File.cs +++ b/Shoko.Server/Models/SVR_AniDB_File.cs @@ -164,6 +164,94 @@ urdu ur written 74 0 } } + private static readonly string[] _possibleAudioLanguages = + { + "english", "japanese", + "chinese (mandarin)", "afrikaans", + "albanian", "arabic", + "basque", "bengali", + "bulgarian", "bosnian", + "catalan", "chinese (unspecified)", + "chinese (cantonese)", "chinese (taiwanese)", + "croatian", "czech", + "danish", "dutch", + "esperanto", "estonian", + "filipino", "filipino (tagalog)", + "finnish", "french", + "galician", "georgian", + "german", "greek", + "haitian creole", "hebrew", + "hindi", "hungarian", + "icelandic", "indonesian", + "instrumental", "italian", + "javanese", "korean", + "latin", "latvian", + "lithuanian", "malay", + "mongolian", "nepali", + "norwegian", "persian", + "polish", "portuguese", + "portuguese (brazilian)", "romanian", + "russian", "serbian", + "sinhala", "slovak", + "slovenian", "spanish", + "spanish (latin american)", "swedish", + "tamil", "tatar", + "telugu", "thai", + "turkish", "ukrainian", + "vietnamese", "unknown", + "other", + }; + + public static string[] GetPossibleAudioLanguages() + { + return _possibleAudioLanguages; + } + + private static readonly string[] _possibleSubtitleLanguages = + { + "english", "japanese", + "japanese (transcription)", "afrikaans", + "albanian", "arabic", + "basque", "bengali", + "bulgarian", "bosnian", + "catalan", "chinese (unspecified)", + "chinese (transcription)", "chinese (traditional)", + "chinese (simplified)", "croatian", + "czech", "danish", + "dutch", "esperanto", + "estonian", "filipino", + "filipino (tagalog)", "finnish", + "french", "galician", + "georgian", "german", + "greek", "greek (ancient)", + "haitian creole", "hebrew", + "hindi", "hungarian", + "icelandic", "indonesian", + "italian", "javanese", + "korean", "korean (transcription)", + "latin", "latvian", + "lithuanian", "malay", + "mongolian", "nepali", + "norwegian", "persian", + "polish", "portuguese", + "portuguese (brazilian)", "romanian", + "russian", "serbian", + "sinhala", "slovak", + "slovenian", "spanish", + "spanish (latin american)", "swedish", + "tamil", "tatar", + "telugu", "thai", + "thai (transcription)", "turkish", + "ukrainian", "urdu", + "vietnamese", "unknown", + "other", + }; + + public static string[] GetPossibleSubtitleLanguages() + { + return _possibleSubtitleLanguages; + } + int IAniDBFile.AniDBFileID => FileID; IReleaseGroup IAniDBFile.ReleaseGroup diff --git a/Shoko.Server/Repositories/Cached/FilterPresetRepository.cs b/Shoko.Server/Repositories/Cached/FilterPresetRepository.cs index 34cdaa743..485ef4443 100644 --- a/Shoko.Server/Repositories/Cached/FilterPresetRepository.cs +++ b/Shoko.Server/Repositories/Cached/FilterPresetRepository.cs @@ -12,6 +12,7 @@ using Shoko.Server.Filters.Info; using Shoko.Server.Filters.Logic; using Shoko.Server.Filters.Logic.DateTimes; +using Shoko.Server.Filters.Logic.Expressions; using Shoko.Server.Filters.Selectors; using Shoko.Server.Filters.SortingSelectors; using Shoko.Server.Filters.User; diff --git a/Shoko.Tests/Shoko.Tests/FilterTests.cs b/Shoko.Tests/Shoko.Tests/FilterTests.cs index 227c93644..06a7c9ebd 100644 --- a/Shoko.Tests/Shoko.Tests/FilterTests.cs +++ b/Shoko.Tests/Shoko.Tests/FilterTests.cs @@ -7,6 +7,7 @@ using Shoko.Server.Filters.Info; using Shoko.Server.Filters.Logic; using Shoko.Server.Filters.Logic.DateTimes; +using Shoko.Server.Filters.Logic.Expressions; using Shoko.Server.Filters.Selectors; using Shoko.Server.Filters.User; using Xunit; diff --git a/Shoko.Tests/Shoko.Tests/LegacyFilterConditionTests.cs b/Shoko.Tests/Shoko.Tests/LegacyFilterConditionTests.cs index 4a31130e1..a3b6adddf 100644 --- a/Shoko.Tests/Shoko.Tests/LegacyFilterConditionTests.cs +++ b/Shoko.Tests/Shoko.Tests/LegacyFilterConditionTests.cs @@ -4,6 +4,7 @@ using Shoko.Server.Filters.Info; using Shoko.Server.Filters.Legacy; using Shoko.Server.Filters.Logic; +using Shoko.Server.Filters.Logic.Expressions; using Shoko.Server.Models; using Xunit; diff --git a/Shoko.Tests/Shoko.Tests/LegacyFilterTests.cs b/Shoko.Tests/Shoko.Tests/LegacyFilterTests.cs index 1a2af710a..13a924683 100644 --- a/Shoko.Tests/Shoko.Tests/LegacyFilterTests.cs +++ b/Shoko.Tests/Shoko.Tests/LegacyFilterTests.cs @@ -5,6 +5,7 @@ using Shoko.Server.Filters.Info; using Shoko.Server.Filters.Legacy; using Shoko.Server.Filters.Logic; +using Shoko.Server.Filters.Logic.Expressions; using Shoko.Server.Filters.User; using Shoko.Server.Models; using Xunit; diff --git a/Shoko.Tests/Shoko.Tests/TestFilterable.cs b/Shoko.Tests/Shoko.Tests/TestFilterable.cs index 5bb856f8c..5429e14d4 100644 --- a/Shoko.Tests/Shoko.Tests/TestFilterable.cs +++ b/Shoko.Tests/Shoko.Tests/TestFilterable.cs @@ -30,6 +30,7 @@ public class TestFilterable : IFilterable public int EpisodeCount { get; init; } public int TotalEpisodeCount { get; init; } public decimal LowestAniDBRating { get; init; } + public decimal AverageAniDBRating { get; init; } public decimal HighestAniDBRating { get; init; } public IReadOnlySet VideoSources { get; init; } public IReadOnlySet SharedVideoSources { get; init; }