Fix #1 by using enums instead of traits
It very much reduces code and complexity.
This commit is contained in:
parent
ba5e5ece69
commit
f99ee75f98
@ -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())
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user