add plugin, comment code and setup ci

This commit is contained in:
MedzikUser 2022-06-04 20:10:21 +02:00
parent a91b0cb788
commit 4f002bb322
No known key found for this signature in database
GPG Key ID: A5FAC1E185C112DB
13 changed files with 193 additions and 70 deletions

48
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: Rust
on:
push:
branches:
- main
paths-ignore:
- '*.md'
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
build:
strategy:
fail-fast: false
matrix:
rust: [stable, nightly]
name: ${{ matrix.rust }}
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: cargo build
uses: actions-rs/cargo@v1
with:
command: build
args: --all
- name: cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings --no-deps

5
.gitignore vendored
View File

@ -1 +1,6 @@
# Cargo
/target /target
# Plugins
/plugins
*.so

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"yaml.schemas": {
"https://json.schemastore.org/github-workflow.json": "file:///home/medzik/git/MedzikUser/rust/servers/.github/workflows/build.yml"
}
}

8
Cargo.lock generated
View File

@ -105,6 +105,14 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "plugin_test"
version = "0.1.0"
dependencies = [
"async-trait",
"servers",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.39" version = "1.0.39"

View File

@ -1,3 +1,6 @@
[workspace]
members = ["plugin_test"]
[package] [package]
name = "servers" name = "servers"
version = "0.1.0" version = "0.1.0"

11
plugin_test/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "plugin_test"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["dylib"]
[dependencies]
async-trait = "0.1.56"
servers = { path = ".." }

36
plugin_test/src/lib.rs Normal file
View File

@ -0,0 +1,36 @@
use async_trait::async_trait;
use servers::{Client, Command, CommandManagerType, CommandRegistrar, Plugin, PluginRegistrar};
struct PluginTest;
#[async_trait]
impl Plugin for PluginTest {
fn name(&self) -> &'static str {
"test"
}
async fn on_plugin_load(&self) {}
async fn on_plugin_unload(&self) {}
}
#[async_trait]
impl Command for PluginTest {
fn name(&self) -> &'static str {
"/test"
}
fn help(&self) -> &'static str {
"test command"
}
async fn execute(&self, client: &mut Client, _args: Vec<&str>, _commands: &CommandManagerType) {
client.send("content").await.expect("send message")
}
}
#[no_mangle]
pub fn plugin_entry(registrar: &mut dyn PluginRegistrar, command: &mut dyn CommandRegistrar) {
registrar.register_plugin(Box::new(PluginTest));
command.register_plugin(Box::new(PluginTest));
}

View File

@ -17,18 +17,28 @@ impl Client {
/// Read message/buffer from Client /// Read message/buffer from Client
pub async fn read(&mut self) -> anyhow::Result<String> { pub async fn read(&mut self) -> anyhow::Result<String> {
// allocate an empty buffer of length 1024 bytes
let mut buf = [0; 1024]; let mut buf = [0; 1024];
// read buffer from stream
self.stream.read(&mut buf).await?; self.stream.read(&mut buf).await?;
let encoded = String::from_utf8(buf.to_vec())?.replace('\0', ""); // encode &[u8] to a String and replace null spaces (empty `\0` bytes)
let decoded = String::from_utf8(buf.to_vec())?.replace('\0', "");
Ok(encoded) Ok(decoded)
} }
/// Send message to Client /// Send message to Client
pub async fn send(&mut self, content: &str) -> anyhow::Result<()> { pub async fn send(&mut self, content: &str) -> anyhow::Result<()> {
self.stream.write_all(format!("{content}\n\r").as_bytes()).await?; // add a new line at the end of the content
let content = format!("{content}\n\r");
// send message
self.stream
.write_all(content.as_bytes())
.await?;
Ok(()) Ok(())
} }
} }

View File

@ -2,21 +2,16 @@ use std::{any::Any, sync::Arc};
use async_trait::async_trait; use async_trait::async_trait;
use crate::{Client, commands}; use crate::Client;
#[async_trait] #[async_trait]
pub trait Command: Any + Send + Sync { pub trait Command: Any + Send + Sync {
/// Command name /// Command name
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
/// Command help message /// Help message of this command
fn help(&self) -> &'static str; fn help(&self) -> &'static str;
/// Command function /// Command function
async fn execute( async fn execute(&self, client: &mut Client, args: Vec<&str>, commands: &CommandManagerType);
&self,
client: &mut Client,
args: Vec<&str>,
plugins: &CommandManagerType,
);
} }
pub struct CommandManager { pub struct CommandManager {
@ -38,20 +33,4 @@ impl Default for CommandManager {
} }
} }
pub type CommandManagerType = Vec<Arc<Box<dyn Command>>>; pub type CommandManagerType = Arc<Vec<Box<dyn Command>>>;
pub fn register_commands() -> CommandManagerType {
let mut command_manager = CommandManager::new();
for command in commands::register_commands() {
command_manager.commands.push(command)
}
// create Arc in Vector
let mut commands: CommandManagerType = Vec::new();
for command in command_manager.commands {
commands.push(Arc::new(command))
}
commands
}

View File

@ -20,7 +20,7 @@ impl Command for CommandHelp {
_args: Vec<&str>, _args: Vec<&str>,
commands: &CommandManagerType, commands: &CommandManagerType,
) { ) {
for command in commands { for command in commands.iter() {
client client
.send(&format!("{} - {}", command.name(), command.help())) .send(&format!("{} - {}", command.name(), command.help()))
.await .await

View File

@ -1,3 +1,5 @@
#![allow(clippy::vec_init_then_push)]
mod help; mod help;
pub use help::*; pub use help::*;

View File

@ -1,4 +1,4 @@
use servers::{loader, Client, PluginManagerType, register_commands, CommandManagerType}; use servers::{loader, Client, CommandManagerType};
use tokio::{io::AsyncWriteExt, net::TcpListener}; use tokio::{io::AsyncWriteExt, net::TcpListener};
#[tokio::main] #[tokio::main]
@ -6,24 +6,23 @@ async fn main() -> anyhow::Result<()> {
// listen Tcp server // listen Tcp server
let listener = TcpListener::bind("0.0.0.0:9999").await?; let listener = TcpListener::bind("0.0.0.0:9999").await?;
// load plugins println!("Tcp server started at: {}", listener.local_addr()?);
let plugin_manager = loader()?;
// load command // load plugins and commands
let commands_manager = register_commands(); let (_plugin_manager, commands_manager) = loader()?;
// Accepts a new incoming connection from this listener. // Accepts a new incoming connection from this listener.
while let Ok((stream, _address)) = listener.accept().await { while let Ok((stream, _address)) = listener.accept().await {
let client = Client::new(stream); let client = Client::new(stream);
// handle client connection in new thread // handle client connection in new thread
tokio::spawn(handle_connection(client, (*plugin_manager).to_vec(), (*commands_manager).to_vec())); tokio::spawn(handle_connection(client, commands_manager.clone()));
} }
Ok(()) Ok(())
} }
async fn handle_connection(mut client: Client, plugins: PluginManagerType, commands: CommandManagerType) -> anyhow::Result<()> { async fn handle_connection(mut client: Client, commands: CommandManagerType) -> anyhow::Result<()> {
println!("New Client: {:?}", client.stream.peer_addr()?); println!("New Client: {:?}", client.stream.peer_addr()?);
loop { loop {
@ -36,26 +35,23 @@ async fn handle_connection(mut client: Client, plugins: PluginManagerType, comma
// get command from args // get command from args
let cmd = args[0]; let cmd = args[0];
println!("{:?}", &args); // search if a command exists
for command in commands.iter() {
for command in &commands { // if this is the entered command
if cmd == command.name() { if cmd == command.name() {
println!("s"); // execute command
command.execute(&mut client, args[1..args.len()].to_vec(), &commands).await; command
break .execute(&mut client, args[1..args.len()].to_vec(), &commands)
} .await;
}
// search command in plugins // don't search for more commands
for plugin in &plugins { break;
// if command found execute plugin
if cmd == plugin.command() {
plugin.execute(&mut client, args[1..args.len()].to_vec())
} }
} }
// if an I/O or EOF error, abort the connection // if an I/O or EOF error, abort the connection
if client.stream.flush().await.is_err() { if client.stream.flush().await.is_err() {
// terminate connection
break; break;
} }
} }

View File

@ -1,27 +1,22 @@
use std::{any::Any, fs, sync::Arc}; use std::{any::Any, fs, sync::Arc};
use async_trait::async_trait;
use libloading::{Library, Symbol}; use libloading::{Library, Symbol};
use log::{debug, trace}; use log::{debug, trace};
use crate::Client; use crate::{commands, Command, CommandManager, CommandManagerType};
/// A plugin which allows you to add extra functionality. /// A plugin which allows you to add extra functionality.
#[async_trait]
pub trait Plugin: Any + Send + Sync { pub trait Plugin: Any + Send + Sync {
/// Get a name describing the `Plugin`. /// Get a name describing the `Plugin`.
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
/// A function that runs immediately after plugin loading. /// A function that runs immediately after plugin loading.
/// Usually used for initialization. /// Usually used for initialization.
fn on_plugin_load(&self); async fn on_plugin_load(&self);
/// A function that runs immediately before the plugin is unloaded. /// A function that runs immediately before the plugin is unloaded.
/// Use this if you want to do any cleanup. /// Use this if you want to do any cleanup.
fn on_plugin_unload(&self); async fn on_plugin_unload(&self);
/// Plugin command.
/// For example: `/command`
fn command(&self) -> &'static str;
/// Help message of this command.
fn help(&self) -> &'static str;
/// The function will be executed, when sending plugin command.
fn execute(&self, client: &mut Client, args: Vec<&str>);
} }
pub struct PluginManager { pub struct PluginManager {
@ -39,12 +34,12 @@ impl PluginManager {
/// Unload all plugins and loaded plugin libraries, making sure to fire /// Unload all plugins and loaded plugin libraries, making sure to fire
/// their `on_plugin_unload()` methods so they can do any necessary cleanup. /// their `on_plugin_unload()` methods so they can do any necessary cleanup.
pub fn unload(&mut self) { pub async fn unload(&mut self) {
debug!("Unloading plugins"); debug!("Unloading plugins");
for plugin in self.plugins.drain(..) { for plugin in self.plugins.drain(..) {
trace!("Firing on_plugin_unload for {:?}", plugin.name()); trace!("Firing on_plugin_unload for {:?}", plugin.name());
plugin.on_plugin_unload(); plugin.on_plugin_unload().await;
} }
} }
} }
@ -65,9 +60,19 @@ impl PluginRegistrar for PluginManager {
} }
} }
pub type PluginManagerType = Vec<Arc<Box<dyn Plugin>>>; pub trait CommandRegistrar {
fn register_plugin(&mut self, command: Box<dyn Command>);
}
pub fn loader() -> anyhow::Result<PluginManagerType> { impl CommandRegistrar for CommandManager {
fn register_plugin(&mut self, command: Box<dyn Command>) {
self.commands.push(command)
}
}
pub type PluginManagerType = Arc<Vec<Box<dyn Plugin>>>;
pub fn loader() -> anyhow::Result<(PluginManagerType, CommandManagerType)> {
// get path to .so lib from command argument // get path to .so lib from command argument
let config_dir = "./plugins"; let config_dir = "./plugins";
let paths = fs::read_dir(config_dir)?; let paths = fs::read_dir(config_dir)?;
@ -75,6 +80,14 @@ pub fn loader() -> anyhow::Result<PluginManagerType> {
// create a plugin manager where all loaded plugins will be located // create a plugin manager where all loaded plugins will be located
let mut plugin_manager = PluginManager::new(); let mut plugin_manager = PluginManager::new();
// create a command manager where located all commands
let mut command_manager = CommandManager::new();
// register default commands
for command in commands::register_commands() {
command_manager.commands.push(command)
}
// for all plugin in directory // for all plugin in directory
for path in paths { for path in paths {
// get library file path // get library file path
@ -87,19 +100,26 @@ pub fn loader() -> anyhow::Result<PluginManagerType> {
let lib = Box::leak(Box::new(Library::new(path)?)); let lib = Box::leak(Box::new(Library::new(path)?));
// get `plugin_entry` from library // get `plugin_entry` from library
let func: Symbol<unsafe extern "C" fn(&mut dyn PluginRegistrar) -> ()> = let func: Symbol<
lib.get(b"plugin_entry")?; unsafe extern "C" fn(&mut dyn PluginRegistrar, &mut dyn CommandRegistrar) -> (),
> = lib.get(b"plugin_entry")?;
// execute initial function // execute initial function
func(&mut plugin_manager); func(&mut plugin_manager, &mut command_manager);
} }
} }
// create Arc in Vector // create Arc in Vector
let mut plugins: PluginManagerType = Vec::new(); let mut commands = Vec::new();
for plugin in plugin_manager.plugins { for command in command_manager.commands {
plugins.push(Arc::new(plugin)) commands.push(command)
} }
Ok(plugins) // create Arc in Vector
let mut plugins = Vec::new();
for plugin in plugin_manager.plugins {
plugins.push(plugin)
}
Ok((Arc::new(plugins), Arc::new(commands)))
} }