Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 8 additions & 19 deletions extensions/puterfs/PuterFSProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,19 +335,14 @@ export default class PuterFSProvider {
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);
async read ({ node, version_id, range }) {
const location = await node.get('s3:location') ?? {};
const stream = (await storage.create_read_stream(await node.get('uid'), {
// TODO: fs:decouple-s3
bucket: location.bucket,
bucket_region: location.bucket_region,
const stream = this.storageController.read({
uid: await node.get('uid'),
location,
range,
version_id,
key: location.key,
memory_file: node.entry,
...(range ? { range } : {}),
}));
});
return stream;
}

Expand Down Expand Up @@ -491,10 +486,7 @@ export default class PuterFSProvider {
},
});

// const storage = new PuterS3StorageStrategy({ services: svc });
const storage = context.get('storage');
const state_copy = storage.create_copy();
await state_copy.run({
await this.storageController.copy({
src_node: source,
dst_storage: {
key: uuid,
Expand Down Expand Up @@ -995,10 +987,7 @@ export default class PuterFSProvider {

if ( await node.get('has-s3') ) {
tasks.add('remove-from-s3', async () => {
// const storage = new PuterS3StorageStrategy({ services: svc });
const storage = Context.get('storage');
const state_delete = storage.create_delete();
await state_delete.run({
await this.storageController.delete({
node: node,
});
});
Expand Down
40 changes: 37 additions & 3 deletions extensions/puterfs/storage/LocalDiskStorageController.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,45 @@ export default class LocalDiskStorageController {
// @ts-ignore (it's wrong about this)
await writePromise;
}
copy () {
async copy ({ src_node, dst_storage, storage_api }) {
const { progress_tracker } = storage_api;

const src_path = this.#getPath(await src_node.get('uid'));
const dst_path = this.#getPath(dst_storage.key);

await fs.promises.copyFile(src_path, dst_path);

// for now we just copy the file, we don't care about the progress
progress_tracker.set_total(1);
progress_tracker.set(1);
}
delete () {
async delete ({ node }) {
const path = this.#getPath(await node.get('uid'));
await fs.promises.unlink(path);
}
read () {
async read ({ uid, range }) {
const path = this.#getPath(uid);

// Handle range requests for partial content
if ( range ) {
const rangeMatch = range.match(/bytes=(\d+)-(\d*)/);
if ( rangeMatch ) {
const start = parseInt(rangeMatch[1], 10);
const endStr = rangeMatch[2];

const streamOptions = { start };

// If end is specified, set it (fs.createReadStream end is inclusive)
if ( endStr ) {
streamOptions.end = parseInt(endStr, 10);
}

return fs.createReadStream(path, streamOptions);
}
}

// Default: create stream for entire file
return fs.createReadStream(path);
}

#getPath (key) {
Expand Down
66 changes: 64 additions & 2 deletions src/backend/src/filesystem/FilesystemService.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
*/
// TODO: database access can be a service
const { RESOURCE_STATUS_PENDING_CREATE } = require('../modules/puterfs/ResourceService.js');
const { NodePathSelector, NodeUIDSelector, NodeInternalIDSelector, NodeSelector } = require('./node/selectors.js');
const { NodePathSelector, NodeUIDSelector, NodeInternalIDSelector, NodeSelector, NodeChildSelector } = require('./node/selectors.js');
const FSNodeContext = require('./FSNodeContext.js');
const { Context } = require('../util/context.js');
const APIError = require('../api/APIError.js');
const { PermissionUtil, PermissionRewriter, PermissionImplicator, PermissionExploder } = require('../services/auth/permissionUtils.mjs');
const { DB_WRITE } = require('../services/database/consts');
const { UserActorType } = require('../services/auth/Actor');
const { get_user } = require('../helpers');
const { get_user, is_valid_uuid4 } = 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');
Expand Down Expand Up @@ -334,6 +334,68 @@ class FilesystemService extends BaseService {
return fsNode;
}

// #region Simplified API
async read (selector, options = {}) {
const node = this.#coerceToNode(selector);
const ll_read = new LLRead();
const stream = await ll_read.run({
...options,
fsNode: node,
});
return stream;
}

#coerceToNode (stringOrSelectorOrNode, { creatable } = {}) {
if ( stringOrSelectorOrNode instanceof FSNodeContext ) {
if ( creatable ) {
throw new Error('cannot specify a file/directory to create with an FSNodeContext');
}
return stringOrSelectorOrNode;
}

if ( stringOrSelectorOrNode instanceof NodeSelector ) {
if ( creatable && (stringOrSelectorOrNode instanceof NodeUIDSelector) ) {
throw new Error('cannot specify a file/directory to create by UUID');
}
return this.node(stringOrSelectorOrNode);
}

if ( typeof stringOrSelectorOrNode !== 'string' ) {
throw new Error('expected string, NodeSelector, or FSNodeContext');
}
const string = stringOrSelectorOrNode;

if ( string.startsWith('./') ) {
throw new Error('relative paths are not supported here');
}

// This will be coerced by `this.node` to a NodePathSelector
if ( string.startsWith('/') ) {
return this.node(string);
}

// UUID followed by path component
if ( string.includes('/') ) {
const uuidPart = string.slice(0, string.indexOf('/'));
if ( ! is_valid_uuidv4(uuidPart) ) {
throw new Error('expected file/directory identifier to begin with UUID or /');
}

throw new Error('"UUID/then/path" form is not yet supported');
}

if ( ! is_valid_uuid4(string) ) {
throw new Error('string is not a valid file/directory specifier');
}

if ( creatable ) {
throw new Error('cannot specify a file/directory to create by UID');
}

return this.node(new NodeUIDSelector(string));
}
// #endregion

/**
* get_entry() returns a filesystem entry using
* path, uid, or id associated with a filesystem
Expand Down