finish trash

This commit is contained in:
Philipp Kunz 2024-06-10 16:47:20 +02:00
parent 9629a04da6
commit 4b70edb947
7 changed files with 178 additions and 97 deletions

View File

@ -24,6 +24,7 @@
"@push.rocks/smartpromise": "^4.0.3", "@push.rocks/smartpromise": "^4.0.3",
"@push.rocks/smartrx": "^3.0.7", "@push.rocks/smartrx": "^3.0.7",
"@push.rocks/smartstream": "^3.0.44", "@push.rocks/smartstream": "^3.0.44",
"@push.rocks/smartstring": "^4.0.15",
"@push.rocks/smartunique": "^3.0.9", "@push.rocks/smartunique": "^3.0.9",
"@tsclass/tsclass": "^4.0.55", "@tsclass/tsclass": "^4.0.55",
"minio": "^8.0.0" "minio": "^8.0.0"

3
pnpm-lock.yaml generated
View File

@ -23,6 +23,9 @@ importers:
'@push.rocks/smartstream': '@push.rocks/smartstream':
specifier: ^3.0.44 specifier: ^3.0.44
version: 3.0.44 version: 3.0.44
'@push.rocks/smartstring':
specifier: ^4.0.15
version: 4.0.15
'@push.rocks/smartunique': '@push.rocks/smartunique':
specifier: ^3.0.9 specifier: ^3.0.9
version: 3.0.9 version: 3.0.9

View File

@ -4,7 +4,13 @@ import * as interfaces from './interfaces.js';
import { SmartBucket } from './classes.smartbucket.js'; import { SmartBucket } from './classes.smartbucket.js';
import { Directory } from './classes.directory.js'; import { Directory } from './classes.directory.js';
import { File } from './classes.file.js'; import { File } from './classes.file.js';
import { Trash } from './classes.trash.js';
/**
* The bucket class exposes the basc functionality of a bucket.
* The functions of the bucket alone are enough to
* operate in s3 basic fashion on blobs of data.
*/
export class Bucket { export class Bucket {
public static async getBucketByName(smartbucketRef: SmartBucket, bucketNameArg: string) { public static async getBucketByName(smartbucketRef: SmartBucket, bucketNameArg: string) {
const buckets = await smartbucketRef.minioClient.listBuckets(); const buckets = await smartbucketRef.minioClient.listBuckets();
@ -45,7 +51,17 @@ export class Bucket {
return new Directory(this, null, ''); return new Directory(this, null, '');
} }
public async getDirectoryFromPath(pathDescriptorArg: interfaces.IPathDecriptor): Promise<Directory> { /**
* gets the trash directory
*/
public async getTrash(): Promise<Trash> {
const trash = new Trash(this);
return trash;
}
public async getDirectoryFromPath(
pathDescriptorArg: interfaces.IPathDecriptor
): Promise<Directory> {
if (!pathDescriptorArg.path && !pathDescriptorArg.directory) { if (!pathDescriptorArg.path && !pathDescriptorArg.directory) {
return this.getBaseDirectory(); return this.getBaseDirectory();
} }
@ -61,34 +77,37 @@ export class Bucket {
/** /**
* store file * store file
*/ */
public async fastPut(optionsArg: { public async fastPut(optionsArg: interfaces.IPathDecriptor & {
path: string;
contents: string | Buffer; contents: string | Buffer;
overwrite?: boolean; overwrite?: boolean;
}): Promise<File> { }): Promise<File> {
try { try {
const reducedPath = await helpers.reducePathDescriptorToPath({ const reducedPath = await helpers.reducePathDescriptorToPath(optionsArg);
path: optionsArg.path,
})
// Check if the object already exists // Check if the object already exists
const exists = await this.fastExists({ path: reducedPath }); const exists = await this.fastExists({ path: reducedPath });
if (exists && !optionsArg.overwrite) { if (exists && !optionsArg.overwrite) {
console.error(`Object already exists at path '${reducedPath}' in bucket '${this.name}'.`); console.error(`Object already exists at path '${reducedPath}' in bucket '${this.name}'.`);
return; return;
} else if (exists && optionsArg.overwrite) { } else if (exists && optionsArg.overwrite) {
console.log(`Overwriting existing object at path '${reducedPath}' in bucket '${this.name}'.`); console.log(
`Overwriting existing object at path '${reducedPath}' in bucket '${this.name}'.`
);
} else { } else {
console.log(`Creating new object at path '${reducedPath}' in bucket '${this.name}'.`); console.log(`Creating new object at path '${reducedPath}' in bucket '${this.name}'.`);
} }
// Proceed with putting the object // Proceed with putting the object
const streamIntake = new plugins.smartstream.StreamIntake(); const streamIntake = new plugins.smartstream.StreamIntake();
const putPromise = this.smartbucketRef.minioClient.putObject(this.name, reducedPath, streamIntake); const putPromise = this.smartbucketRef.minioClient.putObject(
this.name,
reducedPath,
streamIntake
);
streamIntake.pushData(optionsArg.contents); streamIntake.pushData(optionsArg.contents);
streamIntake.signalEnd(); streamIntake.signalEnd();
await putPromise; await putPromise;
console.log(`Object '${reducedPath}' has been successfully stored in bucket '${this.name}'.`); console.log(`Object '${reducedPath}' has been successfully stored in bucket '${this.name}'.`);
const parsedPath = plugins.path.parse(reducedPath); const parsedPath = plugins.path.parse(reducedPath);
return new File({ return new File({
@ -98,18 +117,18 @@ export class Bucket {
fileName: parsedPath.base, fileName: parsedPath.base,
}); });
} catch (error) { } catch (error) {
console.error(`Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`, error); console.error(
`Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`,
error
);
throw error; throw error;
} }
} }
/** /**
* get file * get file
*/ */
public async fastGet(optionsArg: { public async fastGet(optionsArg: { path: string }): Promise<Buffer> {
path: string
}): Promise<Buffer> {
const done = plugins.smartpromise.defer(); const done = plugins.smartpromise.defer();
let completeFile: Buffer; let completeFile: Buffer;
const replaySubject = await this.fastGetReplaySubject(optionsArg); const replaySubject = await this.fastGetReplaySubject(optionsArg);
@ -137,7 +156,7 @@ export class Bucket {
* good when time to first byte is important * good when time to first byte is important
* and multiple subscribers are expected * and multiple subscribers are expected
* @param optionsArg * @param optionsArg
* @returns * @returns
*/ */
public async fastGetReplaySubject(optionsArg: { public async fastGetReplaySubject(optionsArg: {
path: string; path: string;
@ -154,34 +173,40 @@ export class Bucket {
finalFunction: async (cb) => { finalFunction: async (cb) => {
replaySubject.complete(); replaySubject.complete();
return; return;
} },
}); });
if (!fileStream) { if (!fileStream) {
return null; return null;
} }
const smartstream = new plugins.smartstream.StreamWrapper([ const smartstream = new plugins.smartstream.StreamWrapper([fileStream, duplexStream]);
fileStream,
duplexStream,
]);
smartstream.run(); smartstream.run();
return replaySubject; return replaySubject;
} }
public fastGetStream(optionsArg: { public fastGetStream(
path: string; optionsArg: {
}, typeArg: 'webstream'): Promise<ReadableStream> path: string;
public async fastGetStream(optionsArg: { },
path: string; typeArg: 'webstream'
}, typeArg: 'nodestream'): Promise<plugins.stream.Readable> ): Promise<ReadableStream>;
public async fastGetStream(
optionsArg: {
path: string;
},
typeArg: 'nodestream'
): Promise<plugins.stream.Readable>;
/** /**
* fastGetStream * fastGetStream
* @param optionsArg * @param optionsArg
* @returns * @returns
*/ */
public async fastGetStream(optionsArg: { path: string; }, typeArg: 'webstream' | 'nodestream' = 'nodestream'): Promise<ReadableStream | plugins.stream.Readable>{ public async fastGetStream(
optionsArg: { path: string },
typeArg: 'webstream' | 'nodestream' = 'nodestream'
): Promise<ReadableStream | plugins.stream.Readable> {
const fileStream = await this.smartbucketRef.minioClient const fileStream = await this.smartbucketRef.minioClient
.getObject(this.name, optionsArg.path) .getObject(this.name, optionsArg.path)
.catch((e) => console.log(e)); .catch((e) => console.log(e));
@ -191,21 +216,18 @@ export class Bucket {
}, },
finalFunction: async (cb) => { finalFunction: async (cb) => {
return null; return null;
} },
}); });
if (!fileStream) { if (!fileStream) {
return null; return null;
} }
const smartstream = new plugins.smartstream.StreamWrapper([ const smartstream = new plugins.smartstream.StreamWrapper([fileStream, duplexStream]);
fileStream,
duplexStream,
]);
smartstream.run(); smartstream.run();
if (typeArg === 'nodestream') { if (typeArg === 'nodestream') {
return duplexStream; return duplexStream;
}; }
if (typeArg === 'webstream') { if (typeArg === 'webstream') {
return (await duplexStream.getWebStreams()).readable; return (await duplexStream.getWebStreams()).readable;
} }
@ -223,34 +245,44 @@ export class Bucket {
try { try {
// Check if the object already exists // Check if the object already exists
const exists = await this.fastExists({ path: optionsArg.path }); const exists = await this.fastExists({ path: optionsArg.path });
if (exists && !optionsArg.overwrite) { if (exists && !optionsArg.overwrite) {
console.error(`Object already exists at path '${optionsArg.path}' in bucket '${this.name}'.`); console.error(
`Object already exists at path '${optionsArg.path}' in bucket '${this.name}'.`
);
return; return;
} else if (exists && optionsArg.overwrite) { } else if (exists && optionsArg.overwrite) {
console.log(`Overwriting existing object at path '${optionsArg.path}' in bucket '${this.name}'.`); console.log(
`Overwriting existing object at path '${optionsArg.path}' in bucket '${this.name}'.`
);
} else { } else {
console.log(`Creating new object at path '${optionsArg.path}' in bucket '${this.name}'.`); console.log(`Creating new object at path '${optionsArg.path}' in bucket '${this.name}'.`);
} }
const streamIntake = await plugins.smartstream.StreamIntake.fromStream<Uint8Array>(optionsArg.readableStream); const streamIntake = await plugins.smartstream.StreamIntake.fromStream<Uint8Array>(
optionsArg.readableStream
);
// Proceed with putting the object // Proceed with putting the object
await this.smartbucketRef.minioClient.putObject( await this.smartbucketRef.minioClient.putObject(
this.name, this.name,
optionsArg.path, optionsArg.path,
streamIntake, streamIntake,
null, null,
null, // TODO: Add support for custom metadata once proper support is in minio. null // TODO: Add support for custom metadata once proper support is in minio.
);
console.log(
`Object '${optionsArg.path}' has been successfully stored in bucket '${this.name}'.`
); );
console.log(`Object '${optionsArg.path}' has been successfully stored in bucket '${this.name}'.`);
} catch (error) { } catch (error) {
console.error(`Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`, error); console.error(
`Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`,
error
);
throw error; throw error;
} }
} }
public async fastCopy(optionsArg: { public async fastCopy(optionsArg: {
sourcePath: string; sourcePath: string;
@ -291,60 +323,66 @@ export class Bucket {
} }
} }
/** /**
* Move object from one path to another within the same bucket or to another bucket * Move object from one path to another within the same bucket or to another bucket
*/ */
public async fastMove(optionsArg: { public async fastMove(optionsArg: {
sourcePath: string; sourcePath: string;
destinationPath: string; destinationPath: string;
targetBucket?: Bucket; targetBucket?: Bucket;
overwrite?: boolean; overwrite?: boolean;
}): Promise<void> { }): Promise<void> {
try { try {
// Check if the destination object already exists // Check if the destination object already exists
const destinationBucket = optionsArg.targetBucket || this; const destinationBucket = optionsArg.targetBucket || this;
const exists = await destinationBucket.fastExists({ path: optionsArg.destinationPath }); const exists = await destinationBucket.fastExists({ path: optionsArg.destinationPath });
if (exists && !optionsArg.overwrite) { if (exists && !optionsArg.overwrite) {
console.error(`Object already exists at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`); console.error(
return; `Object already exists at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
} else if (exists && optionsArg.overwrite) { );
console.log(`Overwriting existing object at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`); return;
} else { } else if (exists && optionsArg.overwrite) {
console.log(`Moving object to path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`); console.log(
} `Overwriting existing object at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
);
// Proceed with copying the object to the new path } else {
await this.fastCopy(optionsArg); console.log(
`Moving object to path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
// Remove the original object after successful copy );
await this.fastRemove({ path: optionsArg.sourcePath });
console.log(`Object '${optionsArg.sourcePath}' has been successfully moved to '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`);
} catch (error) {
console.error(`Error moving object from '${optionsArg.sourcePath}' to '${optionsArg.destinationPath}':`, error);
throw error;
} }
// Proceed with copying the object to the new path
await this.fastCopy(optionsArg);
// Remove the original object after successful copy
await this.fastRemove({ path: optionsArg.sourcePath });
console.log(
`Object '${optionsArg.sourcePath}' has been successfully moved to '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`
);
} catch (error) {
console.error(
`Error moving object from '${optionsArg.sourcePath}' to '${optionsArg.destinationPath}':`,
error
);
throw error;
} }
}
/** /**
* removeObject * removeObject
*/ */
public async fastRemove(optionsArg: { public async fastRemove(optionsArg: { path: string }) {
path: string;
}) {
await this.smartbucketRef.minioClient.removeObject(this.name, optionsArg.path); await this.smartbucketRef.minioClient.removeObject(this.name, optionsArg.path);
} }
/** /**
* check wether file exists * check wether file exists
* @param optionsArg * @param optionsArg
* @returns * @returns
*/ */
public async fastExists(optionsArg: { public async fastExists(optionsArg: { path: string }): Promise<boolean> {
path: string;
}): Promise<boolean> {
try { try {
await this.smartbucketRef.minioClient.statObject(this.name, optionsArg.path); await this.smartbucketRef.minioClient.statObject(this.name, optionsArg.path);
console.log(`Object '${optionsArg.path}' exists in bucket '${this.name}'.`); console.log(`Object '${optionsArg.path}' exists in bucket '${this.name}'.`);
@ -374,7 +412,7 @@ export class Bucket {
public async isDirectory(pathDescriptor: interfaces.IPathDecriptor): Promise<boolean> { public async isDirectory(pathDescriptor: interfaces.IPathDecriptor): Promise<boolean> {
let checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor); let checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor);
// lets check if the checkPath is a directory // lets check if the checkPath is a directory
const stream = this.smartbucketRef.minioClient.listObjectsV2(this.name, checkPath, true); const stream = this.smartbucketRef.minioClient.listObjectsV2(this.name, checkPath, true);
const done = plugins.smartpromise.defer<boolean>(); const done = plugins.smartpromise.defer<boolean>();
@ -384,21 +422,21 @@ export class Bucket {
done.resolve(true); done.resolve(true);
} }
}); });
stream.on('end', () => { stream.on('end', () => {
done.resolve(false); done.resolve(false);
}); });
stream.on('error', (err) => { stream.on('error', (err) => {
done.reject(err); done.reject(err);
}); });
return done.promise; return done.promise;
}; }
public async isFile(pathDescriptor: interfaces.IPathDecriptor): Promise<boolean> { public async isFile(pathDescriptor: interfaces.IPathDecriptor): Promise<boolean> {
let checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor); let checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor);
// lets check if the checkPath is a directory // lets check if the checkPath is a directory
const stream = this.smartbucketRef.minioClient.listObjectsV2(this.name, checkPath, true); const stream = this.smartbucketRef.minioClient.listObjectsV2(this.name, checkPath, true);
const done = plugins.smartpromise.defer<boolean>(); const done = plugins.smartpromise.defer<boolean>();
@ -408,11 +446,11 @@ export class Bucket {
done.resolve(true); done.resolve(true);
} }
}); });
stream.on('end', () => { stream.on('end', () => {
done.resolve(false); done.resolve(false);
}); });
stream.on('error', (err) => { stream.on('error', (err) => {
done.reject(err); done.reject(err);
}); });

View File

@ -2,6 +2,8 @@ import * as plugins from './plugins.js';
import { Bucket } from './classes.bucket.js'; import { Bucket } from './classes.bucket.js';
import { File } from './classes.file.js'; import { File } from './classes.file.js';
import * as helpers from './helpers.js';
export class Directory { export class Directory {
public bucketRef: Bucket; public bucketRef: Bucket;
public parentDirectoryRef: Directory; public parentDirectoryRef: Directory;
@ -65,11 +67,20 @@ export class Directory {
public async getFile(optionsArg: { public async getFile(optionsArg: {
name: string; name: string;
createWithContents?: string | Buffer; createWithContents?: string | Buffer;
getFromTrash?: boolean;
}): Promise<File> { }): Promise<File> {
const pathDescriptor = {
directory: this,
path: optionsArg.name,
};
// check wether the file exists // check wether the file exists
const exists = await this.bucketRef.fastExists({ const exists = await this.bucketRef.fastExists({
path: this.getBasePath() + optionsArg.name, path: await helpers.reducePathDescriptorToPath(pathDescriptor),
}); });
if (!exists && optionsArg.getFromTrash) {
const trash = await this.bucketRef.getTrash();
const trashKey = await trash.getTrashKeyByOriginalBasePath()
}
if (!exists && !optionsArg.createWithContents) { if (!exists && !optionsArg.createWithContents) {
return null; return null;
} }

View File

@ -116,10 +116,10 @@ export class File {
originalPath: this.getBasePath(), originalPath: this.getBasePath(),
}, },
}); });
const trashName = plugins.smartunique.uuid4(); const trash = await this.parentDirectoryRef.bucketRef.getTrash();
await this.move({ await this.move({
directory: await this.parentDirectoryRef.bucketRef.getBaseDirectory(), directory: await trash.getTrashDir(),
path: plugins.path.join('trash', trashName), path: await trash.getTrashKeyByOriginalBasePath(this.getBasePath()),
}); });
} }

27
ts/classes.trash.ts Normal file
View File

@ -0,0 +1,27 @@
import * as plugins from './plugins.js';
import * as interfaces from './interfaces.js';
import * as helpers from './helpers.js';
import type { Bucket } from './classes.bucket.js';
import type { Directory } from './classes.directory.js';
import type { File } from './classes.file.js';
export class Trash {
public bucketRef: Bucket;
constructor(bucketRefArg: Bucket) {
this.bucketRef = bucketRefArg;
}
public async getTrashDir() {
return this.bucketRef.getDirectoryFromPath({ path: '.trash' });
}
public async getTrashedFileByOriginalName(pathDescriptor: interfaces.IPathDecriptor): Promise<File> {
}
public async getTrashKeyByOriginalBasePath (originalPath: string): Promise<string> {
return plugins.smartstring.base64.encode(originalPath);
}
}

View File

@ -10,9 +10,10 @@ import * as smartpath from '@push.rocks/smartpath';
import * as smartpromise from '@push.rocks/smartpromise'; import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrx from '@push.rocks/smartrx'; import * as smartrx from '@push.rocks/smartrx';
import * as smartstream from '@push.rocks/smartstream'; import * as smartstream from '@push.rocks/smartstream';
import * as smartstring from '@push.rocks/smartstring';
import * as smartunique from '@push.rocks/smartunique'; import * as smartunique from '@push.rocks/smartunique';
export { smartmime, smartpath, smartpromise, smartrx, smartstream, smartunique }; export { smartmime, smartpath, smartpromise, smartrx, smartstream, smartstring, smartunique };
// @tsclass // @tsclass
import * as tsclass from '@tsclass/tsclass'; import * as tsclass from '@tsclass/tsclass';