Fix #1 by using enums instead of traits

It very much reduces code and complexity.
This commit is contained in:
Elias Schriefer 2020-09-17 22:14:20 +02:00
parent ba5e5ece69
commit f99ee75f98
7 changed files with 165 additions and 248 deletions

View File

@ -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
View 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)+
})
})};
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -1,4 +0,0 @@
#[derive(Debug, Default, Copy, Clone)]
pub struct DaemonService;
#[derive(Debug, Default, Copy, Clone)]
pub struct MinecraftService;

View File

@ -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")
}
}

View File

@ -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())
); );