ProzillaOS/src/features/virtual-drive/virtual-folder.js

281 lines
No EOL
6.2 KiB
JavaScript

import { removeFromArray } from "../utils/array.js";
import { VirtualBase } from "./virtual-base.js";
import { VirtualFile } from "./virtual-file.js";
/**
* A virtual folder that can contains files and sub-folders
*/
export class VirtualFolder extends VirtualBase {
static TYPE = {
GENERAL: 0,
MEDIA: 1,
};
/**
* @param {string} name
* @param {number} type
*/
constructor(name, type) {
super(name);
this.subFolders = [];
this.files = [];
this.type = type ?? VirtualFolder.TYPE.GENERAL;
}
/**
* @param {string} alias
* @returns {VirtualFolder}
*/
setAlias(alias) {
return super.setAlias(alias);
}
/**
* Returns true if this folder contains a file matching a name and extension
* @param {string} name
* @param {string} extension
* @returns {VirtualFile}
*/
hasFile(name, extension) {
return this.findFile(name, extension) !== null;
}
/**
* Returns true if this folder contains a folder matching a name
* @param {string} name
* @returns {boolean}
*/
hasFolder(name) {
return this.findSubFolder(name) !== null;
}
/**
* Finds and returns a file inside this folder matching a name and extension
* @param {string} name
* @param {string} extension
* @returns {VirtualFile}
*/
findFile(name, extension) {
let resultFile = null;
this.files.forEach((file) => {
if ((file.name === name || (file.alias && file.alias === name)) && file.extension === extension) {
return resultFile = file;
}
});
return resultFile;
}
/**
* Finds and returns a folder inside this folder matching a name
* @param {string} name
* @returns {VirtualFolder}
*/
findSubFolder(name) {
let resultFolder = null;
this.subFolders.forEach((folder) => {
if (folder.name === name || (folder.alias && folder.alias === name)) {
return resultFolder = folder;
}
});
return resultFolder;
}
/**
* @callback createFileCallback
* @param {VirtualFile} newFile
*/
/**
* Creates a file with a name and extension
* @param {string} name
* @param {string} extension
* @param {createFileCallback} callback
* @returns {VirtualFolder}
*/
createFile(name, extension, callback) {
let newFile = this.findFile(name, extension);
if (newFile == null) {
newFile = new VirtualFile(name, extension);
this.files.push(newFile);
newFile.parent = this;
}
callback?.(newFile);
this.getRoot().saveData();
return this;
}
/**
* Creates files based on an array of objects with file names and extensions
* @param {object[]} files
* @param {string} files.name
* @param {string} files.extension
* @returns {VirtualFolder}
*/
createFiles(files) {
files.forEach(({name, extension}) => {
this.createFile(name, extension);
});
this.getRoot().saveData();
return this;
}
/**
* @callback createFolderCallback
* @param {VirtualFolder} newFolder
*/
/**
* Creates a folder with a name
* @param {string} name
* @returns {VirtualFolder}
* @param {createFolderCallback} callback
*/
createFolder(name, callback) {
let newFolder = this.findSubFolder(name);
if (newFolder == null) {
newFolder = new VirtualFolder(name);
this.subFolders.push(newFolder);
newFolder.parent = this;
}
callback?.(newFolder);
this.getRoot().saveData();
return this;
}
/**
* Creates folders based on an array of folder names
* @param {string[]} folders
* @returns {VirtualFolder}
*/
createFolders(folders) {
folders.forEach((name) => {
this.createFolder(name);
});
this.getRoot().saveData();
return this;
}
/**
* Removes a file or folder from this folder
* @param {VirtualFile|VirtualFolder} child
*/
remove(child) {
child.parent = null;
if (child instanceof VirtualFile) {
removeFromArray(child, this.files);
} else if (child instanceof VirtualFolder) {
removeFromArray(child, this.subFolders);
}
this.getRoot()?.saveData();
}
/**
* Returns the file or folder at a relative path or null if it doesn't exist
* @param {string} relativePath
* @returns {VirtualFile|VirtualFolder|null}
*/
navigate(relativePath) {
const segments = relativePath.split("/");
let currentDirectory = this;
const getDirectory = (path, isStart) => {
if (isStart && path === "") {
return this.getRoot();
} else if (isStart && Object.keys(this.getRoot().shortcuts).includes(path)) {
return this.getRoot().shortcuts[path];
} else if (path === ".") {
return this;
} else if (path === "..") {
return currentDirectory?.parent;
} else {
return currentDirectory?.findSubFolder(path);
}
};
if (segments.length === 1) {
const directory = getDirectory(segments[0], true);
if (directory !== null)
return directory;
}
for (let i = 0; i < segments.length - 1; i++) {
const segment = segments[i];
currentDirectory = getDirectory(segment, i === 0);
}
const lastSegment = segments[segments.length - 1];
if (lastSegment === "") {
return currentDirectory;
} else if (currentDirectory != null) {
const folder = currentDirectory.findSubFolder(lastSegment);
if (folder != null)
return folder;
// To do: add support for file names with dots
const [name, extension] = lastSegment.split(".");
return currentDirectory.findFile(name, extension);
} else {
return null;
}
}
/**
* Deletes this folder and all its files and sub-folders recursively
*/
delete() {
super.delete();
this.files.concat(this.subFolders).forEach((item) => {
item.delete();
});
this.getRoot()?.saveData();
}
/**
* @param {Boolean=false} showHidden
* @returns {Array<VirtualFile>}
*/
getFiles(showHidden = false) {
if (showHidden)
return this.files;
return this.files.filter(({ name }) =>
!name.startsWith(".")
);
}
/**
* @param {Boolean=false} showHidden
* @returns {Array<VirtualFolder>}
*/
getSubFolders(showHidden = false) {
if (showHidden)
return this.subFolders;
return this.subFolders.filter(({ name }) =>
!name.startsWith(".")
);
}
toJSON() {
const object = super.toJSON();
if (this.files.length > 0) {
object.fls = this.files.map((file) => file.toJSON());
}
if (this.subFolders.length > 0) {
object.fds = this.subFolders.map((folder) => folder.toJSON());
}
return object;
}
}