@theia/filesystem
Version:
Theia - FileSystem Extension
323 lines • 14 kB
JavaScript
"use strict";
// *****************************************************************************
// Copyright (C) 2024 EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
Object.defineProperty(exports, "__esModule", { value: true });
exports.OPFSFileSystemProvider = void 0;
const tslib_1 = require("tslib");
const inversify_1 = require("@theia/core/shared/inversify");
const files_1 = require("../common/files");
const core_1 = require("@theia/core");
const opfs_filesystem_initialization_1 = require("./opfs-filesystem-initialization");
let OPFSFileSystemProvider = class OPFSFileSystemProvider {
constructor() {
this.capabilities = 2 /* FileSystemProviderCapabilities.FileReadWrite */;
this.onDidChangeCapabilities = core_1.Event.None;
this.onDidChangeFileEmitter = new core_1.Emitter();
this.onDidChangeFile = this.onDidChangeFileEmitter.event;
this.onFileWatchError = core_1.Event.None;
}
init() {
const setup = async () => {
this.directoryHandle = await this.initialization.getRootDirectory();
await this.initialization.initializeFS(new Proxy(this, {
get(target, prop, receiver) {
if (prop === 'initialized') {
return Promise.resolve(true);
}
return Reflect.get(target, prop, receiver);
}
}));
return true;
};
this.initialized = setup();
}
watch(_resource, _opts) {
return core_1.Disposable.NULL;
}
async exists(resource) {
try {
await this.initialized;
await this.toFileSystemHandle(resource);
return true;
}
catch (error) {
return false;
}
}
async stat(resource) {
try {
await this.initialized;
const handle = await this.toFileSystemHandle(resource);
if (handle.kind === 'file') {
const fileHandle = handle;
const file = await fileHandle.getFile();
return {
type: files_1.FileType.File,
ctime: file.lastModified,
mtime: file.lastModified,
size: file.size
};
}
else if (handle.kind === 'directory') {
return {
type: files_1.FileType.Directory,
ctime: 0,
mtime: 0,
size: 0
};
}
throw (0, files_1.createFileSystemProviderError)('Unknown file handle error', files_1.FileSystemProviderErrorCode.Unknown);
}
catch (error) {
throw toFileSystemProviderError(error);
}
}
async mkdir(resource) {
await this.initialized;
try {
await this.toFileSystemHandle(resource, { create: true, isDirectory: true });
this.onDidChangeFileEmitter.fire([{ resource, type: 1 /* FileChangeType.ADDED */ }]);
}
catch (error) {
throw toFileSystemProviderError(error, true);
}
}
async readdir(resource) {
await this.initialized;
try {
// Get the directory handle from the directoryHandle
const directoryHandle = await this.toFileSystemHandle(resource, { create: false, isDirectory: true });
const result = [];
// Iterate through the entries in the directory (files and subdirectories)
for await (const [name, handle] of directoryHandle.entries()) {
// Determine the type of the entry (file or directory)
if (handle.kind === 'file') {
result.push([name, files_1.FileType.File]);
}
else if (handle.kind === 'directory') {
result.push([name, files_1.FileType.Directory]);
}
}
return result;
}
catch (error) {
throw toFileSystemProviderError(error, true);
}
}
async delete(resource, _opts) {
await this.initialized;
try {
const parentURI = resource.parent;
const parentHandle = await this.toFileSystemHandle(parentURI, { create: false, isDirectory: true });
if (parentHandle.kind !== 'directory') {
throw (0, files_1.createFileSystemProviderError)(new Error('Parent is not a directory'), files_1.FileSystemProviderErrorCode.FileNotADirectory);
}
const name = resource.path.base;
return parentHandle.removeEntry(name, { recursive: _opts.recursive });
}
catch (error) {
throw toFileSystemProviderError(error);
}
finally {
this.onDidChangeFileEmitter.fire([{ resource, type: 2 /* FileChangeType.DELETED */ }]);
}
}
async rename(from, to, opts) {
await this.initialized;
try {
const fromHandle = await this.toFileSystemHandle(from);
// Check whether the source is a file or directory
if (fromHandle.kind === 'directory') {
// Create the new directory and get the handle
await this.mkdir(to);
const toHandle = await this.toFileSystemHandle(to);
await copyDirectoryContents(fromHandle, toHandle);
// Delete the old directory
await this.delete(from, { recursive: true, useTrash: false });
}
else {
const content = await this.readFile(from);
await this.writeFile(to, content, { create: true, overwrite: opts.overwrite });
await this.delete(from, { recursive: true, useTrash: false });
}
this.onDidChangeFileEmitter.fire([{ resource: to, type: 1 /* FileChangeType.ADDED */ }]);
}
catch (error) {
throw toFileSystemProviderError(error);
}
}
async readFile(resource) {
await this.initialized;
try {
// Get the file handle from the directoryHandle
const fileHandle = await this.toFileSystemHandle(resource, { create: false, isDirectory: false });
// Get the file itself (which includes the content)
const file = await fileHandle.getFile();
// Read the file as an ArrayBuffer and convert it to Uint8Array
const arrayBuffer = await file.arrayBuffer();
return new Uint8Array(arrayBuffer);
}
catch (error) {
throw toFileSystemProviderError(error, false);
}
}
async writeFile(resource, content, opts) {
await this.initialized;
let writeableHandle = undefined;
try {
// Validate target unless { create: true, overwrite: true }
if (!opts.create || !opts.overwrite) {
const fileExists = await this.stat(resource).then(() => true, () => false);
if (fileExists) {
if (!opts.overwrite) {
throw (0, files_1.createFileSystemProviderError)('File already exists', files_1.FileSystemProviderErrorCode.FileExists);
}
}
else {
if (!opts.create) {
throw (0, files_1.createFileSystemProviderError)('File does not exist', files_1.FileSystemProviderErrorCode.FileNotFound);
}
}
}
const handle = await this.toFileSystemHandle(resource, { create: true, isDirectory: false });
// Open
writeableHandle = await (handle === null || handle === void 0 ? void 0 : handle.createWritable());
// Write content at once
await (writeableHandle === null || writeableHandle === void 0 ? void 0 : writeableHandle.write(content));
this.onDidChangeFileEmitter.fire([{ resource: resource, type: 0 /* FileChangeType.UPDATED */ }]);
}
catch (error) {
throw toFileSystemProviderError(error, false);
}
finally {
if (typeof writeableHandle !== 'undefined') {
await writeableHandle.close();
}
}
}
/**
* Returns the FileSystemHandle for the given resource given by a URI.
* @param resource URI/path of the resource
* @param options Options for the creation of the handle while traversing the path
* @returns FileSystemHandle for the given resource
*/
async toFileSystemHandle(resource, options) {
const pathParts = resource.path.toString().split(core_1.Path.separator).filter(Boolean);
return recursiveFileSystemHandle(this.directoryHandle, pathParts, options);
}
};
exports.OPFSFileSystemProvider = OPFSFileSystemProvider;
tslib_1.__decorate([
(0, inversify_1.inject)(opfs_filesystem_initialization_1.OPFSInitialization),
tslib_1.__metadata("design:type", Object)
], OPFSFileSystemProvider.prototype, "initialization", void 0);
tslib_1.__decorate([
(0, inversify_1.postConstruct)(),
tslib_1.__metadata("design:type", Function),
tslib_1.__metadata("design:paramtypes", []),
tslib_1.__metadata("design:returntype", void 0)
], OPFSFileSystemProvider.prototype, "init", null);
exports.OPFSFileSystemProvider = OPFSFileSystemProvider = tslib_1.__decorate([
(0, inversify_1.injectable)()
], OPFSFileSystemProvider);
// #region Helper functions
async function recursiveFileSystemHandle(handle, pathParts, options) {
// We reached the end of the path, this happens only when not creating
if (pathParts.length === 0) {
return handle;
}
// If there are parts left, the handle must be a directory
if (handle.kind !== 'directory') {
throw (0, files_1.createFileSystemProviderError)('Not a directory', files_1.FileSystemProviderErrorCode.FileNotADirectory);
}
const dirHandle = handle;
// We need to create it and thus we need to stop early to create the file or directory
if (pathParts.length === 1 && (options === null || options === void 0 ? void 0 : options.create)) {
if (options === null || options === void 0 ? void 0 : options.isDirectory) {
return dirHandle.getDirectoryHandle(pathParts[0], { create: options.create });
}
else {
return dirHandle.getFileHandle(pathParts[0], { create: options.create });
}
}
// Continue to resolve the path
const part = pathParts.shift();
for await (const entry of dirHandle.entries()) {
// Check the entry name in the current directory
if (entry[0] === part) {
return recursiveFileSystemHandle(entry[1], pathParts, options);
}
}
// If we haven't found the part, we need to create it along the way
if (options === null || options === void 0 ? void 0 : options.create) {
const newHandle = await dirHandle.getDirectoryHandle(part, { create: true });
return recursiveFileSystemHandle(newHandle, pathParts, options);
}
throw (0, files_1.createFileSystemProviderError)('File not found', files_1.FileSystemProviderErrorCode.FileNotFound);
}
// Function to copy directory contents recursively
async function copyDirectoryContents(sourceHandle, destinationHandle) {
for await (const [name, handle] of sourceHandle.entries()) {
if (handle.kind === 'file') {
const file = await handle.getFile();
const newFileHandle = await destinationHandle.getFileHandle(name, { create: true });
const writable = await newFileHandle.createWritable();
try {
await writable.write(await file.arrayBuffer());
}
finally {
await writable.close();
}
}
else if (handle.kind === 'directory') {
const newSubDirHandle = await destinationHandle.getDirectoryHandle(name, { create: true });
await copyDirectoryContents(handle, newSubDirHandle);
}
}
}
function toFileSystemProviderError(error, is_dir) {
if (error instanceof files_1.FileSystemProviderError) {
return error; // avoid double conversion
}
let code;
switch (error.name) {
case 'NotFoundError':
code = files_1.FileSystemProviderErrorCode.FileNotFound;
break;
case 'InvalidModificationError':
code = files_1.FileSystemProviderErrorCode.FileExists;
break;
case 'NotAllowedError':
code = files_1.FileSystemProviderErrorCode.NoPermissions;
break;
case 'TypeMismatchError':
if (!is_dir) {
code = files_1.FileSystemProviderErrorCode.FileIsADirectory;
}
else {
code = files_1.FileSystemProviderErrorCode.FileNotADirectory;
}
break;
case 'QuotaExceededError':
code = files_1.FileSystemProviderErrorCode.FileTooLarge;
break;
default:
code = files_1.FileSystemProviderErrorCode.Unknown;
}
return (0, files_1.createFileSystemProviderError)(error, code);
}
// #endregion
//# sourceMappingURL=opfs-filesystem-provider.js.map