feat: implement transcode settings repository and integrate with local-files provider

This commit is contained in:
2026-03-16 04:24:39 +01:00
parent 50df852416
commit 9d792249c9
12 changed files with 269 additions and 177 deletions

View File

@@ -16,6 +16,8 @@ use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberI
use domain::{ChannelService, IMediaProvider, IProviderRegistry, ProviderCapabilities, ScheduleEngineService, StreamingProtocol, UserService};
use infra::factory::{build_activity_log_repository, build_channel_repository, build_provider_config_repository, build_schedule_repository, build_user_repository};
#[cfg(feature = "local-files")]
use infra::factory::build_transcode_settings_repository;
use infra::run_migrations;
use k_core::http::server::{ServerConfig, apply_standard_middleware};
use tokio::net::TcpListener;
@@ -79,14 +81,7 @@ async fn main() -> anyhow::Result<()> {
let db_pool = k_core::db::connect(&db_config).await?;
run_migrations(&db_pool).await?;
#[cfg(feature = "local-files")]
let raw_sqlite_pool: Option<sqlx::SqlitePool> = match &db_pool {
#[cfg(feature = "sqlite")]
k_core::db::DatabasePool::Sqlite(p) => Some(p.clone()),
#[allow(unreachable_patterns)]
_ => None,
};
let db_pool = Arc::new(db_pool);
let user_repo = build_user_repository(&db_pool).await?;
let channel_repo = build_channel_repository(&db_pool).await?;
@@ -101,8 +96,6 @@ async fn main() -> anyhow::Result<()> {
let mut local_index: Option<Arc<infra::LocalIndex>> = None;
#[cfg(feature = "local-files")]
let mut transcode_manager: Option<Arc<infra::TranscodeManager>> = None;
#[cfg(feature = "local-files")]
let mut sqlite_pool_for_state: Option<sqlx::SqlitePool> = None;
let mut registry = infra::ProviderRegistry::new();
@@ -123,51 +116,43 @@ async fn main() -> anyhow::Result<()> {
}
#[cfg(feature = "local-files")]
"local_files" => {
if let k_core::db::DatabasePool::Sqlite(ref sqlite_pool) = db_pool {
if let Ok(cfg_map) = serde_json::from_str::<std::collections::HashMap<String, String>>(&row.config_json) {
if let Some(files_dir) = cfg_map.get("files_dir") {
let transcode_dir = cfg_map.get("transcode_dir")
.filter(|s| !s.is_empty())
.map(std::path::PathBuf::from);
let cleanup_ttl_hours: u32 = cfg_map.get("cleanup_ttl_hours")
.and_then(|s| s.parse().ok())
.unwrap_or(24);
let lf_cfg = infra::LocalFilesConfig {
root_dir: std::path::PathBuf::from(files_dir),
base_url: config.base_url.clone(),
transcode_dir: transcode_dir.clone(),
cleanup_ttl_hours,
};
tracing::info!("Loading local-files provider from DB config at {:?}", files_dir);
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; });
let tm = transcode_dir.as_ref().map(|td| {
std::fs::create_dir_all(td).ok();
tracing::info!("Transcoding enabled; cache dir: {:?}", td);
let tm = infra::TranscodeManager::new(td.clone(), cleanup_ttl_hours);
// Load persisted TTL override from transcode_settings table.
let tm_clone = Arc::clone(&tm);
let pool_clone = sqlite_pool.clone();
tokio::spawn(async move {
if let Ok(row) = sqlx::query_as::<_, (i64,)>(
"SELECT cleanup_ttl_hours FROM transcode_settings WHERE id = 1",
)
.fetch_one(&pool_clone)
.await
{
tm_clone.set_cleanup_ttl(row.0 as u32);
}
});
tm
});
registry.register(
"local",
Arc::new(infra::LocalFilesProvider::new(Arc::clone(&idx), lf_cfg, tm.clone())),
);
transcode_manager = tm;
sqlite_pool_for_state = Some(sqlite_pool.clone());
if let Ok(cfg_map) = serde_json::from_str::<std::collections::HashMap<String, String>>(&row.config_json) {
if let Some(files_dir) = cfg_map.get("files_dir") {
let transcode_dir = cfg_map.get("transcode_dir")
.filter(|s| !s.is_empty())
.map(std::path::PathBuf::from);
let cleanup_ttl_hours: u32 = cfg_map.get("cleanup_ttl_hours")
.and_then(|s| s.parse().ok())
.unwrap_or(24);
tracing::info!("Loading local-files provider from DB config at {:?}", files_dir);
match infra::factory::build_local_files_bundle(
&db_pool,
std::path::PathBuf::from(files_dir),
transcode_dir,
cleanup_ttl_hours,
config.base_url.clone(),
).await {
Ok(bundle) => {
let scan_idx = Arc::clone(&bundle.local_index);
tokio::spawn(async move { scan_idx.rescan().await; });
if let Some(ref tm) = bundle.transcode_manager {
tracing::info!("Transcoding enabled");
// Load persisted TTL override from transcode_settings table.
let tm_clone = Arc::clone(tm);
let repo = build_transcode_settings_repository(&db_pool).await.ok();
tokio::spawn(async move {
if let Some(r) = repo {
if let Ok(Some(ttl)) = r.load_cleanup_ttl().await {
tm_clone.set_cleanup_ttl(ttl);
}
}
});
}
registry.register("local", bundle.provider);
transcode_manager = bundle.transcode_manager;
local_index = Some(bundle.local_index);
}
Err(e) => tracing::warn!("Failed to build local-files provider: {}", e),
}
}
}
@@ -192,48 +177,34 @@ async fn main() -> anyhow::Result<()> {
#[cfg(feature = "local-files")]
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(),
transcode_dir: config.transcode_dir.clone(),
cleanup_ttl_hours: config.transcode_cleanup_ttl_hours,
};
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; });
// Build TranscodeManager if TRANSCODE_DIR is set.
let tm = config.transcode_dir.as_ref().map(|td| {
std::fs::create_dir_all(td).ok();
tracing::info!("Transcoding enabled; cache dir: {:?}", td);
let tm = infra::TranscodeManager::new(td.clone(), config.transcode_cleanup_ttl_hours);
// Load persisted TTL from DB.
let tm_clone = Arc::clone(&tm);
let pool_clone = sqlite_pool.clone();
tokio::spawn(async move {
if let Ok(row) = sqlx::query_as::<_, (i64,)>(
"SELECT cleanup_ttl_hours FROM transcode_settings WHERE id = 1",
)
.fetch_one(&pool_clone)
.await
{
tm_clone.set_cleanup_ttl(row.0 as u32);
}
});
tm
});
registry.register(
"local",
Arc::new(infra::LocalFilesProvider::new(idx, lf_cfg, tm.clone())),
);
transcode_manager = tm;
sqlite_pool_for_state = Some(sqlite_pool.clone());
} else {
tracing::warn!("local-files requires SQLite; ignoring LOCAL_FILES_DIR");
tracing::info!("Media provider: local files at {:?}", dir);
match infra::factory::build_local_files_bundle(
&db_pool,
dir.clone(),
config.transcode_dir.clone(),
config.transcode_cleanup_ttl_hours,
config.base_url.clone(),
).await {
Ok(bundle) => {
let scan_idx = Arc::clone(&bundle.local_index);
tokio::spawn(async move { scan_idx.rescan().await; });
if let Some(ref tm) = bundle.transcode_manager {
tracing::info!("Transcoding enabled; cache dir: {:?}", config.transcode_dir);
let tm_clone = Arc::clone(tm);
let repo = build_transcode_settings_repository(&db_pool).await.ok();
tokio::spawn(async move {
if let Some(r) = repo {
if let Ok(Some(ttl)) = r.load_cleanup_ttl().await {
tm_clone.set_cleanup_ttl(ttl);
}
}
});
}
registry.register("local", bundle.provider);
transcode_manager = bundle.transcode_manager;
local_index = Some(bundle.local_index);
}
Err(e) => tracing::warn!("local-files requires SQLite; ignoring LOCAL_FILES_DIR: {}", e),
}
}
}
@@ -263,6 +234,9 @@ async fn main() -> anyhow::Result<()> {
schedule_repo,
);
#[cfg(feature = "local-files")]
let transcode_settings_repo = build_transcode_settings_repository(&db_pool).await.ok();
#[allow(unused_mut)]
let mut state = AppState::new(
user_service,
@@ -275,12 +249,12 @@ async fn main() -> anyhow::Result<()> {
log_tx,
log_history,
activity_log_repo,
db_pool,
#[cfg(feature = "local-files")]
transcode_settings_repo,
)
.await?;
#[cfg(feature = "local-files")]
{ state.raw_sqlite_pool = raw_sqlite_pool; }
#[cfg(feature = "local-files")]
if let Some(idx) = local_index {
*state.local_index.write().await = Some(idx);
@@ -289,10 +263,6 @@ async fn main() -> anyhow::Result<()> {
if let Some(tm) = transcode_manager {
*state.transcode_manager.write().await = Some(tm);
}
#[cfg(feature = "local-files")]
if let Some(pool) = sqlite_pool_for_state {
*state.sqlite_pool.write().await = Some(pool);
}
let server_config = ServerConfig {
cors_origins: config.cors_allowed_origins.clone(),