mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-06 09:30:49 +00:00
dev(puterfs): move mkshortcut, make ll_rmdir...
...use readdir from the provider instead of calling fast_get_direct_descendants directly on fsEntryService. This change is prerequisite to removing FSEntryService from core.
This commit is contained in:
@@ -100,6 +100,7 @@ const {
|
||||
export default class PuterFSProvider {
|
||||
constructor ({ fsEntryController }) {
|
||||
this.fsEntryController = fsEntryController;
|
||||
this.name = 'puterfs';
|
||||
}
|
||||
|
||||
// TODO: should this be a static member instead?
|
||||
@@ -110,6 +111,7 @@ export default class PuterFSProvider {
|
||||
capabilities.UUID,
|
||||
capabilities.OPERATION_TRACE,
|
||||
capabilities.READDIR_UUID_MODE,
|
||||
capabilities.PUTER_SHORTCUT,
|
||||
|
||||
capabilities.COPY_TREE,
|
||||
capabilities.GET_RECURSIVE_SIZE,
|
||||
@@ -122,6 +124,89 @@ export default class PuterFSProvider {
|
||||
]);
|
||||
}
|
||||
|
||||
// #region PuterOnly
|
||||
async update_thumbnail ({ context, node, thumbnail }) {
|
||||
const {
|
||||
actor: inputActor,
|
||||
} = context.values;
|
||||
const actor = inputActor ?? Context.get('actor');
|
||||
|
||||
context = context ?? Context.get();
|
||||
const services = context.get('services');
|
||||
|
||||
// TODO: this ACL check should not be here, but there's no LL method yet
|
||||
// and it's possible we will never implement the thumbnail
|
||||
// capability for any other filesystem type
|
||||
|
||||
const svc_acl = services.get('acl');
|
||||
if ( ! await svc_acl.check(actor, node, 'write') ) {
|
||||
throw await svc_acl.get_safe_acl_error(actor, node, 'write');
|
||||
}
|
||||
|
||||
const uid = await node.get('uid');
|
||||
|
||||
const entryOp = await this.fsEntryController.update(uid, {
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await entryOp.awaitDone();
|
||||
svc_event.emit('fs.write.file', {
|
||||
node,
|
||||
context,
|
||||
});
|
||||
})();
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
async puter_shortcut ({ parent, name, user, target }) {
|
||||
await target.fetchEntry({ thumbnail: true });
|
||||
|
||||
const ts = Math.round(Date.now() / 1000);
|
||||
const uid = uuidv4();
|
||||
|
||||
svc_resource.register({
|
||||
uid,
|
||||
status: RESOURCE_STATUS_PENDING_CREATE,
|
||||
});
|
||||
|
||||
const raw_fsentry = {
|
||||
is_shortcut: 1,
|
||||
shortcut_to: target.mysql_id,
|
||||
is_dir: target.entry.is_dir,
|
||||
thumbnail: target.entry.thumbnail,
|
||||
uuid: uid,
|
||||
parent_uid: await parent.get('uid'),
|
||||
path: path_.join(await parent.get('path'), name),
|
||||
user_id: user.id,
|
||||
name,
|
||||
created: ts,
|
||||
updated: ts,
|
||||
modified: ts,
|
||||
immutable: false,
|
||||
};
|
||||
|
||||
const entryOp = await this.fsEntryController.insert(raw_fsentry);
|
||||
|
||||
(async () => {
|
||||
await entryOp.awaitDone();
|
||||
svc_resource.free(uid);
|
||||
})();
|
||||
|
||||
const node = await svc_fs.node(new NodeUIDSelector(uid));
|
||||
|
||||
svc_event.emit('fs.create.shortcut', {
|
||||
node,
|
||||
context: Context.get(),
|
||||
});
|
||||
|
||||
return node;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Standard FS
|
||||
|
||||
/**
|
||||
* Check if a given node exists.
|
||||
*
|
||||
@@ -250,41 +335,6 @@ export default class PuterFSProvider {
|
||||
return node;
|
||||
}
|
||||
|
||||
async update_thumbnail ({ context, node, thumbnail }) {
|
||||
const {
|
||||
actor: inputActor,
|
||||
} = context.values;
|
||||
const actor = inputActor ?? Context.get('actor');
|
||||
|
||||
context = context ?? Context.get();
|
||||
const services = context.get('services');
|
||||
|
||||
// TODO: this ACL check should not be here, but there's no LL method yet
|
||||
// and it's possible we will never implement the thumbnail
|
||||
// capability for any other filesystem type
|
||||
|
||||
const svc_acl = services.get('acl');
|
||||
if ( ! await svc_acl.check(actor, node, 'write') ) {
|
||||
throw await svc_acl.get_safe_acl_error(actor, node, 'write');
|
||||
}
|
||||
|
||||
const uid = await node.get('uid');
|
||||
|
||||
const entryOp = await this.fsEntryController.update(uid, {
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await entryOp.awaitDone();
|
||||
svc_event.emit('fs.write.file', {
|
||||
node,
|
||||
context,
|
||||
});
|
||||
})();
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
async read ({ context, node, version_id, range }) {
|
||||
const svc_mountpoint = context.get('services').get('mountpoint');
|
||||
const storage = svc_mountpoint.get_storage(this.constructor.name);
|
||||
@@ -799,6 +849,10 @@ export default class PuterFSProvider {
|
||||
return rows[0].total_size;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region internal
|
||||
|
||||
/**
|
||||
* @param {Object} param
|
||||
* @param {File} param.file: The file to write.
|
||||
@@ -941,4 +995,5 @@ export default class PuterFSProvider {
|
||||
|
||||
await tasks.awaitAll();
|
||||
}
|
||||
// #endregion
|
||||
}
|
||||
|
||||
@@ -286,7 +286,16 @@ module.exports = class APIError {
|
||||
'unresolved_relative_path': {
|
||||
status: 400,
|
||||
message: ({ path }) => `Unresolved relative path: ${quot(path)}. ` +
|
||||
"You may need to specify a full path starting with '/'.",
|
||||
"You may need to specify a full path starting with '/'.",
|
||||
},
|
||||
'missing_filesystem_capability': {
|
||||
status: 422,
|
||||
message: ({ action, subjectName, providerName, capability }) => {
|
||||
return `Cannot perform action ${quot(action)} on ` +
|
||||
`${quot(subjectName)} because it is inside a filesystem ` +
|
||||
`of type ${providerName}, which does not implement the ` +
|
||||
`required capability called ${quot(capability)}.`;
|
||||
},
|
||||
},
|
||||
|
||||
// Open
|
||||
@@ -514,11 +523,11 @@ module.exports = class APIError {
|
||||
status: 400,
|
||||
message: ({ engine, valid_engines }) => `Invalid engine: ${quot(engine)}. Valid engines are: ${valid_engines.map(quot).join(', ')}.`,
|
||||
},
|
||||
|
||||
|
||||
// Abuse prevention
|
||||
'moderation_failed': {
|
||||
status: 422,
|
||||
message: `Content moderation failed`,
|
||||
message: 'Content moderation failed',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -540,7 +549,7 @@ module.exports = class APIError {
|
||||
* - an object with a message property to use as the error message
|
||||
* @returns
|
||||
*/
|
||||
static create(status, source, fields = {}) {
|
||||
static create (status, source, fields = {}) {
|
||||
// Just the error code
|
||||
if ( typeof status === 'string' ) {
|
||||
const code = this.codes[status];
|
||||
@@ -578,12 +587,12 @@ module.exports = class APIError {
|
||||
console.error('Invalid APIError source:', source);
|
||||
return new APIError(500, 'Internal Server Error', null, {});
|
||||
}
|
||||
static adapt(err) {
|
||||
static adapt (err) {
|
||||
if ( err instanceof APIError ) return err;
|
||||
|
||||
return APIError.create('internal_error');
|
||||
}
|
||||
constructor(status, message, source, fields = {}) {
|
||||
constructor (status, message, source, fields = {}) {
|
||||
this.codes = this.constructor.codes;
|
||||
this.status = status;
|
||||
this._message = message;
|
||||
@@ -595,7 +604,7 @@ module.exports = class APIError {
|
||||
this._message = this.codes[message].message;
|
||||
}
|
||||
}
|
||||
write(res) {
|
||||
write (res) {
|
||||
const message = typeof this.message === 'function'
|
||||
? this.message(this.fields)
|
||||
: this.message;
|
||||
@@ -604,7 +613,7 @@ module.exports = class APIError {
|
||||
...this.fields,
|
||||
});
|
||||
}
|
||||
serialize() {
|
||||
serialize () {
|
||||
return {
|
||||
...this.fields,
|
||||
$: 'heyputer:api/APIError',
|
||||
@@ -613,11 +622,11 @@ module.exports = class APIError {
|
||||
};
|
||||
}
|
||||
|
||||
querystringize(extra) {
|
||||
querystringize (extra) {
|
||||
return new URLSearchParams(this.querystringize_(extra));
|
||||
}
|
||||
|
||||
querystringize_(extra) {
|
||||
querystringize_ (extra) {
|
||||
const fields = {};
|
||||
for ( const k in this.fields ) {
|
||||
fields[`field_${k}`] = this.fields[k];
|
||||
@@ -631,14 +640,14 @@ module.exports = class APIError {
|
||||
};
|
||||
}
|
||||
|
||||
get message() {
|
||||
get message () {
|
||||
const message = typeof this._message === 'function'
|
||||
? this._message(this.fields)
|
||||
: this._message;
|
||||
return message;
|
||||
}
|
||||
|
||||
toString() {
|
||||
toString () {
|
||||
return `APIError(${this.status}, ${this.message})`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ const { get_user } = require('../helpers');
|
||||
const BaseService = require('../services/BaseService');
|
||||
const { MANAGE_PERM_PREFIX } = require('../services/auth/permissionConts.mjs');
|
||||
const { quot } = require('@heyputer/putility/src/libs/string.js');
|
||||
const fsCapabilities = require('./definitions/capabilities.js');
|
||||
|
||||
class FilesystemService extends BaseService {
|
||||
static MODULES = {
|
||||
@@ -165,59 +166,18 @@ class FilesystemService extends BaseService {
|
||||
throw APIError.create('shortcut_to_does_not_exist');
|
||||
}
|
||||
|
||||
await target.fetchEntry({ thumbnail: true });
|
||||
if ( ! parent.provider.get_capabilities().has(fsCapabilities.PUTER_SHORTCUT) ) {
|
||||
throw APIError.create('missing_filesystem_capability', null, {
|
||||
action: 'make shortcut',
|
||||
subjectName: parent.path ?? parent.uid,
|
||||
providerName: parent.provider.name,
|
||||
capability: 'PUTER_SHORTCUT',
|
||||
});
|
||||
}
|
||||
|
||||
const { _path, uuidv4 } = this.modules;
|
||||
const svc_fsEntry = this.services.get('fsEntryService');
|
||||
const resourceService = this.services.get('resourceService');
|
||||
|
||||
const ts = Math.round(Date.now() / 1000);
|
||||
const uid = uuidv4();
|
||||
|
||||
resourceService.register({
|
||||
uid,
|
||||
status: RESOURCE_STATUS_PENDING_CREATE,
|
||||
return await parent.provider.puter_shortcut({
|
||||
parent, name, user, target,
|
||||
});
|
||||
|
||||
console.log('registered entry');
|
||||
|
||||
const raw_fsentry = {
|
||||
is_shortcut: 1,
|
||||
shortcut_to: target.mysql_id,
|
||||
is_dir: target.entry.is_dir,
|
||||
thumbnail: target.entry.thumbnail,
|
||||
uuid: uid,
|
||||
parent_uid: await parent.get('uid'),
|
||||
path: _path.join(await parent.get('path'), name),
|
||||
user_id: user.id,
|
||||
name,
|
||||
created: ts,
|
||||
updated: ts,
|
||||
modified: ts,
|
||||
immutable: false,
|
||||
};
|
||||
|
||||
this.log.debug('creating fsentry', { fsentry: raw_fsentry });
|
||||
|
||||
const entryOp = await svc_fsEntry.insert(raw_fsentry);
|
||||
|
||||
console.log('entry op', entryOp);
|
||||
|
||||
(async () => {
|
||||
await entryOp.awaitDone();
|
||||
this.log.debug('finished creating fsentry', { uid });
|
||||
resourceService.free(uid);
|
||||
})();
|
||||
|
||||
const node = await this.node(new NodeUIDSelector(uid));
|
||||
|
||||
const svc_event = this.services.get('event');
|
||||
svc_event.emit('fs.create.shortcut', {
|
||||
node,
|
||||
context: Context.get(),
|
||||
});
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
async mklink ({ parent, name, user, target }) {
|
||||
|
||||
@@ -24,6 +24,7 @@ const capabilityNames = [
|
||||
'operation-trace',
|
||||
'readdir-uuid-mode',
|
||||
'update-thumbnail',
|
||||
'puter-shortcut',
|
||||
|
||||
// Standard Capabilities
|
||||
'read',
|
||||
|
||||
@@ -58,12 +58,11 @@ class LLRmDir extends LLFilesystemOperation {
|
||||
throw APIError.create('immutable');
|
||||
}
|
||||
|
||||
const svc_fsEntry = svc.get('fsEntryService');
|
||||
const fs = svc.get('filesystem');
|
||||
|
||||
const children = await svc_fsEntry.fast_get_direct_descendants(
|
||||
await target.get('uid')
|
||||
);
|
||||
const children = await target.provider.readdir({
|
||||
node: target,
|
||||
});
|
||||
|
||||
if ( children.length > 0 && ! recursive && ! ignore_not_empty ) {
|
||||
throw APIError.create('not_empty');
|
||||
|
||||
Reference in New Issue
Block a user