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
|
||||
|
||||
# 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"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "plugin_test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"servers",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.39"
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
[workspace]
|
||||
members = ["plugin_test"]
|
||||
|
||||
[package]
|
||||
name = "servers"
|
||||
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
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>>>;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::vec_init_then_push)]
|
||||
|
||||
mod 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};
|
||||
|
||||
#[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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue