UNPKG

@push.rocks/smartbucket

Version:

A TypeScript library providing a cloud-agnostic interface for managing object storage with functionalities like bucket management, file and directory operations, and advanced features such as metadata handling and file locking.

304 lines (281 loc) 8.5 kB
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<File> { 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<string> { const fileBuffer = await this.getContents(); return fileBuffer.toString(); } public async getContents(): Promise<Buffer> { const resultBuffer = await this.parentDirectoryRef.bucketRef.fastGet({ path: this.getBasePath(), }); return resultBuffer; } public async getReadStream(typeArg: 'webstream'): Promise<ReadableStream>; public async getReadStream(typeArg: 'nodestream'): Promise<plugins.stream.Readable>; public async getReadStream( typeArg: 'nodestream' | 'webstream' ): Promise<ReadableStream | plugins.stream.Readable> { 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(); } /** * restores */ public async restore(optionsArg: { useOriginalPath?: boolean; toPath?: string; overwrite?: boolean; } = {}) { optionsArg = { useOriginalPath: (() => { return optionsArg.toPath ? false : true; })(), overwrite: false, ...optionsArg, }; const metadata = await this.getMetaData(); const moveToPath = optionsArg.toPath || (await metadata.getCustomMetaData({ key: 'recycle' })).originalPath; await metadata.deleteCustomMetaData({ key: 'recycle' }) await this.move({ path: moveToPath, }); } /** * 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 || false, }); } 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( await helpers.reducePathDescriptorToPath(pathDescriptorArg), { couldBeFilePath: true, } ); this.name = pathDescriptorArg.path!; } public async hasMetaData(): Promise<boolean> { 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<Buffer> { return this.parentDirectoryRef.bucketRef.getMagicBytes({ path: this.getBasePath(), length: optionsArg.length, }); } }