v2 manifest

This commit is contained in:
DecDuck
2025-12-13 11:57:04 +11:00
parent d08881299c
commit ed07dd8804
6 changed files with 364 additions and 102 deletions

201
Cargo.lock generated
View File

@@ -74,7 +74,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
"synstructure",
]
@@ -86,7 +86,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
"synstructure",
]
@@ -98,7 +98,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -109,7 +109,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -130,6 +130,15 @@ version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "boa_ast"
version = "0.20.0"
@@ -227,7 +236,7 @@ checksum = "9fd3f870829131332587f607a7ff909f1af5fc523fd1b192db55fbbdf52e8d3c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
"synstructure",
]
@@ -292,7 +301,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -346,6 +355,15 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
@@ -358,6 +376,16 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "ctor"
version = "0.6.2"
@@ -431,6 +459,16 @@ dependencies = [
"powerfmt",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -439,7 +477,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -449,14 +487,17 @@ dependencies = [
"anyhow",
"boa_engine",
"droplet-rs",
"format-bytes",
"hashing-reader",
"hex",
"md5",
"humansize",
"napi",
"napi-build",
"napi-derive",
"rhai",
"serde",
"serde_json",
"sha2",
"tokio",
"tokio-util",
"uuid",
@@ -537,6 +578,26 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "format-bytes"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd"
dependencies = [
"format-bytes-macros",
]
[[package]]
name = "format-bytes-macros"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "futures"
version = "0.3.31"
@@ -593,7 +654,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -626,6 +687,16 @@ dependencies = [
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.16"
@@ -677,12 +748,32 @@ dependencies = [
"foldhash 0.2.0",
]
[[package]]
name = "hashing-reader"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490b80ea8c6d700506827951d7196fede236081b9c976a42b30598fa77377d15"
dependencies = [
"digest",
"pin-project",
"tokio",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]]
name = "icu_collections"
version = "1.5.0"
@@ -798,7 +889,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -876,6 +967,12 @@ dependencies = [
"windows-link",
]
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "litemap"
version = "0.7.5"
@@ -891,12 +988,6 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "2.7.6"
@@ -965,7 +1056,7 @@ dependencies = [
"napi-derive-backend",
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -978,7 +1069,7 @@ dependencies = [
"proc-macro2",
"quote",
"semver",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -1060,7 +1151,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -1158,7 +1249,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -1170,6 +1261,26 @@ dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@@ -1359,7 +1470,7 @@ checksum = "d4322a2a4e8cf30771dd9f27f7f37ca9ac8fe812dddd811096a98483080dabe6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -1457,7 +1568,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -1473,6 +1584,17 @@ dependencies = [
"serde_core",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
@@ -1535,6 +1657,17 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.111"
@@ -1554,7 +1687,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -1595,7 +1728,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -1606,7 +1739,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -1730,6 +1863,12 @@ dependencies = [
"winnow",
]
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.22"
@@ -1781,7 +1920,7 @@ checksum = "39d11901c36b3650df7acb0f9ebe624f35b5ac4e1922ecd3c57f444648429594"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -1837,7 +1976,7 @@ dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
"wasm-bindgen-shared",
]
@@ -2040,7 +2179,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
"synstructure",
]
@@ -2061,7 +2200,7 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]
[[package]]
@@ -2081,7 +2220,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
"synstructure",
]
@@ -2110,5 +2249,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.111",
]

View File

@@ -17,7 +17,6 @@ napi = { version = "3.0.0-beta.11", default-features = false, features = [
] }
napi-derive = "3.0.0-beta.11"
hex = "0.4.3"
md5 = "0.7.0"
tokio = { version = "1.45.1", features = ["fs", "io-util"] }
tokio-util = { version = "0.7.15", features = ["codec"] }
rhai = "1.22.2"
@@ -26,6 +25,10 @@ boa_engine = "0.20.0"
serde_json = "1.0.143"
anyhow = "*"
droplet-rs = { git = "https://github.com/Drop-OSS/droplet-rs.git" }
hashing-reader = "0.1.0"
sha2 = "0.10.9"
format-bytes = "0.3.0"
humansize = "2.1.3"
[dependencies.serde]
version = "1.0.210"

View File

@@ -149,6 +149,9 @@ test("zip manifest test", async (t) => {
(_, __) => {}
)
);
const files = await listFiles("./assets/" + zipFile);
if(Object.keys(manifest).length == 0) return t.fail("manifest was empty")
for (const [filename, data] of Object.entries(manifest)) {
let start = 0;

View File

@@ -3,6 +3,7 @@
#![deny(clippy::panic)]
#![feature(trait_alias)]
#![feature(iterator_try_collect)]
#![feature(int_roundings)]
pub mod manifest;

View File

@@ -1,23 +1,36 @@
use std::{collections::HashMap, sync::Arc, thread};
use droplet_rs::versions::types::VersionFile;
use hashing_reader::HashingReader;
use hex::ToHex;
use humansize::{format_size, BINARY};
use napi::{
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
Result,
};
use serde::Serialize;
use serde_json::json;
use sha2::{Digest, Sha256};
use tokio::io::AsyncReadExt as _;
use uuid::Uuid;
use crate::version::create_backend_for_path;
const CHUNK_SIZE: usize = 1024 * 1024 * 64;
const CHUNK_SIZE: u64 = 1024 * 1024 * 64;
const WIGGLE: u64 = 1024 * 1024 * 1;
#[derive(serde::Serialize)]
struct ChunkData {
#[derive(Serialize)]
struct FileEntry {
filename: String,
start: usize,
length: usize,
permissions: u32,
ids: Vec<String>,
checksums: Vec<String>,
lengths: Vec<usize>,
}
#[derive(Serialize)]
struct ChunkData {
files: Vec<FileEntry>,
checksum: String,
}
#[napi]
@@ -29,89 +42,185 @@ pub fn call_alt_thread_func(tsfn: Arc<ThreadsafeFunction<()>>) -> Result<(), Str
Ok(())
}
#[derive(Serialize)]
struct Manifest {
version: String,
chunks: HashMap<String, ChunkData>,
size: u64,
}
#[napi]
pub async fn generate_manifest<'a>(
pub async fn generate_manifest(
dir: String,
progress_sfn: ThreadsafeFunction<i32>,
progress_sfn: ThreadsafeFunction<f32>,
log_sfn: ThreadsafeFunction<String>,
) -> anyhow::Result<String> {
let mut backend = create_backend_for_path(dir).ok_or(napi::Error::from_reason(
"Could not create backend for path.",
))?;
let required_single_file = backend.require_whole_files();
let required_single_file = true; //backend.require_whole_files();
let files = backend.list_files().await?;
let mut files = backend.list_files().await?;
// Filepath to chunk data
let mut chunks: HashMap<String, ChunkData> = HashMap::new();
let mut chunks: Vec<Vec<(VersionFile, u64, u64)>> = Vec::new();
let mut current_chunk: Vec<(VersionFile, u64, u64)> = Vec::new();
let total: i32 = files.len() as i32;
let mut i: i32 = 0;
let mut buf = [0u8; 1024 * 16];
log_sfn.call(
Ok(format!("organizing files into chunks...",)),
ThreadsafeFunctionCallMode::NonBlocking,
);
for version_file in files {
let mut reader = backend.reader(&version_file, 0, 0).await?;
// let mut reader = backend.reader(&version_file, 0, 0).await?;
let mut chunk_data = ChunkData {
permissions: version_file.permission,
ids: Vec::new(),
checksums: Vec::new(),
lengths: Vec::new(),
};
// If we need the whole file, and this file would take up a whole chunk, add it to it's own chunk and move on
if required_single_file && version_file.size >= CHUNK_SIZE {
let size = version_file.size;
chunks.push(vec![(version_file, 0, size)]);
let mut chunk_index = 0;
loop {
let mut length = 0;
let mut buffer: Vec<u8> = Vec::new();
let mut file_empty = false;
loop {
let read = reader.read(&mut buf).await?;
length += read;
// If we're out of data, add this chunk and then move onto the next file
if read == 0 {
file_empty = true;
break;
}
buffer.extend_from_slice(&buf[0..read]);
if length >= CHUNK_SIZE && !required_single_file {
break;
}
}
let chunk_id = Uuid::new_v4();
let checksum = md5::compute(buffer).0;
let checksum_string = hex::encode(checksum);
chunk_data.ids.push(chunk_id.to_string());
chunk_data.checksums.push(checksum_string);
chunk_data.lengths.push(length);
let log_str = format!(
"Processed chunk {} for {}",
chunk_index, &version_file.relative_filename
);
log_sfn.call(Ok(log_str), ThreadsafeFunctionCallMode::Blocking);
chunk_index += 1;
if file_empty {
break;
}
continue;
}
chunks.insert(version_file.relative_filename, chunk_data);
let mut current_size = current_chunk.iter().map(|v| v.2 - v.1).sum::<u64>();
i += 1;
let progress = i * 100 / total;
// If we need the whole file, add this current file and move on, potentially adding and creating new chunk if need be
if required_single_file {
let size = version_file.size.try_into().unwrap();
current_chunk.push((version_file, 0, size));
current_size += size;
if current_size >= CHUNK_SIZE {
// Pop current and add, then reset
let new_chunk = std::mem::replace(&mut current_chunk, Vec::new());
chunks.push(new_chunk);
}
continue;
}
// Otherwise we calculate how much of the file we need, then use that much
let remaining_budget = (CHUNK_SIZE + WIGGLE) - current_size;
if version_file.size >= remaining_budget {
let remaining_budget = CHUNK_SIZE - current_size;
current_chunk.push((version_file.clone(), 0, remaining_budget));
let new_chunk = std::mem::replace(&mut current_chunk, Vec::new());
chunks.push(new_chunk);
let remaining_size = version_file.size - remaining_budget;
let mut running_offset = remaining_budget;
// Do everything but the last one
while running_offset < remaining_size {
let chunk_size = CHUNK_SIZE.min(remaining_size);
let chunk = vec![(version_file.clone(), running_offset, chunk_size)];
if chunk_size == CHUNK_SIZE {
chunks.push(chunk);
} else {
current_chunk = chunk;
}
running_offset += chunk_size;
}
continue;
} else {
let size = version_file.size;
current_chunk.push((version_file, 0, size));
current_size += size;
}
if current_size >= CHUNK_SIZE {
// Pop current and add, then reset
let new_chunk = std::mem::replace(&mut current_chunk, Vec::new());
chunks.push(new_chunk);
}
}
if current_chunk.len() > 0 {
chunks.push(current_chunk);
}
log_sfn.call(
Ok(format!(
"organized into {} chunks, generating checksums...",
chunks.len()
)),
ThreadsafeFunctionCallMode::Blocking,
);
let mut manifest: HashMap<String, ChunkData> = HashMap::new();
let mut total_manifest_length = 0;
let mut read_buf = vec![0; 1024 * 1024 * 64];
let chunk_len = chunks.len();
for (index, chunk) in chunks.into_iter().enumerate() {
let uuid = uuid::Uuid::new_v4().to_string();
let mut hasher = Sha256::new();
let mut chunk_data = ChunkData {
files: Vec::new(),
checksum: String::new(),
};
let mut chunk_length = 0;
for (file, start, length) in chunk {
log_sfn.call(
Ok(format!(
"reading {} from {} to {}, {}",
file.relative_filename,
start,
start + length,
format_size(length, BINARY)
)),
ThreadsafeFunctionCallMode::Blocking,
);
let mut reader = backend.reader(&file, start, start + length).await?;
loop {
let amount = reader.read(&mut read_buf).await?;
if amount == 0 {
break;
}
hasher.update(&read_buf[0..amount]);
}
chunk_length += length;
chunk_data.files.push(FileEntry {
filename: file.relative_filename,
start: start.try_into().unwrap(),
length: length.try_into().unwrap(),
permissions: file.permission,
});
}
log_sfn.call(
Ok(format!(
"created chunk of size {} ({}/{})",
format_size(chunk_length, BINARY),
index,
chunk_len
)),
ThreadsafeFunctionCallMode::Blocking,
);
total_manifest_length += chunk_length;
let hash: String = hasher.finalize().encode_hex();
chunk_data.checksum = hash;
manifest.insert(uuid, chunk_data);
let progress: f32 = (index as f32 / chunk_len as f32) * 100.0f32;
progress_sfn.call(Ok(progress), ThreadsafeFunctionCallMode::Blocking);
}
Ok(json!(chunks).to_string())
Ok(
json!(Manifest {
version: "2".to_string(),
chunks: manifest,
size: total_manifest_length
})
.to_string(),
)
}

7
test.js Normal file
View File

@@ -0,0 +1,7 @@
const droplet = require('.');
const fs = require('fs');
(async () => {
const manifest = await droplet.generateManifest("/home/decduck/Games/STAR WARS Jedi Survivor", console.log, console.log);
fs.writeFileSync('./manifest.json', manifest);
})();