2024-06-17 14:01:35 +00:00
|
|
|
// classes.directory.ts
|
|
|
|
|
2024-05-20 23:22:21 +00:00
|
|
|
import * as plugins from './plugins.js';
|
|
|
|
import { Bucket } from './classes.bucket.js';
|
|
|
|
import { File } from './classes.file.js';
|
2024-06-10 14:47:20 +00:00
|
|
|
import * as helpers from './helpers.js';
|
|
|
|
|
2019-10-16 16:12:18 +00:00
|
|
|
export class Directory {
|
|
|
|
public bucketRef: Bucket;
|
2019-10-18 13:43:06 +00:00
|
|
|
public parentDirectoryRef: Directory;
|
2019-10-16 17:11:28 +00:00
|
|
|
public name: string;
|
2019-10-16 16:12:18 +00:00
|
|
|
|
|
|
|
public tree: string[];
|
|
|
|
public files: string[];
|
|
|
|
public folders: string[];
|
|
|
|
|
2024-06-17 14:01:35 +00:00
|
|
|
constructor(bucketRefArg: Bucket, parentDirectory: Directory, name: string) {
|
2019-10-16 16:12:18 +00:00
|
|
|
this.bucketRef = bucketRefArg;
|
2024-06-17 14:01:35 +00:00
|
|
|
this.parentDirectoryRef = parentDirectory;
|
2019-10-16 17:11:28 +00:00
|
|
|
this.name = name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* returns an array of parent directories
|
|
|
|
*/
|
|
|
|
public getParentDirectories(): Directory[] {
|
|
|
|
let parentDirectories: Directory[] = [];
|
2019-10-18 13:43:06 +00:00
|
|
|
if (this.parentDirectoryRef) {
|
|
|
|
parentDirectories.push(this.parentDirectoryRef);
|
|
|
|
parentDirectories = parentDirectories.concat(this.parentDirectoryRef.getParentDirectories());
|
2019-10-16 17:11:28 +00:00
|
|
|
}
|
|
|
|
return parentDirectories;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* returns the directory level
|
|
|
|
*/
|
|
|
|
public getDirectoryLevel(): number {
|
|
|
|
return this.getParentDirectories().length;
|
2019-10-16 17:15:48 +00:00
|
|
|
}
|
2019-10-16 17:11:28 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* updates the base path
|
|
|
|
*/
|
|
|
|
public getBasePath(): string {
|
|
|
|
const parentDirectories = this.getParentDirectories();
|
|
|
|
let basePath = '';
|
2019-10-16 17:15:48 +00:00
|
|
|
for (const parentDir of parentDirectories) {
|
2020-10-12 00:23:25 +00:00
|
|
|
if (!parentDir.name && !basePath) {
|
2020-05-17 19:23:26 +00:00
|
|
|
basePath = this.name + '/';
|
2019-10-19 23:18:13 +00:00
|
|
|
continue;
|
|
|
|
}
|
2020-10-12 00:23:25 +00:00
|
|
|
if (parentDir.name && !basePath) {
|
|
|
|
basePath = parentDir.name + '/' + this.name + '/';
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (parentDir.name && basePath) {
|
|
|
|
basePath = parentDir.name + '/' + basePath;
|
|
|
|
continue;
|
|
|
|
}
|
2019-10-16 17:11:28 +00:00
|
|
|
}
|
|
|
|
return basePath;
|
2019-10-16 16:12:18 +00:00
|
|
|
}
|
|
|
|
|
2024-05-20 23:22:21 +00:00
|
|
|
/**
|
|
|
|
* gets a file by name
|
|
|
|
*/
|
|
|
|
public async getFile(optionsArg: {
|
2024-06-18 16:44:58 +00:00
|
|
|
path: string;
|
2024-05-20 23:22:21 +00:00
|
|
|
createWithContents?: string | Buffer;
|
2024-06-10 14:47:20 +00:00
|
|
|
getFromTrash?: boolean;
|
2024-11-24 01:25:08 +00:00
|
|
|
}): Promise<File | null> {
|
2024-06-10 14:47:20 +00:00
|
|
|
const pathDescriptor = {
|
|
|
|
directory: this,
|
2024-06-18 16:44:58 +00:00
|
|
|
path: optionsArg.path,
|
2024-06-10 14:47:20 +00:00
|
|
|
};
|
2024-05-20 23:22:21 +00:00
|
|
|
const exists = await this.bucketRef.fastExists({
|
2024-06-10 14:47:20 +00:00
|
|
|
path: await helpers.reducePathDescriptorToPath(pathDescriptor),
|
2024-05-20 23:22:21 +00:00
|
|
|
});
|
2024-06-10 14:47:20 +00:00
|
|
|
if (!exists && optionsArg.getFromTrash) {
|
|
|
|
const trash = await this.bucketRef.getTrash();
|
2024-06-17 14:01:35 +00:00
|
|
|
const trashedFile = await trash.getTrashedFileByOriginalName(pathDescriptor);
|
2024-06-11 15:20:48 +00:00
|
|
|
return trashedFile;
|
2024-06-10 14:47:20 +00:00
|
|
|
}
|
2024-05-20 23:22:21 +00:00
|
|
|
if (!exists && !optionsArg.createWithContents) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (!exists && optionsArg.createWithContents) {
|
2024-06-09 14:02:33 +00:00
|
|
|
await File.create({
|
|
|
|
directory: this,
|
2024-06-18 16:44:58 +00:00
|
|
|
name: optionsArg.path,
|
2024-05-20 23:22:21 +00:00
|
|
|
contents: optionsArg.createWithContents,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return new File({
|
|
|
|
directoryRefArg: this,
|
2024-06-18 16:44:58 +00:00
|
|
|
fileName: optionsArg.path,
|
2024-06-11 15:20:48 +00:00
|
|
|
});
|
2024-05-20 23:22:21 +00:00
|
|
|
}
|
|
|
|
|
2024-11-24 01:25:08 +00:00
|
|
|
/**
|
|
|
|
* gets a file strictly
|
|
|
|
* @param args
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
public async getFileStrict(...args: Parameters<Directory['getFile']>) {
|
|
|
|
const file = await this.getFile(...args);
|
|
|
|
if (!file) {
|
|
|
|
throw new Error(`File not found at path '${args[0].path}'`);
|
|
|
|
}
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
2019-10-16 16:12:18 +00:00
|
|
|
/**
|
|
|
|
* lists all files
|
|
|
|
*/
|
|
|
|
public async listFiles(): Promise<File[]> {
|
2024-06-17 14:01:35 +00:00
|
|
|
const command = new plugins.s3.ListObjectsV2Command({
|
|
|
|
Bucket: this.bucketRef.name,
|
|
|
|
Prefix: this.getBasePath(),
|
|
|
|
Delimiter: '/',
|
|
|
|
});
|
|
|
|
const response = await this.bucketRef.smartbucketRef.s3Client.send(command);
|
2019-10-16 16:12:18 +00:00
|
|
|
const fileArray: File[] = [];
|
2024-06-17 14:01:35 +00:00
|
|
|
|
2024-11-24 01:25:08 +00:00
|
|
|
response.Contents?.forEach((item) => {
|
2024-06-17 14:01:35 +00:00
|
|
|
if (item.Key && !item.Key.endsWith('/')) {
|
|
|
|
const subtractedPath = item.Key.replace(this.getBasePath(), '');
|
2019-10-18 13:43:06 +00:00
|
|
|
if (!subtractedPath.includes('/')) {
|
2024-05-17 16:53:11 +00:00
|
|
|
fileArray.push(
|
|
|
|
new File({
|
|
|
|
directoryRefArg: this,
|
|
|
|
fileName: subtractedPath,
|
|
|
|
})
|
|
|
|
);
|
2019-10-18 13:43:06 +00:00
|
|
|
}
|
2024-06-17 14:01:35 +00:00
|
|
|
}
|
2024-05-17 16:53:11 +00:00
|
|
|
});
|
2024-06-17 14:01:35 +00:00
|
|
|
|
2019-10-16 16:12:18 +00:00
|
|
|
return fileArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* lists all folders
|
|
|
|
*/
|
2019-10-16 17:11:28 +00:00
|
|
|
public async listDirectories(): Promise<Directory[]> {
|
2024-06-17 14:01:35 +00:00
|
|
|
try {
|
|
|
|
const command = new plugins.s3.ListObjectsV2Command({
|
|
|
|
Bucket: this.bucketRef.name,
|
|
|
|
Prefix: this.getBasePath(),
|
|
|
|
Delimiter: '/',
|
|
|
|
});
|
|
|
|
const response = await this.bucketRef.smartbucketRef.s3Client.send(command);
|
|
|
|
const directoryArray: Directory[] = [];
|
|
|
|
|
|
|
|
if (response.CommonPrefixes) {
|
|
|
|
response.CommonPrefixes.forEach((item) => {
|
|
|
|
if (item.Prefix) {
|
|
|
|
const subtractedPath = item.Prefix.replace(this.getBasePath(), '');
|
|
|
|
if (subtractedPath.endsWith('/')) {
|
|
|
|
const dirName = subtractedPath.slice(0, -1);
|
|
|
|
// Ensure the directory name is not empty (which would indicate the base directory itself)
|
|
|
|
if (dirName) {
|
|
|
|
directoryArray.push(new Directory(this.bucketRef, this, dirName));
|
|
|
|
}
|
|
|
|
}
|
2019-10-16 17:15:48 +00:00
|
|
|
}
|
2024-06-17 14:01:35 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return directoryArray;
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error listing directories:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
2019-10-16 16:12:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-06-17 14:01:35 +00:00
|
|
|
* gets an array that has all objects with a certain prefix
|
2019-10-16 16:12:18 +00:00
|
|
|
*/
|
|
|
|
public async getTreeArray() {
|
2024-06-17 14:01:35 +00:00
|
|
|
const command = new plugins.s3.ListObjectsV2Command({
|
|
|
|
Bucket: this.bucketRef.name,
|
|
|
|
Prefix: this.getBasePath(),
|
|
|
|
Delimiter: '/',
|
|
|
|
});
|
|
|
|
const response = await this.bucketRef.smartbucketRef.s3Client.send(command);
|
|
|
|
return response.Contents;
|
2019-10-16 16:12:18 +00:00
|
|
|
}
|
2019-10-16 17:11:28 +00:00
|
|
|
|
|
|
|
/**
|
2024-06-17 14:01:35 +00:00
|
|
|
* gets a sub directory by name
|
2019-10-16 17:11:28 +00:00
|
|
|
*/
|
2024-11-24 01:25:08 +00:00
|
|
|
public async getSubDirectoryByName(dirNameArg: string, optionsArg: {
|
|
|
|
getEmptyDirectory?: boolean;
|
|
|
|
createWithInitializerFile?: boolean;
|
|
|
|
} = {}): Promise<Directory | null> {
|
|
|
|
const dirNameArray = dirNameArg.split('/').filter(str => str.trim() !== "");
|
|
|
|
|
|
|
|
optionsArg = {
|
|
|
|
getEmptyDirectory: false,
|
|
|
|
createWithInitializerFile: false,
|
|
|
|
...optionsArg,
|
|
|
|
}
|
|
|
|
|
2019-10-19 23:18:13 +00:00
|
|
|
|
2024-11-24 01:25:08 +00:00
|
|
|
const getDirectory = async (directoryArg: Directory, dirNameToSearch: string, isFinalDirectory: boolean) => {
|
2019-10-19 23:18:13 +00:00
|
|
|
const directories = await directoryArg.listDirectories();
|
2024-11-24 01:25:08 +00:00
|
|
|
let returnDirectory = directories.find((directory) => {
|
2019-10-19 23:18:13 +00:00
|
|
|
return directory.name === dirNameToSearch;
|
|
|
|
});
|
2024-11-24 01:25:08 +00:00
|
|
|
if (returnDirectory) {
|
|
|
|
return returnDirectory;
|
|
|
|
}
|
|
|
|
if (optionsArg.getEmptyDirectory || optionsArg.createWithInitializerFile) {
|
|
|
|
returnDirectory = new Directory(this.bucketRef, this, dirNameToSearch);
|
|
|
|
}
|
|
|
|
if (isFinalDirectory && optionsArg.createWithInitializerFile) {
|
|
|
|
returnDirectory?.createEmptyFile('00init.txt');
|
|
|
|
}
|
|
|
|
return returnDirectory || null;
|
2019-10-19 23:18:13 +00:00
|
|
|
};
|
2024-06-17 14:01:35 +00:00
|
|
|
|
2024-11-24 01:25:08 +00:00
|
|
|
let wantedDirectory: Directory | null = null;
|
|
|
|
let counter = 0;
|
2019-10-19 23:18:13 +00:00
|
|
|
for (const dirNameToSearch of dirNameArray) {
|
2024-11-24 01:25:08 +00:00
|
|
|
counter++;
|
2019-10-19 23:18:13 +00:00
|
|
|
const directoryToSearchIn = wantedDirectory ? wantedDirectory : this;
|
2024-11-24 01:25:08 +00:00
|
|
|
wantedDirectory = await getDirectory(directoryToSearchIn, dirNameToSearch, counter === dirNameArray.length);
|
2019-10-19 23:18:13 +00:00
|
|
|
}
|
2024-06-17 14:01:35 +00:00
|
|
|
|
2024-11-24 01:25:08 +00:00
|
|
|
return wantedDirectory || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getSubDirectoryByNameStrict(...args: Parameters<Directory['getSubDirectoryByName']>) {
|
|
|
|
const directory = await this.getSubDirectoryByName(...args);
|
|
|
|
if (!directory) {
|
|
|
|
throw new Error(`Directory not found at path '${args[0]}'`);
|
|
|
|
}
|
|
|
|
return directory;
|
2019-10-16 17:11:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* moves the directory
|
|
|
|
*/
|
2019-10-16 17:15:48 +00:00
|
|
|
public async move() {
|
2019-10-16 17:11:28 +00:00
|
|
|
// TODO
|
2024-06-17 14:01:35 +00:00
|
|
|
throw new Error('Moving a directory is not yet implemented');
|
2019-10-16 17:11:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-06-17 14:01:35 +00:00
|
|
|
* creates an empty file within this directory
|
2019-10-16 17:15:48 +00:00
|
|
|
* @param relativePathArg
|
2019-10-16 17:11:28 +00:00
|
|
|
*/
|
2021-06-02 09:14:24 +00:00
|
|
|
public async createEmptyFile(relativePathArg: string) {
|
2024-06-17 14:01:35 +00:00
|
|
|
const emptyFile = await File.create({
|
2024-05-17 16:53:11 +00:00
|
|
|
directory: this,
|
|
|
|
name: relativePathArg,
|
|
|
|
contents: '',
|
|
|
|
});
|
2024-06-17 14:01:35 +00:00
|
|
|
return emptyFile;
|
2019-10-16 17:11:28 +00:00
|
|
|
}
|
2019-10-18 16:44:54 +00:00
|
|
|
|
|
|
|
// file operations
|
2024-05-17 16:53:11 +00:00
|
|
|
public async fastPut(optionsArg: { path: string; contents: string | Buffer }) {
|
|
|
|
const path = plugins.path.join(this.getBasePath(), optionsArg.path);
|
|
|
|
await this.bucketRef.fastPut({
|
|
|
|
path,
|
|
|
|
contents: optionsArg.contents,
|
|
|
|
});
|
2019-10-18 16:44:54 +00:00
|
|
|
}
|
|
|
|
|
2024-05-17 16:53:11 +00:00
|
|
|
public async fastGet(optionsArg: { path: string }) {
|
|
|
|
const path = plugins.path.join(this.getBasePath(), optionsArg.path);
|
|
|
|
const result = await this.bucketRef.fastGet({
|
|
|
|
path,
|
|
|
|
});
|
2019-10-19 20:57:36 +00:00
|
|
|
return result;
|
2019-10-18 16:44:54 +00:00
|
|
|
}
|
|
|
|
|
2024-06-11 15:20:48 +00:00
|
|
|
public fastGetStream(
|
|
|
|
optionsArg: {
|
|
|
|
path: string;
|
|
|
|
},
|
|
|
|
typeArg: 'webstream'
|
|
|
|
): Promise<ReadableStream>;
|
|
|
|
public async fastGetStream(
|
|
|
|
optionsArg: {
|
|
|
|
path: string;
|
|
|
|
},
|
|
|
|
typeArg: 'nodestream'
|
|
|
|
): Promise<plugins.stream.Readable>;
|
2024-06-03 19:35:08 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* fastGetStream
|
|
|
|
* @param optionsArg
|
2024-06-11 15:20:48 +00:00
|
|
|
* @returns
|
2024-06-03 19:35:08 +00:00
|
|
|
*/
|
2024-06-11 15:20:48 +00:00
|
|
|
public async fastGetStream(
|
|
|
|
optionsArg: { path: string },
|
|
|
|
typeArg: 'webstream' | 'nodestream'
|
|
|
|
): Promise<ReadableStream | plugins.stream.Readable> {
|
2024-06-03 19:35:08 +00:00
|
|
|
const path = plugins.path.join(this.getBasePath(), optionsArg.path);
|
2024-06-11 15:20:48 +00:00
|
|
|
const result = await this.bucketRef.fastGetStream(
|
|
|
|
{
|
|
|
|
path,
|
|
|
|
},
|
|
|
|
typeArg as any
|
|
|
|
);
|
2019-10-20 10:27:58 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-06-09 14:02:33 +00:00
|
|
|
/**
|
|
|
|
* fast put stream
|
|
|
|
*/
|
|
|
|
public async fastPutStream(optionsArg: {
|
|
|
|
path: string;
|
|
|
|
stream: plugins.stream.Readable;
|
|
|
|
}): Promise<void> {
|
|
|
|
const path = plugins.path.join(this.getBasePath(), optionsArg.path);
|
|
|
|
await this.bucketRef.fastPutStream({
|
|
|
|
path,
|
|
|
|
readableStream: optionsArg.stream,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-06-03 19:35:08 +00:00
|
|
|
/**
|
|
|
|
* removes a file within the directory
|
2024-06-18 16:44:58 +00:00
|
|
|
* uses file class to make sure effects for metadata etc. are handled correctly
|
2024-06-03 19:35:08 +00:00
|
|
|
* @param optionsArg
|
|
|
|
*/
|
2024-06-18 16:44:58 +00:00
|
|
|
public async fastRemove(optionsArg: {
|
|
|
|
path: string
|
|
|
|
/**
|
|
|
|
* wether the file should be placed into trash. Default is false.
|
|
|
|
*/
|
|
|
|
mode?: 'permanent' | 'trash';
|
|
|
|
}) {
|
|
|
|
const file = await this.getFile({
|
|
|
|
path: optionsArg.path,
|
|
|
|
});
|
|
|
|
await file.delete({
|
|
|
|
mode: optionsArg.mode ? optionsArg.mode : 'permanent',
|
2024-05-17 16:53:11 +00:00
|
|
|
});
|
2019-10-18 16:44:54 +00:00
|
|
|
}
|
2021-04-06 02:34:52 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* deletes the directory with all its contents
|
|
|
|
*/
|
2024-06-18 16:44:58 +00:00
|
|
|
public async delete(optionsArg: {
|
|
|
|
mode?: 'permanent' | 'trash';
|
|
|
|
}) {
|
2021-04-06 02:34:52 +00:00
|
|
|
const deleteDirectory = async (directoryArg: Directory) => {
|
|
|
|
const childDirectories = await directoryArg.listDirectories();
|
|
|
|
if (childDirectories.length === 0) {
|
2024-06-17 14:01:35 +00:00
|
|
|
console.log('Directory empty! Path complete!');
|
2021-04-06 02:34:52 +00:00
|
|
|
} else {
|
|
|
|
for (const childDir of childDirectories) {
|
|
|
|
await deleteDirectory(childDir);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const files = await directoryArg.listFiles();
|
|
|
|
for (const file of files) {
|
2024-06-18 16:44:58 +00:00
|
|
|
await file.delete({
|
|
|
|
mode: optionsArg.mode ? optionsArg.mode : 'permanent',
|
|
|
|
})
|
2021-04-06 02:34:52 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
await deleteDirectory(this);
|
|
|
|
}
|
2019-10-16 16:12:18 +00:00
|
|
|
}
|