Skip to content

Commit 3703f7e

Browse files
Add in events for thumbnails (#1700)
1 parent 5450c85 commit 3703f7e

File tree

4 files changed

+265
-4
lines changed

4 files changed

+265
-4
lines changed

src/backend/src/filesystem/FSNodeContext.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,8 @@ module.exports = class FSNodeContext {
287287
* @param {*} fsEntryFetcher fetches the filesystem entry
288288
* @void
289289
*/
290-
async fetchEntry(fetch_entry_options = {}) {
290+
async fetchEntry (fetch_entry_options = {}) {
291+
const svc_event = this.services.get('event');
291292
if ( this.fetching !== null ) {
292293
await Context.get('services').get('traceService').spanify('fetching', async () => {
293294
// ???: does this need to be double-checked? I'm not actually sure...
@@ -308,6 +309,9 @@ module.exports = class FSNodeContext {
308309
) {
309310
const promise = this.fetching;
310311
this.fetching = null;
312+
if (this.entry.thumbnail) {
313+
await svc_event.emit("thumbnail.read", this.entry);
314+
}
311315
promise.resolve();
312316
return;
313317
}
@@ -355,6 +359,8 @@ module.exports = class FSNodeContext {
355359

356360
const promise = this.fetching;
357361
this.fetching = null;
362+
363+
await svc_event.emit("thumbnail.read", this.entry);
358364
promise.resolve();
359365
}
360366

src/backend/src/filesystem/hl_operations/hl_write.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class HLWrite extends HLFilesystemOperation {
124124
const { _path } = this.modules;
125125

126126
const fs = context.get('services').get('filesystem');
127+
const svc_event = context.get('services').get('event');
127128

128129
let parent = values.destination_or_parent;
129130
let destination = null;
@@ -336,7 +337,11 @@ class HLWrite extends HLFilesystemOperation {
336337
stream_to_the_void(thumb_file.stream);
337338
return 'thumbnail error: ' + e.message;
338339
}
339-
thumbnail_promise.resolve(thumbnail);
340+
341+
const thumbnailData = {url: thumbnail}
342+
await svc_event.emit('thumbnail.created', thumbnailData); // An extension can modify where this thumbnail is stored
343+
344+
thumbnail_promise.resolve(thumbnailData.url);
340345
})();
341346
if ( reason ) {
342347
console.log('REASON', reason);

src/backend/src/filesystem/ll_operations/ll_rmnode.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class LLRmNode extends LLFilesystemOperation {
2424

2525
const { context } = this;
2626

27-
const svc = context.get('services');
27+
const svc_event = context.get('services').get("event");
2828

2929
// Access Control
3030
{
@@ -36,7 +36,7 @@ class LLRmNode extends LLFilesystemOperation {
3636
throw await svc_acl.get_safe_acl_error(actor, target, 'write');
3737
}
3838
}
39-
39+
await svc_event.emit('fs.remove.node', this.values);
4040
await target.provider.unlink({ context, node: target });
4141
}
4242
}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
/**
2+
* The PermissionUtil class provides utility methods for handling
3+
* permission strings and operations, including splitting, joining,
4+
* escaping, and unescaping permission components. It also includes
5+
* functionality to convert permission reading structures into options.
6+
*/
7+
export class PermissionUtil {
8+
/**
9+
* Unescapes a permission component string, converting escape sequences to their literal characters.
10+
* @param {string} component - The escaped permission component string.
11+
* @returns {string} The unescaped permission component.
12+
*/
13+
static unescape_permission_component(component) {
14+
let unescaped_str = '';
15+
// Constant for unescaped permission component string
16+
const STATE_NORMAL = {};
17+
// Constant for escaping special characters in permission strings
18+
const STATE_ESCAPE = {};
19+
let state = STATE_NORMAL;
20+
const const_escapes = { C: ':' };
21+
for ( let i = 0 ; i < component.length ; i++ ) {
22+
const c = component[i];
23+
if ( state === STATE_NORMAL ) {
24+
if ( c === '\\' ) {
25+
state = STATE_ESCAPE;
26+
} else {
27+
unescaped_str += c;
28+
}
29+
} else if ( state === STATE_ESCAPE ) {
30+
unescaped_str += Object.prototype.hasOwnProperty.call(const_escapes, c)
31+
? const_escapes[c] : c;
32+
state = STATE_NORMAL;
33+
}
34+
}
35+
return unescaped_str;
36+
}
37+
38+
/**
39+
* Escapes special characters in a permission component string for safe joining.
40+
* @param {string} component - The permission component string to escape.
41+
* @returns {string} The escaped permission component.
42+
*/
43+
static escape_permission_component(component) {
44+
let escaped_str = '';
45+
for ( let i = 0 ; i < component.length ; i++ ) {
46+
const c = component[i];
47+
if ( c === ':' ) {
48+
escaped_str += '\\C';
49+
continue;
50+
}
51+
escaped_str += c;
52+
}
53+
return escaped_str;
54+
}
55+
56+
/**
57+
* Splits a permission string into its component parts, unescaping each component.
58+
* @param {string} permission - The permission string to split.
59+
* @returns {string[]} Array of unescaped permission components.
60+
*/
61+
static split(permission) {
62+
return permission
63+
.split(':')
64+
.map(PermissionUtil.unescape_permission_component)
65+
;
66+
}
67+
68+
/**
69+
* Joins permission components into a single permission string, escaping as needed.
70+
* @param {...string} components - The permission components to join.
71+
* @returns {string} The escaped, joined permission string.
72+
*/
73+
static join(...components) {
74+
return components
75+
.map(PermissionUtil.escape_permission_component)
76+
.join(':')
77+
;
78+
}
79+
80+
/**
81+
* Converts a permission reading structure into an array of option objects.
82+
* Recursively traverses the reading tree to collect all options with their associated path and data.
83+
* @param {Array<Object>} reading - The permission reading structure to convert.
84+
* @param {Object} [parameters={}] - Optional parameters for the conversion.
85+
* @param {Array<Object>} [options=[]] - Accumulator for options (used internally for recursion).
86+
* @param {Array<any>} [extras=[]] - Extra data to include (used internally for recursion).
87+
* @param {Array<Object>} [path=[]] - Current path in the reading tree (used internally for recursion).
88+
* @returns {Array<Object>} Array of option objects with path and data.
89+
*/
90+
static reading_to_options(
91+
// actual arguments
92+
reading, parameters = {},
93+
// recursion state
94+
options = [], extras = [], path = [],
95+
) {
96+
const to_path_item = finding => ({
97+
key: finding.key,
98+
holder: finding.holder_username,
99+
data: finding.data,
100+
});
101+
for ( let finding of reading ) {
102+
if ( finding.$ === 'option' ) {
103+
path = [to_path_item(finding), ...path];
104+
options.push({
105+
...finding,
106+
data: [
107+
...(finding.data ? [finding.data] : []),
108+
...extras,
109+
],
110+
path,
111+
});
112+
}
113+
if ( finding.$ === 'path' ) {
114+
if ( finding.has_terminal === false ) continue;
115+
const new_extras = ( finding.data ) ? [
116+
finding.data,
117+
...extras,
118+
] : [];
119+
const new_path = [to_path_item(finding), ...path];
120+
this.reading_to_options(finding.reading, parameters, options, new_extras, new_path);
121+
}
122+
}
123+
return options;
124+
}
125+
}
126+
127+
/**
128+
* Permission rewriters are used to map one set of permission strings to another.
129+
* These are invoked during permission scanning and when permissions are granted or revoked.
130+
*
131+
* For example, Puter's filesystem uses this to map 'fs:/some/path:mode' to
132+
* 'fs:SOME-UUID:mode'.
133+
*
134+
* A rewriter is constructed using the static method PermissionRewriter.create({ matcher, rewriter }).
135+
* The matcher is a function that takes a permission string and returns true if the rewriter should be applied.
136+
* The rewriter is a function that takes a permission string and returns the rewritten permission string.
137+
*/
138+
export class PermissionRewriter {
139+
static create({ id, matcher, rewriter }) {
140+
return new PermissionRewriter({ id, matcher, rewriter });
141+
}
142+
143+
constructor({ id, matcher, rewriter }) {
144+
this.id = id;
145+
this.matcher = matcher;
146+
this.rewriter = rewriter;
147+
}
148+
149+
matches(permission) {
150+
return this.matcher(permission);
151+
}
152+
153+
/**
154+
* Determines if the given permission matches the criteria set for this rewriter.
155+
*
156+
* @param {string} permission - The permission string to check.
157+
* @returns {boolean} - True if the permission matches, false otherwise.
158+
*/
159+
async rewrite(permission) {
160+
return await this.rewriter(permission);
161+
}
162+
}
163+
164+
/**
165+
* Permission implicators are used to manage implicit permissions.
166+
* It defines a method to check if a given permission is implicitly granted to an actor.
167+
*
168+
* For example, Puter's filesystem uses this to grant permission to a file if the specified
169+
* 'actor' is the owner of the file.
170+
*
171+
* An implicator is constructed using the static method PermissionImplicator.create({ matcher, checker }).
172+
* `matcher is a function that takes a permission string and returns true if the implicator should be applied.
173+
* `checker` is a function that takes an actor and a permission string and returns true if the permission is implied.
174+
* The actor and permission are passed to checker({ actor, permission }) as an object.
175+
*/
176+
export class PermissionImplicator {
177+
static create({ id, matcher, checker, ...options }) {
178+
return new PermissionImplicator({ id, matcher, checker, options });
179+
}
180+
181+
constructor({ id, matcher, checker, options }) {
182+
this.id = id;
183+
this.matcher = matcher;
184+
this.checker = checker;
185+
this.options = options;
186+
}
187+
188+
matches(permission) {
189+
return this.matcher(permission);
190+
}
191+
192+
/**
193+
* Check if the permission is implied by this implicator
194+
* @param {Actor} actor
195+
* @param {string} permission
196+
* @returns
197+
*/
198+
/**
199+
* Rewrites a permission string if it matches any registered rewriter.
200+
* @param {string} permission - The permission string to potentially rewrite.
201+
* @returns {Promise<string>} The possibly rewritten permission string.
202+
*/
203+
async check({ actor, permission, recurse }) {
204+
return await this.checker({ actor, permission, recurse });
205+
}
206+
}
207+
208+
/**
209+
* Permission exploders are used to map any permission to a list of permissions
210+
* which are considered to imply the specified permission.
211+
*
212+
* It uses a matcher function to determine if a permission should be exploded
213+
* and an exploder function to perform the expansion.
214+
*
215+
* The exploder is constructed using the static method PermissionExploder.create({ matcher, explode }).
216+
* The `matcher` is a function that takes a permission string and returns true if the exploder should be applied.
217+
* The `explode` is a function that takes an actor and a permission string and returns a list of implied permissions.
218+
* The actor and permission are passed to explode({ actor, permission }) as an object.
219+
*/
220+
export class PermissionExploder {
221+
static create({ id, matcher, exploder }) {
222+
return new PermissionExploder({ id, matcher, exploder });
223+
}
224+
225+
constructor({ id, matcher, exploder }) {
226+
this.id = id;
227+
this.matcher = matcher;
228+
this.exploder = exploder;
229+
}
230+
231+
matches(permission) {
232+
return this.matcher(permission);
233+
}
234+
235+
/**
236+
* Explodes a permission into a set of implied permissions.
237+
*
238+
* This method takes a permission string and an actor object,
239+
* then uses the associated exploder function to derive additional
240+
* permissions that are implied by the given permission.
241+
*
242+
* @param {Object} options - The options object containing:
243+
* @param {Actor} options.actor - The actor requesting the permission explosion.
244+
* @param {string} options.permission - The base permission to be exploded.
245+
* @returns {Promise<Array<string>>} A promise resolving to an array of implied permissions.
246+
*/
247+
async explode({ actor, permission }) {
248+
return await this.exploder({ actor, permission });
249+
}
250+
}

0 commit comments

Comments
 (0)