From 2d40f530a7df06f0228ed7e1c54dff591d8dac44 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 29 Apr 2024 15:43:17 +0530 Subject: [PATCH] more --- desktop/src/preload.ts | 5 +- .../photos/src/components/Upload/Uploader.tsx | 149 ++++++------------ web/packages/next/types/file.ts | 5 +- web/packages/next/types/ipc.ts | 14 +- 4 files changed, 67 insertions(+), 106 deletions(-) diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index e80625de99..4bb23b9ac6 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -37,7 +37,7 @@ * - [main] desktop/src/main/ipc.ts contains impl */ -import { contextBridge, ipcRenderer } from "electron/renderer"; +import { contextBridge, ipcRenderer, webUtils } from "electron/renderer"; // While we can't import other code, we can import types since they're just // needed when compiling and will not be needed or looked around for at runtime. @@ -242,6 +242,8 @@ const watchFindFiles = (folderPath: string): Promise => // - Upload +const pathForFile = (file: File) => webUtils.getPathForFile(file); + const listZipEntries = (zipPath: string): Promise => ipcRenderer.invoke("listZipEntries", zipPath); @@ -370,6 +372,7 @@ contextBridge.exposeInMainWorld("electron", { // - Upload + pathForFile, listZipEntries, pendingUploads, setPendingUploads, diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx index d8087eb5b0..41b9f48540 100644 --- a/web/apps/photos/src/components/Upload/Uploader.tsx +++ b/web/apps/photos/src/components/Upload/Uploader.tsx @@ -1,6 +1,6 @@ import log from "@/next/log"; import { ElectronFile, type FileAndPath } from "@/next/types/file"; -import type { CollectionMapping, ZipEntry } from "@/next/types/ipc"; +import type { CollectionMapping, Electron, ZipEntry } from "@/next/types/ipc"; import { CustomError } from "@ente/shared/error"; import { isPromise } from "@ente/shared/utils"; import DiscFullIcon from "@mui/icons-material/DiscFull"; @@ -86,7 +86,13 @@ interface Props { activeCollection?: Collection; } -export default function Uploader(props: Props) { +export default function Uploader({ + dragAndDropFiles, + fileSelectorFiles, + folderSelectorFiles, + fileSelectorZipFiles, + ...props +}: Props) { const appContext = useContext(AppContext); const galleryContext = useContext(GalleryContext); const publicCollectionGalleryContext = useContext( @@ -266,108 +272,28 @@ export default function Uploader(props: Props) { // as they are folder being dropped for watching return; } - if ( - pickedUploadType.current === PICKED_UPLOAD_TYPE.FOLDERS && - props.webFolderSelectorFiles?.length > 0 - ) { - log.info(`received folder upload request`); - setWebFiles(props.webFolderSelectorFiles); - } else if ( - pickedUploadType.current === PICKED_UPLOAD_TYPE.FILES && - props.webFileSelectorFiles?.length > 0 - ) { - log.info(`received file upload request`); - setWebFiles(props.webFileSelectorFiles); - } else if ( - pickedUploadType.current === PICKED_UPLOAD_TYPE.ZIPS && - props.webFileSelectorZipFiles?.length > 0 - ) { - if (electron) { - const main = async () => { - const zips: File[] = []; - let electronFiles = [] as ElectronFile[]; - for (const file of props.webFileSelectorZipFiles) { - if (file.name.endsWith(".zip")) { - const zipFiles = await electron.lsZip( - (file as any).path, - ); - log.info( - `zip file - ${file.name} contains ${zipFiles.length} files`, - ); - zips.push(file); - // TODO(MR): This cast is incorrect, but interim. - electronFiles = [ - ...electronFiles, - ...(zipFiles as unknown as ElectronFile[]), - ]; - } - } - // setWebFiles(props.webFileSelectorZipFiles); - zipPaths.current = zips.map((file) => (file as any).path); - setElectronFiles(electronFiles); - }; - main(); - } - } else if (props.dragAndDropFiles?.length > 0) { - isDragAndDrop.current = true; - if (electron) { - const main = async () => { - try { - // check and parse dropped files which are zip files - log.info(`uploading dropped files from desktop app`); - const zips: File[] = []; - let electronFiles = [] as ElectronFile[]; - for (const file of props.dragAndDropFiles) { - if (file.name.endsWith(".zip")) { - const zipFiles = await electron.lsZip( - (file as any).path, - ); - log.info( - `zip file - ${file.name} contains ${zipFiles.length} files`, - ); - zips.push(file); - // TODO(MR): This cast is incorrect, but interim. - electronFiles = [ - ...electronFiles, - ...(zipFiles as unknown as ElectronFile[]), - ]; - } else { - // type cast to ElectronFile as the file is dropped from desktop app - // type file and ElectronFile should be interchangeable, but currently they have some differences. - // Typescript is giving error - // Conversion of type 'File' to type 'ElectronFile' may be a mistake because neither type sufficiently - // overlaps with the other. If this was intentional, convert the expression to 'unknown' first. - // Type 'File' is missing the following properties from type 'ElectronFile': path, blob - // for now patching by type casting first to unknown and then to ElectronFile - // TODO: fix types and remove type cast - electronFiles.push( - file as unknown as ElectronFile, - ); - } - } - log.info( - `uploading dropped files from desktop app - ${electronFiles.length} files found`, - ); - zipPaths.current = zips.map( - (file) => (file as any).path, - ); - setElectronFiles(electronFiles); - } catch (e) { - log.error("failed to upload desktop dropped files", e); - setWebFiles(props.dragAndDropFiles); - } - }; - main(); - } else { - log.info(`uploading dropped files from web app`); - setWebFiles(props.dragAndDropFiles); - } + + const files = [ + dragAndDropFiles, + fileSelectorFiles, + folderSelectorFiles, + fileSelectorZipFiles, + ].flat(); + if (electron) { + desktopFilesAndZipEntries(electron, files).then( + ({ fileAndPaths, zipEntries }) => { + setDesktopFiles(fileAndPaths); + setDesktopZipEntries(zipEntries); + }, + ); + } else { + setWebFiles(files); } }, [ - props.dragAndDropFiles, - props.webFileSelectorFiles, - props.webFolderSelectorFiles, - props.webFileSelectorZipFiles, + dragAndDropFiles, + fileSelectorFiles, + folderSelectorFiles, + fileSelectorZipFiles, ]); useEffect(() => { @@ -905,6 +831,25 @@ async function waitAndRun( await task(); } +const desktopFilesAndZipEntries = async ( + electron: Electron, + files: File[], +): Promise<{ fileAndPaths: FileAndPath[]; zipEntries: ZipEntry[] }> => { + const fileAndPaths: FileAndPath[] = []; + const zipEntries: ZipEntry[] = []; + + for (const file of files) { + const path = electron.pathForFile(file); + if (file.name.endsWith(".zip")) { + zipEntries = zipEntries.concat(await electron.listZipEntries(path)); + } else { + fileAndPaths.push({ file, path }); + } + } + + return { fileAndPaths, zipEntries }; +}; + // This is used to prompt the user the make upload strategy choice interface ImportSuggestion { rootFolderName: string; diff --git a/web/packages/next/types/file.ts b/web/packages/next/types/file.ts index 5d6b62550d..6dd1032cdb 100644 --- a/web/packages/next/types/file.ts +++ b/web/packages/next/types/file.ts @@ -18,8 +18,9 @@ export interface ElectronFile { /** * When we are running in the context of our desktop app, we have access to the - * absolute path of the file under consideration. This type combines these two - * bits of information to remove the need to query it again and again. + * absolute path of {@link File} objects. This convenience type clubs these two + * bits of information, saving us the need to query the path again and again + * using the {@link getPathForFile} method of {@link Electron}. */ export interface FileAndPath { file: File; diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index 7198a2ebce..dab10cc8e0 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -465,6 +465,18 @@ export interface Electron { // - Upload + /** + * Return the file system path that this File object points to. + * + * This method is a bit different from the other methods on the Electron + * object in the sense that there is no actual IPC happening - the + * implementation of this method is completely in the preload script. Thus + * we can pass it an otherwise unserializable File object. + * + * Consequently, it is also _not_ async. + */ + pathForFile: (file: File) => string; + /** * Get the list of files that are present in the given zip file. * @@ -478,7 +490,7 @@ export interface Electron { * * To read the contents of the files themselves, see [Note: IPC streams]. */ - listZipEntries : (zipPath: string) => Promise + listZipEntries: (zipPath: string) => Promise; /** * Return any pending uploads that were previously enqueued but haven't yet