diff --git a/package.json b/package.json index ea49560..fa825b8 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@push.rocks/tapbundle": "^5.0.23" }, "dependencies": { + "@push.rocks/smartmime": "^2.0.0", "@push.rocks/smartpath": "^5.0.18", "@push.rocks/smartpromise": "^4.0.3", "@push.rocks/smartrx": "^3.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e376d2e..2eee75e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@push.rocks/smartmime': + specifier: ^2.0.0 + version: 2.0.0 '@push.rocks/smartpath': specifier: ^5.0.18 version: 5.0.18 @@ -409,6 +412,9 @@ packages: '@push.rocks/smartmime@1.0.6': resolution: {integrity: sha512-PHd+I4UcsnOATNg8wjDsSAmmJ4CwQFrQCNzd0HSJMs4ZpiK3Ya91almd6GLpDPU370U4HFh4FaPF4eEAI6vkJQ==} + '@push.rocks/smartmime@2.0.0': + resolution: {integrity: sha512-yNEYrQzWjxwinCT8djw9eFumpCIvIQQS9KXWLH0LT9COlFoaP/ruk7pogrGYKCo20tFITJyO6NmMCa24402rvA==} + '@push.rocks/smartnetwork@3.0.2': resolution: {integrity: sha512-s6CNGzQ1n/d/6cOKXbxeW6/tO//dr1woLqI01g7XhqTriw0nsm2G2kWaZh2J0VOguGNWBgQVCIpR0LjdRNWb3g==} @@ -609,6 +615,9 @@ packages: '@tempfix/watcher@2.3.0': resolution: {integrity: sha512-a2qVQffcrnetehvwsN+LdipxQ6jejwZLgAvS9/91+C0gP4CKyikY01c0tSs0I4tSL7qHdCw1Fx0quLw+A9uyLA==} + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@tsclass/tsclass@3.0.48': resolution: {integrity: sha512-hC65UvDlp9qvsl6OcIZXz0JNiWZ0gyzsTzbXpg215sGxopgbkOLCr6E0s4qCTnweYm95gt2AdY95uP7M7kExaQ==} @@ -1445,6 +1454,10 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} + file-type@19.0.0: + resolution: {integrity: sha512-s7cxa7/leUWLiXO78DVVfBVse+milos9FitauDLG1pI7lNaJ2+5lzPnr2N24ym+84HVwJL6hVuGfgVE+ALvU8Q==} + engines: {node: '>=18'} + fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -2142,6 +2155,11 @@ packages: engines: {node: '>=4'} hasBin: true + mime@4.0.3: + resolution: {integrity: sha512-KgUb15Oorc0NEKPbvfa0wRU+PItIEZmiv+pyAO2i0oTIVTJhlzMclU7w4RXWQrSOVH5ax/p/CkIO7KI4OyFJTQ==} + engines: {node: '>=16'} + hasBin: true + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2358,6 +2376,10 @@ packages: resolution: {integrity: sha512-AHXsYi9EcYlSm3uUANz7h5WSktHiyTnUeHqdWmyRdjdMhgq9LgZ8pggl9FOUGuCLVfe+NKxp2k9sEMCH3tHIEg==} engines: {node: '>=14'} + peek-readable@5.0.0: + resolution: {integrity: sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==} + engines: {node: '>=14.16'} + pend@1.2.0: resolution: {integrity: sha1-elfrVQpng/kRUzH89GY9XI4AelA=} @@ -2464,6 +2486,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-web-to-node-stream@3.0.2: + resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} + engines: {node: '>=8'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2682,6 +2708,10 @@ packages: strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + strtok3@7.0.0: + resolution: {integrity: sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==} + engines: {node: '>=14.16'} + stubborn-fs@1.2.5: resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} @@ -2732,6 +2762,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + token-types@5.0.1: + resolution: {integrity: sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==} + engines: {node: '>=14.16'} + tr46@0.0.3: resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=} @@ -3630,6 +3664,12 @@ snapshots: '@types/mime-types': 2.1.4 mime-types: 2.1.35 + '@push.rocks/smartmime@2.0.0': + dependencies: + '@types/mime-types': 2.1.4 + file-type: 19.0.0 + mime: 4.0.3 + '@push.rocks/smartnetwork@3.0.2': dependencies: '@pushrocks/smartping': 1.0.8 @@ -4037,6 +4077,8 @@ snapshots: dependencies: stubborn-fs: 1.2.5 + '@tokenizer/token@0.3.0': {} + '@tsclass/tsclass@3.0.48': dependencies: type-fest: 2.19.0 @@ -4969,6 +5011,12 @@ snapshots: dependencies: is-unicode-supported: 2.0.0 + file-type@19.0.0: + dependencies: + readable-web-to-node-stream: 3.0.2 + strtok3: 7.0.0 + token-types: 5.0.1 + fill-range@7.0.1: dependencies: to-regex-range: 5.0.1 @@ -5956,6 +6004,8 @@ snapshots: mime@1.6.0: {} + mime@4.0.3: {} + mimic-fn@2.1.0: {} mimic-response@3.1.0: {} @@ -6125,6 +6175,8 @@ snapshots: transitivePeerDependencies: - supports-color + peek-readable@5.0.0: {} + pend@1.2.0: {} picocolors@1.0.1: {} @@ -6248,6 +6300,10 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-web-to-node-stream@3.0.2: + dependencies: + readable-stream: 3.6.2 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -6530,6 +6586,11 @@ snapshots: strnum@1.0.5: {} + strtok3@7.0.0: + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 5.0.0 + stubborn-fs@1.2.5: {} supports-color@5.5.0: @@ -6588,6 +6649,11 @@ snapshots: toidentifier@1.0.1: {} + token-types@5.0.1: + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + tr46@0.0.3: {} tr46@2.1.0: diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index b90865a..e00bec9 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartbucket', - version: '3.0.3', + version: '3.0.4', description: 'A TypeScript library that offers simple, cloud-independent object storage with features like bucket creation, file management, and directory management.' } diff --git a/ts/smartbucket.classes.bucket.ts b/ts/classes.bucket.ts similarity index 66% rename from ts/smartbucket.classes.bucket.ts rename to ts/classes.bucket.ts index c84e7ed..c836507 100644 --- a/ts/smartbucket.classes.bucket.ts +++ b/ts/classes.bucket.ts @@ -1,6 +1,6 @@ -import * as plugins from './smartbucket.plugins.js'; -import { SmartBucket } from './smartbucket.classes.smartbucket.js'; -import { Directory } from './smartbucket.classes.directory.js'; +import * as plugins from './plugins.js'; +import { SmartBucket } from './classes.smartbucket.js'; +import { Directory } from './classes.directory.js'; export class Bucket { public static async getBucketByName(smartbucketRef: SmartBucket, bucketNameArg: string) { @@ -52,15 +52,35 @@ export class Bucket { public async fastPut(optionsArg: { path: string; contents: string | Buffer; + overwrite?: boolean; }): Promise { - const streamIntake = new plugins.smartstream.StreamIntake(); - const putPromise = this.smartbucketRef.minioClient - .putObject(this.name, optionsArg.path, streamIntake) - .catch((e) => console.log(e)); - streamIntake.pushData(optionsArg.contents); - streamIntake.signalEnd(); - const response = await putPromise; + try { + // Check if the object already exists + const exists = await this.fastExists({ path: optionsArg.path }); + + if (exists && !optionsArg.overwrite) { + console.error(`Object already exists at path '${optionsArg.path}' in bucket '${this.name}'.`); + return; + } else if (exists && optionsArg.overwrite) { + console.log(`Overwriting existing object at path '${optionsArg.path}' in bucket '${this.name}'.`); + } else { + console.log(`Creating new object at path '${optionsArg.path}' in bucket '${this.name}'.`); + } + + // Proceed with putting the object + const streamIntake = new plugins.smartstream.StreamIntake(); + const putPromise = this.smartbucketRef.minioClient.putObject(this.name, optionsArg.path, streamIntake); + streamIntake.pushData(optionsArg.contents); + streamIntake.signalEnd(); + await putPromise; + + console.log(`Object '${optionsArg.path}' has been successfully stored in bucket '${this.name}'.`); + } catch (error) { + console.error(`Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`, error); + throw error; + } } + /** * get file @@ -126,20 +146,42 @@ export class Bucket { path: string; dataStream: plugins.stream.Readable; nativeMetadata?: { [key: string]: string }; + overwrite?: boolean; }): Promise { - await this.smartbucketRef.minioClient.putObject( - this.name, - optionsArg.path, - optionsArg.dataStream, - null, - ...(optionsArg.nativeMetadata - ? (() => { - const returnObject: any = {}; - return returnObject; - })() - : {}) - ); + try { + // Check if the object already exists + const exists = await this.fastExists({ path: optionsArg.path }); + + if (exists && !optionsArg.overwrite) { + console.error(`Object already exists at path '${optionsArg.path}' in bucket '${this.name}'.`); + return; + } else if (exists && optionsArg.overwrite) { + console.log(`Overwriting existing object at path '${optionsArg.path}' in bucket '${this.name}'.`); + } else { + console.log(`Creating new object at path '${optionsArg.path}' in bucket '${this.name}'.`); + } + + // Proceed with putting the object + await this.smartbucketRef.minioClient.putObject( + this.name, + optionsArg.path, + optionsArg.dataStream, + null, + ...(optionsArg.nativeMetadata + ? (() => { + const returnObject: any = {}; + return returnObject; + })() + : {}) + ); + + console.log(`Object '${optionsArg.path}' has been successfully stored in bucket '${this.name}'.`); + } catch (error) { + console.error(`Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`, error); + throw error; + } } + public async copyObject(optionsArg: { /** @@ -198,7 +240,12 @@ export class Bucket { await this.smartbucketRef.minioClient.removeObject(this.name, optionsArg.path); } - public async doesObjectExist(optionsArg: { + /** + * check wether file exists + * @param optionsArg + * @returns + */ + public async fastExists(optionsArg: { path: string; }): Promise { try { @@ -215,4 +262,10 @@ export class Bucket { } } } + + public async fastStat(optionsArg: { + path: string; + }) { + return this.smartbucketRef.minioClient.statObject(this.name, optionsArg.path); + } } diff --git a/ts/smartbucket.classes.directory.ts b/ts/classes.directory.ts similarity index 89% rename from ts/smartbucket.classes.directory.ts rename to ts/classes.directory.ts index ced2763..caa4d1f 100644 --- a/ts/smartbucket.classes.directory.ts +++ b/ts/classes.directory.ts @@ -1,6 +1,6 @@ -import * as plugins from './smartbucket.plugins.js'; -import { Bucket } from './smartbucket.classes.bucket.js'; -import { File } from './smartbucket.classes.file.js'; +import * as plugins from './plugins.js'; +import { Bucket } from './classes.bucket.js'; +import { File } from './classes.file.js'; export class Directory { public bucketRef: Bucket; @@ -59,6 +59,32 @@ export class Directory { return basePath; } + /** + * gets a file by name + */ + public async getFile(optionsArg: { + name: string; + createWithContents?: string | Buffer; + }): Promise { + // check wether the file exists + const exists = await this.bucketRef.fastExists({ + path: this.getBasePath() + optionsArg.name, + }); + if (!exists && !optionsArg.createWithContents) { + return null; + } + if (!exists && optionsArg.createWithContents) { + await this.fastPut({ + path: optionsArg.name, + contents: optionsArg.createWithContents, + }); + } + return new File({ + directoryRefArg: this, + fileName: optionsArg.name, + }) + } + /** * lists all files */ diff --git a/ts/classes.file.ts b/ts/classes.file.ts new file mode 100644 index 0000000..e3ba742 --- /dev/null +++ b/ts/classes.file.ts @@ -0,0 +1,154 @@ +import * as plugins from './plugins.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) {} else { + await optionsArg.directory.fastPut({ + path: optionsArg.name, + contents: contents, + }); + } + return file; + } + + // INSTANCE + public parentDirectoryRef: Directory; + public name: string; + + 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() { + const readStream = this.parentDirectoryRef.bucketRef.fastGetStream({ + path: this.getBasePath(), + }); + } + + /** + * removes this file + * for using recycling mechanics use .delete() + */ + public async remove() { + await this.parentDirectoryRef.bucketRef.fastRemove({ + path: this.getBasePath(), + }); + if (!this.name.endsWith('.metadata')) { + await this.parentDirectoryRef.bucketRef.fastRemove({ + path: this.getBasePath() + '.metadata', + }); + } + await this.parentDirectoryRef.listFiles(); + } + + /** + * deletes the file with recycling mechanics + */ + public async delete() { + await this.remove(); + } + + /** + * allows locking the file + * @param optionsArg + */ + public async lock(optionsArg?: { timeoutMillis?: number }) { + const metadata = await this.getMetaData(); + await metadata.setLock({ + lock: 'locked', + expires: new Date(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; + }) { + + } + + public async updateWithContents(optionsArg: { + contents: Buffer | string | plugins.stream.Readable; + encoding?: 'utf8' | 'binary'; + }) { + if (optionsArg.contents instanceof plugins.stream.Readable) { + await this.parentDirectoryRef.bucketRef.fastPutStream({ + path: this.getBasePath(), + dataStream: optionsArg.contents, + }); + } else if (Buffer.isBuffer(optionsArg.contents)) { + await this.parentDirectoryRef.bucketRef.fastPut({ + path: this.getBasePath(), + contents: optionsArg.contents, + }); + } else if (typeof optionsArg.contents === 'string') { + await this.parentDirectoryRef.bucketRef.fastPut({ + path: this.getBasePath(), + contents: Buffer.from(optionsArg.contents, optionsArg.encoding), + }); + } + } + + /** + * allows updating the metadata of a file + * @param updatedMetadata + */ + public async getMetaData() { + const metadata = await MetaData.createForFile({ + file: this, + }); + return metadata; + } +} diff --git a/ts/classes.metadata.ts b/ts/classes.metadata.ts new file mode 100644 index 0000000..c9c4fc6 --- /dev/null +++ b/ts/classes.metadata.ts @@ -0,0 +1,105 @@ +import * as plugins from './plugins.js'; + +import { File } from './classes.file.js'; + +export class MetaData { + // static + public static async createForFile(optionsArg: { + file: File; + }) { + const metaData = new MetaData(); + metaData.fileRef = optionsArg.file; + + // lets find the existing metadata file + metaData.metadataFile = await metaData.fileRef.parentDirectoryRef.getFile({ + name: metaData.fileRef.name + '.metadata', + createWithContents: '{}', + }); + + return metaData; + } + + // instance + /** + * the file that contains the metadata + */ + metadataFile: File; + + /** + * the file that the metadata is for + */ + fileRef: File; + + public async getFileType(optionsArg?: { + useFileExtension?: boolean; + useMagicBytes?: boolean; + }): Promise { + if (optionsArg && optionsArg.useFileExtension || optionsArg.useFileExtension === undefined) { + return plugins.path.extname(this.fileRef.name); + } + }; + + /** + * gets the size of the fileRef + */ + public async getSizeInBytes(): Promise { + const stat = await this.fileRef.parentDirectoryRef.bucketRef.fastStat({ + path: this.fileRef.getBasePath(), + }); + return stat.size; + }; + + private prefixCustomMetaData = 'custom_'; + + public async storeCustomMetaData(optionsArg: { + key: string; + value: T; + }) { + const json = await this.metadataFile.getContentsAsString(); + const parsed = await JSON.parse(json); + parsed[this.prefixCustomMetaData + optionsArg.key] = optionsArg.value; + await this.metadataFile.updateWithContents({ + contents: JSON.stringify(parsed), + }); + } + + public async getCustomMetaData(optionsArg: { + key: string; + }): Promise { + const json = await this.metadataFile.getContentsAsString(); + const parsed = await JSON.parse(json); + return parsed[this.prefixCustomMetaData + optionsArg.key]; + } + + public async deleteCustomMetaData(optionsArg: { + key: string; + }) { + const json = await this.metadataFile.getContentsAsString(); + const parsed = await JSON.parse(json); + delete parsed[this.prefixCustomMetaData + optionsArg.key]; + await this.metadataFile.updateWithContents({ + contents: JSON.stringify(parsed), + }); + } + + /** + * set a lock on the ref file + * @param optionsArg + */ + public async setLock(optionsArg: { + lock: string; + expires: Date; + }) { + + } + + /** + * remove the lock on the ref file + * @param optionsArg + */ + public async removeLock(optionsArg: { + force: boolean; + }) { + + } +} \ No newline at end of file diff --git a/ts/smartbucket.classes.smartbucket.ts b/ts/classes.smartbucket.ts similarity index 89% rename from ts/smartbucket.classes.smartbucket.ts rename to ts/classes.smartbucket.ts index 07f173a..286ed27 100644 --- a/ts/smartbucket.classes.smartbucket.ts +++ b/ts/classes.smartbucket.ts @@ -1,5 +1,5 @@ -import * as plugins from './smartbucket.plugins.js'; -import { Bucket } from './smartbucket.classes.bucket.js'; +import * as plugins from './plugins.js'; +import { Bucket } from './classes.bucket.js'; export class SmartBucket { public config: plugins.tsclass.storage.IS3Descriptor; diff --git a/ts/index.ts b/ts/index.ts index d20f909..1aea0bc 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -1,4 +1,4 @@ -export * from './smartbucket.classes.smartbucket.js'; -export * from './smartbucket.classes.bucket.js'; -export * from './smartbucket.classes.directory.js'; -export * from './smartbucket.classes.file.js'; +export * from './classes.smartbucket.js'; +export * from './classes.bucket.js'; +export * from './classes.directory.js'; +export * from './classes.file.js'; diff --git a/ts/smartbucket.plugins.ts b/ts/plugins.ts similarity index 80% rename from ts/smartbucket.plugins.ts rename to ts/plugins.ts index 2fb4845..cf4c08c 100644 --- a/ts/smartbucket.plugins.ts +++ b/ts/plugins.ts @@ -5,12 +5,13 @@ import * as stream from 'stream'; export { path, stream }; // @push.rocks scope +import * as smartmime from '@push.rocks/smartmime'; import * as smartpath from '@push.rocks/smartpath'; import * as smartpromise from '@push.rocks/smartpromise'; import * as smartrx from '@push.rocks/smartrx'; import * as smartstream from '@push.rocks/smartstream'; -export { smartpath, smartpromise, smartrx, smartstream }; +export { smartmime, smartpath, smartpromise, smartrx, smartstream }; // @tsclass import * as tsclass from '@tsclass/tsclass'; diff --git a/ts/smartbucket.classes.file.ts b/ts/smartbucket.classes.file.ts deleted file mode 100644 index 9da935f..0000000 --- a/ts/smartbucket.classes.file.ts +++ /dev/null @@ -1,140 +0,0 @@ -import * as plugins from './smartbucket.plugins.js'; -import { Directory } from './smartbucket.classes.directory.js'; - -export interface IFileMetaData { - name: string; - fileType: string; - size: string; -} - -/** - * 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) {} else { - await optionsArg.directory.fastPut({ - path: optionsArg.name, - contents: contents, - }); - } - return file; - } - - // INSTANCE - public parentDirectoryRef: Directory; - public name: string; - - public path: string; - public metaData: IFileMetaData; - - constructor(optionsArg: { directoryRefArg: Directory; fileName: string }) { - this.parentDirectoryRef = optionsArg.directoryRefArg; - this.name = optionsArg.fileName; - } - - public async getContentAsString() { - const fileBuffer = await this.getContentAsBuffer(); - return fileBuffer.toString(); - } - - public async getContentAsBuffer() { - const done = plugins.smartpromise.defer(); - const fileStream = await this.parentDirectoryRef.bucketRef.smartbucketRef.minioClient - .getObject(this.parentDirectoryRef.bucketRef.name, this.path) - .catch((e) => console.log(e)); - let completeFile = Buffer.from(''); - const duplexStream = new plugins.smartstream.SmartDuplex( - { - writeFunction: async (chunk) => { - completeFile = Buffer.concat([chunk]); - return chunk; - }, - finalFunction: async (cb) => { - done.resolve(); - return Buffer.from(''); - }, - } - ); - - if (!fileStream) { - return null; - } - - fileStream.pipe(duplexStream); - await done.promise; - return completeFile; - } - - public async readStreaming() { - // TODO - throw new Error('not yet implemented'); - } - - /** - * removes this file - */ - public async remove() { - await this.parentDirectoryRef.bucketRef.smartbucketRef.minioClient.removeObject( - this.parentDirectoryRef.bucketRef.name, - this.path - ); - await this.parentDirectoryRef.listFiles(); - } - - /** - * deletes the file - */ - public async delete() {} - - /** - * allows locking the file - * @param optionsArg - */ - public async lock(optionsArg?: { timeoutMillis?: number }) {} - - /** - * actively unlocks a file - * - */ - public async unlock(optionsArg?: { - /** - * unlock the file even if not locked from this instance - */ - force?: boolean; - }) {} - - public async updateWithContents(optionsArg: { - contents: Buffer | string | plugins.stream.Readable; - encoding?: 'utf8' | 'binary'; - }) {} - - /** - * allows updating the metadata of a file - * @param updatedMetadata - */ - public async updateMetaData(updatedMetadata: any) {} -}