mirror of
https://github.com/HeyPuter/puter.git
synced 2026-02-20 13:39:10 -06:00
dev: cleanup filesystem utilities
This commit is contained in:
@@ -87,7 +87,7 @@ module.exports = class FSNodeContext {
|
||||
this.fs = fs;
|
||||
|
||||
// Decorate all fetch methods with otel span
|
||||
// TODO: language tool for traits; this is a trait
|
||||
// TODO: Apply method decorators using a putility class feature
|
||||
const fetch_methods = [
|
||||
'fetchEntry',
|
||||
'fetchPath',
|
||||
@@ -271,8 +271,6 @@ module.exports = class FSNodeContext {
|
||||
resourceService,
|
||||
} = Context.get('services').values;
|
||||
|
||||
// await this.fs.resourceService
|
||||
// .waitForResource(this.selector);
|
||||
if ( fetch_entry_options.tracer == null ) {
|
||||
fetch_entry_options.tracer = traceService.tracer;
|
||||
}
|
||||
@@ -288,11 +286,7 @@ module.exports = class FSNodeContext {
|
||||
await new Promise (rslv => {
|
||||
const detachables = new MultiDetachable();
|
||||
|
||||
let resolved = false;
|
||||
|
||||
const callback = (resolver) => {
|
||||
// NOTE: commented out for now because it's too verbose
|
||||
resolved = true;
|
||||
detachables.detach();
|
||||
rslv();
|
||||
}
|
||||
@@ -499,8 +493,7 @@ module.exports = class FSNodeContext {
|
||||
[this.entry.id]
|
||||
);
|
||||
const versions_tidy = [];
|
||||
for (let index = 0; index < versions.length; index++) {
|
||||
const version = versions[index];
|
||||
for ( const version of versions ) {
|
||||
let username = version.user_id ? (await get_user({id: version.user_id})).username : null;
|
||||
versions_tidy.push({
|
||||
id: version.version_id,
|
||||
@@ -551,14 +544,6 @@ module.exports = class FSNodeContext {
|
||||
this.entry.is_empty = await is_empty(this.uid);
|
||||
}
|
||||
|
||||
// TODO: this is currently not called anywhere; for now it
|
||||
// will never be fetched since sharing is not a priority.
|
||||
async fetchIsShared () {
|
||||
if ( ! this.mysql_id ) return;
|
||||
|
||||
this.entry.is_shared = await is_shared_with_anyone(this.mysql_id);
|
||||
}
|
||||
|
||||
async fetchAll(fsEntryFetcher, user, force) {
|
||||
await this.fetchEntry({ thumbnail: true });
|
||||
await this.fetchSubdomains(user);
|
||||
@@ -610,7 +595,6 @@ module.exports = class FSNodeContext {
|
||||
);
|
||||
}
|
||||
if ( ! this.path ) {
|
||||
// console.log('PATH WAS NOT ON ENTRY', this);
|
||||
await this.fetchPath();
|
||||
}
|
||||
if ( ! this.path ) {
|
||||
|
||||
@@ -1,324 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const PerformanceMonitor = require('../monitor/PerformanceMonitor');
|
||||
|
||||
const FSNodeContext = require('./FSNodeContext');
|
||||
const FSAccessContext = require('./FSAccessContext');
|
||||
const { Context } = require('../util/context');
|
||||
|
||||
/**
|
||||
* FSOperationContext represents a single operation on the filesystem.
|
||||
*
|
||||
* FSOperationContext is used to record events such as side-effects
|
||||
* which occur during a high-level filesystem operation. It is also
|
||||
* responsible for generating a client-safe result which describes
|
||||
* the operation.
|
||||
*/
|
||||
module.exports = class FSOperationContext {
|
||||
// TODO: rename this.fs to this.access
|
||||
constructor (op_name, context, options) {
|
||||
// migration: fs:create-service
|
||||
// TODO: rename this.fs to this.access
|
||||
// NOTE: the 2nd parameter of this constructor
|
||||
// was called `fs` and was expected to be FSAccessContext.
|
||||
// Now it should be a context object holding the services
|
||||
// container. context.access is the FSAccessContext.
|
||||
if ( context instanceof FSAccessContext ) {
|
||||
this.fs = context;
|
||||
} else if ( context ) {
|
||||
this.context = context;
|
||||
this.fs = context.access;
|
||||
} else {
|
||||
const x = Context.get();
|
||||
this.fs = {};
|
||||
this.fs.traceService = x.get('services').get('traceService');
|
||||
}
|
||||
|
||||
this.name = op_name;
|
||||
this.events = [];
|
||||
this.parent_dirs_created = [];
|
||||
this.created = [];
|
||||
this.fields = {};
|
||||
this.safeFields = {};
|
||||
|
||||
this.valueListeners_ = {};
|
||||
this.valueFactories_ = {};
|
||||
this.values_ = {};
|
||||
this.rejections_ = {};
|
||||
|
||||
this.tasks_ = [];
|
||||
|
||||
this.currentCheckpoint_ = 'checkpoint not set';
|
||||
|
||||
if ( options.parent_operation ) {
|
||||
this.parent = options.parent_operation;
|
||||
}
|
||||
|
||||
this.donePromise = new Promise((resolve, reject) => {
|
||||
this.doneResolve = resolve;
|
||||
this.doneReject = reject;
|
||||
});
|
||||
|
||||
// migration: arch:trace-service:move-outta-fs
|
||||
if ( this.fs.traceService ) {
|
||||
// Set 'span_' to current active span
|
||||
const { context, trace } = require('@opentelemetry/api');
|
||||
this.span_ = trace.getSpan(context.active());
|
||||
}
|
||||
|
||||
this.monitor = PerformanceMonitor.createContext(`fs.${op_name}`);
|
||||
}
|
||||
|
||||
checkpoint (label) {
|
||||
this.currentCheckpoint_ = label;
|
||||
}
|
||||
|
||||
async addTask (name, fn) {
|
||||
const task = {
|
||||
name,
|
||||
operations: [],
|
||||
promise: Promise.resolve(),
|
||||
};
|
||||
|
||||
const taskContext = {
|
||||
registerOperation: op => {
|
||||
task.operations.push(op);
|
||||
task.promise = task.promise.then(() => op.awaitDone());
|
||||
}
|
||||
};
|
||||
|
||||
const monitor = PerformanceMonitor.createContext('fs.rm');
|
||||
monitor.label(`task:${name}`);
|
||||
task.promise = task.promise.then(() => fn(taskContext));
|
||||
this.tasks_.push(task);
|
||||
|
||||
let last_promise = null;
|
||||
while ( task.promise !== last_promise ) {
|
||||
last_promise = task.promise;
|
||||
await task.promise;
|
||||
}
|
||||
// await task.promise;
|
||||
|
||||
monitor.stamp();
|
||||
monitor.end();
|
||||
}
|
||||
|
||||
get span () { return this.span_; }
|
||||
|
||||
recordParentDirCreated (fsNode) {
|
||||
if ( ! fsNode ) {
|
||||
throw new Error(
|
||||
'falsy value to recordParentDirCreated',
|
||||
fsNode,
|
||||
);
|
||||
}
|
||||
this.parent_dirs_created.push(fsNode);
|
||||
}
|
||||
|
||||
recordCreated (fsNode) {
|
||||
this.created.push(fsNode);
|
||||
}
|
||||
|
||||
set (field, value) {
|
||||
this.fields[field] = value;
|
||||
}
|
||||
|
||||
async set_now (field, value) {
|
||||
this.fields[field] = value;
|
||||
if ( value instanceof FSNodeContext ) {
|
||||
this.safeFields[field] = await value.getSafeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
get (field) {
|
||||
return this.fields[field];
|
||||
}
|
||||
|
||||
complete (options) {
|
||||
options = options ?? {};
|
||||
|
||||
if ( this.parent ) {
|
||||
for ( const fsNode of this.parent_dirs_created ) {
|
||||
this.parent.recordParentDirCreated(fsNode);
|
||||
}
|
||||
|
||||
for ( const fsNode of this.created ) {
|
||||
this.parent.recordCreated(fsNode);
|
||||
}
|
||||
}
|
||||
|
||||
if ( this.tasks_.length > 0 ) {
|
||||
// TODO: it's mutating input options, which is not ideal
|
||||
if ( ! options.after ) options.after = [];
|
||||
|
||||
options.after.push(
|
||||
this.tasks_.map(task => task.promise)
|
||||
);
|
||||
}
|
||||
|
||||
if ( options.after ) {
|
||||
const thingsToWaitFor = options.after.map(item => {
|
||||
if ( item.awaitDone ) return item.awaitDone;
|
||||
return item;
|
||||
});
|
||||
(async () => {
|
||||
await Promise.all(thingsToWaitFor);
|
||||
this.doneResolve();
|
||||
})();
|
||||
return;
|
||||
}
|
||||
|
||||
this.doneResolve();
|
||||
}
|
||||
|
||||
onComplete(fn) {
|
||||
this.donePromise.then(fn);
|
||||
}
|
||||
|
||||
awaitDone () {
|
||||
return this.donePromise;
|
||||
}
|
||||
|
||||
provideValue (key, value) {
|
||||
this.values_[key] = value;
|
||||
|
||||
let listeners = this.valueListeners_[key];
|
||||
if ( ! listeners ) return;
|
||||
|
||||
delete this.valueListeners_[key];
|
||||
|
||||
for ( let listener of listeners ) {
|
||||
if ( Array.isArray(listener) ) listener = listener[0];
|
||||
listener(value);
|
||||
}
|
||||
}
|
||||
|
||||
rejectValue (key, err) {
|
||||
this.rejections_[key] = err;
|
||||
|
||||
let listeners = this.valueListeners_[key];
|
||||
if ( ! listeners ) return;
|
||||
|
||||
delete this.valueListeners_[key];
|
||||
|
||||
for ( let listener of listeners ) {
|
||||
if ( ! Array.isArray(listener) ) continue;
|
||||
if ( ! listener[1] ) continue;
|
||||
listener = listener[1];
|
||||
|
||||
listener(err);
|
||||
}
|
||||
}
|
||||
|
||||
awaitValue (key) {
|
||||
return new Promise ((rslv, rjct) => {
|
||||
this.onValue(key, rslv, rjct);
|
||||
});
|
||||
}
|
||||
|
||||
onValue (key, fn, rjct) {
|
||||
if ( this.values_[key] ) {
|
||||
fn(this.values_[key]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.rejections_[key] ) {
|
||||
if ( rjct ) {
|
||||
rjct(this.rejections_[key]);
|
||||
} else throw this.rejections_[key];
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! this.valueListeners_[key] ) {
|
||||
this.valueListeners_[key] = [];
|
||||
}
|
||||
this.valueListeners_[key].push([fn, rjct]);
|
||||
|
||||
if ( this.valueFactories_[key] ) {
|
||||
const fn = this.valueFactories_[key];
|
||||
delete this.valueFactories_[key];
|
||||
(async () => {
|
||||
try {
|
||||
const value = await fn();
|
||||
this.provideValue(key, value);
|
||||
} catch (e) {
|
||||
this.rejectValue(key, e);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
async setFactory (key, factoryFn) {
|
||||
if ( this.valueListeners_[key] ) {
|
||||
let v;
|
||||
try {
|
||||
v = await factoryFn();
|
||||
} catch (e) {
|
||||
this.rejectValue(key, e);
|
||||
}
|
||||
this.provideValue(key, v);
|
||||
return;
|
||||
}
|
||||
|
||||
this.valueFactories_[key] = factoryFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for another operation to complete, and then
|
||||
* complete this operation. This is useful for operations
|
||||
* which delegate to other operations.
|
||||
*
|
||||
* @param {FSOperationContext} other
|
||||
* @returns {FSOperationContext} this
|
||||
*/
|
||||
completedBy (other) {
|
||||
other.onComplete(() => {
|
||||
this.complete();
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces an object which describes the operation in a
|
||||
* way that is intended to be sent to the client.
|
||||
*
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async getClientSafeResult () {
|
||||
const result = {};
|
||||
for ( const field in this.fields ) {
|
||||
if ( this.fields[field] instanceof FSNodeContext ) {
|
||||
result[field] = this.safeFields[field] ??
|
||||
await this.fields[field].getSafeEntry();
|
||||
continue;
|
||||
}
|
||||
|
||||
result[field] = this.fields[field];
|
||||
}
|
||||
|
||||
result.parent_dirs_created = [];
|
||||
for ( const fsNode of this.parent_dirs_created ) {
|
||||
const fsNodeResult = await fsNode.getSafeEntry();
|
||||
result.parent_dirs_created.push(fsNodeResult);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -47,12 +47,8 @@ class FilesystemService extends BaseService {
|
||||
}
|
||||
|
||||
old_constructor (args) {
|
||||
// super(args);
|
||||
const { services } = args;
|
||||
|
||||
// this.services = services;
|
||||
|
||||
// services.registerService('resourceService', ResourceService);
|
||||
services.registerService('sizeService', SizeService);
|
||||
services.registerService('traceService', TraceService);
|
||||
|
||||
@@ -81,7 +77,7 @@ class FilesystemService extends BaseService {
|
||||
|
||||
|
||||
// Decorate methods with otel span
|
||||
// TODO: language tool for traits; this is a trait
|
||||
// TODO: use putility class feature for method decorators
|
||||
const span_methods = [
|
||||
'write', 'mkdir', 'rm', 'mv', 'cp', 'read', 'stat',
|
||||
'mkdir_2',
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const APIError = require("../../api/APIError");
|
||||
const { CodeModel } = require("../../codex/CodeModel");
|
||||
const { Sequence } = require("../../codex/Sequence");
|
||||
|
||||
const { DB_WRITE } = require("../../services/database/consts");
|
||||
|
||||
Reference in New Issue
Block a user