فهرست منبع

Fix casualty of phoenix due to Docker naming conventions

KernelDeimos 1 سال پیش
والد
کامیت
b21755b5a3

+ 1 - 1
.gitignore

@@ -9,4 +9,4 @@ dist/
 .env
 # this is for jetbrain IDEs
 .idea/
-puter
+/puter

+ 55 - 0
packages/phoenix/src/platform/puter/drivers.js

@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024  Puter Technologies Inc.
+ *
+ * This file is part of Phoenix Shell.
+ *
+ * Phoenix Shell 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/>.
+ */
+export const CreateDriversProvider = ({
+    puterSDK
+}) => {
+    return {
+        call: async (params) => {
+            console.log('the puterSDK', puterSDK);
+            const resp = await fetch(
+                `${puterSDK.APIOrigin}/drivers/call`,
+                {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json',
+                        'Authorization': `Bearer ${puterSDK.authToken}`
+                    },
+                    body: JSON.stringify({
+                        ...params,
+                    }),
+                }
+            );
+    
+            return await resp.blob();
+        },
+        usage: async () => {
+            const resp = await fetch(
+                `${puterSDK.APIOrigin}/drivers/usage`,
+                {
+                    method: 'GET',
+                    headers: {
+                        'Authorization': `Bearer ${puterSDK.authToken}`
+                    },
+                }
+            );
+
+            return await resp.json();                        
+        }
+    }
+};

+ 15 - 0
packages/phoenix/src/platform/puter/env.js

@@ -0,0 +1,15 @@
+export const CreateEnvProvider = ({ config }) => {
+    return {
+        getEnv: () => {
+            return {
+                USER: config['puter.auth.username'],
+                HOME: '/' + config['puter.auth.username'],
+                HOSTNAME: 'puter.com',
+            }
+        },
+
+        get (k) {
+            return this.getEnv()[k];
+        }
+    }
+}

+ 233 - 0
packages/phoenix/src/platform/puter/filesystem.js

@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2024  Puter Technologies Inc.
+ *
+ * This file is part of Phoenix Shell.
+ *
+ * Phoenix Shell 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/>.
+ */
+import { ErrorCodes, PosixError } from '../PosixError.js';
+
+function convertPuterError(e) {
+    // Handle Puter SDK errors
+    switch (e.code) {
+        case 'item_with_same_name_exists': return new PosixError(ErrorCodes.EEXIST, e.message);
+        case 'cannot_move_item_into_itself': return new PosixError(ErrorCodes.EPERM, e.message);
+        case 'cannot_copy_item_into_itself': return new PosixError(ErrorCodes.EPERM, e.message);
+        case 'cannot_move_to_root': return new PosixError(ErrorCodes.EACCES, e.message);
+        case 'cannot_copy_to_root': return new PosixError(ErrorCodes.EACCES, e.message);
+        case 'cannot_write_to_root': return new PosixError(ErrorCodes.EACCES, e.message);
+        case 'cannot_overwrite_a_directory': return new PosixError(ErrorCodes.EPERM, e.message);
+        case 'cannot_read_a_directory': return new PosixError(ErrorCodes.EISDIR, e.message);
+        case 'source_and_dest_are_the_same': return new PosixError(ErrorCodes.EPERM, e.message);
+        case 'dest_is_not_a_directory': return new PosixError(ErrorCodes.ENOTDIR, e.message);
+        case 'dest_does_not_exist': return new PosixError(ErrorCodes.ENOENT, e.message);
+        case 'source_does_not_exist': return new PosixError(ErrorCodes.ENOENT, e.message);
+        case 'subject_does_not_exist': return new PosixError(ErrorCodes.ENOENT, e.message);
+        case 'shortcut_target_not_found': return new PosixError(ErrorCodes.ENOENT, e.message);
+        case 'shortcut_target_is_a_directory': return new PosixError(ErrorCodes.EISDIR, e.message);
+        case 'shortcut_target_is_a_file': return new PosixError(ErrorCodes.ENOTDIR, e.message);
+        case 'forbidden': return new PosixError(ErrorCodes.EPERM, e.message);
+        case 'immutable': return new PosixError(ErrorCodes.EACCES, e.message);
+        case 'field_empty': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'field_missing': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'xor_field_missing': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'field_only_valid_with_other_field': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'invalid_id': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'field_invalid': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'field_immutable': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'field_too_long': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'field_too_short': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'already_in_use': return new PosixError(ErrorCodes.EINVAL, e.message); // Not sure what this one is
+        case 'invalid_file_name': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'storage_limit_reached': return new PosixError(ErrorCodes.ENOSPC, e.message);
+        case 'internal_error': return new PosixError(ErrorCodes.ECONNRESET, e.message); // This isn't quite right
+        case 'response_timeout': return new PosixError(ErrorCodes.ETIMEDOUT, e.message);
+        case 'file_too_large': return new PosixError(ErrorCodes.EFBIG, e.message);
+        case 'thumbnail_too_large': return new PosixError(ErrorCodes.EFBIG, e.message);
+        case 'upload_failed': return new PosixError(ErrorCodes.ECONNRESET, e.message); // This isn't quite right
+        case 'missing_expected_metadata': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'overwrite_and_dedupe_exclusive': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'not_empty': return new PosixError(ErrorCodes.ENOTEMPTY, e.message);
+
+        // Write
+        case 'offset_without_existing_file': return new PosixError(ErrorCodes.ENOENT, e.message);
+        case 'offset_requires_overwrite': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'offset_requires_stream': return new PosixError(ErrorCodes.EPERM, e.message);
+
+        // Batch
+        case 'batch_too_many_files': return new PosixError(ErrorCodes.EINVAL, e.message);
+        case 'batch_missing_file': return new PosixError(ErrorCodes.EINVAL, e.message);
+
+        // Open
+        case 'no_suitable_app': break;
+        case 'app_does_not_exist': break;
+
+        // Apps
+        case 'app_name_already_in_use': break;
+
+        // Subdomains
+        case 'subdomain_limit_reached': break;
+        case 'subdomain_reserved': break;
+
+        // Users
+        case 'email_already_in_use': break;
+        case 'username_already_in_use': break;
+        case 'too_many_username_changes': break;
+        case 'token_invalid': break;
+
+        // drivers
+        case 'interface_not_found': break;
+        case 'no_implementation_available': break;
+        case 'method_not_found': break;
+        case 'missing_required_argument': break;
+        case 'argument_consolidation_failed': break;
+
+        // SLA
+        case 'rate_limit_exceeded': break;
+        case 'monthly_limit_exceeded': break;
+        case 'server_rate_exceeded': break;
+
+        // auth
+        case 'token_missing': break;
+        case 'token_auth_failed': break;
+        case 'token_unsupported': break;
+        case 'account_suspended': break;
+        case 'permission_denied': break;
+        case 'access_token_empty_permissions': break;
+
+        // Object Mapping
+        case 'field_not_allowed_for_create': break;
+        case 'field_required_for_update': break;
+        case 'entity_not_found': break;
+
+        // Chat
+        case 'max_tokens_exceeded': break;
+    }
+    // Some other kind of error
+    return e;
+}
+
+// DRY: Almost the same as node/filesystem.js
+function wrapAPIs(apis) {
+    for (const method in apis) {
+        if (typeof apis[method] !== 'function') {
+            continue;
+        }
+        const original = apis[method];
+        apis[method] = async (...args) => {
+            try {
+                return await original(...args);
+            } catch (e) {
+                throw convertPuterError(e);
+            }
+        };
+    }
+    return apis;
+}
+
+export const CreateFilesystemProvider = ({
+    puterSDK,
+}) => {
+    return wrapAPIs({
+        capabilities: {
+            'readdir.www': true,
+        },
+        // The interface for Puter SDK is a good interface for any filesystem
+        // provider, so we will use that as the basis for the Puter Shell's
+        // own filesystem provider interface.
+        readdir: puterSDK.fs.readdir.bind(puterSDK.fs),
+        stat: puterSDK.fs.stat.bind(puterSDK.fs),
+        mkdir: puterSDK.fs.mkdir.bind(puterSDK.fs),
+        read: puterSDK.fs.read.bind(puterSDK.fs),
+        write: puterSDK.fs.write.bind(puterSDK.fs),
+        
+        // The `rm` method should fail if the destination is a directory
+        rm: async (path, { recursive = false }) => {
+            const stat = await puterSDK.fs.stat(path);
+
+            if ( stat.is_dir && ! recursive ) {
+                throw PosixError.IsDirectory({ path });
+            }
+
+            return await puterSDK.fs.delete(path, { recursive });
+        },
+
+        // The Puter SDK does not implement `rmdir`
+        rmdir: async (path) => {
+            const stat = await puterSDK.fs.stat(path);
+
+            if ( ! stat.is_dir ) {
+                throw PosixError.IsNotDirectory({ path });
+            }
+
+            return await puterSDK.fs.delete(path, { recursive: false });
+        },
+
+        // For move and copy the interface is a compromise between the
+        // Puter SDK and node.js's `fs` module. This compromise is
+        // effectively the same behaviour provided by the POSIX `mv`
+        // command; we accept a new name in newPath (contrary to Puter SDK),
+        // and we do not throw an error if the destination is a directory
+        // (contrary to node.js's `fs`).
+        move: async (oldPath, newPath) => {
+            let dst_stat = null;
+            try {
+                dst_stat = await puterSDK.fs.stat(newPath);
+            } catch (e) {
+                if ( e.code !== 'subject_does_not_exist' ) throw e;
+            }
+
+            // In the Puter SDK, the destination specified is always
+            // the parent directory to move the source under.
+
+            let new_name = undefined;
+            if ( ! dst_stat ) {
+                // take last part of destination path and use it as the new name
+                const parts = newPath.split('/');
+                new_name = parts[parts.length - 1];
+
+                // remove new name from destination path
+                parts.pop();
+                newPath = parts.join('/');
+            }
+
+            return await puterSDK.fs.move(oldPath, newPath, {
+                ...(new_name ? { newName: new_name } : {}),
+            });
+        },
+        copy: async (oldPath, newPath) => {
+            let dst_stat = null;
+            try {
+                dst_stat = await puterSDK.fs.stat(newPath);
+            } catch (e) {
+                if ( e.code !== 'subject_does_not_exist' ) throw e;
+            }
+
+            let new_name = undefined;
+            if ( ! dst_stat ) {
+                // take last part of destination path and use it as the copy's name
+                const parts = newPath.split('/');
+                new_name = parts[parts.length - 1];
+
+                // remove new name from destination path
+                parts.pop();
+                newPath = parts.join('/');
+            }
+
+            return await puterSDK.fs.copy(oldPath, newPath, {
+                ...(new_name ? { newName: new_name } : {}),
+            });
+        },
+    });
+};