diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index 1a9582862..abbe54705 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -14,7 +14,7 @@ import type { CollectionMapping, FolderWatch, PendingUploads, - ZipEntry, + ZipItem, } from "../types/ipc"; import { selectDirectory, @@ -56,7 +56,7 @@ import { listZipEntries, markUploadedFiles, markUploadedZipEntries, - pathOrZipEntrySize, + pathOrZipItemSize, pendingUploads, setPendingUploads, } from "./services/upload"; @@ -152,11 +152,10 @@ export const attachIPCHandlers = () => { "generateImageThumbnail", ( _, - dataOrPathOrZipEntry: Uint8Array | string | ZipEntry, + dataOrPathOrZipItem: Uint8Array | string | ZipItem, maxDimension: number, maxSize: number, - ) => - generateImageThumbnail(dataOrPathOrZipEntry, maxDimension, maxSize), + ) => generateImageThumbnail(dataOrPathOrZipItem, maxDimension, maxSize), ); ipcMain.handle( @@ -164,13 +163,13 @@ export const attachIPCHandlers = () => { ( _, command: string[], - dataOrPathOrZipEntry: Uint8Array | string | ZipEntry, + dataOrPathOrZipItem: Uint8Array | string | ZipItem, outputFileExtension: string, timeoutMS: number, ) => ffmpegExec( command, - dataOrPathOrZipEntry, + dataOrPathOrZipItem, outputFileExtension, timeoutMS, ), @@ -210,10 +209,8 @@ export const attachIPCHandlers = () => { listZipEntries(zipPath), ); - ipcMain.handle( - "pathOrZipEntrySize", - (_, pathOrZipEntry: string | ZipEntry) => - pathOrZipEntrySize(pathOrZipEntry), + ipcMain.handle("pathOrZipItemSize", (_, pathOrZipItem: string | ZipItem) => + pathOrZipItemSize(pathOrZipItem), ); ipcMain.handle("pendingUploads", () => pendingUploads()); @@ -229,7 +226,7 @@ export const attachIPCHandlers = () => { ipcMain.handle( "markUploadedZipEntries", - (_, zipEntries: PendingUploads["zipEntries"]) => + (_, zipEntries: PendingUploads["zipItems"]) => markUploadedZipEntries(zipEntries), ); diff --git a/desktop/src/main/services/ffmpeg.ts b/desktop/src/main/services/ffmpeg.ts index ea9ceaf76..35977409a 100644 --- a/desktop/src/main/services/ffmpeg.ts +++ b/desktop/src/main/services/ffmpeg.ts @@ -1,12 +1,12 @@ import pathToFfmpeg from "ffmpeg-static"; import fs from "node:fs/promises"; -import type { ZipEntry } from "../../types/ipc"; +import type { ZipItem } from "../../types/ipc"; import log from "../log"; import { withTimeout } from "../utils"; import { execAsync } from "../utils-electron"; import { deleteTempFile, - makeFileForDataOrPathOrZipEntry, + makeFileForDataOrPathOrZipItem, makeTempFilePath, } from "../utils-temp"; @@ -44,12 +44,12 @@ const outputPathPlaceholder = "OUTPUT"; */ export const ffmpegExec = async ( command: string[], - dataOrPathOrZipEntry: Uint8Array | string | ZipEntry, + dataOrPathOrZipItem: Uint8Array | string | ZipItem, outputFileExtension: string, timeoutMS: number, ): Promise => { // TODO (MR): This currently copies files for both input (when - // dataOrPathOrZipEntry is data) and output. This needs to be tested + // dataOrPathOrZipItem is data) and output. This needs to be tested // extremely large video files when invoked downstream of `convertToMP4` in // the web code. @@ -57,7 +57,7 @@ export const ffmpegExec = async ( path: inputFilePath, isFileTemporary: isInputFileTemporary, writeToTemporaryFile: writeToTemporaryInputFile, - } = await makeFileForDataOrPathOrZipEntry(dataOrPathOrZipEntry); + } = await makeFileForDataOrPathOrZipItem(dataOrPathOrZipItem); const outputFilePath = await makeTempFilePath(outputFileExtension); try { diff --git a/desktop/src/main/services/image.ts b/desktop/src/main/services/image.ts index c55bacdff..c48e87c5b 100644 --- a/desktop/src/main/services/image.ts +++ b/desktop/src/main/services/image.ts @@ -2,12 +2,12 @@ import fs from "node:fs/promises"; import path from "path"; -import { CustomErrorMessage, type ZipEntry } from "../../types/ipc"; +import { CustomErrorMessage, type ZipItem } from "../../types/ipc"; import log from "../log"; import { execAsync, isDev } from "../utils-electron"; import { deleteTempFile, - makeFileForDataOrPathOrZipEntry, + makeFileForDataOrPathOrZipItem, makeTempFilePath, } from "../utils-temp"; @@ -67,7 +67,7 @@ const imageMagickPath = () => path.join(isDev ? "build" : process.resourcesPath, "image-magick"); export const generateImageThumbnail = async ( - dataOrPathOrZipEntry: Uint8Array | string | ZipEntry, + dataOrPathOrZipItem: Uint8Array | string | ZipItem, maxDimension: number, maxSize: number, ): Promise => { @@ -75,7 +75,7 @@ export const generateImageThumbnail = async ( path: inputFilePath, isFileTemporary: isInputFileTemporary, writeToTemporaryFile: writeToTemporaryInputFile, - } = await makeFileForDataOrPathOrZipEntry(dataOrPathOrZipEntry); + } = await makeFileForDataOrPathOrZipItem(dataOrPathOrZipItem); const outputFilePath = await makeTempFilePath("jpeg"); diff --git a/desktop/src/main/services/upload.ts b/desktop/src/main/services/upload.ts index 804a84736..1a1d343c5 100644 --- a/desktop/src/main/services/upload.ts +++ b/desktop/src/main/services/upload.ts @@ -2,11 +2,11 @@ import StreamZip from "node-stream-zip"; import fs from "node:fs/promises"; import { existsSync } from "original-fs"; import path from "path"; -import type { ElectronFile, PendingUploads, ZipEntry } from "../../types/ipc"; +import type { ElectronFile, PendingUploads, ZipItem } from "../../types/ipc"; import { uploadStatusStore } from "../stores/upload-status"; import { getZipFileStream } from "./fs"; -export const listZipEntries = async (zipPath: string): Promise => { +export const listZipEntries = async (zipPath: string): Promise => { const zip = new StreamZip.async({ file: zipPath }); const entries = await zip.entries(); @@ -26,14 +26,14 @@ export const listZipEntries = async (zipPath: string): Promise => { return entryNames.map((entryName) => [zipPath, entryName]); }; -export const pathOrZipEntrySize = async ( - pathOrZipEntry: string | ZipEntry, +export const pathOrZipItemSize = async ( + pathOrZipItem: string | ZipItem, ): Promise => { - if (typeof pathOrZipEntry == "string") { - const stat = await fs.stat(pathOrZipEntry); + if (typeof pathOrZipItem == "string") { + const stat = await fs.stat(pathOrZipItem); return stat.size; } else { - const [zipPath, entryName] = pathOrZipEntry; + const [zipPath, entryName] = pathOrZipItem; const zip = new StreamZip.async({ file: zipPath }); const entry = await zip.entry(entryName); const size = entry.size; @@ -73,7 +73,7 @@ export const pendingUploads = async (): Promise => { return { collectionName, filePaths, - zipEntries, + zipItems: zipEntries, }; }; diff --git a/desktop/src/main/stores/upload-status.ts b/desktop/src/main/stores/upload-status.ts index 20a431fd9..4fd6c9860 100644 --- a/desktop/src/main/stores/upload-status.ts +++ b/desktop/src/main/stores/upload-status.ts @@ -1,17 +1,28 @@ import Store, { Schema } from "electron-store"; export interface UploadStatusStore { - /* The collection to which we're uploading, or the root collection. */ + /** + * The collection to which we're uploading, or the root collection. + * + * Not all pending uploads will have an associated collection. + */ collectionName?: string; - /** Paths to regular files that are pending upload */ + /** + * Paths to regular files that are pending upload. + * + * This should generally be present, albeit empty, but it is marked optional + * in sympathy with its siblings. + */ filePaths?: string[]; /** * Each item is the path to a zip file and the name of an entry within it. * * This is marked optional since legacy stores will not have it. */ - zipEntries?: [zipPath: string, entryName: string][]; - /** Legacy paths to zip files, now subsumed into zipEntries */ + zipItems?: [zipPath: string, entryName: string][]; + /** + * @deprecated Legacy paths to zip files, now subsumed into zipEntries. + */ zipPaths?: string[]; } @@ -25,7 +36,7 @@ const uploadStatusSchema: Schema = { type: "string", }, }, - zipEntries: { + zipItems: { type: "array", items: { type: "array", diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index bcffe2cc5..b37970cfa 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -95,10 +95,10 @@ const handleRead = async (path: string) => { } }; -const handleReadZip = async (zipPath: string, zipEntryPath: string) => { +const handleReadZip = async (zipPath: string, entryName: string) => { try { const zip = new StreamZip.async({ file: zipPath }); - const entry = await zip.entry(zipEntryPath); + const entry = await zip.entry(entryName); const stream = await zip.stream(entry); // TODO(MR): when to call zip.close() @@ -119,7 +119,7 @@ const handleReadZip = async (zipPath: string, zipEntryPath: string) => { }); } catch (e) { log.error( - `Failed to read entry ${zipEntryPath} from zip file at ${zipPath}`, + `Failed to read entry ${entryName} from zip file at ${zipPath}`, e, ); return new Response(`Failed to read stream: ${e.message}`, { diff --git a/desktop/src/main/utils-temp.ts b/desktop/src/main/utils-temp.ts index 2e416bd65..3f3a6081e 100644 --- a/desktop/src/main/utils-temp.ts +++ b/desktop/src/main/utils-temp.ts @@ -3,7 +3,7 @@ import StreamZip from "node-stream-zip"; import { existsSync } from "node:fs"; import fs from "node:fs/promises"; import path from "path"; -import type { ZipEntry } from "../types/ipc"; +import type { ZipItem } from "../types/ipc"; /** * Our very own directory within the system temp directory. Go crazy, but @@ -64,9 +64,11 @@ export const deleteTempFile = async (tempFilePath: string) => { await fs.rm(tempFilePath, { force: true }); }; -/** The result of {@link makeFileForDataOrPathOrZipEntry}. */ -interface FileForDataOrPathOrZipEntry { - /** The path to the file (possibly temporary) */ +/** The result of {@link makeFileForDataOrPathOrZipItem}. */ +interface FileForDataOrPathOrZipItem { + /** + * The path to the file (possibly temporary). + */ path: string; /** * `true` if {@link path} points to a temporary file which should be deleted @@ -75,12 +77,12 @@ interface FileForDataOrPathOrZipEntry { isFileTemporary: boolean; /** * If set, this'll be a function that can be called to actually write the - * contents of the source `Uint8Array | string | ZipEntry` into the file at + * contents of the source `Uint8Array | string | ZipItem` into the file at * {@link path}. * * It will be undefined if the source is already a path since nothing needs * to be written in that case. In the other two cases this function will - * write the data or zip entry into the file at {@link path}. + * write the data or zip item into the file at {@link path}. */ writeToTemporaryFile?: () => Promise; } @@ -88,31 +90,31 @@ interface FileForDataOrPathOrZipEntry { /** * Return the path to a file, a boolean indicating if this is a temporary path * that needs to be deleted after processing, and a function to write the given - * {@link dataOrPathOrZipEntry} into that temporary file if needed. + * {@link dataOrPathOrZipItem} into that temporary file if needed. * - * @param dataOrPathOrZipEntry The contents of the file, or the path to an + * @param dataOrPathOrZipItem The contents of the file, or the path to an * existing file, or a (path to a zip file, name of an entry within that zip * file) tuple. */ -export const makeFileForDataOrPathOrZipEntry = async ( - dataOrPathOrZipEntry: Uint8Array | string | ZipEntry, -): Promise => { +export const makeFileForDataOrPathOrZipItem = async ( + dataOrPathOrZipItem: Uint8Array | string | ZipItem, +): Promise => { let path: string; let isFileTemporary: boolean; let writeToTemporaryFile: () => Promise | undefined; - if (typeof dataOrPathOrZipEntry == "string") { - path = dataOrPathOrZipEntry; + if (typeof dataOrPathOrZipItem == "string") { + path = dataOrPathOrZipItem; isFileTemporary = false; } else { path = await makeTempFilePath(); isFileTemporary = true; - if (dataOrPathOrZipEntry instanceof Uint8Array) { + if (dataOrPathOrZipItem instanceof Uint8Array) { writeToTemporaryFile = () => - fs.writeFile(path, dataOrPathOrZipEntry); + fs.writeFile(path, dataOrPathOrZipItem); } else { writeToTemporaryFile = async () => { - const [zipPath, entryName] = dataOrPathOrZipEntry; + const [zipPath, entryName] = dataOrPathOrZipItem; const zip = new StreamZip.async({ file: zipPath }); await zip.extract(entryName, path); zip.close(); diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index 76d44591e..48e4d1448 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -47,7 +47,7 @@ import type { ElectronFile, FolderWatch, PendingUploads, - ZipEntry, + ZipItem, } from "./types/ipc"; // - General @@ -129,27 +129,27 @@ const convertToJPEG = (imageData: Uint8Array): Promise => ipcRenderer.invoke("convertToJPEG", imageData); const generateImageThumbnail = ( - dataOrPathOrZipEntry: Uint8Array | string | ZipEntry, + dataOrPathOrZipItem: Uint8Array | string | ZipItem, maxDimension: number, maxSize: number, ): Promise => ipcRenderer.invoke( "generateImageThumbnail", - dataOrPathOrZipEntry, + dataOrPathOrZipItem, maxDimension, maxSize, ); const ffmpegExec = ( command: string[], - dataOrPathOrZipEntry: Uint8Array | string | ZipEntry, + dataOrPathOrZipItem: Uint8Array | string | ZipItem, outputFileExtension: string, timeoutMS: number, ): Promise => ipcRenderer.invoke( "ffmpegExec", command, - dataOrPathOrZipEntry, + dataOrPathOrZipItem, outputFileExtension, timeoutMS, ); @@ -241,12 +241,11 @@ const watchFindFiles = (folderPath: string): Promise => const pathForFile = (file: File) => webUtils.getPathForFile(file); -const listZipEntries = (zipPath: string): Promise => +const listZipEntries = (zipPath: string): Promise => ipcRenderer.invoke("listZipEntries", zipPath); -const pathOrZipEntrySize = ( - pathOrZipEntry: string | ZipEntry, -): Promise => ipcRenderer.invoke("pathOrZipEntrySize", pathOrZipEntry); +const pathOrZipItemSize = (pathOrZipItem: string | ZipItem): Promise => + ipcRenderer.invoke("pathOrZipItemSize", pathOrZipItem); const pendingUploads = (): Promise => ipcRenderer.invoke("pendingUploads"); @@ -258,7 +257,7 @@ const markUploadedFiles = (paths: PendingUploads["filePaths"]): Promise => ipcRenderer.invoke("markUploadedFiles", paths); const markUploadedZipEntries = ( - zipEntries: PendingUploads["zipEntries"], + zipEntries: PendingUploads["zipItems"], ): Promise => ipcRenderer.invoke("markUploadedZipEntries", zipEntries); const clearPendingUploads = (): Promise => @@ -374,7 +373,7 @@ contextBridge.exposeInMainWorld("electron", { pathForFile, listZipEntries, - pathOrZipEntrySize, + pathOrZipItemSize, pendingUploads, setPendingUploads, markUploadedFiles, diff --git a/desktop/src/types/ipc.ts b/desktop/src/types/ipc.ts index 307fb7de3..6e47b7a3a 100644 --- a/desktop/src/types/ipc.ts +++ b/desktop/src/types/ipc.ts @@ -25,12 +25,12 @@ export interface FolderWatchSyncedFile { collectionID: number; } -export type ZipEntry = [zipPath: string, entryName: string]; +export type ZipItem = [zipPath: string, entryName: string]; export interface PendingUploads { collectionName: string; filePaths: string[]; - zipEntries: ZipEntry[]; + zipItems: ZipItem[]; } /**