Skip to content

Commit

Permalink
Extract the File Filtering. Hopefully Fix No Files Returned
Browse files Browse the repository at this point in the history
  • Loading branch information
da3dsoul committed Dec 18, 2023
1 parent b3fb50a commit 4b87246
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 216 deletions.
125 changes: 6 additions & 119 deletions Shoko.Server/API/v3/Controllers/FileController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
using CommandRequestPriority = Shoko.Server.Server.CommandRequestPriority;
using AVDump = Shoko.Server.API.v3.Models.Shoko.AVDump;
using File = Shoko.Server.API.v3.Models.Shoko.File;
using FileSortCriteria = Shoko.Server.API.v3.Models.Shoko.File.FileSortCriteria;
using Path = System.IO.Path;
using MediaInfo = Shoko.Server.API.v3.Models.Shoko.MediaInfo;
using DataSource = Shoko.Server.API.v3.Models.Common.DataSource;
Expand Down Expand Up @@ -77,70 +76,7 @@ public ActionResult<ListResult<File>> GetFiles(
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] List<string> sortOrder = null,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet<DataSource> includeDataFrom = null)
{
include ??= Array.Empty<FileNonDefaultIncludeType>();
exclude ??= Array.Empty<FileExcludeTypes>();
include_only ??= Array.Empty<FileIncludeOnlyType>();

// Filtering.
var user = User;
var includeLocations = exclude.Contains(FileExcludeTypes.Duplicates) ||
(sortOrder?.Any(criteria => criteria.Contains(FileSortCriteria.DuplicateCount.ToString())) ?? false);
var includeUserRecord = exclude.Contains(FileExcludeTypes.Watched) || (sortOrder?.Any(criteria =>
criteria.Contains(FileSortCriteria.ViewedAt.ToString()) || criteria.Contains(FileSortCriteria.WatchedAt.ToString())) ?? false);
var enumerable = RepoFactory.VideoLocal.GetAll()
.Select(video => (
Video: video,
BestLocation: video.GetBestVideoLocalPlace(),
Locations: includeLocations ? video.Places : null,
UserRecord: includeUserRecord ? video.GetUserRecord(user.JMMUserID) : null
))
.Where(tuple =>
{
var (video, _, locations, userRecord) = tuple;
var xrefs = video.EpisodeCrossRefs;
var isAnimeAllowed = xrefs
.Select(xref => xref.AnimeID)
.Distinct()
.Select(anidbID => RepoFactory.AniDB_Anime.GetByAnimeID(anidbID))
.Where(anime => anime != null)
.All(user.AllowedAnime);
if (!isAnimeAllowed)
return false;

if (!include.Contains(FileNonDefaultIncludeType.Ignored) && video.IsIgnored) return false;
if (include_only.Contains(FileIncludeOnlyType.Ignored) && !video.IsIgnored) return false;

if (exclude.Contains(FileExcludeTypes.Duplicates) && locations.Count > 1) return false;
if (include_only.Contains(FileIncludeOnlyType.Duplicates) && locations.Count <= 1) return false;

if (exclude.Contains(FileExcludeTypes.Unrecognized) && xrefs.Count == 0) return false;
if (include_only.Contains(FileIncludeOnlyType.Unrecognized) && xrefs.Count > 0) return false;

if (exclude.Contains(FileExcludeTypes.ManualLinks) && xrefs.Count > 0 && xrefs.Any(xref => xref.CrossRefSource != (int)CrossRefSource.AniDB)) return false;
if (include_only.Contains(FileIncludeOnlyType.ManualLinks) && xrefs.Count == 0 || xrefs.Any(xref => xref.CrossRefSource == (int)CrossRefSource.AniDB)) return false;

if (exclude.Contains(FileExcludeTypes.Watched) && userRecord?.WatchedDate != null) return false;
if (include_only.Contains(FileIncludeOnlyType.Watched) && userRecord?.WatchedDate == null) return false;

return true;
});

// Sorting.
if (sortOrder != null && sortOrder.Count > 0)
enumerable = Models.Shoko.File.OrderBy(enumerable, sortOrder);
else
enumerable = Models.Shoko.File.OrderBy(enumerable, new()
{
// First sort by import folder from A-Z.
FileSortCriteria.ImportFolderName.ToString(),
// Then by the relative path inside the import folder, from A-Z.
FileSortCriteria.RelativePath.ToString(),
});

// Skip and limit.
return enumerable.ToListResult(
tuple => new File(tuple.UserRecord, tuple.Video, include.Contains(FileNonDefaultIncludeType.XRefs), includeDataFrom,
include.Contains(FileNonDefaultIncludeType.MediaInfo), include.Contains(FileNonDefaultIncludeType.AbsolutePaths)), page, pageSize);
return ModelHelper.FilterFiles(RepoFactory.VideoLocal.GetAll(), User, pageSize, page, include, exclude, include_only, sortOrder, includeDataFrom);
}

/// <summary>
Expand All @@ -167,65 +103,16 @@ public ActionResult<ListResult<File>> Search([FromRoute] string query,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet<DataSource> includeDataFrom = null,
[FromQuery] bool fuzzy = true)
{
include ??= Array.Empty<FileNonDefaultIncludeType>();
exclude ??= Array.Empty<FileExcludeTypes>();
include_only ??= Array.Empty<FileIncludeOnlyType>();

// Filtering.
var user = User;
var includeUserRecord = exclude.Contains(FileExcludeTypes.Watched) || (sortOrder?.Any(criteria =>
criteria.Contains(FileSortCriteria.ViewedAt.ToString()) || criteria.Contains(FileSortCriteria.WatchedAt.ToString())) ?? false);
var enumerable = RepoFactory.VideoLocal.GetAll()
.Select(video => (
Video: video,
BestLocation: video.GetBestVideoLocalPlace(),
Locations: video.Places,
UserRecord: includeUserRecord ? video.GetUserRecord(user.JMMUserID) : null
))
.Where(tuple =>
{
var (video, _, locations, userRecord) = tuple;
var xrefs = video.EpisodeCrossRefs;
var isAnimeAllowed = xrefs
.Select(xref => xref.AnimeID)
.Distinct()
.Select(anidbID => RepoFactory.AniDB_Anime.GetByAnimeID(anidbID))
.Where(anime => anime != null)
.All(user.AllowedAnime);
if (!isAnimeAllowed)
return false;

if (!include.Contains(FileNonDefaultIncludeType.Ignored) && video.IsIgnored) return false;
if (include_only.Contains(FileIncludeOnlyType.Ignored) && !video.IsIgnored) return false;

if (exclude.Contains(FileExcludeTypes.Duplicates) && locations.Count > 1) return false;
if (include_only.Contains(FileIncludeOnlyType.Duplicates) && locations.Count <= 1) return false;

if (exclude.Contains(FileExcludeTypes.Unrecognized) && xrefs.Count == 0) return false;
if (include_only.Contains(FileIncludeOnlyType.Unrecognized) && xrefs.Count > 0) return false;

if (exclude.Contains(FileExcludeTypes.ManualLinks) && xrefs.Count > 0 && xrefs.Any(xref => xref.CrossRefSource != (int)CrossRefSource.AniDB)) return false;
if (include_only.Contains(FileIncludeOnlyType.ManualLinks) && xrefs.Count == 0 || xrefs.Any(xref => xref.CrossRefSource == (int)CrossRefSource.AniDB)) return false;

if (exclude.Contains(FileExcludeTypes.Watched) && userRecord?.WatchedDate != null) return false;
if (include_only.Contains(FileIncludeOnlyType.Watched) && userRecord?.WatchedDate == null) return false;

return true;
});
var enumerable = ModelHelper.FilterFiles(RepoFactory.VideoLocal.GetAll(), User, pageSize, page, include, exclude, include_only, sortOrder,
includeDataFrom).List;

// Search.
enumerable = enumerable
.Search(query, tuple => tuple.Locations.Select(place => place.FullServerPath).Where(path => path != null), fuzzy)
var searched = enumerable
.Search(query, tuple => tuple.Locations.Select(place => place.AbsolutePath).Where(path => path != null), fuzzy)
.Select(result => result.Result);

// Sorting.
if (sortOrder != null && sortOrder.Count > 0)
enumerable = Models.Shoko.File.OrderBy(enumerable, sortOrder);

// Skip and limit.
return enumerable.ToListResult(
tuple => new File(tuple.UserRecord, tuple.Video, include.Contains(FileNonDefaultIncludeType.XRefs), includeDataFrom,
include.Contains(FileNonDefaultIncludeType.MediaInfo), include.Contains(FileNonDefaultIncludeType.AbsolutePaths)), page, pageSize);
return searched.ToListResult(page, pageSize);
}

/// <summary>
Expand Down
34 changes: 0 additions & 34 deletions Shoko.Server/API/v3/Controllers/SeriesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1367,40 +1367,6 @@ public ActionResult<Episode> GetNextUnwatchedEpisode([FromRoute] int seriesID,

#region File

/// <summary>
/// Get the <see cref="File"/>s for the <see cref="Series"/> with the given <paramref name="seriesID"/>.
/// </summary>
/// <param name="seriesID">Series ID</param>
/// <param name="includeXRefs">Set to true to include series and episode cross-references.</param>
/// <param name="includeDataFrom">Include data from selected <see cref="DataSource"/>s.</param>
/// <param name="isManuallyLinked">Omit to select all files. Set to true to only select manually
/// linked files, or set to false to only select automatically linked files.</param>
/// <param name="includeMediaInfo">Include media info data.</param>
/// <param name="includeAbsolutePaths">Include absolute paths for the file locations.</param>
/// <returns></returns>
[HttpGet("{seriesID}/File")]
public ActionResult<List<File>> GetFilesForSeries([FromRoute] int seriesID, [FromQuery] bool includeXRefs = false,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet<DataSource> includeDataFrom = null,
[FromQuery] bool? isManuallyLinked = null, [FromQuery] bool includeMediaInfo = false,
[FromQuery] bool includeAbsolutePaths = false)
{
var user = User;
var series = RepoFactory.AnimeSeries.GetByID(seriesID);
if (series == null)
{
return NotFound(SeriesController.SeriesNotFoundWithSeriesID);
}

if (!user.AllowedSeries(series))
{
return Forbid(SeriesController.SeriesForbiddenForUser);
}

return series.GetVideoLocals(isManuallyLinked.HasValue ? isManuallyLinked.Value ? CrossRefSource.User : CrossRefSource.AniDB : null)
.Select(file => new File(HttpContext, file, includeXRefs, includeDataFrom, includeMediaInfo, includeAbsolutePaths))
.ToList();
}

/// <summary>
/// Rescan all files for a series.
/// </summary>
Expand Down
103 changes: 40 additions & 63 deletions Shoko.Server/API/v3/Controllers/TreeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,45 @@ public ActionResult<Series> GetMainSeriesInGroup([FromRoute] int groupID, [FromQ
}

#endregion


/// <summary>
/// Get the <see cref="File"/>s for the <see cref="Series"/> with the given <paramref name="seriesID"/>.
/// </summary>
/// <param name="seriesID">Series ID</param>
/// <param name="pageSize">Limits the number of results per page. Set to 0 to disable the limit.</param>
/// <param name="page">Page number.</param>
/// <param name="include">Include items that are not included by default</param>
/// <param name="exclude">Exclude items of certain types</param>
/// <param name="include_only">Filter to only include items of certain types</param>
/// <param name="sortOrder">Sort ordering. Attach '-' at the start to reverse the order of the criteria.</param>
/// <param name="includeDataFrom">Include data from selected <see cref="DataSource"/>s.</param>
/// <returns></returns>
[HttpGet("{seriesID}/File")]
public ActionResult<ListResult<File>> GetFilesForSeries([FromRoute] int seriesID,
[FromQuery, Range(0, 1000)] int pageSize = 100,
[FromQuery, Range(1, int.MaxValue)] int page = 1,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] FileNonDefaultIncludeType[] include = default,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] FileExcludeTypes[] exclude = default,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] FileIncludeOnlyType[] include_only = default,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] List<string> sortOrder = null,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet<DataSource> includeDataFrom = null)
{
var user = User;
var series = RepoFactory.AnimeSeries.GetByID(seriesID);
if (series == null)
{
return NotFound(SeriesController.SeriesNotFoundWithSeriesID);
}

if (!user.AllowedSeries(series))
{
return Forbid(SeriesController.SeriesForbiddenForUser);
}

return ModelHelper.FilterFiles(series.GetVideoLocals(), user, pageSize, page, include, exclude, include_only, sortOrder, includeDataFrom);
}

#region Episode

/// <summary>
Expand Down Expand Up @@ -561,10 +600,6 @@ public ActionResult<ListResult<File>> GetFilesForEpisode([FromRoute] int episode
return NotFound(EpisodeController.EpisodeNotFoundWithEpisodeID);
}

include ??= Array.Empty<FileNonDefaultIncludeType>();
exclude ??= Array.Empty<FileExcludeTypes>();
include_only ??= Array.Empty<FileIncludeOnlyType>();

var series = episode.GetAnimeSeries();
if (series == null)
{
Expand All @@ -576,65 +611,7 @@ public ActionResult<ListResult<File>> GetFilesForEpisode([FromRoute] int episode
return Forbid(EpisodeController.EpisodeForbiddenForUser);
}

var user = User;
var includeLocations = exclude.Contains(FileExcludeTypes.Duplicates) ||
(sortOrder?.Any(criteria => criteria.Contains(FileSortCriteria.DuplicateCount.ToString())) ?? false);
var includeUserRecord = exclude.Contains(FileExcludeTypes.Watched) || (sortOrder?.Any(criteria =>
criteria.Contains(FileSortCriteria.ViewedAt.ToString()) || criteria.Contains(FileSortCriteria.WatchedAt.ToString())) ?? false);
var enumerable = episode.GetVideoLocals()
.Select(video => (
Video: video,
BestLocation: video.GetBestVideoLocalPlace(),
Locations: includeLocations ? video.Places : null,
UserRecord: includeUserRecord ? video.GetUserRecord(user.JMMUserID) : null
))
.Where(tuple =>
{
var (video, bestLocation, locations, userRecord) = tuple;
var xrefs = video.EpisodeCrossRefs;
var isAnimeAllowed = xrefs
.Select(xref => xref.AnimeID)
.Distinct()
.Select(anidbID => RepoFactory.AniDB_Anime.GetByAnimeID(anidbID))
.Where(anime => anime != null)
.All(user.AllowedAnime);
if (!isAnimeAllowed)
return false;

if (!include.Contains(FileNonDefaultIncludeType.Ignored) && video.IsIgnored) return false;
if (include_only.Contains(FileIncludeOnlyType.Ignored) && !video.IsIgnored) return false;

if (exclude.Contains(FileExcludeTypes.Duplicates) && locations.Count > 1) return false;
if (include_only.Contains(FileIncludeOnlyType.Duplicates) && locations.Count <= 1) return false;

if (exclude.Contains(FileExcludeTypes.Unrecognized) && xrefs.Count == 0) return false;
if (include_only.Contains(FileIncludeOnlyType.Unrecognized) && xrefs.Count > 0) return false;

if (exclude.Contains(FileExcludeTypes.ManualLinks) && xrefs.Count > 0 && xrefs.Any(xref => xref.CrossRefSource != (int)CrossRefSource.AniDB)) return false;
if (include_only.Contains(FileIncludeOnlyType.ManualLinks) && xrefs.Count == 0 || xrefs.Any(xref => xref.CrossRefSource == (int)CrossRefSource.AniDB)) return false;

if (exclude.Contains(FileExcludeTypes.Watched) && userRecord?.WatchedDate != null) return false;
if (include_only.Contains(FileIncludeOnlyType.Watched) && userRecord?.WatchedDate == null) return false;

return true;
});

// Sorting.
if (sortOrder != null && sortOrder.Count > 0)
enumerable = Models.Shoko.File.OrderBy(enumerable, sortOrder);
else
enumerable = Models.Shoko.File.OrderBy(enumerable, new()
{
// First sort by import folder from A-Z.
FileSortCriteria.ImportFolderName.ToString(),
// Then by the relative path inside the import folder, from A-Z.
FileSortCriteria.RelativePath.ToString(),
});

// Skip and limit.
return enumerable.ToListResult(
tuple => new File(tuple.UserRecord, tuple.Video, include.Contains(FileNonDefaultIncludeType.XRefs), includeDataFrom,
include.Contains(FileNonDefaultIncludeType.MediaInfo), include.Contains(FileNonDefaultIncludeType.AbsolutePaths)), page, pageSize);
return ModelHelper.FilterFiles(episode.GetVideoLocals(), User, pageSize, page, include, exclude, include_only, sortOrder, includeDataFrom);
}

#endregion
Expand Down
Loading

0 comments on commit 4b87246

Please sign in to comment.