非event卡推送,以及手动查看
This commit is contained in:
+55
-1
@@ -1,6 +1,7 @@
|
||||
use crate::CONFIG;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -41,6 +42,8 @@ pub struct State {
|
||||
pub git_hash: Option<String>,
|
||||
pub master_hash: Option<String>,
|
||||
pub event_id: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub unlinked_cards: Vec<u64>,
|
||||
}
|
||||
impl State {
|
||||
fn load(x: &str) -> Self {
|
||||
@@ -52,6 +55,7 @@ impl State {
|
||||
git_hash: None,
|
||||
master_hash: None,
|
||||
event_id: None,
|
||||
unlinked_cards: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +66,7 @@ impl State {
|
||||
self.git_hash = i.git_hash;
|
||||
self.master_hash = i.master_hash;
|
||||
self.event_id = i.event_id;
|
||||
self.unlinked_cards = i.unlinked_cards;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -199,6 +204,21 @@ impl Card {
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_card_details(&self) -> String {
|
||||
format!(
|
||||
"#{id} {attr}{rarity} {skill} [{prefix}]{name}",
|
||||
id = self.id,
|
||||
attr = self.attr_short_text(),
|
||||
rarity = self.rarity_short_text(),
|
||||
skill = self
|
||||
.skill_id_short()
|
||||
.map(|s| format!("({})", s))
|
||||
.unwrap_or_default(),
|
||||
prefix = self.prefix,
|
||||
name = self.character_name()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -217,7 +237,41 @@ pub struct FetchResultCard {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct FetchResult {
|
||||
pub struct FetchResultEvent {
|
||||
pub event_id: u64,
|
||||
pub cards: Vec<FetchResultCard>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct FetchResultUnlinkedCards {
|
||||
pub cards: Vec<FetchResultCard>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
pub enum FetchResult {
|
||||
Event(FetchResultEvent),
|
||||
UnlinkedCard(FetchResultUnlinkedCards),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
pub enum ApiResult<T>
|
||||
where
|
||||
T: Debug + Clone + Serialize,
|
||||
{
|
||||
Ok(T),
|
||||
Err(String),
|
||||
}
|
||||
|
||||
impl<T> ApiResult<T>
|
||||
where
|
||||
T: Debug + Clone + Serialize,
|
||||
{
|
||||
pub fn from(result: anyhow::Result<T>) -> Self {
|
||||
match result {
|
||||
Ok(v) => Self::Ok(v),
|
||||
Err(e) => Self::Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+71
-4
@@ -1,9 +1,19 @@
|
||||
use crate::{CONFIG, OPT_CONFIG};
|
||||
use crate::data::{Card, FetchResultCard};
|
||||
use crate::github::get_master_data;
|
||||
use crate::{CONFIG, OPT_CONFIG, ending_point};
|
||||
use anyhow::anyhow;
|
||||
use assets_updater::core::asset_execution::AssetExecutionContext;
|
||||
use assets_updater::core::config::{AppConfig, CryptoConfig, ExecutionConfig, RegionConfig, RegionProviderConfig, RegionRuntimeConfig, ToolsConfig};
|
||||
use assets_updater::core::config::{
|
||||
AppConfig, CryptoConfig, ExecutionConfig, RegionConfig, RegionProviderConfig,
|
||||
RegionRuntimeConfig, ToolsConfig,
|
||||
};
|
||||
use assets_updater::core::errors::AssetExecutionError;
|
||||
use assets_updater::core::export_pipeline::find_files;
|
||||
use common::updater::SyncContext;
|
||||
use futures::StreamExt;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use log::debug;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Downloader {
|
||||
@@ -21,8 +31,8 @@ impl Downloader {
|
||||
}
|
||||
let app_config = AppConfig {
|
||||
execution,
|
||||
tools: ToolsConfig{
|
||||
asset_studio_cli_path : Some(CONFIG.get().unwrap().as_path.clone()),
|
||||
tools: ToolsConfig {
|
||||
asset_studio_cli_path: Some(CONFIG.get().unwrap().as_path.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -58,3 +68,60 @@ impl Downloader {
|
||||
self.exec.download(bundle_path, &self.app_config).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn download_cards(cards: &Vec<Card>) -> anyhow::Result<Vec<FetchResultCard>> {
|
||||
if cards.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let downloader = Downloader::new().await?;
|
||||
ending_point!(ok);
|
||||
let cache_dir = CONFIG.get().unwrap().cache_dir();
|
||||
let mut futures = FuturesUnordered::new();
|
||||
for card in cards {
|
||||
futures.push(async {
|
||||
ending_point!(ok);
|
||||
debug!(
|
||||
"Pulling card {} from bundle {}",
|
||||
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)?;
|
||||
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)?;
|
||||
Ok::<_, anyhow::Error>((card.clone(), local_images))
|
||||
});
|
||||
}
|
||||
let mut images = Vec::new();
|
||||
while let Some(res) = futures.next().await {
|
||||
images.push(res?);
|
||||
}
|
||||
images.sort_by(|a, b| a.0.id.cmp(&b.0.id)); // 小在前
|
||||
Ok(images
|
||||
.into_iter()
|
||||
.map(|(card, path)| FetchResultCard {
|
||||
text: card.build_card_details(),
|
||||
path,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
+21
-3
@@ -9,8 +9,8 @@ mod github;
|
||||
mod main_loop;
|
||||
mod store;
|
||||
|
||||
use crate::data::{Config, FetchResult, OptConfig, State};
|
||||
use crate::main_loop::main;
|
||||
use crate::data::{ApiResult, Config, FetchResult, OptConfig, State};
|
||||
use crate::main_loop::{main, refetch_event_cards};
|
||||
use crate::store::Store;
|
||||
use chrono::Local;
|
||||
use fern::colors::{Color, ColoredLevelConfig};
|
||||
@@ -25,12 +25,13 @@ use tokio::sync::Notify;
|
||||
|
||||
pub static CONFIG: OnceLock<Config> = OnceLock::new();
|
||||
pub static OPT_CONFIG: RwLock<OptConfig> = RwLock::new(OptConfig::new());
|
||||
pub static PROVIDE: RwLock<Option<FetchResult>> = RwLock::new(None);
|
||||
pub static PROVIDE: RwLock<Option<Vec<FetchResult>>> = RwLock::new(None);
|
||||
pub static STORE: RwLock<Store> = RwLock::new(Store::new());
|
||||
pub static STATE: RwLock<State> = RwLock::new(State::new());
|
||||
pub static THREAD: RwLock<Option<thread::JoinHandle<()>>> = RwLock::new(None);
|
||||
pub static KILLING: AtomicBool = AtomicBool::new(false);
|
||||
pub static NOTIFY: Notify = Notify::const_new();
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ending_point {
|
||||
(ok) => {
|
||||
@@ -194,6 +195,23 @@ pub unsafe extern "C" fn fetch_data() -> *mut c_char {
|
||||
c_str.into_raw()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn refetch_event() -> *mut c_char {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
|
||||
let result = rt.block_on(refetch_event_cards());
|
||||
let result = ApiResult::from(result);
|
||||
|
||||
let json_string = serde_json::to_string(&result).unwrap();
|
||||
|
||||
let c_str = CString::new(json_string).unwrap();
|
||||
c_str.into_raw()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn free_string(s: *mut c_char) {
|
||||
|
||||
+74
-71
@@ -1,13 +1,11 @@
|
||||
use crate::data::{FetchResult, FetchResultCard, State};
|
||||
use crate::download::Downloader;
|
||||
use crate::data::{
|
||||
FetchResult, FetchResultEvent, FetchResultUnlinkedCards, State,
|
||||
};
|
||||
use crate::download::{download_cards};
|
||||
use crate::github::{fetch_last_hash, sync_data};
|
||||
use crate::{CONFIG, PROVIDE, STATE, STORE, ending_point};
|
||||
use crate::{PROVIDE, STATE, STORE, ending_point};
|
||||
use anyhow::anyhow;
|
||||
use assets_updater::core::export_pipeline::find_files;
|
||||
use futures::StreamExt;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use log::{debug, info};
|
||||
use std::fs;
|
||||
|
||||
pub async fn main() -> anyhow::Result<()> {
|
||||
let current_hash = fetch_last_hash().await?;
|
||||
@@ -38,6 +36,53 @@ pub async fn main() -> anyhow::Result<()> {
|
||||
|
||||
ending_point!(ok);
|
||||
sync_data().await?;
|
||||
|
||||
// unlinked
|
||||
let unlinked_cards = {
|
||||
STORE
|
||||
.read()
|
||||
.map_err(|e| anyhow!("{}", e))?
|
||||
.find_unlinked_cards()
|
||||
};
|
||||
{
|
||||
let new_cards: Vec<_> = {
|
||||
let state = STATE.read().map_err(|e| anyhow!("{}", e))?;
|
||||
if !state.unlinked_cards.is_empty() {
|
||||
unlinked_cards
|
||||
.iter()
|
||||
.filter(|c| !state.unlinked_cards.contains(&c.id))
|
||||
.cloned()
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
if !new_cards.is_empty() {
|
||||
ending_point!(ok);
|
||||
let images = download_cards(&new_cards).await?;
|
||||
info!("collected {} unlinked cards", images.len());
|
||||
ending_point!(ok);
|
||||
let result = FetchResultUnlinkedCards { cards: images };
|
||||
debug!("Providing result: {:?}", result);
|
||||
{
|
||||
let mut provide = PROVIDE.write().map_err(|e| anyhow!("{}", e))?;
|
||||
if provide.is_none() {
|
||||
*provide = Some(Vec::new());
|
||||
}
|
||||
provide
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.push(FetchResult::UnlinkedCard(result));
|
||||
}
|
||||
ending_point!(ok);
|
||||
}
|
||||
|
||||
let mut state = STATE.write().map_err(|e| anyhow!("{}", e))?;
|
||||
state.unlinked_cards = unlinked_cards.iter().map(|c| c.id).collect();
|
||||
state.save();
|
||||
}
|
||||
|
||||
// event
|
||||
ending_point!(ok);
|
||||
let store = STORE.read().map_err(|e| anyhow!("{}", e))?;
|
||||
let event = store.find_last_event();
|
||||
@@ -61,79 +106,24 @@ pub async fn main() -> anyhow::Result<()> {
|
||||
let cards = store.find_cards_by_event(event.event_id);
|
||||
drop(store);
|
||||
ending_point!(ok);
|
||||
let downloader = Downloader::new().await?;
|
||||
ending_point!(ok);
|
||||
let cache_dir = CONFIG.get().unwrap().cache_dir();
|
||||
let mut futures = FuturesUnordered::new();
|
||||
for card in &cards {
|
||||
futures.push(async {
|
||||
ending_point!(ok);
|
||||
debug!(
|
||||
"Pulling card {} from bundle {}",
|
||||
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)?;
|
||||
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)?;
|
||||
Ok::<_, anyhow::Error>((card.clone(), local_images))
|
||||
});
|
||||
}
|
||||
let mut images = Vec::new();
|
||||
while let Some(res) = futures.next().await {
|
||||
images.push(res?);
|
||||
}
|
||||
images.sort_by(|a, b| a.0.id.cmp(&b.0.id)); // 小在前
|
||||
let images: Vec<_> = images
|
||||
.into_iter()
|
||||
.map(|(card, path)| FetchResultCard {
|
||||
text: format!(
|
||||
"#{id} {attr}{rarity} {skill} [{prefix}]{name}",
|
||||
id = card.id,
|
||||
attr = card.attr_short_text(),
|
||||
rarity = card.rarity_short_text(),
|
||||
skill = card
|
||||
.skill_id_short().map(|s| format!("({})", s))
|
||||
.unwrap_or_default(),
|
||||
prefix = card.prefix,
|
||||
name = card.character_name()
|
||||
),
|
||||
path,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let images = download_cards(&cards).await?;
|
||||
info!(
|
||||
"In event {} collected {} cards",
|
||||
event.event_id,
|
||||
images.len()
|
||||
);
|
||||
let result = FetchResult {
|
||||
let result = FetchResultEvent {
|
||||
event_id: event.event_id,
|
||||
cards: images,
|
||||
};
|
||||
debug!("Providing result: {:?}", result);
|
||||
*PROVIDE.write().map_err(|e| anyhow!("{}", e))? = Some(result);
|
||||
{
|
||||
let mut provide = PROVIDE.write().map_err(|e| anyhow!("{}", e))?;
|
||||
if provide.is_none() {
|
||||
*provide = Some(Vec::new());
|
||||
}
|
||||
provide.as_mut().unwrap().push(FetchResult::Event(result));
|
||||
}
|
||||
let mut state = STATE.write().map_err(|e| anyhow!("{}", e))?;
|
||||
state.git_hash = Some(current_hash);
|
||||
state.event_id = Some(event.event_id);
|
||||
@@ -141,3 +131,16 @@ pub async fn main() -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn refetch_event_cards() -> anyhow::Result<FetchResultEvent> {
|
||||
let event_id = { STATE.read().map_err(|e| anyhow!("{}", e))?.event_id };
|
||||
let event_id = event_id.ok_or(anyhow!("Event ID not found"))?;
|
||||
let store = STORE.read().map_err(|e| anyhow!("{}", e))?;
|
||||
let cards = store.find_cards_by_event(event_id);
|
||||
ending_point!(ok);
|
||||
let images = download_cards(&cards).await?;
|
||||
Ok(FetchResultEvent {
|
||||
event_id,
|
||||
cards: images,
|
||||
})
|
||||
}
|
||||
|
||||
+11
-3
@@ -85,11 +85,19 @@ impl Store {
|
||||
self.event_cards
|
||||
.iter()
|
||||
.filter(|e| e.event_id == event_id)
|
||||
.filter_map(|c| {
|
||||
.filter_map(|c| self.cards.iter().rev().find(|e| e.id == c.card_id))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn find_unlinked_cards(&self) -> Vec<Card> {
|
||||
self.cards
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|e| e.id == c.card_id)
|
||||
.filter(|c| {
|
||||
self.event_cards
|
||||
.iter()
|
||||
.find(|e| e.card_id == c.id)
|
||||
.is_none()
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
|
||||
Reference in New Issue
Block a user