feat: use new droplet-rs crate

This commit is contained in:
DecDuck
2025-12-04 18:46:24 +11:00
parent 2a746f22ac
commit d08881299c
9 changed files with 263 additions and 323 deletions
Generated
+67 -22
View File
@@ -101,6 +101,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.0" version = "1.5.0"
@@ -328,9 +339,9 @@ dependencies = [
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.9.0" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db05ffb6856bf0ecdf6367558a76a0e8a77b1713044eb92845c692100ed50190" checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
@@ -349,9 +360,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.6.1" version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ffc71fcdcdb40d6f087edddf7f8f1f8f79e6cf922f555a9ee8779752d4819bd" checksum = "eb230974aaf0aca4d71665bed0aca156cf43b764fcb9583b69c6c3e686f35e72"
dependencies = [ dependencies = [
"ctor-proc-macro", "ctor-proc-macro",
"dtor", "dtor",
@@ -453,10 +464,11 @@ dependencies = [
[[package]] [[package]]
name = "droplet-rs" name = "droplet-rs"
version = "0.8.1" version = "0.9.2"
source = "git+https://github.com/Drop-OSS/droplet-rs.git#32a7ec91c35f3f676341c52124e19f7953c63a5d" source = "git+https://github.com/Drop-OSS/droplet-rs.git#d8f37886d479d8d9fec7e51523628e863306dddb"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait",
"dyn-clone", "dyn-clone",
"hex", "hex",
"rcgen", "rcgen",
@@ -850,9 +862,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.177" version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]] [[package]]
name = "libloading" name = "libloading"
@@ -907,10 +919,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "napi" name = "mio"
version = "3.5.2" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e917a98ac74187a5d486604a269ed69cd7901dd4824453d5573fb051f69b1b3" checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "napi"
version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3af30fe8e799dda3a555c496c59e960e4cff1e931b63acbaf3a3b25d9fad22b6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags", "bitflags",
@@ -933,9 +956,9 @@ checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1"
[[package]] [[package]]
name = "napi-derive" name = "napi-derive"
version = "3.3.3" version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258a6521951715e00568b258b8fb7a44c6087f588c371dc6b84a413f2728fdb" checksum = "47cffa09ea668c4cc5d7b1198780882e28780ed1804a903b80680725426223d9"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"ctor", "ctor",
@@ -947,9 +970,9 @@ dependencies = [
[[package]] [[package]]
name = "napi-derive-backend" name = "napi-derive-backend"
version = "3.0.2" version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c36636292fe04366a1eec028adc25bc72f4fd7cce35bdcc310499ef74fb7de" checksum = "5e186227ec22f4675267a176d98dffecb27e6cc88926cbb7efb5427268565c0f"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"proc-macro2", "proc-macro2",
@@ -960,9 +983,9 @@ dependencies = [
[[package]] [[package]]
name = "napi-sys" name = "napi-sys"
version = "3.1.1" version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50ef9c1086f16aea2417c3788dbefed7591c3bccd800b827f4dfb271adff1149" checksum = "8eb602b84d7c1edae45e50bbf1374696548f36ae179dfa667f577e384bb90c2b"
dependencies = [ dependencies = [
"libloading", "libloading",
] ]
@@ -1350,7 +1373,7 @@ dependencies = [
"getrandom 0.2.16", "getrandom 0.2.16",
"libc", "libc",
"untrusted", "untrusted",
"windows-sys", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -1456,6 +1479,15 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "1.0.1" version = "1.0.1"
@@ -1637,7 +1669,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc",
"mio",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -1726,9 +1762,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.18.1" version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
dependencies = [ dependencies = [
"getrandom 0.3.4", "getrandom 0.3.4",
"js-sys", "js-sys",
@@ -1739,9 +1775,9 @@ dependencies = [
[[package]] [[package]]
name = "uuid-macro-internal" name = "uuid-macro-internal"
version = "1.18.1" version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9384a660318abfbd7f8932c34d67e4d1ec511095f95972ddc01e19d7ba8413f" checksum = "39d11901c36b3650df7acb0f9ebe624f35b5ac4e1922ecd3c57f444648429594"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1839,6 +1875,15 @@ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.6" version = "0.52.6"
-22
View File
@@ -1,22 +0,0 @@
import test from "ava";
import { DropletHandler, generateManifest } from "../index.js";
test.skip("debug", async (t) => {
const handler = new DropletHandler();
console.log("created handler");
const manifest = JSON.parse(
await new Promise((r, e) =>
generateManifest(
handler,
"./assets/TheGame.zip",
(_, __) => {},
(_, __) => {},
(err, manifest) => (err ? e(err) : r(manifest))
)
)
);
return t.pass();
});
+14 -32
View File
@@ -2,7 +2,7 @@ import test from "ava";
import fs from "node:fs"; import fs from "node:fs";
import path from "path"; import path from "path";
import { DropletHandler, generateManifest } from "../index.js"; import { generateManifest } from "../index.js";
test("numerous small file", async (t) => { test("numerous small file", async (t) => {
// Setup test dir // Setup test dir
@@ -18,17 +18,11 @@ test("numerous small file", async (t) => {
fs.writeFileSync(fileName, i.toString()); fs.writeFileSync(fileName, i.toString());
} }
const dropletHandler = new DropletHandler();
const manifest = JSON.parse( const manifest = JSON.parse(
await new Promise((r, e) => await generateManifest(
generateManifest( dirName,
dropletHandler, (_, __) => {},
dirName, (_, __) => {}
(_, __) => {},
(_, __) => {},
(err, manifest) => (err ? e(err) : r(manifest))
)
) )
); );
@@ -75,17 +69,11 @@ test.skip("performance test", async (t) => {
randomStream.on("end", r); randomStream.on("end", r);
}); });
const dropletHandler = new DropletHandler();
const start = Date.now(); const start = Date.now();
await new Promise((r, e) => await generateManifest(
generateManifest( dirName,
dropletHandler, (_, __) => {},
dirName, (_, __) => {}
(_, __) => {},
(_, __) => {},
(err, manifest) => (err ? e(err) : r(manifest))
)
); );
const end = Date.now(); const end = Date.now();
@@ -108,17 +96,11 @@ test("special characters", async (t) => {
fs.writeFileSync(fileName, i.toString()); fs.writeFileSync(fileName, i.toString());
} }
const dropletHandler = new DropletHandler();
const manifest = JSON.parse( const manifest = JSON.parse(
await new Promise((r, e) => await generateManifest(
generateManifest( dirName,
dropletHandler, (_, __) => {},
dirName, (_, __) => {}
(_, __) => {},
(_, __) => {},
(err, manifest) => (err ? e(err) : r(manifest))
)
) )
); );
@@ -136,4 +118,4 @@ test("special characters", async (t) => {
} }
fs.rmSync(dirName, { recursive: true }); fs.rmSync(dirName, { recursive: true });
}); });
+19 -42
View File
@@ -4,7 +4,7 @@ import path from "path";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import prettyBytes from "pretty-bytes"; import prettyBytes from "pretty-bytes";
import droplet, { DropletHandler, generateManifest } from "../index.js"; import droplet, { generateManifest, listFiles, readFile } from "../index.js";
test("check alt thread util", async (t) => { test("check alt thread util", async (t) => {
let endtime1, endtime2; let endtime1, endtime2;
@@ -17,6 +17,7 @@ test("check alt thread util", async (t) => {
await new Promise((r) => setTimeout(r, 500)); await new Promise((r) => setTimeout(r, 500));
endtime2 = Date.now(); endtime2 = Date.now();
if (!endtime1) return t.fail("alt thread function never ran");
const difference = endtime2 - endtime1; const difference = endtime2 - endtime1;
if (difference >= 600) { if (difference >= 600) {
t.fail("likely isn't multithreaded, difference: " + difference); t.fail("likely isn't multithreaded, difference: " + difference);
@@ -36,8 +37,7 @@ test("list files", async (t) => {
fs.writeFileSync(dirName + "/subdir/one.txt", "the first subdir"); fs.writeFileSync(dirName + "/subdir/one.txt", "the first subdir");
fs.writeFileSync(dirName + "/subddir/two.txt", "the second"); fs.writeFileSync(dirName + "/subddir/two.txt", "the second");
const dropletHandler = new DropletHandler(); const files = await listFiles(dirName);
const files = dropletHandler.listFiles(dirName);
t.assert( t.assert(
files.sort().join("\n"), files.sort().join("\n"),
@@ -56,9 +56,7 @@ test("read file", async (t) => {
fs.writeFileSync(dirName + "/TESTFILE", testString); fs.writeFileSync(dirName + "/TESTFILE", testString);
const dropletHandler = new DropletHandler(); const stream = await readFile(
const stream = dropletHandler.readFile(
dirName, dirName,
"TESTFILE", "TESTFILE",
BigInt(0), BigInt(0),
@@ -84,13 +82,7 @@ test("read file offset", async (t) => {
const testString = "0123456789"; const testString = "0123456789";
fs.writeFileSync(dirName + "/TESTFILE", testString); fs.writeFileSync(dirName + "/TESTFILE", testString);
const dropletHandler = new DropletHandler(); const stream = await readFile(dirName, "TESTFILE", BigInt(1), BigInt(4));
const stream = dropletHandler.readFile(
dirName,
"TESTFILE",
BigInt(1),
BigInt(4)
);
let finalString = ""; let finalString = "";
@@ -110,9 +102,8 @@ test("read file offset", async (t) => {
test.skip("zip speed test", async (t) => { test.skip("zip speed test", async (t) => {
t.timeout(100_000_000); t.timeout(100_000_000);
const dropletHandler = new DropletHandler();
const stream = dropletHandler.readFile("./assets/TheGame.zip", "setup.exe"); const stream = await readFile("./assets/TheGame.zip", "setup.exe");
let totalRead = 0; let totalRead = 0;
let totalSeconds = 0; let totalSeconds = 0;
@@ -148,19 +139,14 @@ test.skip("zip speed test", async (t) => {
test("zip manifest test", async (t) => { test("zip manifest test", async (t) => {
const zipFiles = fs.readdirSync("./assets").filter((v) => v.endsWith(".zip")); const zipFiles = fs.readdirSync("./assets").filter((v) => v.endsWith(".zip"));
const dropletHandler = new DropletHandler();
for (const zipFile of zipFiles) { for (const zipFile of zipFiles) {
console.log("generating manifest for " + zipFile); console.log("generating manifest for " + zipFile);
const manifest = JSON.parse( const manifest = JSON.parse(
await new Promise((r, e) => await generateManifest(
generateManifest( "./assets/" + zipFile,
dropletHandler, (_, __) => {},
"./assets/" + zipFile, (_, __) => {}
(_, __) => {},
(_, __) => {},
(err, manifest) => (err ? e(err) : r(manifest))
)
) )
); );
@@ -168,15 +154,12 @@ test("zip manifest test", async (t) => {
let start = 0; let start = 0;
for (const [chunkIndex, length] of data.lengths.entries()) { for (const [chunkIndex, length] of data.lengths.entries()) {
const hash = createHash("md5"); const hash = createHash("md5");
const stream = ( const stream = await readFile(
await dropletHandler.readFile( "./assets/" + zipFile,
"./assets/" + zipFile, filename,
filename, BigInt(start),
BigInt(start), BigInt(start + length)
BigInt(start + length)
)
); );
console.log(stream);
let streamLength = 0; let streamLength = 0;
await stream.pipeTo( await stream.pipeTo(
@@ -208,17 +191,11 @@ test("zip manifest test", async (t) => {
}); });
test.skip("partially compress zip test", async (t) => { test.skip("partially compress zip test", async (t) => {
const dropletHandler = new DropletHandler();
const manifest = JSON.parse( const manifest = JSON.parse(
await new Promise((r, e) => await generateManifest(
generateManifest( "./assets/my horror game.zip",
dropletHandler, (_, __) => {},
"./assets/my horror game.zip", (_, __) => {}
(_, __) => {},
(_, __) => {},
(err, manifest) => (err ? e(err) : r(manifest))
)
) )
); );
Vendored
+9 -11
View File
@@ -1,15 +1,5 @@
/* auto-generated by NAPI-RS */ /* auto-generated by NAPI-RS */
/* eslint-disable */ /* eslint-disable */
/** * Persistent object so we can cache things between commands
*/
export declare class DropletHandler {
constructor()
hasBackendForPath(path: string): boolean
listFiles(path: string): Array<string>
peekFile(path: string, subPath: string): bigint
readFile(path: string, subPath: string, start?: bigint | undefined | null, end?: bigint | undefined | null): ReadableStream
}
export declare class Script { export declare class Script {
} }
@@ -26,10 +16,18 @@ export declare function callAltThreadFunc(tsfn: ((err: Error | null, ) => any)):
export declare function generateClientCertificate(clientId: string, clientName: string, rootCa: string, rootCaPrivate: string): Array<string> export declare function generateClientCertificate(clientId: string, clientName: string, rootCa: string, rootCaPrivate: string): Array<string>
export declare function generateManifest(dropletHandler: DropletHandler, dir: string, progressSfn: ((err: Error | null, arg: number) => any), logSfn: ((err: Error | null, arg: string) => any), callbackSfn: ((err: Error | null, arg: string) => any)): void export declare function generateManifest(dir: string, progressSfn: ((err: Error | null, arg: number) => any), logSfn: ((err: Error | null, arg: string) => any)): Promise<string>
export declare function generateRootCa(): Array<string> export declare function generateRootCa(): Array<string>
export declare function hasBackendForPath(path: string): boolean
export declare function listFiles(path: string): Promise<Array<string>>
export declare function peekFile(path: string, subPath: string): Promise<bigint>
export declare function readFile(path: string, subPath: string, start?: bigint | undefined | null, end?: bigint | undefined | null): ReadableStream<Buffer>
export declare function signNonce(privateKey: string, nonce: string): string export declare function signNonce(privateKey: string, nonce: string): string
export declare function verifyClientCertificate(clientCert: string, rootCa: string): boolean export declare function verifyClientCertificate(clientCert: string, rootCa: string): boolean
+4 -1
View File
@@ -376,13 +376,16 @@ if (!nativeBinding) {
} }
module.exports = nativeBinding module.exports = nativeBinding
module.exports.DropletHandler = nativeBinding.DropletHandler
module.exports.Script = nativeBinding.Script module.exports.Script = nativeBinding.Script
module.exports.ScriptEngine = nativeBinding.ScriptEngine module.exports.ScriptEngine = nativeBinding.ScriptEngine
module.exports.callAltThreadFunc = nativeBinding.callAltThreadFunc module.exports.callAltThreadFunc = nativeBinding.callAltThreadFunc
module.exports.generateClientCertificate = nativeBinding.generateClientCertificate module.exports.generateClientCertificate = nativeBinding.generateClientCertificate
module.exports.generateManifest = nativeBinding.generateManifest module.exports.generateManifest = nativeBinding.generateManifest
module.exports.generateRootCa = nativeBinding.generateRootCa module.exports.generateRootCa = nativeBinding.generateRootCa
module.exports.hasBackendForPath = nativeBinding.hasBackendForPath
module.exports.listFiles = nativeBinding.listFiles
module.exports.peekFile = nativeBinding.peekFile
module.exports.readFile = nativeBinding.readFile
module.exports.signNonce = nativeBinding.signNonce module.exports.signNonce = nativeBinding.signNonce
module.exports.verifyClientCertificate = nativeBinding.verifyClientCertificate module.exports.verifyClientCertificate = nativeBinding.verifyClientCertificate
module.exports.verifyNonce = nativeBinding.verifyNonce module.exports.verifyNonce = nativeBinding.verifyNonce
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@drop-oss/droplet", "name": "@drop-oss/droplet",
"version": "3.5.1", "version": "4.0.0",
"main": "index.js", "main": "index.js",
"types": "index.d.ts", "types": "index.d.ts",
"napi": { "napi": {
+72 -101
View File
@@ -1,14 +1,14 @@
use std::{collections::HashMap, sync::Arc, thread}; use std::{collections::HashMap, sync::Arc, thread};
use droplet_rs::versions::types::VersionBackend;
use napi::{ use napi::{
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
Result, Result,
}; };
use serde_json::json; use serde_json::json;
use tokio::io::AsyncReadExt as _;
use uuid::Uuid; use uuid::Uuid;
use crate::version::DropletHandler; use crate::version::create_backend_for_path;
const CHUNK_SIZE: usize = 1024 * 1024 * 64; const CHUNK_SIZE: usize = 1024 * 1024 * 64;
@@ -30,117 +30,88 @@ pub fn call_alt_thread_func(tsfn: Arc<ThreadsafeFunction<()>>) -> Result<(), Str
} }
#[napi] #[napi]
pub fn generate_manifest<'a>( pub async fn generate_manifest<'a>(
droplet_handler: &mut DropletHandler,
dir: String, dir: String,
progress_sfn: ThreadsafeFunction<i32>, progress_sfn: ThreadsafeFunction<i32>,
log_sfn: ThreadsafeFunction<String>, log_sfn: ThreadsafeFunction<String>,
callback_sfn: ThreadsafeFunction<String>, ) -> anyhow::Result<String> {
) -> anyhow::Result<()> { let mut backend = create_backend_for_path(dir).ok_or(napi::Error::from_reason(
let backend: &mut Box<dyn VersionBackend + Send> = droplet_handler
.create_backend_for_path(dir)
.ok_or(napi::Error::from_reason(
"Could not create backend for path.", "Could not create backend for path.",
))?; ))?;
// This is unsafe (obviously) let required_single_file = backend.require_whole_files();
// But it's allg as long the DropletHandler doesn't get
// dropped while we're generating the manifest.
let backend: &'static mut Box<dyn VersionBackend + Send> =
unsafe { std::mem::transmute(backend) };
let required_single_file = backend.require_whole_files(); let files = backend.list_files().await?;
// Filepath to chunk data
let mut chunks: HashMap<String, ChunkData> = HashMap::new();
thread::spawn(move || { let total: i32 = files.len() as i32;
let callback_borrow = &callback_sfn; let mut i: i32 = 0;
let mut inner = move || -> Result<()> { let mut buf = [0u8; 1024 * 16];
let files = backend.list_files()?;
// Filepath to chunk data for version_file in files {
let mut chunks: HashMap<String, ChunkData> = HashMap::new(); let mut reader = backend.reader(&version_file, 0, 0).await?;
let total: i32 = files.len() as i32; let mut chunk_data = ChunkData {
let mut i: i32 = 0; permissions: version_file.permission,
ids: Vec::new(),
let mut buf = [0u8; 1024 * 16]; checksums: Vec::new(),
lengths: Vec::new(),
for version_file in files {
let mut reader = backend.reader(&version_file, 0, 0)?;
let mut chunk_data = ChunkData {
permissions: version_file.permission,
ids: Vec::new(),
checksums: Vec::new(),
lengths: Vec::new(),
};
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)?;
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;
}
}
chunks.insert(version_file.relative_filename, chunk_data);
i += 1;
let progress = i * 100 / total;
progress_sfn.call(Ok(progress), ThreadsafeFunctionCallMode::Blocking);
}
callback_borrow.call(
Ok(json!(chunks).to_string()),
ThreadsafeFunctionCallMode::Blocking,
);
Ok(())
}; };
let result = inner(); let mut chunk_index = 0;
if let Err(generate_err) = result { loop {
callback_borrow.call(Err(generate_err), ThreadsafeFunctionCallMode::Blocking); let mut length = 0;
} let mut buffer: Vec<u8> = Vec::new();
}); let mut file_empty = false;
Ok(()) 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;
}
}
chunks.insert(version_file.relative_filename, chunk_data);
i += 1;
let progress = i * 100 / total;
progress_sfn.call(Ok(progress), ThreadsafeFunctionCallMode::Blocking);
}
Ok(json!(chunks).to_string())
} }
+77 -91
View File
@@ -6,116 +6,102 @@ use std::{
}; };
use anyhow::anyhow; use anyhow::anyhow;
use droplet_rs::versions::{create_backend_constructor, types::{ReadToAsyncRead, VersionBackend, VersionFile}}; use droplet_rs::versions::{
use napi::{bindgen_prelude::*, sys::napi_value__, tokio_stream::StreamExt}; create_backend_constructor,
types::{VersionBackend, VersionFile},
};
use napi::{
bindgen_prelude::*,
sys::napi_value__,
tokio_stream::{wrappers::ReceiverStream, StreamExt},
};
use tokio::io::{AsyncReadExt, BufReader};
use tokio_util::codec::{BytesCodec, FramedRead}; use tokio_util::codec::{BytesCodec, FramedRead};
pub fn create_backend_for_path(path: String) -> Option<Box<dyn VersionBackend + Send>> {
let fs_path = Path::new(&path);
let constructor = create_backend_constructor(fs_path)?;
/** Some(constructor().ok()?)
* Persistent object so we can cache things between commands
*/
#[napi(js_name = "DropletHandler")]
pub struct DropletHandler<'a> {
backend_cache: HashMap<String, Box<dyn VersionBackend + Send + 'a>>,
} }
#[napi] #[napi]
impl<'a> DropletHandler<'a> { pub fn has_backend_for_path(path: String) -> bool {
#[napi(constructor)] let path = Path::new(&path);
pub fn new() -> Self {
DropletHandler {
backend_cache: HashMap::new(),
}
}
pub fn create_backend_for_path( let has_backend = create_backend_constructor(path).is_some();
&mut self,
path: String,
) -> Option<&mut Box<dyn VersionBackend + Send + 'a>> {
let fs_path = Path::new(&path);
let constructor = create_backend_constructor(fs_path)?;
let existing_backend = match self.backend_cache.entry(path) { has_backend
std::collections::hash_map::Entry::Occupied(occupied_entry) => occupied_entry.into_mut(), }
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
let backend = constructor().ok()?;
vacant_entry.insert(backend)
}
};
Some(existing_backend) #[napi]
} pub async fn list_files(path: String) -> Result<Vec<String>> {
let mut backend =
create_backend_for_path(path).ok_or(napi::Error::from_reason("No backend for path"))?;
let files = backend.list_files().await?;
Ok(files.into_iter().map(|e| e.relative_filename).collect())
}
#[napi] #[napi]
pub fn has_backend_for_path(&self, path: String) -> bool { pub async fn peek_file(path: String, sub_path: String) -> Result<u64> {
let path = Path::new(&path); let mut backend =
create_backend_for_path(path).ok_or(napi::Error::from_reason("No backend for path"))?;
let has_backend = create_backend_constructor(path).is_some(); let file = backend.peek_file(sub_path).await?;
has_backend Ok(file.size)
} }
#[napi] #[napi]
pub fn list_files(&mut self, path: String) -> Result<Vec<String>> { pub fn read_file(
let backend = self path: String,
.create_backend_for_path(path) sub_path: String,
.ok_or(napi::Error::from_reason("No backend for path"))?; env: &Env,
let files = backend.list_files()?; start: Option<BigInt>,
Ok(files.into_iter().map(|e| e.relative_filename).collect()) end: Option<BigInt>,
} ) -> anyhow::Result<ReadableStream<BufferSlice>> {
let mut backend = create_backend_for_path(path).ok_or(anyhow!("Failed to create backend."))?;
let version_file = VersionFile {
relative_filename: sub_path,
permission: 0, // Shouldn't matter
size: 0, // Shouldn't matter
};
#[napi] let (tx, rx) = tokio::sync::mpsc::channel(100);
pub fn peek_file(&mut self, path: String, sub_path: String) -> Result<u64> {
let backend = self
.create_backend_for_path(path)
.ok_or(napi::Error::from_reason("No backend for path"))?;
let file = backend.peek_file(sub_path)?; spawn(async move {
// Use `?` operator for cleaner error propagation from `Option`
Ok(file.size) let reader = backend
} .reader(
#[napi(ts_return_type = "ReadableStream")]
pub fn read_file(
&mut self,
reference: Reference<DropletHandler<'static>>,
path: String,
sub_path: String,
env: Env,
start: Option<BigInt>,
end: Option<BigInt>,
) -> anyhow::Result<*mut napi_value__> {
let stream = reference.share_with(env, |handler| {
let backend = handler
.create_backend_for_path(path)
.ok_or(anyhow!("Failed to create backend."))?;
let version_file = VersionFile {
relative_filename: sub_path,
permission: 0, // Shouldn't matter
size: 0, // Shouldn't matter
};
// Use `?` operator for cleaner error propagation from `Option`
let reader = backend.reader(
&version_file, &version_file,
start.map(|e| e.get_u64().1).unwrap_or(0), start.map(|e| e.get_u64().1).unwrap_or(0),
end.map(|e| e.get_u64().1).unwrap_or(0), end.map(|e| e.get_u64().1).unwrap_or(0),
)?; )
.await
.expect("failed to open file");
let async_reader = ReadToAsyncRead { inner: reader }; let mut reader = BufReader::new(reader);
// Create a FramedRead stream with BytesCodec for chunking let mut read_buf = [0u8; 4096];
let stream = FramedRead::new(async_reader, BytesCodec::new())
// Use StreamExt::map to transform each Result item
.map(|result_item| {
result_item
// Apply Result::map to transform Ok(BytesMut) to Ok(Vec<u8>)
.map(|bytes| bytes.to_vec())
// Apply Result::map_err to transform Err(std::io::Error) to Err(napi::Error)
.map_err(napi::Error::from) // napi::Error implements From<tokio::io::Error>
});
ReadableStream::create_with_stream_bytes(&env, stream)
})?;
Ok(stream.raw()) loop {
} let amount = reader.read(&mut read_buf).await;
if amount.is_err() {
let _ = tx.send(Err(napi::Error::from_reason(
amount.unwrap_err().to_string(),
))).await;
break;
}
let amount = amount.unwrap();
if amount == 0 {
break;
}
tx.send(Ok(read_buf[0..amount].to_vec())).await.expect("failed to send data");
}
});
return Ok(ReadableStream::create_with_stream_bytes(
env,
ReceiverStream::new(rx),
)?);
} }