diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 2cf0eff3..ef29d1c2 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -5,7 +5,7 @@
":semanticCommitsDisabled"
],
"labels": ["renovate"],
- "schedule": ["on friday"],
+ "schedule": ["on friday before 11am"],
"timezone": "Europe/London",
"ignorePaths": [
"**/node_modules/**",
@@ -65,9 +65,8 @@
"matchPackageNames": [
"winsafe", // newer versions causes runtime errors in release builds
"System.CommandLine", // too many breaking changes too frequently
- "Microsoft.Extensions.Logging.Abstractions", // we have multiple versions of this
- "@vercel/webpack-asset-relocator-loader", // an update was incompatible with electron
- "xunit.runner.visualstudio" // 20-12-2024: broke tests (something about sn signing maybe?)
+ "xunit.runner.visualstudio", // 20-12-2024: broke tests (something about sn signing maybe?)
+ "Microsoft.NET.Test.Sdk", // 23-05-2025: 17.13.0 was the last version which supported net6
],
"dependencyDashboardApproval": true
},
diff --git a/set-nbgv-version.ps1 b/.github/set-nbgv-version.ps1
similarity index 95%
rename from set-nbgv-version.ps1
rename to .github/set-nbgv-version.ps1
index a86995dd..882dad50 100644
--- a/set-nbgv-version.ps1
+++ b/.github/set-nbgv-version.ps1
@@ -2,7 +2,7 @@ param(
[string]$version = $(nbgv get-version -v NuGetPackageVersion).Trim()
)
-$scriptDir = $PSScriptRoot
+$scriptDir = "$PSScriptRoot/.."
$path = Join-Path $scriptDir "Cargo.toml"
Write-Host "Setting version to $version"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index bee9d004..d686c988 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -29,43 +29,42 @@ jobs:
key: "rust-build-windows"
- name: Install Dependencies
run: |
- rustup toolchain install 1.75.0-x86_64-pc-windows-msvc
- rustup target add i686-pc-windows-msvc --toolchain 1.75.0-x86_64-pc-windows-msvc
- rustup target add aarch64-pc-windows-msvc --toolchain 1.75.0-x86_64-pc-windows-msvc
+ rustup target add aarch64-pc-windows-msvc --toolchain nightly-x86_64-pc-windows-msvc
+ rustup component add rust-src --toolchain nightly-x86_64-pc-windows-msvc
- name: Update Version
shell: pwsh
- run: .\set-nbgv-version.ps1
- - name: Build Rust Binaries
- run: cargo +1.75.0-x86_64-pc-windows-msvc build --target i686-pc-windows-msvc --features windows --release -p velopack_bins
+ run: ./.github/set-nbgv-version.ps1
+ - name: Build Rust Binaries (x86)
+ run: cargo +nightly build --target i686-win7-windows-msvc -Z build-std="core,alloc,std,panic_abort" --features windows --release -p velopack_bins
- name: Upload Rust Build Artifacts
uses: actions/upload-artifact@v4
with:
name: rust-windows-latest
path: |
- target\i686-pc-windows-msvc\release\*.exe
- target\i686-pc-windows-msvc\release\*.pdb
+ target\i686-win7-windows-msvc\release\*.exe
+ target\i686-win7-windows-msvc\release\*.pdb
- name: Build Rust (x86)
- run: cargo +1.75.0-x86_64-pc-windows-msvc build --target i686-pc-windows-msvc --release -p velopack_nodeffi -p velopack_libc
+ run: cargo +nightly build --target i686-win7-windows-msvc -Z build-std="core,alloc,std,panic_abort" --release -p velopack_nodeffi -p velopack_libc
- name: Build Rust (x64)
- run: cargo +1.75.0-x86_64-pc-windows-msvc build --target x86_64-pc-windows-msvc --release -p velopack_nodeffi -p velopack_libc
+ run: cargo +nightly build --target x86_64-win7-windows-msvc -Z build-std="core,alloc,std,panic_abort" --release -p velopack_nodeffi -p velopack_libc
- name: Build Rust (arm64)
- run: cargo +1.75.0-x86_64-pc-windows-msvc build --target aarch64-pc-windows-msvc --release -p velopack_nodeffi -p velopack_libc
+ run: cargo +nightly build --target aarch64-pc-windows-msvc --release -p velopack_nodeffi -p velopack_libc
- name: Collect Artifacts
run: |
- move target\i686-pc-windows-msvc\release\velopack_nodeffi.dll target\velopack_nodeffi_win_x86_msvc.node
- move target\x86_64-pc-windows-msvc\release\velopack_nodeffi.dll target\velopack_nodeffi_win_x64_msvc.node
+ move target\i686-win7-windows-msvc\release\velopack_nodeffi.dll target\velopack_nodeffi_win_x86_msvc.node
+ move target\x86_64-win7-windows-msvc\release\velopack_nodeffi.dll target\velopack_nodeffi_win_x64_msvc.node
move target\aarch64-pc-windows-msvc\release\velopack_nodeffi.dll target\velopack_nodeffi_win_arm64_msvc.node
- move target\i686-pc-windows-msvc\release\velopack_libc.dll target\velopack_libc_win_x86_msvc.dll
- move target\x86_64-pc-windows-msvc\release\velopack_libc.dll target\velopack_libc_win_x64_msvc.dll
+ move target\i686-win7-windows-msvc\release\velopack_libc.dll target\velopack_libc_win_x86_msvc.dll
+ move target\x86_64-win7-windows-msvc\release\velopack_libc.dll target\velopack_libc_win_x64_msvc.dll
move target\aarch64-pc-windows-msvc\release\velopack_libc.dll target\velopack_libc_win_arm64_msvc.dll
- move target\i686-pc-windows-msvc\release\velopack_libc.dll.lib target\velopack_libc_win_x86_msvc.dll.lib
- move target\x86_64-pc-windows-msvc\release\velopack_libc.dll.lib target\velopack_libc_win_x64_msvc.dll.lib
+ move target\i686-win7-windows-msvc\release\velopack_libc.dll.lib target\velopack_libc_win_x86_msvc.dll.lib
+ move target\x86_64-win7-windows-msvc\release\velopack_libc.dll.lib target\velopack_libc_win_x64_msvc.dll.lib
move target\aarch64-pc-windows-msvc\release\velopack_libc.dll.lib target\velopack_libc_win_arm64_msvc.dll.lib
- move target\i686-pc-windows-msvc\release\velopack_libc.lib target\velopack_libc_win_x86_msvc.lib
- move target\x86_64-pc-windows-msvc\release\velopack_libc.lib target\velopack_libc_win_x64_msvc.lib
+ move target\i686-win7-windows-msvc\release\velopack_libc.lib target\velopack_libc_win_x86_msvc.lib
+ move target\x86_64-win7-windows-msvc\release\velopack_libc.lib target\velopack_libc_win_x64_msvc.lib
move target\aarch64-pc-windows-msvc\release\velopack_libc.lib target\velopack_libc_win_arm64_msvc.lib
- name: Upload Rust Build Artifacts
uses: actions/upload-artifact@v4
@@ -75,9 +74,9 @@ jobs:
target\*.node
target\*.dll
target\*.lib
- - name: Cancel workflow if failed
- uses: andymckay/cancel-action@0.5
- if: ${{ failure() }}
+ # - name: Cancel workflow if failed
+ # uses: andymckay/cancel-action@0.5
+ # if: ${{ failure() }}
build-rust-linux:
runs-on: ubuntu-latest
@@ -85,42 +84,60 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- - uses: Swatinem/rust-cache@v2
- with:
- key: "rust-build-linux"
- name: Install Dependencies
run: |
+ curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
+ cargo binstall cross
rustup target add aarch64-unknown-linux-gnu
sudo apt update
sudo apt install -y g++-aarch64-linux-gnu gcc-aarch64-linux-gnu
+ mkdir ./artifacts
+ # rustup target add x86_64-unknown-linux-musl
+ # rustup target add aarch64-unknown-linux-musl
+ # sudo apt install -y g++-aarch64-linux-gnu gcc-aarch64-linux-gnu musl-tools musl:arm64
- name: Update Version
shell: pwsh
- run: ./set-nbgv-version.ps1
- - name: Build Rust (x64)
+ run: ./.github/set-nbgv-version.ps1
+ - name: Build Rust Binaries (x64)
run: |
- cargo build --release --target x86_64-unknown-linux-gnu
- ldd ./target/x86_64-unknown-linux-gnu/release/update || true
- cp ./target/x86_64-unknown-linux-gnu/release/update ./target/UpdateNix_x64
- cp ./target/x86_64-unknown-linux-gnu/release/libvelopack_nodeffi.so ./target/velopack_nodeffi_linux_x64_gnu.node
- cp ./target/x86_64-unknown-linux-gnu/release/libvelopack_libc.so ./target/velopack_libc_linux_x64_gnu.so
- cp ./target/x86_64-unknown-linux-gnu/release/libvelopack_libc.a ./target/velopack_libc_linux_x64_gnu.a
- - name: Build Rust (arm64)
+ cargo clean
+ cross build --release --target x86_64-unknown-linux-musl -p velopack_bins
+ ldd ./target/x86_64-unknown-linux-musl/release/update || true
+ cp ./target/x86_64-unknown-linux-musl/release/update ./artifacts/UpdateNix_x64
+ - name: Build Rust Binaries (arm64)
run: |
- cargo build --release --target aarch64-unknown-linux-gnu
- ldd ./target/aarch64-unknown-linux-gnu/release/update || true
- cp ./target/aarch64-unknown-linux-gnu/release/update ./target/UpdateNix_arm64
- cp ./target/aarch64-unknown-linux-gnu/release/libvelopack_nodeffi.so ./target/velopack_nodeffi_linux_arm64_gnu.node
- cp ./target/aarch64-unknown-linux-gnu/release/libvelopack_libc.so ./target/velopack_libc_linux_arm64_gnu.so
- cp ./target/aarch64-unknown-linux-gnu/release/libvelopack_libc.a ./target/velopack_libc_linux_arm64_gnu.a
+ cargo clean
+ cross build --release --target aarch64-unknown-linux-musl -p velopack_bins
+ ldd ./target/aarch64-unknown-linux-musl/release/update || true
+ cp ./target/aarch64-unknown-linux-musl/release/update ./artifacts/UpdateNix_arm64
- name: Upload Rust Build Artifacts
uses: actions/upload-artifact@v4
with:
name: rust-ubuntu-latest
path: |
- target/UpdateNix*
- target/*.so
- target/*.node
- target/*.a
+ artifacts/UpdateNix*
+ - name: Build Rust (x64)
+ run: |
+ cargo clean
+ cargo build --release --target x86_64-unknown-linux-gnu -p velopack_nodeffi -p velopack_libc
+ cp ./target/x86_64-unknown-linux-gnu/release/libvelopack_nodeffi.so ./artifacts/velopack_nodeffi_linux_x64_gnu.node
+ cp ./target/x86_64-unknown-linux-gnu/release/libvelopack_libc.so ./artifacts/velopack_libc_linux_x64_gnu.so
+ cp ./target/x86_64-unknown-linux-gnu/release/libvelopack_libc.a ./artifacts/velopack_libc_linux_x64_gnu.a
+ - name: Build Rust (arm64)
+ run: |
+ cargo clean
+ cargo build --release --target aarch64-unknown-linux-gnu -p velopack_nodeffi -p velopack_libc
+ cp ./target/aarch64-unknown-linux-gnu/release/libvelopack_nodeffi.so ./artifacts/velopack_nodeffi_linux_arm64_gnu.node
+ cp ./target/aarch64-unknown-linux-gnu/release/libvelopack_libc.so ./artifacts/velopack_libc_linux_arm64_gnu.so
+ cp ./target/aarch64-unknown-linux-gnu/release/libvelopack_libc.a ./artifacts/velopack_libc_linux_arm64_gnu.a
+ - name: Upload Rust Build Artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: rust-ubuntu-latest-libs
+ path: |
+ artifacts/*.so
+ artifacts/*.node
+ artifacts/*.a
- name: Cancel workflow if failed
uses: andymckay/cancel-action@0.5
if: ${{ failure() }}
@@ -140,7 +157,7 @@ jobs:
dotnet tool update -g nbgv
- name: Update Version
shell: pwsh
- run: ./set-nbgv-version.ps1
+ run: ./.github/set-nbgv-version.ps1
- name: Build Rust (x64)
run: |
cargo build --release --target x86_64-apple-darwin
@@ -191,7 +208,7 @@ jobs:
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Check lib-rust
- run: cargo check -p velopack -F async,delta
+ run: cargo check -p velopack -F async
- name: Check lib-nodejs
working-directory: src/lib-nodejs
run: |
@@ -255,7 +272,7 @@ jobs:
merge-multiple: true
- name: Azure login
uses: azure/login@v2
- if: github.repository == 'velopack/velopack'
+ if: github.event.pull_request.head.repo.full_name == github.repository
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
@@ -316,7 +333,7 @@ jobs:
setAllVars: true
- name: Update Version
shell: pwsh
- run: ./set-nbgv-version.ps1
+ run: ./.github/set-nbgv-version.ps1
- name: Download Rust Artifacts
uses: actions/download-artifact@v4
with:
diff --git a/Cargo.lock b/Cargo.lock
index 994f6921..1c8dbb74 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -93,9 +93,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.97"
+version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
+checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "arbitrary"
@@ -140,14 +140,15 @@ dependencies = [
[[package]]
name = "async-executor"
-version = "1.13.1"
+version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec"
+checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
+ "pin-project-lite",
"slab",
]
@@ -254,9 +255,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.9.0"
+version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
+checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "block-buffer"
@@ -288,15 +289,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "bytemuck"
-version = "1.21.0"
+version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
-
-[[package]]
-name = "byteorder"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
[[package]]
name = "byteorder-lite"
@@ -306,9 +301,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
-version = "1.10.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cbindgen"
@@ -324,16 +319,16 @@ dependencies = [
"quote",
"serde",
"serde_json",
- "syn 2.0.98",
+ "syn 2.0.101",
"tempfile",
- "toml 0.8.20",
+ "toml 0.8.22",
]
[[package]]
name = "cc"
-version = "1.2.15"
+version = "1.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af"
+checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
dependencies = [
"jobserver",
"libc",
@@ -353,10 +348,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
-name = "chrono"
-version = "0.4.40"
+name = "cfg_aliases"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chrono"
+version = "0.4.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -368,9 +369,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.35"
+version = "4.5.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
+checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
dependencies = [
"clap_builder",
"clap_derive",
@@ -378,9 +379,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.35"
+version = "4.5.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
+checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
dependencies = [
"anstream",
"anstyle",
@@ -397,7 +398,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
@@ -461,6 +462,25 @@ dependencies = [
"cfg-if 1.0.0",
]
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
@@ -523,9 +543,9 @@ dependencies = [
[[package]]
name = "deranged"
-version = "0.4.1"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058"
+checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [
"powerfmt",
]
@@ -549,7 +569,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
@@ -607,9 +627,15 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
[[package]]
name = "enum-flags"
version = "0.4.0"
@@ -629,12 +655,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
-version = "0.3.10"
+version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
+checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -656,9 +682,9 @@ dependencies = [
[[package]]
name = "event-listener-strategy"
-version = "0.5.3"
+version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2"
+checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
dependencies = [
"event-listener 5.4.0",
"pin-project-lite",
@@ -691,11 +717,12 @@ dependencies = [
[[package]]
name = "flate2"
-version = "1.0.35"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
+checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [
"crc32fast",
+ "libz-rs-sys",
"miniz_oxide",
]
@@ -716,16 +743,16 @@ dependencies = [
[[package]]
name = "fs_at"
-version = "0.1.10"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "982f82cc75107eef84f417ad6c53ae89bf65b561937ca4a3b3b0fd04d0aa2425"
+checksum = "14af6c9694ea25db25baa2a1788703b9e7c6648dcaeeebeb98f7561b5384c036"
dependencies = [
"aligned",
"cfg-if 1.0.0",
"cvt",
"libc",
"nix",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -780,9 +807,9 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.2.15"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if 1.0.0",
"libc",
@@ -791,14 +818,14 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.3.1"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if 1.0.0",
"libc",
- "wasi 0.13.3+wasi-0.2.2",
- "windows-targets 0.52.6",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
]
[[package]]
@@ -831,9 +858,9 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.15.2"
+version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
[[package]]
name = "heck"
@@ -855,9 +882,9 @@ checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "http"
-version = "1.2.0"
+version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
+checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
@@ -866,22 +893,23 @@ dependencies = [
[[package]]
name = "httparse"
-version = "1.10.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "iana-time-zone"
-version = "0.1.61"
+version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
+checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
+ "log",
"wasm-bindgen",
- "windows-core 0.52.0",
+ "windows-core",
]
[[package]]
@@ -895,21 +923,22 @@ dependencies = [
[[package]]
name = "icu_collections"
-version = "1.5.0"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
dependencies = [
"displaydoc",
+ "potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
-name = "icu_locid"
-version = "1.5.0"
+name = "icu_locale_core"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
dependencies = [
"displaydoc",
"litemap",
@@ -918,31 +947,11 @@ dependencies = [
"zerovec",
]
-[[package]]
-name = "icu_locid_transform"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
-dependencies = [
- "displaydoc",
- "icu_locid",
- "icu_locid_transform_data",
- "icu_provider",
- "tinystr",
- "zerovec",
-]
-
-[[package]]
-name = "icu_locid_transform_data"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
-
[[package]]
name = "icu_normalizer"
-version = "1.5.0"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
dependencies = [
"displaydoc",
"icu_collections",
@@ -950,67 +959,54 @@ dependencies = [
"icu_properties",
"icu_provider",
"smallvec",
- "utf16_iter",
- "utf8_iter",
- "write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
-version = "1.5.0"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
-version = "1.5.1"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a"
dependencies = [
"displaydoc",
"icu_collections",
- "icu_locid_transform",
+ "icu_locale_core",
"icu_properties_data",
"icu_provider",
- "tinystr",
+ "potential_utf",
+ "zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
-version = "1.5.0"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04"
[[package]]
name = "icu_provider"
-version = "1.5.0"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
dependencies = [
"displaydoc",
- "icu_locid",
- "icu_provider_macros",
+ "icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
+ "zerotrie",
"zerovec",
]
-[[package]]
-name = "icu_provider_macros"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.98",
-]
-
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -1030,9 +1026,9 @@ dependencies = [
[[package]]
name = "idna_adapter"
-version = "1.2.0"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
@@ -1056,9 +1052,9 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.7.1"
+version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
+checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown",
@@ -1072,16 +1068,17 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
-version = "1.0.14"
+version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jobserver"
-version = "0.1.32"
+version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
+checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
dependencies = [
+ "getrandom 0.3.3",
"libc",
]
@@ -1122,18 +1119,18 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
-version = "0.2.171"
+version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
+checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libloading"
-version = "0.8.6"
+version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
+checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c"
dependencies = [
"cfg-if 1.0.0",
- "windows-targets 0.52.6",
+ "windows-targets 0.53.0",
]
[[package]]
@@ -1142,10 +1139,19 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
- "bitflags 2.9.0",
+ "bitflags 2.9.1",
"libc",
]
+[[package]]
+name = "libz-rs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a"
+dependencies = [
+ "zlib-rs",
+]
+
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
@@ -1154,21 +1160,15 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
-version = "0.9.2"
+version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litemap"
-version = "0.7.4"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
-
-[[package]]
-name = "lockfree-object-pool"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
+checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]]
name = "log"
@@ -1217,9 +1217,9 @@ dependencies = [
[[package]]
name = "miniz_oxide"
-version = "0.8.5"
+version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
+checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
"simd-adler32",
@@ -1231,7 +1231,7 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d75440242411c87dc39847b0e33e961ec1f10326a9d8ecf9c1ea64a3b3c13dc"
dependencies = [
- "getrandom 0.2.15",
+ "getrandom 0.2.16",
"libloading",
"neon-macros",
"once_cell",
@@ -1247,18 +1247,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6813fde79b646e47e7ad75f480aa80ef76a5d9599e2717407961531169ee38b"
dependencies = [
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
"syn-mid",
]
[[package]]
name = "nix"
-version = "0.26.4"
+version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.9.1",
"cfg-if 1.0.0",
+ "cfg_aliases",
"libc",
]
@@ -1330,15 +1331,15 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.20.3"
+version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "os_info"
-version = "3.10.0"
+version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a604e53c24761286860eba4e2c8b23a0161526476b1de520139d69cdb85a6b5"
+checksum = "41fc863e2ca13dc2d5c34fb22ea4a588248ac14db929616ba65c45f21744b1e9"
dependencies = [
"log",
"serde",
@@ -1382,9 +1383,9 @@ dependencies = [
[[package]]
name = "pkg-config"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "png"
@@ -1414,6 +1415,15 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "potential_utf"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
+dependencies = [
+ "zerovec",
+]
+
[[package]]
name = "powerfmt"
version = "0.2.0"
@@ -1422,11 +1432,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
-version = "0.2.20"
+version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
- "zerocopy 0.7.35",
+ "zerocopy",
]
[[package]]
@@ -1450,40 +1460,51 @@ dependencies = [
[[package]]
name = "proc-macro-crate"
-version = "3.2.0"
+version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
+checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
-version = "1.0.93"
+version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
-name = "quote"
-version = "1.0.38"
+name = "progress-streams"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
+checksum = "e965d96c8162c607b0cd8d66047ad3c9fd35273c134d994327882c6e47f986a7"
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
-name = "rand"
-version = "0.9.0"
+name = "r-efi"
+version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
+checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+
+[[package]]
+name = "rand"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha",
"rand_core",
- "zerocopy 0.8.20",
]
[[package]]
@@ -1498,12 +1519,31 @@ dependencies = [
[[package]]
name = "rand_core"
-version = "0.9.2"
+version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
- "getrandom 0.3.1",
- "zerocopy 0.8.20",
+ "getrandom 0.3.3",
+]
+
+[[package]]
+name = "rayon"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
]
[[package]]
@@ -1512,7 +1552,7 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
- "getrandom 0.2.15",
+ "getrandom 0.2.16",
"libredox",
"thiserror 1.0.69",
]
@@ -1548,28 +1588,27 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "remove_dir_all"
-version = "0.8.2"
-source = "git+https://github.com/caesay/remove_dir_all.git#c98142b9150c53e6c5f56e752d2bf93433f2e207"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cc0b475acf76adf36f08ca49429b12aad9f678cb56143d5b3cb49b9a1dd08"
dependencies = [
"cfg-if 1.0.0",
"cvt",
"fs_at",
"libc",
- "log",
"normpath",
- "same-file",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
name = "ring"
-version = "0.17.11"
+version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if 1.0.0",
- "getrandom 0.2.15",
+ "getrandom 0.2.16",
"libc",
"untrusted",
"windows-sys 0.52.0",
@@ -1592,31 +1631,31 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
- "bitflags 2.9.0",
+ "bitflags 2.9.1",
"errno",
"libc",
"linux-raw-sys 0.4.15",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
name = "rustix"
-version = "1.0.1"
+version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
- "bitflags 2.9.0",
+ "bitflags 2.9.1",
"errno",
"libc",
- "linux-raw-sys 0.9.2",
- "windows-sys 0.52.0",
+ "linux-raw-sys 0.9.4",
+ "windows-sys 0.59.0",
]
[[package]]
name = "rustls"
-version = "0.23.23"
+version = "0.23.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
+checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
dependencies = [
"log",
"once_cell",
@@ -1638,15 +1677,18 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
-version = "1.11.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
+checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
+dependencies = [
+ "zeroize",
+]
[[package]]
name = "rustls-webpki"
-version = "0.102.8"
+version = "0.103.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
+checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
dependencies = [
"ring",
"rustls-pki-types",
@@ -1655,15 +1697,15 @@ dependencies = [
[[package]]
name = "rustversion"
-version = "1.0.19"
+version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
+checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "ryu"
-version = "1.0.19"
+version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
@@ -1703,7 +1745,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
@@ -1746,9 +1788,9 @@ checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
[[package]]
name = "sha2"
-version = "0.10.8"
+version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
@@ -1798,9 +1840,9 @@ dependencies = [
[[package]]
name = "smallvec"
-version = "1.14.0"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
+checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]]
name = "stable_deref_trait"
@@ -1839,7 +1881,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
@@ -1861,9 +1903,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.98"
+version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
+checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
@@ -1878,31 +1920,31 @@ checksum = "b5dc35bb08dd1ca3dfb09dce91fd2d13294d6711c88897d9a9d60acf39bce049"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
name = "synstructure"
-version = "0.13.1"
+version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
name = "tempfile"
-version = "3.19.1"
+version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
+checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
dependencies = [
"fastrand",
- "getrandom 0.3.1",
+ "getrandom 0.3.3",
"once_cell",
- "rustix 1.0.1",
- "windows-sys 0.52.0",
+ "rustix 1.0.7",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -1940,7 +1982,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
@@ -1951,7 +1993,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
@@ -2000,9 +2042,9 @@ dependencies = [
[[package]]
name = "tinystr"
-version = "0.7.6"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
dependencies = [
"displaydoc",
"zerovec",
@@ -2019,9 +2061,9 @@ dependencies = [
[[package]]
name = "toml"
-version = "0.8.20"
+version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
+checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
dependencies = [
"serde",
"serde_spanned",
@@ -2031,26 +2073,33 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "0.6.8"
+version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
-version = "0.22.24"
+version = "0.22.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
+checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
+ "toml_write",
"winnow",
]
+[[package]]
+name = "toml_write"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
+
[[package]]
name = "tracing"
version = "0.1.41"
@@ -2086,7 +2135,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
"termcolor",
]
@@ -2098,9 +2147,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicode-ident"
-version = "1.0.17"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "untrusted"
@@ -2110,9 +2159,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
-version = "3.0.10"
+version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b0351ca625c7b41a8e4f9bb6c5d9755f67f62c2187ebedecacd9974674b271d"
+checksum = "b7a3e9af6113ecd57b8c63d3cd76a385b2e3881365f1f489e54f49801d0c83ea"
dependencies = [
"base64",
"flate2",
@@ -2123,14 +2172,14 @@ dependencies = [
"rustls-pki-types",
"ureq-proto",
"utf-8",
- "webpki-roots",
+ "webpki-roots 0.26.11",
]
[[package]]
name = "ureq-proto"
-version = "0.3.5"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae239d0a3341aebc94259414d1dc67cfce87d41cbebc816772c91b77902fafa4"
+checksum = "fadf18427d33828c311234884b7ba2afb57143e6e7e69fda7ee883b624661e36"
dependencies = [
"base64",
"http",
@@ -2155,12 +2204,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
-[[package]]
-name = "utf16_iter"
-version = "1.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
-
[[package]]
name = "utf8_iter"
version = "1.0.4"
@@ -2179,7 +2222,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
dependencies = [
- "getrandom 0.3.1",
+ "getrandom 0.3.3",
"rand",
"uuid-macro-internal",
]
@@ -2192,21 +2235,21 @@ checksum = "72dcd78c4f979627a754f5522cea6e6a25e55139056535fe6e69c506cd64a862"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
name = "value-bag"
-version = "1.10.0"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2"
+checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
[[package]]
name = "velopack"
version = "0.0.0-local"
dependencies = [
"async-std",
- "bitflags 2.9.0",
+ "bitflags 2.9.1",
"derivative",
"file-rotate",
"glob",
@@ -2234,7 +2277,6 @@ dependencies = [
"windows",
"xml",
"zip",
- "zstd",
]
[[package]]
@@ -2242,7 +2284,7 @@ name = "velopack_bins"
version = "0.0.0-local"
dependencies = [
"anyhow",
- "bitflags 2.9.0",
+ "bitflags 2.9.1",
"chrono",
"clap",
"core-foundation",
@@ -2251,6 +2293,7 @@ dependencies = [
"dialog",
"enum-flags",
"file-rotate",
+ "flate2",
"fs_extra",
"glob",
"image",
@@ -2265,7 +2308,9 @@ dependencies = [
"os_info",
"pretty-bytes-rust",
"pretty_assertions",
+ "progress-streams",
"rand",
+ "rayon",
"regex",
"remove_dir_all",
"same-file",
@@ -2280,10 +2325,13 @@ dependencies = [
"velopack",
"wait-timeout",
"waitpid-any",
+ "walkdir",
"webview2-com-sys",
"windows",
"winres",
"winsafe",
+ "zip",
+ "zstd",
]
[[package]]
@@ -2336,10 +2384,20 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18aa3ce681e189f125c4c1e1388c03285e2fd434ef52c7203084012ac29c5e4a"
dependencies = [
- "rustix 1.0.1",
+ "rustix 1.0.7",
"windows-sys 0.59.0",
]
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
@@ -2354,9 +2412,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
-version = "0.13.3+wasi-0.2.2"
+version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
@@ -2383,7 +2441,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
"wasm-bindgen-shared",
]
@@ -2418,7 +2476,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -2444,9 +2502,18 @@ dependencies = [
[[package]]
name = "webpki-roots"
-version = "0.26.8"
+version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9"
+checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
+dependencies = [
+ "webpki-roots 1.0.0",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb"
dependencies = [
"rustls-pki-types",
]
@@ -2459,7 +2526,7 @@ checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295"
dependencies = [
"thiserror 2.0.12",
"windows",
- "windows-core 0.61.0",
+ "windows-core",
]
[[package]]
@@ -2518,7 +2585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
dependencies = [
"windows-collections",
- "windows-core 0.61.0",
+ "windows-core",
"windows-future",
"windows-link",
"windows-numerics",
@@ -2530,23 +2597,14 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
- "windows-core 0.61.0",
+ "windows-core",
]
[[package]]
name = "windows-core"
-version = "0.52.0"
+version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-core"
-version = "0.61.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
+checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40"
dependencies = [
"windows-implement",
"windows-interface",
@@ -2557,12 +2615,13 @@ dependencies = [
[[package]]
name = "windows-future"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
+checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
- "windows-core 0.61.0",
+ "windows-core",
"windows-link",
+ "windows-threading",
]
[[package]]
@@ -2573,7 +2632,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
@@ -2584,7 +2643,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
@@ -2599,37 +2658,28 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
- "windows-core 0.61.0",
+ "windows-core",
"windows-link",
]
[[package]]
name = "windows-result"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
+checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
-version = "0.4.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
+checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a"
dependencies = [
"windows-link",
]
-[[package]]
-name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets 0.48.5",
-]
-
[[package]]
name = "windows-sys"
version = "0.52.0"
@@ -2648,21 +2698,6 @@ dependencies = [
"windows-targets 0.52.6",
]
-[[package]]
-name = "windows-targets"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
-dependencies = [
- "windows_aarch64_gnullvm 0.48.5",
- "windows_aarch64_msvc 0.48.5",
- "windows_i686_gnu 0.48.5",
- "windows_i686_msvc 0.48.5",
- "windows_x86_64_gnu 0.48.5",
- "windows_x86_64_gnullvm 0.48.5",
- "windows_x86_64_msvc 0.48.5",
-]
-
[[package]]
name = "windows-targets"
version = "0.52.6"
@@ -2672,7 +2707,7 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
- "windows_i686_gnullvm",
+ "windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
@@ -2680,10 +2715,29 @@ dependencies = [
]
[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.48.5"
+name = "windows-targets"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b"
+dependencies = [
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm 0.53.0",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
+]
+
+[[package]]
+name = "windows-threading"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
+dependencies = [
+ "windows-link",
+]
[[package]]
name = "windows_aarch64_gnullvm"
@@ -2692,10 +2746,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.5"
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
@@ -2704,10 +2758,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
-name = "windows_i686_gnu"
-version = "0.48.5"
+name = "windows_aarch64_msvc"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
@@ -2715,6 +2769,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
@@ -2722,10 +2782,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
-name = "windows_i686_msvc"
-version = "0.48.5"
+name = "windows_i686_gnullvm"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
@@ -2734,10 +2794,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
-name = "windows_x86_64_gnu"
-version = "0.48.5"
+name = "windows_i686_msvc"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
@@ -2746,10 +2806,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.48.5"
+name = "windows_x86_64_gnu"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
@@ -2758,10 +2818,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.5"
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
@@ -2770,10 +2830,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
-name = "winnow"
-version = "0.7.3"
+name = "windows_x86_64_msvc"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
+name = "winnow"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
dependencies = [
"memchr",
]
@@ -2795,24 +2861,18 @@ checksum = "a40369220be405a294b88b13ccc3d916fac12423250b30092c8c4ea19001f7f1"
[[package]]
name = "wit-bindgen-rt"
-version = "0.33.0"
+version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
- "bitflags 2.9.0",
+ "bitflags 2.9.1",
]
-[[package]]
-name = "write16"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
-
[[package]]
name = "writeable"
-version = "0.5.5"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "xml"
@@ -2825,9 +2885,9 @@ dependencies = [
[[package]]
name = "xml-rs"
-version = "0.8.25"
+version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4"
+checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda"
[[package]]
name = "yansi"
@@ -2837,9 +2897,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "yoke"
-version = "0.7.5"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
dependencies = [
"serde",
"stable_deref_trait",
@@ -2849,75 +2909,54 @@ dependencies = [
[[package]]
name = "yoke-derive"
-version = "0.7.5"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
"synstructure",
]
[[package]]
name = "zerocopy"
-version = "0.7.35"
+version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [
- "byteorder",
- "zerocopy-derive 0.7.35",
-]
-
-[[package]]
-name = "zerocopy"
-version = "0.8.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c"
-dependencies = [
- "zerocopy-derive 0.8.20",
+ "zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.7.35"
+version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.8.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
name = "zerofrom"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
"synstructure",
]
@@ -2928,10 +2967,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
-name = "zerovec"
-version = "0.10.4"
+name = "zerotrie"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
dependencies = [
"yoke",
"zerofrom",
@@ -2940,24 +2990,23 @@ dependencies = [
[[package]]
name = "zerovec-derive"
-version = "0.10.3"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn 2.0.101",
]
[[package]]
name = "zip"
-version = "2.6.0"
+version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "febbe83a485467affa75a75d28dc7494acd2f819e549536c47d46b3089b56164"
+checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308"
dependencies = [
"arbitrary",
"crc32fast",
- "crossbeam-utils",
"flate2",
"indexmap",
"memchr",
@@ -2965,16 +3014,20 @@ dependencies = [
]
[[package]]
-name = "zopfli"
-version = "0.8.1"
+name = "zlib-rs"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
+checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8"
+
+[[package]]
+name = "zopfli"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
dependencies = [
"bumpalo",
"crc32fast",
- "lockfree-object-pool",
"log",
- "once_cell",
"simd-adler32",
]
@@ -2989,18 +3042,18 @@ dependencies = [
[[package]]
name = "zstd-safe"
-version = "7.2.3"
+version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722"
+checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
-version = "2.0.14+zstd.1.5.7"
+version = "2.0.15+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5"
+checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
dependencies = [
"cc",
"pkg-config",
diff --git a/Cargo.toml b/Cargo.toml
index 1f31f610..a1ee1b59 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,12 +34,12 @@ derivative = "2.2"
glob = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
-zip = { version = "2.2", default-features = false, features = ["deflate"] }
+zip = { version = "3.0", default-features = false, features = ["deflate"] }
thiserror = "2.0"
lazy_static = "1.5"
regex = "1.10"
normpath = "1.3"
-bitflags = "2.6"
+bitflags = "2.9"
rand = "0.9"
ts-rs = "10.0"
zstd = "0.13"
@@ -54,7 +54,7 @@ strum = { version = "0.27", features = ["derive"] }
file-rotate = "0.8"
simple-stopwatch = "0.1"
enum-flags = "0.4"
-remove_dir_all = { git = "https://github.com/caesay/remove_dir_all.git", features = ["log"] }
+remove_dir_all = "1.0"
sha1 = "0.10"
sha2 = "0.10"
sha1_smol = "1.0"
@@ -83,6 +83,13 @@ log-panics = "2.1.0"
core-foundation = "0.10"
core-foundation-sys = "0.8"
uuid = { version = "1.13.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
+walkdir = "2.5"
+rayon = "1.6"
+progress-streams = "1.1"
+flate2 = { version = "1.0", default-features = false }
+# mtzip = "=4.0.2"
+# ripunzip = "=2.0.1"
+# zerofrom = "=0.1.5"
# default to small, optimized workspace release binaries
[profile.release]
diff --git a/README.md b/README.md
index d025b3a7..c415e42b 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,10 @@
---
[](https://www.nuget.org/packages/Velopack/)
-[](https://discord.gg/CjrCrNzd3F)
+[](https://discord.gg/M6he8ZPAAJ)
[](https://github.com/velopack/velopack/actions)
[](https://app.codecov.io/gh/velopack/velopack)
[](https://github.com/velopack/velopack/blob/develop/LICENSE)
-[](https://hits.seeyoufarm.com)
Velopack is an installation and auto-update framework for cross-platform applications. It's opinionated, extremely easy to use with zero config needed. With just one command you can be up and running with an installable application, and it's lightning fast for your users, too.
@@ -50,4 +49,4 @@ I've used a lot of installer frameworks and Velopack is by far the best. Everyth
[- RandomEngy (Discord)](https://discord.com/channels/767856501477343282/947444323765583913/1200897478036299861)
I'm extremely impressed with Velopack's performance in creating releases, as well as checking for and applying updates. It is significantly faster than other tools. The vpk CLI is intuitive and easy to implement, even with my complex build pipeline. Thanks to Velopack, I've been able to streamline my workflow and save valuable time. It's a fantastic tool that I highly recommend!
-[- khdc (Discord)](https://discord.com/channels/767856501477343282/947444323765583913/1216460920696344576)
\ No newline at end of file
+[- khdc (Discord)](https://discord.com/channels/767856501477343282/947444323765583913/1216460920696344576)
diff --git a/samples/CSharpAvalonia/CSharpAvalonia.csproj b/samples/CSharpAvalonia/CSharpAvalonia.csproj
index 023e1d97..54072f03 100644
--- a/samples/CSharpAvalonia/CSharpAvalonia.csproj
+++ b/samples/CSharpAvalonia/CSharpAvalonia.csproj
@@ -10,12 +10,12 @@
-
-
-
-
+
+
+
+
-
+
diff --git a/samples/CSharpUno/global.json b/samples/CSharpUno/global.json
index e385cb20..bbbdbf58 100644
--- a/samples/CSharpUno/global.json
+++ b/samples/CSharpUno/global.json
@@ -1,7 +1,7 @@
{
// To update the version of Uno please update the version of the Uno.Sdk here. See https://aka.platform.uno/upgrade-uno-packages for more information.
"msbuild-sdks": {
- "Uno.Sdk": "5.6.51"
+ "Uno.Sdk": "5.6.54"
},
"sdk":{
"allowPrerelease": false
diff --git a/samples/NodeJSElectron/package-lock.json b/samples/NodeJSElectron/package-lock.json
index 39a23ff1..c84e1fdd 100644
--- a/samples/NodeJSElectron/package-lock.json
+++ b/samples/NodeJSElectron/package-lock.json
@@ -18,7 +18,7 @@
"@electron/fuses": "^1.8.0",
"@vercel/webpack-asset-relocator-loader": "=1.7.3",
"css-loader": "^7.0.0",
- "electron": "35.1.4",
+ "electron": "36.2.0",
"fork-ts-checker-webpack-plugin": "^9.0.0",
"node-loader": "^2.0.0",
"style-loader": "^4.0.0",
@@ -78,9 +78,9 @@
}
},
"node_modules/@electron-forge/cli": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/cli/-/cli-7.8.0.tgz",
- "integrity": "sha512-XZ+Hg7pxeE9pgrahqcpMlND+VH0l0UTZLyO5wkI+YfanNyBQksB2mw24XeEtCA6x8F2IaEYdIGgijmPF6qpjzA==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/cli/-/cli-7.8.1.tgz",
+ "integrity": "sha512-QI3EShutfq9Y+2TWWrPjm4JZM3eSAKzoQvRZdVhAfVpUbyJ8K23VqJShg3kGKlPf9BXHAGvE+8LyH5s2yDr1qA==",
"dev": true,
"funding": [
{
@@ -94,9 +94,9 @@
],
"license": "MIT",
"dependencies": {
- "@electron-forge/core": "7.8.0",
- "@electron-forge/core-utils": "7.8.0",
- "@electron-forge/shared-types": "7.8.0",
+ "@electron-forge/core": "7.8.1",
+ "@electron-forge/core-utils": "7.8.1",
+ "@electron-forge/shared-types": "7.8.1",
"@electron/get": "^3.0.0",
"chalk": "^4.0.0",
"commander": "^11.1.0",
@@ -116,9 +116,9 @@
}
},
"node_modules/@electron-forge/core": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/core/-/core-7.8.0.tgz",
- "integrity": "sha512-7byf660ECZND+irOhGxvpmRXjk1bMrsTWh5J2AZMEvaXI8tub9OrZY9VSbi5fcDt0lpHPKmgVk7NRf/ZjJ+beQ==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/core/-/core-7.8.1.tgz",
+ "integrity": "sha512-jkh0QPW5p0zmruu1E8+2XNufc4UMxy13WLJcm7hn9jbaXKLkMbKuEvhrN1tH/9uGp1mhr/t8sC4N67gP+gS87w==",
"dev": true,
"funding": [
{
@@ -132,17 +132,17 @@
],
"license": "MIT",
"dependencies": {
- "@electron-forge/core-utils": "7.8.0",
- "@electron-forge/maker-base": "7.8.0",
- "@electron-forge/plugin-base": "7.8.0",
- "@electron-forge/publisher-base": "7.8.0",
- "@electron-forge/shared-types": "7.8.0",
- "@electron-forge/template-base": "7.8.0",
- "@electron-forge/template-vite": "7.8.0",
- "@electron-forge/template-vite-typescript": "7.8.0",
- "@electron-forge/template-webpack": "7.8.0",
- "@electron-forge/template-webpack-typescript": "7.8.0",
- "@electron-forge/tracer": "7.8.0",
+ "@electron-forge/core-utils": "7.8.1",
+ "@electron-forge/maker-base": "7.8.1",
+ "@electron-forge/plugin-base": "7.8.1",
+ "@electron-forge/publisher-base": "7.8.1",
+ "@electron-forge/shared-types": "7.8.1",
+ "@electron-forge/template-base": "7.8.1",
+ "@electron-forge/template-vite": "7.8.1",
+ "@electron-forge/template-vite-typescript": "7.8.1",
+ "@electron-forge/template-webpack": "7.8.1",
+ "@electron-forge/template-webpack-typescript": "7.8.1",
+ "@electron-forge/tracer": "7.8.1",
"@electron/get": "^3.0.0",
"@electron/packager": "^18.3.5",
"@electron/rebuild": "^3.7.0",
@@ -156,6 +156,7 @@
"global-dirs": "^3.0.0",
"got": "^11.8.5",
"interpret": "^3.1.1",
+ "jiti": "^2.4.2",
"listr2": "^7.0.2",
"lodash": "^4.17.20",
"log-symbols": "^4.0.0",
@@ -171,13 +172,13 @@
}
},
"node_modules/@electron-forge/core-utils": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/core-utils/-/core-utils-7.8.0.tgz",
- "integrity": "sha512-ZioRzqkXVOGuwkfvXN/FPZxcssJ9AkOZx6RvxomQn90F77G2KfEbw4ZwAxVTQ+jWNUzydTic5qavWle++Y5IeA==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/core-utils/-/core-utils-7.8.1.tgz",
+ "integrity": "sha512-mRoPLDNZgmjyOURE/K0D3Op53XGFmFRgfIvFC7c9S/BqsRpovVblrqI4XxPRdNmH9dvhd8On9gGz+XIYAKD3aQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.8.0",
+ "@electron-forge/shared-types": "7.8.1",
"@electron/rebuild": "^3.7.0",
"@malept/cross-spawn-promise": "^2.0.0",
"chalk": "^4.0.0",
@@ -192,13 +193,13 @@
}
},
"node_modules/@electron-forge/maker-base": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/maker-base/-/maker-base-7.8.0.tgz",
- "integrity": "sha512-yGRvz70w+NnKO7PhzNFRgYM+x6kxYFgpbChJIQBs3WChd9bGjL+MZLrwYqmxOFLpWNwRAJ6PEi4E/8U5GgV6AQ==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/maker-base/-/maker-base-7.8.1.tgz",
+ "integrity": "sha512-GUZqschGuEBzSzE0bMeDip65IDds48DZXzldlRwQ+85SYVA6RMU2AwDDqx3YiYsvP2OuxKruuqIJZtOF5ps4FQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.8.0",
+ "@electron-forge/shared-types": "7.8.1",
"fs-extra": "^10.0.0",
"which": "^2.0.2"
},
@@ -207,14 +208,14 @@
}
},
"node_modules/@electron-forge/maker-deb": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/maker-deb/-/maker-deb-7.8.0.tgz",
- "integrity": "sha512-9jjhLm/1IBIo0UuRdELgvBhUkNjK3tHNlUsrqeb8EJwWJZShbPwHYZJj+VbgjQfJFFzhHwBBDJViBXJ/4ePv+g==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/maker-deb/-/maker-deb-7.8.1.tgz",
+ "integrity": "sha512-tjjeesQtCP5Xht1X7gl4+K9bwoETPmQfBkOVAY/FZIxPj40uQh/hOUtLX2tYENNGNVZ1ryDYRs8TuPi+I41Vfw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/maker-base": "7.8.0",
- "@electron-forge/shared-types": "7.8.0"
+ "@electron-forge/maker-base": "7.8.1",
+ "@electron-forge/shared-types": "7.8.1"
},
"engines": {
"node": ">= 16.4.0"
@@ -224,14 +225,14 @@
}
},
"node_modules/@electron-forge/maker-rpm": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/maker-rpm/-/maker-rpm-7.8.0.tgz",
- "integrity": "sha512-oTH951NE39LOX2wYMg+C06vBZDWUP/0dsK01PlXEl5e5YfQM5Cifsk3E7BzE6BpZdWRJL3k/ETqpyYeIGNb1jw==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/maker-rpm/-/maker-rpm-7.8.1.tgz",
+ "integrity": "sha512-TF6wylft3BHkw9zdHcxmjEPBZYgTIc0jE31skFnMEQ/aExbNRiNaCZvsXy+7ptTWZxhxUKRc9KHhLFRMCmOK8g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/maker-base": "7.8.0",
- "@electron-forge/shared-types": "7.8.0"
+ "@electron-forge/maker-base": "7.8.1",
+ "@electron-forge/shared-types": "7.8.1"
},
"engines": {
"node": ">= 16.4.0"
@@ -241,14 +242,14 @@
}
},
"node_modules/@electron-forge/maker-zip": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/maker-zip/-/maker-zip-7.8.0.tgz",
- "integrity": "sha512-7MLD7GkZdlGecC9GvgBu0sWYt48p3smYvr+YCwlpdH1CTeLmWhvCqeH33a2AB0XI5CY8U8jnkG2jgdTkzr/EQw==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/maker-zip/-/maker-zip-7.8.1.tgz",
+ "integrity": "sha512-unIxEoV1lnK4BLVqCy3L2y897fTyg8nKY1WT4rrpv0MUKnQG4qmigDfST5zZNNHHaulEn/ElAic2GEiP7d6bhQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/maker-base": "7.8.0",
- "@electron-forge/shared-types": "7.8.0",
+ "@electron-forge/maker-base": "7.8.1",
+ "@electron-forge/shared-types": "7.8.1",
"cross-zip": "^4.0.0",
"fs-extra": "^10.0.0",
"got": "^11.8.5"
@@ -258,41 +259,41 @@
}
},
"node_modules/@electron-forge/plugin-auto-unpack-natives": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/plugin-auto-unpack-natives/-/plugin-auto-unpack-natives-7.8.0.tgz",
- "integrity": "sha512-JGal5ltZmbTQ5rNq67OgGC4MJ2zjjFW0fqykHy8X9J8cgaH7SRdKkT4yYZ8jH01IAF1J57FD2zIob1MvcBqjcg==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/plugin-auto-unpack-natives/-/plugin-auto-unpack-natives-7.8.1.tgz",
+ "integrity": "sha512-4URAgWX9qqqKe6Bfad0VmpFRrwINYMODfKGd2nFQrfHxmBtdpXnsWlLwVGE/wGssIQaTMI5bWQ6F2RNeXTgnhA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/plugin-base": "7.8.0",
- "@electron-forge/shared-types": "7.8.0"
+ "@electron-forge/plugin-base": "7.8.1",
+ "@electron-forge/shared-types": "7.8.1"
},
"engines": {
"node": ">= 16.4.0"
}
},
"node_modules/@electron-forge/plugin-base": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/plugin-base/-/plugin-base-7.8.0.tgz",
- "integrity": "sha512-rDeeChRWIp5rQVo3Uc1q0ncUvA+kWWURW7tMuQjPvy2qVSgX+jIf5krk+T1Dp06+D4YZzEIrkibRaamAaIcR1w==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/plugin-base/-/plugin-base-7.8.1.tgz",
+ "integrity": "sha512-iCZC2d7CbsZ9l6j5d+KPIiyQx0U1QBfWAbKnnQhWCSizjcrZ7A9V4sMFZeTO6+PVm48b/r9GFPm+slpgZtYQLg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.8.0"
+ "@electron-forge/shared-types": "7.8.1"
},
"engines": {
"node": ">= 16.4.0"
}
},
"node_modules/@electron-forge/plugin-fuses": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/plugin-fuses/-/plugin-fuses-7.8.0.tgz",
- "integrity": "sha512-ZxFtol3aHNY+oYrZWa7EDBLl4uk/+NlOCJmqC7C32R/3S/Kn2ebVRxpLwrFM12KtHeD+Z3gmZNBhwOe0TECgOA==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/plugin-fuses/-/plugin-fuses-7.8.1.tgz",
+ "integrity": "sha512-dYTwvbV1HcDOIQ0wTybpdtPq6YoBYXIWBTb7DJuvFu/c/thj1eoEdnbwr8mT9hEivjlu5p4ls46n16P5EtZ0oA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/plugin-base": "7.8.0",
- "@electron-forge/shared-types": "7.8.0"
+ "@electron-forge/plugin-base": "7.8.1",
+ "@electron-forge/shared-types": "7.8.1"
},
"engines": {
"node": ">= 16.4.0"
@@ -302,16 +303,16 @@
}
},
"node_modules/@electron-forge/plugin-webpack": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/plugin-webpack/-/plugin-webpack-7.8.0.tgz",
- "integrity": "sha512-9X/+OLoGgzCjqdDAT12O1UQeS+P0RjoEdD4ms53yiWUaKAwrSI4rJeb4gJBrLuYxUH5jcLD7mzQj+yDswZfGNQ==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/plugin-webpack/-/plugin-webpack-7.8.1.tgz",
+ "integrity": "sha512-4SqQyX7abx6wcMSB8JwsM6gm72r3/8b//JcYZxWihYaqoz9ZMWQqci47FFSpncRlYZjUi7mbRpC2dSAjuQks2A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/core-utils": "7.8.0",
- "@electron-forge/plugin-base": "7.8.0",
- "@electron-forge/shared-types": "7.8.0",
- "@electron-forge/web-multi-logger": "7.8.0",
+ "@electron-forge/core-utils": "7.8.1",
+ "@electron-forge/plugin-base": "7.8.1",
+ "@electron-forge/shared-types": "7.8.1",
+ "@electron-forge/web-multi-logger": "7.8.1",
"chalk": "^4.0.0",
"debug": "^4.3.1",
"fast-glob": "^3.2.7",
@@ -327,26 +328,26 @@
}
},
"node_modules/@electron-forge/publisher-base": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/publisher-base/-/publisher-base-7.8.0.tgz",
- "integrity": "sha512-wrZyptJ0Uqvlh2wYzDZfIu2HgCQ+kdGiBlcucmLY4W+GUqf043O8cbYso3D9NXQxOow55QC/1saCQkgLphprPA==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/publisher-base/-/publisher-base-7.8.1.tgz",
+ "integrity": "sha512-z2C+C4pcFxyCXIFwXGDcxhU8qtVUPZa3sPL6tH5RuMxJi77768chLw2quDWk2/dfupcSELXcOMYCs7aLysCzeQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.8.0"
+ "@electron-forge/shared-types": "7.8.1"
},
"engines": {
"node": ">= 16.4.0"
}
},
"node_modules/@electron-forge/shared-types": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/shared-types/-/shared-types-7.8.0.tgz",
- "integrity": "sha512-Ul+7HPvAZiAirqpZm0vc9YvlkAE+2bcrI10p3t50mEtuxn5VO/mB72NXiEKfWzHm8F31JySIe9bUV6s1MHQcCw==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/shared-types/-/shared-types-7.8.1.tgz",
+ "integrity": "sha512-guLyGjIISKQQRWHX+ugmcjIOjn2q/BEzCo3ioJXFowxiFwmZw/oCZ2KlPig/t6dMqgUrHTH5W/F0WKu0EY4M+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/tracer": "7.8.0",
+ "@electron-forge/tracer": "7.8.1",
"@electron/packager": "^18.3.5",
"@electron/rebuild": "^3.7.0",
"listr2": "^7.0.2"
@@ -356,14 +357,14 @@
}
},
"node_modules/@electron-forge/template-base": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/template-base/-/template-base-7.8.0.tgz",
- "integrity": "sha512-hc8NwoDqEEmZFH/p0p3MK/7xygMmI+cm8Gavoj2Mr2xS7VUUu4r3b5PwIGKvkLfPG34uwsiVwtid2t1rWGF4UA==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/template-base/-/template-base-7.8.1.tgz",
+ "integrity": "sha512-k8jEUr0zWFWb16ZGho+Es2OFeKkcbTgbC6mcH4eNyF/sumh/4XZMcwRtX1i7EiZAYiL9sVxyI6KVwGu254g+0g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/core-utils": "7.8.0",
- "@electron-forge/shared-types": "7.8.0",
+ "@electron-forge/core-utils": "7.8.1",
+ "@electron-forge/shared-types": "7.8.1",
"@malept/cross-spawn-promise": "^2.0.0",
"debug": "^4.3.1",
"fs-extra": "^10.0.0",
@@ -374,14 +375,14 @@
}
},
"node_modules/@electron-forge/template-vite": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/template-vite/-/template-vite-7.8.0.tgz",
- "integrity": "sha512-bf/jd8WzD0gU7Jet+WSi0Lm0SQmseb08WY27ZfJYEs2EVNMiwDfPicgQnOaqP++2yTrXhj1OY/rolZCP9CUyVw==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/template-vite/-/template-vite-7.8.1.tgz",
+ "integrity": "sha512-qzSlJaBYYqQAbBdLk4DqAE3HCNz4yXbpkb+VC74ddL4JGwPdPU57DjCthr6YetKJ2FsOVy9ipovA8HX5UbXpAg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.8.0",
- "@electron-forge/template-base": "7.8.0",
+ "@electron-forge/shared-types": "7.8.1",
+ "@electron-forge/template-base": "7.8.1",
"fs-extra": "^10.0.0"
},
"engines": {
@@ -389,14 +390,14 @@
}
},
"node_modules/@electron-forge/template-vite-typescript": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/template-vite-typescript/-/template-vite-typescript-7.8.0.tgz",
- "integrity": "sha512-kW3CaVxKHUYuVfY+rT3iepeZ69frBRGh3YZOngLY2buCvGIqNEx+VCgrFBRDDbOKGmwQtwO1E9wp2rtC8q6Ztg==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/template-vite-typescript/-/template-vite-typescript-7.8.1.tgz",
+ "integrity": "sha512-CccQhwUjZcc6svzuOi3BtbDal591DzyX2J5GPa6mwVutDP8EMtqJL1VyOHdcWO/7XjI6GNAD0fiXySOJiUAECA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.8.0",
- "@electron-forge/template-base": "7.8.0",
+ "@electron-forge/shared-types": "7.8.1",
+ "@electron-forge/template-base": "7.8.1",
"fs-extra": "^10.0.0"
},
"engines": {
@@ -404,14 +405,14 @@
}
},
"node_modules/@electron-forge/template-webpack": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/template-webpack/-/template-webpack-7.8.0.tgz",
- "integrity": "sha512-AdLGC6NVgrd7Q0SaaeiwJKmSBjN6C2EHxZgLMy1yxNSpazU9m3DtYQilDjXqmCWfxkeNzdke0NaeDvLgdJSw5A==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/template-webpack/-/template-webpack-7.8.1.tgz",
+ "integrity": "sha512-DA77o9kTCHrq+W211pyNP49DyAt0d1mzMp2gisyNz7a+iKvlv2DsMAeRieLoCQ44akb/z8ZsL0YLteSjKLy4AA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.8.0",
- "@electron-forge/template-base": "7.8.0",
+ "@electron-forge/shared-types": "7.8.1",
+ "@electron-forge/template-base": "7.8.1",
"fs-extra": "^10.0.0"
},
"engines": {
@@ -419,14 +420,14 @@
}
},
"node_modules/@electron-forge/template-webpack-typescript": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/template-webpack-typescript/-/template-webpack-typescript-7.8.0.tgz",
- "integrity": "sha512-Pl8l+gv3HzqCfFIMLxlEsoAkNd0VEWeZZ675SYyqs0/kBQUifn0bKNhVE4gUZwKGgQCcG1Gvb23KdVGD3H3XmA==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/template-webpack-typescript/-/template-webpack-typescript-7.8.1.tgz",
+ "integrity": "sha512-h922E+6zWwym1RT6WKD79BLTc4H8YxEMJ7wPWkBX59kw/exsTB/KFdiJq6r82ON5jSJ+Q8sDGqSmDWdyCfo+Gg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.8.0",
- "@electron-forge/template-base": "7.8.0",
+ "@electron-forge/shared-types": "7.8.1",
+ "@electron-forge/template-base": "7.8.1",
"fs-extra": "^10.0.0"
},
"engines": {
@@ -434,9 +435,9 @@
}
},
"node_modules/@electron-forge/tracer": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/tracer/-/tracer-7.8.0.tgz",
- "integrity": "sha512-t4fIATZEX6/7PJNfyh6tLzKEsNMpO01Nz/rgHWBxeRvjCw5UNul9OOxoM7b43vfFAO9Jv++34oI3VJ09LeVQ2Q==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/tracer/-/tracer-7.8.1.tgz",
+ "integrity": "sha512-r2i7aHVp2fylGQSPDw3aTcdNfVX9cpL1iL2MKHrCRNwgrfR+nryGYg434T745GGm1rNQIv5Egdkh5G9xf00oWA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -447,9 +448,9 @@
}
},
"node_modules/@electron-forge/web-multi-logger": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/@electron-forge/web-multi-logger/-/web-multi-logger-7.8.0.tgz",
- "integrity": "sha512-2nUP7O9auXDsoa185AsZPlIbpargj1lNFweNH1Lch1MCwLlJOI9ZJHiCTAB4qviS4usRs00WeebWg/uN/zOWvA==",
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/@electron-forge/web-multi-logger/-/web-multi-logger-7.8.1.tgz",
+ "integrity": "sha512-Z8oU39sbrVDvyk0yILBqL0CFIysVlxkM5m4RWyeo+GLoc/t4LYAhGLSquFTOD1t20nzqZzgzG8M56zIgYuyX1w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -764,9 +765,9 @@
}
},
"node_modules/@electron/rebuild": {
- "version": "3.7.1",
- "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.7.1.tgz",
- "integrity": "sha512-sKGD+xav4Gh25+LcLY0rjIwcCFTw+f/HU1pB48UVbwxXXRGaXEqIH0AaYKN46dgd/7+6kuiDXzoyAEvx1zCsdw==",
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.7.2.tgz",
+ "integrity": "sha512-19/KbIR/DAxbsCkiaGMXIdPnMCJLkcf8AvGnduJtWBs/CBwiAjY1apCqOLVxrXg+rtXFCngbXhBanWjxLUt1Mg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -793,9 +794,9 @@
}
},
"node_modules/@electron/universal": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.2.tgz",
- "integrity": "sha512-mqY1szx5/d5YLvfCDWWoJdkSIjIz+NdWN4pN0r78lYiE7De+slLpuF3lVxIT+hlJnwk5sH2wFRMl6/oUgUVO3A==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz",
+ "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -853,9 +854,9 @@
}
},
"node_modules/@electron/windows-sign": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.1.tgz",
- "integrity": "sha512-YfASnrhJ+ve6Q43ZiDwmpBgYgi2u0bYjeAVi2tDfN7YWAKO8X9EEOuPGtqbJpPLM6TfAHimghICjWe2eaJ8BAg==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz",
+ "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -3137,9 +3138,9 @@
}
},
"node_modules/detect-libc": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
- "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -3297,9 +3298,9 @@
"license": "MIT"
},
"node_modules/electron": {
- "version": "35.1.4",
- "resolved": "https://registry.npmjs.org/electron/-/electron-35.1.4.tgz",
- "integrity": "sha512-8HjE2wqxY//T09Of8k1eTpK/NeTG2FkTyRD+fyKXmec4wZVscGgZcmWFC0HYN4ktyHAjtplpxdFXjtqRnvzBMg==",
+ "version": "36.2.0",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-36.2.0.tgz",
+ "integrity": "sha512-5yldoRjBKxPQfI0QMX+qq750o3Nl8N1SZnJqOPMq0gZ6rIJ+7y4ZLp808GrFwjfTm05TYgq3GSD8FGuKQZqwEw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -5732,6 +5733,16 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/jiti": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
+ "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6465,9 +6476,9 @@
}
},
"node_modules/node-abi": {
- "version": "3.74.0",
- "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz",
- "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==",
+ "version": "3.75.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz",
+ "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9020,9 +9031,9 @@
}
},
"node_modules/typescript": {
- "version": "5.8.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
- "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
diff --git a/samples/NodeJSElectron/package.json b/samples/NodeJSElectron/package.json
index a965d110..122cd94a 100644
--- a/samples/NodeJSElectron/package.json
+++ b/samples/NodeJSElectron/package.json
@@ -23,7 +23,7 @@
"@electron/fuses": "^1.8.0",
"@vercel/webpack-asset-relocator-loader": "=1.7.3",
"css-loader": "^7.0.0",
- "electron": "35.1.4",
+ "electron": "36.2.0",
"fork-ts-checker-webpack-plugin": "^9.0.0",
"node-loader": "^2.0.0",
"style-loader": "^4.0.0",
diff --git a/samples/RustIced/Cargo.lock b/samples/RustIced/Cargo.lock
index 2091ce50..804429c0 100644
--- a/samples/RustIced/Cargo.lock
+++ b/samples/RustIced/Cargo.lock
@@ -101,9 +101,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.97"
+version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
+checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "approx"
@@ -1041,7 +1041,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -2487,9 +2487,9 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.21.2"
+version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "orbclient"
@@ -3035,7 +3035,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.4.15",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -3469,7 +3469,7 @@ dependencies = [
"getrandom 0.3.1",
"once_cell",
"rustix",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -3831,21 +3831,23 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
-version = "1.16.0"
+version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
+checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
dependencies = [
"getrandom 0.3.1",
+ "js-sys",
"rand 0.9.0",
"serde",
"uuid-macro-internal",
+ "wasm-bindgen",
]
[[package]]
name = "uuid-macro-internal"
-version = "1.16.0"
+version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72dcd78c4f979627a754f5522cea6e6a25e55139056535fe6e69c506cd64a862"
+checksum = "26b682e8c381995ea03130e381928e0e005b7c9eb483c6c8682f50e07b33c2b7"
dependencies = [
"proc-macro2",
"quote",
@@ -3887,7 +3889,7 @@ dependencies = [
"ureq",
"url",
"uuid",
- "windows 0.60.0",
+ "windows 0.61.1",
"xml",
"zip",
"zstd",
@@ -4300,7 +4302,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -4335,12 +4337,12 @@ dependencies = [
[[package]]
name = "windows"
-version = "0.60.0"
+version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529"
+checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
dependencies = [
"windows-collections",
- "windows-core 0.60.1",
+ "windows-core 0.61.0",
"windows-future",
"windows-link",
"windows-numerics",
@@ -4348,11 +4350,11 @@ dependencies = [
[[package]]
name = "windows-collections"
-version = "0.1.1"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5467f79cc1ba3f52ebb2ed41dbb459b8e7db636cc3429458d9a852e15bc24dec"
+checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
- "windows-core 0.60.1",
+ "windows-core 0.61.0",
]
[[package]]
@@ -4366,9 +4368,9 @@ dependencies = [
[[package]]
name = "windows-core"
-version = "0.60.1"
+version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247"
+checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
"windows-implement",
"windows-interface",
@@ -4379,19 +4381,19 @@ dependencies = [
[[package]]
name = "windows-future"
-version = "0.1.1"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a787db4595e7eb80239b74ce8babfb1363d8e343ab072f2ffe901400c03349f0"
+checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
dependencies = [
- "windows-core 0.60.1",
+ "windows-core 0.61.0",
"windows-link",
]
[[package]]
name = "windows-implement"
-version = "0.59.0"
+version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
@@ -4400,9 +4402,9 @@ dependencies = [
[[package]]
name = "windows-interface"
-version = "0.59.0"
+version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
@@ -4411,34 +4413,34 @@ dependencies = [
[[package]]
name = "windows-link"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
+checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-numerics"
-version = "0.1.1"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "005dea54e2f6499f2cee279b8f703b3cf3b5734a2d8d21867c8f44003182eeed"
+checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
- "windows-core 0.60.1",
+ "windows-core 0.61.0",
"windows-link",
]
[[package]]
name = "windows-result"
-version = "0.3.1"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189"
+checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
-version = "0.3.1"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
+checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
dependencies = [
"windows-link",
]
diff --git a/src/bins/Cargo.toml b/src/bins/Cargo.toml
index 780c533f..6bdbd77d 100644
--- a/src/bins/Cargo.toml
+++ b/src/bins/Cargo.toml
@@ -63,6 +63,13 @@ wait-timeout.workspace = true
pretty-bytes-rust.workspace = true
enum-flags.workspace = true
log-panics.workspace = true
+zstd.workspace = true
+zip.workspace = true
+walkdir.workspace = true
+sha1_smol.workspace = true
+rayon.workspace = true
+progress-streams.workspace = true
+flate2.workspace = true
[target.'cfg(target_os="linux")'.dependencies]
waitpid-any.workspace = true
@@ -101,6 +108,7 @@ windows = { workspace = true, features = [
"Win32_UI_Shell_Common",
"Win32_UI_Shell_PropertiesSystem",
"Win32_UI_WindowsAndMessaging",
+ "Win32_System_ApplicationInstallationAndServicing",
"Win32_System_Kernel",
"Wdk",
"Wdk_System",
@@ -116,7 +124,6 @@ same-file.workspace = true
tempfile.workspace = true
ntest.workspace = true
pretty_assertions.workspace = true
-sha1_smol.workspace = true
[build-dependencies]
semver.workspace = true
diff --git a/src/bins/src/commands/mod.rs b/src/bins/src/commands/mod.rs
index e6de8bfe..29d9b652 100644
--- a/src/bins/src/commands/mod.rs
+++ b/src/bins/src/commands/mod.rs
@@ -4,6 +4,9 @@ pub use apply::*;
mod start;
pub use start::*;
+mod patch;
+pub use patch::*;
+
#[cfg(target_os = "linux")]
mod apply_linux_impl;
#[cfg(target_os = "macos")]
diff --git a/src/bins/src/commands/patch.rs b/src/bins/src/commands/patch.rs
new file mode 100644
index 00000000..92a54eb5
--- /dev/null
+++ b/src/bins/src/commands/patch.rs
@@ -0,0 +1,184 @@
+use crate::shared::fastzip;
+use anyhow::{anyhow, bail, Result};
+use std::{
+ collections::HashSet,
+ fs, io,
+ path::{Path, PathBuf},
+};
+
+pub fn zstd_patch_single, P2: AsRef, P3: AsRef>(old_file: P1, patch_file: P2, output_file: P3) -> Result<()> {
+ let old_file = old_file.as_ref();
+ let patch_file = patch_file.as_ref();
+ let output_file = output_file.as_ref();
+
+ if !old_file.exists() {
+ bail!("Old file does not exist: {}", old_file.to_string_lossy());
+ }
+
+ if !patch_file.exists() {
+ bail!("Patch file does not exist: {}", patch_file.to_string_lossy());
+ }
+
+ let dict = fs::read(old_file)?;
+
+ // info!("Loading Dictionary (Size: {})", dict.len());
+ let patch = fs::OpenOptions::new().read(true).open(patch_file)?;
+ let patch_reader = io::BufReader::new(patch);
+ let mut decoder = zstd::Decoder::with_dictionary(patch_reader, &dict)?;
+
+ let window_log = fio_highbit64(dict.len() as u64) + 1;
+ if window_log >= 27 {
+ info!("Large File detected. Overriding windowLog to {}", window_log);
+ decoder.window_log_max(window_log)?;
+ }
+
+ // info!("Decoder loaded. Beginning patch...");
+ let mut output = fs::OpenOptions::new().write(true).create(true).truncate(true).open(output_file)?;
+ io::copy(&mut decoder, &mut output)?;
+
+ // info!("Patch applied successfully.");
+ Ok(())
+}
+
+fn fio_highbit64(v: u64) -> u32 {
+ let mut count: u32 = 0;
+ let mut v = v;
+ v >>= 1;
+ while v > 0 {
+ v >>= 1;
+ count += 1;
+ }
+ return count;
+}
+
+pub fn delta, P2: AsRef, P3: AsRef>(
+ old_file: P1,
+ delta_files: Vec<&PathBuf>,
+ temp_dir: P2,
+ output_file: P3,
+) -> Result<()> {
+ let old_file = old_file.as_ref().to_path_buf();
+ let temp_dir = temp_dir.as_ref().to_path_buf();
+ let output_file = output_file.as_ref().to_path_buf();
+
+ if !old_file.exists() {
+ bail!("Old file does not exist: {}", old_file.to_string_lossy());
+ }
+
+ if delta_files.is_empty() {
+ bail!("No delta files provided.");
+ }
+
+ for delta_file in &delta_files {
+ if !delta_file.exists() {
+ bail!("Delta file does not exist: {}", delta_file.to_string_lossy());
+ }
+ }
+
+ let time = simple_stopwatch::Stopwatch::start_new();
+
+ info!("Extracting base package for delta patching: {}", temp_dir.to_string_lossy());
+ let work_dir = temp_dir.join("_work");
+ fs::create_dir_all(&work_dir)?;
+ fastzip::extract_to_directory(&old_file, &work_dir, None)?;
+
+ info!("Base package extracted. {} delta packages to apply.", delta_files.len());
+
+ for (i, delta_file) in delta_files.iter().enumerate() {
+ info!("{}: extracting apply delta patch: {}", i, delta_file.to_string_lossy());
+ let delta_dir = temp_dir.join(format!("delta_{}", i));
+ fs::create_dir_all(&delta_dir)?;
+ fastzip::extract_to_directory(&delta_file, &delta_dir, None)?;
+
+ let delta_relative_paths = fastzip::enumerate_files_relative(&delta_dir);
+ let mut visited_paths = HashSet::new();
+
+ // apply all the zsdiff patches for files which exist in both the delta and the base package
+ for relative_path in &delta_relative_paths {
+ if relative_path.starts_with("lib") {
+ let file_name = relative_path.file_name().ok_or(anyhow!("Failed to get file name"))?;
+ let file_name_str = file_name.to_string_lossy();
+ if file_name_str.ends_with(".zsdiff") || file_name_str.ends_with(".diff") || file_name_str.ends_with(".bsdiff") {
+ // this is a zsdiff patch, we need to apply it to the old file
+ let file_without_extension = relative_path.with_extension("");
+ // let shasum_path = delta_dir.join(relative_path).with_extension("shasum");
+ let old_file_path = work_dir.join(&file_without_extension);
+ let patch_file_path = delta_dir.join(&relative_path);
+ let output_file_path = delta_dir.join(&file_without_extension);
+
+ visited_paths.insert(file_without_extension);
+
+ if fs::metadata(&patch_file_path)?.len() == 0 {
+ // file has not changed, so we can continue.
+ continue;
+ }
+
+ if file_name_str.ends_with(".zsdiff") {
+ info!("{}: applying zsdiff patch: {:?}", i, relative_path);
+ zstd_patch_single(&old_file_path, &patch_file_path, &output_file_path)?;
+ } else {
+ bail!("Unsupported patch format: {:?}", relative_path);
+ }
+
+ fs::rename(&output_file_path, &old_file_path)?;
+ } else if file_name_str.ends_with(".shasum") {
+ // skip shasum files
+ } else {
+ // if this file is inside the lib folder without a known extension, it is a new file
+ let file_path = delta_dir.join(relative_path);
+ let dest_path = work_dir.join(relative_path);
+ info!("{}: new file: {:?}", i, relative_path);
+ fs::copy(&file_path, &dest_path)?;
+ visited_paths.insert(relative_path.clone());
+ }
+ } else {
+ // if this file is not inside the lib folder, we always copy it over
+ let file_path = delta_dir.join(relative_path);
+ let dest_path = work_dir.join(relative_path);
+ info!("{}: copying metadata file: {:?}", i, relative_path);
+ fs::copy(&file_path, &dest_path)?;
+ visited_paths.insert(relative_path.clone());
+ }
+ }
+
+ // anything in the work dir which was not visited is an old / deleted file and should be removed
+ let workdir_relative_paths = fastzip::enumerate_files_relative(&work_dir);
+ for relative_path in &workdir_relative_paths {
+ if !visited_paths.contains(relative_path) {
+ let file_to_delete = work_dir.join(relative_path);
+ info!("{}: deleting old/removed file: {:?}", i, relative_path);
+ let _ = fs::remove_file(file_to_delete); // soft error
+ }
+ }
+ }
+
+ info!("All delta patches applied. Asembling output package at: {}", output_file.to_string_lossy());
+
+ fastzip::compress_directory(&work_dir, &output_file, fastzip::CompressionLevel::fast())?;
+
+ info!("Successfully applied {} delta patches in {}s.", delta_files.len(), time.s());
+ Ok(())
+}
+
+// NOTE: this is some code to do checksum verification, but it is not being used
+// by the current implementation because zstd patching already has checksum verification
+//
+// let actual_checksum = get_sha1(&output_file_path);
+// let expected_checksum = load_release_entry_shasum(&shasum_path)?;
+//
+// if !actual_checksum.eq_ignore_ascii_case(&expected_checksum) {
+// bail!("Checksum mismatch for: {:?}. Expected: {}, Actual: {}", relative_path, expected_checksum, actual_checksum);
+// }
+// fn load_release_entry_shasum(file: &PathBuf) -> Result {
+// let raw_text = fs::read_to_string(file)?.trim().to_string();
+// let first_word = raw_text.splitn(2, ' ').next().unwrap();
+// let cleaned = first_word.trim().trim_matches(|c: char| !c.is_ascii_hexdigit());
+// Ok(cleaned.to_string())
+// }
+//
+// fn get_sha1(file: &PathBuf) -> String {
+// let file_bytes = fs::read(file).unwrap();
+// let mut sha1 = sha1_smol::Sha1::new();
+// sha1.update(&file_bytes);
+// sha1.digest().to_string()
+// }
diff --git a/src/bins/src/commands/start_windows_impl.rs b/src/bins/src/commands/start_windows_impl.rs
index 0faf45c7..baf0dfaf 100644
--- a/src/bins/src/commands/start_windows_impl.rs
+++ b/src/bins/src/commands/start_windows_impl.rs
@@ -175,7 +175,7 @@ fn try_legacy_migration(root_dir: &PathBuf, manifest: &Manifest) -> Result Result<()> {
info!("Command: Uninstall");
-
+
let root_path = locator.get_root_dir();
- fn _uninstall_impl(locator: &VelopackLocator) -> bool {
- let root_path = locator.get_root_dir();
-
- // the real app could be running at the moment
- let _ = shared::force_stop_package(&root_path);
+ // the real app could be running at the moment
+ let _ = shared::force_stop_package(&root_path);
- let mut finished_with_errors = false;
+ // run uninstall hook
+ windows::run_hook(&locator, constants::HOOK_CLI_UNINSTALL, 60);
- // run uninstall hook
- windows::run_hook(&locator, constants::HOOK_CLI_UNINSTALL, 60);
+ // remove all shortcuts pointing to the app
+ windows::remove_all_shortcuts_for_root_dir(&root_path);
- // remove all shortcuts pointing to the app
- windows::remove_all_shortcuts_for_root_dir(&root_path);
+ info!("Removing directory '{}'", root_path.to_string_lossy());
+ let _ = remove_dir_all::remove_dir_contents(&root_path);
- info!("Removing directory '{}'", root_path.to_string_lossy());
- if let Err(e) = shared::retry_io(|| remove_dir_all::remove_dir_but_not_self(&root_path)) {
- error!("Unable to remove directory, some files may be in use ({}).", e);
- finished_with_errors = true;
- }
-
- if let Err(e) = windows::registry::remove_uninstall_entry(&locator) {
- error!("Unable to remove uninstall registry entry ({}).", e);
- // finished_with_errors = true;
- }
-
- !finished_with_errors
+ if let Err(e) = windows::registry::remove_uninstall_entry(&locator) {
+ error!("Unable to remove uninstall registry entry ({}).", e);
}
// if it returns true, it was a success.
// if it returns false, it was completed with errors which the user should be notified of.
- let result = _uninstall_impl(&locator);
let app_title = locator.get_manifest_title();
- if result {
- info!("Finished successfully.");
- shared::dialogs::show_info(format!("{} Uninstall", app_title).as_str(), None, "The application was successfully uninstalled.");
- } else {
- error!("Finished with errors.");
- shared::dialogs::show_uninstall_complete_with_errors_dialog(&app_title, None);
- }
+ info!("Finished successfully.");
+ shared::dialogs::show_info(format!("{} Uninstall", app_title).as_str(), None, "The application was successfully uninstalled.");
let dead_path = root_path.join(".dead");
let _ = File::create(dead_path);
diff --git a/src/bins/src/shared/fastzip/cloneable_seekable_reader.rs b/src/bins/src/shared/fastzip/cloneable_seekable_reader.rs
new file mode 100644
index 00000000..611f676f
--- /dev/null
+++ b/src/bins/src/shared/fastzip/cloneable_seekable_reader.rs
@@ -0,0 +1,168 @@
+// Copyright 2022 Google LLC
+
+// Licensed under the Apache License, Version 2.0 or the MIT license
+// , at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::{
+ io::{Read, Seek, SeekFrom},
+ sync::{Arc, Mutex},
+};
+
+use super::ripunzip::determine_stream_len;
+
+struct Inner {
+ /// The underlying Read implementation.
+ r: R,
+ /// The position of r.
+ pos: u64,
+ /// The length of r, lazily loaded.
+ len: Option,
+}
+
+impl Inner {
+ fn new(r: R) -> Self {
+ Self { r, pos: 0, len: None }
+ }
+
+ /// Get the length of the data stream. This is assumed to be constant.
+ fn len(&mut self) -> std::io::Result {
+ // Return cached size
+ if let Some(len) = self.len {
+ return Ok(len);
+ }
+
+ let len = determine_stream_len(&mut self.r)?;
+ self.len = Some(len);
+ Ok(len)
+ }
+
+ /// Read into the given buffer, starting at the given offset in the data stream.
+ fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result {
+ if offset != self.pos {
+ self.r.seek(SeekFrom::Start(offset))?;
+ }
+ let read_result = self.r.read(buf);
+ if let Ok(bytes_read) = read_result {
+ // TODO, once stabilised, use checked_add_signed
+ self.pos += bytes_read as u64;
+ }
+ read_result
+ }
+}
+
+/// A [`Read`] which refers to its underlying stream by reference count,
+/// and thus can be cloned cheaply. It supports seeking; each cloned instance
+/// maintains its own pointer into the file, and the underlying instance
+/// is seeked prior to each read.
+pub(crate) struct CloneableSeekableReader {
+ /// The wrapper around the Read implementation, shared between threads.
+ inner: Arc>>,
+ /// The position of _this_ reader.
+ pos: u64,
+}
+
+impl Clone for CloneableSeekableReader {
+ fn clone(&self) -> Self {
+ Self { inner: self.inner.clone(), pos: self.pos }
+ }
+}
+
+impl CloneableSeekableReader {
+ /// Constructor. Takes ownership of the underlying `Read`.
+ /// You should pass in only streams whose total length you expect
+ /// to be fixed and unchanging. Odd behavior may occur if the length
+ /// of the stream changes; any subsequent seeks will not take account
+ /// of the changed stream length.
+ pub(crate) fn new(r: R) -> Self {
+ Self { inner: Arc::new(Mutex::new(Inner::new(r))), pos: 0u64 }
+ }
+}
+
+impl Read for CloneableSeekableReader {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result {
+ let mut inner = self.inner.lock().unwrap();
+ let read_result = inner.read_at(self.pos, buf);
+ if let Ok(bytes_read) = read_result {
+ self.pos = self
+ .pos
+ .checked_add(bytes_read as u64)
+ .ok_or(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Read too far forward"))?;
+ }
+ read_result
+ }
+}
+
+impl Seek for CloneableSeekableReader {
+ fn seek(&mut self, pos: SeekFrom) -> std::io::Result {
+ let new_pos = match pos {
+ SeekFrom::Start(pos) => pos,
+ SeekFrom::End(offset_from_end) => {
+ let file_len = self.inner.lock().unwrap().len()?;
+ if -offset_from_end as u64 > file_len {
+ return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Seek too far backwards"));
+ }
+ file_len
+ .checked_add_signed(offset_from_end)
+ .ok_or(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Seek too far backward from end"))?
+ }
+ SeekFrom::Current(offset_from_pos) => self
+ .pos
+ .checked_add_signed(offset_from_pos)
+ .ok_or(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Seek too far forward from current pos"))?,
+ };
+ self.pos = new_pos;
+ Ok(new_pos)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::CloneableSeekableReader;
+ use std::io::{Cursor, Read, Seek, SeekFrom};
+ // use test_log::test;
+
+ #[test]
+ fn test_cloneable_seekable_reader() -> std::io::Result<()> {
+ let buf: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+ let buf = Cursor::new(buf);
+ let mut reader = CloneableSeekableReader::new(buf);
+ let mut out = vec![0; 2];
+ reader.read_exact(&mut out)?;
+ assert_eq!(&out, &[0, 1]);
+ reader.rewind()?;
+ reader.read_exact(&mut out)?;
+ assert_eq!(&out, &[0, 1]);
+ reader.stream_position()?;
+ reader.read_exact(&mut out)?;
+ assert_eq!(&out, &[2, 3]);
+ reader.seek(SeekFrom::End(-2))?;
+ reader.read_exact(&mut out)?;
+ assert_eq!(&out, &[8, 9]);
+ assert!(reader.read_exact(&mut out).is_err());
+ Ok(())
+ }
+
+ #[test]
+ fn test_cloned_independent_positions() -> std::io::Result<()> {
+ let buf: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+ let buf = Cursor::new(buf);
+ let mut r1 = CloneableSeekableReader::new(buf);
+ let mut r2 = r1.clone();
+ let mut out = vec![0; 2];
+ r1.read_exact(&mut out)?;
+ assert_eq!(&out, &[0, 1]);
+ r2.read_exact(&mut out)?;
+ assert_eq!(&out, &[0, 1]);
+ r1.read_exact(&mut out)?;
+ assert_eq!(&out, &[2, 3]);
+ r2.seek(SeekFrom::End(-2))?;
+ r2.read_exact(&mut out)?;
+ assert_eq!(&out, &[8, 9]);
+ r1.read_exact(&mut out)?;
+ assert_eq!(&out, &[4, 5]);
+ Ok(())
+ }
+}
diff --git a/src/bins/src/shared/fastzip/mod.rs b/src/bins/src/shared/fastzip/mod.rs
new file mode 100644
index 00000000..e990d899
--- /dev/null
+++ b/src/bins/src/shared/fastzip/mod.rs
@@ -0,0 +1,86 @@
+#![allow(dead_code)]
+
+mod cloneable_seekable_reader;
+mod mtzip;
+mod progress_updater;
+mod ripunzip;
+
+use anyhow::Result;
+pub use mtzip::level::CompressionLevel;
+use ripunzip::{UnzipEngine, UnzipOptions};
+use std::{
+ fs::File,
+ path::{Path, PathBuf},
+};
+use walkdir::WalkDir;
+
+/// A trait of types which wish to hear progress updates on the unzip.
+pub trait UnzipProgressReporter: Sync {
+ /// Extraction has begun on a file.
+ fn extraction_starting(&self, _display_name: &str) {}
+ /// Extraction has finished on a file.
+ fn extraction_finished(&self, _display_name: &str) {}
+ /// The total number of compressed bytes we expect to extract.
+ fn total_bytes_expected(&self, _expected: u64) {}
+ /// Some bytes of a file have been decompressed. This is probably
+ /// the best way to display an overall progress bar. This should eventually
+ /// add up to the number you're given using `total_bytes_expected`.
+ /// The 'count' parameter is _not_ a running total - you must add up
+ /// each call to this function into the running total.
+ /// It's a bit unfortunate that we give compressed bytes rather than
+ /// uncompressed bytes, but currently we can't calculate uncompressed
+ /// bytes without downloading the whole zip file first, which rather
+ /// defeats the point.
+ fn bytes_extracted(&self, _count: u64) {}
+}
+
+/// A progress reporter which does nothing.
+struct NullProgressReporter;
+
+impl UnzipProgressReporter for NullProgressReporter {}
+
+pub fn extract_to_directory<'b, P1: AsRef, P2: AsRef>(
+ archive_file: P1,
+ target_dir: P2,
+ progress_reporter: Option>,
+) -> Result<()> {
+ let target_dir = target_dir.as_ref().to_path_buf();
+ let file = File::open(archive_file)?;
+ let engine = UnzipEngine::for_file(file)?;
+ let null_progress = Box::new(NullProgressReporter {});
+ let options = UnzipOptions {
+ filename_filter: None,
+ progress_reporter: progress_reporter.unwrap_or(null_progress),
+ output_directory: Some(target_dir),
+ password: None,
+ single_threaded: false,
+ };
+ engine.unzip(options)?;
+ Ok(())
+}
+
+pub fn compress_directory<'b, P1: AsRef, P2: AsRef>(target_dir: P1, output_file: P2, level: CompressionLevel) -> Result<()> {
+ let target_dir = target_dir.as_ref().to_path_buf();
+ let mut zipper = mtzip::ZipArchive::new();
+ let workdir_relative_paths = enumerate_files_relative(&target_dir);
+ for relative_path in &workdir_relative_paths {
+ zipper
+ .add_file_from_fs(target_dir.join(&relative_path), relative_path.to_string_lossy().to_string())
+ .compression_level(level)
+ .done();
+ }
+ let mut file = File::create(&output_file)?;
+ zipper.write_with_rayon(&mut file)?;
+ Ok(())
+}
+
+pub fn enumerate_files_relative>(dir: P) -> Vec {
+ WalkDir::new(&dir)
+ .follow_links(false)
+ .into_iter()
+ .filter_map(|entry| entry.ok())
+ .filter(|entry| entry.file_type().is_file())
+ .map(|entry| entry.path().strip_prefix(&dir).map(|p| p.to_path_buf()))
+ .filter_map(|entry| entry.ok())
+ .collect()
+}
diff --git a/src/bins/src/shared/fastzip/mtzip/level.rs b/src/bins/src/shared/fastzip/mtzip/level.rs
new file mode 100644
index 00000000..f831fce7
--- /dev/null
+++ b/src/bins/src/shared/fastzip/mtzip/level.rs
@@ -0,0 +1,126 @@
+//! Compression level
+
+use core::fmt::Display;
+use std::error::Error;
+
+use flate2::Compression;
+
+/// Compression level that should be used when compressing a file or data.
+///
+/// Current compression providers support only levels from 0 to 9, so these are the only ones being
+/// supported.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CompressionLevel(u8);
+
+impl CompressionLevel {
+ /// Construct a new value of a compression level setting.
+ ///
+ /// The integer value must be less than or equal to 9, otherwise `None` is returned
+ #[inline]
+ pub const fn new(level: u8) -> Option {
+ if level <= 9 { Some(Self(level)) } else { None }
+ }
+
+ /// Construct a new value of a compression level setting without checking the value.
+ ///
+ /// # Safety
+ ///
+ /// The value must be a valid supported compression level
+ #[inline]
+ pub const unsafe fn new_unchecked(level: u8) -> Self {
+ Self(level)
+ }
+
+ /// No compression
+ #[inline]
+ pub const fn none() -> Self {
+ Self(0)
+ }
+
+ /// Fastest compression
+ #[inline]
+ pub const fn fast() -> Self {
+ Self(1)
+ }
+
+ /// Balanced level with moderate compression and speed. The raw value is 6.
+ #[inline]
+ pub const fn balanced() -> Self {
+ Self(6)
+ }
+
+ /// Best compression ratio, comes at a worse performance
+ #[inline]
+ pub const fn best() -> Self {
+ Self(9)
+ }
+
+ /// Get the compression level as an integer
+ #[inline]
+ pub const fn get(self) -> u8 {
+ self.0
+ }
+}
+
+impl Default for CompressionLevel {
+ /// Equivalent to [`Self::balanced`]
+ fn default() -> Self {
+ Self::balanced()
+ }
+}
+
+/// The number for compression level was invalid
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct InvalidCompressionLevel(u32);
+
+impl InvalidCompressionLevel {
+ /// The value which was supplied
+ pub fn value(self) -> u32 {
+ self.0
+ }
+}
+
+impl Display for InvalidCompressionLevel {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "Invalid compression level number: {}", self.0)
+ }
+}
+
+impl Error for InvalidCompressionLevel {}
+
+impl From for Compression {
+ #[inline]
+ fn from(value: CompressionLevel) -> Self {
+ Compression::new(value.0.into())
+ }
+}
+
+impl TryFrom for CompressionLevel {
+ type Error = InvalidCompressionLevel;
+
+ fn try_from(value: Compression) -> Result {
+ let level = value.level();
+ Self::new(
+ level
+ .try_into()
+ .map_err(|_| InvalidCompressionLevel(level))?,
+ )
+ .ok_or(InvalidCompressionLevel(level))
+ }
+}
+
+impl From for u8 {
+ #[inline]
+ fn from(value: CompressionLevel) -> Self {
+ value.0
+ }
+}
+
+impl TryFrom for CompressionLevel {
+ type Error = InvalidCompressionLevel;
+
+ #[inline]
+ fn try_from(value: u8) -> Result {
+ Self::new(value).ok_or(InvalidCompressionLevel(value.into()))
+ }
+}
diff --git a/src/bins/src/shared/fastzip/mtzip/mod.rs b/src/bins/src/shared/fastzip/mtzip/mod.rs
new file mode 100644
index 00000000..a8ee9b52
--- /dev/null
+++ b/src/bins/src/shared/fastzip/mtzip/mod.rs
@@ -0,0 +1,433 @@
+//! # mtzip
+//!
+//! MTZIP (Stands for Multi-Threaded ZIP) is a library for making zip archives while utilising all
+//! available performance available with multithreading. The amount of threads can be limited by
+//! the user or detected automatically.
+//!
+//! Example usage:
+//!
+//! ```ignore
+//! # use std::path::Path;
+//! # use std::fs::File;
+//! use mtzip::ZipArchive;
+//!
+//! // Creating the zipper that holds data and handles compression
+//! let mut zipper = ZipArchive::new();
+//!
+//! // Adding a file from filesystem
+//! zipper.add_file_from_fs(
+//! Path::new("input/test_text_file.txt"),
+//! "test_text_file.txt".to_owned(),
+//! );
+//!
+//! // Adding a file with data from a memory location
+//! zipper.add_file_from_memory(b"Hello, world!", "hello_world.txt".to_owned());
+//!
+//! // Adding a directory and a file to it
+//! zipper.add_directory("test_dir".to_owned());
+//! zipper.add_file_from_fs(
+//! Path::new("input/file_that_goes_to_a_dir.txt"),
+//! "test_dir/file_that_goes_to_a_dir.txt".to_owned(),
+//! );
+//!
+//! // Writing to a file
+//! // First, open the file
+//! let mut file = File::create("output.zip").unwrap();
+//! // Then, write to it
+//! zipper.write(&mut file); // Amount of threads is chosen automatically
+//! ```
+
+use std::{
+ borrow::Cow,
+ io::{Read, Seek, Write},
+ num::NonZeroUsize,
+ panic::{RefUnwindSafe, UnwindSafe},
+ path::Path,
+ sync::{mpsc, Mutex},
+};
+
+use level::CompressionLevel;
+use rayon::prelude::*;
+use zip_archive_parts::{
+ data::ZipData,
+ extra_field::{ExtraField, ExtraFields},
+ file::ZipFile,
+ job::{ZipJob, ZipJobOrigin},
+};
+
+pub mod level;
+mod platform;
+mod zip_archive_parts;
+
+// TODO: tests, maybe examples
+
+/// Compression type for the file. Directories always use [`Stored`](CompressionType::Stored).
+/// Default is [`Deflate`](CompressionType::Deflate).
+#[repr(u16)]
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
+pub enum CompressionType {
+ /// No compression at all, the data is stored as-is.
+ ///
+ /// This is used for directories because they have no data (no payload)
+ Stored = 0,
+ #[default]
+ /// Deflate compression, the most common in ZIP files.
+ Deflate = 8,
+}
+
+/// Builder used to optionally add additional attributes to a file or directory.
+/// The default compression type is [`CompressionType::Deflate`] and default compression level is
+/// [`CompressionLevel::best`]
+#[must_use]
+#[derive(Debug)]
+pub struct ZipFileBuilder<'a, 'b> {
+ archive_handle: &'a mut ZipArchive<'b>,
+ job: ZipJob<'b>,
+}
+
+impl<'a, 'b> ZipFileBuilder<'a, 'b> {
+ /// Call this when you're done configuring the file entry and it will be added to the job list,
+ /// or directly into the resulting dataset if it's a directory. Always needs to be called.
+ pub fn done(self) {
+ let Self { archive_handle, job } = self;
+ match &job.data_origin {
+ ZipJobOrigin::Directory => {
+ let file = job.into_file().expect("No failing code path");
+ archive_handle.push_file(file);
+ }
+ _ => archive_handle.push_job(job),
+ }
+ }
+
+ /// Read filesystem metadata from filesystem and add the properties to this file. It sets
+ /// external attributes (as with [`Self::external_attributes`]) and adds extra fields generated
+ /// with [`ExtraFields::new_from_fs`]
+ pub fn metadata_from_fs(self, fs_path: &Path) -> std::io::Result {
+ let metadata = std::fs::metadata(fs_path)?;
+ let external_attributes = platform::attributes_from_fs(&metadata);
+ let extra_fields = ExtraFields::new_from_fs(&metadata);
+ Ok(self.external_attributes(external_attributes).extra_fields(extra_fields))
+ }
+
+ /// Add a file comment.
+ pub fn file_comment(mut self, comment: String) -> Self {
+ self.job.file_comment = Some(comment);
+ self
+ }
+
+ /// Add additional [`ExtraField`].
+ pub fn extra_field(mut self, extra_field: ExtraField) -> Self {
+ self.job.extra_fields.values.push(extra_field);
+ self
+ }
+
+ /// Add additional [`ExtraField`]s.
+ pub fn extra_fields(mut self, extra_fields: impl IntoIterator- ) -> Self {
+ self.job.extra_fields.extend(extra_fields);
+ self
+ }
+
+ /// Set compression type. Ignored for directories, as they use no compression.
+ ///
+ /// Default is [`CompressionType::Deflate`].
+ pub fn compression_type(mut self, compression_type: CompressionType) -> Self {
+ self.job.compression_type = compression_type;
+ self
+ }
+
+ /// Set compression level. Ignored for directories, as they use no compression.
+ ///
+ /// Default is [`CompressionLevel::best`]
+ pub fn compression_level(mut self, compression_level: CompressionLevel) -> Self {
+ self.job.compression_level = compression_level;
+ self
+ }
+
+ /// Set external attributes. The format depends on a filesystem and is mostly a legacy
+ /// mechanism, usually a default value is used if this is not a filesystem source. When a file
+ /// is added from the filesystem, these attributes will be read and used and the ones set wit
+ /// hthis method are ignored.
+ pub fn external_attributes(mut self, external_attributes: u16) -> Self {
+ self.job.external_attributes = external_attributes;
+ self
+ }
+
+ /// Set external file attributes from a filesystem item. Use of this method is discouraged in
+ /// favor of [`Self::metadata_from_fs`], which also sets extra fields which contain modern
+ /// filesystem attributes instead of using old 16-bit system-dependent format.
+ pub fn external_attributes_from_fs(mut self, fs_path: &Path) -> std::io::Result {
+ let metadata = std::fs::metadata(fs_path)?;
+ self.job.external_attributes = platform::attributes_from_fs(&metadata);
+ Ok(self)
+ }
+
+ #[inline]
+ fn new(archive: &'a mut ZipArchive<'b>, filename: String, origin: ZipJobOrigin<'b>) -> Self {
+ Self {
+ archive_handle: archive,
+ job: ZipJob {
+ data_origin: origin,
+ archive_path: filename,
+ extra_fields: ExtraFields::default(),
+ file_comment: None,
+ external_attributes: platform::default_file_attrs(),
+ compression_type: CompressionType::Deflate,
+ compression_level: CompressionLevel::best(),
+ },
+ }
+ }
+
+ #[inline]
+ fn new_dir(archive: &'a mut ZipArchive<'b>, filename: String) -> Self {
+ Self {
+ archive_handle: archive,
+ job: ZipJob {
+ data_origin: ZipJobOrigin::Directory,
+ archive_path: filename,
+ extra_fields: ExtraFields::default(),
+ file_comment: None,
+ external_attributes: platform::default_dir_attrs(),
+ compression_type: CompressionType::Deflate,
+ compression_level: CompressionLevel::best(),
+ },
+ }
+ }
+}
+
+/// Structure that holds the current state of ZIP archive creation.
+///
+/// # Lifetimes
+///
+/// Because some of the methods allow supplying borrowed data, the lifetimes are used to indicate
+/// that [`Self`](ZipArchive) borrows them. If you only provide owned data, such as
+/// [`Vec`](Vec) or [`PathBuf`](std::path::PathBuf), you won't have to worry about lifetimes
+/// and can simply use `'static`, if you ever need to specify them in your code.
+///
+/// The lifetime `'a` is for the borrowed data passed in
+/// [`add_file_from_memory`](Self::add_file_from_memory),
+/// [`add_file_from_fs`](Self::add_file_from_fs) and
+/// [`add_file_from_reader`](Self::add_file_from_reader)
+#[derive(Debug, Default)]
+pub struct ZipArchive<'a> {
+ jobs_queue: Vec>,
+ data: ZipData,
+}
+
+impl<'a> ZipArchive<'a> {
+ fn push_job(&mut self, job: ZipJob<'a>) {
+ self.jobs_queue.push(job);
+ }
+
+ fn push_file(&mut self, file: ZipFile) {
+ self.data.files.push(file);
+ }
+
+ /// Create an empty [`ZipArchive`]
+ #[inline]
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Add file from filesystem.
+ ///
+ /// Opens the file and reads data from it when [`compress`](Self::compress) is called.
+ ///
+ /// ```
+ /// # use mtzip::ZipArchive;
+ /// # use std::path::Path;
+ /// let mut zipper = ZipArchive::new();
+ /// zipper
+ /// .add_file_from_fs(Path::new("input.txt"), "input.txt".to_owned())
+ /// .done();
+ /// ```
+ #[inline]
+ pub fn add_file_from_fs(&mut self, fs_path: impl Into>, archived_path: String) -> ZipFileBuilder<'_, 'a> {
+ ZipFileBuilder::new(self, archived_path, ZipJobOrigin::Filesystem { path: fs_path.into() })
+ }
+
+ /// Add file with data from memory.
+ ///
+ /// The data can be either borrowed or owned by the [`ZipArchive`] struct to avoid lifetime
+ /// hell.
+ ///
+ /// ```
+ /// # use mtzip::ZipArchive;
+ /// # use std::path::Path;
+ /// let mut zipper = ZipArchive::new();
+ /// let data: &[u8] = "Hello, world!".as_ref();
+ /// zipper
+ /// .add_file_from_memory(data, "hello_world.txt".to_owned())
+ /// .done();
+ /// ```
+ #[inline]
+ pub fn add_file_from_memory(&mut self, data: impl Into>, archived_path: String) -> ZipFileBuilder<'_, 'a> {
+ ZipFileBuilder::new(self, archived_path, ZipJobOrigin::RawData(data.into()))
+ }
+
+ /// Add a file with data from a reader.
+ ///
+ /// This method takes any type implementing [`Read`] and allows it to have borrowed data (`'r`)
+ ///
+ /// ```
+ /// # use mtzip::ZipArchive;
+ /// # use std::path::Path;
+ /// let mut zipper = ZipArchive::new();
+ /// let data_input = std::io::stdin();
+ /// zipper
+ /// .add_file_from_reader(data_input, "stdin_file.txt".to_owned())
+ /// .done();
+ /// ```
+ #[inline]
+ pub fn add_file_from_reader(
+ &mut self,
+ reader: R,
+ archived_path: String,
+ ) -> ZipFileBuilder<'_, 'a> {
+ ZipFileBuilder::new(self, archived_path, ZipJobOrigin::Reader(Box::new(reader)))
+ }
+
+ /// Add a directory entry.
+ ///
+ /// All directories in the tree should be added. This method does not asssociate any filesystem
+ /// properties to the entry.
+ ///
+ /// ```
+ /// # use mtzip::ZipArchive;
+ /// # use std::path::Path;
+ /// let mut zipper = ZipArchive::new();
+ /// zipper.add_directory("test_dir/".to_owned()).done();
+ /// ```
+ #[inline]
+ pub fn add_directory(&mut self, archived_path: String) -> ZipFileBuilder<'_, 'a> {
+ ZipFileBuilder::new_dir(self, archived_path)
+ }
+
+ /// Compress contents. Will be done automatically on [`write`](Self::write) call if files were
+ /// added between last compression and [`write`](Self::write) call. Automatically chooses
+ /// amount of threads to use based on how much are available.
+ #[inline]
+ pub fn compress(&mut self) {
+ self.compress_with_threads(Self::get_threads());
+ }
+
+ /// Compress contents. Will be done automatically on
+ /// [`write_with_threads`](Self::write_with_threads) call if files were added between last
+ /// compression and [`write`](Self::write). Allows specifying amount of threads that will be
+ /// used.
+ ///
+ /// Example of getting amount of threads that this library uses in
+ /// [`compress`](Self::compress):
+ ///
+ /// ```
+ /// # use std::num::NonZeroUsize;
+ /// # use mtzip::ZipArchive;
+ /// # let mut zipper = ZipArchive::new();
+ /// let threads = std::thread::available_parallelism()
+ /// .map(NonZeroUsize::get)
+ /// .unwrap_or(1);
+ ///
+ /// zipper.compress_with_threads(threads);
+ /// ```
+ #[inline]
+ pub fn compress_with_threads(&mut self, threads: usize) {
+ if !self.jobs_queue.is_empty() {
+ self.compress_with_consumer(threads, |zip_data, rx| zip_data.files.extend(rx))
+ }
+ }
+
+ /// Write compressed data to a writer (usually a file). Executes [`compress`](Self::compress)
+ /// if files were added between last [`compress`](Self::compress) call and this call.
+ /// Automatically chooses the amount of threads cpu has.
+ #[inline]
+ pub fn write(&mut self, writer: &mut W) -> std::io::Result<()> {
+ self.write_with_threads(writer, Self::get_threads())
+ }
+
+ /// Write compressed data to a writer (usually a file). Executes
+ /// [`compress_with_threads`](Self::compress_with_threads) if files were added between last
+ /// [`compress`](Self::compress) call and this call. Allows specifying amount of threads that
+ /// will be used.
+ ///
+ /// Example of getting amount of threads that this library uses in [`write`](Self::write):
+ ///
+ /// ```
+ /// # use std::num::NonZeroUsize;
+ /// # use mtzip::ZipArchive;
+ /// # let mut zipper = ZipArchive::new();
+ /// let threads = std::thread::available_parallelism()
+ /// .map(NonZeroUsize::get)
+ /// .unwrap_or(1);
+ ///
+ /// zipper.compress_with_threads(threads);
+ /// ```
+ #[inline]
+ pub fn write_with_threads(&mut self, writer: &mut W, threads: usize) -> std::io::Result<()> {
+ if !self.jobs_queue.is_empty() {
+ self.compress_with_consumer(threads, |zip_data, rx| zip_data.write(writer, rx))
+ } else {
+ self.data.write(writer, std::iter::empty())
+ }
+ }
+
+ /// Starts the compression jobs and passes teh mpsc receiver to teh consumer function, which
+ /// might either store the data in [`ZipData`] - [`Self::compress_with_threads`]; or write the
+ /// zip data as soon as it's available - [`Self::write_with_threads`]
+ fn compress_with_consumer(&mut self, threads: usize, consumer: F) -> T
+ where
+ F: FnOnce(&mut ZipData, mpsc::Receiver) -> T,
+ {
+ let jobs_drain = Mutex::new(self.jobs_queue.drain(..));
+ let jobs_drain_ref = &jobs_drain;
+ std::thread::scope(|s| {
+ let rx = {
+ let (tx, rx) = mpsc::channel();
+ for _ in 0..threads {
+ let thread_tx = tx.clone();
+ s.spawn(move || loop {
+ let next_job = jobs_drain_ref.lock().unwrap().next_back();
+ if let Some(job) = next_job {
+ thread_tx.send(job.into_file().unwrap()).unwrap();
+ } else {
+ break;
+ }
+ });
+ }
+ rx
+ };
+ consumer(&mut self.data, rx)
+ })
+ }
+
+ fn get_threads() -> usize {
+ std::thread::available_parallelism().map(NonZeroUsize::get).unwrap_or(1)
+ }
+}
+
+impl ZipArchive<'_> {
+ /// Compress contents and use rayon for parallelism.
+ ///
+ /// Uses whatever thread pool this function is executed in.
+ ///
+ /// If you want to limit the amount of threads to be used, use
+ /// [`rayon::ThreadPoolBuilder::num_threads`] and either set it as a global pool, or
+ /// [`rayon::ThreadPool::install`] the call to this method in it.
+ pub fn compress_with_rayon(&mut self) {
+ if !self.jobs_queue.is_empty() {
+ let files_par_iter = self.jobs_queue.par_drain(..).map(|job| job.into_file().unwrap());
+ self.data.files.par_extend(files_par_iter)
+ }
+ }
+
+ /// Write the contents to a writer.
+ ///
+ /// This method uses teh same thread logic as [`Self::compress_with_rayon`], refer to its
+ /// documentation for details on how to control the parallelism and thread allocation.
+ pub fn write_with_rayon(&mut self, writer: &mut W) -> std::io::Result<()> {
+ if !self.jobs_queue.is_empty() {
+ let files_par_iter = self.jobs_queue.par_drain(..).map(|job| job.into_file().unwrap());
+ self.data.write_rayon(writer, files_par_iter)
+ } else {
+ self.data.write_rayon(writer, rayon::iter::empty())
+ }
+ }
+}
diff --git a/src/bins/src/shared/fastzip/mtzip/platform/mod.rs b/src/bins/src/shared/fastzip/mtzip/platform/mod.rs
new file mode 100644
index 00000000..618d2317
--- /dev/null
+++ b/src/bins/src/shared/fastzip/mtzip/platform/mod.rs
@@ -0,0 +1,96 @@
+//! Platform-specific stuff
+
+use std::fs::Metadata;
+
+#[cfg(target_os = "windows")]
+/// OS - Windows, id 11 per Info-Zip spec
+/// Specification version 6.2
+pub(crate) const VERSION_MADE_BY: u16 = (11 << 8) + 62;
+
+#[cfg(target_os = "macos")]
+/// OS - MacOS darwin, id 19
+/// Specification version 6.2
+pub(crate) const VERSION_MADE_BY: u16 = (19 << 8) + 62;
+
+#[cfg(not(any(target_os = "windows", target_os = "macos")))]
+// Fallback
+/// OS - Unix assumed, id 3
+/// Specification version 6.2
+pub(crate) const VERSION_MADE_BY: u16 = (3 << 8) + 62;
+
+#[allow(dead_code)]
+pub(crate) const DEFAULT_UNIX_FILE_ATTRS: u16 = 0o100644;
+#[allow(dead_code)]
+pub(crate) const DEFAULT_UNIX_DIR_ATTRS: u16 = 0o040755;
+
+#[cfg(target_os = "windows")]
+pub(crate) const DEFAULT_WINDOWS_FILE_ATTRS: u16 = 128;
+#[cfg(target_os = "windows")]
+pub(crate) const DEFAULT_WINDOWS_DIR_ATTRS: u16 = 16;
+
+#[inline]
+#[allow(dead_code)]
+const fn convert_attrs(attrs: u32) -> u16 {
+ attrs as u16
+}
+
+pub(crate) fn attributes_from_fs(metadata: &Metadata) -> u16 {
+ #[cfg(target_os = "windows")]
+ {
+ use std::os::windows::fs::MetadataExt;
+ return convert_attrs(metadata.file_attributes());
+ }
+
+ #[cfg(target_os = "linux")]
+ {
+ use std::os::linux::fs::MetadataExt;
+ return convert_attrs(metadata.st_mode());
+ }
+
+ #[cfg(target_os = "macos")]
+ {
+ use std::os::darwin::fs::MetadataExt;
+ return convert_attrs(metadata.st_mode());
+ }
+
+ #[cfg(all(unix, not(target_os = "linux"), not(target_os = "macos")))]
+ {
+ use std::os::unix::fs::PermissionsExt;
+ return convert_attrs(metadata.permissions().mode());
+ }
+
+ #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos", unix)))]
+ {
+ if metadata.is_dir() {
+ return DEFAULT_UNIX_DIR_ATTRS;
+ } else {
+ return DEFAULT_UNIX_FILE_ATTRS;
+ }
+ }
+}
+
+#[cfg(target_os = "windows")]
+pub(crate) const fn default_file_attrs() -> u16 {
+ DEFAULT_WINDOWS_FILE_ATTRS
+}
+
+#[cfg(not(windows))]
+pub(crate) const fn default_file_attrs() -> u16 {
+ DEFAULT_UNIX_FILE_ATTRS
+}
+
+#[cfg(target_os = "windows")]
+pub(crate) const fn default_dir_attrs() -> u16 {
+ DEFAULT_WINDOWS_DIR_ATTRS
+}
+
+#[cfg(any(target_os = "linux", unix))]
+#[cfg(not(target_os = "windows"))]
+pub(crate) const fn default_dir_attrs() -> u16 {
+ DEFAULT_UNIX_DIR_ATTRS
+}
+
+#[cfg(not(any(target_os = "windows", target_os = "linux", unix)))]
+pub(crate) const fn default_dir_attrs() -> u16 {
+ 0
+}
diff --git a/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/data.rs b/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/data.rs
new file mode 100644
index 00000000..877e75d4
--- /dev/null
+++ b/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/data.rs
@@ -0,0 +1,156 @@
+use std::io::{Seek, Write};
+use std::sync::Mutex;
+
+use rayon::prelude::*;
+
+use super::file::{ZipFile, ZipFileNoData};
+
+const END_OF_CENTRAL_DIR_SIGNATURE: u32 = 0x06054B50;
+
+#[derive(Debug, Default)]
+pub struct ZipData {
+ pub files: Vec,
+}
+
+impl ZipData {
+ pub fn write>(
+ &mut self,
+ buf: &mut W,
+ zip_file_iter: I,
+ ) -> std::io::Result<()> {
+ let zip_files = self.write_files_contained_and_iter(buf, zip_file_iter)?;
+
+ let files_amount = super::files_amount_u16(&zip_files);
+
+ let central_dir_offset = super::stream_position_u32(buf)?;
+
+ self.write_central_dir(zip_files, buf)?;
+
+ let central_dir_start = super::stream_position_u32(buf)?;
+
+ self.write_end_of_central_directory(
+ buf,
+ central_dir_offset,
+ central_dir_start,
+ files_amount,
+ )
+ }
+
+ pub fn write_rayon>(
+ &mut self,
+ buf: &mut W,
+ zip_file_iter: I,
+ ) -> std::io::Result<()> {
+ let zip_files = self.write_files_contained_and_par_iter(buf, zip_file_iter)?;
+
+ let files_amount = super::files_amount_u16(&zip_files);
+
+ let central_dir_offset = super::stream_position_u32(buf)?;
+
+ self.write_central_dir(zip_files, buf)?;
+
+ let central_dir_start = super::stream_position_u32(buf)?;
+
+ self.write_end_of_central_directory(
+ buf,
+ central_dir_offset,
+ central_dir_start,
+ files_amount,
+ )
+ }
+
+ #[inline]
+ fn write_files_contained_and_iter>(
+ &mut self,
+ buf: &mut W,
+ zip_files_iter: I,
+ ) -> std::io::Result> {
+ let zip_files = std::mem::take(&mut self.files);
+ self.write_files_iter(buf, zip_files.into_iter().chain(zip_files_iter))
+ }
+
+ #[inline]
+ pub fn write_files_contained_and_par_iter<
+ W: Write + Seek + Send,
+ I: ParallelIterator
- ,
+ >(
+ &mut self,
+ buf: &mut W,
+ zip_files_iter: I,
+ ) -> std::io::Result> {
+ let zip_files = std::mem::take(&mut self.files);
+ self.write_files_par_iter(buf, zip_files.into_par_iter().chain(zip_files_iter))
+ }
+
+ pub fn write_files_iter>(
+ &mut self,
+ buf: &mut W,
+ zip_files: I,
+ ) -> std::io::Result> {
+ zip_files
+ .into_iter()
+ .map(|zipfile| zipfile.write_local_file_header_with_data_consuming(buf))
+ .collect::>>()
+ }
+
+ pub fn write_files_par_iter>(
+ &mut self,
+ buf: &mut W,
+ zip_files: I,
+ ) -> std::io::Result> {
+ let buf = Mutex::new(buf);
+ zip_files
+ .map(|zipfile| {
+ let mut buf_lock = buf.lock().unwrap();
+ zipfile.write_local_file_header_with_data_consuming(*buf_lock)
+ })
+ .collect::>>()
+ }
+
+ fn write_central_dir>(
+ &self,
+ zip_files: I,
+ buf: &mut W,
+ ) -> std::io::Result<()> {
+ zip_files
+ .into_iter()
+ .try_for_each(|zip_file| zip_file.write_central_directory_entry(buf))
+ }
+
+ const FOOTER_LENGTH: usize = 22;
+
+ fn write_end_of_central_directory(
+ &self,
+ buf: &mut W,
+ central_dir_offset: u32,
+ central_dir_start: u32,
+ files_amount: u16,
+ ) -> std::io::Result<()> {
+ // Temporary in-memory statically sized array
+ let mut central_dir = [0; Self::FOOTER_LENGTH];
+ {
+ let mut central_dir_buf: &mut [u8] = &mut central_dir;
+
+ // Signature
+ central_dir_buf.write_all(&END_OF_CENTRAL_DIR_SIGNATURE.to_le_bytes())?;
+ // number of this disk
+ central_dir_buf.write_all(&0_u16.to_le_bytes())?;
+ // number of the disk with start
+ central_dir_buf.write_all(&0_u16.to_le_bytes())?;
+ // Number of entries on this disk
+ central_dir_buf.write_all(&files_amount.to_le_bytes())?;
+ // Number of entries
+ central_dir_buf.write_all(&files_amount.to_le_bytes())?;
+ // Central dir size
+ central_dir_buf.write_all(&(central_dir_start - central_dir_offset).to_le_bytes())?;
+ // Central dir offset
+ central_dir_buf.write_all(¢ral_dir_offset.to_le_bytes())?;
+ // Comment length
+ central_dir_buf.write_all(&0_u16.to_le_bytes())?;
+ }
+
+ buf.write_all(¢ral_dir)?;
+
+ Ok(())
+ }
+}
diff --git a/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/extra_field.rs b/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/extra_field.rs
new file mode 100644
index 00000000..4849f28b
--- /dev/null
+++ b/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/extra_field.rs
@@ -0,0 +1,273 @@
+//! ZIP file extra field
+
+use std::{fs::Metadata, io::Write};
+
+/// This is a structure containing [`ExtraField`]s associated with a file or directory in a zip
+/// file, mostly used for filesystem properties, and this is the only functionality implemented
+/// here.
+///
+/// The [`new_from_fs`](Self::new_from_fs) method will use the metadata the filesystem provides to
+/// construct the collection.
+#[derive(Debug, Clone, Default, PartialEq, Eq)]
+pub struct ExtraFields {
+ pub(crate) values: Vec,
+}
+
+impl Extend for ExtraFields {
+ fn extend>(&mut self, iter: T) {
+ self.values.extend(iter)
+ }
+}
+
+impl IntoIterator for ExtraFields {
+ type Item = as IntoIterator>::Item;
+ type IntoIter = as IntoIterator>::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.values.into_iter()
+ }
+}
+
+impl ExtraFields {
+ /// Create a new set of [`ExtraField`]s. [`Self::new_from_fs`] should be preferred.
+ ///
+ /// # Safety
+ ///
+ /// All fields must have valid values depending on the field type.
+ pub unsafe fn new(fields: I) -> Self
+ where
+ I: IntoIterator
- ,
+ {
+ Self { values: fields.into_iter().collect() }
+ }
+
+ /// This method will use the filesystem metadata to get the properties that can be stored in
+ /// ZIP [`ExtraFields`].
+ ///
+ /// The behavior is dependent on the target platform. Will return an empty set if the target os
+ /// is not Windows or Linux and not of UNIX family.
+ pub fn new_from_fs(metadata: &Metadata) -> Self {
+ #[cfg(target_os = "windows")]
+ {
+ return Self::new_windows(metadata);
+ }
+
+ #[cfg(target_os = "linux")]
+ {
+ return Self::new_linux(metadata);
+ }
+
+ #[cfg(all(unix, not(target_os = "linux")))]
+ {
+ return Self::new_unix(metadata);
+ }
+ }
+
+ #[cfg(target_os = "linux")]
+ fn new_linux(metadata: &Metadata) -> Self {
+ use std::os::linux::fs::MetadataExt;
+
+ let mod_time = Some(metadata.st_mtime() as i32);
+ let ac_time = Some(metadata.st_atime() as i32);
+ let cr_time = Some(metadata.st_ctime() as i32);
+
+ let uid = metadata.st_uid();
+ let gid = metadata.st_gid();
+
+ Self { values: vec![ExtraField::UnixExtendedTimestamp { mod_time, ac_time, cr_time }, ExtraField::UnixAttrs { uid, gid }] }
+ }
+
+ #[cfg(all(unix, not(target_os = "linux")))]
+ #[allow(dead_code)]
+ fn new_unix(metadata: &Metadata) -> Self {
+ use std::os::unix::fs::MetadataExt;
+
+ let mod_time = Some(metadata.mtime() as i32);
+ let ac_time = Some(metadata.atime() as i32);
+ let cr_time = Some(metadata.ctime() as i32);
+
+ let uid = metadata.uid();
+ let gid = metadata.gid();
+
+ Self { values: vec![ExtraField::UnixExtendedTimestamp { mod_time, ac_time, cr_time }, ExtraField::UnixAttrs { uid, gid }] }
+ }
+
+ #[cfg(target_os = "windows")]
+ fn new_windows(metadata: &Metadata) -> Self {
+ use std::os::windows::fs::MetadataExt;
+
+ let mtime = metadata.last_write_time();
+ let atime = metadata.last_access_time();
+ let ctime = metadata.creation_time();
+
+ Self { values: vec![ExtraField::Ntfs { mtime, atime, ctime }] }
+ }
+
+ pub(crate) fn data_length(&self) -> u16 {
+ self.values.iter().map(|f| 4 + f.field_size::()).sum()
+ }
+
+ pub(crate) fn write(&self, writer: &mut W) -> std::io::Result<()> {
+ for field in &self.values {
+ field.write::<_, CENTRAL_HEADER>(writer)?;
+ }
+ Ok(())
+ }
+}
+
+/// Extra data that can be associated with a file or directory.
+///
+/// This library only implements the filesystem properties in NTFS and UNIX format.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ExtraField {
+ /// NTFS file properties.
+ Ntfs {
+ /// Last modification timestamp
+ mtime: u64,
+ /// Last access timestamp
+ atime: u64,
+ /// File/directory creation timestamp
+ ctime: u64,
+ },
+ /// Info-Zip extended unix timestamp. Each part is optional by definition, but will be
+ /// populated by [`ExtraFields::new_from_fs`].
+ UnixExtendedTimestamp {
+ /// Last modification timestamp
+ mod_time: Option,
+ /// Last access timestamp
+ ac_time: Option,
+ /// Creation timestamp
+ cr_time: Option,
+ },
+ /// UNIX file/directory attributes defined by Info-Zip.
+ UnixAttrs {
+ /// UID of the owner
+ uid: u32,
+ /// GID of the group
+ gid: u32,
+ },
+}
+
+const MOD_TIME_PRESENT: u8 = 1;
+const AC_TIME_PRESENT: u8 = 1 << 1;
+const CR_TIME_PRESENT: u8 = 1 << 2;
+
+impl ExtraField {
+ #[inline]
+ fn header_id(&self) -> u16 {
+ match self {
+ Self::Ntfs { mtime: _, atime: _, ctime: _ } => 0x000a,
+ Self::UnixExtendedTimestamp { mod_time: _, ac_time: _, cr_time: _ } => 0x5455,
+ Self::UnixAttrs { uid: _, gid: _ } => 0x7875,
+ }
+ }
+
+ #[inline]
+ const fn optional_field_size(field: &Option) -> u16 {
+ match field {
+ Some(_) => std::mem::size_of::() as u16,
+ None => 0,
+ }
+ }
+
+ #[inline]
+ const fn field_size(&self) -> u16 {
+ match self {
+ Self::Ntfs { mtime: _, atime: _, ctime: _ } => 32,
+ Self::UnixExtendedTimestamp { mod_time, ac_time, cr_time } => {
+ 1 + Self::optional_field_size(mod_time) + {
+ if !CENTRAL_HEADER {
+ Self::optional_field_size(ac_time) + Self::optional_field_size(cr_time)
+ } else {
+ 0
+ }
+ }
+ }
+ Self::UnixAttrs { uid: _, gid: _ } => 11,
+ }
+ }
+
+ #[inline]
+ const fn if_present(val: Option, if_present: u8) -> u8 {
+ match val {
+ Some(_) => if_present,
+ None => 0,
+ }
+ }
+
+ const NTFS_FIELD_LEN: usize = 32;
+ const UNIX_ATTRS_LEN: usize = 11;
+
+ pub(crate) fn write(self, writer: &mut W) -> std::io::Result<()> {
+ // Header ID
+ writer.write_all(&self.header_id().to_le_bytes())?;
+ // Field data size
+ writer.write_all(&self.field_size::().to_le_bytes())?;
+
+ match self {
+ Self::Ntfs { mtime, atime, ctime } => {
+ // Writing to a temporary in-memory array
+ let mut field = [0; Self::NTFS_FIELD_LEN];
+ {
+ let mut field_buf: &mut [u8] = &mut field;
+
+ // Reserved field
+ field_buf.write_all(&0_u32.to_le_bytes())?;
+
+ // Tag1 number
+ field_buf.write_all(&1_u16.to_le_bytes())?;
+ // Tag1 size
+ field_buf.write_all(&24_u16.to_le_bytes())?;
+
+ // Mtime
+ field_buf.write_all(&mtime.to_le_bytes())?;
+ // Atime
+ field_buf.write_all(&atime.to_le_bytes())?;
+ // Ctime
+ field_buf.write_all(&ctime.to_le_bytes())?;
+ }
+
+ writer.write_all(&field)?;
+ }
+ Self::UnixExtendedTimestamp { mod_time, ac_time, cr_time } => {
+ let flags = Self::if_present(mod_time, MOD_TIME_PRESENT)
+ | Self::if_present(ac_time, AC_TIME_PRESENT)
+ | Self::if_present(cr_time, CR_TIME_PRESENT);
+ writer.write_all(&[flags])?;
+ if let Some(mod_time) = mod_time {
+ writer.write_all(&mod_time.to_le_bytes())?;
+ }
+ if !CENTRAL_HEADER {
+ if let Some(ac_time) = ac_time {
+ writer.write_all(&ac_time.to_le_bytes())?;
+ }
+ if let Some(cr_time) = cr_time {
+ writer.write_all(&cr_time.to_le_bytes())?;
+ }
+ }
+ }
+ Self::UnixAttrs { uid, gid } => {
+ // Writing to a temporary in-memory array
+ let mut field = [0; Self::UNIX_ATTRS_LEN];
+ {
+ let mut field_buf: &mut [u8] = &mut field;
+
+ // Version of the field
+ field_buf.write_all(&[1])?;
+ // UID size
+ field_buf.write_all(&[4])?;
+ // UID
+ field_buf.write_all(&uid.to_le_bytes())?;
+ // GID size
+ field_buf.write_all(&[4])?;
+ // GID
+ field_buf.write_all(&gid.to_le_bytes())?;
+ }
+
+ writer.write_all(&field)?;
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/file.rs b/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/file.rs
new file mode 100644
index 00000000..14300e9c
--- /dev/null
+++ b/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/file.rs
@@ -0,0 +1,201 @@
+use std::io::{Seek, Write};
+
+use super::extra_field::ExtraFields;
+use super::super::{CompressionType, platform::VERSION_MADE_BY};
+
+const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034B50;
+const CENTRAL_FILE_HEADER_SIGNATURE: u32 = 0x02014B50;
+
+const VERSION_NEEDED_TO_EXTRACT: u16 = 20;
+
+/// Set bit 11 to indicate that the file names are in UTF-8, because all strings in rust are valid
+/// UTF-8
+const GENERAL_PURPOSE_BIT_FLAG: u16 = 1 << 11;
+
+#[derive(Debug)]
+pub struct ZipFile {
+ pub header: ZipFileHeader,
+ pub data: Vec,
+}
+
+#[derive(Debug)]
+pub struct ZipFileHeader {
+ pub compression_type: CompressionType,
+ pub crc: u32,
+ pub uncompressed_size: u32,
+ pub filename: String,
+ pub file_comment: Option,
+ pub external_file_attributes: u32,
+ pub extra_fields: ExtraFields,
+}
+
+#[derive(Debug)]
+pub struct ZipFileNoData {
+ pub header: ZipFileHeader,
+ pub local_header_offset: u32,
+ pub compressed_size: u32,
+}
+
+impl ZipFile {
+ pub fn write_local_file_header_with_data_consuming(
+ self,
+ buf: &mut W,
+ ) -> std::io::Result {
+ let local_header_offset = super::stream_position_u32(buf)?;
+ self.write_local_file_header_and_data(buf)?;
+ let Self { header, data } = self;
+ Ok(ZipFileNoData {
+ header,
+ local_header_offset,
+ compressed_size: data.len() as u32,
+ })
+ }
+
+ const LOCAL_FILE_HEADER_LEN: usize = 30;
+
+ pub fn write_local_file_header_and_data(&self, buf: &mut W) -> std::io::Result<()> {
+ // Writing to a temporary in-memory statically sized array first
+ let mut header = [0; Self::LOCAL_FILE_HEADER_LEN];
+ {
+ let mut header_buf: &mut [u8] = &mut header;
+
+ // signature
+ header_buf.write_all(&LOCAL_FILE_HEADER_SIGNATURE.to_le_bytes())?;
+ // version needed to extract
+ header_buf.write_all(&VERSION_NEEDED_TO_EXTRACT.to_le_bytes())?;
+ // general purpose bit flag
+ header_buf.write_all(&GENERAL_PURPOSE_BIT_FLAG.to_le_bytes())?;
+ // compression type
+ header_buf.write_all(&(self.header.compression_type as u16).to_le_bytes())?;
+ // Last modification time // moved to extra fields
+ header_buf.write_all(&0_u16.to_le_bytes())?;
+ // Last modification date // moved to extra fields
+ header_buf.write_all(&0_u16.to_le_bytes())?;
+ // crc
+ header_buf.write_all(&self.header.crc.to_le_bytes())?;
+ // Compressed size
+ debug_assert!(self.data.len() <= u32::MAX as usize);
+ header_buf.write_all(&(self.data.len() as u32).to_le_bytes())?;
+ // Uncompressed size
+ header_buf.write_all(&self.header.uncompressed_size.to_le_bytes())?;
+ // Filename size
+ debug_assert!(self.header.filename.len() <= u16::MAX as usize);
+ header_buf.write_all(&(self.header.filename.len() as u16).to_le_bytes())?;
+ // extra field size
+ header_buf.write_all(
+ &self
+ .header
+ .extra_fields
+ .data_length::()
+ .to_le_bytes(),
+ )?;
+ }
+
+ buf.write_all(&header)?;
+
+ // Filename
+ buf.write_all(self.header.filename.as_bytes())?;
+ // Extra field
+ self.header.extra_fields.write::<_, false>(buf)?;
+
+ // Data
+ buf.write_all(&self.data)?;
+
+ Ok(())
+ }
+
+ #[inline]
+ pub fn directory(
+ mut name: String,
+ extra_fields: ExtraFields,
+ external_attributes: u16,
+ file_comment: Option,
+ ) -> Self {
+ if !(name.ends_with('/') || name.ends_with('\\')) {
+ name += "/"
+ };
+ Self {
+ header: ZipFileHeader {
+ compression_type: CompressionType::Stored,
+ crc: 0,
+ uncompressed_size: 0,
+ filename: name,
+ external_file_attributes: (external_attributes as u32) << 16,
+ extra_fields,
+ file_comment,
+ },
+ data: vec![],
+ }
+ }
+}
+
+impl ZipFileNoData {
+ const CENTRAL_DIR_ENTRY_LEN: usize = 46;
+
+ pub fn write_central_directory_entry(&self, buf: &mut W) -> std::io::Result<()> {
+ // Writing to a temporary in-memory statically sized array first
+ let mut central_dir_entry_header = [0; Self::CENTRAL_DIR_ENTRY_LEN];
+ {
+ let mut central_dir_entry_buf: &mut [u8] = &mut central_dir_entry_header;
+
+ // signature
+ central_dir_entry_buf.write_all(&CENTRAL_FILE_HEADER_SIGNATURE.to_le_bytes())?;
+ // version made by
+ central_dir_entry_buf.write_all(&VERSION_MADE_BY.to_le_bytes())?;
+ // version needed to extract
+ central_dir_entry_buf.write_all(&VERSION_NEEDED_TO_EXTRACT.to_le_bytes())?;
+ // general purpose bit flag
+ central_dir_entry_buf.write_all(&GENERAL_PURPOSE_BIT_FLAG.to_le_bytes())?;
+ // compression type
+ central_dir_entry_buf
+ .write_all(&(self.header.compression_type as u16).to_le_bytes())?;
+ // Last modification time // moved to extra fields
+ central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
+ // Last modification date // moved to extra fields
+ central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
+ // crc
+ central_dir_entry_buf.write_all(&self.header.crc.to_le_bytes())?;
+ // Compressed size
+ central_dir_entry_buf.write_all(&self.compressed_size.to_le_bytes())?;
+ // Uncompressed size
+ central_dir_entry_buf.write_all(&self.header.uncompressed_size.to_le_bytes())?;
+ // Filename size
+ debug_assert!(self.header.filename.len() <= u16::MAX as usize);
+ central_dir_entry_buf.write_all(&(self.header.filename.len() as u16).to_le_bytes())?;
+ // extra field size
+ central_dir_entry_buf
+ .write_all(&self.header.extra_fields.data_length::().to_le_bytes())?;
+ // comment size
+ central_dir_entry_buf.write_all(
+ &(self
+ .header
+ .file_comment
+ .as_ref()
+ .map(|fc| fc.len())
+ .unwrap_or(0) as u16)
+ .to_le_bytes(),
+ )?;
+ // disk number start
+ central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
+ // internal file attributes
+ central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
+ // external file attributes
+ central_dir_entry_buf.write_all(&self.header.external_file_attributes.to_le_bytes())?;
+ // relative offset of local header
+ central_dir_entry_buf.write_all(&self.local_header_offset.to_le_bytes())?;
+ }
+
+ buf.write_all(¢ral_dir_entry_header)?;
+
+ // Filename
+ buf.write_all(self.header.filename.as_bytes())?;
+ // Extra field
+ self.header.extra_fields.write::<_, true>(buf)?;
+ // File comment
+ if let Some(file_comment) = &self.header.file_comment {
+ buf.write_all(file_comment.as_bytes())?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/job.rs b/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/job.rs
new file mode 100644
index 00000000..cf5a3653
--- /dev/null
+++ b/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/job.rs
@@ -0,0 +1,179 @@
+use std::{
+ borrow::Cow,
+ fs::File,
+ io::Read,
+ panic::{RefUnwindSafe, UnwindSafe},
+ path::Path,
+};
+
+use flate2::{CrcReader, read::DeflateEncoder};
+
+use super::{extra_field::ExtraFields, file::ZipFile};
+use super::super::{
+ CompressionType, level::CompressionLevel, platform::attributes_from_fs,
+ zip_archive_parts::file::ZipFileHeader,
+};
+
+pub enum ZipJobOrigin<'a> {
+ Directory,
+ Filesystem { path: Cow<'a, Path> },
+ RawData(Cow<'a, [u8]>),
+ Reader(Box),
+}
+
+impl core::fmt::Debug for ZipJobOrigin<'_> {
+ #[inline]
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Self::Directory => f.write_str("Directory"),
+ Self::Filesystem { path } => f.debug_struct("Filesystem").field("path", &path).finish(),
+ Self::RawData(raw_data) => f.debug_tuple("RawData").field(&raw_data).finish(),
+ Self::Reader(_reader) => f.debug_tuple("Reader").finish_non_exhaustive(),
+ }
+ }
+}
+
+#[derive(Debug)]
+struct FileDigest {
+ data: Vec,
+ uncompressed_size: u32,
+ crc: u32,
+}
+
+#[derive(Debug)]
+pub struct ZipJob<'a> {
+ pub data_origin: ZipJobOrigin<'a>,
+ pub extra_fields: ExtraFields,
+ pub archive_path: String,
+ pub file_comment: Option,
+ pub external_attributes: u16,
+ /// Ignored when [`data_origin`](Self::data_origin) is a [`ZipJobOrigin::Directory`]
+ pub compression_level: CompressionLevel,
+ /// Ignored when [`data_origin`](Self::data_origin) is a [`ZipJobOrigin::Directory`]
+ pub compression_type: CompressionType,
+}
+
+impl ZipJob<'_> {
+ fn compress_file(
+ source: R,
+ uncompressed_size_approx: Option,
+ compression_type: CompressionType,
+ compression_level: CompressionLevel,
+ ) -> std::io::Result {
+ let mut crc_reader = CrcReader::new(source);
+ let mut data = Vec::with_capacity(uncompressed_size_approx.unwrap_or(0) as usize);
+ let uncompressed_size = match compression_type {
+ CompressionType::Deflate => {
+ let mut encoder = DeflateEncoder::new(&mut crc_reader, compression_level.into());
+ encoder.read_to_end(&mut data)?;
+ encoder.total_in() as usize
+ }
+ CompressionType::Stored => crc_reader.read_to_end(&mut data)?,
+ };
+ debug_assert!(uncompressed_size <= u32::MAX as usize);
+ let uncompressed_size = uncompressed_size as u32;
+ data.shrink_to_fit();
+ let crc = crc_reader.crc().sum();
+ Ok(FileDigest {
+ data,
+ uncompressed_size,
+ crc,
+ })
+ }
+
+ pub fn into_file(self) -> std::io::Result {
+ match self.data_origin {
+ ZipJobOrigin::Directory => Ok(ZipFile::directory(
+ self.archive_path,
+ self.extra_fields,
+ self.external_attributes,
+ self.file_comment,
+ )),
+ ZipJobOrigin::Filesystem { path } => {
+ let file = File::open(path).unwrap();
+ let file_metadata = file.metadata().unwrap();
+ let uncompressed_size_approx = file_metadata.len();
+ debug_assert!(uncompressed_size_approx <= u32::MAX.into());
+ let uncompressed_size_approx = uncompressed_size_approx as u32;
+ let external_file_attributes = attributes_from_fs(&file_metadata);
+ let mut extra_fields = ExtraFields::new_from_fs(&file_metadata);
+ extra_fields.extend(self.extra_fields);
+
+ let FileDigest {
+ data,
+ uncompressed_size,
+ crc,
+ } = Self::compress_file(
+ file,
+ Some(uncompressed_size_approx),
+ self.compression_type,
+ self.compression_level,
+ )?;
+ Ok(ZipFile {
+ header: ZipFileHeader {
+ compression_type: CompressionType::Deflate,
+ crc,
+ uncompressed_size,
+ filename: self.archive_path,
+ external_file_attributes: (external_file_attributes as u32) << 16,
+ extra_fields,
+ file_comment: self.file_comment,
+ },
+ data,
+ })
+ }
+ ZipJobOrigin::RawData(data) => {
+ let uncompressed_size_approx = data.len();
+ debug_assert!(uncompressed_size_approx <= u32::MAX as usize);
+ let uncompressed_size_approx = uncompressed_size_approx as u32;
+
+ let FileDigest {
+ data,
+ uncompressed_size,
+ crc,
+ } = Self::compress_file(
+ data.as_ref(),
+ Some(uncompressed_size_approx),
+ self.compression_type,
+ self.compression_level,
+ )?;
+ Ok(ZipFile {
+ header: ZipFileHeader {
+ compression_type: CompressionType::Deflate,
+ crc,
+ uncompressed_size,
+ filename: self.archive_path,
+ external_file_attributes: (self.external_attributes as u32) << 16,
+ extra_fields: self.extra_fields,
+ file_comment: self.file_comment,
+ },
+ data,
+ })
+ }
+ ZipJobOrigin::Reader(reader) => {
+ let FileDigest {
+ data,
+ uncompressed_size,
+ crc,
+ } = Self::compress_file(
+ reader,
+ None,
+ self.compression_type,
+ self.compression_level,
+ )?;
+ Ok(ZipFile {
+ header: ZipFileHeader {
+ compression_type: CompressionType::Deflate,
+ crc,
+ uncompressed_size,
+ filename: self.archive_path,
+ external_file_attributes: (self.external_attributes as u32) << 16,
+ extra_fields: self.extra_fields,
+ file_comment: self.file_comment,
+ },
+ data,
+ })
+ }
+ }
+ }
+}
diff --git a/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/mod.rs b/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/mod.rs
new file mode 100644
index 00000000..e2f6b8e4
--- /dev/null
+++ b/src/bins/src/shared/fastzip/mtzip/zip_archive_parts/mod.rs
@@ -0,0 +1,17 @@
+pub mod data;
+pub mod extra_field;
+pub mod file;
+pub mod job;
+use std::io::Seek;
+#[inline]
+pub fn stream_position_u32(buf: &mut W) -> std::io::Result {
+ let offset = buf.stream_position()?;
+ debug_assert!(offset <= u32::MAX.into());
+ Ok(offset as u32)
+}
+#[inline]
+pub fn files_amount_u16(files: &[T]) -> u16 {
+ let amount = files.len();
+ debug_assert!(amount <= u16::MAX as usize);
+ amount as u16
+}
diff --git a/src/bins/src/shared/fastzip/progress_updater.rs b/src/bins/src/shared/fastzip/progress_updater.rs
new file mode 100644
index 00000000..c7385bf4
--- /dev/null
+++ b/src/bins/src/shared/fastzip/progress_updater.rs
@@ -0,0 +1,142 @@
+// Copyright 2023 Google LLC
+
+// Licensed under the Apache License, Version 2.0 or the MIT license
+// , at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::cmp::min;
+
+/// A struct which can issue periodic updates indicating progress towards
+/// an external total, based on updates towards an internal goal.
+pub struct ProgressUpdater {
+ callback: F,
+ internal_progress: u64,
+ per_update_internal: u64,
+ update_external_amount: u64,
+ external_updates_sent: u64,
+ remainder_external: u64,
+ internal_total: u64,
+}
+
+impl ProgressUpdater {
+ /// Create a new progress updater, with a callback to be called periodically.
+ pub fn new(callback: F, external_total: u64, internal_total: u64, per_update_internal: u64) -> Self {
+ let per_update_internal = min(internal_total, per_update_internal);
+ let total_updates_expected = if per_update_internal == 0 { 0 } else { internal_total / per_update_internal };
+ let (update_external_amount, remainder_external) = if total_updates_expected == 0 {
+ (0, external_total)
+ } else {
+ (external_total / total_updates_expected, external_total % total_updates_expected)
+ };
+ Self {
+ callback,
+ internal_progress: 0u64,
+ per_update_internal,
+ update_external_amount,
+ external_updates_sent: 0u64,
+ remainder_external,
+ internal_total,
+ }
+ }
+
+ /// Indicate some progress towards the internal goal. May call back the
+ /// external callback function to show some progress towards the external
+ /// goal.
+ pub fn progress(&mut self, amount_internal: u64) {
+ self.internal_progress += amount_internal;
+ self.send_due_updates();
+ }
+
+ fn send_due_updates(&mut self) {
+ let updates_due = if self.per_update_internal == 0 { 0 } else { self.internal_progress / self.per_update_internal };
+ while updates_due > self.external_updates_sent {
+ (self.callback)(self.update_external_amount);
+ self.external_updates_sent += 1;
+ }
+ }
+
+ /// Indicate completion of the task. Fully update the callback towards the
+ /// external state.
+ pub fn finish(&mut self) {
+ self.internal_progress = self.internal_total;
+ self.send_due_updates();
+ if self.remainder_external > 0 {
+ (self.callback)(self.remainder_external);
+ }
+ }
+}
+
+#[test]
+fn test_progress_updater() {
+ let amount_received = std::rc::Rc::new(std::cell::RefCell::new(0u64));
+ let mut progresser = ProgressUpdater::new(
+ |progress| {
+ *(amount_received.borrow_mut()) += progress;
+ },
+ 100,
+ 1000,
+ 100,
+ );
+ assert_eq!(*amount_received.borrow(), 0);
+ progresser.progress(1);
+ assert_eq!(*amount_received.borrow(), 0);
+ progresser.progress(100);
+ assert_eq!(*amount_received.borrow(), 10);
+ progresser.progress(800);
+ assert_eq!(*amount_received.borrow(), 90);
+ progresser.finish();
+ assert_eq!(*amount_received.borrow(), 100);
+}
+
+#[test]
+fn test_progress_updater_zero_external() {
+ let amount_received = std::rc::Rc::new(std::cell::RefCell::new(0u64));
+ let mut progresser = ProgressUpdater::new(
+ |progress| {
+ *(amount_received.borrow_mut()) += progress;
+ },
+ 0,
+ 1000,
+ 100,
+ );
+ assert_eq!(*amount_received.borrow(), 0);
+ progresser.progress(1);
+ progresser.progress(800);
+ progresser.finish();
+ assert_eq!(*amount_received.borrow(), 0);
+}
+
+#[test]
+fn test_progress_updater_small_internal() {
+ let amount_received = std::rc::Rc::new(std::cell::RefCell::new(0u64));
+ let mut progresser = ProgressUpdater::new(
+ |progress| {
+ *(amount_received.borrow_mut()) += progress;
+ },
+ 100,
+ 5,
+ 100,
+ );
+ assert_eq!(*amount_received.borrow(), 0);
+ progresser.progress(1);
+ progresser.finish();
+ assert_eq!(*amount_received.borrow(), 100);
+}
+
+#[test]
+fn test_progress_updater_zero_internal() {
+ let amount_received = std::rc::Rc::new(std::cell::RefCell::new(0u64));
+ let mut progresser = ProgressUpdater::new(
+ |progress| {
+ *(amount_received.borrow_mut()) += progress;
+ },
+ 100,
+ 0,
+ 100,
+ );
+ assert_eq!(*amount_received.borrow(), 0);
+ progresser.finish();
+ assert_eq!(*amount_received.borrow(), 100);
+}
diff --git a/src/bins/src/shared/fastzip/ripunzip.rs b/src/bins/src/shared/fastzip/ripunzip.rs
new file mode 100644
index 00000000..7c5096a3
--- /dev/null
+++ b/src/bins/src/shared/fastzip/ripunzip.rs
@@ -0,0 +1,327 @@
+// Copyright 2022 Google LLC
+
+// Licensed under the Apache License, Version 2.0 or the MIT license
+// , at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::{
+ borrow::Cow,
+ fs::File,
+ io::{ErrorKind, Read, Seek, SeekFrom},
+ path::{Path, PathBuf},
+ sync::Mutex,
+};
+
+use anyhow::{Context, Result};
+use rayon::prelude::*;
+use zip::{read::ZipFile, ZipArchive};
+
+use super::{cloneable_seekable_reader::CloneableSeekableReader, progress_updater::ProgressUpdater, UnzipProgressReporter};
+
+pub(crate) fn determine_stream_len(stream: &mut R) -> std::io::Result {
+ let old_pos = stream.stream_position()?;
+ let len = stream.seek(SeekFrom::End(0))?;
+ if old_pos != len {
+ stream.seek(SeekFrom::Start(old_pos))?;
+ }
+ Ok(len)
+}
+
+/// Options for unzipping.
+pub struct UnzipOptions<'a, 'b> {
+ /// The destination directory.
+ pub output_directory: Option,
+ /// Password if encrypted.
+ pub password: Option,
+ /// Whether to run in single-threaded mode.
+ pub single_threaded: bool,
+ /// A filename filter, optionally
+ pub filename_filter: Option>,
+ /// An object to receive notifications of unzip progress.
+ pub progress_reporter: Box,
+}
+
+/// An object which can unzip a zip file, in its entirety, from a local
+/// file or from a network stream. It tries to do this in parallel wherever
+/// possible.
+pub struct UnzipEngine {
+ zipfile: Box,
+ compressed_length: u64,
+ directory_creator: DirectoryCreator,
+}
+
+/// Code which can determine whether to unzip a given filename.
+pub trait FilenameFilter {
+ /// Returns true if the given filename should be unzipped.
+ fn should_unzip(&self, filename: &str) -> bool;
+}
+
+/// The underlying engine used by the unzipper. This is different
+/// for files and URIs.
+trait UnzipEngineImpl {
+ fn unzip(&mut self, options: UnzipOptions, directory_creator: &DirectoryCreator) -> Vec;
+
+ // Due to lack of RPITIT we'll return a Vec here
+ fn list(&self) -> Result, anyhow::Error>;
+}
+
+/// Engine which knows how to unzip a file.
+#[derive(Clone)]
+struct UnzipFileEngine(ZipArchive>);
+
+impl UnzipEngineImpl for UnzipFileEngine {
+ fn unzip(&mut self, options: UnzipOptions, directory_creator: &DirectoryCreator) -> Vec {
+ unzip_serial_or_parallel(self.0.len(), options, directory_creator, || self.0.clone(), || {})
+ }
+
+ fn list(&self) -> Result, anyhow::Error> {
+ list(&self.0)
+ }
+}
+
+impl UnzipEngine {
+ /// Create an unzip engine which knows how to unzip a file.
+ pub fn for_file(mut zipfile: File) -> Result {
+ // The following line doesn't actually seem to make any significant
+ // performance difference.
+ // let zipfile = BufReader::new(zipfile);
+ let compressed_length = determine_stream_len(&mut zipfile)?;
+ let zipfile = CloneableSeekableReader::new(zipfile);
+ Ok(Self {
+ zipfile: Box::new(UnzipFileEngine(ZipArchive::new(zipfile)?)),
+ compressed_length,
+ directory_creator: DirectoryCreator::default(),
+ })
+ }
+
+ /// The total compressed length that we expect to retrieve over
+ /// the network or from the compressed file.
+ pub fn zip_length(&self) -> u64 {
+ self.compressed_length
+ }
+
+ // Perform the unzip.
+ pub fn unzip(mut self, options: UnzipOptions) -> Result<()> {
+ log::debug!("Starting extract");
+ options.progress_reporter.total_bytes_expected(self.compressed_length);
+ let errors = self.zipfile.unzip(options, &self.directory_creator);
+ // Return the first error code, if any.
+ errors.into_iter().next().map(Result::Err).unwrap_or(Ok(()))
+ }
+
+ /// List the filenames in the archive
+ pub fn list(self) -> Result> {
+ // In future this might be a more dynamic iterator type.
+ self.zipfile.list().map(|mut v| {
+ // Names are returned in a HashMap iteration order so let's
+ // sort thme to be more reasonable
+ v.sort();
+ v.into_iter()
+ })
+ }
+}
+
+/// Return a list of filenames from the zip. For now this is infallible
+/// but provide the option of an error code in case we do something
+/// smarter in future.
+fn list<'a, T: Read + Seek + 'a>(zip_archive: &ZipArchive) -> Result> {
+ Ok(zip_archive.file_names().map(|s| s.to_string()).collect())
+}
+
+fn unzip_serial_or_parallel<'a, T: Read + Seek + 'a>(
+ len: usize,
+ options: UnzipOptions,
+ directory_creator: &DirectoryCreator,
+ get_ziparchive_clone: impl Fn() -> ZipArchive + Sync,
+ // Call when a file is going to be skipped
+ file_skip_callback: impl Fn() + Sync + Send + Clone,
+) -> Vec {
+ let progress_reporter: &dyn UnzipProgressReporter = options.progress_reporter.as_ref();
+ match (options.filename_filter, options.single_threaded) {
+ (None, true) => (0..len)
+ .map(|i| {
+ extract_file_by_index(
+ &get_ziparchive_clone,
+ i,
+ &options.output_directory,
+ &options.password,
+ progress_reporter,
+ directory_creator,
+ )
+ })
+ .filter_map(Result::err)
+ .collect(),
+ (None, false) => {
+ // We use par_bridge here rather than into_par_iter because it turns
+ // out to better preserve ordering of the IDs in the input range,
+ // i.e. we're more likely to ask our initial threads to act upon
+ // file IDs 0, 1, 2, 3, 4, 5 rather than 0, 1000, 2000, 3000 etc.
+ // On a device which is CPU-bound or IO-bound (rather than network
+ // bound) that's beneficial because we can start to decompress
+ // and write data to disk as soon as it arrives from the network.
+ (0..len)
+ .par_bridge()
+ .map(|i| {
+ extract_file_by_index(
+ &get_ziparchive_clone,
+ i,
+ &options.output_directory,
+ &options.password,
+ progress_reporter,
+ directory_creator,
+ )
+ })
+ .filter_map(Result::err)
+ .collect()
+ }
+ (Some(filename_filter), single_threaded) => {
+ // If we have a filename filter, an easy thing would be to
+ // iterate through each file index as above, and check to see if its
+ // name matches. Unfortunately, that seeks all over the place
+ // to get the filename from the local header.
+ // Instead, let's get a list of the filenames we need
+ // and request them from the zip library directly.
+ // As we can't predict their order in the file, this may involve
+ // arbitrary rewinds, so let's do it single-threaded.
+ if !single_threaded {
+ log::warn!("Unzipping specific files - assuming --single-threaded since we currently cannot unzip specific files in a multi-threaded mode. If you need that, consider launching multiple copies of ripunzip in parallel.");
+ }
+ let mut filenames: Vec<_> = get_ziparchive_clone()
+ .file_names()
+ .filter(|name| filename_filter.as_ref().should_unzip(name))
+ .map(|s| s.to_string())
+ .collect();
+ // The filenames returned by the file_names() method above are in
+ // HashMap iteration order (i.e. random). To avoid creating lots
+ // of HTTPS streams for files which are nearby each other in the
+ // zip, we'd ideally extract them in order of file position.
+ // We have no way of knowing file position (without iterating the
+ // whole file) so instead let's sort them and hope that files were
+ // zipped in alphabetical order, or close to it. If we're wrong,
+ // we'll just end up rewinding, that is, creating extra redundant
+ // HTTP(S) streams.
+ filenames.sort();
+ log::info!("Will unzip {} matching filenames", filenames.len());
+ file_skip_callback();
+
+ // let progress_reporter: &dyn UnzipProgressReporter = options.progress_reporter.as_ref();
+ filenames
+ .into_iter()
+ .map(|name| {
+ let myzip: &mut zip::ZipArchive = &mut get_ziparchive_clone();
+ let file: ZipFile = match &options.password {
+ None => myzip.by_name(&name)?,
+ Some(string) => myzip.by_name_decrypt(&name, string.as_bytes())?,
+ };
+ let r = extract_file(file, &options.output_directory, progress_reporter, directory_creator);
+ file_skip_callback();
+ r
+ })
+ .filter_map(Result::err)
+ .collect()
+ }
+ }
+}
+
+fn extract_file_by_index<'a, T: Read + Seek + 'a>(
+ get_ziparchive_clone: impl Fn() -> ZipArchive + Sync,
+ i: usize,
+ output_directory: &Option,
+ password: &Option,
+ progress_reporter: &dyn UnzipProgressReporter,
+ directory_creator: &DirectoryCreator,
+) -> Result<(), anyhow::Error> {
+ let myzip: &mut zip::ZipArchive = &mut get_ziparchive_clone();
+ let file: ZipFile = match password {
+ None => myzip.by_index(i)?,
+ Some(string) => myzip.by_index_decrypt(i, string.as_bytes())?,
+ };
+ extract_file(file, output_directory, progress_reporter, directory_creator)
+}
+
+fn extract_file(
+ file: ZipFile,
+ output_directory: &Option,
+ progress_reporter: &dyn UnzipProgressReporter,
+ directory_creator: &DirectoryCreator,
+) -> Result<(), anyhow::Error> {
+ let name = file.enclosed_name().as_deref().map(Path::to_string_lossy).unwrap_or_else(|| Cow::Borrowed("")).to_string();
+ extract_file_inner(file, output_directory, progress_reporter, directory_creator).with_context(|| format!("Failed to extract {name}"))
+}
+
+/// Extracts a file from a zip file.
+fn extract_file_inner(
+ mut file: ZipFile,
+ output_directory: &Option,
+ progress_reporter: &dyn UnzipProgressReporter,
+ directory_creator: &DirectoryCreator,
+) -> Result<()> {
+ let name = file.enclosed_name().ok_or_else(|| std::io::Error::new(ErrorKind::Unsupported, "path not safe to extract"))?;
+ let display_name = name.display().to_string();
+ let out_path = match output_directory {
+ Some(output_directory) => output_directory.join(name),
+ None => name,
+ };
+ progress_reporter.extraction_starting(&display_name);
+ log::debug!("Start extract of file at {:x}, length {:x}, name {}", file.data_start(), file.compressed_size(), display_name);
+ if file.name().ends_with('/') {
+ directory_creator.create_dir_all(&out_path)?;
+ } else {
+ if let Some(parent) = out_path.parent() {
+ directory_creator.create_dir_all(parent)?;
+ }
+ let out_file = File::create(&out_path).with_context(|| "Failed to create file")?;
+ // Progress bar strategy. The overall progress across the entire zip file must be
+ // denoted in terms of *compressed* bytes, since at the outset we don't know the uncompressed
+ // size of each file. Yet, within a given file, we update progress based on the bytes
+ // of uncompressed data written, once per 1MB, because that's the information that we happen
+ // to have available. So, calculate how many compressed bytes relate to 1MB of uncompressed
+ // data, and the remainder.
+ let uncompressed_size = file.size();
+ let compressed_size = file.compressed_size();
+ let mut progress_updater = ProgressUpdater::new(
+ |external_progress| {
+ progress_reporter.bytes_extracted(external_progress);
+ },
+ compressed_size,
+ uncompressed_size,
+ 1024 * 1024,
+ );
+ let mut out_file = progress_streams::ProgressWriter::new(out_file, |bytes_written| progress_updater.progress(bytes_written as u64));
+ // Using a BufWriter here doesn't improve performance even on a VM with
+ // spinny disks.
+ std::io::copy(&mut file, &mut out_file).with_context(|| "Failed to write directory")?;
+ progress_updater.finish();
+ }
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+ if let Some(mode) = file.unix_mode() {
+ std::fs::set_permissions(&out_path, std::fs::Permissions::from_mode(mode)).with_context(|| "Failed to set permissions")?;
+ }
+ }
+ log::debug!("Finished extract of file at {:x}, length {:x}, name {}", file.data_start(), file.compressed_size(), display_name);
+ progress_reporter.extraction_finished(&display_name);
+ Ok(())
+}
+
+/// An engine used to ensure we don't conflict in creating directories
+/// between threads
+#[derive(Default)]
+struct DirectoryCreator(Mutex<()>);
+
+impl DirectoryCreator {
+ fn create_dir_all(&self, path: &Path) -> Result<()> {
+ // Fast path - avoid locking if the directory exists
+ if path.exists() {
+ return Ok(());
+ }
+ let _exclusivity = self.0.lock().unwrap();
+ if path.exists() {
+ return Ok(());
+ }
+ std::fs::create_dir_all(path).with_context(|| "Failed to create directory")
+ }
+}
diff --git a/src/bins/src/shared/mod.rs b/src/bins/src/shared/mod.rs
index f1523676..9c089cec 100644
--- a/src/bins/src/shared/mod.rs
+++ b/src/bins/src/shared/mod.rs
@@ -1,5 +1,6 @@
pub mod runtime_arch;
pub mod cli_host;
+pub mod fastzip;
mod dialogs_const;
mod dialogs_common;
diff --git a/src/bins/src/shared/runtime_arch.rs b/src/bins/src/shared/runtime_arch.rs
index 639f287d..4845ea96 100644
--- a/src/bins/src/shared/runtime_arch.rs
+++ b/src/bins/src/shared/runtime_arch.rs
@@ -78,7 +78,7 @@ fn check_arch_windows() -> Option {
#[cfg(target_os = "windows")]
type IsWow64Process2Fn = unsafe extern "system" fn(
- hProcess: windows::Win32::Foundation::HANDLE,
+ hprocess: windows::Win32::Foundation::HANDLE,
pprocessmachine: *mut windows::Win32::System::SystemInformation::IMAGE_FILE_MACHINE,
pnativemachine: *mut windows::Win32::System::SystemInformation::IMAGE_FILE_MACHINE,
) -> windows::core::BOOL;
diff --git a/src/bins/src/shared/util_windows.rs b/src/bins/src/shared/util_windows.rs
index f6e78d7f..5dbc0d84 100644
--- a/src/bins/src/shared/util_windows.rs
+++ b/src/bins/src/shared/util_windows.rs
@@ -171,12 +171,21 @@ pub fn has_app_prefixed_folder>(parent_path: P) -> bool {
}
}
-pub fn delete_app_prefixed_folders>(parent_path: P) -> Result<()> {
- let folders = get_app_prefixed_folders(parent_path)?;
- for folder in folders {
- super::retry_io(|| remove_dir_all::remove_dir_all(&folder))?;
+pub fn delete_app_prefixed_folders>(parent_path: P) {
+ match get_app_prefixed_folders(parent_path) {
+ Ok(folders) => {
+ for folder in folders {
+ if let Err(e) = super::retry_io(|| remove_dir_all::remove_dir_all(&folder)) {
+ warn!("Failed to delete app-prefixed folder: {} ({})", folder.display(), e);
+ } else {
+ info!("Deleted app-prefixed folder: {}", folder.display());
+ }
+ }
+ }
+ Err(e) => {
+ warn!("Failed to find app-prefixed folders: {}", e);
+ }
}
- Ok(())
}
fn parse_version_from_folder_name(folder_name: &str) -> Option {
diff --git a/src/bins/src/update.rs b/src/bins/src/update.rs
index aac8bc67..fe84e0a5 100644
--- a/src/bins/src/update.rs
+++ b/src/bins/src/update.rs
@@ -5,7 +5,7 @@
extern crate log;
use anyhow::{anyhow, bail, Result};
-use clap::{arg, value_parser, ArgMatches, Command};
+use clap::{arg, value_parser, ArgAction, ArgMatches, Command};
use std::{env, path::PathBuf};
use velopack::locator::{auto_locate_app_manifest, LocationContext};
use velopack::logging::*;
@@ -34,9 +34,9 @@ fn root_command() -> Command {
.long_flag_aliases(vec!["processStart", "processStartAndWait"])
)
.subcommand(Command::new("patch")
- .about("Applies a Zstd patch file")
+ .about("Applies a series of delta bundles to a base file")
.arg(arg!(--old "Base / old file to apply the patch to").required(true).value_parser(value_parser!(PathBuf)))
- .arg(arg!(--patch "The Zstd patch to apply to the old file").required(true).value_parser(value_parser!(PathBuf)))
+ .arg(arg!(--delta "The delta bundle to apply to the base package").required(true).action(ArgAction::Append).value_parser(value_parser!(PathBuf)))
.arg(arg!(--output "The file to create with the patch applied").required(true).value_parser(value_parser!(PathBuf)))
)
.arg(arg!(--verbose "Print debug messages to console / log").global(true))
@@ -57,6 +57,13 @@ fn root_command() -> Command {
.about("Remove all app shortcuts, files, and registry entries.")
.long_flag_alias("uninstall")
);
+
+ #[cfg(target_os = "windows")]
+ let cmd = cmd.subcommand(Command::new("update-self")
+ .about("Copy the currently executing Update.exe into the default location.")
+ .long_flag_alias("updateSelf")
+ .hide(true)
+ );
cmd
}
@@ -164,6 +171,8 @@ fn main() -> Result<()> {
let result = match subcommand {
#[cfg(target_os = "windows")]
"uninstall" => uninstall(location_context, subcommand_matches).map_err(|e| anyhow!("Uninstall error: {}", e)),
+ #[cfg(target_os = "windows")]
+ "update-self" => update_self(location_context, subcommand_matches).map_err(|e| anyhow!("Update-self error: {}", e)),
"start" => start(location_context, subcommand_matches).map_err(|e| anyhow!("Start error: {}", e)),
"apply" => apply(location_context, subcommand_matches).map_err(|e| anyhow!("Apply error: {}", e)),
"patch" => patch(location_context, subcommand_matches).map_err(|e| anyhow!("Patch error: {}", e)),
@@ -180,19 +189,34 @@ fn main() -> Result<()> {
fn patch(_context: LocationContext, matches: &ArgMatches) -> Result<()> {
let old_file = matches.get_one::("old");
- let patch_file = matches.get_one::("patch");
+ let deltas: Vec<&PathBuf> = matches.get_many::("delta").unwrap_or_default().collect();
let output_file = matches.get_one::("output");
info!("Command: Patch");
info!(" Old File: {:?}", old_file);
- info!(" Patch File: {:?}", patch_file);
+ info!(" Delta Files: {:?}", deltas);
info!(" Output File: {:?}", output_file);
- if old_file.is_none() || patch_file.is_none() || output_file.is_none() {
- bail!("Missing required arguments. Please provide --old, --patch, and --output.");
+ if old_file.is_none() || deltas.is_empty() || output_file.is_none() {
+ bail!("Missing required arguments. Please provide --old, --delta, and --output.");
}
- velopack::delta::zstd_patch_single(old_file.unwrap(), patch_file.unwrap(), output_file.unwrap())?;
+ let temp_dir = match auto_locate_app_manifest(LocationContext::IAmUpdateExe) {
+ Ok(locator) => locator.get_temp_dir_rand16(),
+ Err(_) => {
+ let mut temp_dir = std::env::temp_dir();
+ let rand = shared::random_string(16);
+ temp_dir.push("velopack_".to_owned() + &rand);
+ temp_dir
+ }
+ };
+
+ let result = commands::delta(old_file.unwrap(), deltas, &temp_dir, output_file.unwrap());
+ let _ = remove_dir_all::remove_dir_all(temp_dir);
+
+ if let Err(e) = result {
+ bail!("Delta error: {}", e);
+ }
Ok(())
}
@@ -252,6 +276,47 @@ fn uninstall(context: LocationContext, _matches: &ArgMatches) -> Result<()> {
commands::uninstall(&locator, true)
}
+#[cfg(target_os = "windows")]
+fn update_self(context: LocationContext, _matches: &ArgMatches) -> Result<()> {
+ info!("Command: Update Self");
+ let my_path = env::current_exe()?;
+ const RETRY_DELAY: i32 = 500;
+ const RETRY_COUNT: i32 = 20;
+ match auto_locate_app_manifest(context) {
+ Ok(locator) => {
+ let target_update_path = locator.get_update_path();
+ if same_file::is_same_file(&target_update_path, &my_path)? {
+ bail!("Update.exe is already in the default location. No need to update.");
+ } else {
+ info!("Copying Update.exe to the default location: {:?}", target_update_path);
+ shared::retry_io_ex(|| std::fs::copy(&my_path, &target_update_path), RETRY_DELAY, RETRY_COUNT)?;
+ info!("Update.exe copied successfully.");
+ }
+ }
+ Err(e) => {
+ warn!("Failed to initialise locator: {}", e);
+ // search for an Update.exe in parent directories (at least 2 levels up)
+ let mut current_dir = env::current_dir()?;
+ let mut found = false;
+ for _ in 0..2 {
+ current_dir.pop();
+ let target_update_path = current_dir.join("Update.exe");
+ if target_update_path.exists() {
+ info!("Found Update.exe in parent directory: {:?}", target_update_path);
+ shared::retry_io_ex(|| std::fs::copy(&my_path, &target_update_path), RETRY_DELAY, RETRY_COUNT)?;
+ info!("Update.exe copied successfully.");
+ found = true;
+ break;
+ }
+ }
+ if !found {
+ bail!("Failed to locate Update.exe in parent directories, so it could not be updated.");
+ }
+ }
+ }
+ Ok(())
+}
+
#[cfg(target_os = "windows")]
#[test]
fn test_cli_parse_handles_equals_spaces() {
diff --git a/src/bins/src/windows/mitigate.rs b/src/bins/src/windows/mitigate.rs
index 98ddd2bb..fb1d27e9 100644
--- a/src/bins/src/windows/mitigate.rs
+++ b/src/bins/src/windows/mitigate.rs
@@ -3,7 +3,7 @@ use windows::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_SYSTEM32;
use windows::Win32::System::LibraryLoader::LOAD_LIBRARY_FLAGS;
#[cfg(target_os = "windows")]
-type SetDefaultDllDirectoriesFn = unsafe extern "system" fn(DirectoryFlags: u32) -> BOOL;
+type SetDefaultDllDirectoriesFn = unsafe extern "system" fn(directory_flags: u32) -> BOOL;
#[cfg(target_os = "windows")]
unsafe fn set_default_dll_directories(flags: LOAD_LIBRARY_FLAGS) {
diff --git a/src/bins/src/windows/runtimes.rs b/src/bins/src/windows/runtimes.rs
index 515ffa94..6632a32d 100644
--- a/src/bins/src/windows/runtimes.rs
+++ b/src/bins/src/windows/runtimes.rs
@@ -498,7 +498,7 @@ fn test_dotnet_detects_installed_versions() {
assert!(parse_dotnet_version("net8-runtime").unwrap().is_installed());
assert!(parse_dotnet_version("net8-desktop").unwrap().is_installed());
assert!(parse_dotnet_version("net8-asp").unwrap().is_installed());
- assert!(parse_dotnet_version("net8-sdk").unwrap().is_installed());
+ assert!(parse_dotnet_version("net9-sdk").unwrap().is_installed());
assert!(!parse_dotnet_version("net11").unwrap().is_installed());
}
diff --git a/src/bins/src/windows/util.rs b/src/bins/src/windows/util.rs
index 87a8aaa1..f9dec71a 100644
--- a/src/bins/src/windows/util.rs
+++ b/src/bins/src/windows/util.rs
@@ -86,9 +86,9 @@ pub fn expand_environment_strings>(input: P) -> Result {
#[test]
fn test_expand_environment_strings() {
- assert_eq!(expand_environment_strings("%windir%").unwrap(), "C:\\Windows");
- assert_eq!(expand_environment_strings("%windir%\\system32").unwrap(), "C:\\Windows\\system32");
- assert_eq!(expand_environment_strings("%windir%\\system32\\").unwrap(), "C:\\Windows\\system32\\");
+ assert!(expand_environment_strings("%windir%").unwrap().eq_ignore_ascii_case("C:\\Windows"));
+ assert!(expand_environment_strings("%windir%\\system32").unwrap().eq_ignore_ascii_case("C:\\Windows\\system32"));
+ assert!(expand_environment_strings("%windir%\\system32\\").unwrap().eq_ignore_ascii_case("C:\\Windows\\system32\\"));
}
pub fn get_long_path>(str: P) -> Result {
diff --git a/src/bins/tests/commands.rs b/src/bins/tests/commands.rs
index 32a8bb79..2426726d 100644
--- a/src/bins/tests/commands.rs
+++ b/src/bins/tests/commands.rs
@@ -2,14 +2,15 @@
mod common;
use common::*;
+use std::hint::assert_unchecked;
use std::{fs, path::Path, path::PathBuf};
use tempfile::tempdir;
use velopack_bins::*;
-#[cfg(target_os = "windows")]
-use winsafe::{self as w, co};
use velopack::bundle::load_bundle_from_file;
use velopack::locator::{auto_locate_app_manifest, LocationContext};
+#[cfg(target_os = "windows")]
+use winsafe::{self as w, co};
#[cfg(target_os = "windows")]
#[test]
@@ -87,7 +88,7 @@ pub fn test_install_preserve_symlinks() {
let tmp_dir = tempdir().unwrap();
let tmp_buf = tmp_dir.path().to_path_buf();
let mut tmp_zip = load_bundle_from_file(nupkg).unwrap();
-
+
commands::install(&mut tmp_zip, Some(&tmp_buf), None).unwrap();
assert!(tmp_buf.join("current").join("actual").join("file.txt").exists());
@@ -119,13 +120,45 @@ pub fn test_patch_apply() {
let expected_sha1 = get_sha1(&new_file);
let tmp_file = Path::new("temp.patch").to_path_buf();
- velopack::delta::zstd_patch_single(&old_file, &p1, &tmp_file).unwrap();
+ velopack_bins::commands::zstd_patch_single(&old_file, &p1, &tmp_file).unwrap();
let tmp_sha1 = get_sha1(&tmp_file);
fs::remove_file(&tmp_file).unwrap();
assert_eq!(expected_sha1, tmp_sha1);
- velopack::delta::zstd_patch_single(&old_file, &p2, &tmp_file).unwrap();
+ velopack_bins::commands::zstd_patch_single(&old_file, &p2, &tmp_file).unwrap();
let tmp_sha1 = get_sha1(&tmp_file);
fs::remove_file(&tmp_file).unwrap();
assert_eq!(expected_sha1, tmp_sha1);
-}
+}
+
+#[test]
+pub fn test_delta_apply_legacy() {
+ dialogs::set_silent(true);
+ let fixtures = find_fixtures();
+ let base = fixtures.join("Clowd-3.4.287-full.nupkg");
+ let d1 = fixtures.join("Clowd-3.4.288-delta.nupkg");
+ let d2 = fixtures.join("Clowd-3.4.291-delta.nupkg");
+ let d3 = fixtures.join("Clowd-3.4.292-delta.nupkg");
+ let d4 = fixtures.join("Clowd-3.4.293-delta.nupkg");
+
+ let deltas = vec![&d1, &d2, &d3, &d4];
+
+ let tmp_dir = tempdir().unwrap();
+ let temp_output = tmp_dir.path().join("Clowd-3.4.293-full.nupkg");
+ commands::delta(&base, deltas, tmp_dir.path(), &temp_output).unwrap();
+
+ let mut bundle = load_bundle_from_file(temp_output).unwrap();
+ let manifest = bundle.read_manifest().unwrap();
+
+ assert_eq!(manifest.id, "Clowd");
+ assert_eq!(manifest.version, semver::Version::parse("3.4.293").unwrap());
+
+ #[cfg(not(target_os = "linux"))]
+ {
+ let extract_dir = tmp_dir.path().join("_extracted");
+ bundle.extract_lib_contents_to_path(&extract_dir, |_| {}).unwrap();
+
+ let extracted = extract_dir.join("Clowd.dll");
+ assert!(extracted.exists());
+ }
+}
diff --git a/src/lib-cpp/include/Velopack.h b/src/lib-cpp/include/Velopack.h
index 94910a05..2c5e2ee2 100644
--- a/src/lib-cpp/include/Velopack.h
+++ b/src/lib-cpp/include/Velopack.h
@@ -107,7 +107,7 @@ typedef struct vpkc_update_options_t {
*/
bool AllowVersionDowngrade;
/**
- * **This option should usually be left None**.
+ * **This option should usually be left None**.
* Overrides the default channel used to fetch updates.
* The default channel will be whatever channel was specified on the command line when building this release.
* For example, if the current release was packaged with '--channel beta', then the default channel will be 'beta'.
@@ -116,6 +116,11 @@ typedef struct vpkc_update_options_t {
* without having to reinstall the application.
*/
char *ExplicitChannel;
+ /**
+ * Sets the maximum number of deltas to consider before falling back to a full update.
+ * The default is 10. Set to a negative number (eg. -1) to disable deltas.
+ */
+ int32_t MaximumDeltasBeforeFallback;
} vpkc_update_options_t;
/**
@@ -160,7 +165,19 @@ typedef struct vpkc_update_info_t {
/**
* The available version that we are updating to.
*/
- struct vpkc_asset_t TargetFullRelease;
+ struct vpkc_asset_t *TargetFullRelease;
+ /**
+ * The base release that this update is based on. This is only available if the update is a delta update.
+ */
+ struct vpkc_asset_t *BaseRelease;
+ /**
+ * The list of delta updates that can be applied to the base version to get to the target version.
+ */
+ struct vpkc_asset_t **DeltasToTarget;
+ /**
+ * The number of elements in the DeltasToTarget array.
+ */
+ size_t DeltasToTargetCount;
/**
* True if the update is a version downgrade or lateral move (such as when switching channels to the same version number).
* In this case, only full updates are allowed, and any local packages on disk newer than the downloaded version will be
@@ -267,14 +284,14 @@ bool vpkc_is_portable(vpkc_update_manager_t *p_manager);
* Returns an UpdateInfo object if there is an update downloaded which still needs to be applied.
* You can pass the UpdateInfo object to waitExitThenApplyUpdate to apply the update.
*/
-bool vpkc_update_pending_restart(vpkc_update_manager_t *p_manager, struct vpkc_asset_t *p_asset);
+bool vpkc_update_pending_restart(vpkc_update_manager_t *p_manager, struct vpkc_asset_t **p_asset);
/**
* Checks for updates, returning None if there are none available. If there are updates available, this method will return an
* UpdateInfo object containing the latest available release, and any delta updates that can be applied if they are available.
*/
vpkc_update_check_t vpkc_check_for_updates(vpkc_update_manager_t *p_manager,
- struct vpkc_update_info_t *p_update);
+ struct vpkc_update_info_t **p_update);
/**
* Downloads the specified updates to the local app packages directory. Progress is reported back to the caller via an optional callback.
diff --git a/src/lib-cpp/include/Velopack.hpp b/src/lib-cpp/include/Velopack.hpp
index 7aa5ff8b..64460c95 100644
--- a/src/lib-cpp/include/Velopack.hpp
+++ b/src/lib-cpp/include/Velopack.hpp
@@ -1,6 +1,5 @@
//! This header provides the C++ API for the Velopack library.
//! This C++ API is a thin wrapper around the C API, providing a more idiomatic C++ interface.
-//! You should not mix and match the C and C++ APIs in the same program.
#ifndef VELOPACK_HPP
#define VELOPACK_HPP
@@ -19,181 +18,27 @@
namespace Velopack {
-static inline void throw_last_error() {
+static inline void throw_last_error()
+{
size_t neededSize = vpkc_get_last_error(nullptr, 0);
std::string strError(neededSize, '\0');
vpkc_get_last_error(&strError[0], neededSize);
throw std::runtime_error(strError);
}
-static inline std::string to_cppstring(const char* psz) {
- return psz == nullptr ? "" : psz;
+static inline std::optional to_cpp_string(const char* psz)
+{
+ return psz == nullptr ? std::optional("") : std::optional(psz);
}
-static inline char* to_cstring(const std::string& str) {
- return const_cast(str.c_str());
+static inline char* alloc_c_string(const std::optional& str)
+{
+ if (!str.has_value()) { return nullptr; }
+ return alloc_c_string(str.value());
}
-static inline char* to_cstring_opt(const std::optional& str) {
- return str.has_value() ? to_cstring(str.value()) : nullptr;
-}
-
-static inline std::optional to_cppstring_opt(const char* psz) {
- return psz == nullptr ? std::nullopt : std::optional(psz);
-}
-
-static inline bool to_cppbool(bool b) { return b; }
-static inline bool to_cbool(bool b) { return b; }
-static inline uint64_t to_cu64(uint64_t i) { return i; }
-static inline uint64_t to_cppu64(uint64_t i) { return i; }
-
-// !! AUTO-GENERATED-START CPP_TYPES
-
-/// VelopackLocator provides some utility functions for locating the current app important paths (eg. path to packages, update binary, and so forth).
-struct VelopackLocatorConfig {
- /// The root directory of the current app.
- std::string RootAppDir;
- /// The path to the Update.exe binary.
- std::string UpdateExePath;
- /// The path to the packages' directory.
- std::string PackagesDir;
- /// The current app manifest.
- std::string ManifestPath;
- /// The directory containing the application's user binaries.
- std::string CurrentBinaryDir;
- /// Whether the current application is portable or installed.
- bool IsPortable;
-};
-
-/// An individual Velopack asset, could refer to an asset on-disk or in a remote package feed.
-struct VelopackAsset {
- /// The name or Id of the package containing this release.
- std::string PackageId;
- /// The version of this release.
- std::string Version;
- /// The type of asset (eg. "Full" or "Delta").
- std::string Type;
- /// The filename of the update package containing this release.
- std::string FileName;
- /// The SHA1 checksum of the update package containing this release.
- std::string SHA1;
- /// The SHA256 checksum of the update package containing this release.
- std::string SHA256;
- /// The size in bytes of the update package containing this release.
- uint64_t Size;
- /// The release notes in markdown format, as passed to Velopack when packaging the release. This may be an empty string.
- std::string NotesMarkdown;
- /// The release notes in HTML format, transformed from Markdown when packaging the release. This may be an empty string.
- std::string NotesHtml;
-};
-
-/// Holds information about the current version and pending updates, such as how many there are, and access to release notes.
-struct UpdateInfo {
- /// The available version that we are updating to.
- VelopackAsset TargetFullRelease;
- /// True if the update is a version downgrade or lateral move (such as when switching channels to the same version number).
- /// In this case, only full updates are allowed, and any local packages on disk newer than the downloaded version will be
- /// deleted.
- bool IsDowngrade;
-};
-
-/// Options to customise the behaviour of UpdateManager.
-struct UpdateOptions {
- /// Allows UpdateManager to update to a version that's lower than the current version (i.e. downgrading).
- /// This could happen if a release has bugs and was retracted from the release feed, or if you're using
- /// ExplicitChannel to switch channels to another channel where the latest version on that
- /// channel is lower than the current version.
- bool AllowVersionDowngrade;
- /// **This option should usually be left None**.
- /// Overrides the default channel used to fetch updates.
- /// The default channel will be whatever channel was specified on the command line when building this release.
- /// For example, if the current release was packaged with '--channel beta', then the default channel will be 'beta'.
- /// This allows users to automatically receive updates from the same channel they installed from. This options
- /// allows you to explicitly switch channels, for example if the user wished to switch back to the 'stable' channel
- /// without having to reinstall the application.
- std::optional ExplicitChannel;
-};
-
-static inline vpkc_locator_config_t to_c(const VelopackLocatorConfig& dto) {
- return {
- to_cstring(dto.RootAppDir),
- to_cstring(dto.UpdateExePath),
- to_cstring(dto.PackagesDir),
- to_cstring(dto.ManifestPath),
- to_cstring(dto.CurrentBinaryDir),
- to_cbool(dto.IsPortable),
- };
-}
-
-static inline VelopackLocatorConfig to_cpp(const vpkc_locator_config_t& dto) {
- return {
- to_cppstring(dto.RootAppDir),
- to_cppstring(dto.UpdateExePath),
- to_cppstring(dto.PackagesDir),
- to_cppstring(dto.ManifestPath),
- to_cppstring(dto.CurrentBinaryDir),
- to_cppbool(dto.IsPortable),
- };
-}
-
-static inline vpkc_asset_t to_c(const VelopackAsset& dto) {
- return {
- to_cstring(dto.PackageId),
- to_cstring(dto.Version),
- to_cstring(dto.Type),
- to_cstring(dto.FileName),
- to_cstring(dto.SHA1),
- to_cstring(dto.SHA256),
- to_cu64(dto.Size),
- to_cstring(dto.NotesMarkdown),
- to_cstring(dto.NotesHtml),
- };
-}
-
-static inline VelopackAsset to_cpp(const vpkc_asset_t& dto) {
- return {
- to_cppstring(dto.PackageId),
- to_cppstring(dto.Version),
- to_cppstring(dto.Type),
- to_cppstring(dto.FileName),
- to_cppstring(dto.SHA1),
- to_cppstring(dto.SHA256),
- to_cppu64(dto.Size),
- to_cppstring(dto.NotesMarkdown),
- to_cppstring(dto.NotesHtml),
- };
-}
-
-static inline vpkc_update_info_t to_c(const UpdateInfo& dto) {
- return {
- to_c(dto.TargetFullRelease),
- to_cbool(dto.IsDowngrade),
- };
-}
-
-static inline UpdateInfo to_cpp(const vpkc_update_info_t& dto) {
- return {
- to_cpp(dto.TargetFullRelease),
- to_cppbool(dto.IsDowngrade),
- };
-}
-
-static inline vpkc_update_options_t to_c(const UpdateOptions& dto) {
- return {
- to_cbool(dto.AllowVersionDowngrade),
- to_cstring_opt(dto.ExplicitChannel),
- };
-}
-
-static inline UpdateOptions to_cpp(const vpkc_update_options_t& dto) {
- return {
- to_cppbool(dto.AllowVersionDowngrade),
- to_cppstring_opt(dto.ExplicitChannel),
- };
-}
-// !! AUTO-GENERATED-END CPP_TYPES
-
-static inline char* allocate_cstring(const std::string& str) {
+static inline char* alloc_c_string(const std::string& str)
+{
char* result = new char[str.size() + 1]; // +1 for null-terminator
#ifdef _WIN32
strcpy_s(result, str.size() + 1, str.c_str()); // Copy string content
@@ -204,26 +49,415 @@ static inline char* allocate_cstring(const std::string& str) {
return result;
}
-static inline void free_cstring(char* str) {
+static inline void free_c_string(char* str)
+{
delete[] str;
}
-static inline char** allocate_cstring_array(const std::vector& vec) {
- char** result = new char*[vec.size()];
- for (size_t i = 0; i < vec.size(); ++i) {
- result[i] = allocate_cstring(vec[i]);
+static inline char** alloc_c_string_vec(const std::vector& dto, size_t* count)
+{
+ if (dto.empty()) {
+ *count = 0;
+ return nullptr;
}
- return result;
+ *count = dto.size();
+ char** arr = new char* [*count];
+ for (size_t i = 0; i < *count; ++i) {
+ arr[i] = alloc_c_string(dto[i]);
+ }
+ return arr;
}
-static inline void free_cstring_array(char** arr, size_t size) {
+static inline void free_c_string_vec(char** arr, size_t size)
+{
for (size_t i = 0; i < size; ++i) {
- free_cstring(arr[i]);
+ free_c_string(arr[i]);
arr[i] = nullptr;
}
delete[] arr;
}
+template
+inline T unwrap(const std::optional& opt, const std::string& message = "Expected value not present") {
+ if (!opt.has_value()) {
+ throw std::runtime_error(message);
+ }
+ return opt.value();
+}
+
+// !! AUTO-GENERATED-START CPP_TYPES
+
+/** VelopackLocator provides some utility functions for locating the current app important paths (eg. path to packages, update binary, and so forth). */
+struct VelopackLocatorConfig {
+ /** The root directory of the current app. */
+ std::string RootAppDir;
+ /** The path to the Update.exe binary. */
+ std::string UpdateExePath;
+ /** The path to the packages' directory. */
+ std::string PackagesDir;
+ /** The current app manifest. */
+ std::string ManifestPath;
+ /** The directory containing the application's user binaries. */
+ std::string CurrentBinaryDir;
+ /** Whether the current application is portable or installed. */
+ bool IsPortable;
+};
+
+static inline std::optional to_cpp_VelopackLocatorConfig(const vpkc_locator_config_t* dto) {
+ if (dto == nullptr) { return std::nullopt; }
+ return std::optional({
+ unwrap(to_cpp_string(dto->RootAppDir), "Required property RootAppDir was null"),
+ unwrap(to_cpp_string(dto->UpdateExePath), "Required property UpdateExePath was null"),
+ unwrap(to_cpp_string(dto->PackagesDir), "Required property PackagesDir was null"),
+ unwrap(to_cpp_string(dto->ManifestPath), "Required property ManifestPath was null"),
+ unwrap(to_cpp_string(dto->CurrentBinaryDir), "Required property CurrentBinaryDir was null"),
+ dto->IsPortable,
+ });
+}
+
+static inline std::vector to_cpp_VelopackLocatorConfig_vec(const vpkc_locator_config_t* const* arr, size_t c) {
+ if (arr == nullptr || c < 1) { return std::vector(); }
+ std::vector result;
+ result.reserve(c);
+ for (size_t i = 0; i < c; ++i) {
+ auto dto = arr[i];
+ if (dto == nullptr) { continue; }
+ result.push_back(unwrap(to_cpp_VelopackLocatorConfig(dto)));
+ }
+ return result;
+}
+
+static inline vpkc_locator_config_t* alloc_c_VelopackLocatorConfig_ptr(const VelopackLocatorConfig* dto) {
+ if (dto == nullptr) { return nullptr; }
+ vpkc_locator_config_t* obj = new vpkc_locator_config_t{};
+ obj->RootAppDir = alloc_c_string(dto->RootAppDir);
+ obj->UpdateExePath = alloc_c_string(dto->UpdateExePath);
+ obj->PackagesDir = alloc_c_string(dto->PackagesDir);
+ obj->ManifestPath = alloc_c_string(dto->ManifestPath);
+ obj->CurrentBinaryDir = alloc_c_string(dto->CurrentBinaryDir);
+ obj->IsPortable = dto->IsPortable;
+ return obj;
+}
+
+static inline vpkc_locator_config_t* alloc_c_VelopackLocatorConfig(const std::optional& dto) {
+ if (!dto.has_value()) { return nullptr; }
+ VelopackLocatorConfig obj = unwrap(dto);
+ return alloc_c_VelopackLocatorConfig_ptr(&obj);
+}
+
+static inline vpkc_locator_config_t** alloc_c_VelopackLocatorConfig_vec(const std::vector& dto, size_t* count) {
+ if (dto.empty()) {
+ *count = 0;
+ return nullptr;
+ }
+ *count = dto.size();
+ vpkc_locator_config_t** arr = new vpkc_locator_config_t*[*count];
+ for (size_t i = 0; i < *count; ++i) {
+ arr[i] = alloc_c_VelopackLocatorConfig(dto[i]);
+ }
+ return arr;
+}
+
+static inline void free_c_VelopackLocatorConfig(vpkc_locator_config_t* obj) {
+ if (obj == nullptr) { return; }
+ free_c_string(obj->RootAppDir);
+ free_c_string(obj->UpdateExePath);
+ free_c_string(obj->PackagesDir);
+ free_c_string(obj->ManifestPath);
+ free_c_string(obj->CurrentBinaryDir);
+
+ delete obj;
+}
+
+static inline void free_c_VelopackLocatorConfig_vec(vpkc_locator_config_t** arr, size_t count) {
+ if (arr == nullptr || count < 1) { return; }
+ for (size_t i = 0; i < count; ++i) {
+ free_c_VelopackLocatorConfig(arr[i]);
+ }
+ delete[] arr;
+}
+
+/** An individual Velopack asset, could refer to an asset on-disk or in a remote package feed. */
+struct VelopackAsset {
+ /** The name or Id of the package containing this release. */
+ std::string PackageId;
+ /** The version of this release. */
+ std::string Version;
+ /** The type of asset (eg. "Full" or "Delta"). */
+ std::string Type;
+ /** The filename of the update package containing this release. */
+ std::string FileName;
+ /** The SHA1 checksum of the update package containing this release. */
+ std::string SHA1;
+ /** The SHA256 checksum of the update package containing this release. */
+ std::string SHA256;
+ /** The size in bytes of the update package containing this release. */
+ uint64_t Size;
+ /** The release notes in markdown format, as passed to Velopack when packaging the release. This may be an empty string. */
+ std::string NotesMarkdown;
+ /** The release notes in HTML format, transformed from Markdown when packaging the release. This may be an empty string. */
+ std::string NotesHtml;
+};
+
+static inline std::optional to_cpp_VelopackAsset(const vpkc_asset_t* dto) {
+ if (dto == nullptr) { return std::nullopt; }
+ return std::optional({
+ unwrap(to_cpp_string(dto->PackageId), "Required property PackageId was null"),
+ unwrap(to_cpp_string(dto->Version), "Required property Version was null"),
+ unwrap(to_cpp_string(dto->Type), "Required property Type was null"),
+ unwrap(to_cpp_string(dto->FileName), "Required property FileName was null"),
+ unwrap(to_cpp_string(dto->SHA1), "Required property SHA1 was null"),
+ unwrap(to_cpp_string(dto->SHA256), "Required property SHA256 was null"),
+ dto->Size,
+ unwrap(to_cpp_string(dto->NotesMarkdown), "Required property NotesMarkdown was null"),
+ unwrap(to_cpp_string(dto->NotesHtml), "Required property NotesHtml was null"),
+ });
+}
+
+static inline std::vector to_cpp_VelopackAsset_vec(const vpkc_asset_t* const* arr, size_t c) {
+ if (arr == nullptr || c < 1) { return std::vector(); }
+ std::vector result;
+ result.reserve(c);
+ for (size_t i = 0; i < c; ++i) {
+ auto dto = arr[i];
+ if (dto == nullptr) { continue; }
+ result.push_back(unwrap(to_cpp_VelopackAsset(dto)));
+ }
+ return result;
+}
+
+static inline vpkc_asset_t* alloc_c_VelopackAsset_ptr(const VelopackAsset* dto) {
+ if (dto == nullptr) { return nullptr; }
+ vpkc_asset_t* obj = new vpkc_asset_t{};
+ obj->PackageId = alloc_c_string(dto->PackageId);
+ obj->Version = alloc_c_string(dto->Version);
+ obj->Type = alloc_c_string(dto->Type);
+ obj->FileName = alloc_c_string(dto->FileName);
+ obj->SHA1 = alloc_c_string(dto->SHA1);
+ obj->SHA256 = alloc_c_string(dto->SHA256);
+ obj->Size = dto->Size;
+ obj->NotesMarkdown = alloc_c_string(dto->NotesMarkdown);
+ obj->NotesHtml = alloc_c_string(dto->NotesHtml);
+ return obj;
+}
+
+static inline vpkc_asset_t* alloc_c_VelopackAsset(const std::optional& dto) {
+ if (!dto.has_value()) { return nullptr; }
+ VelopackAsset obj = unwrap(dto);
+ return alloc_c_VelopackAsset_ptr(&obj);
+}
+
+static inline vpkc_asset_t** alloc_c_VelopackAsset_vec(const std::vector& dto, size_t* count) {
+ if (dto.empty()) {
+ *count = 0;
+ return nullptr;
+ }
+ *count = dto.size();
+ vpkc_asset_t** arr = new vpkc_asset_t*[*count];
+ for (size_t i = 0; i < *count; ++i) {
+ arr[i] = alloc_c_VelopackAsset(dto[i]);
+ }
+ return arr;
+}
+
+static inline void free_c_VelopackAsset(vpkc_asset_t* obj) {
+ if (obj == nullptr) { return; }
+ free_c_string(obj->PackageId);
+ free_c_string(obj->Version);
+ free_c_string(obj->Type);
+ free_c_string(obj->FileName);
+ free_c_string(obj->SHA1);
+ free_c_string(obj->SHA256);
+
+ free_c_string(obj->NotesMarkdown);
+ free_c_string(obj->NotesHtml);
+ delete obj;
+}
+
+static inline void free_c_VelopackAsset_vec(vpkc_asset_t** arr, size_t count) {
+ if (arr == nullptr || count < 1) { return; }
+ for (size_t i = 0; i < count; ++i) {
+ free_c_VelopackAsset(arr[i]);
+ }
+ delete[] arr;
+}
+
+/** Holds information about the current version and pending updates, such as how many there are, and access to release notes. */
+struct UpdateInfo {
+ /** The available version that we are updating to. */
+ VelopackAsset TargetFullRelease;
+ /** The base release that this update is based on. This is only available if the update is a delta update. */
+ std::optional BaseRelease;
+ /** The list of delta updates that can be applied to the base version to get to the target version. */
+ std::vector DeltasToTarget;
+ /**
+ * True if the update is a version downgrade or lateral move (such as when switching channels to the same version number).
+ * In this case, only full updates are allowed, and any local packages on disk newer than the downloaded version will be
+ * deleted.
+ */
+ bool IsDowngrade;
+};
+
+static inline std::optional to_cpp_UpdateInfo(const vpkc_update_info_t* dto) {
+ if (dto == nullptr) { return std::nullopt; }
+ return std::optional({
+ unwrap(to_cpp_VelopackAsset(dto->TargetFullRelease), "Required property TargetFullRelease was null"),
+ to_cpp_VelopackAsset(dto->BaseRelease),
+ to_cpp_VelopackAsset_vec(dto->DeltasToTarget, dto->DeltasToTargetCount),
+ dto->IsDowngrade,
+ });
+}
+
+static inline std::vector to_cpp_UpdateInfo_vec(const vpkc_update_info_t* const* arr, size_t c) {
+ if (arr == nullptr || c < 1) { return std::vector(); }
+ std::vector result;
+ result.reserve(c);
+ for (size_t i = 0; i < c; ++i) {
+ auto dto = arr[i];
+ if (dto == nullptr) { continue; }
+ result.push_back(unwrap(to_cpp_UpdateInfo(dto)));
+ }
+ return result;
+}
+
+static inline vpkc_update_info_t* alloc_c_UpdateInfo_ptr(const UpdateInfo* dto) {
+ if (dto == nullptr) { return nullptr; }
+ vpkc_update_info_t* obj = new vpkc_update_info_t{};
+ obj->TargetFullRelease = alloc_c_VelopackAsset(dto->TargetFullRelease);
+ obj->BaseRelease = alloc_c_VelopackAsset(dto->BaseRelease);
+ obj->DeltasToTarget = alloc_c_VelopackAsset_vec(dto->DeltasToTarget, &obj->DeltasToTargetCount);
+ obj->IsDowngrade = dto->IsDowngrade;
+ return obj;
+}
+
+static inline vpkc_update_info_t* alloc_c_UpdateInfo(const std::optional& dto) {
+ if (!dto.has_value()) { return nullptr; }
+ UpdateInfo obj = unwrap(dto);
+ return alloc_c_UpdateInfo_ptr(&obj);
+}
+
+static inline vpkc_update_info_t** alloc_c_UpdateInfo_vec(const std::vector& dto, size_t* count) {
+ if (dto.empty()) {
+ *count = 0;
+ return nullptr;
+ }
+ *count = dto.size();
+ vpkc_update_info_t** arr = new vpkc_update_info_t*[*count];
+ for (size_t i = 0; i < *count; ++i) {
+ arr[i] = alloc_c_UpdateInfo(dto[i]);
+ }
+ return arr;
+}
+
+static inline void free_c_UpdateInfo(vpkc_update_info_t* obj) {
+ if (obj == nullptr) { return; }
+ free_c_VelopackAsset(obj->TargetFullRelease);
+ free_c_VelopackAsset(obj->BaseRelease);
+ free_c_VelopackAsset_vec(obj->DeltasToTarget, obj->DeltasToTargetCount);
+
+ delete obj;
+}
+
+static inline void free_c_UpdateInfo_vec(vpkc_update_info_t** arr, size_t count) {
+ if (arr == nullptr || count < 1) { return; }
+ for (size_t i = 0; i < count; ++i) {
+ free_c_UpdateInfo(arr[i]);
+ }
+ delete[] arr;
+}
+
+/** Options to customise the behaviour of UpdateManager. */
+struct UpdateOptions {
+ /**
+ * Allows UpdateManager to update to a version that's lower than the current version (i.e. downgrading).
+ * This could happen if a release has bugs and was retracted from the release feed, or if you're using
+ * ExplicitChannel to switch channels to another channel where the latest version on that
+ * channel is lower than the current version.
+ */
+ bool AllowVersionDowngrade;
+ /**
+ * **This option should usually be left None**.
+ * Overrides the default channel used to fetch updates.
+ * The default channel will be whatever channel was specified on the command line when building this release.
+ * For example, if the current release was packaged with '--channel beta', then the default channel will be 'beta'.
+ * This allows users to automatically receive updates from the same channel they installed from. This options
+ * allows you to explicitly switch channels, for example if the user wished to switch back to the 'stable' channel
+ * without having to reinstall the application.
+ */
+ std::optional ExplicitChannel;
+ /**
+ * Sets the maximum number of deltas to consider before falling back to a full update.
+ * The default is 10. Set to a negative number (eg. -1) to disable deltas.
+ */
+ int32_t MaximumDeltasBeforeFallback;
+};
+
+static inline std::optional to_cpp_UpdateOptions(const vpkc_update_options_t* dto) {
+ if (dto == nullptr) { return std::nullopt; }
+ return std::optional({
+ dto->AllowVersionDowngrade,
+ to_cpp_string(dto->ExplicitChannel),
+ dto->MaximumDeltasBeforeFallback,
+ });
+}
+
+static inline std::vector to_cpp_UpdateOptions_vec(const vpkc_update_options_t* const* arr, size_t c) {
+ if (arr == nullptr || c < 1) { return std::vector(); }
+ std::vector result;
+ result.reserve(c);
+ for (size_t i = 0; i < c; ++i) {
+ auto dto = arr[i];
+ if (dto == nullptr) { continue; }
+ result.push_back(unwrap(to_cpp_UpdateOptions(dto)));
+ }
+ return result;
+}
+
+static inline vpkc_update_options_t* alloc_c_UpdateOptions_ptr(const UpdateOptions* dto) {
+ if (dto == nullptr) { return nullptr; }
+ vpkc_update_options_t* obj = new vpkc_update_options_t{};
+ obj->AllowVersionDowngrade = dto->AllowVersionDowngrade;
+ obj->ExplicitChannel = alloc_c_string(dto->ExplicitChannel);
+ obj->MaximumDeltasBeforeFallback = dto->MaximumDeltasBeforeFallback;
+ return obj;
+}
+
+static inline vpkc_update_options_t* alloc_c_UpdateOptions(const std::optional& dto) {
+ if (!dto.has_value()) { return nullptr; }
+ UpdateOptions obj = unwrap(dto);
+ return alloc_c_UpdateOptions_ptr(&obj);
+}
+
+static inline vpkc_update_options_t** alloc_c_UpdateOptions_vec(const std::vector& dto, size_t* count) {
+ if (dto.empty()) {
+ *count = 0;
+ return nullptr;
+ }
+ *count = dto.size();
+ vpkc_update_options_t** arr = new vpkc_update_options_t*[*count];
+ for (size_t i = 0; i < *count; ++i) {
+ arr[i] = alloc_c_UpdateOptions(dto[i]);
+ }
+ return arr;
+}
+
+static inline void free_c_UpdateOptions(vpkc_update_options_t* obj) {
+ if (obj == nullptr) { return; }
+
+ free_c_string(obj->ExplicitChannel);
+
+ delete obj;
+}
+
+static inline void free_c_UpdateOptions_vec(vpkc_update_options_t** arr, size_t count) {
+ if (arr == nullptr || count < 1) { return; }
+ for (size_t i = 0; i < count; ++i) {
+ free_c_UpdateOptions(arr[i]);
+ }
+ delete[] arr;
+}
+// !! AUTO-GENERATED-END CPP_TYPES
+
/**
* VelopackApp helps you to handle app activation events correctly.
* This should be used as early as possible in your application startup code.
@@ -260,9 +494,10 @@ public:
* Override the command line arguments used by VelopackApp. (by default this is env::args().skip(1))
*/
VelopackApp& SetArgs(const std::vector& args) {
- char** pArgs = allocate_cstring_array(args);
- vpkc_app_set_args(pArgs, args.size());
- free_cstring_array(pArgs, args.size());
+ size_t c;
+ char** pArgs = alloc_c_string_vec(args, &c);
+ vpkc_app_set_args(pArgs, c);
+ free_c_string_vec(pArgs, c);
return *this;
};
@@ -270,8 +505,9 @@ public:
* VelopackLocator provides some utility functions for locating the current app important paths (eg. path to packages, update binary, and so forth).
*/
VelopackApp& SetLocator(const VelopackLocatorConfig& locator) {
- vpkc_locator_config_t vpkc_locator = to_c(locator);
- vpkc_app_set_locator(&vpkc_locator);
+ vpkc_locator_config_t* vpkc_locator = alloc_c_VelopackLocatorConfig(locator);
+ vpkc_app_set_locator(vpkc_locator);
+ free_c_VelopackLocatorConfig(vpkc_locator);
return *this;
};
@@ -347,7 +583,7 @@ public:
/**
* Progress callback function. Call with values between 0 and 100 inclusive.
*/
-typedef std::function vpkc_progress_send_t;
+typedef std::function vpkc_progress_send_t;
/**
* Abstract class for retrieving release feeds and downloading assets. You should subclass this and
@@ -372,16 +608,16 @@ public:
[](void* userData, const char* releasesName) {
IUpdateSource* source = reinterpret_cast(userData);
std::string json = source->GetReleaseFeed(releasesName);
- return allocate_cstring(json);
+ return alloc_c_string(json);
},
[](void* userData, char* pszFeed) {
- free_cstring(pszFeed);
+ free_c_string(pszFeed);
},
[](void* userData, const struct vpkc_asset_t *pAsset, const char* pszLocalPath, size_t progressCallbackId) {
IUpdateSource* source = reinterpret_cast(userData);
- VelopackAsset asset = to_cpp(*pAsset);
- std::string localPath = to_cppstring(pszLocalPath);
- std::function progress_callback = [progressCallbackId](size_t progress) {
+ VelopackAsset asset = to_cpp_VelopackAsset(pAsset).value();
+ std::string localPath = to_cpp_string(pszLocalPath).value();
+ std::function progress_callback = [progressCallbackId](int16_t progress) {
vpkc_source_report_progress(progressCallbackId, progress);
};
return source->DownloadReleaseEntry(asset, localPath, progress_callback);
@@ -444,21 +680,12 @@ public:
* @param locator Override the default locator configuration (usually used for testing / mocks).
*/
UpdateManager(const std::string& urlOrPath, const UpdateOptions* options = nullptr, const VelopackLocatorConfig* locator = nullptr) {
- vpkc_update_options_t vpkc_options;
- vpkc_update_options_t* pOptions = nullptr;
- if (options != nullptr) {
- vpkc_options = to_c(*options);
- pOptions = &vpkc_options;
- }
-
- vpkc_locator_config_t vpkc_locator;
- vpkc_locator_config_t* pLocator = nullptr;
- if (locator != nullptr) {
- vpkc_locator = to_c(*locator);
- pLocator = &vpkc_locator;
- }
-
- if (!vpkc_new_update_manager(urlOrPath.c_str(), pOptions, pLocator, &m_pManager)) {
+ vpkc_update_options_t* pOptions = alloc_c_UpdateOptions_ptr(options);
+ vpkc_locator_config_t* pLocator = alloc_c_VelopackLocatorConfig_ptr(locator);
+ bool result = vpkc_new_update_manager(urlOrPath.c_str(), pOptions, pLocator, &m_pManager);
+ free_c_UpdateOptions(pOptions);
+ free_c_VelopackLocatorConfig(pLocator);
+ if (!result) {
throw_last_error();
}
};
@@ -471,23 +698,14 @@ public:
*/
template >>
UpdateManager(std::unique_ptr pUpdateSource, const UpdateOptions* options = nullptr, const VelopackLocatorConfig* locator = nullptr) {
- vpkc_update_options_t vpkc_options;
- vpkc_update_options_t* pOptions = nullptr;
- if (options != nullptr) {
- vpkc_options = to_c(*options);
- pOptions = &vpkc_options;
- }
-
- vpkc_locator_config_t vpkc_locator;
- vpkc_locator_config_t* pLocator = nullptr;
- if (locator != nullptr) {
- vpkc_locator = to_c(*locator);
- pLocator = &vpkc_locator;
- }
-
+ vpkc_update_options_t* pOptions = alloc_c_UpdateOptions_ptr(options);
+ vpkc_locator_config_t* pLocator = alloc_c_VelopackLocatorConfig_ptr(locator);
m_pUpdateSource = std::unique_ptr(static_cast(pUpdateSource.release()));
vpkc_update_source_t* pSource = m_pUpdateSource->m_pSource;
- if (!vpkc_new_update_manager_with_source(pSource, pOptions, pLocator, &m_pManager)) {
+ bool result = vpkc_new_update_manager_with_source(pSource, pOptions, pLocator, &m_pManager);
+ free_c_UpdateOptions(pOptions);
+ free_c_VelopackLocatorConfig(pLocator);
+ if (!result) {
throw_last_error();
}
};
@@ -496,7 +714,10 @@ public:
* Destructor for UpdateManager.
*/
~UpdateManager() {
- vpkc_free_update_manager(m_pManager);
+ if (m_pManager != nullptr) {
+ vpkc_free_update_manager(m_pManager);
+ m_pManager = nullptr;
+ }
};
/**
@@ -532,10 +753,10 @@ public:
* You can pass the UpdateInfo object to waitExitThenApplyUpdate to apply the update.
*/
std::optional UpdatePendingRestart() noexcept {
- vpkc_asset_t asset;
+ vpkc_asset_t* asset;
if (vpkc_update_pending_restart(m_pManager, &asset)) {
- VelopackAsset cpp_asset = to_cpp(asset);
- vpkc_free_asset(&asset);
+ VelopackAsset cpp_asset = to_cpp_VelopackAsset(asset).value();
+ vpkc_free_asset(asset);
return cpp_asset;
}
return std::nullopt;
@@ -546,7 +767,7 @@ public:
* UpdateInfo object containing the latest available release, and any delta updates that can be applied if they are available.
*/
std::optional CheckForUpdates() {
- vpkc_update_info_t update;
+ vpkc_update_info_t* update;
vpkc_update_check_t result = vpkc_check_for_updates(m_pManager, &update);
switch (result) {
case vpkc_update_check_t::UPDATE_ERROR:
@@ -556,8 +777,8 @@ public:
case vpkc_update_check_t::REMOTE_IS_EMPTY:
return std::nullopt;
case vpkc_update_check_t::UPDATE_AVAILABLE:
- UpdateInfo cpp_info = to_cpp(update);
- vpkc_free_update_info(&update);
+ UpdateInfo cpp_info = to_cpp_UpdateInfo(update).value();
+ vpkc_free_update_info(update);
return cpp_info;
}
return std::nullopt;
@@ -572,8 +793,10 @@ public:
* packages, this method will fall back to downloading the full version of the update.
*/
void DownloadUpdates(const UpdateInfo& update, vpkc_progress_callback_t progress = nullptr, void* pUserData = 0) {
- vpkc_update_info_t vpkc_update = to_c(update);
- if (!vpkc_download_updates(m_pManager, &vpkc_update, progress, pUserData)) {
+ vpkc_update_info_t* vpkc_update = alloc_c_UpdateInfo(update);
+ bool result = vpkc_download_updates(m_pManager, vpkc_update, progress, pUserData);
+ free_c_UpdateInfo(vpkc_update);
+ if (!result) {
throw_last_error();
}
};
@@ -593,11 +816,12 @@ public:
* optionally restart your app. The updater will only wait for 60 seconds before giving up.
*/
void WaitExitThenApplyUpdates(const VelopackAsset& asset, bool silent = false, bool restart = true, std::vector restartArgs = {}) {
- char** pRestartArgs = allocate_cstring_array(restartArgs);
- vpkc_asset_t vpkc_asset = to_c(asset);
- bool result = vpkc_wait_exit_then_apply_updates(m_pManager, &vpkc_asset, silent, restart, pRestartArgs, restartArgs.size());
- free_cstring_array(pRestartArgs, restartArgs.size());
-
+ size_t cRestartArgs;
+ char** pRestartArgs = alloc_c_string_vec(restartArgs, &cRestartArgs);
+ vpkc_asset_t* vpkc_asset = alloc_c_VelopackAsset(asset);
+ bool result = vpkc_wait_exit_then_apply_updates(m_pManager, vpkc_asset, silent, restart, pRestartArgs, cRestartArgs);
+ free_c_string_vec(pRestartArgs, cRestartArgs);
+ free_c_VelopackAsset(vpkc_asset);
if (!result) {
throw_last_error();
}
@@ -610,11 +834,12 @@ public:
* If waitPid is 0, the updater will not wait for any process to exit before applying updates (Not Recommended).
*/
void UnsafeApplyUpdates(const VelopackAsset& asset, bool silent, uint32_t waitPid, bool restart, std::vector restartArgs) {
- char** pRestartArgs = allocate_cstring_array(restartArgs);
- vpkc_asset_t vpkc_asset = to_c(asset);
- bool result = vpkc_unsafe_apply_updates(m_pManager, &vpkc_asset, silent, waitPid, restart, pRestartArgs, restartArgs.size());
- free_cstring_array(pRestartArgs, restartArgs.size());
-
+ size_t cRestartArgs;
+ char** pRestartArgs = alloc_c_string_vec(restartArgs, &cRestartArgs);
+ vpkc_asset_t* vpkc_asset = alloc_c_VelopackAsset(asset);
+ bool result = vpkc_unsafe_apply_updates(m_pManager, vpkc_asset, silent, waitPid, restart, pRestartArgs, cRestartArgs);
+ free_c_string_vec(pRestartArgs, cRestartArgs);
+ free_c_VelopackAsset(vpkc_asset);
if (!result) {
throw_last_error();
}
diff --git a/src/lib-cpp/src/csource.rs b/src/lib-cpp/src/csource.rs
index 40b21340..9a2d051d 100644
--- a/src/lib-cpp/src/csource.rs
+++ b/src/lib-cpp/src/csource.rs
@@ -42,9 +42,9 @@ impl UpdateSource for CCallbackUpdateSource {
if let Some(cb_get_release_feed) = self.cb_get_release_feed {
let json_cstr_ptr = (cb_get_release_feed)(self.p_user_data, releases_name_cstr.as_ptr());
- let json = c_to_string_opt(json_cstr_ptr)
- .ok_or(Error::Generic("User vpkc_release_feed_delegate_t returned a null pointer instead of an asset feed".to_string()))?;
-
+ let json = c_to_String(json_cstr_ptr).map_err(|_| {
+ Error::Generic("User vpkc_release_feed_delegate_t returned a null pointer instead of an asset feed".to_string())
+ })?;
if let Some(cb_free_release_feed) = self.cb_free_release_feed {
(cb_free_release_feed)(self.p_user_data, json_cstr_ptr); // Free the C string returned by the callback
} else {
@@ -60,9 +60,7 @@ impl UpdateSource for CCallbackUpdateSource {
fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option>) -> Result<(), Error> {
if let Some(cb_download_release_entry) = self.cb_download_release_entry {
let local_file_cstr = CString::new(local_file).unwrap();
- let mut asset_c: vpkc_asset_t = unsafe { std::mem::zeroed() };
- let asset_ptr: *mut vpkc_asset_t = &mut asset_c as *mut vpkc_asset_t;
- unsafe { allocate_velopackasset(asset.clone(), asset_ptr) };
+ let asset_ptr = unsafe { allocate_VelopackAsset(asset) };
let progress_callback_id = PROGRESS_ID.fetch_add(1, Ordering::SeqCst);
if let Some(progress_sender) = &progress_sender {
@@ -71,8 +69,7 @@ impl UpdateSource for CCallbackUpdateSource {
}
let success = (cb_download_release_entry)(self.p_user_data, asset_ptr, local_file_cstr.as_ptr(), progress_callback_id);
-
- unsafe { free_velopackasset(asset_ptr) };
+ unsafe { free_VelopackAsset(asset_ptr) };
if let Some(sender) = PROGRESS_CALLBACKS.write().unwrap().remove(&progress_callback_id) {
let _ = sender.send(100);
diff --git a/src/lib-cpp/src/lib.rs b/src/lib-cpp/src/lib.rs
index 4c4b9fc6..ffe16e16 100644
--- a/src/lib-cpp/src/lib.rs
+++ b/src/lib-cpp/src/lib.rs
@@ -15,16 +15,16 @@ use anyhow::{anyhow, bail};
use libc::{c_char, c_void, size_t};
use log_derive::{logfn, logfn_inputs};
use std::{ffi::CString, ptr};
-use velopack::{sources, ApplyWaitMode, Error as VelopackError, UpdateCheck, UpdateManager, VelopackApp};
use velopack::locator::LocationContext;
use velopack::logging::{default_logfile_path, init_logging};
+use velopack::{sources, ApplyWaitMode, Error as VelopackError, UpdateCheck, UpdateManager, VelopackApp};
/// Create a new FileSource update source for a given file path.
#[no_mangle]
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_new_source_file(psz_file_path: *const c_char) -> *mut vpkc_update_source_t {
- if let Some(update_path) = c_to_string_opt(psz_file_path) {
+ if let Some(update_path) = c_to_String(psz_file_path).ok() {
UpdateSourceRawPtr::new(Box::new(sources::FileSource::new(update_path)))
} else {
log::error!("psz_file_path is null");
@@ -37,7 +37,7 @@ pub extern "C" fn vpkc_new_source_file(psz_file_path: *const c_char) -> *mut vpk
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_new_source_http_url(psz_http_url: *const c_char) -> *mut vpkc_update_source_t {
- if let Some(update_url) = c_to_string_opt(psz_http_url) {
+ if let Some(update_url) = c_to_String(psz_http_url).ok() {
UpdateSourceRawPtr::new(Box::new(sources::HttpSource::new(update_url)))
} else {
log::error!("psz_http_url is null");
@@ -103,10 +103,10 @@ pub extern "C" fn vpkc_new_update_manager(
p_manager: *mut *mut vpkc_update_manager_t,
) -> bool {
wrap_error(|| {
- let update_url = c_to_string_opt(psz_url_or_path).ok_or(anyhow!("URL or path is null"))?;
+ let update_url = c_to_String(psz_url_or_path)?;
let source = sources::AutoSource::new(&update_url);
- let options = c_to_updateoptions_opt(p_options);
- let locator = c_to_velopacklocatorconfig_opt(p_locator);
+ let options = c_to_UpdateOptions(p_options).ok();
+ let locator = c_to_VelopackLocatorConfig(p_locator).ok();
let manager = UpdateManager::new(source, options, locator)?;
unsafe { *p_manager = UpdateManagerRawPtr::new(manager) };
Ok(())
@@ -128,8 +128,8 @@ pub extern "C" fn vpkc_new_update_manager_with_source(
) -> bool {
wrap_error(|| {
let source = UpdateSourceRawPtr::get_source_clone(p_source).ok_or(anyhow!("pSource must not be null"))?;
- let options = c_to_updateoptions_opt(p_options);
- let locator = c_to_velopacklocatorconfig_opt(p_locator);
+ let options = c_to_UpdateOptions(p_options).ok();
+ let locator = c_to_VelopackLocatorConfig(p_locator).ok();
let manager = UpdateManager::new_boxed(source, options, locator)?;
unsafe { *p_manager = UpdateManagerRawPtr::new(manager) };
Ok(())
@@ -181,11 +181,11 @@ pub extern "C" fn vpkc_is_portable(p_manager: *mut vpkc_update_manager_t) -> boo
#[no_mangle]
#[logfn(Trace)]
#[logfn_inputs(Trace)]
-pub extern "C" fn vpkc_update_pending_restart(p_manager: *mut vpkc_update_manager_t, p_asset: *mut vpkc_asset_t) -> bool {
+pub extern "C" fn vpkc_update_pending_restart(p_manager: *mut vpkc_update_manager_t, p_asset: *mut *mut vpkc_asset_t) -> bool {
match p_manager.to_opaque_ref() {
Some(manager) => match manager.get_update_pending_restart() {
Some(asset) => {
- unsafe { allocate_velopackasset(asset, p_asset) };
+ unsafe { *p_asset = allocate_VelopackAsset(&asset) };
true
}
None => false,
@@ -199,13 +199,14 @@ pub extern "C" fn vpkc_update_pending_restart(p_manager: *mut vpkc_update_manage
#[no_mangle]
#[logfn(Trace)]
#[logfn_inputs(Trace)]
-pub extern "C" fn vpkc_check_for_updates(p_manager: *mut vpkc_update_manager_t, p_update: *mut vpkc_update_info_t) -> vpkc_update_check_t {
+pub extern "C" fn vpkc_check_for_updates(
+ p_manager: *mut vpkc_update_manager_t,
+ p_update: *mut *mut vpkc_update_info_t,
+) -> vpkc_update_check_t {
match p_manager.to_opaque_ref() {
Some(manager) => match manager.check_for_updates() {
Ok(UpdateCheck::UpdateAvailable(info)) => {
- unsafe {
- allocate_updateinfo(info, p_update);
- }
+ unsafe { *p_update = allocate_UpdateInfo(&info) };
vpkc_update_check_t::UPDATE_AVAILABLE
}
Ok(UpdateCheck::RemoteIsEmpty) => vpkc_update_check_t::REMOTE_IS_EMPTY,
@@ -243,7 +244,7 @@ pub extern "C" fn vpkc_download_updates(
None => bail!("pManager must not be null"),
};
- let update = c_to_updateinfo_opt(p_update).ok_or(anyhow!("pUpdate must not be null"))?;
+ let update = c_to_UpdateInfo(p_update)?;
if let Some(cb_progress) = cb_progress {
let (progress_sender, progress_receiver) = std::sync::mpsc::channel::();
@@ -311,15 +312,15 @@ pub extern "C" fn vpkc_wait_exit_then_apply_updates(
None => bail!("pManager must not be null"),
};
- let asset = c_to_velopackasset_opt(p_asset).ok_or(anyhow!("pAsset must not be null"))?;
- let restart_args = c_to_string_array_opt(p_restart_args, c_restart_args).unwrap_or_default();
+ let asset = c_to_VelopackAsset(p_asset)?;
+ let restart_args = c_to_String_vec(p_restart_args, c_restart_args)?;
manager.wait_exit_then_apply_updates(&asset, b_silent, b_restart, &restart_args)?;
Ok(())
})
}
/// This will launch the Velopack updater and optionally wait for a program to exit gracefully.
-/// This method is unsafe because it does not necessarily wait for any / the correct process to exit
+/// This method is unsafe because it does not necessarily wait for any / the correct process to exit
/// before applying updates. The `vpkc_wait_exit_then_apply_updates` method is recommended for most use cases.
/// If dw_wait_pid is 0, the updater will not wait for any process to exit before applying updates (Not Recommended).
#[no_mangle]
@@ -335,13 +336,9 @@ pub extern "C" fn vpkc_unsafe_apply_updates(
c_restart_args: size_t,
) -> bool {
wrap_error(|| {
- let manager = match p_manager.to_opaque_ref() {
- Some(manager) => manager,
- None => bail!("pManager must not be null"),
- };
-
- let asset = c_to_velopackasset_opt(p_asset).ok_or(anyhow!("pAsset must not be null"))?;
- let restart_args = c_to_string_array_opt(p_restart_args, c_restart_args).unwrap_or_default();
+ let manager = p_manager.to_opaque_ref().ok_or(anyhow!("pManager must not be null"))?;
+ let asset = c_to_VelopackAsset(p_asset)?;
+ let restart_args = c_to_String_vec(p_restart_args, c_restart_args)?;
let wait_mode = if dw_wait_pid > 0 { ApplyWaitMode::WaitPid(dw_wait_pid) } else { ApplyWaitMode::NoWait };
manager.unsafe_apply_updates(&asset, b_silent, wait_mode, b_restart, &restart_args)?;
Ok(())
@@ -361,7 +358,7 @@ pub extern "C" fn vpkc_free_update_manager(p_manager: *mut vpkc_update_manager_t
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_free_update_info(p_update_info: *mut vpkc_update_info_t) {
- unsafe { free_updateinfo(p_update_info) };
+ unsafe { free_UpdateInfo(p_update_info) };
}
/// Frees a vpkc_asset_t instance.
@@ -369,7 +366,7 @@ pub extern "C" fn vpkc_free_update_info(p_update_info: *mut vpkc_update_info_t)
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_free_asset(p_asset: *mut vpkc_asset_t) {
- unsafe { free_velopackasset(p_asset) };
+ unsafe { free_VelopackAsset(p_asset) };
}
/// VelopackApp helps you to handle app activation events correctly.
@@ -439,14 +436,14 @@ pub extern "C" fn vpkc_app_run(p_user_data: *mut c_void) {
hook(p_user_data, c_string.as_ptr());
});
}
-
+
// init logging
let log_file = if let Some(locator) = &app_options.locator {
default_logfile_path(locator)
} else {
default_logfile_path(LocationContext::FromCurrentExe)
};
-
+
init_logging("lib-cpp", Some(&log_file), false, false, Some(create_shared_logger()));
app.run();
}
@@ -463,7 +460,7 @@ pub extern "C" fn vpkc_app_set_auto_apply_on_startup(b_auto_apply: bool) {
#[no_mangle]
pub extern "C" fn vpkc_app_set_args(p_args: *mut *mut c_char, c_args: size_t) {
update_app_options(|opt| {
- opt.args = c_to_string_array_opt(p_args, c_args);
+ opt.args = c_to_String_vec(p_args, c_args).ok();
});
}
@@ -471,7 +468,7 @@ pub extern "C" fn vpkc_app_set_args(p_args: *mut *mut c_char, c_args: size_t) {
#[no_mangle]
pub extern "C" fn vpkc_app_set_locator(p_locator: *mut vpkc_locator_config_t) {
update_app_options(|opt| {
- opt.locator = c_to_velopacklocatorconfig_opt(p_locator);
+ opt.locator = c_to_VelopackLocatorConfig(p_locator).ok();
});
}
diff --git a/src/lib-cpp/src/types.rs b/src/lib-cpp/src/types.rs
index 7a0f7584..48d45c9e 100644
--- a/src/lib-cpp/src/types.rs
+++ b/src/lib-cpp/src/types.rs
@@ -1,6 +1,8 @@
+use anyhow::{bail, Result};
use libc::{c_char, c_void, size_t};
use std::ffi::{CStr, CString};
use std::path::PathBuf;
+use std::mem::size_of;
use velopack::{locator::VelopackLocatorConfig, UpdateInfo, UpdateOptions, VelopackAsset};
/// The result of a call to check for updates. This can indicate that an update is available, or that an error occurred.
@@ -47,73 +49,53 @@ pub type vpkc_download_asset_delegate_t = Option<
) -> bool,
>;
-pub fn c_to_string_opt(psz: *const c_char) -> Option {
+pub fn c_to_String(psz: *const c_char) -> Result {
if psz.is_null() {
- return None;
+ bail!("Null pointer: String must be set.");
}
let cstr = unsafe { CStr::from_ptr(psz) };
- Some(String::from_utf8_lossy(cstr.to_bytes()).to_string())
+ Ok(String::from_utf8_lossy(cstr.to_bytes()).to_string())
}
-pub fn c_to_string(psz: *const c_char) -> String {
- c_to_string_opt(psz).unwrap_or_default()
+pub fn c_to_String_vec(p_args: *mut *mut c_char, c_args: size_t) -> Result> {
+ if p_args.is_null() || c_args == 0 {
+ return Ok(Vec::new());
+ }
+ let mut args = Vec::with_capacity(c_args);
+ for i in 0..c_args {
+ let arg = c_to_String(unsafe { *p_args.add(i) })?;
+ args.push(arg);
+ }
+ Ok(args)
}
-pub fn c_to_pathbuf(psz: *const c_char) -> PathBuf {
- PathBuf::from(c_to_string(psz))
+pub fn c_to_PathBuf(psz: *const c_char) -> Result {
+ c_to_String(psz).map(PathBuf::from)
}
-pub fn string_to_cstr(s: &str) -> *mut c_char {
- let cstr = CString::new(s).unwrap();
+pub fn allocate_String<'a, T: Into