Skip to content

Commit

Permalink
Improvements to Filter Logic
Browse files Browse the repository at this point in the history
  • Loading branch information
da3dsoul committed Nov 12, 2023
1 parent 2a1a0f9 commit 28aadc3
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 72 deletions.
2 changes: 1 addition & 1 deletion Shoko.Server/API/v2/Models/common/Filters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public Filters()

internal static Filters GenerateFromGroupFilter(HttpContext ctx, FilterPreset gf, int uid, bool nocast,
bool notag, int level,
bool all, bool allpic, int pic, TagFilter.Filter tagfilter, Dictionary<FilterPreset, IEnumerable<IGrouping<int, int>>> evaluatedResults = null)
bool all, bool allpic, int pic, TagFilter.Filter tagfilter, IDictionary<FilterPreset, IEnumerable<IGrouping<int, int>>> evaluatedResults = null)
{
var f = new Filters { id = gf.FilterPresetID, name = gf.Name };
var hideCategories = ctx.GetUser().GetHideCategories();
Expand Down
87 changes: 52 additions & 35 deletions Shoko.Server/Filters/FilterEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,55 +89,72 @@ public Dictionary<FilterPreset, IEnumerable<IGrouping<int, int>>> BatchEvaluateF
ArgumentNullException.ThrowIfNull(filters);
if (!filters.Any()) return new Dictionary<FilterPreset, IEnumerable<IGrouping<int, int>>>();
// count it as a user filter if it needs to sort using a user-dependent expression
var needsUser = filters.Any(a => (a?.Expression?.UserDependent ?? false) || skipSorting && (a?.SortingExpression?.UserDependent ?? false));
var hasSeries = filters.Any(a => a.ApplyAtSeriesLevel);
var seriesNeedsUser = hasSeries && filters.Any(a =>
{
if (!a.ApplyAtSeriesLevel) return false;
if (a.Expression?.UserDependent ?? false) return true;
if (skipSorting) return false;
return a.SortingExpression?.UserDependent ?? false;
});
var hasGroups = filters.Any(a => !a.ApplyAtSeriesLevel);
var groupsNeedUser = hasGroups && filters.Any(a =>
{
if (a.ApplyAtSeriesLevel) return false;
if (a.Expression?.UserDependent ?? false) return true;
if (skipSorting) return false;
return a.SortingExpression?.UserDependent ?? false;
});
var needsUser = seriesNeedsUser || groupsNeedUser;
if (needsUser && userID == null) throw new ArgumentNullException(nameof(userID));

var user = userID != null ? RepoFactory.JMMUser.GetByID(userID.Value) : null;
ILookup<int, CrossRef_AniDB_Other> movieDBMappings;
using (var session = DatabaseFactory.SessionFactory.OpenStatelessSession())
{
movieDBMappings = RepoFactory.CrossRef_AniDB_Other.GetByAnimeIDsAndType(session.Wrap(), null, CrossRefType.MovieDB);
}

ParallelQuery<FilterableWithID> series = null;
ParallelQuery<FilterableWithID> groups = null;
FilterableWithID[] series = null;
if (hasSeries)
{
var allowedSeries = _series.GetAll().Where(a => user?.AllowedSeries(a) ?? true);
series = seriesNeedsUser
? allowedSeries.Select(a =>
new FilterableWithID(a.AnimeSeriesID, a.AnimeGroupID, a.ToFilterable(movieDBMappings), a.ToFilterableUserInfo(userID.Value))).ToArray()
: allowedSeries.Select(a => new FilterableWithID(a.AnimeSeriesID, a.AnimeGroupID, a.ToFilterable(movieDBMappings))).ToArray();
}

var filterables = filters.ToDictionary(filter => filter, filter =>
FilterableWithID[] groups = null;
if (hasGroups)
{
var filterNeedsUser = (filter.Expression?.UserDependent ?? false) || skipSorting && (filter?.SortingExpression?.UserDependent ?? false);
return filter.ApplyAtSeriesLevel switch
{
true when filterNeedsUser => series ??= _series.GetAll().AsParallel().Where(a => user?.AllowedSeries(a) ?? true)
.Select(a => new FilterableWithID(a.AnimeSeriesID, a.AnimeGroupID, a.ToFilterable(movieDBMappings), a.ToFilterableUserInfo(userID.Value))),
true => series ??= _series.GetAll().AsParallel().Where(a => user?.AllowedSeries(a) ?? true)
.Select(a => new FilterableWithID(a.AnimeSeriesID, a.AnimeGroupID, a.ToFilterable(movieDBMappings))),
false when filterNeedsUser => groups ??= _groups.GetAll().AsParallel().Where(a => user?.AllowedGroup(a) ?? true)
.Select(a => new FilterableWithID(0, a.AnimeGroupID, a.ToFilterable(movieDBMappings), a.ToFilterableUserInfo(userID.Value))),
false => groups ??= _groups.GetAll().AsParallel().Where(a => user?.AllowedGroup(a) ?? true)
.Select(a => new FilterableWithID(0, a.AnimeGroupID, a.ToFilterable(movieDBMappings)))
};
});
var allowedGroups = _groups.GetAll().Where(a => user?.AllowedGroup(a) ?? true);
groups = groupsNeedUser
? allowedGroups.Select(a => new FilterableWithID(0, a.AnimeGroupID, a.ToFilterable(movieDBMappings), a.ToFilterableUserInfo(userID.Value)))
.ToArray()
: allowedGroups.Select(a => new FilterableWithID(0, a.AnimeGroupID, a.ToFilterable(movieDBMappings))).ToArray();
}

// Filtering
var filtered = filterables.SelectMany(a => a.Value.Select(filterable => (Filter: a.Key, FilterableWithID: filterable))).Where(a =>
(a.Filter.FilterType & GroupFilterType.Directory) == 0 && (a.Filter.Expression?.Evaluate(a.FilterableWithID.Filterable, a.FilterableWithID.UserInfo) ?? true));
var filterableMap = filters.Where(a => (a.FilterType & GroupFilterType.Directory) == 0)
.ToDictionary(filter => filter, filter => filter.ApplyAtSeriesLevel switch { true => series, false => groups });

// ordering
var grouped = filtered.GroupBy(a => a.Filter).ToDictionary(a => a.Key, f =>
// Filtering
var results = new Dictionary<FilterPreset, IEnumerable<IGrouping<int, int>>>();
foreach (var (filter, filterables) in filterableMap.AsParallel())
{
var ordered = skipSorting ? f.Select(a => a.FilterableWithID) : OrderFilterables(f.Key, f.Select(a => a.FilterableWithID));

var expression = filter.Expression;
var filtered = filterables.AsParallel().AsUnordered().Where(a => expression?.Evaluate(a.Filterable, a.UserInfo) ?? true).ToArray();
var ordered = skipSorting ? (IEnumerable<FilterableWithID>)filtered : OrderFilterables(filter, filtered);

var result = ordered.GroupBy(a => a.GroupID, a => a.SeriesID);
if (!f.Key.ApplyAtSeriesLevel)
result = result.Select(a => new Grouping(a.Key, _series.GetByGroupID(a.Key).Select(ser => ser.AnimeSeriesID).ToArray()));
if (!filter.ApplyAtSeriesLevel)
result = result.Select(a => new Grouping(a.Key, _series.GetByGroupID(a.Key).Select(ser => ser.AnimeSeriesID)));

return result;
});
results.Add(filter, result);
}

foreach (var filter in filters.Where(filter => !grouped.ContainsKey(filter)))
grouped.Add(filter, Array.Empty<IGrouping<int, int>>());
foreach (var filter in filters.Where(filter => !results.ContainsKey(filter)))
results.Add(filter, Array.Empty<IGrouping<int, int>>());

return grouped;
return results;
}

private static IOrderedEnumerable<FilterableWithID> OrderFilterables(FilterPreset filter, IEnumerable<FilterableWithID> filtered)
Expand All @@ -160,11 +177,11 @@ private static IOrderedEnumerable<FilterableWithID> OrderFilterables(FilterPrese

private record FilterableWithID(int SeriesID, int GroupID, IFilterable Filterable, IFilterableUserInfo UserInfo=null);

private record Grouping(int GroupID, int[] SeriesIDs) : IGrouping<int, int>
private record Grouping(int GroupID, IEnumerable<int> SeriesIDs) : IGrouping<int, int>
{
public IEnumerator<int> GetEnumerator()
{
return ((IEnumerable<int>)SeriesIDs).GetEnumerator();
return SeriesIDs.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
Expand Down
3 changes: 1 addition & 2 deletions Shoko.Server/Filters/FilterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Shoko.Server.Models;
using Shoko.Server.Providers.AniDB;
using Shoko.Server.Repositories;
using Shoko.Server.Utilities;
using AnimeType = Shoko.Models.Enums.AnimeType;

namespace Shoko.Server.Filters;
Expand All @@ -27,7 +26,7 @@ public static Filterable ToFilterable(this SVR_AnimeSeries series, ILookup<int,
AirDateDelegate = () => series.GetAnime()?.AirDate,
MissingEpisodesDelegate = () => series.MissingEpisodeCount,
MissingEpisodesCollectingDelegate = () => series.MissingEpisodeCountGroups,
TagsDelegate = () => series.GetAnime()?.GetAllTags() ?? new HashSet<string>(),
TagsDelegate = () => series.GetAnime()?.GetTags().Select(a => a.TagName).ToHashSet() ?? new HashSet<string>(),
CustomTagsDelegate =
() => series.GetAnime()?.GetCustomTagsForAnime().Select(a => a.TagName).ToHashSet(StringComparer.InvariantCultureIgnoreCase) ??
new HashSet<string>(),
Expand Down
Loading

0 comments on commit 28aadc3

Please sign in to comment.