2024-10-19 15:12:52 -06:00
|
|
|
import { FilesystemEvent, Sandbox, WatchHandle } from "e2b"
|
|
|
|
import path from "path"
|
2024-10-19 16:23:31 -06:00
|
|
|
import RemoteFileStorage from "./RemoteFileStorage"
|
2024-10-19 15:12:52 -06:00
|
|
|
import { MAX_BODY_SIZE } from "./ratelimit"
|
|
|
|
import { TFile, TFileData, TFolder } from "./types"
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Define the structure for sandbox files
|
2024-10-19 15:12:52 -06:00
|
|
|
export type SandboxFiles = {
|
|
|
|
files: (TFolder | TFile)[]
|
|
|
|
fileData: TFileData[]
|
|
|
|
}
|
|
|
|
|
2024-10-19 18:42:44 -06:00
|
|
|
// Convert list of paths to the hierchical file structure used by the editor
|
|
|
|
function generateFileStructure(paths: string[]): (TFolder | TFile)[] {
|
2024-10-19 16:23:31 -06:00
|
|
|
const root: TFolder = { id: "/", type: "folder", name: "/", children: [] }
|
|
|
|
|
|
|
|
paths.forEach((path) => {
|
2024-10-19 18:42:44 -06:00
|
|
|
const parts = path.split("/")
|
2024-10-19 16:23:31 -06:00
|
|
|
let current: TFolder = root
|
|
|
|
|
|
|
|
for (let i = 0; i < parts.length; i++) {
|
|
|
|
const part = parts[i]
|
|
|
|
const isFile = i === parts.length - 1 && part.length
|
|
|
|
const existing = current.children.find((child) => child.name === part)
|
|
|
|
|
|
|
|
if (existing) {
|
|
|
|
if (!isFile) {
|
|
|
|
current = existing as TFolder
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (isFile) {
|
2024-10-19 16:41:26 -06:00
|
|
|
const file: TFile = { id: `/${parts.join("/")}`, type: "file", name: part }
|
2024-10-19 16:23:31 -06:00
|
|
|
current.children.push(file)
|
|
|
|
} else {
|
|
|
|
const folder: TFolder = {
|
2024-10-19 16:41:26 -06:00
|
|
|
id: `/${parts.slice(0, i + 1).join("/")}`,
|
2024-10-19 16:23:31 -06:00
|
|
|
type: "folder",
|
|
|
|
name: part,
|
|
|
|
children: [],
|
|
|
|
}
|
|
|
|
current.children.push(folder)
|
|
|
|
current = folder
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-10-19 18:42:44 -06:00
|
|
|
return root.children
|
2024-10-19 16:23:31 -06:00
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// FileManager class to handle file operations in a sandbox
|
2024-10-19 15:12:52 -06:00
|
|
|
export class FileManager {
|
|
|
|
private sandboxId: string
|
|
|
|
private sandbox: Sandbox
|
|
|
|
public sandboxFiles: SandboxFiles
|
|
|
|
private fileWatchers: WatchHandle[] = []
|
2024-10-19 16:41:26 -06:00
|
|
|
private dirName = "/home/user/project"
|
2024-10-19 15:12:52 -06:00
|
|
|
private refreshFileList: (files: SandboxFiles) => void
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Constructor to initialize the FileManager
|
2024-10-19 15:12:52 -06:00
|
|
|
constructor(
|
|
|
|
sandboxId: string,
|
|
|
|
sandbox: Sandbox,
|
|
|
|
refreshFileList: (files: SandboxFiles) => void
|
|
|
|
) {
|
|
|
|
this.sandboxId = sandboxId
|
|
|
|
this.sandbox = sandbox
|
|
|
|
this.sandboxFiles = { files: [], fileData: [] }
|
|
|
|
this.refreshFileList = refreshFileList
|
|
|
|
}
|
|
|
|
|
2024-10-19 18:42:44 -06:00
|
|
|
// Fetch file data from list of paths
|
|
|
|
private async generateFileData(paths: string[]): Promise<TFileData[]> {
|
|
|
|
const fileData: TFileData[] = []
|
|
|
|
|
|
|
|
for (const path of paths) {
|
|
|
|
const parts = path.split("/")
|
|
|
|
const isFile = parts.length > 0 && parts[parts.length - 1].length > 0
|
|
|
|
|
|
|
|
if (isFile) {
|
|
|
|
const fileId = `/${parts.join("/")}`
|
|
|
|
const data = await RemoteFileStorage.fetchFileContent(`projects/${this.sandboxId}${fileId}`)
|
|
|
|
fileData.push({ id: fileId, data })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fileData
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert local file path to remote path
|
2024-10-19 16:41:26 -06:00
|
|
|
private getRemoteFileId(localId: string): string {
|
|
|
|
return `projects/${this.sandboxId}${localId}`
|
|
|
|
}
|
|
|
|
|
2024-10-19 18:42:44 -06:00
|
|
|
// Convert remote file path to local file path
|
|
|
|
private getLocalFileId(remoteId: string): string | undefined {
|
|
|
|
const allParts = remoteId.split("/")
|
|
|
|
if (allParts[1] !== this.sandboxId) return undefined;
|
|
|
|
return allParts.slice(2).join("/")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert remote file paths to local file paths
|
|
|
|
private getLocalFileIds(remoteIds: string[]): string[] {
|
|
|
|
return remoteIds
|
|
|
|
.map(this.getLocalFileId.bind(this))
|
|
|
|
.filter((id) => id !== undefined);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Download files from remote storage
|
|
|
|
private async updateFileData(): Promise<TFileData[]> {
|
|
|
|
const remotePaths = await RemoteFileStorage.getSandboxPaths(this.sandboxId)
|
|
|
|
const localPaths = this.getLocalFileIds(remotePaths)
|
|
|
|
this.sandboxFiles.fileData = await this.generateFileData(localPaths)
|
|
|
|
return this.sandboxFiles.fileData
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update file structure
|
|
|
|
private async updateFileStructure(): Promise<(TFolder | TFile)[]> {
|
|
|
|
const remotePaths = await RemoteFileStorage.getSandboxPaths(this.sandboxId)
|
|
|
|
const localPaths = this.getLocalFileIds(remotePaths)
|
|
|
|
this.sandboxFiles.files = generateFileStructure(localPaths)
|
|
|
|
return this.sandboxFiles.files
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Initialize the FileManager
|
2024-10-19 15:12:52 -06:00
|
|
|
async initialize() {
|
2024-10-19 18:42:44 -06:00
|
|
|
|
|
|
|
// Download files from remote file storage
|
|
|
|
await this.updateFileStructure()
|
|
|
|
await this.updateFileData()
|
|
|
|
|
2024-10-19 15:12:52 -06:00
|
|
|
// Copy all files from the project to the container
|
|
|
|
const promises = this.sandboxFiles.fileData.map(async (file) => {
|
|
|
|
try {
|
|
|
|
const filePath = path.join(this.dirName, file.id)
|
|
|
|
const parentDirectory = path.dirname(filePath)
|
|
|
|
if (!this.sandbox.files.exists(parentDirectory)) {
|
|
|
|
await this.sandbox.files.makeDir(parentDirectory)
|
|
|
|
}
|
|
|
|
await this.sandbox.files.write(filePath, file.data)
|
|
|
|
} catch (e: any) {
|
|
|
|
console.log("Failed to create file: " + e)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
await Promise.all(promises)
|
|
|
|
|
|
|
|
// Make the logged in user the owner of all project files
|
|
|
|
this.fixPermissions()
|
|
|
|
|
2024-10-19 18:42:44 -06:00
|
|
|
await this.watchDirectory(this.dirName)
|
|
|
|
await this.watchSubdirectories(this.dirName)
|
2024-10-19 15:12:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the given path is a directory
|
2024-10-19 18:42:44 -06:00
|
|
|
private async isDirectory(directoryPath: string): Promise<boolean> {
|
2024-10-19 15:12:52 -06:00
|
|
|
try {
|
|
|
|
const result = await this.sandbox.commands.run(
|
2024-10-19 18:42:44 -06:00
|
|
|
`[ -d "${directoryPath}" ] && echo "true" || echo "false"`
|
2024-10-19 15:12:52 -06:00
|
|
|
)
|
|
|
|
return result.stdout.trim() === "true"
|
|
|
|
} catch (e: any) {
|
|
|
|
console.log("Failed to check if directory: " + e)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Change the owner of the project directory to user
|
|
|
|
private async fixPermissions() {
|
|
|
|
try {
|
|
|
|
await this.sandbox.commands.run(
|
2024-10-19 16:41:26 -06:00
|
|
|
`sudo chown -R user "${this.dirName}"`
|
2024-10-19 15:12:52 -06:00
|
|
|
)
|
|
|
|
} catch (e: any) {
|
|
|
|
console.log("Failed to fix permissions: " + e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Watch a directory for changes
|
2024-10-19 15:12:52 -06:00
|
|
|
async watchDirectory(directory: string): Promise<WatchHandle | undefined> {
|
|
|
|
try {
|
|
|
|
const handle = await this.sandbox.files.watch(
|
|
|
|
directory,
|
|
|
|
async (event: FilesystemEvent) => {
|
|
|
|
try {
|
|
|
|
function removeDirName(path: string, dirName: string) {
|
|
|
|
return path.startsWith(dirName)
|
|
|
|
? path.slice(dirName.length)
|
|
|
|
: path
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is the absolute file path in the container
|
|
|
|
const containerFilePath = path.posix.join(directory, event.name)
|
2024-10-19 16:41:26 -06:00
|
|
|
// This is the file path relative to the project directory
|
|
|
|
const sandboxFilePath = removeDirName(containerFilePath, this.dirName)
|
|
|
|
// This is the directory being watched relative to the project directory
|
|
|
|
const sandboxDirectory = removeDirName(directory, this.dirName)
|
2024-10-19 15:12:52 -06:00
|
|
|
|
|
|
|
// Helper function to find a folder by id
|
|
|
|
function findFolderById(
|
|
|
|
files: (TFolder | TFile)[],
|
|
|
|
folderId: string
|
|
|
|
) {
|
|
|
|
return files.find(
|
|
|
|
(file: TFolder | TFile) =>
|
|
|
|
file.type === "folder" && file.id === folderId
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Handle file/directory creation event
|
2024-10-19 15:12:52 -06:00
|
|
|
if (event.type === "create") {
|
|
|
|
const folder = findFolderById(
|
|
|
|
this.sandboxFiles.files,
|
|
|
|
sandboxDirectory
|
|
|
|
) as TFolder
|
|
|
|
const isDir = await this.isDirectory(containerFilePath)
|
|
|
|
|
|
|
|
const newItem = isDir
|
|
|
|
? ({
|
2024-10-19 15:16:24 -06:00
|
|
|
id: sandboxFilePath,
|
|
|
|
name: event.name,
|
|
|
|
type: "folder",
|
|
|
|
children: [],
|
|
|
|
} as TFolder)
|
2024-10-19 15:12:52 -06:00
|
|
|
: ({
|
2024-10-19 15:16:24 -06:00
|
|
|
id: sandboxFilePath,
|
|
|
|
name: event.name,
|
|
|
|
type: "file",
|
|
|
|
} as TFile)
|
2024-10-19 15:12:52 -06:00
|
|
|
|
|
|
|
if (folder) {
|
|
|
|
// If the folder exists, add the new item (file/folder) as a child
|
|
|
|
folder.children.push(newItem)
|
|
|
|
} else {
|
|
|
|
// If folder doesn't exist, add the new item to the root
|
|
|
|
this.sandboxFiles.files.push(newItem)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isDir) {
|
|
|
|
const fileData = await this.sandbox.files.read(
|
|
|
|
containerFilePath
|
|
|
|
)
|
|
|
|
const fileContents =
|
|
|
|
typeof fileData === "string" ? fileData : ""
|
|
|
|
this.sandboxFiles.fileData.push({
|
|
|
|
id: sandboxFilePath,
|
|
|
|
data: fileContents,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(`Create ${sandboxFilePath}`)
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Handle file/directory removal or rename event
|
2024-10-19 15:12:52 -06:00
|
|
|
else if (event.type === "remove" || event.type == "rename") {
|
|
|
|
const folder = findFolderById(
|
|
|
|
this.sandboxFiles.files,
|
|
|
|
sandboxDirectory
|
|
|
|
) as TFolder
|
|
|
|
const isDir = await this.isDirectory(containerFilePath)
|
|
|
|
|
|
|
|
const isFileMatch = (file: TFolder | TFile | TFileData) =>
|
|
|
|
file.id === sandboxFilePath ||
|
|
|
|
file.id.startsWith(containerFilePath + "/")
|
|
|
|
|
|
|
|
if (folder) {
|
|
|
|
// Remove item from its parent folder
|
|
|
|
folder.children = folder.children.filter(
|
|
|
|
(file: TFolder | TFile) => !isFileMatch(file)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// Remove from the root if it's not inside a folder
|
|
|
|
this.sandboxFiles.files = this.sandboxFiles.files.filter(
|
|
|
|
(file: TFolder | TFile) => !isFileMatch(file)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Also remove any corresponding file data
|
|
|
|
this.sandboxFiles.fileData = this.sandboxFiles.fileData.filter(
|
|
|
|
(file: TFileData) => !isFileMatch(file)
|
|
|
|
)
|
|
|
|
|
|
|
|
console.log(`Removed: ${sandboxFilePath}`)
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Handle file write event
|
2024-10-19 15:12:52 -06:00
|
|
|
else if (event.type === "write") {
|
|
|
|
const folder = findFolderById(
|
|
|
|
this.sandboxFiles.files,
|
|
|
|
sandboxDirectory
|
|
|
|
) as TFolder
|
|
|
|
const fileToWrite = this.sandboxFiles.fileData.find(
|
|
|
|
(file) => file.id === sandboxFilePath
|
|
|
|
)
|
|
|
|
|
|
|
|
if (fileToWrite) {
|
|
|
|
fileToWrite.data = await this.sandbox.files.read(
|
|
|
|
containerFilePath
|
|
|
|
)
|
|
|
|
console.log(`Write to ${sandboxFilePath}`)
|
|
|
|
} else {
|
|
|
|
// If the file is part of a folder structure, locate it and update its data
|
|
|
|
const fileInFolder = folder?.children.find(
|
|
|
|
(file) => file.id === sandboxFilePath
|
|
|
|
)
|
|
|
|
if (fileInFolder) {
|
|
|
|
const fileData = await this.sandbox.files.read(
|
|
|
|
containerFilePath
|
|
|
|
)
|
|
|
|
const fileContents =
|
|
|
|
typeof fileData === "string" ? fileData : ""
|
|
|
|
this.sandboxFiles.fileData.push({
|
|
|
|
id: sandboxFilePath,
|
|
|
|
data: fileContents,
|
|
|
|
})
|
|
|
|
console.log(`Write to ${sandboxFilePath}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tell the client to reload the file list
|
|
|
|
this.refreshFileList(this.sandboxFiles)
|
|
|
|
} catch (error) {
|
|
|
|
console.error(
|
|
|
|
`Error handling ${event.type} event for ${event.name}:`,
|
|
|
|
error
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ timeout: 0 }
|
|
|
|
)
|
|
|
|
this.fileWatchers.push(handle)
|
|
|
|
return handle
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error watching filesystem:`, error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Watch subdirectories recursively
|
2024-10-19 15:12:52 -06:00
|
|
|
async watchSubdirectories(directory: string) {
|
|
|
|
const dirContent = await this.sandbox.files.list(directory)
|
|
|
|
await Promise.all(
|
|
|
|
dirContent.map(async (item) => {
|
|
|
|
if (item.type === "dir") {
|
|
|
|
console.log("Watching " + item.path)
|
|
|
|
await this.watchDirectory(item.path)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Get file content
|
2024-10-19 15:12:52 -06:00
|
|
|
async getFile(fileId: string): Promise<string | undefined> {
|
|
|
|
const file = this.sandboxFiles.fileData.find((f) => f.id === fileId)
|
|
|
|
return file?.data
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Get folder content
|
2024-10-19 15:12:52 -06:00
|
|
|
async getFolder(folderId: string): Promise<string[]> {
|
2024-10-19 18:42:44 -06:00
|
|
|
const remotePaths = await RemoteFileStorage.getFolder(this.getRemoteFileId(folderId))
|
|
|
|
return this.getLocalFileIds(remotePaths)
|
2024-10-19 15:12:52 -06:00
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Save file content
|
2024-10-19 15:12:52 -06:00
|
|
|
async saveFile(fileId: string, body: string): Promise<void> {
|
|
|
|
if (!fileId) return // handles saving when no file is open
|
|
|
|
|
|
|
|
if (Buffer.byteLength(body, "utf-8") > MAX_BODY_SIZE) {
|
|
|
|
throw new Error("File size too large. Please reduce the file size.")
|
|
|
|
}
|
2024-10-19 16:41:26 -06:00
|
|
|
await RemoteFileStorage.saveFile(this.getRemoteFileId(fileId), body)
|
2024-10-19 15:12:52 -06:00
|
|
|
const file = this.sandboxFiles.fileData.find((f) => f.id === fileId)
|
|
|
|
if (!file) return
|
|
|
|
file.data = body
|
|
|
|
|
|
|
|
await this.sandbox.files.write(path.posix.join(this.dirName, file.id), body)
|
|
|
|
this.fixPermissions()
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Move a file to a different folder
|
2024-10-19 15:12:52 -06:00
|
|
|
async moveFile(
|
|
|
|
fileId: string,
|
|
|
|
folderId: string
|
|
|
|
): Promise<(TFolder | TFile)[]> {
|
|
|
|
const fileData = this.sandboxFiles.fileData.find((f) => f.id === fileId)
|
|
|
|
const file = this.sandboxFiles.files.find((f) => f.id === fileId)
|
|
|
|
if (!fileData || !file) return this.sandboxFiles.files
|
|
|
|
|
|
|
|
const parts = fileId.split("/")
|
|
|
|
const newFileId = folderId + "/" + parts.pop()
|
|
|
|
|
|
|
|
await this.moveFileInContainer(fileId, newFileId)
|
|
|
|
|
|
|
|
await this.fixPermissions()
|
|
|
|
|
|
|
|
fileData.id = newFileId
|
|
|
|
file.id = newFileId
|
|
|
|
|
2024-10-19 16:41:26 -06:00
|
|
|
await RemoteFileStorage.renameFile(this.getRemoteFileId(fileId), this.getRemoteFileId(newFileId), fileData.data)
|
2024-10-19 18:42:44 -06:00
|
|
|
return this.updateFileStructure()
|
2024-10-19 15:12:52 -06:00
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Move a file within the container
|
2024-10-19 15:12:52 -06:00
|
|
|
private async moveFileInContainer(oldPath: string, newPath: string) {
|
|
|
|
try {
|
|
|
|
const fileContents = await this.sandbox.files.read(
|
|
|
|
path.posix.join(this.dirName, oldPath)
|
|
|
|
)
|
|
|
|
await this.sandbox.files.write(
|
|
|
|
path.posix.join(this.dirName, newPath),
|
|
|
|
fileContents
|
|
|
|
)
|
|
|
|
await this.sandbox.files.remove(path.posix.join(this.dirName, oldPath))
|
|
|
|
} catch (e) {
|
|
|
|
console.error(`Error moving file from ${oldPath} to ${newPath}:`, e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Create a new file
|
2024-10-19 15:12:52 -06:00
|
|
|
async createFile(name: string): Promise<boolean> {
|
2024-10-19 16:23:31 -06:00
|
|
|
const size: number = await RemoteFileStorage.getProjectSize(this.sandboxId)
|
2024-10-19 15:12:52 -06:00
|
|
|
if (size > 200 * 1024 * 1024) {
|
|
|
|
throw new Error("Project size exceeded. Please delete some files.")
|
|
|
|
}
|
|
|
|
|
2024-10-19 16:41:26 -06:00
|
|
|
const id = `/${name}`
|
2024-10-19 15:12:52 -06:00
|
|
|
|
|
|
|
await this.sandbox.files.write(path.posix.join(this.dirName, id), "")
|
|
|
|
await this.fixPermissions()
|
|
|
|
|
|
|
|
this.sandboxFiles.files.push({
|
|
|
|
id,
|
|
|
|
name,
|
|
|
|
type: "file",
|
|
|
|
})
|
|
|
|
|
|
|
|
this.sandboxFiles.fileData.push({
|
|
|
|
id,
|
|
|
|
data: "",
|
|
|
|
})
|
|
|
|
|
2024-10-19 16:41:26 -06:00
|
|
|
await RemoteFileStorage.createFile(this.getRemoteFileId(id))
|
2024-10-19 15:12:52 -06:00
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Create a new folder
|
2024-10-19 15:12:52 -06:00
|
|
|
async createFolder(name: string): Promise<void> {
|
2024-10-19 16:41:26 -06:00
|
|
|
const id = `/${name}`
|
2024-10-19 15:12:52 -06:00
|
|
|
await this.sandbox.files.makeDir(path.posix.join(this.dirName, id))
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Rename a file
|
2024-10-19 15:12:52 -06:00
|
|
|
async renameFile(fileId: string, newName: string): Promise<void> {
|
|
|
|
const fileData = this.sandboxFiles.fileData.find((f) => f.id === fileId)
|
|
|
|
const file = this.sandboxFiles.files.find((f) => f.id === fileId)
|
|
|
|
if (!fileData || !file) return
|
|
|
|
|
|
|
|
const parts = fileId.split("/")
|
|
|
|
const newFileId = parts.slice(0, parts.length - 1).join("/") + "/" + newName
|
|
|
|
|
|
|
|
await this.moveFileInContainer(fileId, newFileId)
|
|
|
|
await this.fixPermissions()
|
2024-10-19 16:41:26 -06:00
|
|
|
await RemoteFileStorage.renameFile(this.getRemoteFileId(fileId), this.getRemoteFileId(newFileId), fileData.data)
|
2024-10-19 15:12:52 -06:00
|
|
|
|
|
|
|
fileData.id = newFileId
|
|
|
|
file.id = newFileId
|
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Delete a file
|
2024-10-19 15:12:52 -06:00
|
|
|
async deleteFile(fileId: string): Promise<(TFolder | TFile)[]> {
|
|
|
|
const file = this.sandboxFiles.fileData.find((f) => f.id === fileId)
|
|
|
|
if (!file) return this.sandboxFiles.files
|
|
|
|
|
|
|
|
await this.sandbox.files.remove(path.posix.join(this.dirName, fileId))
|
|
|
|
this.sandboxFiles.fileData = this.sandboxFiles.fileData.filter(
|
|
|
|
(f) => f.id !== fileId
|
|
|
|
)
|
|
|
|
|
2024-10-19 16:41:26 -06:00
|
|
|
await RemoteFileStorage.deleteFile(this.getRemoteFileId(fileId))
|
2024-10-19 18:42:44 -06:00
|
|
|
return this.updateFileStructure()
|
2024-10-19 15:12:52 -06:00
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Delete a folder
|
2024-10-19 15:12:52 -06:00
|
|
|
async deleteFolder(folderId: string): Promise<(TFolder | TFile)[]> {
|
2024-10-19 16:41:26 -06:00
|
|
|
const files = await RemoteFileStorage.getFolder(this.getRemoteFileId(folderId))
|
2024-10-19 15:12:52 -06:00
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
files.map(async (file) => {
|
|
|
|
await this.sandbox.files.remove(path.posix.join(this.dirName, file))
|
|
|
|
this.sandboxFiles.fileData = this.sandboxFiles.fileData.filter(
|
|
|
|
(f) => f.id !== file
|
|
|
|
)
|
2024-10-19 16:41:26 -06:00
|
|
|
await RemoteFileStorage.deleteFile(this.getRemoteFileId(file))
|
2024-10-19 15:12:52 -06:00
|
|
|
})
|
|
|
|
)
|
|
|
|
|
2024-10-19 18:42:44 -06:00
|
|
|
return this.updateFileStructure()
|
2024-10-19 15:12:52 -06:00
|
|
|
}
|
|
|
|
|
2024-10-19 15:16:24 -06:00
|
|
|
// Close all file watchers
|
2024-10-19 15:12:52 -06:00
|
|
|
async closeWatchers() {
|
|
|
|
await Promise.all(
|
|
|
|
this.fileWatchers.map(async (handle: WatchHandle) => {
|
|
|
|
await handle.close()
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
2024-10-19 16:23:31 -06:00
|
|
|
}
|