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
# 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"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "plugin_test"
version = "0.1.0"
dependencies = [
"async-trait",
"servers",
]
[[package]]
name = "proc-macro2"
version = "1.0.39"

View File

@ -1,3 +1,6 @@
[workspace]
members = ["plugin_test"]
[package]
name = "servers"
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
pub async fn read(&mut self) -> anyhow::Result<String> {
// allocate an empty buffer of length 1024 bytes
let mut buf = [0; 1024];
// read buffer from stream
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
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(())
}
}

View File

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

View File

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

View File

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

View File

@ -1,27 +1,22 @@
use std::{any::Any, fs, sync::Arc};
use async_trait::async_trait;
use libloading::{Library, Symbol};
use log::{debug, trace};
use crate::Client;
use crate::{commands, Command, CommandManager, CommandManagerType};
/// A plugin which allows you to add extra functionality.
#[async_trait]
pub trait Plugin: Any + Send + Sync {
/// Get a name describing the `Plugin`.
fn name(&self) -> &'static str;
/// A function that runs immediately after plugin loading.
/// 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.
/// Use this if you want to do any cleanup.
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>);
async fn on_plugin_unload(&self);
}
pub struct PluginManager {
@ -39,12 +34,12 @@ impl PluginManager {
/// Unload all plugins and loaded plugin libraries, making sure to fire
/// 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");
for plugin in self.plugins.drain(..) {
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
let config_dir = "./plugins";
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
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 path in paths {
// get library file path
@ -87,19 +100,26 @@ pub fn loader() -> anyhow::Result<PluginManagerType> {
let lib = Box::leak(Box::new(Library::new(path)?));
// get `plugin_entry` from library
let func: Symbol<unsafe extern "C" fn(&mut dyn PluginRegistrar) -> ()> =
lib.get(b"plugin_entry")?;
let func: Symbol<
unsafe extern "C" fn(&mut dyn PluginRegistrar, &mut dyn CommandRegistrar) -> (),
> = lib.get(b"plugin_entry")?;
// execute initial function
func(&mut plugin_manager);
func(&mut plugin_manager, &mut command_manager);
}
}
// create Arc in Vector
let mut plugins: PluginManagerType = Vec::new();
for plugin in plugin_manager.plugins {
plugins.push(Arc::new(plugin))
let mut commands = Vec::new();
for command in command_manager.commands {
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)))
}