mirror of
https://github.com/StarCitizenToolBox/app.git
synced 2026-01-14 04:00:27 +00:00
feat: unp4k update
This commit is contained in:
parent
c172b623d7
commit
23e909e330
@ -8,7 +8,7 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
|
||||
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 文件(仅打开,不读取文件列表)
|
||||
Future<void> p4KOpen({required String p4KPath}) =>
|
||||
|
||||
@ -399,10 +399,10 @@ class Unp4kCModel extends _$Unp4kCModel {
|
||||
dPrint("extractFile .... $filePath -> $fullOutputPath");
|
||||
|
||||
// 使用 Rust API 提取到磁盘
|
||||
await unp4k_api.p4KExtractToDisk(filePath: filePath, outputPath: fullOutputPath);
|
||||
await unp4k_api.p4KExtractToDisk(filePath: filePath, outputPath: outputPath);
|
||||
|
||||
if (mode == "extract_open") {
|
||||
const textExt = [".txt", ".xml", ".json", ".lua", ".cfg", ".ini"];
|
||||
const textExt = [".txt", ".xml", ".json", ".lua", ".cfg", ".ini", ".mtl"];
|
||||
const imgExt = [".png"];
|
||||
String openType = "unknown";
|
||||
for (var element in textExt) {
|
||||
@ -475,9 +475,7 @@ class Unp4kCModel extends _$Unp4kCModel {
|
||||
|
||||
current++;
|
||||
onProgress?.call(current, total, entryPath);
|
||||
|
||||
final fullOutputPath = "$outputDir\\$entryPath";
|
||||
await unp4k_api.p4KExtractToDisk(filePath: entryPath, outputPath: fullOutputPath);
|
||||
await unp4k_api.p4KExtractToDisk(filePath: entryPath, outputPath: outputDir);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
final fullOutputPath = "$outputDir\\$filePath";
|
||||
await unp4k_api.p4KExtractToDisk(filePath: filePath, outputPath: fullOutputPath);
|
||||
await unp4k_api.p4KExtractToDisk(filePath: filePath, outputPath: outputDir);
|
||||
|
||||
state = state.copyWith(endMessage: S.current.tools_unp4k_extract_completed(1));
|
||||
return (true, 1, null);
|
||||
@ -609,9 +606,7 @@ class Unp4kCModel extends _$Unp4kCModel {
|
||||
|
||||
current++;
|
||||
onProgress?.call(current, total, extractPath);
|
||||
|
||||
final fullOutputPath = "$outputDir\\$extractPath";
|
||||
await unp4k_api.p4KExtractToDisk(filePath: extractPath, outputPath: fullOutputPath);
|
||||
await unp4k_api.p4KExtractToDisk(filePath: extractPath, outputPath: outputDir);
|
||||
}
|
||||
|
||||
state = state.copyWith(endMessage: S.current.tools_unp4k_extract_completed(current));
|
||||
|
||||
@ -50,7 +50,7 @@ final class ToolsLogAnalyzeProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$toolsLogAnalyzeHash() => r'f5079c7d35daf25b07f83bacb224484171e9c93f';
|
||||
String _$toolsLogAnalyzeHash() => r'4c1aea03394e5c5641b2eb40a31d37892bb978bf';
|
||||
|
||||
final class ToolsLogAnalyzeFamily extends $Family
|
||||
with
|
||||
|
||||
42
rust/Cargo.lock
generated
42
rust/Cargo.lock
generated
@ -3404,15 +3404,6 @@ dependencies = [
|
||||
"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]]
|
||||
name = "matches"
|
||||
version = "0.1.10"
|
||||
@ -3716,15 +3707,6 @@ dependencies = [
|
||||
"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]]
|
||||
name = "num"
|
||||
version = "0.2.1"
|
||||
@ -4941,8 +4923,6 @@ dependencies = [
|
||||
"tao",
|
||||
"tokenizers",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"unp4k_rs",
|
||||
"url",
|
||||
"uuid",
|
||||
@ -6074,33 +6054,15 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec 1.15.1",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6186,7 +6148,7 @@ checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
|
||||
[[package]]
|
||||
name = "unp4k_rs"
|
||||
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 = [
|
||||
"aes",
|
||||
"anyhow",
|
||||
@ -6199,7 +6161,7 @@ dependencies = [
|
||||
"globset",
|
||||
"indicatif",
|
||||
"quick-xml 0.38.4",
|
||||
"rayon",
|
||||
"sha2",
|
||||
"thiserror 2.0.17",
|
||||
"zip",
|
||||
"zstd",
|
||||
|
||||
@ -31,13 +31,11 @@ tokenizers = { version = "0.22.2", default-features = false, features = ["onig"]
|
||||
ndarray = "0.17.1"
|
||||
serde_json = "1.0.145"
|
||||
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"] }
|
||||
parking_lot = "0.12.5"
|
||||
crossbeam-channel = "0.5.15"
|
||||
librqbit = { git = "https://github.com/StarCitizenToolBox/rqbit", rev = "f8c0b0927904e1d8b0e28e708bd69fd8069d413a" }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
bytes = "1.10"
|
||||
|
||||
# WebView
|
||||
|
||||
@ -7,7 +7,9 @@ use anyhow::{bail, Context, Result};
|
||||
use bytes::Bytes;
|
||||
use flutter_rust_bridge::frb;
|
||||
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 serde::{Deserialize, Serialize};
|
||||
@ -72,7 +74,7 @@ pub struct DownloadGlobalStat {
|
||||
}
|
||||
|
||||
/// Initialize the download manager session with persistence enabled
|
||||
///
|
||||
///
|
||||
/// Parameters:
|
||||
/// - working_dir: The directory to store session data (persistence, DHT, etc.)
|
||||
/// - default_download_dir: The default directory to store downloads
|
||||
@ -90,7 +92,7 @@ pub async fn downloader_init(
|
||||
}
|
||||
|
||||
let _lock = SESSION_INIT_LOCK.lock().await;
|
||||
|
||||
|
||||
// Double check after acquiring lock
|
||||
if SESSION.read().is_some() {
|
||||
return Ok(());
|
||||
@ -99,15 +101,15 @@ pub async fn downloader_init(
|
||||
// Working directory for persistence and session data
|
||||
let working_folder = PathBuf::from(&working_dir);
|
||||
std::fs::create_dir_all(&working_folder)?;
|
||||
|
||||
|
||||
// Default download folder
|
||||
let output_folder = PathBuf::from(&default_download_dir);
|
||||
std::fs::create_dir_all(&output_folder)?;
|
||||
|
||||
|
||||
// Create persistence folder for session state in working directory
|
||||
let persistence_folder = working_folder.join("rqbit-session");
|
||||
std::fs::create_dir_all(&persistence_folder)?;
|
||||
|
||||
|
||||
// DHT persistence file in working directory
|
||||
let dht_persistence_file = working_folder.join("dht.json");
|
||||
|
||||
@ -131,7 +133,7 @@ pub async fn downloader_init(
|
||||
upload_bps: upload_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_total_concurrent: 64,
|
||||
request_timeout_secs: 30,
|
||||
@ -162,21 +164,21 @@ pub fn downloader_is_initialized() -> bool {
|
||||
|
||||
/// Check if there are pending tasks to restore from session file (without starting the downloader)
|
||||
/// This reads the session.json file directly to check if there are any torrents saved.
|
||||
///
|
||||
///
|
||||
/// Parameters:
|
||||
/// - working_dir: The directory where session data is stored (same as passed to downloader_init)
|
||||
///
|
||||
///
|
||||
/// Returns: true if there are tasks to restore, false otherwise
|
||||
#[frb(sync)]
|
||||
pub fn downloader_has_pending_session_tasks(working_dir: String) -> bool {
|
||||
let session_file = PathBuf::from(&working_dir)
|
||||
.join("rqbit-session")
|
||||
.join("session.json");
|
||||
|
||||
|
||||
if !session_file.exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Try to read and parse the session file
|
||||
match std::fs::read_to_string(&session_file) {
|
||||
Ok(content) => {
|
||||
@ -200,7 +202,8 @@ pub fn downloader_has_pending_session_tasks(working_dir: String) -> bool {
|
||||
|
||||
/// Helper function to get session
|
||||
fn get_session() -> Result<Arc<Session>> {
|
||||
SESSION.read()
|
||||
SESSION
|
||||
.read()
|
||||
.clone()
|
||||
.context("Downloader not initialized. Call downloader_init first.")
|
||||
}
|
||||
@ -321,7 +324,10 @@ pub async fn downloader_add_url(
|
||||
.context("Failed to download torrent file")?;
|
||||
|
||||
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
|
||||
@ -345,7 +351,10 @@ pub async fn downloader_pause(task_id: usize) -> Result<()> {
|
||||
};
|
||||
|
||||
if let Some(handle) = handle {
|
||||
session.pause(&handle).await.context("Failed to pause torrent")?;
|
||||
session
|
||||
.pause(&handle)
|
||||
.await
|
||||
.context("Failed to pause torrent")?;
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("Task not found: {}", task_id)
|
||||
@ -362,7 +371,10 @@ pub async fn downloader_resume(task_id: usize) -> Result<()> {
|
||||
};
|
||||
|
||||
if let Some(handle) = handle {
|
||||
session.unpause(&handle).await.context("Failed to resume torrent")?;
|
||||
session
|
||||
.unpause(&handle)
|
||||
.await
|
||||
.context("Failed to resume torrent")?;
|
||||
Ok(())
|
||||
} else {
|
||||
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 down = (live.download_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)
|
||||
} else {
|
||||
(0, 0, 0)
|
||||
@ -455,7 +469,7 @@ fn get_task_status(stats: &TorrentStats) -> DownloadTaskStatus {
|
||||
if stats.error.is_some() {
|
||||
return DownloadTaskStatus::Error;
|
||||
}
|
||||
|
||||
|
||||
if stats.finished {
|
||||
return DownloadTaskStatus::Finished;
|
||||
}
|
||||
@ -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 down = (live.download_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)
|
||||
} else {
|
||||
(0, 0, 0)
|
||||
@ -536,7 +552,7 @@ pub async fn downloader_get_all_tasks() -> Result<Vec<DownloadTaskInfo>> {
|
||||
// Merge cached completed tasks with IDs based on cache index (10000 + index)
|
||||
let mut result = tasks.into_inner();
|
||||
let completed_tasks_cache = COMPLETED_TASKS_CACHE.read();
|
||||
|
||||
|
||||
for (cache_index, task) in completed_tasks_cache.iter().enumerate() {
|
||||
let mut task_with_id = task.clone();
|
||||
// Assign ID based on cache index: 10000, 10001, 10002, etc.
|
||||
@ -552,11 +568,11 @@ pub async fn downloader_get_global_stats() -> Result<DownloadGlobalStat> {
|
||||
let tasks = downloader_get_all_tasks().await?;
|
||||
|
||||
let mut stat = DownloadGlobalStat::default();
|
||||
|
||||
|
||||
for task in &tasks {
|
||||
stat.download_speed += task.download_speed;
|
||||
stat.upload_speed += task.upload_speed;
|
||||
|
||||
|
||||
match task.status {
|
||||
DownloadTaskStatus::Live => stat.num_active += 1,
|
||||
DownloadTaskStatus::Paused | DownloadTaskStatus::Checking => stat.num_waiting += 1,
|
||||
@ -568,20 +584,23 @@ pub async fn downloader_get_global_stats() -> Result<DownloadGlobalStat> {
|
||||
}
|
||||
|
||||
/// Check if a task with given name exists
|
||||
///
|
||||
///
|
||||
/// Parameters:
|
||||
/// - name: Task name to search for
|
||||
/// - downloading_only: If true, only search in active/waiting tasks. If false, include completed tasks (default: true)
|
||||
pub async fn downloader_is_name_in_task(name: String, downloading_only: Option<bool>) -> bool {
|
||||
let downloading_only = downloading_only.unwrap_or(true);
|
||||
|
||||
|
||||
if let Ok(tasks) = downloader_get_all_tasks().await {
|
||||
for task in 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;
|
||||
}
|
||||
|
||||
|
||||
if task.name.contains(&name) {
|
||||
return true;
|
||||
}
|
||||
@ -595,11 +614,11 @@ pub async fn downloader_pause_all() -> Result<()> {
|
||||
let session = get_session()?;
|
||||
|
||||
let handles: Vec<_> = TORRENT_HANDLES.read().values().cloned().collect();
|
||||
|
||||
|
||||
for handle in handles {
|
||||
let _ = session.pause(&handle).await;
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -608,11 +627,11 @@ pub async fn downloader_resume_all() -> Result<()> {
|
||||
let session = get_session()?;
|
||||
|
||||
let handles: Vec<_> = TORRENT_HANDLES.read().values().cloned().collect();
|
||||
|
||||
|
||||
for handle in handles {
|
||||
let _ = session.unpause(&handle).await;
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -632,11 +651,11 @@ pub async fn downloader_shutdown() -> Result<()> {
|
||||
let mut guard = SESSION.write();
|
||||
guard.take()
|
||||
};
|
||||
|
||||
|
||||
if let Some(session) = session_opt {
|
||||
session.stop().await;
|
||||
}
|
||||
|
||||
|
||||
TORRENT_HANDLES.write().clear();
|
||||
// Clear completed tasks cache on shutdown
|
||||
COMPLETED_TASKS_CACHE.write().clear();
|
||||
@ -657,7 +676,7 @@ pub fn downloader_clear_completed_tasks_cache() {
|
||||
}
|
||||
|
||||
/// Update global speed limits
|
||||
/// Note: rqbit Session doesn't support runtime limit changes,
|
||||
/// Note: rqbit Session doesn't support runtime limit changes,
|
||||
/// this function is a placeholder that returns an error.
|
||||
/// Speed limits should be set during downloader_init.
|
||||
pub async fn downloader_update_speed_limits(
|
||||
@ -676,7 +695,7 @@ pub async fn downloader_remove_completed_tasks() -> Result<u32> {
|
||||
|
||||
let tasks = downloader_get_all_tasks().await?;
|
||||
let mut removed_count = 0u32;
|
||||
|
||||
|
||||
for task in tasks {
|
||||
if task.status == DownloadTaskStatus::Finished {
|
||||
// Only process active tasks (id < 10000)
|
||||
@ -684,7 +703,11 @@ pub async fn downloader_remove_completed_tasks() -> Result<u32> {
|
||||
let has_handle = TORRENT_HANDLES.read().contains_key(&task.id);
|
||||
if has_handle {
|
||||
// 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
|
||||
COMPLETED_TASKS_CACHE.write().push(task.clone());
|
||||
TORRENT_HANDLES.write().remove(&task.id);
|
||||
@ -694,7 +717,7 @@ pub async fn downloader_remove_completed_tasks() -> Result<u32> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(removed_count)
|
||||
}
|
||||
|
||||
@ -702,8 +725,9 @@ pub async fn downloader_remove_completed_tasks() -> Result<u32> {
|
||||
pub async fn downloader_has_active_tasks() -> bool {
|
||||
if let Ok(tasks) = downloader_get_all_tasks().await {
|
||||
for task in tasks {
|
||||
if task.status != DownloadTaskStatus::Finished
|
||||
&& task.status != DownloadTaskStatus::Error {
|
||||
if task.status != DownloadTaskStatus::Finished
|
||||
&& task.status != DownloadTaskStatus::Error
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ use flutter_rust_bridge::frb;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use unp4k::{P4kEntry, P4kFile};
|
||||
use unp4k::{CryXmlReader, P4kEntry, P4kFile};
|
||||
|
||||
/// P4K 文件项信息
|
||||
#[frb(dart_metadata=("freezed"))]
|
||||
@ -32,7 +32,11 @@ fn dos_datetime_to_millis(date: u16, time: u16) -> i64 {
|
||||
let days_since_epoch = {
|
||||
let mut days = 0i64;
|
||||
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];
|
||||
if month >= 1 && month <= 12 {
|
||||
@ -45,7 +49,8 @@ fn dos_datetime_to_millis(date: u16, time: u16) -> i64 {
|
||||
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 读取器实例(用于保持状态)
|
||||
@ -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>> {
|
||||
// 确保文件列表已加载
|
||||
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("\\") {
|
||||
@ -149,34 +180,27 @@ pub async fn p4k_extract_to_memory(file_path: String) -> Result<Vec<u8>> {
|
||||
.clone()
|
||||
};
|
||||
|
||||
// 在后台线程执行阻塞的提取操作
|
||||
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)
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
/// 提取文件到磁盘
|
||||
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 || {
|
||||
let output = PathBuf::from(&output_path);
|
||||
|
||||
// 创建父目录
|
||||
if let Some(parent) = output.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>(())
|
||||
})
|
||||
.await??;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user