smartbucket/ts/classes.file.ts
Dominik Schwank 8d160cefb0 fix: use overwrite to make metadata files work
During a delete the metadata file is updated. As the overwrite property was not set, the metadata
couldn't be updated and caused issues.
2024-11-18 21:08:39 +00:00

245 lines
6.7 KiB
TypeScript

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')) {
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();
await this.move({
directory: await trash.getTrashDir(),
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 = '';
const isDirectory = await this.parentDirectoryRef.bucketRef.isDirectory(pathDescriptorArg);
if (isDirectory) {
moveToPath = await helpers.reducePathDescriptorToPath({
...pathDescriptorArg,
path: plugins.path.join(pathDescriptorArg.path!, this.name),
});
}
// lets move the file
await this.parentDirectoryRef.bucketRef.fastMove({
sourcePath: this.getBasePath(),
destinationPath: moveToPath,
});
// lets move the metadatafile
const metadata = await this.getMetaData();
await metadata.metadataFile.move(pathDescriptorArg);
}
/**
* 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,
});
}
}