diff --git a/Shoko.Server/API/v2/Models/common/Filters.cs b/Shoko.Server/API/v2/Models/common/Filters.cs index 96086fda2..1e5695a52 100644 --- a/Shoko.Server/API/v2/Models/common/Filters.cs +++ b/Shoko.Server/API/v2/Models/common/Filters.cs @@ -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>> evaluatedResults = null) + bool all, bool allpic, int pic, TagFilter.Filter tagfilter, IDictionary>> evaluatedResults = null) { var f = new Filters { id = gf.FilterPresetID, name = gf.Name }; var hideCategories = ctx.GetUser().GetHideCategories(); diff --git a/Shoko.Server/Filters/FilterEvaluator.cs b/Shoko.Server/Filters/FilterEvaluator.cs index bb8ef3dcf..84e49fe14 100644 --- a/Shoko.Server/Filters/FilterEvaluator.cs +++ b/Shoko.Server/Filters/FilterEvaluator.cs @@ -89,55 +89,72 @@ public Dictionary>> BatchEvaluateF ArgumentNullException.ThrowIfNull(filters); if (!filters.Any()) return new Dictionary>>(); // 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 movieDBMappings; using (var session = DatabaseFactory.SessionFactory.OpenStatelessSession()) - { movieDBMappings = RepoFactory.CrossRef_AniDB_Other.GetByAnimeIDsAndType(session.Wrap(), null, CrossRefType.MovieDB); - } - ParallelQuery series = null; - ParallelQuery 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>>(); + 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)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>()); + foreach (var filter in filters.Where(filter => !results.ContainsKey(filter))) + results.Add(filter, Array.Empty>()); - return grouped; + return results; } private static IOrderedEnumerable OrderFilterables(FilterPreset filter, IEnumerable filtered) @@ -160,11 +177,11 @@ private static IOrderedEnumerable OrderFilterables(FilterPrese private record FilterableWithID(int SeriesID, int GroupID, IFilterable Filterable, IFilterableUserInfo UserInfo=null); - private record Grouping(int GroupID, int[] SeriesIDs) : IGrouping + private record Grouping(int GroupID, IEnumerable SeriesIDs) : IGrouping { public IEnumerator GetEnumerator() { - return ((IEnumerable)SeriesIDs).GetEnumerator(); + return SeriesIDs.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() diff --git a/Shoko.Server/Filters/FilterExtensions.cs b/Shoko.Server/Filters/FilterExtensions.cs index bd95c4681..e5b8bb21b 100644 --- a/Shoko.Server/Filters/FilterExtensions.cs +++ b/Shoko.Server/Filters/FilterExtensions.cs @@ -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; @@ -27,7 +26,7 @@ public static Filterable ToFilterable(this SVR_AnimeSeries series, ILookup series.GetAnime()?.AirDate, MissingEpisodesDelegate = () => series.MissingEpisodeCount, MissingEpisodesCollectingDelegate = () => series.MissingEpisodeCountGroups, - TagsDelegate = () => series.GetAnime()?.GetAllTags() ?? new HashSet(), + TagsDelegate = () => series.GetAnime()?.GetTags().Select(a => a.TagName).ToHashSet() ?? new HashSet(), CustomTagsDelegate = () => series.GetAnime()?.GetCustomTagsForAnime().Select(a => a.TagName).ToHashSet(StringComparer.InvariantCultureIgnoreCase) ?? new HashSet(), diff --git a/Shoko.Server/Filters/Filterable.cs b/Shoko.Server/Filters/Filterable.cs index c1586be66..1f0a9a54b 100644 --- a/Shoko.Server/Filters/Filterable.cs +++ b/Shoko.Server/Filters/Filterable.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using Shoko.Models.Enums; using Shoko.Server.Filters.Interfaces; @@ -46,224 +45,224 @@ public class Filterable : IFilterable public Func NameDelegate { - init => _name = new Lazy(value, LazyThreadSafetyMode.None); + init => _name = new Lazy(value); } public string SortingName => _sortingName.Value; public Func SortingNameDelegate { - init => _sortingName = new Lazy(value, LazyThreadSafetyMode.None); + init => _sortingName = new Lazy(value); } public int SeriesCount => _seriesCount.Value; public Func SeriesCountDelegate { - init => _seriesCount = new Lazy(value, LazyThreadSafetyMode.None); + init => _seriesCount = new Lazy(value); } public int MissingEpisodes => _missingEpisodes.Value; public Func MissingEpisodesDelegate { - init => _missingEpisodes = new Lazy(value, LazyThreadSafetyMode.None); + init => _missingEpisodes = new Lazy(value); } public int MissingEpisodesCollecting => _missingEpisodesCollecting.Value; public Func MissingEpisodesCollectingDelegate { - init => _missingEpisodesCollecting = new Lazy(value, LazyThreadSafetyMode.None); + init => _missingEpisodesCollecting = new Lazy(value); } public IReadOnlySet Tags => _tags.Value; public Func> TagsDelegate { - init => _tags = new Lazy>(value, LazyThreadSafetyMode.None); + init => _tags = new Lazy>(value); } public IReadOnlySet CustomTags => _customTags.Value; public Func> CustomTagsDelegate { - init => _customTags = new Lazy>(value, LazyThreadSafetyMode.None); + init => _customTags = new Lazy>(value); } public IReadOnlySet Years => _years.Value; public Func> YearsDelegate { - init => _years = new Lazy>(value, LazyThreadSafetyMode.None); + init => _years = new Lazy>(value); } public IReadOnlySet<(int year, AnimeSeason season)> Seasons => _seasons.Value; public Func> SeasonsDelegate { - init => _seasons = new Lazy>(value, LazyThreadSafetyMode.None); + init => _seasons = new Lazy>(value); } public bool HasTvDBLink => _hasTvDBLink.Value; public Func HasTvDBLinkDelegate { - init => _hasTvDBLink = new Lazy(value, LazyThreadSafetyMode.None); + init => _hasTvDBLink = new Lazy(value); } public bool HasMissingTvDbLink => _hasMissingTvDBLink.Value; public Func HasMissingTvDbLinkDelegate { - init => _hasMissingTvDBLink = new Lazy(value, LazyThreadSafetyMode.None); + init => _hasMissingTvDBLink = new Lazy(value); } public bool HasTMDbLink => _hasTMDbLink.Value; public Func HasTMDbLinkDelegate { - init => _hasTMDbLink = new Lazy(value, LazyThreadSafetyMode.None); + init => _hasTMDbLink = new Lazy(value); } public bool HasMissingTMDbLink => _hasMissingTMDbLink.Value; public Func HasMissingTMDbLinkDelegate { - init => _hasMissingTMDbLink = new Lazy(value, LazyThreadSafetyMode.None); + init => _hasMissingTMDbLink = new Lazy(value); } public bool HasTraktLink => _hasTraktLink.Value; public Func HasTraktLinkDelegate { - init => _hasTraktLink = new Lazy(value, LazyThreadSafetyMode.None); + init => _hasTraktLink = new Lazy(value); } public bool HasMissingTraktLink => _hasMissingTraktLink.Value; public Func HasMissingTraktLinkDelegate { - init => _hasMissingTraktLink = new Lazy(value, LazyThreadSafetyMode.None); + init => _hasMissingTraktLink = new Lazy(value); } public bool IsFinished => _isFinished.Value; public Func IsFinishedDelegate { - init => _isFinished = new Lazy(value, LazyThreadSafetyMode.None); + init => _isFinished = new Lazy(value); } public DateTime? AirDate => _airDate.Value; public Func AirDateDelegate { - init => _airDate = new Lazy(value, LazyThreadSafetyMode.None); + init => _airDate = new Lazy(value); } public DateTime? LastAirDate => _lastAirDate.Value; public Func LastAirDateDelegate { - init => _lastAirDate = new Lazy(value, LazyThreadSafetyMode.None); + init => _lastAirDate = new Lazy(value); } public DateTime AddedDate => _addedDate.Value; public Func AddedDateDelegate { - init => _addedDate = new Lazy(value, LazyThreadSafetyMode.None); + init => _addedDate = new Lazy(value); } public DateTime LastAddedDate => _lastAddedDate.Value; public Func LastAddedDateDelegate { - init => _lastAddedDate = new Lazy(value, LazyThreadSafetyMode.None); + init => _lastAddedDate = new Lazy(value); } public int EpisodeCount => _episodeCount.Value; public Func EpisodeCountDelegate { - init => _episodeCount = new Lazy(value, LazyThreadSafetyMode.None); + init => _episodeCount = new Lazy(value); } public int TotalEpisodeCount => _totalEpisodeCount.Value; public Func TotalEpisodeCountDelegate { - init => _totalEpisodeCount = new Lazy(value, LazyThreadSafetyMode.None); + init => _totalEpisodeCount = new Lazy(value); } public decimal LowestAniDBRating => _lowestAniDBRating.Value; public Func LowestAniDBRatingDelegate { - init => _lowestAniDBRating = new Lazy(value, LazyThreadSafetyMode.None); + init => _lowestAniDBRating = new Lazy(value); } public decimal HighestAniDBRating => _highestAniDBRating.Value; public Func HighestAniDBRatingDelegate { - init => _highestAniDBRating = new Lazy(value, LazyThreadSafetyMode.None); + init => _highestAniDBRating = new Lazy(value); } public decimal AverageAniDBRating => _averageAniDBRating.Value; public Func AverageAniDBRatingDelegate { - init => _averageAniDBRating = new Lazy(value, LazyThreadSafetyMode.None); + init => _averageAniDBRating = new Lazy(value); } public IReadOnlySet VideoSources => _videoSources.Value; public Func> VideoSourcesDelegate { - init => _videoSources = new Lazy>(value, LazyThreadSafetyMode.None); + init => _videoSources = new Lazy>(value); } public IReadOnlySet SharedVideoSources => _sharedVideoSources.Value; public Func> SharedVideoSourcesDelegate { - init => _sharedVideoSources = new Lazy>(value, LazyThreadSafetyMode.None); + init => _sharedVideoSources = new Lazy>(value); } public IReadOnlySet AnimeTypes => _animeTypes.Value; public Func> AnimeTypesDelegate { - init => _animeTypes = new Lazy>(value, LazyThreadSafetyMode.None); + init => _animeTypes = new Lazy>(value); } public IReadOnlySet AudioLanguages => _audioLanguages.Value; public Func> AudioLanguagesDelegate { - init => _audioLanguages = new Lazy>(value, LazyThreadSafetyMode.None); + init => _audioLanguages = new Lazy>(value); } public IReadOnlySet SharedAudioLanguages => _sharedAudioLanguages.Value; public Func> SharedAudioLanguagesDelegate { - init => _sharedAudioLanguages = new Lazy>(value, LazyThreadSafetyMode.None); + init => _sharedAudioLanguages = new Lazy>(value); } public IReadOnlySet SubtitleLanguages => _subtitleLanguages.Value; public Func> SubtitleLanguagesDelegate { - init => _subtitleLanguages = new Lazy>(value, LazyThreadSafetyMode.None); + init => _subtitleLanguages = new Lazy>(value); } public IReadOnlySet SharedSubtitleLanguages => _sharedSubtitleLanguages.Value; public Func> SharedSubtitleLanguagesDelegate { - init => _sharedSubtitleLanguages = new Lazy>(value, LazyThreadSafetyMode.None); + init => _sharedSubtitleLanguages = new Lazy>(value); } public IReadOnlySet Resolutions => _resolutions.Value; @@ -271,7 +270,7 @@ public Func> ResolutionsDelegate { init { - _resolutions = new Lazy>(value, LazyThreadSafetyMode.None); + _resolutions = new Lazy>(value); } } }