Skip to content

Commit 47435bd

Browse files
committed
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.
1 parent 45f9d0c commit 47435bd

File tree

5 files changed

+126
-102
lines changed

5 files changed

+126
-102
lines changed

extensions/puterfs/PuterFSProvider.js

Lines changed: 90 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ const {
100100
export default class PuterFSProvider {
101101
constructor ({ fsEntryController }) {
102102
this.fsEntryController = fsEntryController;
103+
this.name = 'puterfs';
103104
}
104105

105106
// TODO: should this be a static member instead?
@@ -110,6 +111,7 @@ export default class PuterFSProvider {
110111
capabilities.UUID,
111112
capabilities.OPERATION_TRACE,
112113
capabilities.READDIR_UUID_MODE,
114+
capabilities.PUTER_SHORTCUT,
113115

114116
capabilities.COPY_TREE,
115117
capabilities.GET_RECURSIVE_SIZE,
@@ -122,6 +124,89 @@ export default class PuterFSProvider {
122124
]);
123125
}
124126

127+
// #region PuterOnly
128+
async update_thumbnail ({ context, node, thumbnail }) {
129+
const {
130+
actor: inputActor,
131+
} = context.values;
132+
const actor = inputActor ?? Context.get('actor');
133+
134+
context = context ?? Context.get();
135+
const services = context.get('services');
136+
137+
// TODO: this ACL check should not be here, but there's no LL method yet
138+
// and it's possible we will never implement the thumbnail
139+
// capability for any other filesystem type
140+
141+
const svc_acl = services.get('acl');
142+
if ( ! await svc_acl.check(actor, node, 'write') ) {
143+
throw await svc_acl.get_safe_acl_error(actor, node, 'write');
144+
}
145+
146+
const uid = await node.get('uid');
147+
148+
const entryOp = await this.fsEntryController.update(uid, {
149+
thumbnail,
150+
});
151+
152+
(async () => {
153+
await entryOp.awaitDone();
154+
svc_event.emit('fs.write.file', {
155+
node,
156+
context,
157+
});
158+
})();
159+
160+
return node;
161+
}
162+
163+
async puter_shortcut ({ parent, name, user, target }) {
164+
await target.fetchEntry({ thumbnail: true });
165+
166+
const ts = Math.round(Date.now() / 1000);
167+
const uid = uuidv4();
168+
169+
svc_resource.register({
170+
uid,
171+
status: RESOURCE_STATUS_PENDING_CREATE,
172+
});
173+
174+
const raw_fsentry = {
175+
is_shortcut: 1,
176+
shortcut_to: target.mysql_id,
177+
is_dir: target.entry.is_dir,
178+
thumbnail: target.entry.thumbnail,
179+
uuid: uid,
180+
parent_uid: await parent.get('uid'),
181+
path: path_.join(await parent.get('path'), name),
182+
user_id: user.id,
183+
name,
184+
created: ts,
185+
updated: ts,
186+
modified: ts,
187+
immutable: false,
188+
};
189+
190+
const entryOp = await this.fsEntryController.insert(raw_fsentry);
191+
192+
(async () => {
193+
await entryOp.awaitDone();
194+
svc_resource.free(uid);
195+
})();
196+
197+
const node = await svc_fs.node(new NodeUIDSelector(uid));
198+
199+
svc_event.emit('fs.create.shortcut', {
200+
node,
201+
context: Context.get(),
202+
});
203+
204+
return node;
205+
}
206+
// #endregion
207+
208+
// #region Standard FS
209+
125210
/**
126211
* Check if a given node exists.
127212
*
@@ -250,41 +335,6 @@ export default class PuterFSProvider {
250335
return node;
251336
}
252337

253-
async update_thumbnail ({ context, node, thumbnail }) {
254-
const {
255-
actor: inputActor,
256-
} = context.values;
257-
const actor = inputActor ?? Context.get('actor');
258-
259-
context = context ?? Context.get();
260-
const services = context.get('services');
261-
262-
// TODO: this ACL check should not be here, but there's no LL method yet
263-
// and it's possible we will never implement the thumbnail
264-
// capability for any other filesystem type
265-
266-
const svc_acl = services.get('acl');
267-
if ( ! await svc_acl.check(actor, node, 'write') ) {
268-
throw await svc_acl.get_safe_acl_error(actor, node, 'write');
269-
}
270-
271-
const uid = await node.get('uid');
272-
273-
const entryOp = await this.fsEntryController.update(uid, {
274-
thumbnail,
275-
});
276-
277-
(async () => {
278-
await entryOp.awaitDone();
279-
svc_event.emit('fs.write.file', {
280-
node,
281-
context,
282-
});
283-
})();
284-
285-
return node;
286-
}
287-
288338
async read ({ context, node, version_id, range }) {
289339
const svc_mountpoint = context.get('services').get('mountpoint');
290340
const storage = svc_mountpoint.get_storage(this.constructor.name);
@@ -799,6 +849,10 @@ export default class PuterFSProvider {
799849
return rows[0].total_size;
800850
}
801851

852+
// #endregion
853+
854+
// #region internal
855+
802856
/**
803857
* @param {Object} param
804858
* @param {File} param.file: The file to write.
@@ -941,4 +995,5 @@ export default class PuterFSProvider {
941995

942996
await tasks.awaitAll();
943997
}
998+
// #endregion
944999
}

src/backend/src/api/APIError.js

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,16 @@ module.exports = class APIError {
286286
'unresolved_relative_path': {
287287
status: 400,
288288
message: ({ path }) => `Unresolved relative path: ${quot(path)}. ` +
289-
"You may need to specify a full path starting with '/'.",
289+
"You may need to specify a full path starting with '/'.",
290+
},
291+
'missing_filesystem_capability': {
292+
status: 422,
293+
message: ({ action, subjectName, providerName, capability }) => {
294+
return `Cannot perform action ${quot(action)} on ` +
295+
`${quot(subjectName)} because it is inside a filesystem ` +
296+
`of type ${providerName}, which does not implement the ` +
297+
`required capability called ${quot(capability)}.`;
298+
},
290299
},
291300

292301
// Open
@@ -514,11 +523,11 @@ module.exports = class APIError {
514523
status: 400,
515524
message: ({ engine, valid_engines }) => `Invalid engine: ${quot(engine)}. Valid engines are: ${valid_engines.map(quot).join(', ')}.`,
516525
},
517-
526+
518527
// Abuse prevention
519528
'moderation_failed': {
520529
status: 422,
521-
message: `Content moderation failed`,
530+
message: 'Content moderation failed',
522531
},
523532
};
524533

@@ -540,7 +549,7 @@ module.exports = class APIError {
540549
* - an object with a message property to use as the error message
541550
* @returns
542551
*/
543-
static create(status, source, fields = {}) {
552+
static create (status, source, fields = {}) {
544553
// Just the error code
545554
if ( typeof status === 'string' ) {
546555
const code = this.codes[status];
@@ -578,12 +587,12 @@ module.exports = class APIError {
578587
console.error('Invalid APIError source:', source);
579588
return new APIError(500, 'Internal Server Error', null, {});
580589
}
581-
static adapt(err) {
590+
static adapt (err) {
582591
if ( err instanceof APIError ) return err;
583592

584593
return APIError.create('internal_error');
585594
}
586-
constructor(status, message, source, fields = {}) {
595+
constructor (status, message, source, fields = {}) {
587596
this.codes = this.constructor.codes;
588597
this.status = status;
589598
this._message = message;
@@ -595,7 +604,7 @@ module.exports = class APIError {
595604
this._message = this.codes[message].message;
596605
}
597606
}
598-
write(res) {
607+
write (res) {
599608
const message = typeof this.message === 'function'
600609
? this.message(this.fields)
601610
: this.message;
@@ -604,7 +613,7 @@ module.exports = class APIError {
604613
...this.fields,
605614
});
606615
}
607-
serialize() {
616+
serialize () {
608617
return {
609618
...this.fields,
610619
$: 'heyputer:api/APIError',
@@ -613,11 +622,11 @@ module.exports = class APIError {
613622
};
614623
}
615624

616-
querystringize(extra) {
625+
querystringize (extra) {
617626
return new URLSearchParams(this.querystringize_(extra));
618627
}
619628

620-
querystringize_(extra) {
629+
querystringize_ (extra) {
621630
const fields = {};
622631
for ( const k in this.fields ) {
623632
fields[`field_${k}`] = this.fields[k];
@@ -631,14 +640,14 @@ module.exports = class APIError {
631640
};
632641
}
633642

634-
get message() {
643+
get message () {
635644
const message = typeof this._message === 'function'
636645
? this._message(this.fields)
637646
: this._message;
638647
return message;
639648
}
640649

641-
toString() {
650+
toString () {
642651
return `APIError(${this.status}, ${this.message})`;
643652
}
644653
};

src/backend/src/filesystem/FilesystemService.js

Lines changed: 11 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const { get_user } = require('../helpers');
2929
const BaseService = require('../services/BaseService');
3030
const { MANAGE_PERM_PREFIX } = require('../services/auth/permissionConts.mjs');
3131
const { quot } = require('@heyputer/putility/src/libs/string.js');
32+
const fsCapabilities = require('./definitions/capabilities.js');
3233

3334
class FilesystemService extends BaseService {
3435
static MODULES = {
@@ -165,59 +166,18 @@ class FilesystemService extends BaseService {
165166
throw APIError.create('shortcut_to_does_not_exist');
166167
}
167168

168-
await target.fetchEntry({ thumbnail: true });
169-
170-
const { _path, uuidv4 } = this.modules;
171-
const svc_fsEntry = this.services.get('fsEntryService');
172-
const resourceService = this.services.get('resourceService');
173-
174-
const ts = Math.round(Date.now() / 1000);
175-
const uid = uuidv4();
176-
177-
resourceService.register({
178-
uid,
179-
status: RESOURCE_STATUS_PENDING_CREATE,
180-
});
181-
182-
console.log('registered entry');
183-
184-
const raw_fsentry = {
185-
is_shortcut: 1,
186-
shortcut_to: target.mysql_id,
187-
is_dir: target.entry.is_dir,
188-
thumbnail: target.entry.thumbnail,
189-
uuid: uid,
190-
parent_uid: await parent.get('uid'),
191-
path: _path.join(await parent.get('path'), name),
192-
user_id: user.id,
193-
name,
194-
created: ts,
195-
updated: ts,
196-
modified: ts,
197-
immutable: false,
198-
};
199-
200-
this.log.debug('creating fsentry', { fsentry: raw_fsentry });
201-
202-
const entryOp = await svc_fsEntry.insert(raw_fsentry);
203-
204-
console.log('entry op', entryOp);
205-
206-
(async () => {
207-
await entryOp.awaitDone();
208-
this.log.debug('finished creating fsentry', { uid });
209-
resourceService.free(uid);
210-
})();
211-
212-
const node = await this.node(new NodeUIDSelector(uid));
169+
if ( ! parent.provider.get_capabilities().has(fsCapabilities.PUTER_SHORTCUT) ) {
170+
throw APIError.create('missing_filesystem_capability', null, {
171+
action: 'make shortcut',
172+
subjectName: parent.path ?? parent.uid,
173+
providerName: parent.provider.name,
174+
capability: 'PUTER_SHORTCUT',
175+
});
176+
}
213177

214-
const svc_event = this.services.get('event');
215-
svc_event.emit('fs.create.shortcut', {
216-
node,
217-
context: Context.get(),
178+
return await parent.provider.puter_shortcut({
179+
parent, name, user, target,
218180
});
219-
220-
return node;
221181
}
222182

223183
async mklink ({ parent, name, user, target }) {

src/backend/src/filesystem/definitions/capabilities.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const capabilityNames = [
2424
'operation-trace',
2525
'readdir-uuid-mode',
2626
'update-thumbnail',
27+
'puter-shortcut',
2728

2829
// Standard Capabilities
2930
'read',

src/backend/src/filesystem/ll_operations/ll_rmdir.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,11 @@ class LLRmDir extends LLFilesystemOperation {
5858
throw APIError.create('immutable');
5959
}
6060

61-
const svc_fsEntry = svc.get('fsEntryService');
6261
const fs = svc.get('filesystem');
6362

64-
const children = await svc_fsEntry.fast_get_direct_descendants(
65-
await target.get('uid')
66-
);
63+
const children = await target.provider.readdir({
64+
node: target,
65+
});
6766

6867
if ( children.length > 0 && ! recursive && ! ignore_not_empty ) {
6968
throw APIError.create('not_empty');

0 commit comments

Comments
 (0)