Skip to content

Commit f273128

Browse files
committed
feat: fs.readdir and tests working with tauri ws
1 parent fdc5276 commit f273128

10 files changed

Lines changed: 480 additions & 261 deletions

File tree

dist/phoenix-fs.js

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const WebSocket = require('ws');
2+
const fs = require("fs/promises");
3+
const path = require("path");
24
const { exec } = require('child_process');
35

46
function getWindowsDrives(callback) {
@@ -82,7 +84,8 @@ const WS_COMMAND = {
8284
RESPONSE: "response",
8385
LARGE_DATA_SOCKET_ANNOUNCE: "largeDataSock",
8486
CONTROL_SOCKET_ANNOUNCE: "controlSock",
85-
GET_WINDOWS_DRIVES: "getWinDrives"
87+
GET_WINDOWS_DRIVES: "getWinDrives",
88+
READ_DIR: "readDir"
8689
};
8790

8891
const LARGE_DATA_THRESHOLD = 2*1024*1024; // 2MB
@@ -120,6 +123,64 @@ function _sendResponse(ws, metadata, dataObjectToSend = null, dataBuffer = new A
120123
socketToUse.send(mergeMetadataAndArrayBuffer(response, dataBuffer));
121124
}
122125

126+
function _getStat(fullPath) {
127+
return new Promise((resolve, reject) => {
128+
fs.stat(fullPath)
129+
.then((stat)=>{
130+
const isOwnerReadOnly = (stat.mode & fs.constants.S_IWUSR) === 0;
131+
resolve({
132+
dev: stat.dev,
133+
isFile: stat.isFile(),
134+
isDirectory: stat.isDirectory(),
135+
isSymbolicLink: stat.isSymbolicLink(),
136+
size: stat.size,
137+
nlink: stat.nlink,
138+
// Unix timestamp MS Numbers
139+
atimeMs: stat.atimeMs,
140+
mtimeMs: stat.mtimeMs,
141+
ctimeMs: stat.ctimeMs,
142+
mode: stat.mode,
143+
readonly: isOwnerReadOnly,
144+
name: path.basename(fullPath)
145+
});
146+
})
147+
.catch(reject);
148+
});
149+
}
150+
151+
function _readDir(ws, metadata) {
152+
const fullPath = metadata.data.path,
153+
options = metadata.data.options || {};
154+
const withFileTypes = options.withFileTypes;
155+
options.withFileTypes = null;
156+
157+
function _reportError(err) {
158+
metadata.error = {
159+
message: err.message || "Cannot readdir "+ fullPath,
160+
code: err.code || "EIO",
161+
stack: err.stack
162+
};
163+
_sendResponse(ws, metadata);
164+
}
165+
166+
fs.readdir(fullPath, options)
167+
.then(contents=>{
168+
if(withFileTypes){
169+
let statPromises = [];
170+
for(let name of contents) {
171+
statPromises.push(_getStat(path.join(fullPath, name)));
172+
}
173+
Promise.all(statPromises)
174+
.then(contentStats =>{
175+
_sendResponse(ws, metadata, {contentStats});
176+
})
177+
.catch(_reportError);
178+
} else {
179+
_sendResponse(ws, metadata, {contents});
180+
}
181+
}).catch(_reportError);
182+
}
183+
123184
function processWSCommand(ws, metadata, dataBuffer) {
124185
try{
125186
switch (metadata.commandCode) {
@@ -132,6 +193,9 @@ function processWSCommand(ws, metadata, dataBuffer) {
132193
_sendResponse(ws, metadata, {drives}, dataBuffer);
133194
});
134195
return;
196+
case WS_COMMAND.READ_DIR:
197+
_readDir(ws, metadata);
198+
return;
135199
case WS_COMMAND.LARGE_DATA_SOCKET_ANNOUNCE:
136200
console.log("Large Data Transfer Socket established, socket Group: ", metadata.socketGroupID);
137201
ws.isLargeData = true;

dist/virtualfs.js

Lines changed: 102 additions & 109 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/virtualfs.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/node-src/phoenix-fs.js

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const WebSocket = require('ws');
2+
const fs = require("fs/promises");
3+
const path = require("path");
24
const { exec } = require('child_process');
35

46
function getWindowsDrives(callback) {
@@ -82,7 +84,8 @@ const WS_COMMAND = {
8284
RESPONSE: "response",
8385
LARGE_DATA_SOCKET_ANNOUNCE: "largeDataSock",
8486
CONTROL_SOCKET_ANNOUNCE: "controlSock",
85-
GET_WINDOWS_DRIVES: "getWinDrives"
87+
GET_WINDOWS_DRIVES: "getWinDrives",
88+
READ_DIR: "readDir"
8689
};
8790

8891
const LARGE_DATA_THRESHOLD = 2*1024*1024; // 2MB
@@ -120,6 +123,64 @@ function _sendResponse(ws, metadata, dataObjectToSend = null, dataBuffer = new A
120123
socketToUse.send(mergeMetadataAndArrayBuffer(response, dataBuffer));
121124
}
122125

126+
function _getStat(fullPath) {
127+
return new Promise((resolve, reject) => {
128+
fs.stat(fullPath)
129+
.then((stat)=>{
130+
const isOwnerReadOnly = (stat.mode & fs.constants.S_IWUSR) === 0;
131+
resolve({
132+
dev: stat.dev,
133+
isFile: stat.isFile(),
134+
isDirectory: stat.isDirectory(),
135+
isSymbolicLink: stat.isSymbolicLink(),
136+
size: stat.size,
137+
nlink: stat.nlink,
138+
// Unix timestamp MS Numbers
139+
atimeMs: stat.atimeMs,
140+
mtimeMs: stat.mtimeMs,
141+
ctimeMs: stat.ctimeMs,
142+
mode: stat.mode,
143+
readonly: isOwnerReadOnly,
144+
name: path.basename(fullPath)
145+
});
146+
})
147+
.catch(reject);
148+
});
149+
}
150+
151+
function _readDir(ws, metadata) {
152+
const fullPath = metadata.data.path,
153+
options = metadata.data.options || {};
154+
const withFileTypes = options.withFileTypes;
155+
options.withFileTypes = null;
156+
157+
function _reportError(err) {
158+
metadata.error = {
159+
message: err.message || "Cannot readdir "+ fullPath,
160+
code: err.code || "EIO",
161+
stack: err.stack
162+
};
163+
_sendResponse(ws, metadata);
164+
}
165+
166+
fs.readdir(fullPath, options)
167+
.then(contents=>{
168+
if(withFileTypes){
169+
let statPromises = [];
170+
for(let name of contents) {
171+
statPromises.push(_getStat(path.join(fullPath, name)));
172+
}
173+
Promise.all(statPromises)
174+
.then(contentStats =>{
175+
_sendResponse(ws, metadata, {contentStats});
176+
})
177+
.catch(_reportError);
178+
} else {
179+
_sendResponse(ws, metadata, {contents});
180+
}
181+
}).catch(_reportError);
182+
}
183+
123184
function processWSCommand(ws, metadata, dataBuffer) {
124185
try{
125186
switch (metadata.commandCode) {
@@ -132,6 +193,9 @@ function processWSCommand(ws, metadata, dataBuffer) {
132193
_sendResponse(ws, metadata, {drives}, dataBuffer);
133194
});
134195
return;
196+
case WS_COMMAND.READ_DIR:
197+
_readDir(ws, metadata);
198+
return;
135199
case WS_COMMAND.LARGE_DATA_SOCKET_ANNOUNCE:
136200
console.log("Large Data Transfer Socket established, socket Group: ", metadata.socketGroupID);
137201
ws.isLargeData = true;

src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
const Constants = {
2525
MOUNT_DEVICE_NAME: 'nativeFsAccess',
2626
TAURI_DEVICE_NAME: 'tauri',
27+
TAURI_WS_DEVICE_NAME: 'tauriWS',
2728
KIND_FILE: 'file',
2829
KIND_DIRECTORY: 'directory',
2930
NODE_TYPE_FILE: 'FILE',

src/fslib_node_ws.js

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ const WS_COMMAND = {
3232
RESPONSE: "response",
3333
LARGE_DATA_SOCKET_ANNOUNCE: "largeDataSock",
3434
CONTROL_SOCKET_ANNOUNCE: "controlSock",
35-
GET_WINDOWS_DRIVES: "getWinDrives"
35+
GET_WINDOWS_DRIVES: "getWinDrives",
36+
READ_DIR: "readDir"
3637
};
3738

3839
// each browser context belongs to a single socket group. So multiple websocket connections can be pooled
@@ -46,22 +47,20 @@ const SOCKET_TYPE_DATA = "data",
4647
const LARGE_DATA_THRESHOLD = 2*1024*1024; // 2MB
4748
const MAX_RECONNECT_BACKOFF_TIME_MS = 1000;
4849

49-
function mapNodeTauriErrorMessage(nodeErrorMessage, path, userMessage= '') {
50-
switch (nodeErrorMessage) {
51-
case '2': return new Errors.ENOENT(userMessage + ` No such File or Directory: ` + path + nodeErrorMessage, path);
52-
case '3': return new Errors.ENOENT(userMessage + ` System cannot find the path specified: ` + path + nodeErrorMessage, path); // windows
53-
case '17': return new Errors.EEXIST(userMessage + ` File exists: ` + path + nodeErrorMessage, path);
54-
case '183': return new Errors.EEXIST(userMessage + ` File exists: ` + path + nodeErrorMessage, path); // windows
55-
case '39': return new Errors.ENOTEMPTY(userMessage + ` Directory not empty: ` + path + nodeErrorMessage, path);
56-
case '20': return new Errors.ENOTDIR(userMessage + ` Not a Directory: ` + path + nodeErrorMessage, path);
57-
case '13': return new Errors.EACCES(userMessage + ` Permission denied: ` + path + nodeErrorMessage, path);
58-
case '21': return new Errors.EISDIR(userMessage + ` Is a directory: ` + path + nodeErrorMessage, path);
59-
case '9': return new Errors.EBADF(userMessage + ` Bad file number: ` + path + nodeErrorMessage, path);
60-
case '30': return new Errors.EROFS(userMessage + ` Read-only file system: ` + path + nodeErrorMessage, path);
61-
case '28': return new Errors.ENOSPC(userMessage + ` No space left on device: ` + path + nodeErrorMessage, path);
62-
case '16': return new Errors.EBUSY(userMessage + ` Device or resource busy: ` + path + nodeErrorMessage, path);
63-
case '22': return new Errors.EINVAL(userMessage + ` Invalid argument: ` + path + nodeErrorMessage, path);
64-
default: return new Errors.EIO(userMessage + ` IO error on path: ` + path + nodeErrorMessage, path);
50+
function mapNodeTauriErrorMessage(nodeError, path, userMessage= '') {
51+
switch (nodeError.code) {
52+
case 'ENOENT': return new Errors.ENOENT(userMessage + ` No such File or Directory: ` + path + nodeError.message, path);
53+
case 'EEXIST': return new Errors.EEXIST(userMessage + ` File exists: ` + path + nodeError.message, path);
54+
case '39': return new Errors.ENOTEMPTY(userMessage + ` Directory not empty: ` + path + nodeError.message, path);
55+
case '20': return new Errors.ENOTDIR(userMessage + ` Not a Directory: ` + path + nodeError.message, path);
56+
case '13': return new Errors.EACCES(userMessage + ` Permission denied: ` + path + nodeError.message, path);
57+
case '21': return new Errors.EISDIR(userMessage + ` Is a directory: ` + path + nodeError.message, path);
58+
case '9': return new Errors.EBADF(userMessage + ` Bad file number: ` + path + nodeError.message, path);
59+
case '30': return new Errors.EROFS(userMessage + ` Read-only file system: ` + path + nodeError.message, path);
60+
case '28': return new Errors.ENOSPC(userMessage + ` No space left on device: ` + path + nodeError.message, path);
61+
case '16': return new Errors.EBUSY(userMessage + ` Device or resource busy: ` + path + nodeError.message, path);
62+
case '22': return new Errors.EINVAL(userMessage + ` Invalid argument: ` + path + nodeError.message, path);
63+
default: return new Errors.EIO(userMessage + ` IO error on path: ` + path + nodeError.message + "\nNode Error stack: " + nodeError.stack, path);
6564
}
6665
}
6766

@@ -111,7 +110,7 @@ function _execCommandInternal(command, commandData, binaryData, resolve, reject)
111110
* @param binaryData {ArrayBuffer}
112111
* @returns {Promise<{metadata: Object, bufferData: ArrayBuffer}>} A promise that resolves with an object containing `metadata` and `bufferData` or rejects with error.
113112
*/
114-
function _execCommand(command, commandData, binaryData) {
113+
function _execCommand(command, commandData = null, binaryData = undefined) {
115114
return new Promise((resolve, reject)=>{
116115
if(!_isSocketOpen(controlSocket) && !_isSocketOpen(dataSocket)) {
117116
_pendingCommandQueue.push({command, commandData, binaryData, resolve, reject});
@@ -290,23 +289,48 @@ function testNodeWsEndpoint(wsEndPoint, echoData, echoBuffer) {
290289
});
291290
}
292291

292+
function _readDirWindowsDrives(path, options, callback) {
293+
_execCommand(WS_COMMAND.GET_WINDOWS_DRIVES)
294+
.then(({metadata})=>{
295+
const drives = metadata.data.drives;
296+
if(!options.withFileTypes) {
297+
callback(null, drives);
298+
return;
299+
}
300+
let entries = [];
301+
for(let drive of drives) {
302+
entries.push(Utils.createDummyStatObject(`${path}/${drive}`, true, Constants.TAURI_WS_DEVICE_NAME));
303+
}
304+
callback(null, entries);
305+
})
306+
.catch((err)=>{
307+
callback(mapNodeTauriErrorMessage(err, path, 'Failed to get drives: '));
308+
});
309+
}
310+
293311
function readdir(path, options, callback) {
294312
if(IS_WINDOWS && path === Constants.TAURI_ROOT){
295-
_execCommand(WS_COMMAND.GET_WINDOWS_DRIVES)
296-
.then(drives=>{
297-
console.log(drives);
298-
// let entries = [];
299-
// for(let drive of drives) {
300-
// entries.push({name: drive});
301-
// }
302-
//_readDirHelper(entries, path, options, callback, true);
303-
// console.log(entries);
304-
})
305-
.catch((err)=>{
306-
callback(mapNodeTauriErrorMessage(err, path, 'Failed to get drives: '));
307-
});
313+
_readDirWindowsDrives(path, options, callback);
308314
return;
309315
}
316+
let platformPath = Utils.getTauriPlatformPath(path);
317+
_execCommand(WS_COMMAND.READ_DIR, {path: platformPath, options})
318+
.then(({metadata})=>{
319+
if(metadata.data.contents){
320+
callback(null, metadata.data.contents);
321+
} else if(metadata.data.contentStats){
322+
let stats = [];
323+
for(let contentStat of metadata.data.contentStats) {
324+
stats.push(Utils.createFromNodeStat(`${path}/${contentStat.name}`,contentStat));
325+
}
326+
callback(null, stats);
327+
} else {
328+
callback(new Errors.EIO("Failed readdir as node ws connector returned empty", path));
329+
}
330+
})
331+
.catch((err)=>{
332+
callback(mapNodeTauriErrorMessage(err, path, 'Failed to read directory: '));
333+
});
310334
}
311335

312336
const NodeTauriFS = {

0 commit comments

Comments
 (0)