向nonebot对齐了一下日志输出
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user