// classes.directory.ts import * as plugins from './plugins.js'; import { Bucket } from './classes.bucket.js'; import { File } from './classes.file.js'; import * as helpers from './helpers.js'; export class Directory { public bucketRef: Bucket; public parentDirectoryRef: Directory; public name: string; public tree: string[]; public files: string[]; public folders: string[]; constructor(bucketRefArg: Bucket, parentDirectory: Directory, name: string) { this.bucketRef = bucketRefArg; this.parentDirectoryRef = parentDirectory; this.name = name; } /** * returns an array of parent directories */ public getParentDirectories(): Directory[] { let parentDirectories: Directory[] = []; if (this.parentDirectoryRef) { parentDirectories.push(this.parentDirectoryRef); parentDirectories = parentDirectories.concat(this.parentDirectoryRef.getParentDirectories()); } return parentDirectories; } /** * returns the directory level */ public getDirectoryLevel(): number { return this.getParentDirectories().length; } /** * updates the base path */ public getBasePath(): string { const parentDirectories = this.getParentDirectories(); let basePath = ''; for (const parentDir of parentDirectories) { if (!parentDir.name && !basePath) { basePath = this.name + '/'; continue; } if (parentDir.name && !basePath) { basePath = parentDir.name + '/' + this.name + '/'; continue; } if (parentDir.name && basePath) { basePath = parentDir.name + '/' + basePath; continue; } } return basePath; } /** * gets a file by name */ public async getFile(optionsArg: { path: string; createWithContents?: string | Buffer; getFromTrash?: boolean; }): Promise { const pathDescriptor = { directory: this, path: optionsArg.path, }; const exists = await this.bucketRef.fastExists({ path: await helpers.reducePathDescriptorToPath(pathDescriptor), }); if (!exists && optionsArg.getFromTrash) { const trash = await this.bucketRef.getTrash(); const trashedFile = await trash.getTrashedFileByOriginalName(pathDescriptor); return trashedFile; } if (!exists && !optionsArg.createWithContents) { return null; } if (!exists && optionsArg.createWithContents) { await File.create({ directory: this, name: optionsArg.path, contents: optionsArg.createWithContents, }); } return new File({ directoryRefArg: this, fileName: optionsArg.path, }); } /** * gets a file strictly * @param args * @returns */ public async getFileStrict(...args: Parameters) { const file = await this.getFile(...args); if (!file) { throw new Error(`File not found at path '${args[0].path}'`); } return file; } /** * lists all files */ public async listFiles(): Promise { const command = new plugins.s3.ListObjectsV2Command({ Bucket: this.bucketRef.name, Prefix: this.getBasePath(), Delimiter: '/', }); const response = await this.bucketRef.smartbucketRef.s3Client.send(command); const fileArray: File[] = []; response.Contents?.forEach((item) => { if (item.Key && !item.Key.endsWith('/')) { const subtractedPath = item.Key.replace(this.getBasePath(), ''); if (!subtractedPath.includes('/')) { fileArray.push( new File({ directoryRefArg: this, fileName: subtractedPath, }) ); } } }); return fileArray; } /** * lists all folders */ public async listDirectories(): Promise { try { const command = new plugins.s3.ListObjectsV2Command({ Bucket: this.bucketRef.name, Prefix: this.getBasePath(), Delimiter: '/', }); const response = await this.bucketRef.smartbucketRef.s3Client.send(command); const directoryArray: Directory[] = []; if (response.CommonPrefixes) { response.CommonPrefixes.forEach((item) => { if (item.Prefix) { const subtractedPath = item.Prefix.replace(this.getBasePath(), ''); if (subtractedPath.endsWith('/')) { const dirName = subtractedPath.slice(0, -1); // Ensure the directory name is not empty (which would indicate the base directory itself) if (dirName) { directoryArray.push(new Directory(this.bucketRef, this, dirName)); } } } }); } return directoryArray; } catch (error) { console.error('Error listing directories:', error); throw error; } } /** * gets an array that has all objects with a certain prefix */ public async getTreeArray() { const command = new plugins.s3.ListObjectsV2Command({ Bucket: this.bucketRef.name, Prefix: this.getBasePath(), Delimiter: '/', }); const response = await this.bucketRef.smartbucketRef.s3Client.send(command); return response.Contents; } /** * gets a sub directory by name */ public async getSubDirectoryByName(dirNameArg: string, optionsArg: { getEmptyDirectory?: boolean; createWithInitializerFile?: boolean; } = {}): Promise { const dirNameArray = dirNameArg.split('/').filter(str => str.trim() !== ""); optionsArg = { getEmptyDirectory: false, createWithInitializerFile: false, ...optionsArg, } const getDirectory = async (directoryArg: Directory, dirNameToSearch: string, isFinalDirectory: boolean) => { const directories = await directoryArg.listDirectories(); let returnDirectory = directories.find((directory) => { return directory.name === dirNameToSearch; }); if (returnDirectory) { return returnDirectory; } if (optionsArg.getEmptyDirectory || optionsArg.createWithInitializerFile) { returnDirectory = new Directory(this.bucketRef, this, dirNameToSearch); } if (isFinalDirectory && optionsArg.createWithInitializerFile) { returnDirectory?.createEmptyFile('00init.txt'); } return returnDirectory || null; }; let wantedDirectory: Directory | null = null; let counter = 0; for (const dirNameToSearch of dirNameArray) { counter++; const directoryToSearchIn = wantedDirectory ? wantedDirectory : this; wantedDirectory = await getDirectory(directoryToSearchIn, dirNameToSearch, counter === dirNameArray.length); } return wantedDirectory || null; } public async getSubDirectoryByNameStrict(...args: Parameters) { const directory = await this.getSubDirectoryByName(...args); if (!directory) { throw new Error(`Directory not found at path '${args[0]}'`); } return directory; } /** * moves the directory */ public async move() { // TODO throw new Error('Moving a directory is not yet implemented'); } /** * creates an empty file within this directory * @param relativePathArg */ public async createEmptyFile(relativePathArg: string) { const emptyFile = await File.create({ directory: this, name: relativePathArg, contents: '', }); return emptyFile; } // file operations public async fastPut(optionsArg: { path: string; contents: string | Buffer }) { const path = plugins.path.join(this.getBasePath(), optionsArg.path); await this.bucketRef.fastPut({ path, contents: optionsArg.contents, }); } public async fastGet(optionsArg: { path: string }) { const path = plugins.path.join(this.getBasePath(), optionsArg.path); const result = await this.bucketRef.fastGet({ path, }); return result; } public fastGetStream( optionsArg: { path: string; }, typeArg: 'webstream' ): Promise; public async fastGetStream( optionsArg: { path: string; }, typeArg: 'nodestream' ): Promise; /** * fastGetStream * @param optionsArg * @returns */ public async fastGetStream( optionsArg: { path: string }, typeArg: 'webstream' | 'nodestream' ): Promise { const path = plugins.path.join(this.getBasePath(), optionsArg.path); const result = await this.bucketRef.fastGetStream( { path, }, typeArg as any ); return result; } /** * fast put stream */ public async fastPutStream(optionsArg: { path: string; stream: plugins.stream.Readable; }): Promise { const path = plugins.path.join(this.getBasePath(), optionsArg.path); await this.bucketRef.fastPutStream({ path, readableStream: optionsArg.stream, }); } /** * removes a file within the directory * uses file class to make sure effects for metadata etc. are handled correctly * @param optionsArg */ public async fastRemove(optionsArg: { path: string /** * wether the file should be placed into trash. Default is false. */ mode?: 'permanent' | 'trash'; }) { const file = await this.getFile({ path: optionsArg.path, }); await file.delete({ mode: optionsArg.mode ? optionsArg.mode : 'permanent', }); } /** * deletes the directory with all its contents */ public async delete(optionsArg: { mode?: 'permanent' | 'trash'; }) { const deleteDirectory = async (directoryArg: Directory) => { const childDirectories = await directoryArg.listDirectories(); if (childDirectories.length === 0) { console.log('Directory empty! Path complete!'); } else { for (const childDir of childDirectories) { await deleteDirectory(childDir); } } const files = await directoryArg.listFiles(); for (const file of files) { await file.delete({ mode: optionsArg.mode ? optionsArg.mode : 'permanent', }) } }; await deleteDirectory(this); } }