chore: update dependencies and optimise archive operations
This commit is contained in:
parent
00237d5281
commit
95eba5503f
22 changed files with 948 additions and 1032 deletions
1528
Cargo.lock
generated
1528
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
84
Cargo.toml
84
Cargo.toml
|
|
@ -40,7 +40,7 @@ discord-rich-presence = {version = "=1.1.0"}
|
|||
interfrost = {version = "=1.4.0"}
|
||||
|
||||
# logging
|
||||
sentry = {version = "=0.48.5", default-features = false, features = [
|
||||
sentry = {version = "=0.49.3", default-features = false, features = [
|
||||
"backtrace",
|
||||
"contexts",
|
||||
"debug-images",
|
||||
|
|
@ -49,7 +49,7 @@ sentry = {version = "=0.48.5", default-features = false, features = [
|
|||
"reqwest",
|
||||
"rustls",
|
||||
]}
|
||||
sentry-tracing = {version = "=0.48.5"}
|
||||
sentry-tracing = {version = "=0.49.3"}
|
||||
tracing = {version = "=0.1.44"}
|
||||
tracing-futures = {version = "=0.2.5"}
|
||||
tracing-subscriber = {version = "=0.3.23", features = ["env-filter", "fmt", "ansi", "json", "parking_lot", "registry"]}
|
||||
|
|
@ -68,15 +68,15 @@ sqlx = {version = "=0.9.0", default-features = false, features = [
|
|||
]}
|
||||
|
||||
# async
|
||||
astral-tokio-tar = {version = "=0.6.2"}
|
||||
async-compression = {version = "=0.4.42", features = ["tokio", "gzip"]}
|
||||
astral-tokio-tar = {version = "=0.7.0"}
|
||||
async-compression = {version = "=0.4.48", features = ["tokio", "gzip"]}
|
||||
async-stream = {version = "=0.3.6"}
|
||||
async-tempfile = {version = "=0.7.0", features = ["uuid"]}
|
||||
async-trait = {version = "=0.1.89"}
|
||||
async_zip = {version = "=0.0.18", features = ["chrono", "tokio", "tokio-fs", "deflate", "deflate64"]}
|
||||
async-tempfile = {version = "=0.8.0", features = ["uuid"]}
|
||||
async-trait = {version = "=0.1.92"}
|
||||
async_zip = {version = "=0.0.19", features = ["tracing", "chrono", "tokio", "tokio-fs", "deflate", "deflate64"]}
|
||||
futures-lite = {version = "=2.6.1"}
|
||||
futures-util = {version = "=0.3.32", features = ["tokio-io"]}
|
||||
reqwest = {version = "=0.13.3", default-features = false, features = [
|
||||
futures-util = {version = "=0.3.34", features = ["tokio-io"]}
|
||||
reqwest = {version = "=0.13.5", default-features = false, features = [
|
||||
"json",
|
||||
"stream",
|
||||
"multipart",
|
||||
|
|
@ -91,63 +91,63 @@ reqwest = {version = "=0.13.3", default-features = false, features = [
|
|||
"system-proxy",
|
||||
]}
|
||||
reqwest-websocket = {version = "=0.6.0"}
|
||||
tokio = {version = "=1.52.3", features = ["full"]}
|
||||
tokio-stream = {version = "=0.1.18", features = ["full"]}
|
||||
tokio-util = {version = "=0.7.18", features = ["full"]}
|
||||
tokio = {version = "=1.53.1", features = ["full"]}
|
||||
tokio-stream = {version = "=0.1.19", features = ["full"]}
|
||||
tokio-util = {version = "=0.7.19", features = ["full"]}
|
||||
|
||||
# crypto (content hashing)
|
||||
byteorder = {version = "=1.5.0"}
|
||||
digest = {version = "=0.10.7", features = ["std"]}
|
||||
md-5 = {version = "=0.10.6", default-features = false}
|
||||
p256 = {version = "=0.13.2", features = ["ecdsa", "pkcs8"]}
|
||||
sha1 = {version = "=0.10.6", default-features = false}
|
||||
sha2 = {version = "=0.10.9", default-features = false}
|
||||
digest = {version = "=0.11.3"}
|
||||
md-5 = {version = "=0.11.0", default-features = false}
|
||||
p256 = {version = "=0.14.0", features = ["ecdsa", "pkcs8"]}
|
||||
sha1 = {version = "=0.11.0", default-features = false}
|
||||
sha2 = {version = "=0.11.0", default-features = false}
|
||||
|
||||
# util
|
||||
arboard = {version = "=3.6.1", features = ["wayland-data-control"]}
|
||||
arc-swap = {version = "=1.7.1"}
|
||||
base64 = {version = "=0.22.1"}
|
||||
bytes = {version = "=1.11.1"}
|
||||
chrono = {version = "=0.4.44", features = ["serde"]}
|
||||
arc-swap = {version = "=1.9.2"}
|
||||
base64 = {version = "=0.23.1"}
|
||||
bytes = {version = "=1.12.1"}
|
||||
chrono = {version = "=0.4.45", features = ["serde"]}
|
||||
directories = {version = "=6.0.0"}
|
||||
dunce = {version = "=1.0.5"}
|
||||
htmd = {version = "=0.5.4"}
|
||||
indicatif = {version = "=0.18.4", features = ["tokio"]}
|
||||
junction = {version = "=2.0.0"}
|
||||
htmd = {version = "=0.5.5"}
|
||||
indicatif = {version = "=0.18.6", features = ["tokio"]}
|
||||
junction = {version = "=2.1.0"}
|
||||
libc = {version = "=0.2.189"}
|
||||
notify = {version = "=8.2.0"}
|
||||
open = {version = "=5.4.4"}
|
||||
parking_lot = {version = "=0.12.5"}
|
||||
pulldown-cmark = {version = "=0.13.4", default-features = false}
|
||||
rand = {version = "=0.10.1"}
|
||||
regex = {version = "=1.12.3"}
|
||||
rand = {version = "=0.10.3"}
|
||||
regex = {version = "=1.13.1"}
|
||||
rfd = {version = "=0.17.2"}
|
||||
rust-embed = {version = "=8.12.0", features = ["include-exclude", "compression", "debug-embed"]}
|
||||
sanitize-filename = {version = "=0.6.0"}
|
||||
serde = {version = "=1.0.228"}
|
||||
serde_json = {version = "=1.0.150"}
|
||||
serde_repr = {version = "=0.1.20"}
|
||||
sysinfo = {version = "=0.39.5"}
|
||||
toml = {version = "=1.1.4", default-features = false, features = ["parse", "serde"]}
|
||||
trash = {version = "=5.2.6"}
|
||||
serde = {version = "=1.0.229"}
|
||||
serde_json = {version = "=1.0.151"}
|
||||
serde_repr = {version = "=0.1.21"}
|
||||
sysinfo = {version = "=0.39.6"}
|
||||
toml = {version = "=1.1.6", default-features = false, features = ["parse", "serde"]}
|
||||
trash = {version = "=5.2.9"}
|
||||
percent-encoding = {version = "=2.3.2"}
|
||||
url = {version = "=2.5.8", features = ["serde"]}
|
||||
uuid = {version = "=1.23.1", features = ["serde", "v4"]}
|
||||
uuid = {version = "=1.26.1", features = ["serde", "v4"]}
|
||||
windows-sys = {version = "=0.61.2", features = ["Win32_Foundation", "Win32_Storage_FileSystem"]}
|
||||
|
||||
# util (codegen/macro)
|
||||
anyhow = {version = "=1.0.102"}
|
||||
proc-macro2 = {version = "=1.0.106"}
|
||||
quote = {version = "=1.0.45"}
|
||||
anyhow = {version = "=1.0.104"}
|
||||
proc-macro2 = {version = "=1.0.107"}
|
||||
quote = {version = "=1.0.47"}
|
||||
strum = {version = "=0.28.0", features = ["derive"]}
|
||||
syn = {version = "=2.0.117"}
|
||||
thiserror = {version = "=2.0.18"}
|
||||
syn = {version = "=3.0.6"}
|
||||
thiserror = {version = "=2.0.21"}
|
||||
|
||||
# ui
|
||||
freya = {git = "https://github.com/marc2332/freya", tag = "v0.5.0-rc.7", features = ["engine", "winit", "router", "remote-asset", "sdk-tokio", "query", "radio", "borderless", "titlebar", "tray", "metrics"]}
|
||||
freya-core = {git = "https://github.com/marc2332/freya", tag = "v0.5.0-rc.7"}
|
||||
freya-testing = {git = "https://github.com/marc2332/freya", tag = "v0.5.0-rc.7"}
|
||||
freya-skia-safe = "=0.100.0" # required for the SKSL player renderer
|
||||
freya = {git = "https://github.com/marc2332/freya", tag = "v0.5.0-rc.8", features = ["engine", "winit", "router", "remote-asset", "sdk-tokio", "query", "radio", "borderless", "titlebar", "tray", "metrics"]}
|
||||
freya-core = {git = "https://github.com/marc2332/freya", tag = "v0.5.0-rc.8"}
|
||||
freya-testing = {git = "https://github.com/marc2332/freya", tag = "v0.5.0-rc.8"}
|
||||
freya-skia-safe = "=0.100.0-beta.1" # required for the SKSL player renderer
|
||||
image = {version = "=0.25.10", default-features = false, features = ["png", "jpeg"]}
|
||||
|
||||
[profile.release]
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ impl Component for AccountPanel {
|
|||
.opacity(progress)
|
||||
.margin(Gaps::new((1.0 - progress) * -8.0, 0., 0., 0.))
|
||||
.background(colors::page_elevated().with_a(220))
|
||||
.blur(12.)
|
||||
.backdrop_blur(12.)
|
||||
.corner_radius(CornerRadius::new_all(12.))
|
||||
.border(
|
||||
Border::new()
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ impl Component for Button {
|
|||
if enabled() {
|
||||
rect = rect
|
||||
.on_pointer_down(move |_| pressing.set(true))
|
||||
.on_global_pointer_press(move |_| pressing.set(false))
|
||||
.on_global_pointer_up(move |_| pressing.set(false))
|
||||
.on_pointer_over(move |_| hovering.set(true))
|
||||
.on_pointer_out(move |_| {
|
||||
hovering.set(false);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ impl Component for ControlPanel {
|
|||
.opacity(progress)
|
||||
.margin(Gaps::new((1.0 - progress) * -8.0, 0., 0., 0.))
|
||||
.background(colors::page_elevated().with_a(220))
|
||||
.blur(12.)
|
||||
.backdrop_blur(12.)
|
||||
.corner_radius(CornerRadius::new_all(14.))
|
||||
.border(
|
||||
Border::new()
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ impl Component for Dropdown {
|
|||
.map(|b| Size::px(b.width()))
|
||||
.unwrap_or_else(|| self.width.clone());
|
||||
|
||||
let on_global_pointer_press = move |_: Event<PointerEventData>| {
|
||||
let on_global_pointer_up = move |_: Event<PointerEventData>| {
|
||||
open.set_if_modified(false);
|
||||
};
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ impl Component for Dropdown {
|
|||
})
|
||||
.on_pointer_enter(move |_| hovering.set(true))
|
||||
.on_pointer_leave(move |_| hovering.set(false))
|
||||
.on_global_pointer_press(on_global_pointer_press)
|
||||
.on_global_pointer_up(on_global_pointer_up)
|
||||
.on_sized(move |e: Event<SizedEventData>| {
|
||||
button_area.set_if_modified(Some(e.area));
|
||||
})
|
||||
|
|
|
|||
|
|
@ -455,7 +455,7 @@ fn scroll_body(args: BodyArgs) -> Element {
|
|||
.height(Size::px(content_h))
|
||||
.on_pointer_down(on_body_down)
|
||||
.on_capture_global_pointer_move(on_global_move)
|
||||
.on_capture_global_pointer_press(on_global_release)
|
||||
.on_capture_global_pointer_up(on_global_release)
|
||||
.on_global_key_down(on_key_down)
|
||||
.child(log_body(
|
||||
&lines[..],
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ impl Component for NotificationPanel {
|
|||
.opacity(progress)
|
||||
.margin(Gaps::new((1.0 - progress) * -8.0, 0., 0., 0.))
|
||||
.background(colors::page_elevated().with_a(220))
|
||||
.blur(12.)
|
||||
.backdrop_blur(12.)
|
||||
.corner_radius(CornerRadius::new_all(12.))
|
||||
.border(
|
||||
Border::new()
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ impl Component for PlayerModel {
|
|||
Platform::get().send(UserEvent::RequestRedraw);
|
||||
})
|
||||
// Global so a release outside the widget still ends the drag
|
||||
.on_global_pointer_press(move |_: Event<PointerEventData>| {
|
||||
.on_global_pointer_up(move |_: Event<PointerEventData>| {
|
||||
if drag.peek().is_some() {
|
||||
drag.set(None);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ impl Component for OtherVersionsTile {
|
|||
} else {
|
||||
colors::ghost_overlay_hover()
|
||||
})
|
||||
.blur(32.)
|
||||
.backdrop_blur(32.)
|
||||
.a11y_id(a11y_id)
|
||||
.a11y_focusable(true)
|
||||
.a11y_role(AccessibilityRole::Button)
|
||||
|
|
|
|||
|
|
@ -655,7 +655,7 @@ impl ScrollArea {
|
|||
})
|
||||
.on_wheel(on_wheel)
|
||||
.on_capture_global_pointer_move(on_global_move)
|
||||
.on_capture_global_pointer_press(on_global_release)
|
||||
.on_capture_global_pointer_up(on_global_release)
|
||||
.on_global_key_down(move |e: Event<KeyboardEventData>| {
|
||||
let held = e.modifiers.contains(Modifiers::SHIFT)
|
||||
|| matches!(e.code, Code::ShiftLeft | Code::ShiftRight);
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ impl Component for AnimatedAppOutlet {
|
|||
.height(Size::fill())
|
||||
.overflow(Overflow::Clip)
|
||||
.on_global_key_down(on_global_key)
|
||||
.on_global_pointer_press(on_global_pointer)
|
||||
.on_global_pointer_up(on_global_pointer)
|
||||
.maybe_child(overlay)
|
||||
.child(column)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ impl Component for PackageBanner {
|
|||
.position(Position::new_absolute().top(0.).left(0.))
|
||||
.width(Size::fill())
|
||||
.height(Size::fill())
|
||||
.blur(12.)
|
||||
.backdrop_blur(12.)
|
||||
.background(BANNER_BG.with_a(120))
|
||||
.overflow(Overflow::Clip)
|
||||
.layer(Layer::Relative(3)),
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ pub(super) fn toolbar_bar(
|
|||
.direction(Direction::Horizontal)
|
||||
.invert_scroll_wheel(true)
|
||||
.show_scrollbar(true)
|
||||
.scrollbar_theme(tabs_scrollbar_theme())
|
||||
.scrollbar(|context| ScrollBar::new(context).theme(tabs_scrollbar_theme()).into())
|
||||
.width(if stacked {
|
||||
Size::fill()
|
||||
} else {
|
||||
|
|
@ -495,7 +495,7 @@ impl Component for FilterPopover {
|
|||
.offset_y((progress - 1.0) * 6.)
|
||||
.corner_radius(CornerRadius::new_all(10.))
|
||||
.background(colors::page_elevated().with_a(230))
|
||||
.blur(12.)
|
||||
.backdrop_blur(12.)
|
||||
.border(crate::ui::border_all_color(1., colors::component_border()))
|
||||
.shadow(Shadow::from((
|
||||
0.,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#![recursion_limit = "256"]
|
||||
pub mod bundles;
|
||||
pub mod packages;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures_lite::AsyncReadExt;
|
||||
use oneclient_db::models::ClusterRow;
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
|
@ -51,23 +49,19 @@ impl MrpackInstaller {
|
|||
let cluster = PackageStore::get_cluster(cluster_id, ctx).await?;
|
||||
let cluster_root = oneclient_common::paths::clusters_dir()?.join(&cluster.folder_name);
|
||||
|
||||
let bytes = Arc::new(polyio::read(archive_path).await?);
|
||||
|
||||
let mut manifest_bytes: Option<Vec<u8>> = None;
|
||||
|
||||
polyio::read_zip_entries_bytes(bytes.to_vec(), async |_, entry, reader| {
|
||||
let entry_name = entry.filename().as_str().map_err(io_err)?;
|
||||
|
||||
if entry_name == "modrinth.index.json" {
|
||||
let mut buf = Vec::new();
|
||||
reader.read_to_end(&mut buf).await.map_err(io_err)?;
|
||||
manifest_bytes = Some(buf);
|
||||
let file = tokio::fs::File::open(&archive_path).await?;
|
||||
let manifest_bytes = match polyio::try_read_zip_entry_bytes(
|
||||
tokio::io::BufReader::new(file),
|
||||
"modrinth.index.json",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(polyio::IOError::FileNotFoundInZip { .. }) => {
|
||||
return Err(PackageError::UnsupportedModpackFormat.into());
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
result => result?,
|
||||
};
|
||||
|
||||
let manifest_bytes = manifest_bytes.ok_or(PackageError::UnsupportedModpackFormat)?;
|
||||
let manifest: MrpackManifest = serde_json::from_slice(&manifest_bytes)?;
|
||||
|
||||
let mut failed = 0u64;
|
||||
|
|
@ -99,26 +93,15 @@ impl MrpackInstaller {
|
|||
ctx.events
|
||||
.progress(progress_id, "Installing Modpack Files", total, total);
|
||||
|
||||
polyio::read_zip_entries_bytes(bytes.to_vec(), async |_, entry, reader| {
|
||||
let name = entry.filename().as_str().map_err(io_err)?;
|
||||
|
||||
let Some(rest) = name.strip_prefix("overrides/") else {
|
||||
return Ok(());
|
||||
};
|
||||
if rest.is_empty() || name.ends_with('/') {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let dest = cluster_root.join(rest);
|
||||
if let Some(parent) = dest.parent() {
|
||||
polyio::create_dir_all(parent).await.map_err(io_err)?;
|
||||
}
|
||||
|
||||
let mut file_bytes = Vec::new();
|
||||
reader.read_to_end(&mut file_bytes).await.map_err(io_err)?;
|
||||
polyio::write(&dest, &file_bytes).await.map_err(io_err)?;
|
||||
Ok(())
|
||||
})
|
||||
polyio::extract_zip_filtered(
|
||||
&archive_path,
|
||||
&cluster_root,
|
||||
Some(|name: &str| {
|
||||
name.strip_prefix("overrides/")
|
||||
.is_some_and(|rest| !rest.is_empty())
|
||||
}),
|
||||
Some(|name: &str| name.strip_prefix("overrides/").unwrap_or(name).to_string()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if failed > 0 {
|
||||
|
|
@ -183,13 +166,6 @@ fn content_type_from_path(path: &str) -> ContentType {
|
|||
ContentType::from_folder_name(top).unwrap_or(ContentType::Mod)
|
||||
}
|
||||
|
||||
fn io_err(err: impl std::error::Error + Send + Sync + 'static) -> polyio::IOError {
|
||||
polyio::IOError::PathIOError {
|
||||
source: std::io::Error::other(err),
|
||||
path: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(ctx))]
|
||||
pub async fn install_mrpack_to_cluster(
|
||||
archive_path: PathBuf,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ path = "src/lib.rs"
|
|||
default = []
|
||||
|
||||
[dev-dependencies]
|
||||
async-tungstenite = {version = "=0.32.1", features = ["tokio-runtime"]}
|
||||
async-tungstenite = {version = "=0.35.0", features = ["tokio-runtime"]}
|
||||
polyio = {workspace = true, features = ["testing"]}
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use std::panic;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use sentry::protocol::{Breadcrumb, Context, Event};
|
||||
|
|
@ -59,15 +58,13 @@ pub fn init(enabled: bool) -> Option<ClientInitGuard> {
|
|||
|
||||
let guard = sentry::init((
|
||||
SENTRY_DSN,
|
||||
ClientOptions {
|
||||
release: Some(format!("oneclient@{}", env!("CARGO_PKG_VERSION")).into()),
|
||||
environment: Some(ENVIRONMENT.into()),
|
||||
attach_stacktrace: true,
|
||||
send_default_pii: false,
|
||||
before_send: Some(Arc::new(drop_opted_out_events)),
|
||||
before_breadcrumb: Some(Arc::new(drop_opted_out_breadcrumbs)),
|
||||
..Default::default()
|
||||
},
|
||||
ClientOptions::new()
|
||||
.release(format!("oneclient@{}", env!("CARGO_PKG_VERSION")))
|
||||
.environment(ENVIRONMENT)
|
||||
.attach_stacktrace(true)
|
||||
.send_default_pii(false)
|
||||
.before_send(drop_opted_out_events)
|
||||
.before_breadcrumb(drop_opted_out_breadcrumbs),
|
||||
));
|
||||
|
||||
if !guard.is_enabled() {
|
||||
|
|
|
|||
|
|
@ -276,45 +276,22 @@ pub fn processor_arguments<T: AsRef<str>, S: std::hash::BuildHasher>(
|
|||
|
||||
#[tracing::instrument(skip_all, level = "debug")]
|
||||
pub async fn main_class(path: impl AsRef<std::path::Path>) -> McResult<Option<String>> {
|
||||
let data = polyio::read(path.as_ref()).await?;
|
||||
let mut class_name = None;
|
||||
let file = tokio::fs::File::open(path.as_ref()).await?;
|
||||
let manifest = match polyio::try_read_zip_entry_bytes(
|
||||
tokio::io::BufReader::new(file),
|
||||
"META-INF/MANIFEST.MF",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(polyio::IOError::FileNotFoundInZip { .. }) => return Ok(None),
|
||||
result => result?,
|
||||
};
|
||||
|
||||
use futures_util::TryStreamExt;
|
||||
|
||||
let stream = polyio::stream_zip_entries_bytes(data);
|
||||
let mut stream = std::pin::pin!(stream);
|
||||
|
||||
while let Some(item) = stream.try_next().await? {
|
||||
let (index, entry, reader) = item;
|
||||
if entry.dir().map_err(polyio::IOError::from)? {
|
||||
continue;
|
||||
}
|
||||
|
||||
if entry.filename().as_str().map_err(polyio::IOError::from)? != "META-INF/MANIFEST.MF" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut buf = String::new();
|
||||
let mut entry_reader = reader
|
||||
.reader_without_entry(index)
|
||||
.await
|
||||
.map_err(polyio::IOError::from)?;
|
||||
futures_util::AsyncReadExt::read_to_string(&mut entry_reader, &mut buf)
|
||||
.await
|
||||
.map_err(polyio::IOError::from)?;
|
||||
|
||||
for line in buf.lines() {
|
||||
let line = line.trim();
|
||||
if line.starts_with("Main-Class:")
|
||||
&& let Some(class) = line.split(':').nth(1)
|
||||
{
|
||||
class_name = Some(class.trim().to_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(class_name)
|
||||
Ok(String::from_utf8_lossy(&manifest).lines().find_map(|line| {
|
||||
line.trim()
|
||||
.strip_prefix("Main-Class:")
|
||||
.map(|class| class.trim().to_string())
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
|
|
|||
|
|
@ -34,5 +34,5 @@ url.workspace = true
|
|||
uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
async-tungstenite = { version = "=0.32.1", features = ["tokio-runtime"] }
|
||||
async-tungstenite = { version = "=0.35.0", features = ["tokio-runtime"] }
|
||||
tokio = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ futures-lite.workspace = true
|
|||
|
||||
async-tempfile.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-stream.workspace = true
|
||||
async_zip.workspace = true
|
||||
astral-tokio-tar.workspace = true
|
||||
sanitize-filename.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,65 +1,33 @@
|
|||
use std::sync::Arc;
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use async_stream::try_stream;
|
||||
use async_zip::StoredZipEntry;
|
||||
use async_zip::base::read::WithoutEntry;
|
||||
use futures_util::{Stream, TryStreamExt, pin_mut};
|
||||
use tokio_util::compat::TokioAsyncWriteCompatExt;
|
||||
use async_zip::base::read1::ZipOptions;
|
||||
use async_zip::base::read1::seek::ZipArchiveReader;
|
||||
use async_zip::spec::constructs::CDR;
|
||||
use futures_lite::{AsyncBufRead, AsyncSeek};
|
||||
use tokio_util::compat::{Compat, TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
|
||||
|
||||
use crate::{IOError, PolyIOResult};
|
||||
|
||||
#[tracing::instrument(skip(data, f), level = "debug")]
|
||||
pub async fn read_zip_entries_bytes<F>(data: Vec<u8>, mut f: F) -> PolyIOResult<()>
|
||||
where
|
||||
F: AsyncFnMut(
|
||||
usize,
|
||||
&StoredZipEntry,
|
||||
&mut async_zip::base::read::ZipEntryReader<
|
||||
'_,
|
||||
futures_lite::io::Cursor<&[u8]>,
|
||||
WithoutEntry,
|
||||
>,
|
||||
) -> PolyIOResult<()>,
|
||||
{
|
||||
let reader = async_zip::base::read::mem::ZipFileReader::new(data).await?;
|
||||
type FileZipReader = ZipArchiveReader<Compat<tokio::io::BufReader<tokio::fs::File>>>;
|
||||
|
||||
let entries = reader.file().entries();
|
||||
for index in 0..entries.len() {
|
||||
let entry = entries.get(index).expect("expected more zip entries");
|
||||
let mut entry_reader = reader
|
||||
.reader_without_entry(index)
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("expected zip entry at index '{index}'"));
|
||||
|
||||
f(index, entry, &mut entry_reader).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
async fn open_zip<R: AsyncBufRead + AsyncSeek + Unpin>(
|
||||
reader: R,
|
||||
) -> PolyIOResult<ZipArchiveReader<R>> {
|
||||
Ok(ZipArchiveReader::open_with_options(reader, ZipOptions::untrusted()).await?)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(data), level = "debug")]
|
||||
pub fn stream_zip_entries_bytes(
|
||||
data: Vec<u8>,
|
||||
) -> impl Stream<
|
||||
Item = Result<
|
||||
(
|
||||
usize,
|
||||
StoredZipEntry,
|
||||
Arc<async_zip::base::read::mem::ZipFileReader>,
|
||||
),
|
||||
IOError,
|
||||
>,
|
||||
> {
|
||||
try_stream! {
|
||||
let reader = Arc::new(async_zip::base::read::mem::ZipFileReader::new(data).await?);
|
||||
let entries = reader.file().entries();
|
||||
async fn open_zip_file(zip_path: &Path) -> PolyIOResult<FileZipReader> {
|
||||
let file = tokio::fs::File::open(zip_path).await?;
|
||||
open_zip(tokio::io::BufReader::new(file).compat()).await
|
||||
}
|
||||
|
||||
for index in 0..entries.len() {
|
||||
let entry = entries.get(index).expect("expected more zip entries");
|
||||
fn entry_name(cdr: &CDR) -> Cow<'_, str> {
|
||||
String::from_utf8_lossy(cdr.insecure_file_name.as_bytes())
|
||||
}
|
||||
|
||||
yield (index, entry.clone(), reader.clone());
|
||||
}
|
||||
}
|
||||
fn unix_permissions(cdr: &CDR) -> Option<u32> {
|
||||
(cdr.cdrh.v_made_by >> 8 == 3).then_some(cdr.cdrh.exter_attr >> 16)
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
|
|
@ -88,39 +56,15 @@ pub async fn unzip_bytes_filtered(
|
|||
filter_entries: Option<impl Fn(&str) -> bool + Send + Sync>,
|
||||
dest_path: impl AsRef<std::path::Path>,
|
||||
) -> PolyIOResult<()> {
|
||||
let stream = stream_zip_entries_bytes(data);
|
||||
pin_mut!(stream);
|
||||
let reader = open_zip(futures_lite::io::Cursor::new(data)).await?;
|
||||
|
||||
while let Some((index, entry, reader)) = stream.try_next().await? {
|
||||
let file_name = entry.filename().as_str()?;
|
||||
|
||||
if let Some(filter) = &filter_entries
|
||||
&& !filter(file_name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = dest_path.as_ref().join(crate::sanitize_path(file_name));
|
||||
|
||||
let is_dir = entry.dir()?;
|
||||
|
||||
if is_dir {
|
||||
crate::create_dir_all(path).await?;
|
||||
} else {
|
||||
if let Some(parent) = path.parent() {
|
||||
crate::create_dir_all(parent).await?;
|
||||
}
|
||||
|
||||
let entry_reader = reader.reader_without_entry(index).await?;
|
||||
|
||||
let file = tokio::fs::File::create(&path).await?;
|
||||
let writer = tokio::io::BufWriter::new(file);
|
||||
|
||||
futures_lite::io::copy(entry_reader, &mut writer.compat_write()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
extract_entries(
|
||||
reader,
|
||||
dest_path.as_ref(),
|
||||
filter_entries,
|
||||
None::<fn(&str) -> String>,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
|
|
@ -142,7 +86,7 @@ pub async fn extract_zip(
|
|||
extract_zip_filtered(
|
||||
zip_path,
|
||||
dest_path,
|
||||
None::<fn(&StoredZipEntry) -> bool>,
|
||||
None::<fn(&str) -> bool>,
|
||||
None::<fn(&str) -> String>,
|
||||
)
|
||||
.await
|
||||
|
|
@ -176,35 +120,44 @@ where
|
|||
pub async fn extract_zip_filtered(
|
||||
zip_path: impl AsRef<std::path::Path>,
|
||||
dest_path: impl AsRef<std::path::Path>,
|
||||
filter_entries: Option<impl Fn(&StoredZipEntry) -> bool + Send + Sync>,
|
||||
filter_entries: Option<impl Fn(&str) -> bool + Send + Sync>,
|
||||
modify_entry_name: Option<impl Fn(&str) -> String>,
|
||||
) -> PolyIOResult<()> {
|
||||
let zip_path = zip_path.as_ref();
|
||||
let dest_path = dest_path.as_ref();
|
||||
let reader = open_zip_file(zip_path.as_ref()).await?;
|
||||
|
||||
let reader = async_zip::tokio::read::fs::ZipFileReader::new(zip_path).await?;
|
||||
let entries = reader.file().entries();
|
||||
extract_entries(
|
||||
reader,
|
||||
dest_path.as_ref(),
|
||||
filter_entries,
|
||||
modify_entry_name,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
for index in 0..entries.len() {
|
||||
let entry = entries.get(index).expect("expected more zip entries");
|
||||
async fn extract_entries<R: AsyncBufRead + AsyncSeek + Unpin>(
|
||||
mut reader: ZipArchiveReader<R>,
|
||||
dest_path: &Path,
|
||||
filter_entries: Option<impl Fn(&str) -> bool>,
|
||||
modify_entry_name: Option<impl Fn(&str) -> String>,
|
||||
) -> PolyIOResult<()> {
|
||||
let inner = reader.inner().clone();
|
||||
|
||||
for (index, cdr) in inner.cdrs().iter().enumerate() {
|
||||
let old_name = entry_name(cdr);
|
||||
|
||||
if let Some(filter) = &filter_entries
|
||||
&& !filter(entry)
|
||||
&& !filter(&old_name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let old_name = entry.filename().as_str()?;
|
||||
let name: String = modify_entry_name
|
||||
.as_ref()
|
||||
.map_or_else(|| old_name.to_string(), |modify| modify(old_name));
|
||||
.map_or_else(|| old_name.to_string(), |modify| modify(&old_name));
|
||||
|
||||
let name = crate::sanitize_path(name);
|
||||
let path = dest_path.join(crate::sanitize_path(name));
|
||||
|
||||
let path = dest_path.join(name);
|
||||
let is_dir = entry.dir()?;
|
||||
|
||||
if is_dir {
|
||||
if old_name.ends_with('/') {
|
||||
crate::create_dir_all(&path).await?;
|
||||
} else {
|
||||
if let Some(parent) = path.parent() {
|
||||
|
|
@ -213,15 +166,15 @@ pub async fn extract_zip_filtered(
|
|||
|
||||
let file = tokio::fs::File::create(&path).await?;
|
||||
let writer = tokio::io::BufWriter::new(file);
|
||||
let entry_reader = reader.reader_without_entry(index).await?;
|
||||
let entry_reader = reader.file(index).await?;
|
||||
|
||||
futures_lite::io::copy(entry_reader, &mut writer.compat_write()).await?;
|
||||
|
||||
#[cfg(unix)]
|
||||
if let Some(mode) = entry.unix_permissions().filter(|mode| mode & 0o777 != 0) {
|
||||
if let Some(mode) = unix_permissions(cdr).filter(|mode| mode & 0o777 != 0) {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let mode = u32::from(mode & 0o777) | 0o600;
|
||||
let mode = (mode & 0o777) | 0o600;
|
||||
tokio::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode)).await?;
|
||||
}
|
||||
}
|
||||
|
|
@ -248,31 +201,30 @@ pub async fn read_zip_file_entries(
|
|||
filter: impl Fn(&str) -> bool,
|
||||
) -> PolyIOResult<Vec<(String, Vec<u8>)>> {
|
||||
let zip_path = zip_path.as_ref();
|
||||
let reader = async_zip::tokio::read::fs::ZipFileReader::new(zip_path).await?;
|
||||
let entries = reader.file().entries();
|
||||
let mut reader = open_zip_file(zip_path).await?;
|
||||
let inner = reader.inner().clone();
|
||||
|
||||
let mut out = Vec::new();
|
||||
for index in 0..entries.len() {
|
||||
let entry = entries.get(index).expect("expected more zip entries");
|
||||
let Ok(name) = entry.filename().as_str() else {
|
||||
for (index, cdr) in inner.cdrs().iter().enumerate() {
|
||||
let Some(name) = cdr.insecure_file_name.as_str() else {
|
||||
continue;
|
||||
};
|
||||
let name = name.to_string();
|
||||
|
||||
if entry.dir().unwrap_or(false) || !filter(&name) {
|
||||
if name.ends_with('/') || !filter(&name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if entry.uncompressed_size() > MAX_ZIP_ENTRY_BYTES {
|
||||
let uncompressed_size = cdr.uncompressed_size()?;
|
||||
if uncompressed_size > MAX_ZIP_ENTRY_BYTES {
|
||||
tracing::warn!(
|
||||
"skipping '{name}' in {}: declares {} bytes over the {MAX_ZIP_ENTRY_BYTES} byte entry cap",
|
||||
"skipping '{name}' in {}: declares {uncompressed_size} bytes over the {MAX_ZIP_ENTRY_BYTES} byte entry cap",
|
||||
zip_path.display(),
|
||||
entry.uncompressed_size()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry_reader = reader.reader_without_entry(index).await?;
|
||||
let entry_reader = reader.file(index).await?;
|
||||
let mut data = Vec::new();
|
||||
futures_lite::AsyncReadExt::read_to_end(
|
||||
&mut futures_lite::AsyncReadExt::take(entry_reader, MAX_ZIP_ENTRY_BYTES + 1),
|
||||
|
|
@ -300,13 +252,12 @@ pub async fn read_zip_file_entries(
|
|||
fields(zip_path = %zip_path.as_ref().display())
|
||||
)]
|
||||
pub async fn zip_entry_names(zip_path: impl AsRef<std::path::Path>) -> PolyIOResult<Vec<String>> {
|
||||
let reader = async_zip::tokio::read::fs::ZipFileReader::new(zip_path.as_ref()).await?;
|
||||
let reader = open_zip_file(zip_path.as_ref()).await?;
|
||||
|
||||
Ok(reader
|
||||
.file()
|
||||
.entries()
|
||||
.cdrs()
|
||||
.iter()
|
||||
.filter_map(|entry| entry.filename().as_str().ok().map(ToString::to_string))
|
||||
.filter_map(|cdr| cdr.insecure_file_name.as_str().map(ToString::to_string))
|
||||
.collect())
|
||||
}
|
||||
|
||||
|
|
@ -316,21 +267,16 @@ pub async fn try_read_zip_entry_bytes<R>(reader: R, file_name: &str) -> PolyIORe
|
|||
where
|
||||
R: tokio::io::AsyncRead + tokio::io::AsyncBufRead + tokio::io::AsyncSeek + Unpin,
|
||||
{
|
||||
use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||
let compat = reader.compat();
|
||||
|
||||
let mut zip_reader = async_zip::base::read::seek::ZipFileReader::new(compat).await?;
|
||||
let mut zip_reader = open_zip(reader.compat()).await?;
|
||||
|
||||
let index = zip_reader
|
||||
.file()
|
||||
.entries()
|
||||
.iter()
|
||||
.position(|entry| entry.filename().as_str().is_ok_and(|n| n == file_name))
|
||||
.find(file_name.as_bytes())?
|
||||
.next()
|
||||
.ok_or_else(|| IOError::FileNotFoundInZip {
|
||||
file_name: file_name.to_string(),
|
||||
})?;
|
||||
|
||||
let mut entry_reader = zip_reader.reader_without_entry(index).await?;
|
||||
let mut entry_reader = zip_reader.file(index).await?;
|
||||
|
||||
let mut data: Vec<u8> = Vec::new();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue