Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions __test__/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,13 @@ if (testResolve) {
}
});
}
ava_1.default.serial('should return error when getting file for deleted file handle', async (t) => {
const rootHandle = await getRootHandle();
const fileHandle = await rootHandle.getFileHandle('transient', { create: true });
await rootHandle.removeEntry(fileHandle.name);
const err = await t.throwsAsync(fileHandle.getFile());
t.is(err?.message, 'File "transient" not found');
});
ava_1.default.serial('should return file for file handle', async (t) => {
const rootHandle = await getRootHandle();
const fileHandle = await rootHandle.getFileHandle('annar');
Expand Down Expand Up @@ -568,6 +575,13 @@ ava_1.default.serial('should return stream for file', async (t) => {
const y = await reader.read();
t.true(y.done);
});
ava_1.default.serial('should return error when creating writable for deleted file handle', async (t) => {
const rootHandle = await getRootHandle();
const fileHandle = await rootHandle.getFileHandle('fleeting', { create: true });
await rootHandle.removeEntry(fileHandle.name);
const err = await t.throwsAsync(fileHandle.createWritable());
t.is(err?.message, 'File "fleeting" not found');
});
ava_1.default.serial('should succeed when streaming file larger than max_read_size', async (t) => {
const rootHandle = await getRootHandle();
const fileHandle = await rootHandle.getFileHandle('writable-stream-larger-than-max-read-size', { create: true });
Expand Down Expand Up @@ -1185,6 +1199,62 @@ ava_1.default.serial('should handle getting directories concurrently', async (t)
t.is(quatre.name, 'quatre');
}
});
ava_1.default.serial('should handle getting stats for a directory', async (t) => {
const rootHandle = await getRootHandle();
const rootStats = await rootHandle.stat();
t.assert(rootStats, 'root dir stats not returned');
if (!node_process_1.default.env.TEST_USING_MOCKS) {
t.assert(rootStats.inode, 'root dir stats do not include inode');
t.not(rootStats.creationTime, 0);
}
else {
t.assert(!rootStats.inode, 'root dir stats include indode');
}
t.assert(rootStats.modifiedTime >= rootStats.creationTime, `root dir stats have creation time greater than modified time: ${JSON.stringify(rootStats)}`);
t.assert(rootStats.accessedTime >= rootStats.creationTime, `root dir stats have creation time greater than accessed time: ${JSON.stringify(rootStats)}`);
const dirHandle = await rootHandle.getDirectoryHandle('subdir-for-statting', { create: true });
const dirStats = await dirHandle.stat();
t.assert(dirStats, 'subdir stats not returned');
if (!node_process_1.default.env.TEST_USING_MOCKS) {
t.assert(dirStats.inode, 'subdir stats do not include inode');
t.not(dirStats.creationTime, 0);
}
else {
t.assert(!dirStats.inode, 'subdir stats include indode');
}
t.not(dirStats.creationTime, 0);
t.assert(dirStats.modifiedTime >= dirStats.creationTime, `subdir stats have creation time greater than modified time: ${JSON.stringify(dirStats)}`);
t.assert(dirStats.accessedTime >= dirStats.creationTime, `subdir stats have creation time greater than accessed time: ${JSON.stringify(dirStats)}`);
});
ava_1.default.serial('should handle getting stats for a file', async (t) => {
const rootHandle = await getRootHandle();
const dirHandle = await rootHandle.getDirectoryHandle('first');
const dirStats = await dirHandle.stat();
t.assert(dirStats, 'dir stats not returned');
if (!node_process_1.default.env.TEST_USING_MOCKS) {
t.assert(dirStats.inode, 'dir stats do not include inode');
t.not(dirStats.creationTime, 0);
}
else {
t.assert(!dirStats.inode, 'dir stats include indode');
}
t.not(dirStats.creationTime, 0);
t.assert(dirStats.modifiedTime >= dirStats.creationTime, `dir stats have creation time greater than modified time: ${JSON.stringify(dirStats)}`);
t.assert(dirStats.accessedTime >= dirStats.creationTime, `dir stats have creation time greater than accessed time: ${JSON.stringify(dirStats)}`);
const fileHandle = await rootHandle.getFileHandle('annar');
const fileStats = await fileHandle.stat();
t.assert(fileStats, 'file stats not returned');
if (!node_process_1.default.env.TEST_USING_MOCKS) {
t.assert(fileStats.inode, 'file stats do not include inode');
t.not(fileStats.creationTime, 0);
}
else {
t.assert(!fileStats.inode, 'file stats include indode');
}
t.not(fileStats.creationTime, 0);
t.assert(fileStats.modifiedTime >= fileStats.creationTime, `file stats have creation time greater than modified time: ${JSON.stringify(fileStats)}`);
t.assert(fileStats.accessedTime >= fileStats.creationTime, `file stats have creation time greater than accessed time: ${JSON.stringify(fileStats)}`);
});
if (!node_process_1.default.env.TEST_USING_MOCKS) {
ava_1.default.serial.skip('should handle watch', async (t) => {
const sleep = async (ms) => { return new Promise((resolve) => setTimeout(resolve, ms)); };
Expand Down
70 changes: 70 additions & 0 deletions __test__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,14 @@ if (testResolve) {

}

test.serial('should return error when getting file for deleted file handle', async (t) => {
const rootHandle = await getRootHandle();
const fileHandle = await rootHandle.getFileHandle('transient', {create: true});
await rootHandle.removeEntry(fileHandle.name);
const err = await t.throwsAsync(fileHandle.getFile());
t.is(err?.message, 'File "transient" not found');
})

test.serial('should return file for file handle', async (t) => {
const rootHandle = await getRootHandle();
const fileHandle = await rootHandle.getFileHandle('annar');
Expand Down Expand Up @@ -626,6 +634,14 @@ test.serial('should return stream for file', async (t) => {
t.true(y.done);
})

test.serial('should return error when creating writable for deleted file handle', async (t) => {
const rootHandle = await getRootHandle();
const fileHandle = await rootHandle.getFileHandle('fleeting', {create: true});
await rootHandle.removeEntry(fileHandle.name);
const err = await t.throwsAsync(fileHandle.createWritable());
t.is(err?.message, 'File "fleeting" not found');
})

test.serial('should succeed when streaming file larger than max_read_size', async (t) => {
const rootHandle = await getRootHandle();
const fileHandle = await rootHandle.getFileHandle('writable-stream-larger-than-max-read-size', {create: true}) as SmbFileHandle;
Expand Down Expand Up @@ -1292,6 +1308,60 @@ test.serial('should handle getting directories concurrently', async (t) => {
}
})

test.serial('should handle getting stats for a directory', async (t) => {
const rootHandle = await getRootHandle() as any as SmbDirectoryHandle;
const rootStats = await rootHandle.stat();
t.assert(rootStats, 'root dir stats not returned');
if (!process.env.TEST_USING_MOCKS) {
t.assert(rootStats.inode, 'root dir stats do not include inode');
t.not(rootStats.creationTime, 0);
} else {
t.assert(!rootStats.inode, 'root dir stats include indode');
}
t.assert(rootStats.modifiedTime >= rootStats.creationTime, `root dir stats have creation time greater than modified time: ${JSON.stringify(rootStats)}`);
t.assert(rootStats.accessedTime >= rootStats.creationTime, `root dir stats have creation time greater than accessed time: ${JSON.stringify(rootStats)}`);
const dirHandle = await rootHandle.getDirectoryHandle('subdir-for-statting', {create: true}) as any as SmbDirectoryHandle;
const dirStats = await dirHandle.stat();
t.assert(dirStats, 'subdir stats not returned');
if (!process.env.TEST_USING_MOCKS) {
t.assert(dirStats.inode, 'subdir stats do not include inode');
t.not(dirStats.creationTime, 0);
} else {
t.assert(!dirStats.inode, 'subdir stats include indode');
}
t.not(dirStats.creationTime, 0);
t.assert(dirStats.modifiedTime >= dirStats.creationTime, `subdir stats have creation time greater than modified time: ${JSON.stringify(dirStats)}`);
t.assert(dirStats.accessedTime >= dirStats.creationTime, `subdir stats have creation time greater than accessed time: ${JSON.stringify(dirStats)}`);
})

test.serial('should handle getting stats for a file', async (t) => {
const rootHandle = await getRootHandle();
const dirHandle = await rootHandle.getDirectoryHandle('first') as any as SmbDirectoryHandle;
const dirStats = await dirHandle.stat();
t.assert(dirStats, 'dir stats not returned');
if (!process.env.TEST_USING_MOCKS) {
t.assert(dirStats.inode, 'dir stats do not include inode');
t.not(dirStats.creationTime, 0);
} else {
t.assert(!dirStats.inode, 'dir stats include indode');
}
t.not(dirStats.creationTime, 0);
t.assert(dirStats.modifiedTime >= dirStats.creationTime, `dir stats have creation time greater than modified time: ${JSON.stringify(dirStats)}`);
t.assert(dirStats.accessedTime >= dirStats.creationTime, `dir stats have creation time greater than accessed time: ${JSON.stringify(dirStats)}`);
const fileHandle = await rootHandle.getFileHandle('annar') as any as SmbFileHandle;
const fileStats = await fileHandle.stat();
t.assert(fileStats, 'file stats not returned');
if (!process.env.TEST_USING_MOCKS) {
t.assert(fileStats.inode, 'file stats do not include inode');
t.not(fileStats.creationTime, 0);
} else {
t.assert(!fileStats.inode, 'file stats include indode');
}
t.not(fileStats.creationTime, 0);
t.assert(fileStats.modifiedTime >= fileStats.creationTime, `file stats have creation time greater than modified time: ${JSON.stringify(fileStats)}`);
t.assert(fileStats.accessedTime >= fileStats.creationTime, `file stats have creation time greater than accessed time: ${JSON.stringify(fileStats)}`);
})

if (!process.env.TEST_USING_MOCKS) {
test.serial.skip('should handle watch', async (t) => {
const sleep = async (ms: number) => { return new Promise((resolve) => setTimeout(resolve, ms)); };
Expand Down
3 changes: 3 additions & 0 deletions indax.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class SmbHandle {
async requestPermission(perm) {
return this._jsh.requestPermission(perm);
}
async stat() {
return this._jsh.stat();
}
}
exports.SmbHandle = SmbHandle;
class SmbDirectoryHandle extends SmbHandle {
Expand Down
5 changes: 5 additions & 0 deletions indax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ import {
JsSmbGetFileOptions,
JsSmbRemoveOptions,
JsSmbCreateWritableOptions,
JsSmbStat,
JsSmbHandle,
JsSmbDirectoryHandle,
JsSmbFileHandle,
JsSmbWritableFileStream,
} from './index';

type SmbStat = JsSmbStat;
type SmbHandlePermissionDescriptor = JsSmbHandlePermissionDescriptor;
// @ts-ignore
type SmbCreateWritableOptions = FileSystemCreateWritableOptions;
Expand Down Expand Up @@ -60,6 +62,9 @@ export class SmbHandle implements FileSystemHandle {
async requestPermission(perm: SmbHandlePermissionDescriptor): Promise<PermissionState> {
return this._jsh.requestPermission(perm) as Promise<PermissionState>;
}
async stat(): Promise<SmbStat> {
return this._jsh.stat() as Promise<SmbStat>;
}
}

export class SmbDirectoryHandle extends SmbHandle implements FileSystemDirectoryHandle {
Expand Down
8 changes: 8 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ export interface JsSmbRemoveOptions {
export interface JsSmbCreateWritableOptions {
keepExistingData: boolean
}
export interface JsSmbStat {
readonly inode?: bigint
readonly size: bigint
readonly creationTime: bigint
readonly modifiedTime: bigint
readonly accessedTime: bigint
}
export interface JsSmbNotifyChange {
path: string
action: string
Expand All @@ -38,6 +45,7 @@ export declare class JsSmbHandle {
isSameEntry(other: JsSmbHandle): boolean
queryPermission(perm: JsSmbHandlePermissionDescriptor): Promise<string>
requestPermission(perm: JsSmbHandlePermissionDescriptor): Promise<string>
stat(): Promise<JsSmbStat>
}
export declare class JsSmbDirectoryHandle {
[Symbol.asyncIterator]: JsSmbDirectoryHandle['entries']
Expand Down
4 changes: 4 additions & 0 deletions libsmb2-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,12 @@ pub struct DirEntry {
pub atime: u64,
pub mtime: u64,
pub ctime: u64,
pub btime: u64,
pub nlink: u32,
pub atime_nsec: u64,
pub mtime_nsec: u64,
pub ctime_nsec: u64,
pub btime_nsec: u64,
}

#[derive(Clone)]
Expand Down Expand Up @@ -1126,10 +1128,12 @@ impl Iterator for SmbDirectory {
atime: (stat).smb2_atime,
mtime: (stat).smb2_mtime,
ctime: (stat).smb2_ctime,
btime: (stat).smb2_btime,
nlink: (stat).smb2_nlink,
atime_nsec: (stat).smb2_atime_nsec,
mtime_nsec: (stat).smb2_mtime_nsec,
ctime_nsec: (stat).smb2_ctime_nsec,
btime_nsec: (stat).smb2_btime_nsec,
}))
}
}
Expand Down
38 changes: 37 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ use std::{path::Path, sync::{mpsc::{channel, Receiver, Sender}, Arc, RwLock, RwL
mod smb;
use smb::{VFSEntryType, VFSFileNotificationOperation, VFSNotifyChangeCallback, VFSWatchMode, VFS};

use crate::smb::VFSStat;

/*

See https://wicg.github.io/file-system-access/
Expand Down Expand Up @@ -334,6 +336,32 @@ impl Default for JsSmbCreateWritableOptions {
}
}

#[napi(object)]
pub struct JsSmbStat {
#[napi(readonly, ts_type="bigint")]
pub inode: Option<i64>,
#[napi(readonly, ts_type="bigint")]
pub size: i64,
#[napi(readonly, ts_type="bigint")]
pub creation_time: i64,
#[napi(readonly, ts_type="bigint")]
pub modified_time: i64,
#[napi(readonly, ts_type="bigint")]
pub accessed_time: i64
}

impl From<VFSStat> for JsSmbStat {
fn from(value: VFSStat) -> Self {
JsSmbStat {
inode: (value.ino != 0).then_some(value.ino as i64),
size: value.size as i64,
creation_time: ((value.btime * 1_000_000_000) + value.btime_nsec) as i64,
modified_time: ((value.mtime * 1_000_000_000) + value.mtime_nsec) as i64,
accessed_time: ((value.atime * 1_000_000_000) + value.atime_nsec) as i64,
}
}
}

#[derive(Clone)]
#[napi]
pub struct JsSmbHandle {
Expand Down Expand Up @@ -412,6 +440,14 @@ impl JsSmbHandle {
}*/
self.query_permission(perm).await
}

#[napi]
pub async fn stat(&self) -> Result<JsSmbStat> {
let smb = &self.smb;
let my_smb = using_rwlock!(smb);
let smb_stat = my_smb.stat(&self.path)?;
Ok(smb_stat.into())
}
}

impl FromNapiValue for JsSmbHandle {
Expand Down Expand Up @@ -760,7 +796,7 @@ impl JsSmbFileHandle {
let smb = &self.handle.smb;
let my_smb = using_rwlock!(smb);
let smb_stat = my_smb.stat(self.handle.path.as_str())?;
Ok(JsSmbFile{handle: self.handle.clone(), size: smb_stat.size as i64, type_, last_modified: (smb_stat.mtime * 1000) as i64, name: self.name.clone()})
Ok(JsSmbFile{handle: self.handle.clone(), size: smb_stat.size as i64, type_, last_modified: ((smb_stat.mtime * 1000) + (smb_stat.mtime_nsec / 1000000)) as i64, name: self.name.clone()})
}

#[napi]
Expand Down
6 changes: 6 additions & 0 deletions src/smb/libsmb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,11 @@ impl VFS for SMBConnection {
atime: res.smb2_atime,
mtime: res.smb2_mtime,
ctime: res.smb2_ctime,
btime: res.smb2_btime,
atime_nsec: res.smb2_atime_nsec,
mtime_nsec: res.smb2_mtime_nsec,
ctime_nsec: res.smb2_ctime_nsec,
btime_nsec: res.smb2_btime_nsec,
})
}

Expand Down Expand Up @@ -288,10 +290,12 @@ impl Iterator for SMBDirectory2 {
atime: Time{seconds: entry.atime as u32, nseconds: entry.atime_nsec},
mtime: Time{seconds: entry.mtime as u32, nseconds: entry.mtime_nsec},
ctime: Time{seconds: entry.ctime as u32, nseconds: entry.ctime_nsec},
btime: Time{seconds: entry.btime as u32, nseconds: entry.btime_nsec},
nlink: entry.nlink,
atime_nsec: entry.atime_nsec,
mtime_nsec: entry.mtime_nsec,
ctime_nsec: entry.ctime_nsec,
btime_nsec: entry.btime_nsec,
}))
}
}
Expand All @@ -315,9 +319,11 @@ impl VFSFile for SMBFile2 {
atime: res.smb2_atime,
mtime: res.smb2_mtime,
ctime: res.smb2_ctime,
btime: res.smb2_btime,
atime_nsec: res.smb2_atime_nsec,
mtime_nsec: res.smb2_mtime_nsec,
ctime_nsec: res.smb2_ctime_nsec,
btime_nsec: res.smb2_btime_nsec,
})
}

Expand Down
Loading
Loading