First working POC
This commit is contained in:
@@ -0,0 +1,435 @@
|
||||
// Tauri commands for repository access
|
||||
// Uses handle-based system: UUID -> Arc<HybridRepository>
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use log::{debug, error, info};
|
||||
use tauri::State;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::jellyfin::HttpClient;
|
||||
use crate::repository::{HybridRepository, MediaRepository, OnlineRepository, OfflineRepository, types::*};
|
||||
|
||||
/// Repository handle manager
|
||||
pub struct RepositoryManager {
|
||||
repositories: Arc<Mutex<HashMap<String, Arc<HybridRepository>>>>,
|
||||
}
|
||||
|
||||
impl RepositoryManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
repositories: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(&self, handle: String, repository: HybridRepository) {
|
||||
let mut repos = self.repositories.lock().unwrap();
|
||||
repos.insert(handle, Arc::new(repository));
|
||||
}
|
||||
|
||||
pub fn get(&self, handle: &str) -> Option<Arc<HybridRepository>> {
|
||||
let repos = self.repositories.lock().unwrap();
|
||||
repos.get(handle).cloned()
|
||||
}
|
||||
|
||||
pub fn destroy(&self, handle: &str) {
|
||||
let mut repos = self.repositories.lock().unwrap();
|
||||
repos.remove(handle);
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for Tauri state
|
||||
pub struct RepositoryManagerWrapper(pub RepositoryManager);
|
||||
|
||||
/// Create a new repository instance
|
||||
/// Returns a handle (UUID) for accessing the repository
|
||||
#[tauri::command]
|
||||
pub async fn repository_create(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
db: State<'_, crate::commands::storage::DatabaseWrapper>,
|
||||
server_url: String,
|
||||
user_id: String,
|
||||
access_token: String,
|
||||
server_id: String,
|
||||
) -> Result<String, String> {
|
||||
info!("[REPO] repository_create called for user: {}", user_id);
|
||||
|
||||
// Create HTTP client for online repository
|
||||
debug!("[REPO] Creating HTTP client...");
|
||||
let http_config = crate::jellyfin::HttpConfig::default();
|
||||
let http_client = HttpClient::new(http_config).map_err(|e| {
|
||||
error!("[REPO] HTTP client creation failed: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
debug!("[REPO] HTTP client created successfully");
|
||||
|
||||
// Create online repository
|
||||
debug!("[REPO] Creating online repository...");
|
||||
let online = OnlineRepository::new(Arc::new(http_client), server_url, user_id.clone(), access_token);
|
||||
debug!("[REPO] Online repository created");
|
||||
|
||||
// Create offline repository with async-safe database service
|
||||
debug!("[REPO] Creating database service...");
|
||||
let db_service = {
|
||||
let database = db.0.lock().map_err(|e| {
|
||||
error!("[REPO] Database lock failed: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
debug!("[REPO] Database lock acquired, getting service...");
|
||||
Arc::new(database.service())
|
||||
}; // Lock is released here
|
||||
debug!("[REPO] Database service created");
|
||||
|
||||
debug!("[REPO] Creating offline repository...");
|
||||
let offline = OfflineRepository::new(db_service, server_id, user_id);
|
||||
debug!("[REPO] Offline repository created");
|
||||
|
||||
// Create hybrid repository
|
||||
debug!("[REPO] Creating hybrid repository...");
|
||||
let hybrid = HybridRepository::new(online, offline);
|
||||
debug!("[REPO] Hybrid repository created");
|
||||
|
||||
// Generate handle and store repository
|
||||
let uuid = Uuid::new_v4();
|
||||
let handle = format!("{}", uuid);
|
||||
info!("[REPO] Generated handle: {}", handle);
|
||||
|
||||
// Store repository synchronously
|
||||
debug!("[REPO] Storing repository...");
|
||||
manager.0.create(handle.clone(), hybrid);
|
||||
info!("[REPO] Repository stored successfully");
|
||||
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
/// Destroy a repository instance
|
||||
#[tauri::command]
|
||||
pub async fn repository_destroy(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
) -> Result<(), String> {
|
||||
manager.0.destroy(&handle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get libraries
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_libraries(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
) -> Result<Vec<Library>, String> {
|
||||
debug!("[REPO] get_libraries called with handle: {}", handle);
|
||||
let repo = manager.0.get(&handle).ok_or_else(|| {
|
||||
error!("[REPO] Repository not found for handle: {}", handle);
|
||||
"Repository not found".to_string()
|
||||
})?;
|
||||
debug!("[REPO] Repository found, fetching libraries...");
|
||||
repo.as_ref().get_libraries()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("[REPO] Error fetching libraries: {:?}", e);
|
||||
format!("{:?}", e)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get items in a container (library, folder, album, etc.)
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_items(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
parent_id: String,
|
||||
options: Option<GetItemsOptions>,
|
||||
) -> Result<SearchResult, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().get_items(&parent_id, options)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get a single item by ID
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_item(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
) -> Result<MediaItem, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().get_item(&item_id)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get latest items in a library
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_latest_items(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
parent_id: String,
|
||||
limit: Option<usize>,
|
||||
) -> Result<Vec<MediaItem>, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().get_latest_items(&parent_id, limit)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get resume items (continue watching/listening)
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_resume_items(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
parent_id: Option<String>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<Vec<MediaItem>, String> {
|
||||
debug!("[REPO] get_resume_items called with handle: {}", handle);
|
||||
let repo = manager.0.get(&handle).ok_or_else(|| {
|
||||
error!("[REPO] Repository not found for handle: {}", handle);
|
||||
"Repository not found".to_string()
|
||||
})?;
|
||||
debug!("[REPO] Repository found, fetching resume items...");
|
||||
repo.as_ref().get_resume_items(parent_id.as_deref(), limit)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("[REPO] Error fetching resume items: {:?}", e);
|
||||
format!("{:?}", e)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get next up episodes
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_next_up_episodes(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
series_id: Option<String>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<Vec<MediaItem>, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().get_next_up_episodes(series_id.as_deref(), limit)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get recently played audio
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_recently_played_audio(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
limit: Option<usize>,
|
||||
) -> Result<Vec<MediaItem>, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().get_recently_played_audio(limit)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get resume movies
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_resume_movies(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
limit: Option<usize>,
|
||||
) -> Result<Vec<MediaItem>, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().get_resume_movies(limit)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get genres for a library
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_genres(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
parent_id: Option<String>,
|
||||
) -> Result<Vec<Genre>, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().get_genres(parent_id.as_deref())
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Search for items
|
||||
#[tauri::command]
|
||||
pub async fn repository_search(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
query: String,
|
||||
options: Option<SearchOptions>,
|
||||
) -> Result<SearchResult, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().search(&query, options)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get playback info for an item
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_playback_info(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
) -> Result<PlaybackInfo, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().get_playback_info(&item_id)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get video stream URL with optional seeking support
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_video_stream_url(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
media_source_id: Option<String>,
|
||||
start_time_seconds: Option<f64>,
|
||||
audio_stream_index: Option<i32>,
|
||||
) -> Result<String, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref()
|
||||
.get_video_stream_url(
|
||||
&item_id,
|
||||
media_source_id.as_deref(),
|
||||
start_time_seconds,
|
||||
audio_stream_index,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get audio stream URL for a track
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_audio_stream_url(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
) -> Result<String, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref()
|
||||
.get_audio_stream_url(&item_id)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Report playback start
|
||||
#[tauri::command]
|
||||
pub async fn repository_report_playback_start(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
position_ticks: i64,
|
||||
) -> Result<(), String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().report_playback_start(&item_id, position_ticks)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Report playback progress
|
||||
#[tauri::command]
|
||||
pub async fn repository_report_playback_progress(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
position_ticks: i64,
|
||||
) -> Result<(), String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().report_playback_progress(&item_id, position_ticks)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Report playback stopped
|
||||
#[tauri::command]
|
||||
pub async fn repository_report_playback_stopped(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
position_ticks: i64,
|
||||
) -> Result<(), String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().report_playback_stopped(&item_id, position_ticks)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get image URL for an item
|
||||
#[tauri::command]
|
||||
pub fn repository_get_image_url(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
image_type: ImageType,
|
||||
options: Option<ImageOptions>,
|
||||
) -> Result<String, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
Ok(repo.as_ref().get_image_url(&item_id, image_type, options))
|
||||
}
|
||||
|
||||
/// Mark an item as favorite
|
||||
#[tauri::command]
|
||||
pub async fn repository_mark_favorite(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
) -> Result<(), String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().mark_favorite(&item_id)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Unmark an item as favorite
|
||||
#[tauri::command]
|
||||
pub async fn repository_unmark_favorite(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
) -> Result<(), String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().unmark_favorite(&item_id)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get person details
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_person(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
person_id: String,
|
||||
) -> Result<MediaItem, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().get_person(&person_id)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get items by person (actor, director, etc.)
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_items_by_person(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
person_id: String,
|
||||
options: Option<GetItemsOptions>,
|
||||
) -> Result<SearchResult, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().get_items_by_person(&person_id, options)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get similar/related items for a media item
|
||||
#[tauri::command]
|
||||
pub async fn repository_get_similar_items(
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
limit: Option<usize>,
|
||||
) -> Result<SearchResult, String> {
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref().get_similar_items(&item_id, limit)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
Reference in New Issue
Block a user