3 Commits

Author SHA1 Message Date
fe43f79062 chore: bump version and add test 2025-05-30 20:55:53 +10:00
30b9c4a1cc bump version 2025-05-29 09:30:10 +10:00
42f770aed9 feat: Add file start and end to read_file function
Signed-off-by: quexeky <git@quexeky.dev>
2025-05-28 22:32:37 +10:00
5 changed files with 50 additions and 9 deletions

View File

@ -22,7 +22,7 @@ time-macros = "0.2.22"
time = "0.3.41" time = "0.3.41"
webpki = "0.22.4" webpki = "0.22.4"
ring = "0.17.14" ring = "0.17.14"
tokio = { version = "1.45.1", features = ["fs"] } tokio = { version = "1.45.1", features = ["fs", "io-util"] }
tokio-util = { version = "0.7.15", features = ["codec"] } tokio-util = { version = "0.7.15", features = ["codec"] }
[dependencies.x509-parser] [dependencies.x509-parser]

View File

@ -28,11 +28,11 @@ test("read file", async (t) => {
if (fs.existsSync(dirName)) fs.rmSync(dirName, { recursive: true }); if (fs.existsSync(dirName)) fs.rmSync(dirName, { recursive: true });
fs.mkdirSync(dirName, { recursive: true }); fs.mkdirSync(dirName, { recursive: true });
const testString = "g'day what's up my koala bros\n".repeat(10000); const testString = "g'day what's up my koala bros\n".repeat(1000);
fs.writeFileSync("./.test2/TESTFILE", testString); fs.writeFileSync(dirName + "/TESTFILE", testString);
const stream = droplet.readFile("./.test2", "TESTFILE"); const stream = droplet.readFile(dirName, "TESTFILE");
let finalString = ""; let finalString = "";
@ -44,3 +44,28 @@ test("read file", async (t) => {
t.assert(finalString == testString, "file strings don't match"); t.assert(finalString == testString, "file strings don't match");
fs.rmSync(dirName, { recursive: true }); fs.rmSync(dirName, { recursive: true });
}); });
test("read file offset", async (t) => {
const dirName = "./.test3";
if (fs.existsSync(dirName)) fs.rmSync(dirName, { recursive: true });
fs.mkdirSync(dirName, { recursive: true });
const testString = "0123456789";
fs.writeFileSync(dirName + "/TESTFILE", testString);
const stream = droplet.readFile(dirName, "TESTFILE", 1, 4);
let finalString = "";
for await (const chunk of stream) {
// Do something with each 'chunk'
finalString += String.fromCharCode.apply(null, chunk);
}
const expectedString = testString.slice(1, 5);
t.assert(finalString == expectedString, "file strings don't match");
fs.rmSync(dirName, { recursive: true });
});

2
index.d.ts vendored
View File

@ -12,7 +12,7 @@ export declare function hasBackendForPath(path: string): boolean
export declare function listFiles(path: string): Array<string> export declare function listFiles(path: string): Array<string>
export declare function readFile(path: string, subPath: string): ReadableStream<Buffer> | null export declare function readFile(path: string, subPath: string, start?: number | undefined | null, end?: number | undefined | null): ReadableStream<Buffer> | null
export declare function signNonce(privateKey: string, nonce: string): string export declare function signNonce(privateKey: string, nonce: string): string

View File

@ -1,6 +1,6 @@
{ {
"name": "@drop-oss/droplet", "name": "@drop-oss/droplet",
"version": "1.2.1", "version": "1.3.1",
"main": "index.js", "main": "index.js",
"types": "index.d.ts", "types": "index.d.ts",
"napi": { "napi": {

View File

@ -2,7 +2,7 @@
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
use std::{ use std::{
fs::{self, metadata, File}, fs::{self, metadata, File},
io::{self, BufReader, ErrorKind, Read}, io::{self, BufReader, ErrorKind, Read, Seek},
path::{Path, PathBuf}, path::{Path, PathBuf},
task::Poll, task::Poll,
}; };
@ -11,6 +11,7 @@ use napi::{
bindgen_prelude::*, bindgen_prelude::*,
tokio_stream::{Stream, StreamExt}, tokio_stream::{Stream, StreamExt},
}; };
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt, Take};
use tokio_util::{ use tokio_util::{
bytes::BytesMut, bytes::BytesMut,
codec::{BytesCodec, FramedRead}, codec::{BytesCodec, FramedRead},
@ -134,6 +135,8 @@ pub fn read_file(
path: String, path: String,
sub_path: String, sub_path: String,
env: &Env, env: &Env,
start: Option<u32>,
end: Option<u32>
) -> Option<ReadableStream<'static, BufferSlice<'static>>> { ) -> Option<ReadableStream<'static, BufferSlice<'static>>> {
let path = Path::new(&path); let path = Path::new(&path);
let backend = create_backend_for_path(path).unwrap(); let backend = create_backend_for_path(path).unwrap();
@ -142,14 +145,27 @@ pub fn read_file(
permission: 0, // Shouldn't matter permission: 0, // Shouldn't matter
}; };
// Use `?` operator for cleaner error propagation from `Option` // Use `?` operator for cleaner error propagation from `Option`
let reader = backend.reader(&version_file)?; let mut reader = backend.reader(&version_file)?;
// Can't do this in tokio because it requires a .await, which we can't do here
if let Some(start) = start {
reader.seek(io::SeekFrom::Start(start as u64)).unwrap();
}
// Convert std::fs::File to tokio::fs::File for async operations // Convert std::fs::File to tokio::fs::File for async operations
let reader = tokio::fs::File::from_std(reader); let reader = tokio::fs::File::from_std(reader);
let boxed_reader: Box<dyn AsyncRead + Send + Unpin> = match end {
Some(end_val) => Box::new(reader.take(end_val as u64)),
None => Box::new(reader),
};
// Create a FramedRead stream with BytesCodec for chunking // Create a FramedRead stream with BytesCodec for chunking
let stream = FramedRead::new(reader, BytesCodec::new()) let stream = FramedRead::new(boxed_reader, BytesCodec::new())
// Use StreamExt::map to transform each Result item // Use StreamExt::map to transform each Result item
.map(|result_item| { .map(|result_item| {
result_item result_item