puniyu_core 0.7.8

puniyu 的核心模块,包含基础设施和通用功能
Documentation
use crate::{
	VERSION, common,
	logger::log_init,
	logger::{OwoColorize, debug, error, info},
};
use bytes::Bytes;
use convert_case::{Case, Casing};
use figlet_rs::FIGfont;
pub use puniyu_common::APP_NAME;
use puniyu_common::path::{DATA_DIR, PLUGIN_DATA_DIR, PLUGIN_DIR, RESOURCE_DIR, WORKING_DIR};
use puniyu_config::{init_config, start_config_watcher};
use puniyu_event::init_event_bus;
use puniyu_registry::plugin::PluginType;
use puniyu_registry::{HookRegistry, adapter::AdapterRegistry, plugin::PluginRegistry};
use puniyu_types::adapter::AdapterBuilder;
use puniyu_types::hook::{HookType, StatusType};
use puniyu_types::plugin::PluginBuilder;
use std::env::current_dir;
use std::path::{Path, PathBuf};
use std::{env, env::consts::DLL_EXTENSION, io};
use tokio::{fs, signal};

pub struct AppBuilder {
	app_name: String,
	app_logo: Bytes,
	working_dir: PathBuf,
	plugins: Vec<&'static dyn PluginBuilder>,
	adapters: Vec<&'static dyn AdapterBuilder>,
}

impl Default for AppBuilder {
	fn default() -> Self {
		Self {
			app_name: String::from("puniyu"),
			app_logo: Bytes::from_static(include_bytes!(concat!(
				env!("CARGO_MANIFEST_DIR"),
				"/assets/logo.png"
			))),
			working_dir: current_dir().unwrap(),
			plugins: Vec::new(),
			adapters: Vec::new(),
		}
	}
}

impl AppBuilder {
	pub fn new() -> Self {
		Self::default()
	}

	pub fn with_name(mut self, name: impl Into<String>) -> Self {
		self.app_name = name.into();
		self
	}

	pub fn with_logo(mut self, logo: impl Into<Bytes>) -> Self {
		self.app_logo = logo.into();
		self
	}

	pub fn with_working_dir(mut self, path: &Path) -> Self {
		self.working_dir = path.to_path_buf();
		self
	}

	pub fn with_plugin(mut self, plugin: &'static dyn PluginBuilder) -> Self {
		self.plugins.push(plugin);
		self
	}

	pub fn with_adapter(mut self, adapter: &'static dyn AdapterBuilder) -> Self {
		self.adapters.push(adapter);
		self
	}

	pub fn build(self) -> App {
		WORKING_DIR.get_or_init(|| self.working_dir.clone());
		APP_NAME.get_or_init(|| self.app_name.clone());
		#[cfg(feature = "server")]
		{
			puniyu_server::LOGO.get_or_init(|| self.app_logo.clone());
		}
		App { builder: self }
	}
}

pub struct App {
	builder: AppBuilder,
}

impl App {
	pub fn builder() -> AppBuilder {
		AppBuilder::new()
	}
	pub async fn run(&self) -> io::Result<()>{
		use crate::common::format_duration;
		use std::time::Duration;
		print_start_log();
		init_config();
		#[cfg(feature = "logger")]
		{
			log_init();
		}
		let start_time = std::time::Instant::now();
		let app_name = APP_NAME.get().unwrap();
		init_app(&self.builder.plugins, &self.builder.adapters).await;
		info!("钩子数量: {}", HookRegistry::all().len());
		start_config_watcher();
		let duration_str = format_duration(start_time.elapsed());
		execute_hooks(StatusType::Start).await;
		info!(
			"{} 初始化完成,耗时: {}",
			app_name.to_case(Case::Lower).fg_rgb::<64, 224, 208>(),
			duration_str.fg_rgb::<255, 127, 80>()
		);

		init_event_bus();
		#[cfg(feature = "server")]
		{
			use crate::config::Config;
			use std::net::IpAddr;
			let logo_path = RESOURCE_DIR.join("logo.png");
			if !logo_path.exists() {
				fs::write(&logo_path, &self.builder.app_logo).await.expect("写入logo失败");
			}
			let config = Config::app();
			let config = config.server();
			let host = IpAddr::V4(config.host().parse().unwrap());
			let port = config.port();
			puniyu_server::run_server_spawn(Some(host), Some(port));
		}

		signal::ctrl_c().await?;
		debug!("接收到中断信号,正在关闭...");
		execute_hooks(StatusType::Stop).await;
		info!(
				"{} 本次运行时间: {}",
				app_name.to_case(Case::Lower).fg_rgb::<64, 224, 208>(),
				format_duration(Duration::from_secs(common::uptime())).fg_rgb::<255, 127, 80>()
			);
		Ok(())
	}
}

async fn init_app(
	plugins: &[&'static dyn PluginBuilder],
	adapters: &[&'static dyn AdapterBuilder],
) {
	if !DATA_DIR.as_path().exists() {
		fs::create_dir(DATA_DIR.as_path()).await.unwrap();
	}

	if !RESOURCE_DIR.as_path().exists() {
		fs::create_dir(RESOURCE_DIR.as_path()).await.unwrap();
	}
	init_plugin(plugins).await;
	init_adapter(adapters).await;
}

async fn init_plugin(plugins: &[&'static dyn PluginBuilder]) {
	if !PLUGIN_DIR.as_path().exists() {
		fs::create_dir(PLUGIN_DIR.as_path()).await.expect("Failed to create plugin directory");
	}

	if !PLUGIN_DATA_DIR.as_path().exists() {
		fs::create_dir(PLUGIN_DATA_DIR.as_path())
			.await
			.expect("Failed to create plugin data directory");
	}
	let mut plugins_list =
		plugins.iter().map(|p| PluginType::Builder(*p)).collect::<Vec<PluginType>>();

	let pattern = PLUGIN_DIR.join(format!("*.{}", DLL_EXTENSION));
	if let Ok(paths) = glob::glob(pattern.to_str().expect("Failed to parse plugin path")) {
		for entry in paths.filter_map(Result::ok) {
			plugins_list.push(entry.into());
		}
	}

	PluginRegistry::load_plugins(plugins_list).await.unwrap_or_else(|e| {
		error!("插件加载失败: {:?}", e);
	});

	let plugin_count = PluginRegistry::get_all_plugins().len();
	debug!(
		"{}: {} {}",
		"共加载".fg_rgb::<135, 206, 250>(),
		plugin_count,
		"个插件".fg_rgb::<135, 206, 250>()
	)
}

async fn init_adapter(adapters: &[&'static dyn AdapterBuilder]) {
	AdapterRegistry::load_adapters(adapters).await.unwrap_or_else(|e| {
		error!("适配器加载失败: {:?}", e);
	});

	let adapter_count = AdapterRegistry::adapters().len();

	debug!(
		"{}: {} {}",
		"共加载".fg_rgb::<135, 206, 250>(),
		adapter_count,
		"个适配器".fg_rgb::<135, 206, 250>()
	)
}

fn print_start_log() {
	let app_name = APP_NAME.get().unwrap();
	let app_name = app_name.to_case(Case::Pascal);
	if let Ok(standard_font) = FIGfont::standard()
		&& let Some(art_text) = standard_font.convert(app_name.as_str())
	{
		println!("{}", art_text);
	} else {
		println!("{}", app_name);
	}

	println!("{} 启动中...", app_name.to_case(Case::Lower));
	println!("版本: {}", VERSION);
	println!("Github: {}", env!("CARGO_PKG_REPOSITORY"));
}

async fn execute_hooks(status_type: StatusType) {
	let mut hooks = HookRegistry::all()
		.into_iter()
		.filter(|x| match x.builder.r#type() {
			HookType::Status(status) => status == status_type,
			_ => false,
		})
		.collect::<Vec<_>>();
	hooks.sort_unstable_by_key(|a| a.builder.rank());

	for hook in hooks {
		if let Err(e) = hook.builder.run(None).await {
			match status_type {
				StatusType::Start => error!("启动hook钩子执行失败: {}", e),
				StatusType::Stop => error!("关闭hook钩子执行失败: {}", e),
			}
		}
		HookRegistry::unregister(hook.index);
	}
}