mirror of
https://github.com/falk-werner/webfuse
synced 2024-10-27 20:34:10 +00:00
extracted JavaScript provider API
This commit is contained in:
parent
f22a1dfd5e
commit
85ae74a57a
@ -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",
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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() {
|
||||
|
@ -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); }
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
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;
|
||||
if ((mode & FileMode.ACCESS_MODE) === FileMode.READONLY) {
|
||||
return {handle: 1337};
|
||||
}
|
||||
else {
|
||||
throw new BadState(BadState.NO_ACCESS);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new BadState(BadState.NO_ENTRY);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
const end = Math.min(offset + length, entry.contents.length);
|
||||
const data = (offset < entry.contents.length) ? entry.contents.substring(offset, end) : "";
|
||||
|
||||
return result;
|
||||
return data;
|
||||
}
|
||||
else {
|
||||
throw new BadState(BadState.NO_ENTRY);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
15
example/daemon/www/js/wsfs/bad_state.js
Normal file
15
example/daemon/www/js/wsfs/bad_state.js
Normal file
@ -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;
|
||||
}
|
||||
}
|
156
example/daemon/www/js/wsfs/client.js
Normal file
156
example/daemon/www/js/wsfs/client.js
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
10
example/daemon/www/js/wsfs/file_mode.js
Normal file
10
example/daemon/www/js/wsfs/file_mode.js
Normal file
@ -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; }
|
||||
}
|
38
example/daemon/www/js/wsfs/provider.js
Normal file
38
example/daemon/www/js/wsfs/provider.js
Normal file
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user