mirror of
https://github.com/HeyPuter/puter
synced 2024-11-14 22:06:00 +00:00
dev: prepare puter.js fs for decorator pattern
- de-coupled xhr callback passing from the interface of the underlying filesystem implementation. - This makes the interface to delegate calls more suitable for use with the decorator pattern. - The decorator pattern will be used to manage the complexity of the caching layer by separating the concerns of different caching methods.
This commit is contained in:
parent
4f8304ec15
commit
c12ae2a923
@ -1,5 +1,5 @@
|
||||
import OS from './modules/OS.js';
|
||||
import FileSystem from './modules/FileSystem/index.js';
|
||||
import { PuterJSFileSystemModule } from './modules/FileSystem/index.js';
|
||||
import Hosting from './modules/Hosting.js';
|
||||
import Email from './modules/Email.js';
|
||||
import Apps from './modules/Apps.js';
|
||||
@ -203,7 +203,7 @@ window.puter = (function() {
|
||||
new OS(this.authToken, this.APIOrigin, this.appID, this.env));
|
||||
// FileSystem
|
||||
this.registerModule('fs',
|
||||
new FileSystem(this.authToken, this.APIOrigin, this.appID, this.env));
|
||||
new PuterJSFileSystemModule(this.authToken, this.APIOrigin, this.appID, this.env));
|
||||
// UI
|
||||
this.registerModule('ui',
|
||||
new UI(this.appInstanceID, this.parentInstanceID, this.appID, this.env, this.util));
|
||||
|
107
src/puter-js/src/modules/FileSystem/definitions.js
Normal file
107
src/puter-js/src/modules/FileSystem/definitions.js
Normal file
@ -0,0 +1,107 @@
|
||||
import * as utils from '../../lib/utils.js';
|
||||
import putility from "@heyputer/putility";
|
||||
import { TeePromise } from "@heyputer/putility/src/libs/promise";
|
||||
import getAbsolutePathForApp from './utils/getAbsolutePathForApp.js';
|
||||
|
||||
export const TFilesystem = 'TFilesystem';
|
||||
|
||||
// TODO: UNUSED (eventually putility will support these definitions)
|
||||
// This is here so that the idea is not forgotten.
|
||||
export const IFilesystem = {
|
||||
methods: {
|
||||
stat: {
|
||||
parameters: {
|
||||
path: {
|
||||
alias: 'uid',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export class PuterAPIFilesystem extends putility.AdvancedBase {
|
||||
constructor ({ api_info }) {
|
||||
super();
|
||||
this.api_info = api_info;
|
||||
}
|
||||
|
||||
static IMPLEMENTS = {
|
||||
[TFilesystem]: {
|
||||
stat: async function (options) {
|
||||
this.ensure_auth_();
|
||||
const tp = new TeePromise();
|
||||
|
||||
const xhr = new utils.initXhr('/stat', this.api_info.APIOrigin, this.api_info.authToken);
|
||||
utils.setupXhrEventHandlers(xhr, undefined, undefined,
|
||||
tp.resolve.bind(tp),
|
||||
tp.reject.bind(tp),
|
||||
);
|
||||
|
||||
let dataToSend = {};
|
||||
if (options.uid !== undefined) {
|
||||
dataToSend.uid = options.uid;
|
||||
} else if (options.path !== undefined) {
|
||||
// If dirPath is not provided or it's not starting with a slash, it means it's a relative path
|
||||
// in that case, we need to prepend the app's root directory to it
|
||||
dataToSend.path = getAbsolutePathForApp(options.path);
|
||||
}
|
||||
|
||||
dataToSend.return_subdomains = options.returnSubdomains;
|
||||
dataToSend.return_permissions = options.returnPermissions;
|
||||
dataToSend.return_versions = options.returnVersions;
|
||||
dataToSend.return_size = options.returnSize;
|
||||
|
||||
xhr.send(JSON.stringify(dataToSend));
|
||||
|
||||
return await tp;
|
||||
},
|
||||
readdir: async function (options) {
|
||||
this.ensure_auth_();
|
||||
const tp = new TeePromise();
|
||||
|
||||
const xhr = new utils.initXhr('/readdir', this.api_info.APIOrigin, this.api_info.authToken);
|
||||
utils.setupXhrEventHandlers(xhr, undefined, undefined,
|
||||
tp.resolve.bind(tp),
|
||||
tp.reject.bind(tp),
|
||||
);
|
||||
|
||||
xhr.send(JSON.stringify({path: getAbsolutePathForApp(options.path)}));
|
||||
|
||||
return await tp;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
ensure_auth_ () {
|
||||
// TODO: remove reference to global 'puter'; get 'env' via context
|
||||
if ( ! this.api_info.authToken && puter.env === 'web' ) {
|
||||
try {
|
||||
this.ui.authenticateWithPuter();
|
||||
} catch (e) {
|
||||
throw new Error('Authentication failed.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ProxyFilesystem extends putility.AdvancedBase {
|
||||
static PROPERTIES = {
|
||||
delegate: () => {},
|
||||
}
|
||||
// TODO: constructor implied by properties
|
||||
constructor ({ delegate }) {
|
||||
super();
|
||||
this.delegate = delegate;
|
||||
}
|
||||
static IMPLEMENTS = {
|
||||
[TFilesystem]: {
|
||||
stat: async function (o) {
|
||||
return this.delegate.stat(o);
|
||||
},
|
||||
readdir: async function (o) {
|
||||
return this.delegate.readdir(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
import io from '../../lib/socket.io/socket.io.esm.min.js';
|
||||
|
||||
// Operations
|
||||
import readdir from "./operations/readdir.js";
|
||||
import stat from "./operations/stat.js";
|
||||
import space from "./operations/space.js";
|
||||
import mkdir from "./operations/mkdir.js";
|
||||
import copy from "./operations/copy.js";
|
||||
@ -15,11 +13,11 @@ import sign from "./operations/sign.js";
|
||||
// Why is this called deleteFSEntry instead of just delete? because delete is
|
||||
// a reserved keyword in javascript
|
||||
import deleteFSEntry from "./operations/deleteFSEntry.js";
|
||||
import { ProxyFilesystem, PuterAPIFilesystem, TFilesystem } from './definitions.js';
|
||||
import { AdvancedBase } from '../../../../putility/index.js';
|
||||
|
||||
class FileSystem{
|
||||
export class PuterJSFileSystemModule extends AdvancedBase {
|
||||
|
||||
readdir = readdir;
|
||||
stat = stat;
|
||||
space = space;
|
||||
mkdir = mkdir;
|
||||
copy = copy;
|
||||
@ -33,6 +31,21 @@ class FileSystem{
|
||||
write = write;
|
||||
sign = sign;
|
||||
|
||||
static NARI_METHODS = {
|
||||
stat: {
|
||||
positional: ['path'],
|
||||
fn (parameters) {
|
||||
return this.filesystem.stat(parameters);
|
||||
}
|
||||
},
|
||||
readdir: {
|
||||
positional: ['path'],
|
||||
fn (parameters) {
|
||||
return this.filesystem.readdir(parameters);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given authentication token, API origin, and app ID,
|
||||
* and connects to the socket.
|
||||
@ -43,13 +56,30 @@ class FileSystem{
|
||||
* @param {string} appID - ID of the app to use.
|
||||
*/
|
||||
constructor (authToken, APIOrigin, appID) {
|
||||
super();
|
||||
this.authToken = authToken;
|
||||
this.APIOrigin = APIOrigin;
|
||||
this.appID = appID;
|
||||
// Connect socket.
|
||||
this.initializeSocket();
|
||||
|
||||
// We need to use `Object.defineProperty` instead of passing
|
||||
// `authToken` and `APIOrigin` because they will change.
|
||||
const api_info = {};
|
||||
Object.defineProperty(api_info, 'authToken', {
|
||||
get: () => this.authToken,
|
||||
});
|
||||
Object.defineProperty(api_info, 'APIOrigin', {
|
||||
get: () => this.APIOrigin,
|
||||
});
|
||||
|
||||
// Construct the decorator chain for the client-side filesystem.
|
||||
let fs = new PuterAPIFilesystem({ api_info }).as(TFilesystem);
|
||||
fs = new ProxyFilesystem({ delegate: fs }).as(TFilesystem);
|
||||
this.filesystem = fs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the socket connection to the server using the current API origin.
|
||||
* If a socket connection already exists, it disconnects it before creating a new one.
|
||||
@ -136,5 +166,3 @@ class FileSystem{
|
||||
this.initializeSocket();
|
||||
}
|
||||
}
|
||||
|
||||
export default FileSystem;
|
@ -26,6 +26,7 @@ class AdvancedBase extends FeatureBase {
|
||||
require('./features/NodeModuleDIFeature'),
|
||||
require('./features/PropertiesFeature'),
|
||||
require('./features/TraitsFeature'),
|
||||
require('./features/NariMethodsFeature'),
|
||||
]
|
||||
}
|
||||
|
||||
|
108
src/putility/src/features/NariMethodsFeature.js
Normal file
108
src/putility/src/features/NariMethodsFeature.js
Normal file
@ -0,0 +1,108 @@
|
||||
module.exports = {
|
||||
readme: `
|
||||
Normalized Asynchronous Request Invocation (NARI) Methods Feature
|
||||
|
||||
This feature allows a class to define "Nari methods", which are methods
|
||||
that support both async/await and callback-style invocation, have
|
||||
positional arguments, and an options argument.
|
||||
|
||||
"the expected interface for methods in puter.js"
|
||||
|
||||
The underlying method will receive parameters as an object, with the
|
||||
positional arguments as keys in the object. The options argument will
|
||||
be merged into the parameters object unless the method spec specifies
|
||||
\`separate_options: true\`.
|
||||
|
||||
Example:
|
||||
|
||||
\`\`\`
|
||||
class MyClass extends AdvancedBase {
|
||||
static NARI_METHODS = {
|
||||
myMethod: {
|
||||
positional: ['param1', 'param2'],
|
||||
fn: ({ param1, param2 }) => {
|
||||
return param1 + param2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new MyClass();
|
||||
const result = instance.myMethod(1, 2); // returns 3
|
||||
\`\`\`
|
||||
|
||||
The method can also be called with options and callbacks:
|
||||
|
||||
\`\`\`
|
||||
instance.myMethod(1, 2, { option1: 'value' }, (result) => {
|
||||
console.log('success', result);
|
||||
}, (error) => {
|
||||
console.error('error', error);
|
||||
});
|
||||
\`\`\`
|
||||
`,
|
||||
install_in_instance: (instance) => {
|
||||
const nariMethodSpecs = instance._get_merged_static_object('NARI_METHODS');
|
||||
|
||||
instance._.nariMethods = {};
|
||||
|
||||
for ( const method_name in nariMethodSpecs ) {
|
||||
const spec = nariMethodSpecs[method_name];
|
||||
const bound_fn = spec.fn.bind(instance);
|
||||
instance._.nariMethods[method_name] = bound_fn;
|
||||
|
||||
instance[method_name] = async (...args) => {
|
||||
const endArgsIndex = spec.positional.length;
|
||||
const posArgs = args.slice(0, endArgsIndex);
|
||||
const endArgs = args.slice(endArgsIndex);
|
||||
|
||||
const parameters = {};
|
||||
const options = {};
|
||||
const callbacks = {};
|
||||
for ( const [index, arg] of posArgs.entries() ) {
|
||||
parameters[spec.positional[index]] = arg;
|
||||
}
|
||||
|
||||
if ( typeof endArgs[0] === 'object' ) {
|
||||
Object.assign(options, endArgs[0]);
|
||||
endArgs.shift();
|
||||
}
|
||||
|
||||
if ( typeof endArgs[0] === 'function' ) {
|
||||
callbacks.success = endArgs[0];
|
||||
endArgs.shift();
|
||||
}
|
||||
|
||||
if ( typeof endArgs[0] === 'function' ) {
|
||||
callbacks.error = endArgs[0];
|
||||
endArgs.shift();
|
||||
}
|
||||
|
||||
if ( spec.separate_options ) {
|
||||
parameters.options = options;
|
||||
} else {
|
||||
Object.assign(parameters, options);
|
||||
}
|
||||
|
||||
console.log('parameters being passed', parameters);
|
||||
|
||||
let retval;
|
||||
try {
|
||||
retval = await bound_fn(parameters);
|
||||
} catch (e) {
|
||||
if ( callbacks.error ) {
|
||||
callbacks.error(e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if ( callbacks.success ) {
|
||||
callbacks.success(retval);
|
||||
}
|
||||
|
||||
return retval;
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user