//! 미디어 서버용 file_id → 경로/원격스트림 리졸버 (DB 조회 + VFS). use crate::remote::RemotePool; use archive_db::Db; use archive_media::RemoteStream; use std::path::PathBuf; use std::sync::Arc; pub struct DbResolver { pub db: Arc, pub thumb_root: PathBuf, pub remote: Arc, } struct FileLoc { kind: String, // sources.kind source_id: i64, dir: String, name: String, size: i64, } impl DbResolver { fn locate(&self, file_id: i64) -> Option { self.db .with_read(|conn| { conn.query_row( "SELECT s.kind, f.source_id, fo.path, f.name, f.size FROM files f JOIN folders fo ON fo.id = f.folder_id JOIN sources s ON s.id = f.source_id WHERE f.id = ?1 AND f.deleted_at IS NULL", [file_id], |r| { Ok(FileLoc { kind: r.get(0)?, source_id: r.get(1)?, dir: r.get(2)?, name: r.get(3)?, size: r.get(4)?, }) }, ) }) .ok() } } #[async_trait::async_trait] impl archive_media::PathResolver for DbResolver { fn resolve(&self, file_id: i64) -> Option { let loc = self.locate(file_id)?; if loc.kind != "local" { return None; } Some(PathBuf::from(loc.dir).join(loc.name)) } fn resolve_thumb(&self, file_id: i64, size_class: u8) -> Option { let key: String = self .db .with_read(|conn| { conn.query_row( "SELECT cache_key FROM thumbs WHERE file_id = ?1 AND size_class = ?2", rusqlite::params![file_id, size_class], |r| r.get(0), ) }) .ok()?; let path = archive_indexer::thumbq::thumb_path(&self.thumb_root, size_class, &key); path.exists().then_some(path) } fn is_remote(&self, file_id: i64) -> bool { self.locate(file_id).map(|l| l.kind != "local").unwrap_or(false) } async fn open_remote( &self, file_id: i64, range: Option>, ) -> Option { let loc = self.locate(file_id)?; let provider = self.remote.get(loc.source_id).await?; // 원격 상대 경로: 폴더 path + name (루트는 빈 문자열) let rel = if loc.dir == "/" || loc.dir.is_empty() { loc.name.clone() } else { format!("{}/{}", loc.dir.trim_start_matches('/'), loc.name) }; let mime = mime_guess::from_path(&loc.name).first_or_octet_stream().to_string(); // 0바이트 요청(HEAD 격)이면 길이만 반환 if let Some(r) = &range { if r.start == 0 && r.end == 0 { return Some(RemoteStream { total_len: loc.size.max(0) as u64, reader: Box::new(tokio::io::empty()), mime, }); } } let reader = provider.open_range(&rel, range).await.ok()?; Some(RemoteStream { total_len: loc.size.max(0) as u64, reader, mime, }) } }