feat: multi-instance provider support
- provider_configs: add id TEXT PK; migrate existing rows (provider_type becomes id)
- local_files_index: add provider_id column + index; scope all queries per instance
- ProviderConfigRow: add id field; add get_by_id to trait
- LocalIndex:🆕 add provider_id param; all SQL scoped by provider_id
- factory: thread provider_id through build_local_files_bundle
- AppState.local_index: Option<Arc<LocalIndex>> → HashMap<String, Arc<LocalIndex>>
- admin_providers: restructured routes (POST /admin/providers create, PUT/DELETE /{id}, POST /test)
- admin_providers: use row.id as registry key for jellyfin and local_files
- files.rescan: optional ?provider=<id> query param
- frontend: add id to ProviderConfig, update api/hooks, new multi-instance panel UX
This commit is contained in:
@@ -36,15 +36,17 @@ pub fn decode_id(id: &MediaItemId) -> Option<String> {
|
||||
pub struct LocalIndex {
|
||||
items: Arc<RwLock<HashMap<MediaItemId, LocalFileItem>>>,
|
||||
pub root_dir: PathBuf,
|
||||
provider_id: String,
|
||||
pool: sqlx::SqlitePool,
|
||||
}
|
||||
|
||||
impl LocalIndex {
|
||||
/// Create the index, immediately loading persisted entries from SQLite.
|
||||
pub async fn new(config: &LocalFilesConfig, pool: sqlx::SqlitePool) -> Self {
|
||||
pub async fn new(config: &LocalFilesConfig, pool: sqlx::SqlitePool, provider_id: String) -> Self {
|
||||
let idx = Self {
|
||||
items: Arc::new(RwLock::new(HashMap::new())),
|
||||
root_dir: config.root_dir.clone(),
|
||||
provider_id,
|
||||
pool,
|
||||
};
|
||||
idx.load_from_db().await;
|
||||
@@ -65,8 +67,10 @@ impl LocalIndex {
|
||||
}
|
||||
|
||||
let rows = sqlx::query_as::<_, Row>(
|
||||
"SELECT id, rel_path, title, duration_secs, year, tags, top_dir FROM local_files_index",
|
||||
"SELECT id, rel_path, title, duration_secs, year, tags, top_dir \
|
||||
FROM local_files_index WHERE provider_id = ?",
|
||||
)
|
||||
.bind(&self.provider_id)
|
||||
.fetch_all(&self.pool)
|
||||
.await;
|
||||
|
||||
@@ -86,7 +90,7 @@ impl LocalIndex {
|
||||
};
|
||||
map.insert(MediaItemId::new(row.id), item);
|
||||
}
|
||||
info!("Local files index: loaded {} items from DB", map.len());
|
||||
info!("Local files index [{}]: loaded {} items from DB", self.provider_id, map.len());
|
||||
}
|
||||
Err(e) => {
|
||||
// Table might not exist yet on first run — that's fine.
|
||||
@@ -100,7 +104,7 @@ impl LocalIndex {
|
||||
/// Returns the number of items found. Called on startup (background task)
|
||||
/// and via `POST /files/rescan`.
|
||||
pub async fn rescan(&self) -> u32 {
|
||||
info!("Local files: scanning {:?}", self.root_dir);
|
||||
info!("Local files [{}]: scanning {:?}", self.provider_id, self.root_dir);
|
||||
let new_items = scan_dir(&self.root_dir).await;
|
||||
let count = new_items.len() as u32;
|
||||
|
||||
@@ -119,15 +123,16 @@ impl LocalIndex {
|
||||
error!("Failed to persist local files index: {}", e);
|
||||
}
|
||||
|
||||
info!("Local files: indexed {} items", count);
|
||||
info!("Local files [{}]: indexed {} items", self.provider_id, count);
|
||||
count
|
||||
}
|
||||
|
||||
async fn save_to_db(&self, items: &[LocalFileItem]) -> Result<(), sqlx::Error> {
|
||||
// Rebuild the table in one transaction.
|
||||
// Rebuild the table in one transaction, scoped to this provider.
|
||||
let mut tx = self.pool.begin().await?;
|
||||
|
||||
sqlx::query("DELETE FROM local_files_index")
|
||||
sqlx::query("DELETE FROM local_files_index WHERE provider_id = ?")
|
||||
.bind(&self.provider_id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
@@ -137,8 +142,8 @@ impl LocalIndex {
|
||||
let tags_json = serde_json::to_string(&item.tags).unwrap_or_else(|_| "[]".into());
|
||||
sqlx::query(
|
||||
"INSERT INTO local_files_index \
|
||||
(id, rel_path, title, duration_secs, year, tags, top_dir, scanned_at) \
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(id, rel_path, title, duration_secs, year, tags, top_dir, scanned_at, provider_id) \
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
)
|
||||
.bind(&id)
|
||||
.bind(&item.rel_path)
|
||||
@@ -148,6 +153,7 @@ impl LocalIndex {
|
||||
.bind(&tags_json)
|
||||
.bind(&item.top_dir)
|
||||
.bind(&now)
|
||||
.bind(&self.provider_id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user