X-Git-Url: https://git.kengrimes.com/?p=henge%2Fkiak.git;a=blobdiff_plain;f=src%2FstrappFileSystem.js;fp=src%2FstrappFileSystem.js;h=5b2df7b162ce600b3c712a326156e22af18280bb;hp=00f10507611057a94f6f1149295bca150355a8fa;hb=2b03fdac9b4b79770ab8cd5ded7b0353e179ab4d;hpb=b98d95ba22c7a5b1bcf89fb8ac0a8b159f8c8184 diff --git a/src/strappFileSystem.js b/src/strappFileSystem.js index 00f1050..5b2df7b 100644 --- a/src/strappFileSystem.js +++ b/src/strappFileSystem.js @@ -6,101 +6,239 @@ * @copyright August 2017 - Ken Grimes, Jordan Lavatai * @summmary File system implementation for a strapp node */ -import LocalForage from "localforage" - -const StrappFile = (() => { - const localforage = LocalForage.createInstance({ - driver: [LocalForage.LOCALSTORAGE, - LocalForage.INDEXEDDB, - LocalForage.WEBSQL], - name: 'strapp', - version: 0.1, - storeName: 'strapp' +import strapp from './strapp.js' +import localforage from 'localforage' +localforage.configure({ + name: 'strapp', + version: 0.1 +}) +/** extend localforage with an assign operation */ +localforage.assignItem = (path, data) => new Promise((resolve, reject) => { + localforage.getItem(path, data).then((fdata) => { + localforage.setItem(path, Object.assign(fdata, data)) + .then(resolve).catch(reject) + }).catch((err) => { + localforage.setItem(path, data) + .then(resolve).catch(reject) }) - const authorize = (pubKey, mode, stat) => { - let allowed - if (pubKey === stat.owner) - allowed = (stat.perms >>> 16) & 0xF - else { - let gAccess = false - let uGroups = StrappFile.get(`acct/${pubKey}/groups`).split(' ') - for (let i = 0; i < uGroups.length; i++) { - if (uGroups[i] === stat.group) { - gAccess = true - break +}) +localforage.getOrSetItem = (path, defaults) => new Promise((resolve, reject) => localforage.getItem(path).then(resolve).catch((err) => localforage.setItem(path, defaults).then(resolve).catch(reject))) + + +const strappRuntime = { + run: new StrappDirectory({ + perm: 0xFFF4, + files: { + keyboard: new StrappDevice(), + mouse: new StrappDevice(), + touch: new StrappDevice(), + client: new StrappDirectory({ + perm: 0xFF0, + files: { + create: new StrappFile({ + GET(){}, + POST(){} //TODO: create client action + }) } - } - if (gAccess) - allowed = (stat.perms >>> 8) & 0xF - else - allowed = stat.perms & 0xF - } - switch(mode){ - case 'r+': - case 'rw': - case 'wr': - return (allowed & 0x6) === 0x6 - case 'w': - return (allowed & 0x2) === 0x2 - case 'r': - return (allowed & 0x4) === 0x4 - case 'x': - return (allowed & 0x1) === 0x1 - default: - console.log(`Unknown access mode: ${mode}`) - return false + }), + relay: new StrappDirectory({ + perm: 0x000, + files: { + create: new StrappFile({ + GET(){}, + POST(){} //TODO: create relay + }) + } + }), + spc: new StrappDirectory({ + perm: 0xF00, + files: { + create: new StrappFile({ + GET(){}, + POST(){} //TODO: spc creation (probably only internal) + }) + } + }) } + }) +} + +const StrappFileSystem = (() => { + /* Internal State */ + let rootUser + const rootDir = new StrappDirectory(StrappFile.literal({ + type: 'strapp/directory', + perm: 0xFF4, + path: '.' + })) + + /* Internal Functions */ + const _genKeyPair = () => { + //TODO + return { + pubKey: '', + privKey: '' + } + } + const _request = (location, method, data) => + rootDir.request(location.split('/'), method, rootUser.pubKey, data) + + /* API Definition */ + const StrappFileSystem = { + request: (location, method, data) => + new Promise((resolve, reject) => this.loadWaiters.push([ + location, method, data, resolve, reject + ])), + get: (location) => StrappFileSystem.request(location, 'GET'), + set: (location, data) => StrappFileSystem.request(location, 'PUT', data), + resolveRootUser: (path) => + localforage.getItem(path) + .then((data) => Promise.resolve, + (err) => localforage.setItem(path, _genKeyPair())) + .then((data) => Promise.resolve(rootUser = data)) } + StrappFileSystem.loadWaiters = [] + StrappFileSystem.bootTime = new Date() + + /* Init sequence */ + StrappFileSystem.resolveRootUser('acct/local') + .then((data) => Promise.all( + [ + ['.', { + acct: StrappDirectory.literal() + }], + ['acct', { + local: StrappFile.literal() + }] + ].map((entry) => localforage.getOrSetItem(entry[0],entry[1])) + )) + .then((loadedFiles) => { + rootDir.loadFiles(loadedFiles[0]) + StrappFileSystem.request = _request + StrappFileSystem.loadWaiters + .map((w) => _request(w[0], w[1], w[2], w[3]).then(w[4], w[5])) + }) + + return StrappFileSystem +})() + +const StrappFile = (() => { class StrappFile extends Object { constructor(...props) { super() return Object.assign(this, new.target.defaults, ...props) } - static PermissionDenied() { - return new Promise((resolve, reject) => reject('Permission denied')) - } - static get(path) { - return localforage.getItem(path) + static literal(...props) { + return Object.assign(Object.create(null, StrappFile.defaults), ...props) } - static set(path, data) { - return localforage.setItem(path, data) + static parse(fileLiteral) { + switch(fileLiteral.type) { + case 'application/JSON': + return new StrappJSON(fileLiteral) + case 'strapp/directory': + return new StrappDirectory(fileLiteral) + case 'strapp/file': + default: + return new StrappFile(fileLiteral) + } } - static delete(path) { - return localforage.removeItem(path) + resolveRequest(method, pubKey, data, locStack) { + if (!locStack) + return Promise.reject(400) + if (locStack.length > 0) + return Promise.reject(404) + let reqPerms = 0 + switch(method) { + case 'OPTIONS': + return new Promise((resolve, reject) => { + this.resolveClientPerms.then((perms) => { + let allow = ['OPTIONS'] + if (perms & 0x9) + allow.push('CONNECT') + if (perms & 0x2) + allow.push('DELETE').push('PUT').push('POST') + if (perms & 0x4) + allow.push('GET').push('HEAD') + resolve(allow.join(', ')) + }).catch(reject) + }) + default: + return Promise.reject(405) + case 'PUT': + case 'POST': + case 'DELETE': + reqPerms = 0x2 + break + case 'GET': + case 'HEAD': + reqPerms = 0x4 + break + case 'CONNECT': + reqPerms = 0x9 + break + } + return new Promise((resolve, reject) => { + this.resolveClientPerms.then((perms) => { + if ((reqPerms & perms) === reqPerms) + this[method](pubKey, data).then(resolve).catch(reject) + else + reject(401) + }).catch(reject) + }) } - static routeMessage(lmkid) { - //split lmkid by spaces - //regex sanitize. if '/', MSG. else if ' ', resolve method + resolveClientPerms(pubKey) { + return new Promise((resolve, reject) => { + if (!pubKey || pubKey === '') + resolve(this.perms >>> 12 & 0xF) + else if (pubKey === this.owner) + resolve((this.perms >>> 8) & 0xF) + else + localforage.getItem(`acct/${pubKey}`).then((account) => { + let grpLen = account.groups ? account.groups.length : 0 + let found = false + for (let i = 0; i < grpLen; i++) { + if (account.groups[i] === this.group) { + resolve((this.perms >>> 4) & 0xF) + found = true + break + } + } + if (!found) + resolve(this.perms & 0xF) + }).catch(reject) + }) } - HEAD(opt) { - if (authorize(opt.pubKey, 'r', this.stat)) - return new Promise((resolve, reject) => resolve('')) - else - return StrappFile.PermissionDenied() + HEAD(pubKey) { + return Promise.resolve() } - GET(opt) { - if (authorize(opt.pubKey, 'r', this.stat)) - return StrappFile.get(this.path) - else return StrappFile.PermissionDenied() + GET(pubKey) { + return localforage.getItem(this.path) } - PUT(opt) { - if (authorize(opt.pubKey, 'w', this.stat)) - return StrappFile.set(this.path, opt.data) - else return StrappFile.PermissionDenied() + PUT(pubKey, data) { + return localforage.setItem(this.path, data) } - POST(opt) { - return this.PUT(Object.assign(opt, { data: this.GET(opt) + opt.data })) + POST(pubKey, data) { + return new Promise((resolve, reject) => { + localforage.getItem(this.path) + .then((fData) => + this.setItem(this.path, fData + data) + .then(resolve) + .catch(reject) + ) + .catch(reject) + }) } - DELETE(opt) { - if (authorize(opt.pubKey, 'w', this.stat)) - return StrappFile.delete(this.path) - else return StrappFile.PermissionDenied() + DELETE(pubKey) { + return localforage.removeItem(this.path) } - OPTIONS(opt) { - return this.stat + OPTIONS(pubKey) { + return new Promise((resolve, reject) => { + this.resolveClientPerms(pubKey).then((perms) => { + }).catch(reject) + }) } - CONNECT(opt) { //make channel - return this.GET(opt) + CONNECT(pubKey) { + return this.GET(pubKey) } TRACE(opt) { } @@ -108,15 +246,14 @@ const StrappFile = (() => { } } StrappFile.defaults = { - stat: { - type: 'mime/type', - perm: 0, - owner: 'thisOwnerPubKey', - group: 'groupname', - changed: 'time', - created: 'time', - accessed: 'time - not saved' - } + type: 'strapp/file', + perm: 0xF00, + owner: 'local', + group: '', + changed: StrappFileSystem.bootTime, + created: StrappFileSystem.bootTime, + accessed: StrappFileSystem.bootTime, + files: undefined } return StrappFile })() @@ -133,7 +270,7 @@ const StrappPeerConnection = (() => { POST(opts) { //send msg } - MSG(opts) { + routeMessage(msg) { //send routing message down socket //POST(opts.routemessage) } @@ -142,15 +279,81 @@ const StrappPeerConnection = (() => { })() const StrappDirectory = (() => { + const _traverse_loaded = function(method, pubKey, data, locStack) { + if (locStack[0] in this.files) + return this.files[locStack[0]].resolveRequest(method, pubKey, data, locStack.slice(1)) + return Promise.reject(404) + } + const _traverse_loading = function(method, pubKey, data, locStack) { + if (this.loadWaiters) + return new Promise((resolve, reject) => this.loadWaiters.push( + [method, pubKey, data, locStack, resolve, reject] + )) + return _traverse_loaded(method, pubKey, data, locStack) + } + class StrappDirectory extends StrappFile { + static literal(...props) { + return Object.assign(Object.create(null, StrappDirectory.defaults), ...props) + } + loadFiles(fileData) { + Object.keys(fileData) + .map((key) => (this.files[key] = StrappFile.parse(fileData[key]))) + } + resolveOwnFiles() { + return new Promise((resolve, reject) => { + localforage.getItem(this.path).then((fileData) => { + this.loadFiles(fileData) + resolve() + }).catch(reject) + }) + } + traverse(method, pubKey, data, locStack) { + this.traverse = _traverse_loading + this.loadWaiters = [] + this.resolveOwnFiles().then(() => { + this.traverse = _traverse_loaded + this.loadWaiters + .map((w) => _traverse_loaded(w[0], w[1], w[2], w[3]).then(w[4], w[5])) + delete this.loadWaiters + }) + return _traverse_loading(method, pubKey, data, locStack) + } + request(method, pubKey, data, locStack) { + return new Promise((resolve, reject) => { + super.resolveRequest(method, pubKey, data, locStack) + .then(resolve) + .catch((err) => { + if (err === 404) + return this.traverse(method, pubKey, data, locStack) + return Promise.reject(err) + }) + }) + } CONNECT(opts) { //send routing message to the directory (handle the next part here) } } + StrappDirectory.defaults = { + type: 'strapp/directory' + } return StrappDirectory })() +const StrappDevice = (() => { + class StrappDevice extends StrappFile { + } + return StrappDevice +})() +const StrappJSON = (() => { + class StrappJSON extends StrappFile { + POST(pubKey, data) { + return localforage.assignItem(this.path, data) + } + } + return StrappJSON +})() -export default StrappFile -export { StrappPeerConnection, StrappDirectory } +export default StrappFileSystem +export { StrappFile, StrappPeerConnection, StrappDirectory }