Fix #1 by using enums instead of traits
It very much reduces code and complexity.
This commit is contained in:
		| @@ -14,3 +14,6 @@ dbus = { version = "0.8", features = ["futures"] } | |||||||
| dbus-tokio = "0.5" | dbus-tokio = "0.5" | ||||||
| log = "0.4" | 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::Factory, | ||||||
|     tree::MethodErr |     tree::MethodErr | ||||||
| }; | }; | ||||||
| use ifaces::Interface; | use ifaces::*; | ||||||
|  | use tokio::sync::RwLock; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
|  |  | ||||||
| mod ifaces; | 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())); |     debug!("PID: {}, User: {}", std::process::id(), std::env::var("USER").unwrap_or("N/A".into())); | ||||||
|  |  | ||||||
|     // Build interfaces |     // Build interfaces | ||||||
|     let f = Factory::new_sync::<ifaces::Data>(); |     let f = Factory::new_sync::<ServiceData>(); | ||||||
|     let mut iface_map = std::collections::HashMap::new(); |     let mut iface_map = std::collections::HashMap::new(); | ||||||
|     iface_map.insert(iface_name("Service"), Arc::new({ |     iface_map.insert(iface_name("Service"), Arc::new( | ||||||
|         use ifaces::Service::*; |  | ||||||
|  |  | ||||||
|         f.interface(iface_name("Service"), ()) |         f.interface(iface_name("Service"), ()) | ||||||
|             // org_ddnss_sfs_mc_Service::start() |             .add_m(f.method_sync("Start", (), handle_iface!(m, |data| { | ||||||
|             .add_m(f.method_sync("Start", (), handle!(m, |data| async move { |                 data.read().await | ||||||
|                 org_ddnss_sfs_mc_Service::start(&mut *data.write().await) |                     .start().await | ||||||
|                     .map(|_| vec![m.msg.method_return()]) |                     .map(|_| vec![m.msg.method_return()]) | ||||||
|                     .map_err(|err| MethodErr::failed(&err)) |                     .map_err(|err| MethodErr::failed(&err)) | ||||||
|             }))) |             }))) | ||||||
|             // org_ddnss_sfs_mc_Service::stop() |             .add_m(f.method_sync("Stop", (), handle_iface!(m, |data| { | ||||||
|             .add_m(f.method_sync("Stop", (), handle!(m, |data| async move { |                 data.read().await | ||||||
|                 org_ddnss_sfs_mc_Service::stop(&mut *data.write().await) |                     .stop().await | ||||||
|                     .map(|_| vec![m.msg.method_return()]) |                     .map(|_| vec![m.msg.method_return()]) | ||||||
|                     .map_err(|err| MethodErr::failed(&err)) |                     .map_err(|err| MethodErr::failed(&err)) | ||||||
|             }))) |             }))) | ||||||
|             // org_ddnss_sfs_mc_Service::restart() |             .add_m(f.method_sync("Restart", (), handle_iface!(m, |data| { | ||||||
|             .add_m(f.method_sync("Restart", (), handle!(m, |data| async move { |                 data.read().await | ||||||
|                 org_ddnss_sfs_mc_Service::restart(&mut *data.write().await) |                     .restart().await | ||||||
|                     .map(|_| vec![m.msg.method_return()]) |                     .map(|_| vec![m.msg.method_return()]) | ||||||
|                     .map_err(|err| MethodErr::failed(&err)) |                     .map_err(|err| MethodErr::failed(&err)) | ||||||
|             }))) |             }))) | ||||||
|             // org_ddnss_sfs_mc_Service::is_running() |             .add_m(f.method_sync("IsRunning", (), handle_iface!(m, |data| { | ||||||
|             .add_m(f.method_sync("IsRunning", (), handle!(m, |data| async move { |  | ||||||
|                 Ok(vec![m.msg.method_return().append1( |                 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")) |             })).out_arg("b")) | ||||||
|     })); |     )); | ||||||
|  |  | ||||||
|     // Add interfaces to object paths in a new tree |     // Add interfaces to object paths in a new tree | ||||||
|     let tree = f.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() |             .introspectable() | ||||||
|             .add(iface_map[&iface_name("Service")].clone()) |             .add(iface_map[&iface_name("Service")].clone()) | ||||||
|         ); |         ); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user