mirror of
https://github.com/MedzikUser/servers
synced 2024-08-14 23:57:48 +00:00
add plugin, comment code and setup ci
This commit is contained in:
parent
a91b0cb788
commit
4f002bb322
13 changed files with 193 additions and 70 deletions
48
.github/workflows/build.yml
vendored
Normal file
48
.github/workflows/build.yml
vendored
Normal 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
5
.gitignore
vendored
|
@ -1 +1,6 @@
|
||||||
|
# Cargo
|
||||||
/target
|
/target
|
||||||
|
|
||||||
|
# Plugins
|
||||||
|
/plugins
|
||||||
|
*.so
|
||||||
|
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal 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
8
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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
11
plugin_test/Cargo.toml
Normal 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
36
plugin_test/src/lib.rs
Normal 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));
|
||||||
|
}
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![allow(clippy::vec_init_then_push)]
|
||||||
|
|
||||||
mod help;
|
mod help;
|
||||||
|
|
||||||
pub use help::*;
|
pub use help::*;
|
||||||
|
|
36
src/main.rs
36
src/main.rs
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue