From a400f518a989f39a7ce20c0434862b36d7bfd8a1 Mon Sep 17 00:00:00 2001 From: da3dsoul Date: Fri, 1 Dec 2023 23:39:55 -0500 Subject: [PATCH] Cleanup and minor bug fix in APIv3 File Controller --- .../ShokoServiceImplementation_Entities.cs | 0 .../API/v3/Controllers/FileController.cs | 127 ++++-------------- .../v3/Controllers/ObsoleteFileController.cs | 102 ++++++++++++++ 3 files changed, 125 insertions(+), 104 deletions(-) mode change 100755 => 100644 Shoko.Server/API/v1/Implementations/ShokoServiceImplementation/ShokoServiceImplementation_Entities.cs create mode 100644 Shoko.Server/API/v3/Controllers/ObsoleteFileController.cs diff --git a/Shoko.Server/API/v1/Implementations/ShokoServiceImplementation/ShokoServiceImplementation_Entities.cs b/Shoko.Server/API/v1/Implementations/ShokoServiceImplementation/ShokoServiceImplementation_Entities.cs old mode 100755 new mode 100644 diff --git a/Shoko.Server/API/v3/Controllers/FileController.cs b/Shoko.Server/API/v3/Controllers/FileController.cs index 45fcee8ae..fab957b56 100644 --- a/Shoko.Server/API/v3/Controllers/FileController.cs +++ b/Shoko.Server/API/v3/Controllers/FileController.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; -using Microsoft.ApplicationInsights.AspNetCore.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -305,7 +304,7 @@ public ActionResult DeleteFiles([FromBody] File.Input.BatchDeleteBody body = nul ModelState.AddModelError("fileIds", $"Unable to find a file with id {fileId}"); return file; }) - .OfType() + .Where(a => a != null) .ToList(); if (!ModelState.IsValid) @@ -481,6 +480,7 @@ public ActionResult RescanFileByAniDBFileID([FromRoute] int anidbFileID, [FromQu /// This is not a good way to scrobble, but it allows for players without plugin support to have an option to scrobble. /// The readahead buffer on the player would determine the required percentage to scrobble. /// A file stream for the specified file. + [AllowAnonymous] [HttpGet("{fileID}/Stream")] [HttpGet("{fileID}/StreamDirectory/{filename}")] public ActionResult GetFileStream([FromRoute] int fileID, [FromRoute] string filename = null, [FromQuery] bool streamPositionScrobbling = false) @@ -519,6 +519,7 @@ public ActionResult GetFileStream([FromRoute] int fileID, [FromRoute] string fil /// /// Shoko ID /// A file stream for the specified file. + [AllowAnonymous] [HttpGet("{fileID}/StreamDirectory/")] public ActionResult GetFileStreamDirectory([FromRoute] int fileID) { @@ -537,6 +538,7 @@ public ActionResult GetFileStreamDirectory([FromRoute] int fileID) /// /// /// + [AllowAnonymous] [HttpGet("{fileID}/StreamDirectory/ExternalSub/{filename}")] public ActionResult GetExternalSubtitle([FromRoute] int fileID, [FromRoute] string filename) { @@ -571,7 +573,7 @@ public ActionResult GetFileMediaInfo([FromRoute] int fileID) if (file == null) return NotFound(FileNotFoundWithFileID); - var mediaContainer = file?.Media; + var mediaContainer = file.Media; if (mediaContainer == null) return InternalError("Unable to find media container for File"); @@ -710,8 +712,8 @@ private void ScrobbleToTrakt(SVR_VideoLocal file, SVR_AnimeEpisode episode, long if (User.IsTraktUser == 0) return; - float percentage = 100 * (position / file.Duration); - ScrobblePlayingType scrobbleType = episode.GetAnimeSeries()?.GetAnime()?.AnimeType == (int)AnimeType.Movie + var percentage = 100 * ((float)position / file.Duration); + var scrobbleType = episode.GetAnimeSeries()?.GetAnime()?.AnimeType == (int)AnimeType.Movie ? ScrobblePlayingType.movie : ScrobblePlayingType.episode; @@ -723,7 +725,7 @@ private ActionResult ScrobbleStatusOnFile(SVR_VideoLocal file, bool? watched, lo { if (!(watched ?? false) && resumePosition != null) { - var safeRP = resumePosition ?? 0; + var safeRP = resumePosition.Value; if (safeRP < 0) safeRP = 0; if (safeRP >= file.Duration) @@ -734,7 +736,7 @@ private ActionResult ScrobbleStatusOnFile(SVR_VideoLocal file, bool? watched, lo if (watched != null) { - var safeWatched = watched ?? false; + var safeWatched = watched.Value; file.ToggleWatchedStatus(safeWatched, User.JMMUserID); if (safeWatched) file.SetResumePosition(0, User.JMMUserID); @@ -938,11 +940,11 @@ public ActionResult LinkMultipleEpisodesToFile([FromRoute] int fileID, [FromBody if (series == null) ModelState.AddModelError(nameof(body.SeriesID), $"Unable to find series with id {body.SeriesID}."); - var (rangeStart, startType, startErrorMessage) = Helpers.ModelHelper.GetEpisodeNumberAndTypeFromInput(body.RangeStart); + var (rangeStart, startType, startErrorMessage) = ModelHelper.GetEpisodeNumberAndTypeFromInput(body.RangeStart); if (!string.IsNullOrEmpty(startErrorMessage)) ModelState.AddModelError(nameof(body.RangeStart), string.Format(startErrorMessage, nameof(body.RangeStart))); - var (rangeEnd, endType, endErrorMessage) = Helpers.ModelHelper.GetEpisodeNumberAndTypeFromInput(body.RangeEnd); + var (rangeEnd, endType, endErrorMessage) = ModelHelper.GetEpisodeNumberAndTypeFromInput(body.RangeEnd); if (!string.IsNullOrEmpty(endErrorMessage)) ModelState.AddModelError(nameof(body.RangeEnd), string.Format(endErrorMessage, nameof(body.RangeEnd))); @@ -954,7 +956,7 @@ public ActionResult LinkMultipleEpisodesToFile([FromRoute] int fileID, [FromBody // Validate that the ranges are valid for the series. var episodeType = startType ?? EpisodeType.Episode; - var totalEpisodes = Helpers.ModelHelper.GetTotalEpisodesForType(series.GetAnimeEpisodes(), episodeType); + var totalEpisodes = ModelHelper.GetTotalEpisodesForType(series!.GetAnimeEpisodes(), episodeType); if (rangeStart < 1) ModelState.AddModelError(nameof(body.RangeStart), "`RangeStart` cannot be lower then 1."); @@ -972,7 +974,7 @@ public ActionResult LinkMultipleEpisodesToFile([FromRoute] int fileID, [FromBody // Validate the episodes. var episodeList = new List(); - for (int episodeNumber = rangeStart; episodeNumber <= rangeEnd; episodeNumber++) + for (var episodeNumber = rangeStart; episodeNumber <= rangeEnd; episodeNumber++) { var anidbEpisode = RepoFactory.AniDB_Episode.GetByAnimeIDAndEpisodeTypeNumber(series.AniDB_ID, episodeType, episodeNumber)[0]; if (anidbEpisode == null) @@ -1032,7 +1034,7 @@ public ActionResult UnlinkMultipleEpisodesFromFile([FromRoute] int fileID, [From .Select(episode => (Episode: episode, XRef: RepoFactory.CrossRef_File_Episode.GetByHashAndEpisodeID(file.Hash, episode.AniDB_EpisodeID))) .Where(obj => obj.XRef != null) .ToList(); - foreach (var (episode, xref) in episodeList) + foreach (var (_, xref) in episodeList) if (xref.CrossRefSource == (int)CrossRefSource.AniDB) ModelState.AddModelError("CrossReferences", $"Unable to remove AniDB cross-reference to anidb episode with id {xref.EpisodeID} for file with id {file.VideoLocalID}."); @@ -1058,8 +1060,7 @@ public ActionResult UnlinkMultipleEpisodesFromFile([FromRoute] int fileID, [From foreach (var seriesID in seriesIDs) { var series = RepoFactory.AnimeSeries.GetByID(seriesID); - if (series != null) - series.QueueUpdateStats(); + series?.QueueUpdateStats(); } return Ok(); @@ -1094,7 +1095,7 @@ public ActionResult LinkMultipleFiles([FromBody] File.Input.LinkSeriesMultipleBo if (series == null) ModelState.AddModelError(nameof(body.SeriesID), $"Unable to find series with id {body.SeriesID}."); - var (rangeStart, startType, startErrorMessage) = Helpers.ModelHelper.GetEpisodeNumberAndTypeFromInput(body.RangeStart); + var (rangeStart, startType, startErrorMessage) = ModelHelper.GetEpisodeNumberAndTypeFromInput(body.RangeStart); if (!string.IsNullOrEmpty(startErrorMessage)) ModelState.AddModelError(nameof(body.RangeStart), string.Format(startErrorMessage, nameof(body.RangeStart))); @@ -1104,7 +1105,7 @@ public ActionResult LinkMultipleFiles([FromBody] File.Input.LinkSeriesMultipleBo // Validate the range. var episodeType = startType ?? EpisodeType.Episode; var rangeEnd = rangeStart + files.Count - 1; - var totalEpisodes = Helpers.ModelHelper.GetTotalEpisodesForType(series.GetAnimeEpisodes(), episodeType); + var totalEpisodes = ModelHelper.GetTotalEpisodesForType(series!.GetAnimeEpisodes(), episodeType); if (rangeStart < 1) ModelState.AddModelError(nameof(body.RangeStart), "`RangeStart` cannot be lower then 1."); @@ -1160,7 +1161,7 @@ public ActionResult LinkMultipleFiles([FromBody] File.Input.LinkSeriesMultipleBo } ); if (singleEpisode) - command.Percentage = (int)Math.Round((double)(fileCount / files.Count * 100)); + command.Percentage = (int)Math.Round((double)fileCount / files.Count * 100); else episodeNumber++; @@ -1203,7 +1204,7 @@ public ActionResult LinkMultipleFiles([FromBody] File.Input.LinkMultipleFilesBod if (!ModelState.IsValid) return ValidationProblem(ModelState); - var anidbEpisode = episode.AniDB_Episode; + var anidbEpisode = episode!.AniDB_Episode; if (anidbEpisode == null) return InternalError("Could not find the AniDB entry for episode"); @@ -1298,7 +1299,8 @@ public ActionResult> PathEndsWithPath([FromRoute] string path, [FromQ /// Include data from selected s. /// Limit the number of returned results. /// A list of all files with a file location that ends with the given path. - internal ActionResult> PathEndsWithInternal(string path, bool includeXRefs, + [NonAction] + private ActionResult> PathEndsWithInternal(string path, bool includeXRefs, HashSet includeDataFrom, int limit = 0) { if (string.IsNullOrWhiteSpace(path)) @@ -1324,12 +1326,12 @@ internal ActionResult> PathEndsWithInternal(string path, bool include if (limit <= 0) return results - .Select(a => new File(HttpContext, a, true, includeDataFrom)) + .Select(a => new File(HttpContext, a, includeXRefs, includeDataFrom)) .ToList(); return results .Take(limit) - .Select(a => new File(HttpContext, a, true, includeDataFrom)) + .Select(a => new File(HttpContext, a, includeXRefs, includeDataFrom)) .ToList(); } @@ -1401,74 +1403,6 @@ public ActionResult> RegexSearchByFileName([FromRoute] string path) return results; } - /// - /// Get recently added files. - /// - /// - [HttpGet("Recent/{limit:int?}")] - [Obsolete("Use the universal file endpoint instead.")] - public ActionResult> GetRecentFilesObselete([FromRoute] [Range(0, 1000)] int limit = 100) - => GetRecentFiles(limit); - - /// - /// Get recently added files. - /// - /// Limits the number of results per page. Set to 0 to disable the limit. - /// Page number. - /// Set to false to exclude series and episode cross-references. - /// - [Obsolete("Use the universal file endpoint instead.")] - [HttpGet("Recent")] - public ActionResult> GetRecentFiles([FromQuery] [Range(0, 1000)] int pageSize = 100, [FromQuery] [Range(1, int.MaxValue)] int page = 1, [FromQuery] bool includeXRefs = true) - { - return RepoFactory.VideoLocal.GetMostRecentlyAdded(-1, 0, User.JMMUserID) - .ToListResult(file => new File(HttpContext, file, includeXRefs), page, pageSize); - } - - /// - /// Get ignored files. - /// - /// Limits the number of results per page. Set to 0 to disable the limit. - /// Page number. - /// - [Obsolete("Use the universal file endpoint instead.")] - [HttpGet("Ignored")] - public ActionResult> GetIgnoredFiles([FromQuery] [Range(0, 1000)] int pageSize = 100, [FromQuery] [Range(1, int.MaxValue)] int page = 1) - { - return RepoFactory.VideoLocal.GetIgnoredVideos() - .ToListResult(file => new File(HttpContext, file), page, pageSize); - } - - /// - /// Get files with more than one location. - /// - /// Limits the number of results per page. Set to 0 to disable the limit. - /// Page number. - /// Set to true to include series and episode cross-references. - /// - [Obsolete("Use the universal file endpoint instead.")] - [HttpGet("Duplicates")] - public ActionResult> GetExactDuplicateFiles([FromQuery] [Range(0, 1000)] int pageSize = 100, [FromQuery] [Range(1, int.MaxValue)] int page = 1, [FromQuery] bool includeXRefs = false) - { - return RepoFactory.VideoLocal.GetExactDuplicateVideos() - .ToListResult(file => new File(HttpContext, file, includeXRefs), page, pageSize); - } - - /// - /// Get files with no cross-reference. - /// - /// Limits the number of results per page. Set to 0 to disable the limit. - /// Page number. - /// Set to false to exclude series and episode cross-references. - /// - [Obsolete("Use the universal file endpoint instead.")] - [HttpGet("Linked")] - public ActionResult> GetManuallyLinkedFiles([FromQuery] [Range(0, 1000)] int pageSize = 100, [FromQuery] [Range(1, int.MaxValue)] int page = 1, [FromQuery] bool includeXRefs = true) - { - return RepoFactory.VideoLocal.GetManuallyLinkedVideos() - .ToListResult(file => new File(HttpContext, file, includeXRefs), page, pageSize); - } - /// /// Get all files with missing cross-references data. /// @@ -1492,19 +1426,4 @@ public ActionResult> GetFilesWithMissingCrossReferenceData([Fro pageSize ); } - - /// - /// Get unrecognized files. - /// Use pageSize and page (index 0) in the query to enable pagination. - /// - /// Limits the number of results per page. Set to 0 to disable the limit. - /// Page number. - /// - [Obsolete("Use the universal file endpoint instead.")] - [HttpGet("Unrecognized")] - public ActionResult> GetUnrecognizedFiles([FromQuery] [Range(0, 1000)] int pageSize = 100, [FromQuery] [Range(1, int.MaxValue)] int page = 1) - { - return RepoFactory.VideoLocal.GetVideosWithoutEpisode() - .ToListResult(file => new File(HttpContext, file), page, pageSize); - } } diff --git a/Shoko.Server/API/v3/Controllers/ObsoleteFileController.cs b/Shoko.Server/API/v3/Controllers/ObsoleteFileController.cs new file mode 100644 index 000000000..44e36fa00 --- /dev/null +++ b/Shoko.Server/API/v3/Controllers/ObsoleteFileController.cs @@ -0,0 +1,102 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Shoko.Server.API.Annotations; +using Shoko.Server.API.v3.Helpers; +using Shoko.Server.API.v3.Models.Common; +using Shoko.Server.API.v3.Models.Shoko; +using Shoko.Server.Repositories; +using Shoko.Server.Settings; + +namespace Shoko.Server.API.v3.Controllers; + +[ApiController, Route("/api/v{version:apiVersion}/File"), ApiV3] +[Authorize] +public class ObsoleteFileController : BaseController +{ + public ObsoleteFileController(ISettingsProvider settingsProvider) : base(settingsProvider) { } + + /// + /// Get recently added files. + /// + /// + [HttpGet("Recent/{limit:int?}")] + [Obsolete("Use the universal file endpoint instead.")] + public ActionResult> GetRecentFilesObselete([FromRoute] [Range(0, 1000)] int limit = 100) + => GetRecentFiles(limit); + + /// + /// Get recently added files. + /// + /// Limits the number of results per page. Set to 0 to disable the limit. + /// Page number. + /// Set to false to exclude series and episode cross-references. + /// + [Obsolete("Use the universal file endpoint instead.")] + [HttpGet("Recent")] + public ActionResult> GetRecentFiles([FromQuery] [Range(0, 1000)] int pageSize = 100, [FromQuery] [Range(1, int.MaxValue)] int page = 1, [FromQuery] bool includeXRefs = true) + { + return RepoFactory.VideoLocal.GetMostRecentlyAdded(-1, 0, User.JMMUserID) + .ToListResult(file => new File(HttpContext, file, includeXRefs), page, pageSize); + } + + /// + /// Get ignored files. + /// + /// Limits the number of results per page. Set to 0 to disable the limit. + /// Page number. + /// + [Obsolete("Use the universal file endpoint instead.")] + [HttpGet("Ignored")] + public ActionResult> GetIgnoredFiles([FromQuery] [Range(0, 1000)] int pageSize = 100, [FromQuery] [Range(1, int.MaxValue)] int page = 1) + { + return RepoFactory.VideoLocal.GetIgnoredVideos() + .ToListResult(file => new File(HttpContext, file), page, pageSize); + } + + /// + /// Get files with more than one location. + /// + /// Limits the number of results per page. Set to 0 to disable the limit. + /// Page number. + /// Set to true to include series and episode cross-references. + /// + [Obsolete("Use the universal file endpoint instead.")] + [HttpGet("Duplicates")] + public ActionResult> GetExactDuplicateFiles([FromQuery] [Range(0, 1000)] int pageSize = 100, [FromQuery] [Range(1, int.MaxValue)] int page = 1, [FromQuery] bool includeXRefs = false) + { + return RepoFactory.VideoLocal.GetExactDuplicateVideos() + .ToListResult(file => new File(HttpContext, file, includeXRefs), page, pageSize); + } + + /// + /// Get files with no cross-reference. + /// + /// Limits the number of results per page. Set to 0 to disable the limit. + /// Page number. + /// Set to false to exclude series and episode cross-references. + /// + [Obsolete("Use the universal file endpoint instead.")] + [HttpGet("Linked")] + public ActionResult> GetManuallyLinkedFiles([FromQuery] [Range(0, 1000)] int pageSize = 100, [FromQuery] [Range(1, int.MaxValue)] int page = 1, [FromQuery] bool includeXRefs = true) + { + return RepoFactory.VideoLocal.GetManuallyLinkedVideos() + .ToListResult(file => new File(HttpContext, file, includeXRefs), page, pageSize); + } + + /// + /// Get unrecognized files. + /// Use pageSize and page (index 0) in the query to enable pagination. + /// + /// Limits the number of results per page. Set to 0 to disable the limit. + /// Page number. + /// + [Obsolete("Use the universal file endpoint instead.")] + [HttpGet("Unrecognized")] + public ActionResult> GetUnrecognizedFiles([FromQuery] [Range(0, 1000)] int pageSize = 100, [FromQuery] [Range(1, int.MaxValue)] int page = 1) + { + return RepoFactory.VideoLocal.GetVideosWithoutEpisode() + .ToListResult(file => new File(HttpContext, file), page, pageSize); + } +}