First working POC

This commit is contained in:
2026-01-26 22:21:54 +01:00
commit cfddc1edea
255 changed files with 77606 additions and 0 deletions
+435
View File
@@ -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))
}