diff --git a/example/daemon/www/js/.eslintrc.js b/example/daemon/www/js/.eslintrc.js index b5171ab..ccd1df7 100644 --- a/example/daemon/www/js/.eslintrc.js +++ b/example/daemon/www/js/.eslintrc.js @@ -234,7 +234,7 @@ module.exports = { "quotes": "off", "radix": "error", "require-atomic-updates": "error", - "require-await": "error", + "require-await": "off", "require-jsdoc": "off", "require-unicode-regexp": "off", "rest-spread-spacing": "error", diff --git a/example/daemon/www/js/connection.js b/example/daemon/www/js/connection.js deleted file mode 100644 index 991bb96..0000000 --- a/example/daemon/www/js/connection.js +++ /dev/null @@ -1,36 +0,0 @@ -export class Connection { - - constructor() { - this.ws = null; - this.isConnected = false; - this.onopen = () => {}; - this.onclose = () => {}; - this.onmessage = () => {}; - } - - connectTo(url) { - if (this.ws) { this.close(); } - - this.ws = new WebSocket(url, "fs"); - this.ws.onopen = () => { - this.isConnected = true; - this.onopen(); - }; - this.ws.onclose = () => { - this.isConnected = false; - this.onclose(); - }; - this.ws.onmessage = (message) => { - this.onmessage(message); - }; - } - - send(message) { - this.ws.send(message); - } - - close() { - this.ws.close(); - } - -} \ No newline at end of file diff --git a/example/daemon/www/js/connection_view.js b/example/daemon/www/js/connection_view.js index 219803c..a75955e 100644 --- a/example/daemon/www/js/connection_view.js +++ b/example/daemon/www/js/connection_view.js @@ -1,8 +1,6 @@ export class ConnectionView { - constructor(connection) { - this.connection = connection; - this.connection.onclose = () => { this.onConnectionClosed(); }; - this.connection.onopen = () => { this.onConnectionOpened(); }; + constructor(client) { + this.connection = client; this.element = document.createElement("div"); @@ -28,7 +26,7 @@ export class ConnectionView { this.connection.connectTo(url); } else { - this.connection.close(); + this.connection.disconnect(); } } onConnectionOpened() { diff --git a/example/daemon/www/js/filesystem_handler.js b/example/daemon/www/js/filesystem_handler.js deleted file mode 100644 index 017c047..0000000 --- a/example/daemon/www/js/filesystem_handler.js +++ /dev/null @@ -1,88 +0,0 @@ -/* eslint no-console: "off" */ - -export class FileSystemHandler { - - constructor(filesystem, connection) { - this._fs = filesystem; - this._connection = connection; - this._connection.onmessage = (message) => { - this._onmessage(message); - }; - } - - _onmessage(message) { - - try { - let request = JSON.parse(message.data); - let result = -42; - let response; - - console.log(request); - if (("string" === typeof(request.method)) && - ("number" === typeof(request.id)) && - (request.params)) { - switch(request.method) - { - case "lookup": - { - const parent = request.params[0]; - const name = request.params[1]; - result = this._fs.lookup(parent, name); - } - break; - case "getattr": - { - const inode = request.params[0]; - result = this._fs.getattr(inode); - } - break; - case "readdir": - { - const inode = request.params[0]; - result = this._fs.readdir(inode); - } - break; - case "open": - { - const inode = request.params[0]; - const mode = request.params[1]; - result = this._fs.open(inode, mode); - } - break; - case "close": - { - const inode = request.params[0]; - const handle = request.params[1]; - const mode = request.params[2]; - this._fs.close(inode, handle, mode); - } - break; - case "read": - { - const inode = request.params[0]; - const handle = request.params[1]; - const offset = request.params[2]; - const length = request.params[3]; - result = this._fs.read(inode, handle, offset, length); - } - break; - default: - break; - } - - if ("number" == typeof(request.id)) - { - if ("number" !== typeof(result)) { - response = {result: result, id: request.id}; - } - else { - response = {error: {code: result}, id: request.id}; - } - console.log(response); - this._connection.send(JSON.stringify(response)); - } - } - } - catch (ex) { console.log(ex, message); } - } -} diff --git a/example/daemon/www/js/filesystem.js b/example/daemon/www/js/filesystem_provider.js similarity index 57% rename from example/daemon/www/js/filesystem.js rename to example/daemon/www/js/filesystem_provider.js index 8393026..be9a32b 100644 --- a/example/daemon/www/js/filesystem.js +++ b/example/daemon/www/js/filesystem_provider.js @@ -1,32 +1,31 @@ /* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ -export class FileSystem { - static get GOOD() { return 0; } - static get BAD() { return 1; } - - static get BAD_NOTIMPLEMENTED() { return 2; } - static get BAD_TIMEOUT() { return 3; } - static get BAD_FORMAT() { return 4; } - - static get BAD_NOENTRY() { return 101; } - static get BAD_NOACCESS() { return 102; } - - static get O_ACCMODE() { return 0x003; } - static get O_RDONLY() { return 0x000; } - static get O_WRONLY() { return 0x001; } - static get O_RDWR() { return 0x002; } - static get O_CREAT() { return 0x040; } - static get O_EXCL() { return 0x080; } - static get O_TRUNK() { return 0x200; } - static get O_APPEND() { return 0x400; } +import { BadState } from "./wsfs/bad_state.js"; +import { FileMode } from "./wsfs/file_mode.js"; +import { Provider } from "./wsfs/provider.js"; +export class FileSystemProvider extends Provider { constructor(root) { + super(); + this.root = root; this._inodes = { }; this._walk(this.root, (entry) => { this._inodes[entry.inode] = entry; }); } + setView(view) { + this._view = view; + } + + connected() { + if (this._view) { this._view.onConnectionOpened(); } + } + + disconnected() { + if (this._view) { this._view.onConnectionClosed(); } + } + _walk(node, callback) { callback(node); @@ -39,7 +38,7 @@ export class FileSystem { } - lookup(parent, name) { + async lookup(parent, name) { const parentEntry = this._inodes[parent]; const entry = (parentEntry && parentEntry.entries && parentEntry.entries[name]) || null; if (entry) { @@ -54,12 +53,12 @@ export class FileSystem { }; } else { - return FileSystem.BAD_NOENTRY; + throw new BadState(BadState.NO_ENTRY); } } - getattr(inode) { + async getattr(inode) { let entry = this._inodes[inode]; if (entry) { return { @@ -72,16 +71,15 @@ export class FileSystem { }; } else { - return FileSystem.BAD_NOENTRY; + throw new BadState(BadState.NO_ENTRY); } } - readdir(inode) { - let result = FileSystem.BAD_NOENTRY; + async readdir(inode) { let entry = this._inodes[inode]; if ((entry) && ("dir" === entry.type)) { - result = [ + let result = [ {name: ".", inode: entry.inode}, {name: "..", inode: entry.inode} ]; @@ -90,20 +88,29 @@ export class FileSystem { const inode = subdir[1].inode; result.push({name, inode}); } + + return result; + } + else { + throw new BadState(BadState.NO_ENTRY); } - return result; } - open(inode, mode) { - let result = FileSystem.BAD_NOENTRY; + async open(inode, mode) { let entry = this._inodes[inode]; if (entry.type === "file") { - result = ((mode & FileSystem.O_ACCMODE) === FileSystem.O_RDONLY) ? {handle: 1337} : FileSystem.BAD_NOACCESS; - } - - return result; + if ((mode & FileMode.ACCESS_MODE) === FileMode.READONLY) { + return {handle: 1337}; + } + else { + throw new BadState(BadState.NO_ACCESS); + } + } + else { + throw new BadState(BadState.NO_ENTRY); + } } close(_inode, _handle, _mode) { @@ -111,20 +118,17 @@ export class FileSystem { return true; } - read(inode, handle, offset, length) { - let result = FileSystem.BAD_NOENTRY; + async read(inode, handle, offset, length) { let entry = this._inodes[inode]; if (entry.type === "file") { - let end = Math.min(offset + length, entry.contents.length); - let data = (offset < entry.contents.length) ? entry.contents.substring(offset, end) : ""; - result = { - data: btoa(data), - format: "base64", - count: data.length - }; - } - - return result; + const end = Math.min(offset + length, entry.contents.length); + const data = (offset < entry.contents.length) ? entry.contents.substring(offset, end) : ""; + + return data; + } + else { + throw new BadState(BadState.NO_ENTRY); + } } } \ No newline at end of file diff --git a/example/daemon/www/js/startup.js b/example/daemon/www/js/startup.js index 29ffab6..3118a74 100644 --- a/example/daemon/www/js/startup.js +++ b/example/daemon/www/js/startup.js @@ -1,7 +1,6 @@ -import { Connection } from "./connection.js"; +import { Client } from "./wsfs/client.js"; import { ConnectionView } from "./connection_view.js"; -import { FileSystem } from "./filesystem.js"; -import { FileSystemHandler } from "./filesystem_handler.js"; +import { FileSystemProvider } from "./filesystem_provider.js"; function mode(value) { @@ -9,11 +8,7 @@ function mode(value) { } function startup() { - let connection = new Connection(); - let connectionView = new ConnectionView(connection); - document.getElementById('connection').appendChild(connectionView.element); - - let fs = new FileSystem({ + let provider = new FileSystemProvider({ inode: 1, mode: mode("0755"), type: "dir", @@ -22,8 +17,10 @@ function startup() { "say_hello.sh": { inode: 3, mode: mode("0555"), type: "file", contents: "#!/bin/sh\necho hello\n"} } }); - - let handler = new FileSystemHandler(fs, connection); + let client = new Client(provider); + let connectionView = new ConnectionView(client); + document.getElementById('connection').appendChild(connectionView.element); + provider.setView(connectionView); } window.onload = startup; diff --git a/example/daemon/www/js/wsfs/bad_state.js b/example/daemon/www/js/wsfs/bad_state.js new file mode 100644 index 0000000..fdb05d9 --- /dev/null +++ b/example/daemon/www/js/wsfs/bad_state.js @@ -0,0 +1,15 @@ +export class BadState extends Error { + static get BAD() { return 1; } + + static get NOT_IMPLEMENTED() { return 2; } + static get TIMEOUT() { return 3; } + static get FORMAT() { return 4; } + + static get NO_ENTRY() { return 101; } + static get NO_ACCESS() { return 102; } + + constructor(code) { + super("Bad State"); + this.code = code; + } +} \ No newline at end of file diff --git a/example/daemon/www/js/wsfs/client.js b/example/daemon/www/js/wsfs/client.js new file mode 100644 index 0000000..f6428d6 --- /dev/null +++ b/example/daemon/www/js/wsfs/client.js @@ -0,0 +1,156 @@ +import { BadState } from "./bad_state.js"; + +export class Client { + static get _PROTOCOL() { return "fs"; } + + constructor(provider) { + this._provider = provider; + this._ws = null; + this.isConnected = false; + } + + connectTo(url) { + this.disconnect(); + + this._ws = new WebSocket(url, Client._PROTOCOL); + this._ws.onopen = () => { + this.isConnected = true; + this._provider.connected(); + }; + this._ws.onclose = () => { + this.isConnected = false; + this._provider.disconnected(); + }; + this._ws.onmessage = (message) => { + this._onmessage(message); + }; + } + + disconnect() { + if (this._ws) { + this._ws.close(); + } + } + + _onmessage(message) { + try { + const request = JSON.parse(message.data); + const method = request.method; + const id = request.id; + const params = request.params; + + if ("string" !== typeof(method)) { + throw new Error("parse error: missing field: \"method\""); + } + + if (!params) { + throw new Error("parse error: missing field: \"params\""); + } + + if ("number" === typeof(request.id)) { + this._invoke(method, params, id); + } + else { + this._notify(method, params); + } + } + catch (ex) { + // swallow + } + } + + _invoke(method, params, id) { + this._invokeAsync(method, params).then((result) => { + const response = { result, id }; + this._ws.send(JSON.stringify(response)); + }). + catch((ex) => { + const code = ex.code || BadState.BAD; + const response = {error: {code}, id}; + this._ws.send(JSON.stringify(response)); + }); + + } + + async _invokeAsync(method, params) { + switch(method) + { + case "lookup": + return this._lookup(params); + case "getattr": + return this._getattr(params); + case "readdir": + return this._readdir(params); + case "open": + return this._open(params); + case "read": + return this._read(params); + default: + throw new BadState(BadState.NOT_IMPLEMENTED); + } + } + + _notify(method, params) { + switch(method) { + case 'close': + this._close(params); + break; + default: + throw new Error(`Invalid method: "${method}"`); + } + } + + async _lookup(params) { + const parent = params[0]; + const name = params[1]; + + return this._provider.lookup(parent, name); + } + + async _getattr(params) { + const inode = params[0]; + + return this._provider.getattr(inode); + } + + async _readdir(params) { + const inode = params[0]; + + return this._provider.readdir(inode); + } + + async _open(params) { + const inode = params[0]; + const mode = params[1]; + + return this._provider.open(inode, mode); + } + + _close(params) { + const inode = params[0]; + const handle = params[1]; + const mode = params[2]; + + this._provider.close(inode, handle, mode); + } + + async _read(params) { + const inode = params[0]; + const handle = params[1]; + const offset = params[2]; + const length = params[3]; + + const data = await this._provider.read(inode, handle, offset, length); + + if ("string" === typeof(data)) { + return { + data: btoa(data), + format: "base64", + count: data.length + }; + } + else { + throw new BadState(BadState.BAD); + } + } +} \ No newline at end of file diff --git a/example/daemon/www/js/wsfs/file_mode.js b/example/daemon/www/js/wsfs/file_mode.js new file mode 100644 index 0000000..b81a1ea --- /dev/null +++ b/example/daemon/www/js/wsfs/file_mode.js @@ -0,0 +1,10 @@ +export class FileMode { + static get ACCESS_MODE() { return 0x003; } + static get READONLY() { return 0x000; } + static get WRITEONLY() { return 0x001; } + static get READWRITE() { return 0x002; } + static get CREATE() { return 0x040; } + static get EXCLUSIVE() { return 0x080; } + static get TRUNKATE() { return 0x200; } + static get APPEND() { return 0x400; } +} \ No newline at end of file diff --git a/example/daemon/www/js/wsfs/provider.js b/example/daemon/www/js/wsfs/provider.js new file mode 100644 index 0000000..c9008f9 --- /dev/null +++ b/example/daemon/www/js/wsfs/provider.js @@ -0,0 +1,38 @@ +/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ + +import { BadState } from "./bad_state.js"; + +export class Provider { + + connected() { + // empty + } + + disconnected() { + // empty + } + + async lookup(_parent, _name) { + throw new BadState(BadState.NOT_IMPLEMENTED); + } + + async getattr(_inode) { + throw new BadState(BadState.NOT_IMPLEMENTED); + } + + async readdir(_inode) { + throw new BadState(BadState.NOT_IMPLEMENTED); + } + + async open(_inode, _mode) { + throw new BadState(BadState.NOT_IMPLEMENTED); + } + + close(_inode, _handle, _mode) { + // empty + } + + async read(_inode, _handle, _offset, _length) { + throw new BadState(BadState.NOT_IMPLEMENTED); + } +}