aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
authorBond-009 <bond.009@outlook.com>2026-06-07 22:56:51 +0200
committerGitHub <noreply@github.com>2026-06-07 22:56:51 +0200
commit4459147788c0a11d3039723644708f8c8f7cdf2d (patch)
tree2d76561f3f60d62fb4ff1ebff3ee04ac97d35df6 /MediaBrowser.Controller
parent003f01a99aa7765c5380ff8cff0955addb72d083 (diff)
parentd8d386e88a8bd27ef8e40497e302db184cc02b08 (diff)
Merge pull request #16121 from Shadowghost/search-rebased
Implement search providers
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/Library/IExternalSearchProvider.cs20
-rw-r--r--MediaBrowser.Controller/Library/IInternalSearchProvider.cs8
-rw-r--r--MediaBrowser.Controller/Library/ISearchEngine.cs18
-rw-r--r--MediaBrowser.Controller/Library/ISearchManager.cs48
-rw-r--r--MediaBrowser.Controller/Library/ISearchProvider.cs44
-rw-r--r--MediaBrowser.Controller/Library/SearchProviderQuery.cs45
-rw-r--r--MediaBrowser.Controller/Library/SearchResult.cs60
7 files changed, 225 insertions, 18 deletions
diff --git a/MediaBrowser.Controller/Library/IExternalSearchProvider.cs b/MediaBrowser.Controller/Library/IExternalSearchProvider.cs
new file mode 100644
index 0000000000..bded8ba3a3
--- /dev/null
+++ b/MediaBrowser.Controller/Library/IExternalSearchProvider.cs
@@ -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);
+}
diff --git a/MediaBrowser.Controller/Library/IInternalSearchProvider.cs b/MediaBrowser.Controller/Library/IInternalSearchProvider.cs
new file mode 100644
index 0000000000..f87931395d
--- /dev/null
+++ b/MediaBrowser.Controller/Library/IInternalSearchProvider.cs
@@ -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
+{
+}
diff --git a/MediaBrowser.Controller/Library/ISearchEngine.cs b/MediaBrowser.Controller/Library/ISearchEngine.cs
deleted file mode 100644
index 31dcbba5bd..0000000000
--- a/MediaBrowser.Controller/Library/ISearchEngine.cs
+++ /dev/null
@@ -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);
- }
-}
diff --git a/MediaBrowser.Controller/Library/ISearchManager.cs b/MediaBrowser.Controller/Library/ISearchManager.cs
new file mode 100644
index 0000000000..4f763829a7
--- /dev/null
+++ b/MediaBrowser.Controller/Library/ISearchManager.cs
@@ -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();
+}
diff --git a/MediaBrowser.Controller/Library/ISearchProvider.cs b/MediaBrowser.Controller/Library/ISearchProvider.cs
new file mode 100644
index 0000000000..3b300ed38b
--- /dev/null
+++ b/MediaBrowser.Controller/Library/ISearchProvider.cs
@@ -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);
+}
diff --git a/MediaBrowser.Controller/Library/SearchProviderQuery.cs b/MediaBrowser.Controller/Library/SearchProviderQuery.cs
new file mode 100644
index 0000000000..845588c872
--- /dev/null
+++ b/MediaBrowser.Controller/Library/SearchProviderQuery.cs
@@ -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; }
+}
diff --git a/MediaBrowser.Controller/Library/SearchResult.cs b/MediaBrowser.Controller/Library/SearchResult.cs
new file mode 100644
index 0000000000..e6f145e979
--- /dev/null
+++ b/MediaBrowser.Controller/Library/SearchResult.cs
@@ -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);
+}