Fix #1 by using enums instead of traits
It very much reduces code and complexity.
This commit is contained in:
		| @@ -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" | ||||
| env_logger = "0.7" | ||||
|  | ||||
| [features] | ||||
| minecraft-server = [] | ||||
							
								
								
									
										139
									
								
								sfsmcd/src/ifaces.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								sfsmcd/src/ifaces.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<std::io::Error> 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<tokio::sync::RwLock<Service>>; | ||||
|     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)+ | ||||
|         }) | ||||
|     })}; | ||||
| } | ||||
| @@ -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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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<RwLock<structs::DaemonService>> | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct ObjectPathData { | ||||
|     daemon: Option<Arc<RwLock<structs::DaemonService>>>, | ||||
|     minecraft: Option<Arc<RwLock<structs::MinecraftService>>> | ||||
| } | ||||
|  | ||||
| impl ObjectPathData { | ||||
|     pub fn daemon(&self) -> Option<Arc<RwLock<structs::DaemonService>>> { | ||||
|         self.daemon.clone() | ||||
|     } | ||||
|  | ||||
|     pub fn minecraft(&self) -> Option<Arc<RwLock<structs::MinecraftService>>> { | ||||
|         self.minecraft.clone() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<structs::DaemonService> for ObjectPathData { | ||||
|     fn from(d: structs::DaemonService) -> Self { | ||||
|         Self { | ||||
|             daemon: Some(Arc::new(RwLock::new(d))), | ||||
|             minecraft: None | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<structs::MinecraftService> 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<tokio::sync::RwLock<_>>| { | ||||
|             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<Option<JoinHandle<()>>, ServiceError> { | ||||
|             Err(ServiceError::AlreadyStarted) | ||||
|         } | ||||
|  | ||||
|         fn is_running(&self) -> bool { | ||||
|             true | ||||
|         } | ||||
|  | ||||
|         fn stop(&mut self) -> Result<Option<JoinHandle<()>>, ServiceError> { | ||||
|             std::process::exit(0); | ||||
|         } | ||||
|  | ||||
|         fn restart(&mut self) -> Result<Option<JoinHandle<()>>, ServiceError> { | ||||
|             Err(ServiceError::NoPermission) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl org_ddnss_sfs_mc_Service for MinecraftService { | ||||
|         fn start(&mut self) -> Result<Option<JoinHandle<()>>, 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<Option<JoinHandle<()>>, ServiceError> { | ||||
|             Err(ServiceError::NoPermission) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| #[derive(Debug, Default, Copy, Clone)] | ||||
| pub struct DaemonService; | ||||
| #[derive(Debug, Default, Copy, Clone)] | ||||
| pub struct MinecraftService; | ||||
| @@ -1,19 +0,0 @@ | ||||
| use tokio::task::JoinHandle; | ||||
| use super::errors::ServiceError; | ||||
| pub trait org_ddnss_sfs_mc_Service { | ||||
|     fn start(&mut self) -> Result<Option<JoinHandle<()>>, ServiceError>; | ||||
|     fn stop(&mut self) -> Result<Option<JoinHandle<()>>, ServiceError>; | ||||
|     fn is_running(&self) -> bool; | ||||
|     fn restart(&mut self) -> Result<Option<JoinHandle<()>>, 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") | ||||
|     } | ||||
| } | ||||
| @@ -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<dyn std::error::Error>> { | ||||
|     debug!("PID: {}, User: {}", std::process::id(), std::env::var("USER").unwrap_or("N/A".into())); | ||||
|  | ||||
|     // Build interfaces | ||||
|     let f = Factory::new_sync::<ifaces::Data>(); | ||||
|     let f = Factory::new_sync::<ServiceData>(); | ||||
|     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()) | ||||
|         ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user