疑似有点过于代码多了

This commit is contained in:
shenjack 2024-08-18 00:11:13 +08:00
parent d12773981d
commit 5636c8e1d9
Signed by: shenjack
GPG Key ID: 7B1134A979775551
5 changed files with 236 additions and 49 deletions

17
Cargo.lock generated
View File

@ -678,6 +678,7 @@ dependencies = [
"thiserror",
"tokio",
"toml",
"toml_edit",
"tracing",
"tracing-subscriber",
]
@ -1391,9 +1392,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.6"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
dependencies = [
"serde",
]
@ -1713,18 +1714,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.6"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.13"
version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [
"indexmap",
"serde",
@ -2197,9 +2198,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.9"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]

View File

@ -51,3 +51,4 @@ anyhow = { version = "1.0", features = ["backtrace"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["time"] }
thiserror = "1.0.63"
toml_edit = "0.22.20"

View File

@ -60,7 +60,7 @@ pub fn get_func<'py>(
}
}
pub fn verify_plugins() {
pub fn verify_and_reload_plugins() {
let mut need_reload_files: Vec<PathBuf> = Vec::new();
let plugin_path = MainStatus::global_config().py().plugin_path.clone();
@ -77,14 +77,20 @@ pub fn verify_plugins() {
return;
}
info!("file change list: {:?}", need_reload_files);
let exist_plugins = PyStatus::get_map_mut();
for reload_file in need_reload_files {
match PyPlugin::new_from_path(&reload_file) {
Some(plugin) => {
PyStatus::add_file(reload_file.clone(), plugin);
info!("重载 Python 插件: {:?}", reload_file);
}
None => {
warn!("重载 Python 插件: {:?} 失败", reload_file);
if let Some(plugin) = exist_plugins.get_mut(&reload_file) {
plugin.reload_from_file();
event!(Level::INFO, "重载 Python 插件: {:?} 完成", reload_file);
} else {
match PyPlugin::new_from_path(&reload_file) {
Some(plugin) => {
PyStatus::add_file(reload_file.clone(), plugin);
info!("加载 Python 插件: {:?} 完成", reload_file);
}
None => {
warn!("加载 Python 插件: {:?} 失败", reload_file);
}
}
}
}
@ -133,7 +139,7 @@ macro_rules! call_py_func {
/// 执行 new message 的 python 插件
pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Client) {
// 验证插件是否改变
verify_plugins();
verify_and_reload_plugins();
let plugins = PyStatus::get_map();
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
@ -146,7 +152,7 @@ pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Cl
}
pub async fn ica_delete_message_py(msg_id: ica::MessageId, client: &Client) {
verify_plugins();
verify_and_reload_plugins();
let plugins = PyStatus::get_map();
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
@ -161,7 +167,7 @@ pub async fn tailchat_new_message_py(
message: &tailchat::messages::ReceiveMessage,
client: &Client,
) {
verify_plugins();
verify_and_reload_plugins();
let plugins = PyStatus::get_map();
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {

View File

@ -1 +1,109 @@
use std::{path::{Path, PathBuf}, str::FromStr};
use toml_edit::{value, DocumentMut, Key, Table, TomlError, Value};
use crate::py::PyStatus;
/// ```toml
/// # 这个文件是由 shenbot 自动生成的, 请 **谨慎** 修改
/// # 请不要修改这个文件, 除非你知道你在做什么
///
/// [plugins]
/// "xxxxxxx" = false # 被禁用的插件
/// "xxxxxxx" = true # 被启用的插件
/// ```
#[derive(Debug, Clone)]
pub struct PluginConfigFile {
pub data: DocumentMut,
}
const CONFIG_KEY: &str = "plugins";
pub const CONFIG_FILE_NAME: &str = "/plugins.toml";
pub const DEFAULT_CONFIG: &str = r#"
# shenbot , ****
# ,
[plugins]
"#;
impl PluginConfigFile {
pub fn from_str(data: &str) -> Result<Self, TomlError> {
let data = DocumentMut::from_str(data)?;
Ok(Self { data })
}
pub fn from_config_path(path: &Path) -> anyhow::Result<Self> {
let config_path = path.join(CONFIG_FILE_NAME);
if !config_path.exists() {
std::fs::write(&config_path, DEFAULT_CONFIG)?;
Ok(Self::from_str(DEFAULT_CONFIG)?)
} else {
let data = std::fs::read_to_string(&config_path)?;
Ok(Self::from_str(&data)?)
}
}
pub fn verify_and_init(&mut self) {
if self.data.get(CONFIG_KEY).is_none() {
self.data.insert_formatted(
&Key::from_str(CONFIG_KEY).unwrap(),
toml_edit::Item::Table(Table::new()),
);
}
}
/// 获取插件状态
/// 默认为 true
pub fn get_status(&self, path: &Path) -> bool {
let path_str = path.to_str().unwrap();
if let Some(item) = self.data.get(CONFIG_KEY) {
if let Some(table) = item.as_table() {
if let Some(item) = table.get(path_str) {
if let Some(bool) = item.as_bool() {
return bool;
}
}
}
}
true
}
/// 设置插件状态
pub fn set_status(&mut self, path: &Path, status: bool) {
self.verify_and_init();
let path_str = path.to_str().unwrap();
let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap();
if table.contains_key(path_str) {
match table.get_mut(path_str).unwrap().as_value_mut() {
Some(value) => *value = Value::from(status),
None => {
table.insert(path_str, value(status));
}
}
} else {
table.insert(path_str, value(status));
}
}
pub fn sync_status_from_config(&mut self) {
let plugins = PyStatus::get_map_mut();
self.verify_and_init();
plugins.iter_mut().for_each(|(path, status)| {
status.enabled = self.get_status(path);
});
}
pub fn sync_status_to_config(&mut self) {
let plugins = PyStatus::get_map();
self.verify_and_init();
let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap();
table.clear();
plugins.iter().for_each(|(path, status)| {
table.insert(path.to_str().unwrap(), value(status.enabled));
});
}
pub fn write_to_file(&self, path: &PathBuf) -> Result<(), std::io::Error> {
std::fs::write(path, self.data.to_string())?;
Ok(())
}
}

View File

@ -2,7 +2,6 @@ pub mod call;
pub mod class;
pub mod config;
use std::fmt::Display;
use std::path::Path;
use std::time::SystemTime;
use std::{collections::HashMap, path::PathBuf};
@ -10,13 +9,14 @@ use std::{collections::HashMap, path::PathBuf};
use colored::Colorize;
use pyo3::prelude::*;
use pyo3::types::PyTuple;
use tracing::{debug, event, info, span, warn, Level};
use tracing::{event, info, span, warn, Level};
use crate::MainStatus;
#[derive(Debug, Clone)]
pub struct PyStatus {
pub files: Option<PyPlugins>,
pub config: Option<config::PluginConfigFile>,
}
pub type PyPlugins = HashMap<PathBuf, PyPlugin>;
@ -28,16 +28,24 @@ impl PyStatus {
if PYSTATUS.files.is_none() {
PYSTATUS.files = Some(HashMap::new());
}
if PYSTATUS.config.is_none() {
let plugin_path = MainStatus::global_config().py().plugin_path;
let mut config =
config::PluginConfigFile::from_config_path(&PathBuf::from(plugin_path))
.unwrap();
config.verify_and_init();
PYSTATUS.config = Some(config);
}
}
}
pub fn add_file(path: PathBuf, plugin: PyPlugin) { Self::get_mut_map().insert(path, plugin); }
pub fn add_file(path: PathBuf, plugin: PyPlugin) { Self::get_map_mut().insert(path, plugin); }
pub fn verify_file(path: &PathBuf) -> bool {
Self::get_map().get(path).map_or(false, |plugin| plugin.verifiy())
}
fn get_map() -> &'static PyPlugins {
pub fn get_map() -> &'static PyPlugins {
unsafe {
match PYSTATUS.files.as_ref() {
Some(files) => files,
@ -49,7 +57,7 @@ impl PyStatus {
}
}
fn get_mut_map() -> &'static mut PyPlugins {
pub fn get_map_mut() -> &'static mut PyPlugins {
unsafe {
match PYSTATUS.files.as_mut() {
Some(files) => files,
@ -61,11 +69,43 @@ impl PyStatus {
}
}
pub fn list_plugins() -> Vec<PathBuf> { Self::get_map().keys().cloned().collect() }
pub fn get_config() -> &'static config::PluginConfigFile {
unsafe {
match PYSTATUS.config.as_ref() {
Some(config) => config,
None => {
Self::init();
PYSTATUS.config.as_ref().unwrap()
}
}
}
}
pub fn get_config_mut() -> &'static mut config::PluginConfigFile {
unsafe {
match PYSTATUS.config.as_mut() {
Some(config) => config,
None => {
Self::init();
PYSTATUS.config.as_mut().unwrap()
}
}
}
}
pub fn get_status(path: &Path) -> bool { Self::get_config().get_status(path) }
// pub fn list_plugins() -> Vec<PathBuf> { Self::get_map().keys().cloned().collect() }
pub fn display() -> String {
let map = Self::get_map();
format!("Python 插件 {{ {} }}", (",".join(map.keys().map(|x| x.to_string()))))
format!(
"Python 插件 {{ {} }}",
map.iter()
.map(|(k, v)| format!("{:?}: {:?}", k, v))
.collect::<Vec<String>>()
.join(", ")
)
}
}
@ -90,6 +130,7 @@ pub struct PyPlugin {
}
impl PyPlugin {
/// 从文件创建一个新的
pub fn new_from_path(path: &PathBuf) -> Option<Self> {
let raw_file = load_py_file(path);
match raw_file {
@ -111,6 +152,34 @@ impl PyPlugin {
}
}
}
/// 从文件更新
pub fn reload_from_file(&mut self) {
let raw_file = load_py_file(&self.file_path);
match raw_file {
Ok(raw_file) => match Self::try_from(raw_file) {
Ok(plugin) => {
self.py_module = plugin.py_module;
self.changed_time = plugin.changed_time;
self.enabled = PyStatus::get_status(self.file_path.as_path());
event!(Level::INFO, "更新 Python 插件文件 {:?} 完成", self.file_path);
}
Err(e) => {
warn!(
"更新 Python 插件文件{:?}: {:?} 失败\n{}",
self.file_path,
e,
get_py_err_traceback(&e)
);
}
},
Err(e) => {
warn!("更新插件 {:?}: {:?} 失败", self.file_path, e);
}
}
}
/// 检查文件是否被修改
pub fn verifiy(&self) -> bool {
match get_change_time(&self.file_path) {
None => false,
@ -175,29 +244,26 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
}
let py_config = py_config.unwrap();
// 先判定一下原来有没有
match module.hasattr(CONFIG_DATA_NAME) {
Ok(true) => {
// get 过来, 后面直接覆盖, 这里用于发个警告
match module.getattr(CONFIG_DATA_NAME) {
Ok(old_config) => {
// 先判断是不是 None, 直接忽略掉 None
// 毕竟有可能有占位
if !old_config.is_none() {
warn!(
"Python 插件 {:?} 的配置文件信息已经存在\n原始内容: {}",
path, old_config
);
}
}
Err(e) => {
if let Ok(true) = module.hasattr(CONFIG_DATA_NAME) {
// get 过来, 后面直接覆盖, 这里用于发个警告
match module.getattr(CONFIG_DATA_NAME) {
Ok(old_config) => {
// 先判断是不是 None, 直接忽略掉 None
// 毕竟有可能有占位
if !old_config.is_none() {
warn!(
"Python 插件 {:?} 的配置文件信息已经存在, 但获取失败:{:?}",
path, e
"Python 插件 {:?} 的配置文件信息已经存在\n原始内容: {}",
path, old_config
);
}
}
Err(e) => {
warn!(
"Python 插件 {:?} 的配置文件信息已经存在, 但获取失败:{:?}",
path, e
);
}
}
_ => {}
}
match module.setattr(CONFIG_DATA_NAME, py_config) {
Ok(()) => Ok(PyPlugin {
@ -266,11 +332,14 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
}
}
pub static mut PYSTATUS: PyStatus = PyStatus { files: None };
pub static mut PYSTATUS: PyStatus = PyStatus {
files: None,
config: None,
};
pub fn load_py_plugins(path: &PathBuf) {
if path.exists() {
event!(Level::INFO, "finding plugins in: {:?}", path);
event!(Level::INFO, "找到位于 {:?} 的插件", path);
// 搜索所有的 py 文件 和 文件夹单层下面的 py 文件
match path.read_dir() {
Err(e) => {
@ -293,6 +362,7 @@ pub fn load_py_plugins(path: &PathBuf) {
} else {
event!(Level::WARN, "插件加载目录不存在: {:?}", path);
}
PyStatus::get_config_mut().sync_status_from_config();
event!(
Level::INFO,
"python 插件目录: {:?} 加载完成, 加载到 {} 个插件",
@ -328,15 +398,16 @@ pub fn load_py_file(path: &PathBuf) -> std::io::Result<RawPyPlugin> {
/// Python 侧初始化
pub fn init_py() {
// 从 全局配置中获取 python 插件路径
let span = span!(Level::INFO, "Init Python Plugin");
let span = span!(Level::INFO, "初始化 python 及其插件.ing");
let _enter = span.enter();
let global_config = MainStatus::global_config().py();
let plugin_path = MainStatus::global_config().py().plugin_path;
event!(Level::INFO, "正在初始化 python");
pyo3::prepare_freethreaded_python();
let plugin_path = PathBuf::from(global_config.plugin_path);
PyStatus::init();
let plugin_path = PathBuf::from(plugin_path);
load_py_plugins(&plugin_path);
event!(Level::DEBUG, "python 插件列表: {}", PyStatus::display());