X-Git-Url: https://git.kengrimes.com/?p=henge%2Fkiak.git;a=blobdiff_plain;f=src%2FstrappFileSystem.js;h=8101311dc5c503bac2eb7b6b0c5cd1971b8772af;hp=6f0b9d48864ffebaac49279043e5732f923a2c01;hb=2cc3f63700e73340ac203f260a884b45fc1de892;hpb=552b28b4fc1ed42e3362c1826acf94c349425b1c diff --git a/src/strappFileSystem.js b/src/strappFileSystem.js index 6f0b9d4..8101311 100644 --- a/src/strappFileSystem.js +++ b/src/strappFileSystem.js @@ -1,224 +1,360 @@ /** - * @file File System Interface - * @desc Provides basic file commands for interacting with Strapp - * file system as well as storage (and backups) of file system - * @author Jordan Lavatai and Ken Grimes - * @version 0.0.1 - * @license AGPL-3.0 - * @copyright Strapp.io - */ - -import localforage from "localforage" -import StrappPeerConnection from "strappPeerConnection" +* @file Strapp File System +* @author Jordan Lavatai, Ken Grimes +* @version 0.0.1 +* @license AGPL-3.0 +* @copyright August 2017 - Ken Grimes, Jordan Lavatai +* @summmary File system implementation for a strapp node +*/ +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) + }) +}) +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 + }) + } + }), + 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) + }) + } + }) + } + }) +} -/* File constructor */ -class File extends Object { - constructor(...props) { - super() - return Object.assign(this, new.target.defaults, ...props) +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: '' + } } - get() { - return this.data + 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) => StrappFileSystem.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)) } - post(postedData) { - this.data += postedData - this.lastModified = new Date() + 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])) + delete StrappFileSystem.loadWaiters + }) + + return StrappFileSystem +})() + +const StrappFile = (() => { + class StrappFile extends Object { + constructor(...props) { + super() + return Object.assign(this, new.target.defaults, ...props) + } + static literal(...props) { + return Object.assign(Object.create(null, StrappFile.defaults), ...props) + } + 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) + } + } + 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) + }) + } + 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(pubKey) { + return Promise.resolve() + } + GET(pubKey) { + return localforage.getItem(this.path) + } + PUT(pubKey, data) { + return localforage.setItem(this.path, 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(pubKey) { + return localforage.removeItem(this.path) + } + OPTIONS(pubKey) { + return new Promise((resolve, reject) => { + this.resolveClientPerms(pubKey).then((perms) => { + }).catch(reject) + }) + } + CONNECT(pubKey) { + return this.GET(pubKey) + } + TRACE(opt) { + } + PATCH(opt) { + } } - put(putData) { - this.data = putData - this.lastModified = new Date() + StrappFile.defaults = { + type: 'strapp/file', + perm: 0xF00, + owner: 'local', + group: '', + changed: StrappFileSystem.bootTime, + created: StrappFileSystem.bootTime, + accessed: StrappFileSystem.bootTime, + files: undefined } - delete() { - this.data = '' - this.lastModified = new Date() + return StrappFile +})() + +const StrappPeerConnection = (() => { + class StrappPeerConnection extends StrappFile { + GET(opts) { + //get metadata (held in filesystem), with owner, usage info, etc + //if unauthed, send message down socket + } + PUT(opts) { + //create w/ sdp, register callback (or pipe), set owner + } + POST(opts) { + //send msg + } + routeMessage(msg) { + //send routing message down socket + //POST(opts.routemessage) + } } - connect() { - + return 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) } - options(publicKey) { - return this.availPermissions(publicKey) + 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) } -} -/* TODO: Continue to flesh this out */ -File.defaults = { - name: '', - data: '', - mode: {}, - size: 0 - //lastModified: new Date()? - //lastAccessed: new Date()? - -} - - -/* Filesystem maintains the current session memory for the strapp instance. Files can be - created and killed from the filesystem without leveraging localForage. Files that are - in the filesystem can be stored to localForage while files that are in localForage can be loaded - to the filesystem. When a Filesystem is first intialized, it attempts to get its strappID it populates itself from localForage and - overwrites any files in its current memory. Files that have restore() as a property (which will be some method needed to - make the file functions e.g. strappPeerConnections will restore() themselves lazily, i.e. when they are needed.*/ - - /* TODO: Should it be possible to create/preserve/destroy a file to both localForage and fileSystem? (same time) */ - /* TODO: Should initFileSystem not overwrite files? */ - -/* These are the default files on all file systems */ -let defaultFiles = [ "..", ".", "accounts", "ice", "log", "run" ] - - -/* TODO: Protect data via closures? */ -const fs = { - /* TODO: What if files are added to file system before init is is called? */ - initFileSystem(){ - this.db = localforage.createInstance({ name: "database" }) - /* Iterate through all files on localforage, adding them to FileSystem - and calling their restore methods */ - this.db.iterate( (value, key, n) => { - /* just btw, return !undefined to exit early */ - this.loadFile(key, true) - - }).catch( (err) => { - console.log(`error: ${err} when iterating through localForage during initFileSystem`) - }) - /* Add the hardcoded default files if they dont already exist */ - /* Restore these files --> need the private/public key*/ - initialFiles.map( (val, idx, array) => { - if (this.fileExists(val)) { - let file = this.getFile(val) - let restoreProp = file['restore'] - if (restoreProp === undefined && typeof restoreFx === 'function') { - //restore file - } - /* Else don't do anything, file exists in FS and doesnt need to be restored */ - } - else { - /* TODO: Remove checking for every file --> although its only for the default files which - will probably be a low number. Still, unnecessary. Could make initialFiles a - Map object with fileType in it and switch(val.fileType) */ - if (val === '..') { - let file = new StrappPeerConnection() - /* Connect with host */ - } - else { - /* Each default file is going to have specific permissions, */ - let filedata = new File() - filedata.name = val - filedata.mode = - this.createFile(val,) - } - } - - }) - - }, - - fileExists(filename) { - return this.files[filename] === undefined ? false : true - }, - - /* Create a file in the file system, if specified overwrites any file that is already there - else does nothing */ - createFile(filename, filedata, overwrite = false){ - filedata.name = filename - if (this.files[filename] === undefined) { - this.files[filename] = filedata - } - else { - if (overwrite) { - console.log(`Overwriting ${filename}`) - this.files[filename] = filedata - } - else { - console.log(`Didn't overwrite file so nothing happened`) - } - } - }, - - /* Get a file from browser session memory */ - /* TODO: Option to get from localForage? */ - getFile(filename) { - return this.files[filename] - }, - - /* Save a file to file system*/ - saveFile(filename, filedata) { - /* TODO: Determine if file to be saved is of saveable nature e.g. SPC's cant really be saved */ - this.db.setItem(filename, filedata) - }, - - /* Delete file from localForage */ - removeFile(filename) { - this.db.removeItem(filename) - }, - - /* Delete a file from file system */ - killFile(filename) { - delete this.files[filename] - - }, - - /* Store file in file system to localForage */ - storeFile(filename) { - this.db.setItem(filename, this.files[filename].filedata) - }, - - /* Load file from localForage to file system */ - loadFile(filename, overwrite = false) { - let filedata = this.db.getItem(filename) - filedata = filedata.restore === undefined ? filedata : filedata.restore() - this.createFile(filename, filedata, overwrite) - }, - - saveFileSystem() { - /* TODO: save all files in filesystem to localforage */ - }, - db: {}, - files: {} - -} - -/* File System API */ - - -/* Load file system from localStorage/indexedDB if strapp.js is running on a host */ -function loadFileSystem(){ -} - -/* Store file system before shutting down if strapp.js is running on a host- */ -function storeFileSystem(){} - - - -/* addFile - adds a created file to a file */ -//@arg fileType - what to set the type property -//@arg fileData - what to set the data property -//@arg filePos - where to create the file - -/* Determine if a publicKey has permissions to execute methods - -/* rm - Delete file */ - -/* ls - display file contents */ - //open file -//return all of file files - -/* cat - display file data */ -//return file data - -/* open - Open file */ - //traverse to file path -/* perm - display file permissions (if you have permissions) */ -/* find - find a file */ -//@arg fileToFind -/* stat - info about a file */ -//@arg path - path to the file -/* exists - determine if a file contains a file */ -//@arg {String} searchedFile - file to be searched -//@arg {String} fileName -//@arg {Number} Depth to look -//@return {Boolean} true if exists, false if doesn't - - + 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 StrappFileSystem +export { StrappFile, StrappPeerConnection, StrappDirectory }