Skip to content

Commit

Permalink
Change the syntax on File endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
da3dsoul committed Dec 16, 2023
1 parent 4bdde70 commit dfc9093
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 101 deletions.
51 changes: 37 additions & 14 deletions Shoko.Server/API/ModelBinders/CommaDelimitedModelBinder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
Expand All @@ -11,6 +13,7 @@ namespace Shoko.Server.API.ModelBinders;
public class CommaDelimitedModelBinder : IModelBinder
{
private readonly ILogger<CommaDelimitedModelBinder> _logger;
private static readonly Dictionary<Type, MethodInfo> AddCache = new();

public CommaDelimitedModelBinder(ILogger<CommaDelimitedModelBinder> logger)
{
Expand All @@ -24,26 +27,46 @@ public Task BindModelAsync(ModelBindingContext bindingContext)
var elementType = bindingContext.ModelType.GetElementType() ?? bindingContext.ModelType.GenericTypeArguments[0];
var converter = TypeDescriptor.GetConverter(elementType);

// HashSet<T> makes things really hard, as it needs compile time types
var result = Activator.CreateInstance(bindingContext.ModelType);
var addMethod = result?.GetType().GetMethod("Add");
if (addMethod == null)
var items = valueProviderResult
.SelectMany(value => value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)).ToArray();
object? result;

if (bindingContext.ModelType.IsArray)
{
_logger.LogDebug("Could not get Add method for {Type}", bindingContext.ModelType.FullName);
return Task.CompletedTask;
var array = Array.CreateInstance(elementType, items.Length);
Array.Copy(items.Select(a => converter.ConvertFromString(a)).ToArray(), array, items.Length);
result = array;
}

foreach (var item in valueProviderResult
.SelectMany(value => value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)))
else
{
try
// HashSet<T> makes things really hard, as it needs compile time types
result = Activator.CreateInstance(bindingContext.ModelType);
if (!AddCache.TryGetValue(bindingContext.ModelType, out var addMethod))
{
addMethod = bindingContext.ModelType.GetMethod("Add");
if (addMethod != null) AddCache[bindingContext.ModelType] = addMethod;
}

if (addMethod == null)
{
var value = converter.ConvertFromString(item);
addMethod.Invoke(result, new[] { value });
_logger.LogDebug("Could not get Add method for {Type}", bindingContext.ModelType.FullName);
return Task.CompletedTask;
}
catch (Exception e)

foreach (var item in items)
{
_logger.LogDebug(e, "Error converting value to {Name}", elementType.FullName);
try
{
var value = converter.ConvertFromString(item);
addMethod.Invoke(result, new[]
{
value
});
}
catch (Exception e)
{
_logger.LogDebug(e, "Error converting value to {Name}", elementType.FullName);
}
}
}

Expand Down
111 changes: 24 additions & 87 deletions Shoko.Server/API/v3/Controllers/FileController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,8 @@ public FileController(TraktTVHelper traktHelper, ICommandRequestFactory commandF
/// </summary>
/// <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="includeMissing">Include missing files among the results.</param>
/// <param name="includeIgnored">Include ignored files among the results.</param>
/// <param name="includeVariations">Include files marked as a variation among the results.</param>
/// <param name="includeDuplicates">Include files with multiple locations (and thus have duplicates) among the results.</param>
/// <param name="includeUnrecognized">Include unrecognized files among the results.</param>
/// <param name="includeLinked">Include manually linked files among the results.</param>
/// <param name="includeViewed">Include previously viewed files among the results.</param>
/// <param name="includeWatched">Include previously watched files among the results</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>
/// <param name="includeMediaInfo">Include media info data.</param>
/// <param name="includeAbsolutePaths">Include absolute paths for the file locations.</param>
/// <param name="includeXRefs">Include series and episode cross-references.</param>
/// <param name="seriesID">Filter the search to only files for a given shoko series.</param>
/// <param name="episodeID">Filter the search to only files for a given shoko episode.</param>
/// <param name="anidbSeriesID">Filter the search to only files for a given anidb series.</param>
Expand All @@ -85,26 +74,22 @@ public FileController(TraktTVHelper traktHelper, ICommandRequestFactory commandF
public ActionResult<ListResult<File>> GetFiles(
[FromQuery, Range(0, 1000)] int pageSize = 100,
[FromQuery, Range(1, int.MaxValue)] int page = 1,
[FromQuery] IncludeOnlyFilter includeMissing = IncludeOnlyFilter.True,
[FromQuery] IncludeOnlyFilter includeIgnored = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeVariations = IncludeOnlyFilter.True,
[FromQuery] IncludeOnlyFilter includeDuplicates = IncludeOnlyFilter.True,
[FromQuery] IncludeOnlyFilter includeUnrecognized = IncludeOnlyFilter.True,
[FromQuery] IncludeOnlyFilter includeLinked = IncludeOnlyFilter.True,
[FromQuery] IncludeOnlyFilter includeViewed = IncludeOnlyFilter.True,
[FromQuery] IncludeOnlyFilter includeWatched = IncludeOnlyFilter.True,
[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,
[FromQuery] bool includeMediaInfo = false,
[FromQuery] bool includeAbsolutePaths = false,
[FromQuery] bool includeXRefs = false,
[FromQuery] int? seriesID = null,
[FromQuery] int? episodeID = null,
[FromQuery] int? anidbSeriesID = null,
[FromQuery] int? anidbEpisodeID = null,
[FromQuery] string search = null,
[FromQuery] bool fuzzy = true)
{
include ??= Array.Empty<FileNonDefaultIncludeType>();
exclude ??= Array.Empty<FileExcludeTypes>();
include_only ??= Array.Empty<FileIncludeOnlyType>();

// Map shoko series id to anidb series id and check if the series
// exists.
if (seriesID.HasValue)
Expand Down Expand Up @@ -151,16 +136,14 @@ public ActionResult<ListResult<File>> GetFiles(
}
// Filtering.
var user = User;
var includeLocations = includeDuplicates != IncludeOnlyFilter.True ||
!string.IsNullOrEmpty(search) ||
var includeLocations = exclude.Contains(FileExcludeTypes.Duplicates) || !string.IsNullOrEmpty(search) ||
(sortOrder?.Any(criteria => criteria.Contains(FileSortCriteria.DuplicateCount.ToString())) ?? false);
var includeUserRecord = includeViewed != IncludeOnlyFilter.True ||
includeWatched != IncludeOnlyFilter.True ||
(sortOrder?.Any(criteria => criteria.Contains(FileSortCriteria.ViewedAt.ToString()) || criteria.Contains(FileSortCriteria.WatchedAt.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(includeMissing != IncludeOnlyFilter.True),
BestLocation: video.GetBestVideoLocalPlace(),
Locations: includeLocations ? video.Places : null,
UserRecord: includeUserRecord ? video.GetUserRecord(user.JMMUserID) : null
))
Expand Down Expand Up @@ -194,67 +177,20 @@ public ActionResult<ListResult<File>> GetFiles(
return false;
}

if (includeMissing != IncludeOnlyFilter.True)
{
var shouldHideMissing = includeMissing == IncludeOnlyFilter.False;
var fileIsMissing = bestLocation == null;
if (shouldHideMissing == fileIsMissing)
return false;
}

if (includeIgnored != IncludeOnlyFilter.True)
{
var shouldHideIgnored = includeIgnored == IncludeOnlyFilter.False;
if (shouldHideIgnored == video.IsIgnored)
return false;
}

if (includeVariations != IncludeOnlyFilter.True)
{
var shouldHideVariation = includeVariations == IncludeOnlyFilter.False;
if (shouldHideVariation == video.IsVariation)
return false;
}

if (includeDuplicates != IncludeOnlyFilter.True)
{
var shouldHideDuplicate = includeDuplicates == IncludeOnlyFilter.False;
var hasDuplicates = locations.Count > 1;
if (shouldHideDuplicate == hasDuplicates)
return false;
}
if (!include.Contains(FileNonDefaultIncludeType.Ignored) && video.IsIgnored) return false;
if (include_only.Contains(FileIncludeOnlyType.Ignored) && !video.IsIgnored) return false;

if (includeUnrecognized != IncludeOnlyFilter.True)
{
var shouldHideUnrecognized = includeUnrecognized == IncludeOnlyFilter.False;
var fileIsUnrecognized = xrefs.Count == 0;
if (shouldHideUnrecognized == fileIsUnrecognized)
return false;
}
if (exclude.Contains(FileExcludeTypes.Duplicates) && locations.Count > 1) return false;
if (include_only.Contains(FileIncludeOnlyType.Duplicates) && locations.Count <= 1) return false;

if (includeLinked != IncludeOnlyFilter.True)
{
var shouldHideLinked = includeLinked == IncludeOnlyFilter.False;
var fileIsLinked = xrefs.Count > 0 && xrefs.Any(xref => xref.CrossRefSource != (int)CrossRefSource.AniDB);
if (shouldHideLinked == fileIsLinked)
return false;
}
if (exclude.Contains(FileExcludeTypes.Unrecognized) && xrefs.Count == 0) return false;
if (include_only.Contains(FileIncludeOnlyType.Unrecognized) && xrefs.Count > 0) return false;

if (includeViewed != IncludeOnlyFilter.True)
{
var shouldHideViewed = includeViewed == IncludeOnlyFilter.False;
var fileIsViewed = userRecord != null;
if (shouldHideViewed == fileIsViewed)
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 (includeWatched != IncludeOnlyFilter.True)
{
var shouldHideWatched = includeWatched == IncludeOnlyFilter.False;
var fileIsWatched = userRecord?.WatchedDate != null;
if (shouldHideWatched == fileIsWatched)
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;
});
Expand All @@ -278,8 +214,9 @@ public ActionResult<ListResult<File>> GetFiles(
});

// Skip and limit.
return enumerable
.ToListResult(tuple => new File(tuple.UserRecord, tuple.Video, includeXRefs, includeDataFrom, includeMediaInfo, includeAbsolutePaths), page, pageSize);
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);
}

/// <summary>
Expand Down
45 changes: 45 additions & 0 deletions Shoko.Server/API/v3/Models/Common/DataIncludeContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace Shoko.Server.API.v3.Models.Common;

public class DataIncludeContext
{
/// <summary>
/// Include ignored files among the results
/// </summary>
public IncludeOnlyFilter IncludeIgnored { get; set; } = IncludeOnlyFilter.False;
/// <summary>
/// Include files marked as a variation among the results
/// </summary>
public IncludeOnlyFilter IncludeVariations { get; set; } = IncludeOnlyFilter.True;
/// <summary>
/// Include files with multiple locations (and thus have duplicates) among the results
/// </summary>
public IncludeOnlyFilter IncludeDuplicates { get; set; } = IncludeOnlyFilter.True;
/// <summary>
/// Include unrecognized files among the results
/// </summary>
public IncludeOnlyFilter IncludeUnrecognized { get; set; } = IncludeOnlyFilter.True;
/// <summary>
/// Include manually linked files among the results
/// </summary>
public IncludeOnlyFilter IncludeManuallyLinked { get; set; } = IncludeOnlyFilter.True;
/// <summary>
/// Include previously viewed files among the results
/// </summary>
public IncludeOnlyFilter IncludeViewed { get; set; } = IncludeOnlyFilter.True;
/// <summary>
/// Include previously watched files among the results
/// </summary>
public IncludeOnlyFilter IncludeWatched { get; set; } = IncludeOnlyFilter.True;
/// <summary>
/// Include media info data
/// </summary>
public IncludeOnlyFilter IncludeMediaInfo { get; set; } = IncludeOnlyFilter.False;
/// <summary>
/// Include absolute paths for the file locations
/// </summary>
public IncludeOnlyFilter IncludeAbsolutePaths { get; set; } = IncludeOnlyFilter.False;
/// <summary>
/// Include series and episode cross-references
/// </summary>
public IncludeOnlyFilter IncludeXRefs { get; set; } = IncludeOnlyFilter.False;
}
34 changes: 34 additions & 0 deletions Shoko.Server/API/v3/Models/Common/FileIncludeTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Shoko.Server.API.v3.Models.Common;

[JsonConverter(typeof(StringEnumConverter))]
public enum FileExcludeTypes
{
Watched,
Variations,
Duplicates,
Unrecognized,
ManualLinks,
}

[JsonConverter(typeof(StringEnumConverter))]
public enum FileNonDefaultIncludeType
{
Ignored,
MediaInfo,
XRefs,
AbsolutePaths
}

[JsonConverter(typeof(StringEnumConverter))]
public enum FileIncludeOnlyType
{
Watched,
Variations,
Duplicates,
Unrecognized,
ManualLinks,
Ignored
}

0 comments on commit dfc9093

Please sign in to comment.