use crate::protobuf::gou::{MessageType, Starpoles}; use crate::utils::cxx_string_to_string; use ::log::{debug, error, info, trace, warn}; use bytes::Bytes; use cxx::CxxString; use encoding_rs::Encoding; use futures_util::SinkExt; use prost::Message as WebMessage; use spin::Mutex; use std::sync::OnceLock; use std::sync::mpsc; use tokio::net::TcpStream; use tokio::runtime::Runtime; use tokio_tungstenite::tungstenite::Message; static LOGGER_SENDER: OnceLock> = OnceLock::new(); mod config; mod log; mod network; pub mod protobuf; pub mod utils; #[cxx::bridge] mod ffi { extern "Rust" { fn download_file(url: &CxxString, path: &CxxString) -> Result<()>; fn http_get(url: &CxxString) -> Result; /// 尝试将未知编码的字符串转换为UTF-8 fn to_utf8(string: &CxxString) -> String; /// UTF-16LE转换为UTF-8 fn unicode_to_utf_8(string: &CxxString) -> String; /// 猜测编码 fn guess_encoding(string: &CxxString) -> String; fn init_log(is_debug: bool, ws_uel: &CxxString); fn log_trace(msg: &CxxString); fn log_debug(msg: &CxxString); fn log_info(msg: &CxxString); fn log_warning(msg: &CxxString); fn log_error(msg: &CxxString); } } fn unicode_to_utf_8(string: &CxxString) -> String { // 约定:传入的内容按 UTF-16LE 字节序解释 let mut bytes = string.as_bytes(); // UTF-16 必须是偶数长度;若是奇数长度,丢弃最后 1 个字节避免越界/误解码 if (bytes.len() & 1) == 1 { bytes = &bytes[..bytes.len() - 1]; } // 用 encoding_rs 直接把 UTF-16LE 转成 Rust 的 UTF-8 String let (cow, _actual_used, _had_errors) = encoding_rs::UTF_16LE.decode(bytes); cow.into_owned() } fn guess_encoding(string: &CxxString) -> String { utils::guess_encoding_label(string) } fn to_utf8(string: &CxxString) -> String { cxx_string_to_string(string).to_string() } fn log_error(msg: &cxx::CxxString) { let msg = cxx_string_to_string(msg); let lock = LOGGER_SENDER.get().unwrap(); let _ = lock.send(Starpoles { r#type: MessageType::Error.into(), message: msg, }); //error!("{}", msg); } fn log_warning(msg: &cxx::CxxString) { let msg = cxx_string_to_string(msg); let lock = LOGGER_SENDER.get().unwrap(); let _ = lock.send(Starpoles { r#type: MessageType::Warning.into(), message: msg, }); } fn log_info(msg: &cxx::CxxString) { let msg = cxx_string_to_string(msg); let lock = LOGGER_SENDER.get().unwrap(); let _ = lock.send(Starpoles { r#type: MessageType::Info.into(), message: msg, }); } fn log_debug(msg: &cxx::CxxString) { let msg = cxx_string_to_string(msg); let lock = LOGGER_SENDER.get().unwrap(); let _ = lock.send(Starpoles { r#type: MessageType::Error.into(), message: msg, }); } fn log_trace(msg: &cxx::CxxString) { let msg = cxx_string_to_string(msg); let lock = LOGGER_SENDER.get().unwrap(); let _ = lock.send(Starpoles { r#type: MessageType::Trace.into(), message: msg, }); } fn init_log(is_debug: bool, ws_uel: &CxxString) { let url = cxx_string_to_string(ws_uel); let (tx, rx) = mpsc::channel::(); LOGGER_SENDER.set(tx).expect("已经初始化过了"); std::thread::spawn(move || { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); rt.block_on(async move { loop { match tokio_tungstenite::connect_async(url.clone()).await { Ok((mut stream, _resp)) => { while let Ok(line) = rx.recv() { let mut buffer = vec![]; // Vec总是具备足够的空间 line.encode(&mut buffer).unwrap(); if stream .send(Message::Binary(Bytes::from(buffer))) .await .is_err() { break; } } } Err(_) => { tokio::time::sleep(std::time::Duration::from_secs(2)).await; } } } }); }); log::init_log(is_debug); } fn get_runtime() -> Runtime { Runtime::new().expect("创建Tokio运行时失败") } fn download_file( url: &cxx::CxxString, path: &cxx::CxxString, ) -> Result<(), Box> { let rt = get_runtime(); let url = cxx_string_to_string(url); let path = cxx_string_to_string(path); rt.block_on(network::download_file(&url, &path))?; Ok(()) } fn http_get(url: &cxx::CxxString) -> Result> { let rt = get_runtime(); let url = cxx_string_to_string(url); rt.block_on(network::http_get(&url)) }