mirror of
https://github.com/MrSleeps/jellyfin-scene-search.git
synced 2026-11-15 22:41:28 +00:00
Add SceneSearchClient.cs
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using Jellyfin.Plugin.SceneSearch.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Plugin.SceneSearch;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP client for the local scene-search service.
|
||||
/// </summary>
|
||||
public sealed class SceneSearchClient : IDisposable
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<SceneSearchClient> _logger;
|
||||
|
||||
public SceneSearchClient(ILogger<SceneSearchClient> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether enough configuration exists to search.
|
||||
/// </summary>
|
||||
public bool IsConfigured
|
||||
{
|
||||
get
|
||||
{
|
||||
var config = Configuration;
|
||||
return config.Enabled
|
||||
&& Uri.TryCreate(config.ApiUrl, UriKind.Absolute, out _);
|
||||
}
|
||||
}
|
||||
|
||||
private static PluginConfiguration Configuration
|
||||
=> Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
||||
|
||||
/// <summary>
|
||||
/// Searches the local scene-search API.
|
||||
/// </summary>
|
||||
public async Task<IReadOnlyList<SceneSearchItem>> SearchAsync(
|
||||
string searchTerm,
|
||||
int limit,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var config = Configuration;
|
||||
if (!config.Enabled || string.IsNullOrWhiteSpace(config.ApiUrl))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var safeLimit = Math.Clamp(limit, 1, Math.Clamp(config.MaxResults, 1, 50));
|
||||
var requestUri = string.Concat(
|
||||
config.ApiUrl.TrimEnd('/'),
|
||||
"/api/search?q=",
|
||||
Uri.EscapeDataString(searchTerm),
|
||||
"&scene_limit=",
|
||||
Math.Min(safeLimit * 4, 100).ToString(System.Globalization.CultureInfo.InvariantCulture),
|
||||
"&item_limit=",
|
||||
safeLimit.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(config.ApiKey))
|
||||
{
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", config.ApiKey);
|
||||
}
|
||||
|
||||
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
timeout.CancelAfter(TimeSpan.FromSeconds(Math.Clamp(config.TimeoutSeconds, 5, 300)));
|
||||
|
||||
try
|
||||
{
|
||||
using var response = await _httpClient.SendAsync(
|
||||
request,
|
||||
HttpCompletionOption.ResponseHeadersRead,
|
||||
timeout.Token).ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Scene Search API returned HTTP {StatusCode} for '{SearchTerm}'",
|
||||
(int)response.StatusCode,
|
||||
searchTerm);
|
||||
return [];
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<SceneSearchResponse>(
|
||||
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}'",
|
||||
config.TimeoutSeconds,
|
||||
searchTerm);
|
||||
return [];
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Scene Search API request failed for '{SearchTerm}'", searchTerm);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_httpClient.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user