Merge branch 'master' into master

This commit is contained in:
DarkFighterLuke
2025-12-02 14:37:51 +01:00
committed by GitHub
8 changed files with 221 additions and 17 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>1.4.2.0</Version> <Version>1.4.4.0</Version>
<AssemblyVersion>1.4.2.0</AssemblyVersion> <AssemblyVersion>1.4.4.0</AssemblyVersion>
<FileVersion>1.4.2.0</FileVersion> <FileVersion>1.4.4.0</FileVersion>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
@@ -170,7 +170,15 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
} }
processedVideosCount += currentPlaylistVideos; processedVideosCount += currentPlaylistVideos;
progress.Report(processedVideosCount * 100 / totalVideosCount);
if (totalVideosCount == 0)
{
progress.Report(100);
}
else
{
progress.Report(processedVideosCount * 100 / totalVideosCount);
}
} }
} }
} }
@@ -0,0 +1,99 @@
using System.Collections.ObjectModel;
using Newtonsoft.Json;
namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
{
/// <summary>
/// A class representing pagination information from the TubeArchivist API.
/// </summary>
public class PaginationInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="PaginationInfo"/> class.
/// </summary>
/// <param name="pageSize">The size of the page.</param>
/// <param name="pageFrom">The starting page number.</param>
/// <param name="prevPages">The collection of previous page numbers.</param>
/// <param name="currentPage">The current page number.</param>
/// <param name="maxHits">A value indicating whether the max hits have been reached.</param>
/// <param name="parameters">The parameters used for pagination.</param>
/// <param name="lastPage">The last page number.</param>
/// <param name="nextPages">The collection of next page numbers.</param>
/// <param name="totalHits">The total number of hits.</param>
public PaginationInfo(
int pageSize,
int pageFrom,
Collection<int> prevPages,
int currentPage,
bool maxHits,
string parameters,
int lastPage,
Collection<int> nextPages,
int totalHits)
{
this.PageSize = pageSize;
this.PageFrom = pageFrom;
this.PrevPages = prevPages;
this.CurrentPage = currentPage;
this.MaxHits = maxHits;
this.Parameters = parameters;
this.LastPage = lastPage;
this.NextPages = nextPages;
this.TotalHits = totalHits;
}
/// <summary>
/// Gets the page size.
/// </summary>
[JsonProperty(PropertyName = "page_size")]
public int PageSize { get; }
/// <summary>
/// Gets the page from.
/// </summary>
[JsonProperty(PropertyName = "page_from")]
public int PageFrom { get; }
/// <summary>
/// Gets the previous pages.
/// </summary>
[JsonProperty(PropertyName = "prev_pages")]
public Collection<int> PrevPages { get; }
/// <summary>
/// Gets the current page.
/// </summary>
[JsonProperty(PropertyName = "current_page")]
public int CurrentPage { get; }
/// <summary>
/// Gets a value indicating whether the max hits have been reached.
/// </summary>
[JsonProperty(PropertyName = "max_hits")]
public bool MaxHits { get; }
/// <summary>
/// Gets the parameters.
/// </summary>
[JsonProperty(PropertyName = "params")]
public string Parameters { get; }
/// <summary>
/// Gets the last page.
/// </summary>
[JsonProperty(PropertyName = "last_page")]
public int LastPage { get; }
/// <summary>
/// Gets the next pages.
/// </summary>
[JsonProperty(PropertyName = "next_pages")]
public Collection<int> NextPages { get; }
/// <summary>
/// Gets the total hits.
/// </summary>
[JsonProperty(PropertyName = "total_hits")]
public int TotalHits { get; }
}
}
@@ -112,5 +112,16 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
/// <value>One of the <see cref="PlaylistType"/> values indicating the playlist's type.</value> /// <value>One of the <see cref="PlaylistType"/> values indicating the playlist's type.</value>
[JsonProperty(PropertyName = "playlist_type")] [JsonProperty(PropertyName = "playlist_type")]
public PlaylistType Type { get; set; } public PlaylistType Type { get; set; }
/// <summary>
/// Returns a string of the playlist's key fields for logging.
/// </summary>
/// <returns>A string containing the playlist id, name, and entries count.</returns>
public override string ToString()
{
return "PlaylistId: " + this.Id +
", Name: " + this.Name +
", EntriesCount: " + this.Entries.Count;
}
} }
} }
@@ -10,5 +10,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
/// Gets or sets the contained data object. /// Gets or sets the contained data object.
/// </summary> /// </summary>
public T? Data { get; set; } public T? Data { get; set; }
/// <summary>
/// Gets or sets the pagination info object.
/// </summary>
public PaginationInfo? Paginate { get; set; }
} }
} }
@@ -219,7 +219,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
{ {
url = response.Headers.Location; url = response.Headers.Location;
_logger.LogInformation("{Message}", "Received redirect to: " + url); _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); _logger.LogInformation("{Message}", url + ": " + response.StatusCode);
@@ -228,6 +228,50 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
{ {
string rawData = await response.Content.ReadAsStringAsync().ConfigureAwait(true); string rawData = await response.Content.ReadAsStringAsync().ConfigureAwait(true);
playlists = JsonConvert.DeserializeObject<ResponseContainer<ISet<Playlist>?>>(rawData); playlists = JsonConvert.DeserializeObject<ResponseContainer<ISet<Playlist>?>>(rawData);
if (playlists?.Paginate != null)
{
var lastPage = playlists.Paginate.LastPage;
_logger.LogInformation("Pagination info: Current page {CurrentPage} / Last page {LastPage}, Total hits: {TotalHits}", playlists.Paginate.CurrentPage, playlists.Paginate.LastPage, playlists.Paginate.TotalHits);
while (playlists.Paginate.CurrentPage < lastPage)
{
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)
{
rawData = await response.Content.ReadAsStringAsync().ConfigureAwait(true);
var nextPagePlaylists = JsonConvert.DeserializeObject<ResponseContainer<ISet<Playlist>?>>(rawData);
if (nextPagePlaylists?.Data != null)
{
foreach (var playlist in nextPagePlaylists.Data)
{
playlists.Data?.Add(playlist);
}
}
if (nextPagePlaylists?.Paginate != null)
{
playlists.Paginate = nextPagePlaylists.Paginate;
_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;
}
}
}
} }
return playlists?.Data; return playlists?.Data;
@@ -1,8 +1,6 @@
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities
{ {
@@ -22,20 +20,59 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities
/// <returns>The URL string without spaces, doubled slashes and with a trailing slash.</returns> /// <returns>The URL string without spaces, doubled slashes and with a trailing slash.</returns>
public static string SanitizeUrl(string inputUrl) public static string SanitizeUrl(string inputUrl)
{ {
// Extract the schema part if (string.IsNullOrWhiteSpace(inputUrl))
Match schemaMatch = Regex.Match(inputUrl, @"^(?<schema>https?://)"); {
return string.Empty;
}
// Remove double slashes and spaces from the remaining part of the URL // Extract the schema part (http:// or https://)
string cleanedPath = Regex.Replace(inputUrl.Substring(schemaMatch.Length), @"[/\s]+", "/"); Match schemaMatch = Regex.Match(inputUrl, @"^(?<schema>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 // Remove slashes at the start
cleanedPath = cleanedPath.TrimStart('/'); cleanedPath = cleanedPath.TrimStart('/');
// Add a trailing slash if not already present // Add a trailing slash only when there are no query or fragment parts
cleanedPath = cleanedPath.TrimEnd('/') + "/"; if (string.IsNullOrEmpty(queryAndFragment))
{
cleanedPath = cleanedPath.TrimEnd('/') + "/";
}
// Combine the schema and cleaned path // Combine the schema and cleaned path and re-append query/fragment
string cleanedUrl = schemaMatch.Groups["schema"].Value + cleanedPath; string cleanedUrl = (schemaMatch.Success ? schemaMatch.Groups["schema"].Value : string.Empty) + cleanedPath + queryAndFragment;
return cleanedUrl; return cleanedUrl;
} }
+2 -2
View File
@@ -2,7 +2,7 @@
name: "TubeArchivistMetadata" name: "TubeArchivistMetadata"
guid: "dc97d0c6-28b0-4242-afb4-5833ae1b3715" guid: "dc97d0c6-28b0-4242-afb4-5833ae1b3715"
imageUrl: https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png imageUrl: https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png
version: "1.4.2.0" version: "1.4.4.0"
targetAbi: "10.11.0.0" targetAbi: "10.11.0.0"
framework: "net9.0" framework: "net9.0"
overview: "Metadata for your TubeArchivist library on Jellyfin" overview: "Metadata for your TubeArchivist library on Jellyfin"
@@ -14,4 +14,4 @@ owner: "DarkFighterLuke"
artifacts: artifacts:
- "Jellyfin.Plugin.TubeArchivistMetadata.dll" - "Jellyfin.Plugin.TubeArchivistMetadata.dll"
changelog: > changelog: >
Introduce episode numbering alternative schema Handle TubeArchivist playlists pagination