diff --git a/mediarepo-api/Cargo.toml b/mediarepo-api/Cargo.toml index 91da66e..41a3a71 100644 --- a/mediarepo-api/Cargo.toml +++ b/mediarepo-api/Cargo.toml @@ -32,7 +32,7 @@ features = [] [dependencies.tokio] version = "1.12.0" optional = true -features = ["sync", "fs", "net", "io-util", "io-std", "time", "rt"] +features = ["sync", "fs", "net", "io-util", "io-std", "time", "rt", "process"] [dependencies.toml] version = "0.5.8" diff --git a/mediarepo-api/src/daemon_management/cli.rs b/mediarepo-api/src/daemon_management/cli.rs new file mode 100644 index 0000000..215403f --- /dev/null +++ b/mediarepo-api/src/daemon_management/cli.rs @@ -0,0 +1,82 @@ +use crate::daemon_management::error::{DaemonError, DaemonResult}; +use std::ffi::OsStr; +use tokio::process::{Child, Command}; + +#[derive(Debug)] +pub struct DaemonCli { + daemon_path: String, + repo_path: String, + child: Option, +} + +impl DaemonCli { + pub fn new(daemon_path: String, repo_path: String) -> Self { + Self { + daemon_path, + repo_path, + child: None, + } + } + + /// Initializes a repository at the specified path + #[tracing::instrument] + pub async fn init_repo(&self) -> DaemonResult<()> { + let output = self + .run_command(vec!["--repo", self.repo_path.as_str(), "init"]) + .await?; + tracing::debug!("Response: {}", String::from_utf8(output).unwrap()); + + Ok(()) + } + + /// Starts a daemon for the given repository + #[tracing::instrument] + pub fn start_daemon(&mut self) -> DaemonResult<()> { + let child = self.run_daemon_process(vec!["--repo", self.repo_path.as_str(), "start"])?; + self.child = Some(child); + + Ok(()) + } + + /// Returns if the daemon is currently running + pub fn daemon_running(&mut self) -> bool { + if let Some(child) = &mut self.child { + child.try_wait().map(|e| e.is_some()).unwrap_or(true) + } else { + false + } + } + + /// Returns the path the daemon is serving + pub fn repo_path(&self) -> &String { + &self.repo_path + } + + /// Runs a daemon subcommand + async fn run_command, I: IntoIterator>( + &self, + args: I, + ) -> DaemonResult> { + let child = self.run_daemon_process(args)?; + let output = child.wait_with_output().await?; + + if output.status.success() { + Ok(output.stdout) + } else { + let stdout = String::from_utf8(output.stdout).map_err(|e| e.to_string())?; + let stderr = String::from_utf8(output.stderr).map_err(|e| e.to_string())?; + Err(DaemonError::from(format!("{}\n{}", stdout, stderr))) + } + } + + /// Runs a daemon process with the given args + fn run_daemon_process, I: IntoIterator>( + &self, + args: I, + ) -> DaemonResult { + Command::new(&self.daemon_path) + .args(args) + .spawn() + .map_err(DaemonError::from) + } +} diff --git a/mediarepo-api/src/daemon_management/error.rs b/mediarepo-api/src/daemon_management/error.rs new file mode 100644 index 0000000..6506695 --- /dev/null +++ b/mediarepo-api/src/daemon_management/error.rs @@ -0,0 +1,30 @@ +use std::fmt::{Display, Formatter}; + +pub type DaemonResult = Result; + +#[derive(Debug)] +pub struct DaemonError { + pub message: String, +} + +impl Display for DaemonError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.message.fmt(f) + } +} + +impl std::error::Error for DaemonError {} + +impl From for DaemonError { + fn from(e: std::io::Error) -> Self { + Self { + message: e.to_string(), + } + } +} + +impl From for DaemonError { + fn from(s: String) -> Self { + Self { message: s } + } +} diff --git a/mediarepo-api/src/daemon_management/mod.rs b/mediarepo-api/src/daemon_management/mod.rs new file mode 100644 index 0000000..0414a80 --- /dev/null +++ b/mediarepo-api/src/daemon_management/mod.rs @@ -0,0 +1,2 @@ +pub mod cli; +pub mod error; diff --git a/mediarepo-api/src/lib.rs b/mediarepo-api/src/lib.rs index 1c1dbbe..0d2a12b 100644 --- a/mediarepo-api/src/lib.rs +++ b/mediarepo-api/src/lib.rs @@ -3,5 +3,8 @@ pub mod types; #[cfg(feature = "client-api")] pub mod client_api; +#[cfg(feature = "client-api")] +pub mod daemon_management; + #[cfg(feature = "tauri-plugin")] pub mod tauri_plugin; diff --git a/mediarepo-api/src/tauri_plugin/commands/daemon.rs b/mediarepo-api/src/tauri_plugin/commands/daemon.rs new file mode 100644 index 0000000..14711b7 --- /dev/null +++ b/mediarepo-api/src/tauri_plugin/commands/daemon.rs @@ -0,0 +1,25 @@ +use crate::client_api::ApiClient; +use crate::tauri_plugin::commands::AppAccess; +use crate::tauri_plugin::error::PluginResult; + +#[tauri::command] +pub async fn init_repository(app_state: AppAccess<'_>, repo_path: String) -> PluginResult<()> { + let daemon = app_state.get_daemon_cli(repo_path).await; + daemon.init_repo().await?; + + Ok(()) +} + +#[tauri::command] +pub async fn start_daemon(app_state: AppAccess<'_>, repo_path: String) -> PluginResult<()> { + let mut daemon = app_state.get_daemon_cli(repo_path).await; + daemon.start_daemon()?; + app_state.add_started_daemon(daemon).await; + + Ok(()) +} + +#[tauri::command] +pub async fn check_daemon_running(address: String) -> PluginResult { + Ok(ApiClient::connect(&address).await.is_ok()) +} diff --git a/mediarepo-api/src/tauri_plugin/commands/mod.rs b/mediarepo-api/src/tauri_plugin/commands/mod.rs index 7e2cac7..42f6626 100644 --- a/mediarepo-api/src/tauri_plugin/commands/mod.rs +++ b/mediarepo-api/src/tauri_plugin/commands/mod.rs @@ -1,12 +1,14 @@ use parking_lot::lock_api::Mutex; use tauri::State; +pub use daemon::*; pub use file::*; pub use repo::*; pub use tag::*; use crate::tauri_plugin::state::{ApiState, AppState, BufferState, VolatileBuffer}; +pub mod daemon; pub mod file; pub mod repo; pub mod tag; diff --git a/mediarepo-api/src/tauri_plugin/error.rs b/mediarepo-api/src/tauri_plugin/error.rs index f5440fd..5abf4e3 100644 --- a/mediarepo-api/src/tauri_plugin/error.rs +++ b/mediarepo-api/src/tauri_plugin/error.rs @@ -1,4 +1,5 @@ use crate::client_api::error::ApiError; +use crate::daemon_management::error::DaemonError; use rmp_ipc::error::Error; use serde::Serialize; use std::fmt::{Display, Formatter}; @@ -67,3 +68,9 @@ impl From for PluginError { } } } + +impl From for PluginError { + fn from(e: DaemonError) -> Self { + Self { message: e.message } + } +} diff --git a/mediarepo-api/src/tauri_plugin/mod.rs b/mediarepo-api/src/tauri_plugin/mod.rs index d155002..5c13ce9 100644 --- a/mediarepo-api/src/tauri_plugin/mod.rs +++ b/mediarepo-api/src/tauri_plugin/mod.rs @@ -39,7 +39,10 @@ impl MediarepoPlugin { get_tags_for_file, get_active_repository, add_repository, - select_repository + select_repository, + init_repository, + start_daemon, + check_daemon_running, ]), } } @@ -61,14 +64,15 @@ impl Plugin for MediarepoPlugin { let buffer_state = BufferState::default(); app.manage(buffer_state.clone()); + + let repo_state = AppState::load()?; + app.manage(repo_state); + thread::spawn(move || loop { thread::sleep(Duration::from_secs(10)); buffer_state.clear_expired(); }); - 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 index 8fbcc68..4c1addd 100644 --- a/mediarepo-api/src/tauri_plugin/settings.rs +++ b/mediarepo-api/src/tauri_plugin/settings.rs @@ -14,11 +14,21 @@ pub struct Repository { pub(crate) address: String, } -#[derive(Default, Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct Settings { + pub daemon_path: String, pub repositories: HashMap, } +impl Default for Settings { + fn default() -> Self { + Self { + daemon_path: String::from("mediarepo-daemon"), + repositories: HashMap::new(), + } + } +} + fn get_settings_path() -> PathBuf { let dirs = ProjectDirs::from("com", "trivernis", "mediarepo").unwrap(); let config_path = dirs.config_dir().to_path_buf(); diff --git a/mediarepo-api/src/tauri_plugin/state.rs b/mediarepo-api/src/tauri_plugin/state.rs index 98dadb6..28bbb6e 100644 --- a/mediarepo-api/src/tauri_plugin/state.rs +++ b/mediarepo-api/src/tauri_plugin/state.rs @@ -9,6 +9,7 @@ use tauri::async_runtime::RwLock; use tokio::time::Instant; use crate::client_api::ApiClient; +use crate::daemon_management::cli::DaemonCli; use crate::tauri_plugin::error::{PluginError, PluginResult}; use crate::tauri_plugin::settings::{load_settings, Repository, Settings}; @@ -117,6 +118,7 @@ impl BufferState { pub struct AppState { pub active_repo: Arc>>, pub settings: Arc>, + pub running_daemons: Arc>>, } impl AppState { @@ -125,10 +127,25 @@ impl AppState { let settings = load_settings()?; let state = Self { - active_repo: Arc::new(RwLock::new(None)), + active_repo: Default::default(), settings: Arc::new(RwLock::new(settings)), + running_daemons: Default::default(), }; Ok(state) } + + /// Returns the daemon cli client + pub async fn get_daemon_cli(&self, repo_path: String) -> DaemonCli { + let settings = self.settings.read().await; + let path = settings.daemon_path.clone(); + + DaemonCli::new(path, repo_path) + } + + /// Adds a started daemon to the running daemons + pub async fn add_started_daemon(&self, daemon: DaemonCli) { + let mut daemons = self.running_daemons.write().await; + daemons.insert(daemon.repo_path().to_owned(), daemon); + } }