向nonebot对齐了一下日志输出

This commit is contained in:
2026-07-05 18:39:57 +08:00
parent 355cb36025
commit 1a645f9177
10 changed files with 260 additions and 169 deletions
+5 -2
View File
@@ -11,10 +11,13 @@ crate-type = ["rlib", "cdylib"]
common = { path = "../common" }
assets-updater = { path = "../assets-updater" }
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "sync"] }
tokio = { workspace = true, features = ["macros", "rt", "sync", ] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
log = { workspace = true }
anyhow = { workspace = true }
simplelog = "0.12.2"
fern = { workspace = true, features = ["colored"] }
futures = { workspace = true }
mimalloc = { workspace = true, default-features = false }
chrono = { workspace = true }
+2
View File
@@ -20,6 +20,7 @@ impl Config {
pub struct OptConfig {
pub proxy: Option<String>,
pub ghp: Option<String>,
pub log_level: Option<String>,
}
impl OptConfig {
@@ -27,6 +28,7 @@ impl OptConfig {
Self {
proxy: None,
ghp: None,
log_level: None,
}
}
}
+33 -24
View File
@@ -3,15 +3,19 @@ use crate::{ending_point, OPT_CONFIG, STORE, UA};
use anyhow::anyhow;
use reqwest::Client;
use serde::Deserialize;
use std::sync::OnceLock;
const COMMIT_URL: &str = "https://api.github.com/repos/kotori8823/sekai-master-db/commits?path=events.json&per_page=1";
const COMMIT_URL: &str =
"https://api.github.com/repos/kotori8823/sekai-master-db/commits?path=events.json&per_page=1";
#[derive(Debug, Deserialize)]
struct CommitResponse {
sha: String,
}
fn http_client() -> anyhow::Result<Client> {
static HTTP_CLIENT: OnceLock<Client> = OnceLock::new();
fn new_http_client() -> anyhow::Result<Client> {
let mut builder = Client::builder();
if let Ok(opt_conf) = &OPT_CONFIG.read() {
if let Some(proxy) = &opt_conf.proxy {
@@ -32,6 +36,17 @@ fn http_client() -> anyhow::Result<Client> {
Ok(builder.build()?)
}
fn http_client() -> anyhow::Result<&'static Client> {
let is_init = { HTTP_CLIENT.get().is_some() };
if !is_init {
let client = new_http_client()?;
HTTP_CLIENT
.set(client)
.map_err(|_| anyhow!("Failed to set HTTP client"))?;
}
Ok(HTTP_CLIENT.get().unwrap())
}
pub async fn fetch_last_hash() -> anyhow::Result<String> {
let resp = http_client()?.get(COMMIT_URL).send().await?;
let text = resp.text().await?;
@@ -46,30 +61,24 @@ pub fn raw_url(path: &str) -> String {
pub async fn sync_data() -> anyhow::Result<()> {
let client = http_client()?;
let events_cards = &client
.get(raw_url("eventCards.json"))
.send()
.await?
.text()
.await?;
let (events_cards, event_stories, card_episodes) = tokio::try_join!(
client.get(raw_url("eventCards.json")).send().await?.text(),
client
.get(raw_url("eventStories.json"))
.send()
.await?
.text(),
client
.get(raw_url("cardEpisodes.json"))
.send()
.await?
.text()
)?;
ending_point!(ok);
let event_stories = &client
.get(raw_url("eventStories.json"))
.send()
.await?
.text()
.await?;
ending_point!(ok);
let card_episodes = &client
.get(raw_url("cardEpisodes.json"))
.send()
.await?
.text()
.await?;
let mut store = STORE.write().map_err(|e| anyhow!("{}", e))?;
store.set_event_cards(serde_json::from_str(events_cards)?);
store.set_event_stories(serde_json::from_str(event_stories)?);
store.set_card_episodes(serde_json::from_str(card_episodes)?);
store.set_event_cards(serde_json::from_str(&events_cards)?);
store.set_event_stories(serde_json::from_str(&event_stories)?);
store.set_card_episodes(serde_json::from_str(&card_episodes)?);
Ok(())
}
+61 -17
View File
@@ -1,3 +1,8 @@
use mimalloc::MiMalloc;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
mod data;
mod download;
mod github;
@@ -7,14 +12,15 @@ mod store;
use crate::data::{Config, FetchResult, OptConfig, State};
use crate::main_loop::main;
use crate::store::Store;
use log::{LevelFilter, debug, error};
use simplelog::{ColorChoice, TermLogger, TerminalMode};
use chrono::Local;
use fern::colors::{Color, ColoredLevelConfig};
use log::{LevelFilter, debug};
use std::ffi::{CStr, CString, c_char, c_int};
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{OnceLock, RwLock};
use std::thread;
use std::time::Duration;
use tokio::runtime::Runtime;
use tokio::sync::Notify;
pub static CONFIG: OnceLock<Config> = OnceLock::new();
@@ -27,23 +33,23 @@ pub static KILLING: AtomicBool = AtomicBool::new(false);
pub static NOTIFY: Notify = Notify::const_new();
#[macro_export]
macro_rules! ending_point {
(ok)=>{
(ok) => {
ending_point!(Err(anyhow!("Killed")))
};
(ok_with $b:block)=>{
if $crate::KILLING.load(::std::sync::atomic::Ordering::SeqCst){
(ok_with $b:block) => {
if $crate::KILLING.load(::std::sync::atomic::Ordering::SeqCst) {
$b;
return Err(anyhow!("Killed"));
}
};
($ret: expr) => {
if $crate::KILLING.load(::std::sync::atomic::Ordering::SeqCst){
if $crate::KILLING.load(::std::sync::atomic::Ordering::SeqCst) {
return $ret;
}
};
() => {
ending_point!(())
}
};
}
pub const UA: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36";
@@ -73,6 +79,7 @@ macro_rules! def_opt_str_setter {
def_opt_str_setter!(proxy);
def_opt_str_setter!(ghp);
def_opt_str_setter!(log_level);
/// # Safety
#[unsafe(no_mangle)]
@@ -96,17 +103,53 @@ pub unsafe extern "C" fn boot(
.init()
.expect("Failed to initialize state");
*STORE.write().unwrap() = Store::load();
// TermLogger::init(
// LevelFilter::Debug,
// simplelog::Config::default(),
// TerminalMode::Mixed,
// ColorChoice::Auto,
// )
// .unwrap();
if let Some(log_level) = &OPT_CONFIG.read().unwrap().log_level {
match LevelFilter::from_str(log_level) {
Ok(level) => {
let name = CONFIG.get().unwrap().name.clone();
let colors = ColoredLevelConfig::new()
.info(Color::BrightWhite)
.debug(Color::BrightBlue)
.warn(Color::Yellow)
.error(Color::Red);
const GREEN: &str = "\x1b[32m"; // 绿色
const CYAN_UNDERLINE: &str = "\x1b[36;4m"; // 青色 + 下划线
const RESET: &str = "\x1b[0m"; // 重置颜色
let _ = fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"{green}{time}{reset} [{level}] {cyan_ul}{name}{reset} | {message}",
green = GREEN,
time = Local::now().format("%m-%d %H:%M:%S"),
reset = RESET,
level = colors.color(record.level()), // fern 处理等级颜色
cyan_ul = CYAN_UNDERLINE,
name = name,
message = message
))
})
.level(level)
.chain(std::io::stdout())
.apply()
.is_err_and(|e| {
eprintln!("Failed to initialize logger: {}", e);
true
});
}
Err(err) => {
eprintln!("Invalid log level: '{}': {}", log_level, err);
}
}
}
let handle = thread::Builder::new()
.name("sekai-sync-lib".to_string())
.spawn(|| {
let rt = Runtime::new().expect("Failed to create Tokio runtime");
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
rt.block_on(keep_loop());
});
@@ -117,12 +160,13 @@ pub unsafe extern "C" fn boot(
0
}
Err(error) => {
error!("{}", error);
eprintln!("{}", error);
-1
}
}
}
/// # Safety
#[unsafe(no_mangle)]
pub unsafe extern "C" fn stop() -> c_int {
if let Some(thread) = THREAD.write().unwrap().take() {
+46 -28
View File
@@ -4,7 +4,9 @@ use crate::github::{fetch_last_hash, sync_data};
use crate::{CONFIG, PROVIDE, STATE, STORE, ending_point};
use anyhow::anyhow;
use assets_updater::core::export_pipeline::find_files;
use log::debug;
use futures::StreamExt;
use futures::stream::FuturesUnordered;
use log::{debug, info};
use std::fs;
pub async fn main() -> anyhow::Result<()> {
@@ -55,45 +57,61 @@ pub async fn main() -> anyhow::Result<()> {
let event = event.unwrap();
// event updated
info!("Event updated to {}", event.event_id);
let cards = store.find_cards_by_event(event.event_id);
drop(store);
ending_point!(ok);
let downloader = Downloader::new().await?;
ending_point!(ok);
let mut images = Vec::new();
let cache_dir = CONFIG.get().unwrap().cache_dir();
for card in cards {
ending_point!(ok);
debug!(
"Pulling card {} from bundle {}",
card.card_id, card.assetbundle_name
);
let dir = downloader
.pull(&format!("character/member/{}", card.assetbundle_name))
.await?;
ending_point!(ok_with {
fs::remove_dir_all(dir.0)?;
});
let files = find_files(&dir.0)?;
for file in files {
let mut futures = FuturesUnordered::new();
for card in &cards {
futures.push(async {
ending_point!(ok);
debug!(
"Pulling card {} from bundle {}",
card.card_id, card.assetbundle_name
);
let dir = downloader
.pull(&format!("character/member/{}", card.assetbundle_name))
.await?;
ending_point!(ok_with {
fs::remove_dir_all(dir.0)?;
});
if file.is_file() && file.extension().map(|ext| ext == "png").unwrap_or(false) {
let new_filename = format!(
"{}_{}",
card.assetbundle_name,
file.file_name().unwrap().to_str().unwrap()
);
let new_file = cache_dir.join(new_filename);
fs::copy(&file, &new_file)?;
fs::remove_file(&file)?;
images.push(new_file);
let files = find_files(&dir.0)?;
let mut local_images = Vec::new();
for file in files {
ending_point!(ok_with {
fs::remove_dir_all(dir.0)?;
});
if file.is_file() && file.extension().map(|ext| ext == "png").unwrap_or(false) {
let new_filename = format!(
"{}_{}",
card.assetbundle_name,
file.file_name().unwrap().to_str().unwrap()
);
let new_file = cache_dir.join(new_filename);
fs::copy(&file, &new_file)?;
fs::remove_file(&file)?;
local_images.push(new_file);
}
}
}
fs::remove_dir_all(dir.0)?;
fs::remove_dir_all(dir.0)?;
Ok::<_, anyhow::Error>((card.card_id, local_images))
});
}
let mut images = Vec::new();
while let Some(res) = futures.next().await {
images.push(res?);
}
images.sort_by(|a, b| b.0.cmp(&a.0)); // 大在前
let images: Vec<_> = images.into_iter().flat_map(|(_, sub)| sub).collect();
info!(
"In event {} collected {} images",
event.event_id,
images.len()
);
*PROVIDE.write().map_err(|e| anyhow!("{}", e))? = Some(FetchResult {
event_id: event.event_id,
card_paths: images,