1
0
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:
Falk Werner 2019-02-22 00:35:59 +01:00
parent f22a1dfd5e
commit 85ae74a57a
10 changed files with 279 additions and 185 deletions

View File

@ -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",

View File

@ -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();
}
}

View File

@ -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() {

View File

@ -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); }
}
}

View File

@ -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);
}
}
}

View File

@ -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;

View 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;
}
}

View 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);
}
}
}

View 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; }
}

View 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);
}
}