feat: implement multi-provider support in media library
- Introduced IProviderRegistry to manage multiple media providers. - Updated AppState to use provider_registry instead of a single media_provider. - Refactored library routes to support provider-specific queries for collections, series, genres, and items. - Enhanced ProgrammingBlock to include provider_id for algorithmic and manual content types. - Modified frontend components to allow selection of providers and updated API calls to include provider parameters. - Adjusted hooks and types to accommodate provider-specific functionality.
This commit is contained in:
@@ -10,7 +10,7 @@ use axum::http::{HeaderName, HeaderValue};
|
||||
use std::sync::Arc;
|
||||
use tower_http::cors::{AllowHeaders, AllowMethods, AllowOrigin, CorsLayer};
|
||||
|
||||
use domain::{ChannelService, IMediaProvider, ProviderCapabilities, ScheduleEngineService, StreamingProtocol, UserService};
|
||||
use domain::{ChannelService, IMediaProvider, IProviderRegistry, ProviderCapabilities, ScheduleEngineService, StreamingProtocol, UserService};
|
||||
use infra::factory::{build_channel_repository, build_schedule_repository, build_user_repository};
|
||||
use infra::run_migrations;
|
||||
use k_core::http::server::{ServerConfig, apply_standard_middleware};
|
||||
@@ -72,11 +72,11 @@ async fn main() -> anyhow::Result<()> {
|
||||
let user_service = UserService::new(user_repo);
|
||||
let channel_service = ChannelService::new(channel_repo.clone());
|
||||
|
||||
// Build media provider — Jellyfin → local-files → noop, first match wins.
|
||||
// Build provider registry — all configured providers are registered simultaneously.
|
||||
#[cfg(feature = "local-files")]
|
||||
let mut local_index: Option<Arc<infra::LocalIndex>> = None;
|
||||
|
||||
let mut maybe_provider: Option<Arc<dyn IMediaProvider>> = None;
|
||||
let mut registry = infra::ProviderRegistry::new();
|
||||
|
||||
#[cfg(feature = "jellyfin")]
|
||||
if let (Some(base_url), Some(api_key), Some(user_id)) = (
|
||||
@@ -85,7 +85,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
&config.jellyfin_user_id,
|
||||
) {
|
||||
tracing::info!("Media provider: Jellyfin at {}", base_url);
|
||||
maybe_provider = Some(Arc::new(infra::JellyfinMediaProvider::new(infra::JellyfinConfig {
|
||||
registry.register("jellyfin", Arc::new(infra::JellyfinMediaProvider::new(infra::JellyfinConfig {
|
||||
base_url: base_url.clone(),
|
||||
api_key: api_key.clone(),
|
||||
user_id: user_id.clone(),
|
||||
@@ -93,35 +93,33 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-files")]
|
||||
if maybe_provider.is_none() {
|
||||
if let Some(dir) = &config.local_files_dir {
|
||||
if let k_core::db::DatabasePool::Sqlite(ref sqlite_pool) = db_pool {
|
||||
tracing::info!("Media provider: local files at {:?}", dir);
|
||||
let lf_cfg = infra::LocalFilesConfig {
|
||||
root_dir: dir.clone(),
|
||||
base_url: config.base_url.clone(),
|
||||
};
|
||||
let idx = Arc::new(infra::LocalIndex::new(&lf_cfg, sqlite_pool.clone()).await);
|
||||
local_index = Some(Arc::clone(&idx));
|
||||
let scan_idx = Arc::clone(&idx);
|
||||
tokio::spawn(async move { scan_idx.rescan().await; });
|
||||
maybe_provider = Some(Arc::new(infra::LocalFilesProvider::new(idx, lf_cfg)));
|
||||
} else {
|
||||
tracing::warn!("local-files requires SQLite; ignoring LOCAL_FILES_DIR");
|
||||
}
|
||||
if let Some(dir) = &config.local_files_dir {
|
||||
if let k_core::db::DatabasePool::Sqlite(ref sqlite_pool) = db_pool {
|
||||
tracing::info!("Media provider: local files at {:?}", dir);
|
||||
let lf_cfg = infra::LocalFilesConfig {
|
||||
root_dir: dir.clone(),
|
||||
base_url: config.base_url.clone(),
|
||||
};
|
||||
let idx = Arc::new(infra::LocalIndex::new(&lf_cfg, sqlite_pool.clone()).await);
|
||||
local_index = Some(Arc::clone(&idx));
|
||||
let scan_idx = Arc::clone(&idx);
|
||||
tokio::spawn(async move { scan_idx.rescan().await; });
|
||||
registry.register("local", Arc::new(infra::LocalFilesProvider::new(idx, lf_cfg)));
|
||||
} else {
|
||||
tracing::warn!("local-files requires SQLite; ignoring LOCAL_FILES_DIR");
|
||||
}
|
||||
}
|
||||
|
||||
let media_provider: Arc<dyn IMediaProvider> = maybe_provider.unwrap_or_else(|| {
|
||||
tracing::warn!(
|
||||
"No media provider configured. Set JELLYFIN_BASE_URL / LOCAL_FILES_DIR."
|
||||
);
|
||||
Arc::new(NoopMediaProvider)
|
||||
});
|
||||
if registry.is_empty() {
|
||||
tracing::warn!("No media provider configured. Set JELLYFIN_BASE_URL / LOCAL_FILES_DIR.");
|
||||
registry.register("noop", Arc::new(NoopMediaProvider));
|
||||
}
|
||||
|
||||
let registry = Arc::new(registry);
|
||||
|
||||
let bg_channel_repo = channel_repo.clone();
|
||||
let schedule_engine = ScheduleEngineService::new(
|
||||
Arc::clone(&media_provider),
|
||||
Arc::clone(®istry) as Arc<dyn IProviderRegistry>,
|
||||
channel_repo,
|
||||
schedule_repo,
|
||||
);
|
||||
@@ -131,7 +129,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
user_service,
|
||||
channel_service,
|
||||
schedule_engine,
|
||||
media_provider,
|
||||
registry,
|
||||
config.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Reference in New Issue
Block a user