feat: unp4k update

This commit is contained in:
xkeyC 2025-12-10 21:04:47 +08:00
parent c172b623d7
commit 23e909e330
7 changed files with 114 additions and 111 deletions

View File

@ -8,7 +8,7 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
import 'package:freezed_annotation/freezed_annotation.dart' hide protected; import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
part 'unp4k_api.freezed.dart'; part 'unp4k_api.freezed.dart';
// These functions are ignored because they are not marked as `pub`: `dos_datetime_to_millis`, `ensure_files_loaded` // These functions are ignored because they are not marked as `pub`: `dos_datetime_to_millis`, `ensure_files_loaded`, `p4k_get_entry`
/// P4K /// P4K
Future<void> p4KOpen({required String p4KPath}) => Future<void> p4KOpen({required String p4KPath}) =>

View File

@ -399,10 +399,10 @@ class Unp4kCModel extends _$Unp4kCModel {
dPrint("extractFile .... $filePath -> $fullOutputPath"); dPrint("extractFile .... $filePath -> $fullOutputPath");
// 使 Rust API // 使 Rust API
await unp4k_api.p4KExtractToDisk(filePath: filePath, outputPath: fullOutputPath); await unp4k_api.p4KExtractToDisk(filePath: filePath, outputPath: outputPath);
if (mode == "extract_open") { if (mode == "extract_open") {
const textExt = [".txt", ".xml", ".json", ".lua", ".cfg", ".ini"]; const textExt = [".txt", ".xml", ".json", ".lua", ".cfg", ".ini", ".mtl"];
const imgExt = [".png"]; const imgExt = [".png"];
String openType = "unknown"; String openType = "unknown";
for (var element in textExt) { for (var element in textExt) {
@ -475,9 +475,7 @@ class Unp4kCModel extends _$Unp4kCModel {
current++; current++;
onProgress?.call(current, total, entryPath); onProgress?.call(current, total, entryPath);
await unp4k_api.p4KExtractToDisk(filePath: entryPath, outputPath: outputDir);
final fullOutputPath = "$outputDir\\$entryPath";
await unp4k_api.p4KExtractToDisk(filePath: entryPath, outputPath: fullOutputPath);
} }
state = state.copyWith(endMessage: S.current.tools_unp4k_extract_completed(current)); state = state.copyWith(endMessage: S.current.tools_unp4k_extract_completed(current));
@ -493,8 +491,7 @@ class Unp4kCModel extends _$Unp4kCModel {
return (false, 0, S.current.tools_unp4k_extract_cancelled); return (false, 0, S.current.tools_unp4k_extract_cancelled);
} }
final fullOutputPath = "$outputDir\\$filePath"; await unp4k_api.p4KExtractToDisk(filePath: filePath, outputPath: outputDir);
await unp4k_api.p4KExtractToDisk(filePath: filePath, outputPath: fullOutputPath);
state = state.copyWith(endMessage: S.current.tools_unp4k_extract_completed(1)); state = state.copyWith(endMessage: S.current.tools_unp4k_extract_completed(1));
return (true, 1, null); return (true, 1, null);
@ -609,9 +606,7 @@ class Unp4kCModel extends _$Unp4kCModel {
current++; current++;
onProgress?.call(current, total, extractPath); onProgress?.call(current, total, extractPath);
await unp4k_api.p4KExtractToDisk(filePath: extractPath, outputPath: outputDir);
final fullOutputPath = "$outputDir\\$extractPath";
await unp4k_api.p4KExtractToDisk(filePath: extractPath, outputPath: fullOutputPath);
} }
state = state.copyWith(endMessage: S.current.tools_unp4k_extract_completed(current)); state = state.copyWith(endMessage: S.current.tools_unp4k_extract_completed(current));

View File

@ -50,7 +50,7 @@ final class ToolsLogAnalyzeProvider
} }
} }
String _$toolsLogAnalyzeHash() => r'f5079c7d35daf25b07f83bacb224484171e9c93f'; String _$toolsLogAnalyzeHash() => r'4c1aea03394e5c5641b2eb40a31d37892bb978bf';
final class ToolsLogAnalyzeFamily extends $Family final class ToolsLogAnalyzeFamily extends $Family
with with

42
rust/Cargo.lock generated
View File

@ -3404,15 +3404,6 @@ dependencies = [
"syn 2.0.111", "syn 2.0.111",
] ]
[[package]]
name = "matchers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata",
]
[[package]] [[package]]
name = "matches" name = "matches"
version = "0.1.10" version = "0.1.10"
@ -3716,15 +3707,6 @@ dependencies = [
"zbus", "zbus",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "num" name = "num"
version = "0.2.1" version = "0.2.1"
@ -4941,8 +4923,6 @@ dependencies = [
"tao", "tao",
"tokenizers", "tokenizers",
"tokio", "tokio",
"tracing",
"tracing-subscriber",
"unp4k_rs", "unp4k_rs",
"url", "url",
"uuid", "uuid",
@ -6074,33 +6054,15 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
] ]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.22" version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
dependencies = [ dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex-automata",
"sharded-slab", "sharded-slab",
"smallvec 1.15.1",
"thread_local", "thread_local",
"tracing",
"tracing-core", "tracing-core",
"tracing-log",
] ]
[[package]] [[package]]
@ -6186,7 +6148,7 @@ checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
[[package]] [[package]]
name = "unp4k_rs" name = "unp4k_rs"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/StarCitizenToolBox/unp4k_rs?tag=V0.0.2#02867472dda1c18e81b0f635b8653fa86bd145cb" source = "git+https://github.com/StarCitizenToolBox/unp4k_rs?rev=b55d64934bde37bb1079c2c3e2996c8286532914#b55d64934bde37bb1079c2c3e2996c8286532914"
dependencies = [ dependencies = [
"aes", "aes",
"anyhow", "anyhow",
@ -6199,7 +6161,7 @@ dependencies = [
"globset", "globset",
"indicatif", "indicatif",
"quick-xml 0.38.4", "quick-xml 0.38.4",
"rayon", "sha2",
"thiserror 2.0.17", "thiserror 2.0.17",
"zip", "zip",
"zstd", "zstd",

View File

@ -31,13 +31,11 @@ tokenizers = { version = "0.22.2", default-features = false, features = ["onig"]
ndarray = "0.17.1" ndarray = "0.17.1"
serde_json = "1.0.145" serde_json = "1.0.145"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
unp4k_rs = { git = "https://github.com/StarCitizenToolBox/unp4k_rs", tag = "V0.0.2" } unp4k_rs = { git = "https://github.com/StarCitizenToolBox/unp4k_rs", rev = "b55d64934bde37bb1079c2c3e2996c8286532914" }
uuid = { version = "1.19.0", features = ["v4"] } uuid = { version = "1.19.0", features = ["v4"] }
parking_lot = "0.12.5" parking_lot = "0.12.5"
crossbeam-channel = "0.5.15" crossbeam-channel = "0.5.15"
librqbit = { git = "https://github.com/StarCitizenToolBox/rqbit", rev = "f8c0b0927904e1d8b0e28e708bd69fd8069d413a" } librqbit = { git = "https://github.com/StarCitizenToolBox/rqbit", rev = "f8c0b0927904e1d8b0e28e708bd69fd8069d413a" }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
bytes = "1.10" bytes = "1.10"
# WebView # WebView

View File

@ -7,7 +7,9 @@ use anyhow::{bail, Context, Result};
use bytes::Bytes; use bytes::Bytes;
use flutter_rust_bridge::frb; use flutter_rust_bridge::frb;
use librqbit::{ use librqbit::{
AddTorrent, AddTorrentOptions, AddTorrentResponse, ManagedTorrent, Session, SessionOptions, SessionPersistenceConfig, TorrentStats, TorrentStatsState, WebSeedConfig, api::TorrentIdOrHash, dht::PersistentDhtConfig, limits::LimitsConfig api::TorrentIdOrHash, dht::PersistentDhtConfig, limits::LimitsConfig, AddTorrent,
AddTorrentOptions, AddTorrentResponse, ManagedTorrent, Session, SessionOptions,
SessionPersistenceConfig, TorrentStats, TorrentStatsState, WebSeedConfig,
}; };
use parking_lot::RwLock; use parking_lot::RwLock;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -131,7 +133,7 @@ pub async fn downloader_init(
upload_bps: upload_limit_bps.and_then(NonZeroU32::new), upload_bps: upload_limit_bps.and_then(NonZeroU32::new),
download_bps: download_limit_bps.and_then(NonZeroU32::new), download_bps: download_limit_bps.and_then(NonZeroU32::new),
}, },
webseed_config: Some(WebSeedConfig{ webseed_config: Some(WebSeedConfig {
max_concurrent_per_source: 32, max_concurrent_per_source: 32,
max_total_concurrent: 64, max_total_concurrent: 64,
request_timeout_secs: 30, request_timeout_secs: 30,
@ -200,7 +202,8 @@ pub fn downloader_has_pending_session_tasks(working_dir: String) -> bool {
/// Helper function to get session /// Helper function to get session
fn get_session() -> Result<Arc<Session>> { fn get_session() -> Result<Arc<Session>> {
SESSION.read() SESSION
.read()
.clone() .clone()
.context("Downloader not initialized. Call downloader_init first.") .context("Downloader not initialized. Call downloader_init first.")
} }
@ -321,7 +324,10 @@ pub async fn downloader_add_url(
.context("Failed to download torrent file")?; .context("Failed to download torrent file")?;
if !response.status().is_success() { if !response.status().is_success() {
bail!("Failed to download torrent file: HTTP {}", response.status()); bail!(
"Failed to download torrent file: HTTP {}",
response.status()
);
} }
let bytes = response let bytes = response
@ -345,7 +351,10 @@ pub async fn downloader_pause(task_id: usize) -> Result<()> {
}; };
if let Some(handle) = handle { if let Some(handle) = handle {
session.pause(&handle).await.context("Failed to pause torrent")?; session
.pause(&handle)
.await
.context("Failed to pause torrent")?;
Ok(()) Ok(())
} else { } else {
bail!("Task not found: {}", task_id) bail!("Task not found: {}", task_id)
@ -362,7 +371,10 @@ pub async fn downloader_resume(task_id: usize) -> Result<()> {
}; };
if let Some(handle) = handle { if let Some(handle) = handle {
session.unpause(&handle).await.context("Failed to resume torrent")?; session
.unpause(&handle)
.await
.context("Failed to resume torrent")?;
Ok(()) Ok(())
} else { } else {
bail!("Task not found: {}", task_id) bail!("Task not found: {}", task_id)
@ -427,7 +439,9 @@ pub async fn downloader_get_task_info(task_id: usize) -> Result<DownloadTaskInfo
let (download_speed, upload_speed, num_peers) = if let Some(live) = &stats.live { let (download_speed, upload_speed, num_peers) = if let Some(live) = &stats.live {
let down = (live.download_speed.mbps * 1024.0 * 1024.0) as u64; let down = (live.download_speed.mbps * 1024.0 * 1024.0) as u64;
let up = (live.upload_speed.mbps * 1024.0 * 1024.0) as u64; let up = (live.upload_speed.mbps * 1024.0 * 1024.0) as u64;
let peers = (live.snapshot.peer_stats.queued + live.snapshot.peer_stats.connecting + live.snapshot.peer_stats.live) as usize; let peers = (live.snapshot.peer_stats.queued
+ live.snapshot.peer_stats.connecting
+ live.snapshot.peer_stats.live) as usize;
(down, up, peers) (down, up, peers)
} else { } else {
(0, 0, 0) (0, 0, 0)
@ -508,7 +522,9 @@ pub async fn downloader_get_all_tasks() -> Result<Vec<DownloadTaskInfo>> {
let (download_speed, upload_speed, num_peers) = if let Some(live) = &stats.live { let (download_speed, upload_speed, num_peers) = if let Some(live) = &stats.live {
let down = (live.download_speed.mbps * 1024.0 * 1024.0) as u64; let down = (live.download_speed.mbps * 1024.0 * 1024.0) as u64;
let up = (live.upload_speed.mbps * 1024.0 * 1024.0) as u64; let up = (live.upload_speed.mbps * 1024.0 * 1024.0) as u64;
let peers = (live.snapshot.peer_stats.queued + live.snapshot.peer_stats.connecting + live.snapshot.peer_stats.live) as usize; let peers = (live.snapshot.peer_stats.queued
+ live.snapshot.peer_stats.connecting
+ live.snapshot.peer_stats.live) as usize;
(down, up, peers) (down, up, peers)
} else { } else {
(0, 0, 0) (0, 0, 0)
@ -578,7 +594,10 @@ pub async fn downloader_is_name_in_task(name: String, downloading_only: Option<b
if let Ok(tasks) = downloader_get_all_tasks().await { if let Ok(tasks) = downloader_get_all_tasks().await {
for task in tasks { for task in tasks {
// If downloading_only is true, skip finished and error tasks // If downloading_only is true, skip finished and error tasks
if downloading_only && (task.status == DownloadTaskStatus::Finished || task.status == DownloadTaskStatus::Error) { if downloading_only
&& (task.status == DownloadTaskStatus::Finished
|| task.status == DownloadTaskStatus::Error)
{
continue; continue;
} }
@ -684,7 +703,11 @@ pub async fn downloader_remove_completed_tasks() -> Result<u32> {
let has_handle = TORRENT_HANDLES.read().contains_key(&task.id); let has_handle = TORRENT_HANDLES.read().contains_key(&task.id);
if has_handle { if has_handle {
// Use TorrentIdOrHash::Id for deletion // Use TorrentIdOrHash::Id for deletion
if session.delete(TorrentIdOrHash::Id(task.id), false).await.is_ok() { if session
.delete(TorrentIdOrHash::Id(task.id), false)
.await
.is_ok()
{
// Cache the task - it will get ID based on cache length // Cache the task - it will get ID based on cache length
COMPLETED_TASKS_CACHE.write().push(task.clone()); COMPLETED_TASKS_CACHE.write().push(task.clone());
TORRENT_HANDLES.write().remove(&task.id); TORRENT_HANDLES.write().remove(&task.id);
@ -703,7 +726,8 @@ pub async fn downloader_has_active_tasks() -> bool {
if let Ok(tasks) = downloader_get_all_tasks().await { if let Ok(tasks) = downloader_get_all_tasks().await {
for task in tasks { for task in tasks {
if task.status != DownloadTaskStatus::Finished if task.status != DownloadTaskStatus::Finished
&& task.status != DownloadTaskStatus::Error { && task.status != DownloadTaskStatus::Error
{
return true; return true;
} }
} }

View File

@ -3,7 +3,7 @@ use flutter_rust_bridge::frb;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use unp4k::{P4kEntry, P4kFile}; use unp4k::{CryXmlReader, P4kEntry, P4kFile};
/// P4K 文件项信息 /// P4K 文件项信息
#[frb(dart_metadata=("freezed"))] #[frb(dart_metadata=("freezed"))]
@ -32,7 +32,11 @@ fn dos_datetime_to_millis(date: u16, time: u16) -> i64 {
let days_since_epoch = { let days_since_epoch = {
let mut days = 0i64; let mut days = 0i64;
for y in 1970..year { for y in 1970..year {
days += if (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0) { 366 } else { 365 }; days += if (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0) {
366
} else {
365
};
} }
let days_in_months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; let days_in_months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
if month >= 1 && month <= 12 { if month >= 1 && month <= 12 {
@ -45,7 +49,8 @@ fn dos_datetime_to_millis(date: u16, time: u16) -> i64 {
days days
}; };
(days_since_epoch * 86400 + (hour as i64) * 3600 + (minute as i64) * 60 + (second as i64)) * 1000 (days_since_epoch * 86400 + (hour as i64) * 3600 + (minute as i64) * 60 + (second as i64))
* 1000
} }
// 全局 P4K 读取器实例(用于保持状态) // 全局 P4K 读取器实例(用于保持状态)
@ -132,6 +137,32 @@ pub async fn p4k_get_all_files() -> Result<Vec<P4kFileItem>> {
pub async fn p4k_extract_to_memory(file_path: String) -> Result<Vec<u8>> { pub async fn p4k_extract_to_memory(file_path: String) -> Result<Vec<u8>> {
// 确保文件列表已加载 // 确保文件列表已加载
tokio::task::spawn_blocking(|| ensure_files_loaded()).await??; tokio::task::spawn_blocking(|| ensure_files_loaded()).await??;
// 获取文件 entry 的克隆
let entry = p4k_get_entry(file_path).await?;
// 在后台线程执行阻塞的提取操作
let data = tokio::task::spawn_blocking(move || {
let mut reader = GLOBAL_P4K_READER.lock().unwrap();
if reader.is_none() {
return Err(anyhow!("P4K reader not initialized"));
}
let data = reader.as_mut().unwrap().extract_entry(&entry)?;
if (entry.name.ends_with(".xml") || entry.name.ends_with(".mtl"))
&& CryXmlReader::is_cryxml(&data)
{
let cry_xml_string = CryXmlReader::parse(&data)?;
return Ok(cry_xml_string.into_bytes());
}
Ok::<_, anyhow::Error>(data)
})
.await??;
Ok(data)
}
async fn p4k_get_entry(file_path: String) -> Result<P4kEntry> {
// 确保文件列表已加载
tokio::task::spawn_blocking(|| ensure_files_loaded()).await??;
// 规范化路径 // 规范化路径
let normalized_path = if file_path.starts_with("\\") { let normalized_path = if file_path.starts_with("\\") {
@ -149,34 +180,27 @@ pub async fn p4k_extract_to_memory(file_path: String) -> Result<Vec<u8>> {
.clone() .clone()
}; };
// 在后台线程执行阻塞的提取操作 Ok(entry)
let data = tokio::task::spawn_blocking(move || {
let mut reader = GLOBAL_P4K_READER.lock().unwrap();
if reader.is_none() {
return Err(anyhow!("P4K reader not initialized"));
}
let data = reader.as_mut().unwrap().extract_entry(&entry)?;
Ok::<_, anyhow::Error>(data)
})
.await??;
Ok(data)
} }
/// 提取文件到磁盘 /// 提取文件到磁盘
pub async fn p4k_extract_to_disk(file_path: String, output_path: String) -> Result<()> { pub async fn p4k_extract_to_disk(file_path: String, output_path: String) -> Result<()> {
let data = p4k_extract_to_memory(file_path).await?; let entry = p4k_get_entry(file_path).await?;
// 在后台线程执行阻塞的文件写入操作 // 在后台线程执行阻塞的文件写入操作
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let output = PathBuf::from(&output_path); let output = PathBuf::from(&output_path);
// 创建父目录 // 创建父目录
if let Some(parent) = output.parent() { if let Some(parent) = output.parent() {
std::fs::create_dir_all(parent)?; std::fs::create_dir_all(parent)?;
} }
std::fs::write(output, data)?; let mut reader_guard = GLOBAL_P4K_READER.lock().unwrap();
let reader = reader_guard
.as_mut()
.ok_or_else(|| anyhow!("P4K reader not initialized"))?;
unp4k::p4k_utils::extract_single_file(reader, &entry, &output, true)?;
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
}) })
.await??; .await??;