From f99ee75f9870b37fe4e2cc7d738e8ba0cb00cedb Mon Sep 17 00:00:00 2001 From: EliasSchriefer Date: Thu, 17 Sep 2020 22:14:20 +0200 Subject: [PATCH] Fix #1 by using enums instead of traits It very much reduces code and complexity. --- sfsmcd/Cargo.toml | 5 +- sfsmcd/src/ifaces.rs | 139 ++++++++++++++++++++++++++ sfsmcd/src/ifaces/errors.rs | 18 ---- sfsmcd/src/ifaces/mod.rs | 187 ----------------------------------- sfsmcd/src/ifaces/structs.rs | 4 - sfsmcd/src/ifaces/traits.rs | 19 ---- sfsmcd/src/main.rs | 41 ++++---- 7 files changed, 165 insertions(+), 248 deletions(-) create mode 100644 sfsmcd/src/ifaces.rs delete mode 100644 sfsmcd/src/ifaces/errors.rs delete mode 100644 sfsmcd/src/ifaces/mod.rs delete mode 100644 sfsmcd/src/ifaces/structs.rs delete mode 100644 sfsmcd/src/ifaces/traits.rs diff --git a/sfsmcd/Cargo.toml b/sfsmcd/Cargo.toml index 4770f0b..3654223 100644 --- a/sfsmcd/Cargo.toml +++ b/sfsmcd/Cargo.toml @@ -13,4 +13,7 @@ async-minecraft-ping = "0.2" dbus = { version = "0.8", features = ["futures"] } dbus-tokio = "0.5" log = "0.4" -env_logger = "0.7" \ No newline at end of file +env_logger = "0.7" + +[features] +minecraft-server = [] \ No newline at end of file diff --git a/sfsmcd/src/ifaces.rs b/sfsmcd/src/ifaces.rs new file mode 100644 index 0000000..cf6091f --- /dev/null +++ b/sfsmcd/src/ifaces.rs @@ -0,0 +1,139 @@ +pub use std::error::Error; + +#[derive(Debug)] +pub enum ServiceError { + NoPermission, + NotImplementedYet, + AlreadyStarted, + AlreadyStopped, + Failed, + IOError(std::io::Error) +} + +impl Error for ServiceError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::IOError(err) => Some(err), + _ => None + } + } +} + +impl From for ServiceError { + fn from(err: std::io::Error) -> Self { + ServiceError::IOError(err) + } +} + +impl core::fmt::Display for ServiceError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Service Error: ") + .and(match self { + Self::NoPermission => write!(f, "no permission"), + Self::NotImplementedYet => write!(f, "not implemented yet"), + Self::AlreadyStarted => write!(f, "already started"), + Self::AlreadyStopped => write!(f, "already stopped"), + Self::Failed => write!(f, "failed"), + Self::IOError(err) => write!(f, "IO error: {}", err) + }) + } +} + +#[derive(Debug, Copy, Clone)] +pub enum Service { + Daemon, + Minecraft +} + +impl Service { + pub async fn start(&self) -> Result<(), ServiceError> { + match self { + Self::Daemon => Err(ServiceError::AlreadyStarted), + #[cfg(not(feature = "minecraft-server"))] + Self::Minecraft => Err(ServiceError::NotImplementedYet), + #[cfg(feature = "minecraft-server")] + Self::Minecraft => if !self.is_running().await { + // Configure the command + let res = tokio::process::Command::new("screen") + .current_dir("/media/games/bukkit") + .args(r#"-dmS sfs-mc -t sfs-mc "java -server -Xmx2G -jar server.jar""#.split_whitespace()) + // Handle spawning + .spawn() + .map_err(|err| ServiceError::IOError(err))? + .await + // Convert io::Error to ServiceError + .map_err(|err| err.into()); + + if let Ok(status) = res { + if !status.success() { + Err(ServiceError::Failed)?; + } + } + + res.map(|_| ()) + } else { + Err(ServiceError::AlreadyStarted) + } + } + } + + pub async fn is_running(&self) -> bool { + match self { + Self::Daemon => true, + #[cfg(not(feature = "minecraft-server"))] + Self::Minecraft => false, + #[cfg(feature = "minecraft-server")] + Self::Minecraft => tokio::process::Command::new("screen") + .args("-rqx sfs-mc -t sfs-mc -Q title".split_whitespace()) + .output().await + .unwrap() + .status + .success() + } + } + + pub async fn stop(&self) -> Result<(), ServiceError> { + match self { + Self::Daemon => std::process::exit(0), + #[cfg(not(feature = "minecraft-server"))] + Self::Minecraft => Err(ServiceError::NotImplementedYet), + #[cfg(feature = "minecraft-server")] + Self::Minecraft => Err(ServiceError::NoPermission) + } + } + + pub async fn restart(&self) -> Result<(), ServiceError> { + match self { + Self::Daemon => Err(ServiceError::NoPermission), + _ => { + if self.is_running().await { + self.stop().await?; + } + self.start().await + } + } + } +} + +#[derive(Default, Debug, Copy, Clone)] +pub struct ServiceData; + +impl dbus::tree::DataType for ServiceData { + type Tree = (); + type ObjectPath = std::sync::Arc>; + type Property = (); + type Interface = (); + type Method = (); + type Signal = (); +} + +#[macro_export] +macro_rules! handle_iface { + ($m:ident, |$data:ident| $($f:tt)+) => {move |$m| tokio::task::block_in_place(move || { + let $data = $m.path.get_data(); + // Run a Future on the dedicated tokio runtime + iface_rt_handle().block_on(async move { + $($f)+ + }) + })}; +} \ No newline at end of file diff --git a/sfsmcd/src/ifaces/errors.rs b/sfsmcd/src/ifaces/errors.rs deleted file mode 100644 index a7b0108..0000000 --- a/sfsmcd/src/ifaces/errors.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[derive(Debug)] -pub enum ServiceError { - NoPermission, - AlreadyStarted, - AlreadyStopped, - IOError(std::io::Error) -} - -impl core::fmt::Display for ServiceError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::NoPermission => write!(f, "no permission"), - Self::AlreadyStarted => write!(f, "already started"), - Self::AlreadyStopped => write!(f, "already stopped"), - Self::IOError(err) => write!(f, "IO error ({})", err) - } - } -} \ No newline at end of file diff --git a/sfsmcd/src/ifaces/mod.rs b/sfsmcd/src/ifaces/mod.rs deleted file mode 100644 index 77dca0b..0000000 --- a/sfsmcd/src/ifaces/mod.rs +++ /dev/null @@ -1,187 +0,0 @@ -use dbus::{ - tree::DataType, - Path -}; -use std::sync::Arc; -use tokio::{ - sync::RwLock, - task::JoinHandle, - process::Command -}; -use log::{ info, warn }; -#[allow(non_camel_case_types)] -pub mod traits; -mod structs; -mod errors; - -pub trait Interface { - fn path() -> Path<'static>; - fn default_path(&self) -> Path<'static>; -} - -#[derive(Clone, Debug, Default)] -pub struct Data { - daemon: Arc> -} - -#[derive(Debug)] -pub struct ObjectPathData { - daemon: Option>>, - minecraft: Option>> -} - -impl ObjectPathData { - pub fn daemon(&self) -> Option>> { - self.daemon.clone() - } - - pub fn minecraft(&self) -> Option>> { - self.minecraft.clone() - } -} - -impl From for ObjectPathData { - fn from(d: structs::DaemonService) -> Self { - Self { - daemon: Some(Arc::new(RwLock::new(d))), - minecraft: None - } - } -} - -impl From for ObjectPathData { - fn from(m: structs::MinecraftService) -> Self { - Self { - daemon: None, - minecraft: Some(Arc::new(RwLock::new(m))) - } - } -} - -impl DataType for Data { - type Tree = (); - type ObjectPath = ObjectPathData; - type Property = (); - type Interface = (); - type Method = (); - type Signal = (); -} - -#[macro_export] -macro_rules! handle_iface_generic { - ($m:ident, $no_path:ident, $path:ident, |$data:ident: $data_ty:ty| {$($d:tt)+} $($f:tt)+) => {move |$m| tokio::task::block_in_place(move || { - // Prepare no-path error - let $no_path = dbus::tree::MethodErr::no_path($m.path.get_name()); - let $path = $m.path.get_name(); - let $data = $m.path.get_data(); - // Get data depending on the object path - $($d)+ - // If the object path couldn't be found, the method will return the no-path error - - let f = |$data: $data_ty| $($f)+; - - // Run a Future on the dedicated tokio runtime - iface_rt_handle().block_on(f($data)) - })} -} - -#[macro_export] -#[inline] -macro_rules! handle_org_ddnss_sfs_mc_Service { - ($m:ident, |$data:ident| $($f:tt)+) => { - $crate::handle_iface_generic!($m, no_path, path, |$data: std::sync::Arc>| { - let $data = if path == &DaemonService::path() { - $data.daemon() - } else if path == &MinecraftService::path() { - $data.minecraft() - } else {None}.ok_or(no_path)?; - } $($f)+) - }; -} - -#[allow(non_snake_case)] -pub mod Service { - use super::{ - Interface, - JoinHandle, Path, Arc, Command, info, warn - }; - pub use super::{ - traits::org_ddnss_sfs_mc_Service, - structs::{ DaemonService, MinecraftService }, - errors::ServiceError - }; - pub use crate::handle_org_ddnss_sfs_mc_Service as handle; - - impl Interface for DaemonService { - fn path() -> Path<'static> { - Self.default_path() - } - - fn default_path(&self) -> Path<'static> { - Path::new("/").unwrap() - } - } - - impl Interface for MinecraftService { - fn path() -> Path<'static> { - Self.default_path() - } - - fn default_path(&self) -> Path<'static> { - Path::new("/minecraft").unwrap() - } - } - - impl org_ddnss_sfs_mc_Service for DaemonService { - fn start(&mut self) -> Result>, ServiceError> { - Err(ServiceError::AlreadyStarted) - } - - fn is_running(&self) -> bool { - true - } - - fn stop(&mut self) -> Result>, ServiceError> { - std::process::exit(0); - } - - fn restart(&mut self) -> Result>, ServiceError> { - Err(ServiceError::NoPermission) - } - } - - impl org_ddnss_sfs_mc_Service for MinecraftService { - fn start(&mut self) -> Result>, ServiceError> { - if !self.is_running() { - // Configure the command - std::process::Command::new("screen") - .current_dir("/media/games/bukkit") - .args(r#"-dmS sfs-mc -t sfs-mc "java -server -Xmx2G -jar server.jar""#.split_whitespace()) - // Handle spawning - .spawn() - .map_err(|err| ServiceError::IOError(err))? - // Wait for execution - .wait() - .map(|status| info!("Started minecraft server {}successfully", if !status.success() { "un" } else { "" })) - .map_err(|err| warn!("Couldn't start minecraft server successfully ({})", err)) - .or(Ok(()))?; - Ok(None) - } else { - Err(ServiceError::AlreadyStarted) - } - } - - fn is_running(&self) -> bool { - std::process::Command::new("screen") - .args("-rqx sfs-mc -t sfs-mc -Q title".split_whitespace()) - .output() - .unwrap() - .status - .success() - } - - fn stop(&mut self) -> Result>, ServiceError> { - Err(ServiceError::NoPermission) - } - } -} \ No newline at end of file diff --git a/sfsmcd/src/ifaces/structs.rs b/sfsmcd/src/ifaces/structs.rs deleted file mode 100644 index d8a4286..0000000 --- a/sfsmcd/src/ifaces/structs.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[derive(Debug, Default, Copy, Clone)] -pub struct DaemonService; -#[derive(Debug, Default, Copy, Clone)] -pub struct MinecraftService; \ No newline at end of file diff --git a/sfsmcd/src/ifaces/traits.rs b/sfsmcd/src/ifaces/traits.rs deleted file mode 100644 index f731b8d..0000000 --- a/sfsmcd/src/ifaces/traits.rs +++ /dev/null @@ -1,19 +0,0 @@ -use tokio::task::JoinHandle; -use super::errors::ServiceError; -pub trait org_ddnss_sfs_mc_Service { - fn start(&mut self) -> Result>, ServiceError>; - fn stop(&mut self) -> Result>, ServiceError>; - fn is_running(&self) -> bool; - fn restart(&mut self) -> Result>, ServiceError> { - if self.is_running() { - self.stop()?; - } - self.start() - } -} - -impl core::fmt::Debug for (dyn org_ddnss_sfs_mc_Service + 'static) { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "org.ddnss.sfs.mc.Service") - } -} \ No newline at end of file diff --git a/sfsmcd/src/main.rs b/sfsmcd/src/main.rs index 083bb2c..32bfa95 100644 --- a/sfsmcd/src/main.rs +++ b/sfsmcd/src/main.rs @@ -5,7 +5,8 @@ use dbus::{ tree::Factory, tree::MethodErr }; -use ifaces::Interface; +use ifaces::*; +use tokio::sync::RwLock; use std::sync::Arc; mod ifaces; @@ -59,41 +60,43 @@ async fn main() -> Result<(), Box> { debug!("PID: {}, User: {}", std::process::id(), std::env::var("USER").unwrap_or("N/A".into())); // Build interfaces - let f = Factory::new_sync::(); + let f = Factory::new_sync::(); let mut iface_map = std::collections::HashMap::new(); - iface_map.insert(iface_name("Service"), Arc::new({ - use ifaces::Service::*; - + iface_map.insert(iface_name("Service"), Arc::new( f.interface(iface_name("Service"), ()) - // org_ddnss_sfs_mc_Service::start() - .add_m(f.method_sync("Start", (), handle!(m, |data| async move { - org_ddnss_sfs_mc_Service::start(&mut *data.write().await) + .add_m(f.method_sync("Start", (), handle_iface!(m, |data| { + data.read().await + .start().await .map(|_| vec![m.msg.method_return()]) .map_err(|err| MethodErr::failed(&err)) }))) - // org_ddnss_sfs_mc_Service::stop() - .add_m(f.method_sync("Stop", (), handle!(m, |data| async move { - org_ddnss_sfs_mc_Service::stop(&mut *data.write().await) + .add_m(f.method_sync("Stop", (), handle_iface!(m, |data| { + data.read().await + .stop().await .map(|_| vec![m.msg.method_return()]) .map_err(|err| MethodErr::failed(&err)) }))) - // org_ddnss_sfs_mc_Service::restart() - .add_m(f.method_sync("Restart", (), handle!(m, |data| async move { - org_ddnss_sfs_mc_Service::restart(&mut *data.write().await) + .add_m(f.method_sync("Restart", (), handle_iface!(m, |data| { + data.read().await + .restart().await .map(|_| vec![m.msg.method_return()]) .map_err(|err| MethodErr::failed(&err)) }))) - // org_ddnss_sfs_mc_Service::is_running() - .add_m(f.method_sync("IsRunning", (), handle!(m, |data| async move { + .add_m(f.method_sync("IsRunning", (), handle_iface!(m, |data| { Ok(vec![m.msg.method_return().append1( - org_ddnss_sfs_mc_Service::is_running(&*data.read().await) + data.read().await + .is_running().await )]) })).out_arg("b")) - })); + )); // Add interfaces to object paths in a new tree let tree = f.tree(()) - .add(f.object_path(ifaces::Service::DaemonService::path(), ifaces::Service::DaemonService.into()) + .add(f.object_path("/", Arc::new(RwLock::new(Service::Daemon))) + .introspectable() + .add(iface_map[&iface_name("Service")].clone()) + ) + .add(f.object_path("/minecraft", Arc::new(RwLock::new(Service::Minecraft))) .introspectable() .add(iface_map[&iface_name("Service")].clone()) );