import * as plugins from './plugins.js'; import * as helpers from './helpers.js'; import * as interfaces from './interfaces.js'; import { Directory } from './classes.directory.js'; import { MetaData } from './classes.metadata.js'; /** * represents a file in a directory */ export class File { // STATIC /** * creates a file in draft mode * you need to call .save() to store it in s3 * @param optionsArg */ public static async create(optionsArg: { directory: Directory; name: string; contents: Buffer | string | plugins.stream.Readable; /** * if contents are of type string, you can specify the encoding here */ encoding?: 'utf8' | 'binary'; }): Promise { const contents = typeof optionsArg.contents === 'string' ? Buffer.from(optionsArg.contents, optionsArg.encoding) : optionsArg.contents; const file = new File({ directoryRefArg: optionsArg.directory, fileName: optionsArg.name, }); if (contents instanceof plugins.stream.Readable) { await optionsArg.directory.fastPutStream({ path: optionsArg.name, stream: contents, }); } else { await optionsArg.directory.fastPut({ path: optionsArg.name, contents: contents, }); } return file; } // INSTANCE public parentDirectoryRef: Directory; public name: string; /** * get the full path to the file * @returns the full path to the file */ public getBasePath(): string { return plugins.path.join(this.parentDirectoryRef.getBasePath(), this.name); } constructor(optionsArg: { directoryRefArg: Directory; fileName: string }) { this.parentDirectoryRef = optionsArg.directoryRefArg; this.name = optionsArg.fileName; } public async getContentsAsString(): Promise { const fileBuffer = await this.getContents(); return fileBuffer.toString(); } public async getContents(): Promise { const resultBuffer = await this.parentDirectoryRef.bucketRef.fastGet({ path: this.getBasePath(), }); return resultBuffer; } public async getReadStream(typeArg: 'webstream'): Promise; public async getReadStream(typeArg: 'nodestream'): Promise; public async getReadStream( typeArg: 'nodestream' | 'webstream' ): Promise { const readStream = this.parentDirectoryRef.bucketRef.fastGetStream( { path: this.getBasePath(), }, typeArg as any ); return readStream; } /** * deletes this file */ public async delete(optionsArg?: { mode: 'trash' | 'permanent' }) { optionsArg = { ...{ mode: 'permanent', }, ...optionsArg, }; if (optionsArg.mode === 'permanent') { await this.parentDirectoryRef.bucketRef.fastRemove({ path: this.getBasePath(), }); if (!this.name.endsWith('.metadata')) { if (await this.hasMetaData()) { const metadata = await this.getMetaData(); await metadata.metadataFile.delete(optionsArg); } } } else if (optionsArg.mode === 'trash') { const metadata = await this.getMetaData(); await metadata.storeCustomMetaData({ key: 'recycle', value: { deletedAt: Date.now(), originalPath: this.getBasePath(), }, }); const trash = await this.parentDirectoryRef.bucketRef.getTrash(); const trashDir = await trash.getTrashDir(); await this.move({ directory: trashDir, path: await trash.getTrashKeyByOriginalBasePath(this.getBasePath()), }); } await this.parentDirectoryRef.listFiles(); } /** * allows locking the file * @param optionsArg */ public async lock(optionsArg?: { timeoutMillis?: number }) { const metadata = await this.getMetaData(); await metadata.setLock({ lock: 'locked', expires: Date.now() + (optionsArg?.timeoutMillis || 1000), }); } /** * actively unlocks a file * */ public async unlock(optionsArg?: { /** * unlock the file even if not locked from this instance */ force?: boolean; }) { const metadata = await this.getMetaData(); await metadata.removeLock({ force: optionsArg?.force, }); } public async updateWithContents(optionsArg: { contents: Buffer | string | plugins.stream.Readable | ReadableStream; encoding?: 'utf8' | 'binary'; }) { if ( optionsArg.contents instanceof plugins.stream.Readable || optionsArg.contents instanceof ReadableStream ) { await this.parentDirectoryRef.bucketRef.fastPutStream({ path: this.getBasePath(), readableStream: optionsArg.contents, overwrite: true, }); } else if (Buffer.isBuffer(optionsArg.contents)) { await this.parentDirectoryRef.bucketRef.fastPut({ path: this.getBasePath(), contents: optionsArg.contents, overwrite: true, }); } else if (typeof optionsArg.contents === 'string') { await this.parentDirectoryRef.bucketRef.fastPut({ path: this.getBasePath(), contents: Buffer.from(optionsArg.contents, optionsArg.encoding), overwrite: true, }); } } /** * moves the file to another directory */ public async move(pathDescriptorArg: interfaces.IPathDecriptor) { let moveToPath: string = ''; const isDirectory = await this.parentDirectoryRef.bucketRef.isDirectory(pathDescriptorArg); if (isDirectory) { moveToPath = await helpers.reducePathDescriptorToPath({ ...pathDescriptorArg, path: plugins.path.join(pathDescriptorArg.path!, this.name), }); } else { moveToPath = await helpers.reducePathDescriptorToPath(pathDescriptorArg); } // lets move the file await this.parentDirectoryRef.bucketRef.fastMove({ sourcePath: this.getBasePath(), destinationPath: moveToPath, overwrite: true, }); // lets move the metadatafile if (!this.name.endsWith('.metadata')) { const metadata = await this.getMetaData(); await this.parentDirectoryRef.bucketRef.fastMove({ sourcePath: metadata.metadataFile.getBasePath(), destinationPath: moveToPath + '.metadata', overwrite: true, }); } // lets update references of this const baseDirectory = await this.parentDirectoryRef.bucketRef.getBaseDirectory(); this.parentDirectoryRef = await baseDirectory.getSubDirectoryByNameStrict( pathDescriptorArg.directory?.getBasePath()! ); this.name = pathDescriptorArg.path!; } public async hasMetaData(): Promise { if (!this.name.endsWith('.metadata')) { const hasMetadataBool = MetaData.hasMetaData({ file: this, }); return hasMetadataBool; } else { return false; } } /** * allows updating the metadata of a file * @param updatedMetadata */ public async getMetaData() { if (this.name.endsWith('.metadata')) { throw new Error('metadata files cannot have metadata'); } const metadata = await MetaData.createForFile({ file: this, }); return metadata; } /** * gets the contents as json */ public async getJsonData() { const json = await this.getContentsAsString(); const parsed = await JSON.parse(json); return parsed; } public async writeJsonData(dataArg: any) { await this.updateWithContents({ contents: JSON.stringify(dataArg), }); } public async getMagicBytes(optionsArg: { length: number }): Promise { return this.parentDirectoryRef.bucketRef.getMagicBytes({ path: this.getBasePath(), length: optionsArg.length, }); } }