mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
119 lines
4.6 KiB
TypeScript
119 lines
4.6 KiB
TypeScript
import type { FileWithHandle } from 'browser-fs-access';
|
|
|
|
|
|
interface FileWithHandleAndPath {
|
|
fileWithHandle: FileWithHandle;
|
|
relativeName: string;
|
|
}
|
|
|
|
/**
|
|
* Recursively get all files from a directory, returning an array of `FileWithHandleAndPath` objects,
|
|
* where the handles are all FileSystemFileHandle objects (allows for LiveFile support).
|
|
*/
|
|
export async function getAllFilesFromDirectoryRecursively(directoryHandle: FileSystemDirectoryHandle): Promise<FileWithHandleAndPath[]> {
|
|
const list: FileWithHandleAndPath[] = [];
|
|
const separator = '/';
|
|
|
|
async function traverseDirectory(dirHandle: FileSystemDirectoryHandle, path: string = '') {
|
|
if ('values' in dirHandle && typeof dirHandle.values === 'function') {
|
|
for await (const handle of dirHandle.values()) {
|
|
if (!handle) continue;
|
|
const relativePath = path ? `${path}${separator}${handle.name}` : handle.name;
|
|
|
|
if (handle.kind === 'file') {
|
|
try {
|
|
const fileWithHandle = await handle.getFile() as FileWithHandle;
|
|
fileWithHandle.handle = handle;
|
|
list.push({
|
|
fileWithHandle: fileWithHandle,
|
|
relativeName: relativePath,
|
|
});
|
|
} catch (error: any) {
|
|
// #845 - Handle NotAllowedError from Edge 141+ and other browsers with strict file permissions
|
|
console.warn(error?.name === 'NotAllowedError' ? '[FileSystem] File access denied, skipping:' : '[FileSystem] Error accessing file:', relativePath, error);
|
|
// #845 - Skip this file and continue directory traversal
|
|
}
|
|
} else if (handle.kind === 'directory') {
|
|
await traverseDirectory(handle, relativePath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
await traverseDirectory(directoryHandle);
|
|
return list;
|
|
}
|
|
|
|
|
|
/// Helpers for handling DataTransfer items that contain File System Handles, and we need to pre-get all the promises to files/handles upfront.
|
|
|
|
/*
|
|
* Note: was File | Promise<Handles | null>, but we added and extra File fallback in the promise
|
|
* to handle cases where the handle is null (e.g. Chrome screen captures), which would break the
|
|
* downstream logic.
|
|
*/
|
|
type FileOrFileHandlePromise = Promise<FileSystemFileHandle | FileSystemDirectoryHandle | File | null> | File;
|
|
|
|
/**
|
|
* Extracts file system handles or files from a list of data transfer items.
|
|
* Note: the main purpose of this function is to get all the files/handles **upfront** in a
|
|
* datatransfer, as those objects expire with async operations.
|
|
*/
|
|
export function getDataTransferFilesOrPromises(items: DataTransferItemList, fallbackAsFiles: boolean): FileOrFileHandlePromise[] {
|
|
const results: FileOrFileHandlePromise[] = [];
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
const item = items[i];
|
|
if (item.kind !== 'file')
|
|
continue;
|
|
|
|
// Try to get file system handle if available and not forced to use file
|
|
if ('getAsFileSystemHandle' in item && typeof item.getAsFileSystemHandle === 'function') {
|
|
try {
|
|
const fsHandle = item.getAsFileSystemHandle() as Promise<FileSystemFileHandle | FileSystemDirectoryHandle | null>;
|
|
if (fsHandle)
|
|
results.push(fsHandle.then(handleOrNull => {
|
|
// if null, return a File instead - note that this is a fallback, as we prefer to get the handle
|
|
// but when pasing screen captures from Chrome, the handle will be null, while the file shall
|
|
// still be retrievable.
|
|
return handleOrNull || item.getAsFile();
|
|
}));
|
|
continue;
|
|
} catch (error) {
|
|
console.error('Error getting file system handle:', error);
|
|
}
|
|
}
|
|
|
|
// Fallback to getAsFile
|
|
if (fallbackAsFiles) {
|
|
const file = item.getAsFile();
|
|
if (file)
|
|
results.push(file);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
|
|
/**
|
|
* Utility function to get the first file system handle from a DataTransfer object.
|
|
* Note that a DataTransfer object can contain multiple files, but we assume the first is the one.
|
|
*/
|
|
export async function getFirstFileSystemFileHandle(dataTransfer: DataTransfer): Promise<FileSystemFileHandle | null> {
|
|
|
|
// get FileSystemFileHandle objects from the DataTransfer
|
|
const fileOrFSHandlePromises = getDataTransferFilesOrPromises(dataTransfer.items, false);
|
|
if (!fileOrFSHandlePromises.length)
|
|
return null;
|
|
|
|
// resolve the promises to get the actual files/handles
|
|
const filesOrHandles = await Promise.all(fileOrFSHandlePromises);
|
|
for (let filesOrHandle of filesOrHandles)
|
|
if (!(filesOrHandle instanceof File) && filesOrHandle?.kind === 'file' && filesOrHandle)
|
|
return filesOrHandle;
|
|
|
|
// no file system handle found
|
|
return null;
|
|
}
|