Skip to content

Commit

Permalink
refactor: refactor soft duplicate endpoints
Browse files Browse the repository at this point in the history
refactor the internals for how we get soft duplicates to support
filtering by anime id, add an endpoint to get series with soft
duplicates, and add query parameters to the existing episode
endpoints to filter by series id.
  • Loading branch information
revam committed Nov 21, 2023
1 parent 7101c40 commit 2094167
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1239,7 +1239,7 @@ public List<CL_AnimeEpisode_User> GetAllEpisodesWithMultipleFiles(int userID, bo
}
}

foreach (var ep in RepoFactory.AnimeEpisode.GetEpisodesWithMultipleFiles(ignoreVariations))
foreach (var ep in RepoFactory.AnimeEpisode.GetWithSoftDuplicates(ignoreVariations))
{
if (onlyFinishedSeries)
{
Expand Down
2 changes: 1 addition & 1 deletion Shoko.Server/API/v2/Modules/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,7 @@ public object GetMultipleFiles([FromQuery] API_Call_Parameters para)
var results = new Dictionary<int, Serie>();
try
{
var list = RepoFactory.AnimeEpisode.GetEpisodesWithMultipleFiles(true).ToList();
var list = RepoFactory.AnimeEpisode.GetWithSoftDuplicates(true).ToList();
foreach (var ep in list)
{
var series = ep?.GetAnimeSeries();
Expand Down
32 changes: 30 additions & 2 deletions Shoko.Server/API/v3/Controllers/EpisodeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,8 @@ public ActionResult SetWatchedStatusOnEpisode([FromRoute] int episodeID, [FromRo
/// <summary>
/// Get episodes with multiple files attached.
/// </summary>
/// <param name="seriesID">Shoko Series ID</param>
/// <param name="animeID">AniDB Anime ID</param>
/// <param name="includeDataFrom">Include data from selected <see cref="DataSource"/>s.</param>
/// <param name="includeFiles">Include files with the episodes.</param>
/// <param name="includeMediaInfo">Include media info data.</param>
Expand All @@ -476,8 +478,10 @@ public ActionResult SetWatchedStatusOnEpisode([FromRoute] int episodeID, [FromRo
/// <param name="pageSize">Limits the number of results per page. Set to 0 to disable the limit.</param>
/// <param name="page">Page number.</param>
/// <returns></returns>
[HttpGet("WithMultipleFiles")]
[HttpGet("WithSoftDuplicates")]
public ActionResult<ListResult<Episode>> GetSoftDuplicatesForEpisode(
[FromQuery] int? seriesID = null,
[FromQuery] int? animeID = null,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet<DataSource> includeDataFrom = null,
[FromQuery] bool includeFiles = true,
[FromQuery] bool includeMediaInfo = true,
Expand All @@ -487,8 +491,32 @@ public ActionResult<ListResult<Episode>> GetSoftDuplicatesForEpisode(
[FromQuery, Range(0, 1000)] int pageSize = 100,
[FromQuery, Range(1, int.MaxValue)] int page = 1)
{
if (seriesID.HasValue && !animeID.HasValue && seriesID.Value > 0)
{
var series = RepoFactory.AnimeSeries.GetByID(seriesID.Value);
if (series == null)
return new ListResult<Episode>();

if (!User.AllowedSeries(series))
return new ListResult<Episode>();
animeID = series.AniDB_ID;
}
else if (animeID.HasValue && animeID.Value > 0)
{
var anime = RepoFactory.AniDB_Anime.GetByAnimeID(animeID.Value);
if (anime == null)
return new ListResult<Episode>();

if (!User.AllowedAnime(anime))
return new ListResult<Episode>();
}
else if (animeID.HasValue && animeID.Value == 0)
{
animeID = null;
}

IEnumerable<SVR_AnimeEpisode> enumerable =
RepoFactory.AnimeEpisode.GetEpisodesWithMultipleFiles(ignoreVariations);
RepoFactory.AnimeEpisode.GetWithSoftDuplicates(ignoreVariations, animeID);
if (onlyFinishedSeries)
{
var dictSeriesFinishedAiring = RepoFactory.AnimeSeries.GetAll()
Expand Down
35 changes: 35 additions & 0 deletions Shoko.Server/API/v3/Controllers/SeriesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
using Shoko.Commons.Extensions;
using Shoko.Models.Enums;
using Shoko.Models.Server;
using Shoko.Plugin.Abstractions.DataModels;
Expand Down Expand Up @@ -2096,4 +2097,38 @@ private static void CheckTitlesStartsWith(SVR_AnimeSeries a, string query,
#endregion

#endregion

#region Utility

/// <summary>
/// Get series with soft duplicates.
/// </summary>
/// <param name="includeDataFrom">Include data from selected <see cref="DataSource"/>s.</param>
/// <param name="ignoreVariations">Ignore manually toggled variations in the results.</param>
/// <param name="onlyFinishedSeries">Only show finished series.</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>
/// <returns></returns>
[HttpGet("WithSoftDuplicates")]
public ActionResult<ListResult<Series>> GetSeriesWithSoftDuplicates(
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet<DataSource> includeDataFrom = null,
[FromQuery] bool ignoreVariations = true,
[FromQuery] bool onlyFinishedSeries = false,
[FromQuery, Range(0, 1000)] int pageSize = 100,
[FromQuery, Range(1, int.MaxValue)] int page = 1)
{
IEnumerable<SVR_AnimeSeries> enumerable =
RepoFactory.AnimeSeries.GetWithSoftDuplicates(ignoreVariations);
if (onlyFinishedSeries)
{
var dictSeriesFinishedAiring = RepoFactory.AnimeSeries.GetAll()
.ToDictionary(a => a.AnimeSeriesID, a => a.GetAnime().GetFinishedAiring());
enumerable = enumerable.Where(a => a.GetAnime().GetFinishedAiring());
}

return enumerable
.ToListResult(series => _seriesFactory.GetSeries(series, false, includeDataFrom), page, pageSize);
}

#endregion
}
38 changes: 34 additions & 4 deletions Shoko.Server/API/v3/Controllers/WebUIController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,14 +284,44 @@ public ActionResult<WebUISeriesFileSummary> GetSeriesFileSummary(
/// Get the list of file ids to remove according to the file quality
/// preference.
/// </summary>
/// <param name="seriesID">Shoko Series ID</param>
/// <param name="animeID">AniDB Anime ID</param>
/// <param name="ignoreVariations">Ignore manually toggled variations in the results.</param>
/// <param name="onlyFinishedSeries">Only show finished series.</param>
/// <returns></returns>
[HttpGet("Episode/WithMultipleFiles/FilesToDelete")]
public ActionResult<List<int>> GetFileIdsWithPreference([FromQuery] bool ignoreVariations = true, [FromQuery] bool onlyFinishedSeries = false)
[HttpGet("Episode/WithSoftDuplicates/FilesToDelete")]
public ActionResult<List<int>> GetFileIdsWithPreference(
[FromQuery] int? seriesID = null,
[FromQuery] int? animeID = null,
[FromQuery] bool ignoreVariations = true,
[FromQuery] bool onlyFinishedSeries = false
)
{
IEnumerable<SVR_AnimeEpisode> enumerable =
RepoFactory.AnimeEpisode.GetEpisodesWithMultipleFiles(ignoreVariations);
if (seriesID.HasValue && !animeID.HasValue && seriesID.Value > 0)
{
var series = RepoFactory.AnimeSeries.GetByID(seriesID.Value);
if (series == null)
return new List<int>();

if (!User.AllowedSeries(series))
return new List<int>();
animeID = series.AniDB_ID;
}
else if (animeID.HasValue && animeID.Value > 0)
{
var anime = RepoFactory.AniDB_Anime.GetByAnimeID(animeID.Value);
if (anime == null)
return new List<int>();

if (!User.AllowedAnime(anime))
return new List<int>();
}
else if (animeID.HasValue && animeID.Value == 0)
{
animeID = null;
}

IEnumerable<SVR_AnimeEpisode> enumerable = RepoFactory.AnimeEpisode.GetWithSoftDuplicates(ignoreVariations, animeID);
if (onlyFinishedSeries)
{
var dictSeriesFinishedAiring = RepoFactory.AnimeSeries.GetAll()
Expand Down
27 changes: 23 additions & 4 deletions Shoko.Server/Repositories/Cached/AnimeEpisodeRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NHibernate;
using NutzCode.InMemoryIndex;
using Shoko.Commons.Extensions;
using Shoko.Models.Enums;
Expand Down Expand Up @@ -89,21 +90,39 @@ public List<SVR_AnimeEpisode> GetByHash(string hash)
.ToList();
}

private const string IgnoreVariationsWithAnimeQuery =
@"SELECT ani.EpisodeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE ani.AnimeID == :animeID AND vl.IsVariation = 0 AND vl.Hash != '' GROUP BY ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1";
private const string CountVariationsAithAnimeQuery =
@"SELECT ani.EpisodeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE ani.AnimeID == :animeID AND vl.Hash != '' GROUP BY ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1";
private const string IgnoreVariationsQuery =
@"SELECT ani.EpisodeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE vl.IsVariation = 0 AND vl.Hash != '' GROUP BY ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1";
private const string CountVariationsQuery =
@"SELECT ani.EpisodeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE vl.Hash != '' GROUP BY ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1";

public List<SVR_AnimeEpisode> GetEpisodesWithMultipleFiles(bool ignoreVariations)
public List<SVR_AnimeEpisode> GetWithSoftDuplicates(bool ignoreVariations, int? animeID = null)
{
var ids = Lock(() =>
{
var query = ignoreVariations ? IgnoreVariationsQuery : CountVariationsQuery;
using var session = DatabaseFactory.SessionFactory.OpenSession();
return session.CreateSQLQuery(query).List<object>().Select(Convert.ToInt32);
if (animeID.HasValue && animeID.Value > 0)
{
var animeQuery = ignoreVariations ? IgnoreVariationsWithAnimeQuery : CountVariationsAithAnimeQuery;
return session.CreateSQLQuery(animeQuery)
.AddScalar("EpisodeID", NHibernateUtil.Int32)
.SetParameter("animeID", animeID.Value)
.List<int>();
}

var query = ignoreVariations ? IgnoreVariationsQuery : CountVariationsQuery;
return session.CreateSQLQuery(query)
.AddScalar("EpisodeID", NHibernateUtil.Int32)
.List<int>();
});

return ids.Select(GetByAniDBEpisodeID).Where(a => a != null).ToList();
return ids
.Select(GetByAniDBEpisodeID)
.Where(a => a != null)
.ToList();
}

public List<SVR_AnimeEpisode> GetUnwatchedEpisodes(int seriesid, int userid)
Expand Down
25 changes: 25 additions & 0 deletions Shoko.Server/Repositories/Cached/AnimeSeriesRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NHibernate;
using NLog;
using NutzCode.InMemoryIndex;
using Shoko.Commons.Properties;
Expand Down Expand Up @@ -324,4 +325,28 @@ public List<SVR_AnimeSeries> GetMostRecentlyAdded(int maxResults, int userID)
: Cache.Values.Where(a => user.AllowedSeries(a)).OrderByDescending(a => a.DateTimeCreated).Take(maxResults)
.ToList());
}

private const string IgnoreVariationsQuery =
@"SELECT ani.AnimeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE vl.IsVariation = 0 AND vl.Hash != '' GROUP BY ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1";
private const string CountVariationsQuery =
@"SELECT ani.AnimeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE vl.Hash != '' GROUP BY ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1";

public List<SVR_AnimeSeries> GetWithSoftDuplicates(bool ignoreVariations)
{
var ids = Lock(() =>
{
using var session = DatabaseFactory.SessionFactory.OpenSession();

var query = ignoreVariations ? IgnoreVariationsQuery : CountVariationsQuery;
return session.CreateSQLQuery(query)
.AddScalar("AnimeID", NHibernateUtil.Int32)
.List<int>();
});

return ids
.Distinct()
.Select(GetByAnimeID)
.Where(a => a != null)
.ToList();
}
}

0 comments on commit 2094167

Please sign in to comment.