Proxy index controls through Jellyfin plugin

This commit is contained in:
Sleeps
2026-10-06 17:56:57 +01:00
parent 5a6ebab77d
commit 7a858bd463
@@ -1,5 +1,7 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Jellyfin.Plugin.SceneSearch.Configuration;
using Microsoft.Extensions.Logging;
@@ -19,9 +21,6 @@ public sealed class SceneSearchClient : IDisposable
_httpClient = new HttpClient();
}
/// <summary>
/// Gets a value indicating whether enough configuration exists to search.
/// </summary>
public bool IsConfigured
{
get
@@ -35,9 +34,6 @@ public sealed class SceneSearchClient : IDisposable
private static PluginConfiguration Configuration
=> Plugin.Instance?.Configuration ?? new PluginConfiguration();
/// <summary>
/// Searches the local scene-search API.
/// </summary>
internal async Task<IReadOnlyList<SceneSearchItem>> SearchAsync(
string searchTerm,
int limit,
@@ -50,8 +46,7 @@ public sealed class SceneSearchClient : IDisposable
}
var safeLimit = Math.Clamp(limit, 1, Math.Clamp(config.MaxResults, 1, 50));
var requestUri = string.Concat(
config.ApiUrl.TrimEnd('/'),
var path = string.Concat(
"/api/search?q=",
Uri.EscapeDataString(searchTerm),
"&scene_limit=",
@@ -59,7 +54,58 @@ public sealed class SceneSearchClient : IDisposable
"&item_limit=",
safeLimit.ToString(System.Globalization.CultureInfo.InvariantCulture));
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
var payload = await SendAsync<SceneSearchResponse>(
HttpMethod.Get,
path,
content: null,
cancellationToken).ConfigureAwait(false);
return payload?.Items ?? [];
}
public Task<SceneSearchLibraryListResponse?> GetLibrariesAsync(
CancellationToken cancellationToken)
=> SendAsync<SceneSearchLibraryListResponse>(
HttpMethod.Get,
"/api/jellyfin/libraries",
content: null,
cancellationToken);
public Task<SceneSearchIndexStatus?> StartIndexAsync(
SceneSearchIndexRequest request,
CancellationToken cancellationToken)
=> SendAsync<SceneSearchIndexStatus>(
HttpMethod.Post,
"/api/jellyfin/index",
JsonContent.Create(request),
cancellationToken);
public Task<SceneSearchIndexStatus?> GetIndexStatusAsync(
CancellationToken cancellationToken)
=> SendAsync<SceneSearchIndexStatus>(
HttpMethod.Get,
"/api/jellyfin/index/status",
content: null,
cancellationToken);
private async Task<T?> SendAsync<T>(
HttpMethod method,
string path,
HttpContent? content,
CancellationToken cancellationToken)
{
var config = Configuration;
if (string.IsNullOrWhiteSpace(config.ApiUrl))
{
return default;
}
using var request = new HttpRequestMessage(
method,
config.ApiUrl.TrimEnd('/') + path)
{
Content = content
};
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (!string.IsNullOrWhiteSpace(config.ApiKey))
@@ -79,30 +125,26 @@ public sealed class SceneSearchClient : IDisposable
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning(
"Scene Search API returned HTTP {StatusCode} for '{SearchTerm}'",
(int)response.StatusCode,
searchTerm);
return [];
var body = await response.Content.ReadAsStringAsync(timeout.Token).ConfigureAwait(false);
throw new HttpRequestException(
$"Scene Search API returned HTTP {(int)response.StatusCode}: {body}");
}
var payload = await response.Content.ReadFromJsonAsync<SceneSearchResponse>(
return await response.Content.ReadFromJsonAsync<T>(
cancellationToken: timeout.Token).ConfigureAwait(false);
return payload?.Items ?? [];
}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
_logger.LogWarning(
"Scene Search API timed out after {TimeoutSeconds}s for '{SearchTerm}'",
"Scene Search API timed out after {TimeoutSeconds}s for {Path}",
config.TimeoutSeconds,
searchTerm);
return [];
path);
return default;
}
catch (HttpRequestException ex)
{
_logger.LogWarning(ex, "Scene Search API request failed for '{SearchTerm}'", searchTerm);
return [];
_logger.LogWarning(ex, "Scene Search API request failed for {Path}", path);
return default;
}
}