Skip to content

Commit fdc5276

Browse files
committed
feat: readdir with ws scaffolding
1 parent 61ca0ed commit fdc5276

10 files changed

Lines changed: 268 additions & 91 deletions

File tree

dist/phoenix-fs.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
const WebSocket = require('ws');
2+
const { exec } = require('child_process');
3+
4+
function getWindowsDrives(callback) {
5+
exec('wmic logicaldisk get name', (error, stdout) => {
6+
if (error) {
7+
callback(error, null);
8+
return;
9+
}
10+
11+
// Parse the result
12+
const drives = stdout.split('\n')
13+
.filter(value => /^[A-Z]:/.test(value.trim()))
14+
.map(value => value.trim()[0]);
15+
16+
callback(null, drives);
17+
});
18+
}
219

320
/**
421
*
@@ -63,7 +80,9 @@ function splitMetadataAndBuffer(concatenatedBuffer) {
6380
const WS_COMMAND = {
6481
PING: "ping",
6582
RESPONSE: "response",
66-
LARGE_DATA_SOCKET_ANNOUNCE: "largeDataSock"
83+
LARGE_DATA_SOCKET_ANNOUNCE: "largeDataSock",
84+
CONTROL_SOCKET_ANNOUNCE: "controlSock",
85+
GET_WINDOWS_DRIVES: "getWinDrives"
6786
};
6887

6988
const LARGE_DATA_THRESHOLD = 2*1024*1024; // 2MB
@@ -75,9 +94,11 @@ const largeDataSocketMap = {};
7594

7695
function _getResponse(originalMetadata, data = null) {
7796
return {
97+
originalCommand: originalMetadata.commandCode,
7898
commandCode: WS_COMMAND.RESPONSE,
7999
commandId: originalMetadata.commandId,
80100
socketGroupID: originalMetadata.socketGroupID,
101+
error: originalMetadata.error,
81102
data
82103
}
83104
}
@@ -103,11 +124,25 @@ function processWSCommand(ws, metadata, dataBuffer) {
103124
try{
104125
switch (metadata.commandCode) {
105126
case WS_COMMAND.PING: _sendResponse(ws, metadata, metadata.data, dataBuffer); return;
127+
case WS_COMMAND.GET_WINDOWS_DRIVES:
128+
getWindowsDrives((error, drives)=>{
129+
if(error) {
130+
metadata.error = error.message || "Cannot read windows drive letters.";
131+
}
132+
_sendResponse(ws, metadata, {drives}, dataBuffer);
133+
});
134+
return;
106135
case WS_COMMAND.LARGE_DATA_SOCKET_ANNOUNCE:
136+
console.log("Large Data Transfer Socket established, socket Group: ", metadata.socketGroupID);
107137
ws.isLargeData = true;
108138
ws.LargeDataSocketGroupID = metadata.socketGroupID;
109139
largeDataSocketMap[metadata.socketGroupID] = ws;
110-
_sendResponse(ws, metadata, {}, dataBuffer); return;
140+
_sendResponse(ws, metadata, {}, dataBuffer);
141+
return;
142+
case WS_COMMAND.CONTROL_SOCKET_ANNOUNCE:
143+
console.log("Control Socket established, socket Group:", metadata.socketGroupID);
144+
_sendResponse(ws, metadata, {}, dataBuffer);
145+
return;
111146
default: console.error("unknown command: "+ metadata);
112147
}
113148
} catch (e) {

dist/virtualfs.js

Lines changed: 76 additions & 68 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.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@
4747
"eslint": "8.19.0",
4848
"filer": "1.4.1",
4949
"http-server": "14.1.0",
50+
"iconv-lite": "^0.6.3",
5051
"idb": "7.0.1",
5152
"open-cli": "7.0.1",
5253
"parcel": "2.9.3",
5354
"process": "0.11.10",
5455
"run.env": "1.1.0",
5556
"string_decoder": "^1.3.0",
56-
"workbox-window": "4.2.0",
57-
"iconv-lite": "^0.6.3"
57+
"workbox-window": "4.2.0"
5858
},
5959
"dependencies": {
6060
"ws": "^8.13.0"

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
const WebSocket = require('ws');
2+
const { exec } = require('child_process');
3+
4+
function getWindowsDrives(callback) {
5+
exec('wmic logicaldisk get name', (error, stdout) => {
6+
if (error) {
7+
callback(error, null);
8+
return;
9+
}
10+
11+
// Parse the result
12+
const drives = stdout.split('\n')
13+
.filter(value => /^[A-Z]:/.test(value.trim()))
14+
.map(value => value.trim()[0]);
15+
16+
callback(null, drives);
17+
});
18+
}
219

320
/**
421
*
@@ -63,7 +80,9 @@ function splitMetadataAndBuffer(concatenatedBuffer) {
6380
const WS_COMMAND = {
6481
PING: "ping",
6582
RESPONSE: "response",
66-
LARGE_DATA_SOCKET_ANNOUNCE: "largeDataSock"
83+
LARGE_DATA_SOCKET_ANNOUNCE: "largeDataSock",
84+
CONTROL_SOCKET_ANNOUNCE: "controlSock",
85+
GET_WINDOWS_DRIVES: "getWinDrives"
6786
};
6887

6988
const LARGE_DATA_THRESHOLD = 2*1024*1024; // 2MB
@@ -75,9 +94,11 @@ const largeDataSocketMap = {};
7594

7695
function _getResponse(originalMetadata, data = null) {
7796
return {
97+
originalCommand: originalMetadata.commandCode,
7898
commandCode: WS_COMMAND.RESPONSE,
7999
commandId: originalMetadata.commandId,
80100
socketGroupID: originalMetadata.socketGroupID,
101+
error: originalMetadata.error,
81102
data
82103
}
83104
}
@@ -103,11 +124,25 @@ function processWSCommand(ws, metadata, dataBuffer) {
103124
try{
104125
switch (metadata.commandCode) {
105126
case WS_COMMAND.PING: _sendResponse(ws, metadata, metadata.data, dataBuffer); return;
127+
case WS_COMMAND.GET_WINDOWS_DRIVES:
128+
getWindowsDrives((error, drives)=>{
129+
if(error) {
130+
metadata.error = error.message || "Cannot read windows drive letters.";
131+
}
132+
_sendResponse(ws, metadata, {drives}, dataBuffer);
133+
});
134+
return;
106135
case WS_COMMAND.LARGE_DATA_SOCKET_ANNOUNCE:
136+
console.log("Large Data Transfer Socket established, socket Group: ", metadata.socketGroupID);
107137
ws.isLargeData = true;
108138
ws.LargeDataSocketGroupID = metadata.socketGroupID;
109139
largeDataSocketMap[metadata.socketGroupID] = ws;
110-
_sendResponse(ws, metadata, {}, dataBuffer); return;
140+
_sendResponse(ws, metadata, {}, dataBuffer);
141+
return;
142+
case WS_COMMAND.CONTROL_SOCKET_ANNOUNCE:
143+
console.log("Control Socket established, socket Group:", metadata.socketGroupID);
144+
_sendResponse(ws, metadata, {}, dataBuffer);
145+
return;
111146
default: console.error("unknown command: "+ metadata);
112147
}
113148
} catch (e) {

src/fslib.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,15 @@ const fileSystemLib = {
324324
setNodeWSEndpoint: function (wsEndPoint) {
325325
return NodeTauriFS.setNodeWSEndpoint(wsEndPoint);
326326
},
327+
stopNodeWSEndpoint: function (wsEndPoint) {
328+
return NodeTauriFS.stopNodeWSEndpoint(wsEndPoint);
329+
},
330+
getNodeWSEndpoint: function () {
331+
return NodeTauriFS.getNodeWSEndpoint();
332+
},
333+
forceUseNodeWSEndpoint: function (use) {
334+
return TauriFS.forceUseNodeWSEndpoint(use);
335+
},
327336
BYTE_ARRAY_ENCODING: Constants.BYTE_ARRAY_ENCODING,
328337
MOUNT_POINT_ROOT: Constants.MOUNT_POINT_ROOT,
329338
TAURI_ROOT: Constants.TAURI_ROOT,

src/fslib_node_ws.js

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,49 @@
2323

2424
const {Errors} = require("./errno");
2525
const {Utils} = require("./utils");
26+
const {Constants} = require("./constants");
27+
28+
const IS_WINDOWS = navigator.userAgent.includes('Windows');
2629

2730
const WS_COMMAND = {
2831
PING: "ping",
2932
RESPONSE: "response",
30-
LARGE_DATA_SOCKET_ANNOUNCE: "largeDataSock"
33+
LARGE_DATA_SOCKET_ANNOUNCE: "largeDataSock",
34+
CONTROL_SOCKET_ANNOUNCE: "controlSock",
35+
GET_WINDOWS_DRIVES: "getWinDrives"
3136
};
3237

3338
// each browser context belongs to a single socket group. So multiple websocket connections can be pooled
3439
// to increase throughput. Note that socketGroupID will be different for each web workers/threads in the window as
3540
// they are separate contexts.
3641
const socketGroupID = Math.floor(Math.random() * 4294967296);
37-
let commandIdCounter = 0;
42+
let commandIdCounter = 1; // should be greater than 0!
3843
let wssEndpoint, controlSocket, dataSocket;
3944
const SOCKET_TYPE_DATA = "data",
4045
SOCKET_TYPE_CONTROL = "control";
4146
const LARGE_DATA_THRESHOLD = 2*1024*1024; // 2MB
4247
const MAX_RECONNECT_BACKOFF_TIME_MS = 1000;
4348

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);
65+
}
66+
}
67+
68+
4469
function _getCommand(command, data) {
4570
return {
4671
commandCode: command,
@@ -84,6 +109,7 @@ function _execCommandInternal(command, commandData, binaryData, resolve, reject)
84109
* @param command {string}
85110
* @param commandData {Object}
86111
* @param binaryData {ArrayBuffer}
112+
* @returns {Promise<{metadata: Object, bufferData: ArrayBuffer}>} A promise that resolves with an object containing `metadata` and `bufferData` or rejects with error.
87113
*/
88114
function _execCommand(command, commandData, binaryData) {
89115
return new Promise((resolve, reject)=>{
@@ -109,7 +135,7 @@ function _execPendingCommands() {
109135
function _processMessage(data) {
110136
const {metadata, bufferData} = Utils.splitMetadataAndBuffer(data);
111137
if(!metadata.commandId || !commandIdMap[metadata.commandId]){
112-
throw new Error("CommandID not found in ws response! " + JSON.parse(metadata));
138+
throw new Error("CommandID not found in ws response! " + metadata);
113139
}
114140
const {resolve, reject} = commandIdMap[metadata.commandId];
115141
if(metadata.error){
@@ -148,6 +174,9 @@ async function _establishAndMaintainConnection(socketType, firstConnectCB) {
148174
if(ws.isLargeDataWS){
149175
_execCommand(WS_COMMAND.LARGE_DATA_SOCKET_ANNOUNCE)
150176
.catch(console.error);
177+
} else {
178+
_execCommand(WS_COMMAND.CONTROL_SOCKET_ANNOUNCE)
179+
.catch(console.error);
151180
}
152181
_execPendingCommands();
153182
});
@@ -164,10 +193,13 @@ async function _establishAndMaintainConnection(socketType, firstConnectCB) {
164193
wsClosePromiseResolve();
165194
});
166195
await wsClosePromise;
167-
ws.backoffTime = Math.min(ws.backoffTime * 2, MAX_RECONNECT_BACKOFF_TIME_MS) || 1;
168-
await _wait(ws.backoffTime);
196+
const backoffTime = Math.min(ws.backoffTime * 2, MAX_RECONNECT_BACKOFF_TIME_MS) || 1;
197+
ws.backoffTime = backoffTime;
198+
await _wait(backoffTime);
169199
if(ws.autoReconnect) {
200+
console.log(wssEndpoint);
170201
ws = new WebSocket(wssEndpoint);
202+
ws.backoffTime = backoffTime;
171203
ws.binaryType = 'arraybuffer';
172204
ws.autoReconnect = true;
173205
}
@@ -202,6 +234,18 @@ async function setNodeWSEndpoint(websocketEndpoint) {
202234
});
203235
}
204236

237+
function stopNodeWSEndpoint() {
238+
_silentlyCloseSocket(controlSocket);
239+
controlSocket = null;
240+
_silentlyCloseSocket(dataSocket);
241+
dataSocket = null;
242+
wssEndpoint = null;
243+
}
244+
245+
function getNodeWSEndpoint() {
246+
return wssEndpoint;
247+
}
248+
205249
function testNodeWsEndpoint(wsEndPoint, echoData, echoBuffer) {
206250
return new Promise((resolve, reject)=>{
207251
const ws = new WebSocket(wsEndPoint);
@@ -246,9 +290,31 @@ function testNodeWsEndpoint(wsEndPoint, echoData, echoBuffer) {
246290
});
247291
}
248292

293+
function readdir(path, options, callback) {
294+
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+
});
308+
return;
309+
}
310+
}
311+
249312
const NodeTauriFS = {
250313
testNodeWsEndpoint,
251-
setNodeWSEndpoint
314+
setNodeWSEndpoint,
315+
stopNodeWSEndpoint,
316+
getNodeWSEndpoint,
317+
readdir
252318
};
253319

254320
module.exports = {

src/fslib_tauri.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
const {Constants} = require('./constants');
2525
const {Errors, ERR_CODES} = require("./errno");
2626
const {Utils} = require("./utils");
27+
const {NodeTauriFS} = require("./fslib_node_ws");
2728

2829
const TAURI_PATH_PREFIX = Constants.TAURI_ROOT+ '/';
2930
const IS_WINDOWS = navigator.userAgent.includes('Windows');
31+
let preferNodeWs = false;
3032

3133
/**
3234
* Convert Phoenix virtual file system path to platform-specific paths.
@@ -315,6 +317,10 @@ function readdir(path, options, callback) {
315317
options = {};
316318
}
317319

320+
if(!window.__TAURI__ || preferNodeWs) {
321+
return NodeTauriFS.readdir(path, options, callback);
322+
}
323+
318324
if(IS_WINDOWS && path === Constants.TAURI_ROOT){
319325
window.__TAURI__.invoke('_get_windows_drives')
320326
.then(drives=>{
@@ -627,6 +633,10 @@ function writeFile (path, data, options, callback) {
627633
}
628634
}
629635

636+
function forceUseNodeWSEndpoint(use) {
637+
preferNodeWs = use;
638+
}
639+
630640
const TauriFS = {
631641
isTauriPath,
632642
isTauriSubPath,
@@ -640,7 +650,8 @@ const TauriFS = {
640650
rename,
641651
unlink,
642652
readFile,
643-
writeFile
653+
writeFile,
654+
forceUseNodeWSEndpoint
644655
};
645656

646657
module.exports ={

0 commit comments

Comments
 (0)