Files
daedalOS/contexts/fileSystem/useFileSystemContextState.ts

367 lines
11 KiB
TypeScript
Raw Normal View History

import type HTTPRequest from "browserfs/dist/node/backend/HTTPRequest";
import type IndexedDBFileSystem from "browserfs/dist/node/backend/IndexedDB";
import type IIsoFS from "browserfs/dist/node/backend/IsoFS";
import type OverlayFS from "browserfs/dist/node/backend/OverlayFS";
import type IZipFS from "browserfs/dist/node/backend/ZipFS";
2021-11-06 21:36:41 -07:00
import type { ApiError } from "browserfs/dist/node/core/api_error";
import type { BFSCallback } from "browserfs/dist/node/core/file_system";
import type { FSModule } from "browserfs/dist/node/core/FS";
import type { InputChangeEvent } from "components/system/Files/FileManager/functions";
2021-11-06 21:36:41 -07:00
import {
handleFileInputEvent,
iterateFileName,
} from "components/system/Files/FileManager/functions";
2022-02-15 12:53:07 -08:00
import {
addFileSystemHandle,
getFileSystemHandles,
KEYVAL_DB,
2022-02-15 19:45:43 -08:00
removeFileSystemHandle,
2022-02-15 12:53:07 -08:00
} from "contexts/fileSystem/functions";
2021-12-11 21:53:42 -08:00
import type { AsyncFS, RootFileSystem } from "contexts/fileSystem/useAsyncFs";
2021-10-16 21:47:42 -07:00
import useAsyncFs from "contexts/fileSystem/useAsyncFs";
2021-08-14 23:18:07 -07:00
import type { UpdateFiles } from "contexts/session/types";
2021-12-30 10:36:58 -08:00
import useDialog from "hooks/useDialog";
2021-11-06 21:36:41 -07:00
import { basename, dirname, extname, isAbsolute, join } from "path";
2021-11-06 21:12:56 -07:00
import { useCallback, useEffect, useState } from "react";
2021-08-21 22:19:06 -07:00
type FilePasteOperations = Record<string, "copy" | "move">;
export type FileSystemContextState = AsyncFS & {
2021-11-27 22:32:02 -08:00
addFile: (
directory: string,
2022-04-23 23:35:10 -07:00
callback: (name: string, buffer?: Buffer) => void,
accept?: string,
multiple?: boolean
2021-11-27 22:32:02 -08:00
) => void;
2021-08-14 23:18:07 -07:00
addFsWatcher: (folder: string, updateFiles: UpdateFiles) => void;
2022-03-31 14:00:34 -07:00
copyEntries: (entries: string[]) => void;
2021-11-06 21:36:41 -07:00
createPath: (
name: string,
directory: string,
buffer?: Buffer
) => Promise<string>;
deletePath: (path: string) => Promise<void>;
2022-03-31 14:00:34 -07:00
fs?: FSModule;
mapFs: (directory: string) => Promise<string>;
mkdirRecursive: (path: string) => Promise<void>;
mountFs: (url: string) => Promise<void>;
moveEntries: (entries: string[]) => void;
pasteList: FilePasteOperations;
removeFsWatcher: (folder: string, updateFiles: UpdateFiles) => void;
resetStorage: () => Promise<void>;
rootFs?: RootFileSystem;
unMapFs: (directory: string) => void;
unMountFs: (url: string) => void;
updateFolder: (folder: string, newFile?: string, oldFile?: string) => void;
2021-03-13 21:29:22 -08:00
};
const useFileSystemContextState = (): FileSystemContextState => {
2021-12-11 21:53:42 -08:00
const { rootFs, FileSystemAccess, IsoFS, ZipFS, ...asyncFs } = useAsyncFs();
const { exists, mkdir, readdir, readFile, rename, rmdir, unlink, writeFile } =
asyncFs;
2021-08-14 23:18:07 -07:00
const [fsWatchers, setFsWatchers] = useState<Record<string, UpdateFiles[]>>(
{}
);
2021-08-21 22:19:06 -07:00
const [pasteList, setPasteList] = useState<FilePasteOperations>({});
const updatePasteEntries = (
entries: string[],
operation: "copy" | "move"
): void =>
setPasteList(
Object.fromEntries(entries.map((entry) => [entry, operation]))
);
const copyEntries = (entries: string[]): void =>
updatePasteEntries(entries, "copy");
const moveEntries = (entries: string[]): void =>
updatePasteEntries(entries, "move");
2021-08-14 23:18:07 -07:00
const addFsWatcher = useCallback(
(folder: string, updateFiles: UpdateFiles): void =>
setFsWatchers((currentFsWatcher) => ({
...currentFsWatcher,
2021-08-21 22:36:35 -07:00
[folder]: [...(currentFsWatcher[folder] || []), updateFiles],
2021-08-14 23:18:07 -07:00
})),
[]
);
const removeFsWatcher = useCallback(
(folder: string, updateFiles: UpdateFiles): void =>
setFsWatchers((currentFsWatcher) => ({
...currentFsWatcher,
2021-08-21 22:36:35 -07:00
[folder]: (currentFsWatcher[folder] || []).filter(
2021-08-14 23:18:07 -07:00
(updateFilesInstance) => updateFilesInstance !== updateFiles
),
})),
[]
);
const updateFolder = useCallback(
(folder: string, newFile?: string, oldFile?: string): void =>
fsWatchers[folder]?.forEach((updateFiles) =>
updateFiles(newFile, oldFile)
),
2021-08-14 23:18:07 -07:00
[fsWatchers]
);
2022-02-15 12:53:07 -08:00
const mapFs = useCallback(
async (
directory: string,
existingHandle?: FileSystemDirectoryHandle
): Promise<string> => {
let handle: FileSystemDirectoryHandle;
2021-12-18 21:12:35 -08:00
2022-02-15 12:53:07 -08:00
try {
handle = existingHandle ?? (await window.showDirectoryPicker());
} catch {
// Ignore cancelling the dialog
}
2021-12-11 21:53:42 -08:00
2022-02-15 12:53:07 -08:00
return new Promise((resolve, reject) => {
2022-02-15 21:03:57 -08:00
if (handle instanceof FileSystemDirectoryHandle) {
FileSystemAccess?.Create({ handle }, (error, newFs) => {
2022-05-12 10:47:15 -07:00
if (error || !newFs) {
reject();
return;
2022-02-15 21:03:57 -08:00
}
2022-05-12 10:47:15 -07:00
rootFs?.mount?.(join(directory, handle.name), newFs);
resolve(handle.name);
addFileSystemHandle(directory, handle);
2022-02-15 21:03:57 -08:00
});
}
2021-12-11 21:53:42 -08:00
});
2022-02-15 12:53:07 -08:00
},
[FileSystemAccess, rootFs]
);
2021-10-16 21:47:42 -07:00
const mountFs = async (url: string): Promise<void> => {
const fileData = await readFile(url);
2021-06-12 23:16:59 -07:00
2021-10-16 21:47:42 -07:00
return new Promise((resolve, reject) => {
const createFs: BFSCallback<IIsoFS | IZipFS> = (createError, newFs) => {
2021-10-16 21:47:42 -07:00
if (createError) reject();
else if (newFs) {
rootFs?.mount?.(url, newFs);
2021-10-16 21:47:42 -07:00
resolve();
2021-08-21 21:57:43 -07:00
}
2021-10-16 21:47:42 -07:00
};
2021-12-25 21:53:01 -08:00
if (extname(url).toLowerCase() === ".iso") {
IsoFS?.Create({ data: fileData }, createFs);
2021-10-16 21:47:42 -07:00
} else {
ZipFS?.Create({ zipData: fileData }, createFs);
2021-10-16 21:47:42 -07:00
}
});
};
2022-02-15 19:45:43 -08:00
const unMountFs = useCallback(
(url: string): void => rootFs?.umount?.(url),
[rootFs]
);
const unMapFs = useCallback(
(directory: string): void => {
updateFolder(dirname(directory), undefined, directory);
removeFileSystemHandle(directory);
unMountFs(directory);
},
[unMountFs, updateFolder]
);
2021-11-27 22:32:02 -08:00
const { openTransferDialog } = useDialog();
const addFile = (
directory: string,
callback: (name: string, buffer?: Buffer) => void
2021-11-27 22:32:02 -08:00
): void => {
2022-03-16 19:49:58 -07:00
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.multiple = true;
2022-03-16 19:49:58 -07:00
fileInput.setAttribute("style", "display: none");
fileInput.addEventListener(
2021-10-16 21:47:42 -07:00
"change",
(event) => {
2022-03-16 19:49:58 -07:00
handleFileInputEvent(
event as InputChangeEvent,
2022-03-16 19:49:58 -07:00
callback,
directory,
openTransferDialog
);
fileInput.remove();
},
2021-10-16 21:47:42 -07:00
{ once: true }
);
2022-03-16 19:49:58 -07:00
document.body.appendChild(fileInput);
fileInput.click();
2021-07-31 22:06:13 -07:00
};
2021-12-04 21:11:52 -08:00
const resetStorage = (): Promise<void> =>
new Promise((resolve, reject) => {
2021-12-04 21:11:52 -08:00
localStorage.clear();
sessionStorage.clear();
const clearFs = (): void => {
2021-12-04 21:11:52 -08:00
const overlayFs = rootFs?._getFs("/")?.fs as OverlayFS;
2022-03-16 20:21:43 -07:00
const overlayedFileSystems = overlayFs?.getOverlayedFileSystems();
const readable = overlayedFileSystems?.readable as HTTPRequest;
const writable = overlayedFileSystems?.writable as IndexedDBFileSystem;
2021-12-04 21:11:52 -08:00
2022-03-16 20:21:43 -07:00
readable?.empty();
2022-01-06 13:31:20 -08:00
2022-03-16 20:21:43 -07:00
if (writable?.getName() === "InMemory") {
2022-01-06 13:31:20 -08:00
resolve();
} else {
2022-03-16 20:21:43 -07:00
writable?.empty((apiError) =>
2022-01-06 13:31:20 -08:00
apiError ? reject(apiError) : resolve()
);
}
};
2022-03-12 19:18:42 -08:00
if (!window.indexedDB.databases) clearFs();
2021-12-11 22:48:18 -08:00
else {
import("idb").then(({ deleteDB }) =>
deleteDB(KEYVAL_DB).then(clearFs).catch(clearFs)
);
}
});
2021-10-16 21:47:42 -07:00
const mkdirRecursive = async (path: string): Promise<void> => {
2021-08-28 21:14:59 -07:00
const pathParts = path.split("/").filter(Boolean);
2021-10-16 21:47:42 -07:00
const recursePath = async (position = 1): Promise<void> => {
2021-08-28 21:14:59 -07:00
const makePath = join("/", pathParts.slice(0, position).join("/"));
2021-11-20 22:22:25 -08:00
let created: boolean;
try {
created = (await exists(makePath)) || (await mkdir(makePath));
} catch {
created = false;
}
2021-08-28 21:14:59 -07:00
2021-10-16 21:47:42 -07:00
if (created && position !== pathParts.length) {
await recursePath(position + 1);
}
2021-08-28 21:14:59 -07:00
};
2021-10-16 21:47:42 -07:00
await recursePath();
2021-08-28 21:14:59 -07:00
};
const deletePath = async (path: string): Promise<void> => {
try {
await unlink(path);
} catch (error) {
if ((error as ApiError).code === "EISDIR") {
const dirContents = await readdir(path);
await Promise.all(
dirContents.map((entry) => deletePath(join(path, entry)))
);
await rmdir(path);
}
}
};
2021-11-06 21:36:41 -07:00
const createPath = async (
name: string,
directory: string,
buffer?: Buffer,
iteration = 0
): Promise<string> => {
const isInternal = !buffer && isAbsolute(name);
const baseName = isInternal ? basename(name) : name;
const uniqueName = !iteration
? baseName
: iterateFileName(baseName, iteration);
const fullNewPath = join(directory, uniqueName);
if (isInternal) {
2021-12-18 21:41:50 -08:00
if (
name !== fullNewPath &&
!directory.startsWith(name) &&
!rootFs?.mntMap[name]
) {
2021-11-06 21:36:41 -07:00
if (await exists(fullNewPath)) {
return createPath(name, directory, buffer, iteration + 1);
}
if (await rename(name, fullNewPath)) {
updateFolder(dirname(name), "", name);
}
return uniqueName;
}
} else {
const baseDir = dirname(fullNewPath);
2022-02-26 20:43:26 -08:00
try {
2022-02-26 20:43:26 -08:00
if (!(await exists(baseDir))) {
await mkdir(baseDir);
updateFolder(dirname(baseDir), basename(baseDir));
}
} catch {
// Ignore error to make directory
}
2022-02-26 20:43:26 -08:00
try {
2021-11-06 21:36:41 -07:00
if (
buffer
? await writeFile(fullNewPath, buffer)
: await mkdir(fullNewPath)
) {
return uniqueName;
}
} catch (error) {
2022-02-26 20:43:26 -08:00
if ((error as ApiError)?.code === "EEXIST") {
2021-11-06 21:36:41 -07:00
return createPath(name, directory, buffer, iteration + 1);
}
}
}
return "";
};
2022-02-15 12:53:07 -08:00
const restoreFsHandles = useCallback(
async (): Promise<void> =>
Object.entries(await getFileSystemHandles()).forEach(
async ([handleDirectory, handle]) => {
if (!(await exists(handleDirectory))) {
2022-03-05 17:57:01 -08:00
mapFs(dirname(handleDirectory), handle);
2022-02-15 12:53:07 -08:00
}
}
),
[exists, mapFs]
);
useEffect(() => {
restoreFsHandles();
}, [restoreFsHandles]);
2021-08-14 23:18:07 -07:00
2021-11-06 21:12:56 -07:00
useEffect(() => {
const watchedPaths = Object.keys(fsWatchers).filter(
(watchedPath) => fsWatchers[watchedPath].length > 0
);
const mountedPaths = Object.keys(rootFs?.mntMap || {}).filter(
(mountedPath) => mountedPath !== "/"
);
mountedPaths.forEach((mountedPath) => {
if (
2021-12-11 21:53:42 -08:00
!watchedPaths.some((watchedPath) =>
watchedPath.startsWith(mountedPath)
) &&
rootFs?.mntMap[mountedPath]?.getName() !== "FileSystemAccess"
2021-11-06 21:12:56 -07:00
) {
rootFs?.umount?.(mountedPath);
}
});
}, [fsWatchers, rootFs]);
2021-08-14 23:18:07 -07:00
return {
2021-10-09 21:51:01 -07:00
addFile,
addFsWatcher,
copyEntries,
2021-11-06 21:36:41 -07:00
createPath,
deletePath,
2021-12-11 21:53:42 -08:00
mapFs,
2021-10-09 21:51:01 -07:00
mkdirRecursive,
2021-08-14 23:18:07 -07:00
mountFs,
2021-10-09 21:51:01 -07:00
moveEntries,
pasteList,
removeFsWatcher,
2021-12-04 21:11:52 -08:00
resetStorage,
2021-12-11 21:53:42 -08:00
rootFs,
2022-02-15 19:45:43 -08:00
unMapFs,
2021-08-14 23:18:07 -07:00
unMountFs,
updateFolder,
2021-10-16 21:47:42 -07:00
...asyncFs,
2021-08-14 23:18:07 -07:00
};
};
export default useFileSystemContextState;