diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/TAToJellyfinPlaylistsSyncTask.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/TAToJellyfinPlaylistsSyncTask.cs index a2eccba..19c66ae 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/TAToJellyfinPlaylistsSyncTask.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/TAToJellyfinPlaylistsSyncTask.cs @@ -170,7 +170,15 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks } processedVideosCount += currentPlaylistVideos; - progress.Report(processedVideosCount * 100 / totalVideosCount); + + if (totalVideosCount == 0) + { + progress.Report(100); + } + else + { + progress.Report(processedVideosCount * 100 / totalVideosCount); + } } } } diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs index fe13273..123ca60 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs @@ -219,7 +219,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist { url = response.Headers.Location; _logger.LogInformation("{Message}", "Received redirect to: " + url); - response = await client.GetAsync(url).ConfigureAwait(true); + response = await client.GetAsync(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + url)).ConfigureAwait(true); } _logger.LogInformation("{Message}", url + ": " + response.StatusCode); @@ -238,6 +238,13 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist var nextPage = playlists.Paginate.CurrentPage + 1; var pagedUrl = new Uri(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + playlistsEndpoint + "?page=" + nextPage)); response = await client.GetAsync(pagedUrl).ConfigureAwait(true); + while (response.StatusCode == HttpStatusCode.Moved) + { + url = response.Headers.Location; + _logger.LogInformation("{Message}", "Received redirect to: " + url); + response = await client.GetAsync(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + url)).ConfigureAwait(true); + } + _logger.LogInformation("{Message}", pagedUrl + ": " + response.StatusCode); if (response.IsSuccessStatusCode) @@ -258,6 +265,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist _logger.LogInformation("Pagination info: Current page {CurrentPage} / Last page {LastPage}, Total hits: {TotalHits}", playlists.Paginate.CurrentPage, playlists.Paginate.LastPage, playlists.Paginate.TotalHits); } } + else + { + _logger.LogCritical("Failed to retrieve page {PageNumber} of playlists during pagination.", nextPage); + break; + } } } } diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Utils/Utils.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Utils/Utils.cs index be9f97b..f526db0 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Utils/Utils.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Utils/Utils.cs @@ -1,8 +1,6 @@ using System; -using System.IO; using System.Linq; using System.Text.RegularExpressions; -using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities { @@ -22,20 +20,59 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities /// The URL string without spaces, doubled slashes and with a trailing slash. public static string SanitizeUrl(string inputUrl) { - // Extract the schema part - Match schemaMatch = Regex.Match(inputUrl, @"^(?https?://)"); + if (string.IsNullOrWhiteSpace(inputUrl)) + { + return string.Empty; + } - // Remove double slashes and spaces from the remaining part of the URL - string cleanedPath = Regex.Replace(inputUrl.Substring(schemaMatch.Length), @"[/\s]+", "/"); + // Extract the schema part (http:// or https://) + Match schemaMatch = Regex.Match(inputUrl, @"^(?https?://)", RegexOptions.IgnoreCase); + + // If no schema found, treat whole string as the rest + int schemaLength = schemaMatch.Success ? schemaMatch.Length : 0; + + // Separate the main part from query (?) and fragment (#) + string rest = inputUrl.Substring(schemaLength); + string pathPart = rest; + string queryAndFragment = string.Empty; + + int qIndex = rest.IndexOf('?', StringComparison.Ordinal); + int fIndex = rest.IndexOf('#', StringComparison.Ordinal); + + int splitIndex = -1; + if (qIndex >= 0 && fIndex >= 0) + { + splitIndex = Math.Min(qIndex, fIndex); + } + else if (qIndex >= 0) + { + splitIndex = qIndex; + } + else if (fIndex >= 0) + { + splitIndex = fIndex; + } + + if (splitIndex >= 0) + { + pathPart = rest.Substring(0, splitIndex); + queryAndFragment = rest.Substring(splitIndex); + } + + // Remove double slashes and spaces from the path part + string cleanedPath = Regex.Replace(pathPart, @"[/\s]+", "/"); // Remove slashes at the start cleanedPath = cleanedPath.TrimStart('/'); - // Add a trailing slash if not already present - cleanedPath = cleanedPath.TrimEnd('/') + "/"; + // Add a trailing slash only when there are no query or fragment parts + if (string.IsNullOrEmpty(queryAndFragment)) + { + cleanedPath = cleanedPath.TrimEnd('/') + "/"; + } - // Combine the schema and cleaned path - string cleanedUrl = schemaMatch.Groups["schema"].Value + cleanedPath; + // Combine the schema and cleaned path and re-append query/fragment + string cleanedUrl = (schemaMatch.Success ? schemaMatch.Groups["schema"].Value : string.Empty) + cleanedPath + queryAndFragment; return cleanedUrl; }