diff --git a/mediarepo-api/Cargo.toml b/mediarepo-api/Cargo.toml index 668c4ad..61b03a2 100644 --- a/mediarepo-api/Cargo.toml +++ b/mediarepo-api/Cargo.toml @@ -13,6 +13,7 @@ async-trait = {version = "0.1.51", optional=true} rmp-ipc = {version = "0.7.2", optional=true} parking_lot = {version="0.11.2", optional=true} serde_json = {version="1.0.68", optional=true} +directories = {version="4.0.1", optional=true} [dependencies.serde] version = "1.0.130" @@ -31,7 +32,11 @@ features = [] [dependencies.tokio] version = "1.12.0" optional = true -features = ["sync"] +features = ["sync", "fs"] + +[dependencies.toml] +version = "0.5.8" +optional = true [features] tauri-plugin = ["client-api","tauri", "rmp-ipc", "parking_lot", "serde_json"] diff --git a/mediarepo-api/src/tauri_plugin/commands/mod.rs b/mediarepo-api/src/tauri_plugin/commands/mod.rs index f6b415f..845dc1f 100644 --- a/mediarepo-api/src/tauri_plugin/commands/mod.rs +++ b/mediarepo-api/src/tauri_plugin/commands/mod.rs @@ -1,16 +1,26 @@ use tauri::State; pub use file::*; +pub use repo::*; +pub use tag::*; -use crate::tauri_plugin::state::{ApiState, BufferState, OnceBuffer}; +use crate::tauri_plugin::state::{ApiState, AppState, BufferState, OnceBuffer}; pub mod file; +pub mod repo; +pub mod tag; pub type ApiAccess<'a> = State<'a, ApiState>; pub type BufferAccess<'a> = State<'a, BufferState>; +pub type AppAccess<'a> = State<'a, AppState>; /// Adds a once-buffer to the buffer store -fn add_once_buffer(buffer_state: BufferAccess, key: String, mime: String, buf: Vec) -> String { +fn add_once_buffer( + buffer_state: BufferAccess<'_>, + key: String, + mime: String, + buf: Vec, +) -> String { let uri = format!("once://{}", key); let once_buffer = OnceBuffer::new(mime, buf); let mut once_buffers = buffer_state.buffer.lock(); diff --git a/mediarepo-api/src/tauri_plugin/commands/repo.rs b/mediarepo-api/src/tauri_plugin/commands/repo.rs new file mode 100644 index 0000000..2bd9cc8 --- /dev/null +++ b/mediarepo-api/src/tauri_plugin/commands/repo.rs @@ -0,0 +1,82 @@ +use crate::client_api::ApiClient; +use crate::tauri_plugin::commands::{ApiAccess, AppAccess}; +use crate::tauri_plugin::error::{PluginError, PluginResult}; +use crate::tauri_plugin::settings::{save_settings, Repository}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use tokio::fs; + +static REPO_CONFIG_FILE: &str = "repo.toml"; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct RepoConfig { + pub listen_address: String, + pub database_path: String, + pub default_file_store: String, +} + +#[tauri::command] +pub async fn get_repositories(app_state: AppAccess<'_>) -> PluginResult> { + let settings = app_state.settings.read().await; + + Ok(settings.repositories.values().cloned().collect()) +} + +#[tauri::command] +pub async fn get_active_repository(app_state: AppAccess<'_>) -> PluginResult> { + let repo = app_state.active_repo.read().await; + Ok(repo.clone()) +} + +#[tauri::command] +pub async fn add_repository( + name: String, + path: String, + app_state: AppAccess<'_>, +) -> PluginResult> { + let repo_path = path.clone(); + let path = PathBuf::from(path); + let RepoConfig { listen_address, .. } = read_repo_config(path.join(REPO_CONFIG_FILE)).await?; + + let repo = Repository { + name, + path: Some(repo_path), + address: listen_address, + }; + + let mut repositories = Vec::new(); + { + let mut settings = app_state.settings.write().await; + settings.repositories.insert(repo.name.clone(), repo); + save_settings(&settings)?; + repositories.append(&mut settings.repositories.values().cloned().collect()); + } + + Ok(repositories) +} + +#[tauri::command] +pub async fn select_repository( + name: String, + app_state: AppAccess<'_>, + api_state: ApiAccess<'_>, +) -> PluginResult<()> { + let settings = app_state.settings.read().await; + let repo = settings.repositories.get(&name).ok_or(PluginError::from( + format!("Repository '{}' not found", name).as_str(), + ))?; + let client = ApiClient::connect(&repo.address).await?; + api_state.set_api(client).await; + + let mut active_repo = app_state.active_repo.write().await; + *active_repo = Some(repo.clone()); + + Ok(()) +} + +async fn read_repo_config(path: PathBuf) -> PluginResult { + let toml_str = fs::read_to_string(path).await?; + let config = toml::from_str(&toml_str)?; + + Ok(config) +} diff --git a/mediarepo-api/src/tauri_plugin/commands/tag.rs b/mediarepo-api/src/tauri_plugin/commands/tag.rs new file mode 100644 index 0000000..3e6b136 --- /dev/null +++ b/mediarepo-api/src/tauri_plugin/commands/tag.rs @@ -0,0 +1,14 @@ +use crate::tauri_plugin::commands::ApiAccess; +use crate::tauri_plugin::error::PluginResult; +use crate::types::tags::TagResponse; + +#[tauri::command] +pub async fn get_tags_for_file( + hash: String, + api_state: ApiAccess<'_>, +) -> PluginResult> { + let api = api_state.api().await?; + let tags = api.tag.get_tags_for_file(hash).await?; + + Ok(tags) +} diff --git a/mediarepo-api/src/tauri_plugin/error.rs b/mediarepo-api/src/tauri_plugin/error.rs index 7fc37b0..d1f1375 100644 --- a/mediarepo-api/src/tauri_plugin/error.rs +++ b/mediarepo-api/src/tauri_plugin/error.rs @@ -1,13 +1,22 @@ use crate::client_api::error::ApiError; use serde::Serialize; +use std::fmt::{Display, Formatter}; pub type PluginResult = Result; -#[derive(Clone, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct PluginError { message: String, } +impl Display for PluginError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.message.fmt(f) + } +} + +impl std::error::Error for PluginError {} + impl From<&str> for PluginError { fn from(s: &str) -> Self { Self { @@ -23,3 +32,27 @@ impl From for PluginError { } } } + +impl From for PluginError { + fn from(e: std::io::Error) -> Self { + Self { + message: e.to_string(), + } + } +} + +impl From for PluginError { + fn from(e: toml::de::Error) -> Self { + Self { + message: format!("Deserialization failed: {:?}", e), + } + } +} + +impl From for PluginError { + fn from(e: toml::ser::Error) -> Self { + Self { + message: format!("Serialization failed: {:?}", e), + } + } +} diff --git a/mediarepo-api/src/tauri_plugin/mod.rs b/mediarepo-api/src/tauri_plugin/mod.rs index 3fa250f..801a384 100644 --- a/mediarepo-api/src/tauri_plugin/mod.rs +++ b/mediarepo-api/src/tauri_plugin/mod.rs @@ -3,11 +3,12 @@ use tauri::{AppHandle, Builder, Invoke, Manager, Runtime}; use state::ApiState; -use crate::tauri_plugin::state::BufferState; +use crate::tauri_plugin::state::{AppState, BufferState}; -mod commands; +pub(crate) mod commands; pub mod custom_schemes; pub mod error; +mod settings; mod state; use commands::*; @@ -30,7 +31,12 @@ impl MediarepoPlugin { find_files, read_file_by_hash, get_file_thumbnails, - read_thumbnail + read_thumbnail, + get_repositories, + get_tags_for_file, + get_active_repository, + add_repository, + select_repository ]), } } @@ -52,6 +58,9 @@ impl Plugin for MediarepoPlugin { let buffer_state = BufferState::default(); app.manage(buffer_state); + let repo_state = AppState::load()?; + app.manage(repo_state); + Ok(()) } diff --git a/mediarepo-api/src/tauri_plugin/settings.rs b/mediarepo-api/src/tauri_plugin/settings.rs new file mode 100644 index 0000000..0b7d164 --- /dev/null +++ b/mediarepo-api/src/tauri_plugin/settings.rs @@ -0,0 +1,54 @@ +use crate::tauri_plugin::error::PluginResult; +use directories::ProjectDirs; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; + +static SETTINGS_FILE: &str = "settings.toml"; + +#[derive(Serialize, Deserialize, Clone)] +pub struct Repository { + pub(crate) name: String, + pub(crate) path: Option, + pub(crate) address: String, +} + +#[derive(Default, Serialize, Deserialize)] +pub struct Settings { + pub repositories: HashMap, +} + +fn get_settings_path() -> PathBuf { + let dirs = ProjectDirs::from("com", "trivernis", "mediarepo").unwrap(); + let config_path = dirs.config_dir().to_path_buf(); + + config_path.join(SETTINGS_FILE) +} + +/// Writes the settings to the file +pub fn save_settings(settings: &Settings) -> PluginResult<()> { + let settings_path = get_settings_path(); + let settings_string = toml::to_string(&settings)?; + fs::write(&settings_path, &settings_string.into_bytes())?; + + Ok(()) +} + +/// Loads the settings from the file +pub fn load_settings() -> PluginResult { + let dirs = ProjectDirs::from("com", "trivernis", "mediarepo").unwrap(); + let config_path = dirs.config_dir().to_path_buf(); + if !config_path.exists() { + fs::create_dir_all(&config_path)?; + } + let settings_path = config_path.join(SETTINGS_FILE); + if !settings_path.exists() { + let settings = Settings::default(); + save_settings(&settings)?; + } + let config_str = fs::read_to_string(settings_path)?; + let settings = toml::from_str(&config_str)?; + + Ok(settings) +} diff --git a/mediarepo-api/src/tauri_plugin/state.rs b/mediarepo-api/src/tauri_plugin/state.rs index 307c814..f23178e 100644 --- a/mediarepo-api/src/tauri_plugin/state.rs +++ b/mediarepo-api/src/tauri_plugin/state.rs @@ -7,6 +7,7 @@ use tauri::async_runtime::RwLock; use crate::client_api::ApiClient; use crate::tauri_plugin::error::{PluginError, PluginResult}; +use crate::tauri_plugin::settings::{load_settings, Repository, Settings}; pub struct ApiState { inner: Arc>>, @@ -51,3 +52,21 @@ impl OnceBuffer { pub struct BufferState { pub buffer: Arc>>, } + +pub struct AppState { + pub active_repo: Arc>>, + pub settings: Arc>, +} + +impl AppState { + pub fn load() -> PluginResult { + let settings = load_settings()?; + + let state = Self { + active_repo: Arc::new(RwLock::new(None)), + settings: Arc::new(RwLock::new(settings)), + }; + + Ok(state) + } +}