Merge pull request #16121 from Shadowghost/search-rebased
Some checks failed
Stale PR Check / Check PRs with merge conflicts (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
Format / format-check (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
OpenAPI Publish / OpenAPI - Publish Artifact (push) Has been cancelled
OpenAPI Publish / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI Publish / OpenAPI - Publish Stable Spec (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / main (push) Has been cancelled

Implement search providers
This commit is contained in:
Bond-009
2026-06-07 22:56:51 +02:00
committed by GitHub
16 changed files with 1089 additions and 251 deletions

View File

@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Threading;
namespace MediaBrowser.Controller.Library;
/// <summary>
/// Interface for external search providers that offer enhanced search capabilities.
/// </summary>
public interface IExternalSearchProvider : ISearchProvider
{
/// <summary>
/// Searches for items matching the query.
/// </summary>
/// <param name="query">The search query.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Async enumerable of search results with relevance scores.</returns>
new IAsyncEnumerable<SearchResult> SearchAsync(
SearchProviderQuery query,
CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,8 @@
namespace MediaBrowser.Controller.Library;
/// <summary>
/// Marker interface for internal search providers that typically query the local database directly.
/// </summary>
public interface IInternalSearchProvider : ISearchProvider
{
}

View File

@@ -1,18 +0,0 @@
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search;
namespace MediaBrowser.Controller.Library
{
/// <summary>
/// Interface ILibrarySearchEngine.
/// </summary>
public interface ISearchEngine
{
/// <summary>
/// Gets the search hints.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>Task{IEnumerable{SearchHintInfo}}.</returns>
QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query);
}
}

View File

@@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search;
namespace MediaBrowser.Controller.Library;
/// <summary>
/// Orchestrates search operations across registered search providers.
/// </summary>
public interface ISearchManager
{
/// <summary>
/// Searches for items and returns hints suitable for autocomplete/typeahead UI.
/// Results are ordered by relevance score from search providers.
/// </summary>
/// <param name="query">The search query including filters and pagination.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Paginated search hints with item metadata for display.</returns>
Task<QueryResult<SearchHintInfo>> GetSearchHintsAsync(
SearchQuery query,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets ranked search results from registered providers. Returns only item IDs and
/// relevance scores; callers are responsible for loading items and applying user-access filtering.
/// </summary>
/// <param name="query">The search provider query with type/media filters.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Search results containing item IDs and relevance scores.</returns>
Task<IReadOnlyList<SearchResult>> GetSearchResultsAsync(
SearchProviderQuery query,
CancellationToken cancellationToken = default);
/// <summary>
/// Registers search providers discovered through dependency injection.
/// Called during application startup.
/// </summary>
/// <param name="providers">The search providers to register.</param>
void AddParts(IEnumerable<ISearchProvider> providers);
/// <summary>
/// Gets all registered search providers ordered by priority.
/// </summary>
/// <returns>The list of search providers including the SQL fallback provider.</returns>
IReadOnlyList<ISearchProvider> GetProviders();
}

View File

@@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Controller.Library;
/// <summary>
/// Interface for search providers.
/// </summary>
public interface ISearchProvider
{
/// <summary>
/// Gets the name of the provider.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the type of the provider.
/// </summary>
MetadataPluginType Type { get; }
/// <summary>
/// Gets the priority of the provider. Lower values execute first.
/// </summary>
int Priority { get; }
/// <summary>
/// Searches for items matching the query.
/// </summary>
/// <param name="query">The search query.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Ranked list of candidate item IDs with scores.</returns>
Task<IReadOnlyList<SearchResult>> SearchAsync(
SearchProviderQuery query,
CancellationToken cancellationToken);
/// <summary>
/// Determines whether this provider can handle the given query.
/// </summary>
/// <param name="query">The search query to evaluate.</param>
/// <returns>True if this provider can search for the query; otherwise, false.</returns>
bool CanSearch(SearchProviderQuery query);
}

View File

@@ -0,0 +1,45 @@
using System;
using Jellyfin.Data.Enums;
namespace MediaBrowser.Controller.Library;
/// <summary>
/// Query object for search providers.
/// </summary>
public class SearchProviderQuery
{
/// <summary>
/// Gets the search term.
/// </summary>
public required string SearchTerm { get; init; }
/// <summary>
/// Gets the user ID for user-specific searches.
/// </summary>
public Guid? UserId { get; init; }
/// <summary>
/// Gets the item types to include in the search.
/// </summary>
public BaseItemKind[] IncludeItemTypes { get; init; } = [];
/// <summary>
/// Gets the item types to exclude from the search.
/// </summary>
public BaseItemKind[] ExcludeItemTypes { get; init; } = [];
/// <summary>
/// Gets the media types to include in the search.
/// </summary>
public MediaType[] MediaTypes { get; init; } = [];
/// <summary>
/// Gets the maximum number of results to return.
/// </summary>
public int? Limit { get; init; }
/// <summary>
/// Gets the parent ID to scope the search.
/// </summary>
public Guid? ParentId { get; init; }
}

View File

@@ -0,0 +1,60 @@
using System;
namespace MediaBrowser.Controller.Library;
/// <summary>
/// Represents an item matched by a search query with its relevance score.
/// </summary>
public readonly struct SearchResult : IEquatable<SearchResult>
{
/// <summary>
/// Initializes a new instance of the <see cref="SearchResult"/> struct.
/// </summary>
/// <param name="itemId">The item ID.</param>
/// <param name="score">The relevance score.</param>
public SearchResult(Guid itemId, float score)
{
ItemId = itemId;
Score = score;
}
/// <summary>
/// Gets the ID of the matching item.
/// </summary>
public Guid ItemId { get; init; }
/// <summary>
/// Gets the relevance score. Higher values indicate more relevant results.
/// </summary>
public float Score { get; init; }
/// <summary>
/// Compares two <see cref="SearchResult"/> instances for equality.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns>True if the instances are equal; otherwise, false.</returns>
public static bool operator ==(SearchResult left, SearchResult right)
=> left.Equals(right);
/// <summary>
/// Compares two <see cref="SearchResult"/> instances for inequality.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns>True if the instances are not equal; otherwise, false.</returns>
public static bool operator !=(SearchResult left, SearchResult right)
=> !left.Equals(right);
/// <inheritdoc/>
public override bool Equals(object? obj)
=> obj is SearchResult other && Equals(other);
/// <inheritdoc/>
public bool Equals(SearchResult other)
=> ItemId.Equals(other.ItemId) && Score.Equals(other.Score);
/// <inheritdoc/>
public override int GetHashCode()
=> HashCode.Combine(ItemId, Score);
}