[photos-desktop] Disable node integration - Part x/x (#1176)

This continues the refactoring to disable node integration in our
rendered process. The code is still in a WIP state, and more PRs in this
series will come.
This commit is contained in:
Manav Rathi 2024-03-22 16:06:16 +05:30 committed by GitHub
commit 22e57669fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 191 additions and 348 deletions

View file

@ -42,13 +42,6 @@ This spins up a server for serving files using a protocol handler inside our
Electron process. This allows us to directly use the output produced by
`next build` for loading into our renderer process.
### electron-reload
Reloads contents of the BrowserWindow (renderer process) when source files are
changed.
* TODO (MR): Do we need this? Isn't the next-electron-server HMR covering this?
## DX
See [web/docs/dependencies#DX](../../web/docs/dependencies.md#dx) for the

View file

@ -25,7 +25,6 @@
"chokidar": "^3.5.3",
"compare-versions": "^6.1.0",
"electron-log": "^4.3.5",
"electron-reload": "^2.0.0-alpha.1",
"electron-store": "^8.0.1",
"electron-updater": "^4.3.8",
"ffmpeg-static": "^5.1.0",
@ -33,17 +32,13 @@
"html-entities": "^2.4.0",
"jpeg-js": "^0.4.4",
"next-electron-server": "^1",
"node-fetch": "^2.6.7",
"node-stream-zip": "^1.15.0",
"onnxruntime-node": "^1.16.3",
"promise-fs": "^2.1.1"
"onnxruntime-node": "^1.16.3"
},
"devDependencies": {
"@types/auto-launch": "^5.0.2",
"@types/ffmpeg-static": "^3.0.1",
"@types/get-folder-size": "^2.0.0",
"@types/node-fetch": "^2.6.2",
"@types/promise-fs": "^2.1.1",
"@typescript-eslint/eslint-plugin": "^7",
"@typescript-eslint/parser": "^7",
"concurrently": "^8",

View file

@ -1,6 +1,7 @@
import { ipcRenderer } from "electron/renderer";
import { existsSync } from "node:fs";
import * as fs from "node:fs/promises";
import path from "path";
import { existsSync, mkdir, rmSync } from "promise-fs";
import { DiskCache } from "../services/diskCache";
const ENTE_CACHE_DIR_NAME = "ente";
@ -21,16 +22,14 @@ export async function openDiskCache(
cacheLimitInBytes?: number,
) {
const cacheBucketDir = await getCacheBucketDir(cacheName);
if (!existsSync(cacheBucketDir)) {
await mkdir(cacheBucketDir, { recursive: true });
}
await fs.mkdir(cacheBucketDir, { recursive: true });
return new DiskCache(cacheBucketDir, cacheLimitInBytes);
}
export async function deleteDiskCache(cacheName: string) {
const cacheBucketDir = await getCacheBucketDir(cacheName);
if (existsSync(cacheBucketDir)) {
rmSync(cacheBucketDir, { recursive: true, force: true });
await fs.rm(cacheBucketDir, { recursive: true, force: true });
return true;
} else {
return false;

View file

@ -1,23 +0,0 @@
import * as fs from "promise-fs";
import { writeStream } from "./../services/fs";
export const exists = (path: string) => {
return fs.existsSync(path);
};
export const checkExistsAndCreateDir = async (dirPath: string) => {
if (!fs.existsSync(dirPath)) {
await fs.mkdir(dirPath);
}
};
export const saveStreamToDisk = async (
filePath: string,
fileStream: ReadableStream<Uint8Array>,
) => {
await writeStream(filePath, fileStream);
};
export const saveFileToDisk = async (path: string, fileData: string) => {
await fs.writeFile(path, fileData);
};

View file

@ -1,5 +1,4 @@
import { app, BrowserWindow } from "electron";
import electronReload from "electron-reload";
import serveNextAt from "next-electron-server";
import { initWatcher } from "./services/chokidar";
import { isDev } from "./utils/common";
@ -42,19 +41,6 @@ export const setIsUpdateAvailable = (value: boolean): void => {
updateIsAvailable = value;
};
/**
* Hot reload the main process if anything changes in the source directory that
* we're running from.
*
* In particular, this gets triggered when the `tsc -w` rebuilds JS files in the
* `app/` directory when we change the TS files in the `src/` directory.
*/
const setupMainHotReload = () => {
if (isDev) {
electronReload(__dirname, {});
}
};
/**
* The URL where the renderer HTML is being served from.
*/
@ -77,7 +63,6 @@ const setupRendererServer = () => {
serveNextAt(rendererURL);
};
setupMainHotReload();
setupRendererServer();
setupLogging(isDev);

View file

@ -28,18 +28,12 @@
*/
import { contextBridge, ipcRenderer } from "electron";
import { existsSync } from "fs";
import { createWriteStream, existsSync } from "node:fs";
import * as fs from "node:fs/promises";
import { Readable } from "node:stream";
import path from "path";
import * as fs from "promise-fs";
import { Readable } from "stream";
import { deleteDiskCache, openDiskCache } from "./api/cache";
import { logToDisk, openLogDirectory } from "./api/common";
import {
checkExistsAndCreateDir,
exists,
saveFileToDisk,
saveStreamToDisk,
} from "./api/export";
import { runFFmpegCmd } from "./api/ffmpeg";
import { getDirFiles } from "./api/fs";
import { convertToJPEG, generateImageThumbnail } from "./api/imageProcessor";
@ -67,8 +61,12 @@ import {
} from "./api/watch";
import { setupLogging } from "./utils/logging";
/* Some of the code below has been duplicated to make this file self contained.
Enhancement: consider alternatives */
/*
Some of the code below has been duplicated to make this file self contained
(see the documentation at the top of why it needs to be a single file).
Enhancement: consider alternatives
*/
/* preload: duplicated logError */
export function logError(error: Error, message: string, info?: string): void {
@ -77,10 +75,27 @@ export function logError(error: Error, message: string, info?: string): void {
// -
export const convertBrowserStreamToNode = (
fileStream: ReadableStream<Uint8Array>,
) => {
const reader = fileStream.getReader();
/* preload: duplicated writeStream */
/**
* Write a (web) ReadableStream to a file at the given {@link filePath}.
*
* The returned promise resolves when the write completes.
*
* @param filePath The local filesystem path where the file should be written.
* @param readableStream A [web
* ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
*/
const writeStream = (filePath: string, readableStream: ReadableStream) =>
writeNodeStream(filePath, convertWebReadableStreamToNode(readableStream));
/**
* Convert a Web ReadableStream into a Node.js ReadableStream
*
* This can be used to, for example, write a ReadableStream obtained via
* `net.fetch` into a file using the Node.js `fs` APIs
*/
const convertWebReadableStreamToNode = (readableStream: ReadableStream) => {
const reader = readableStream.getReader();
const rs = new Readable();
rs._read = async () => {
@ -101,11 +116,11 @@ export const convertBrowserStreamToNode = (
return rs;
};
export async function writeNodeStream(
const writeNodeStream = async (
filePath: string,
fileStream: NodeJS.ReadableStream,
) {
const writeable = fs.createWriteStream(filePath);
) => {
const writeable = createWriteStream(filePath);
fileStream.on("error", (error) => {
writeable.destroy(error); // Close the writable stream with an error
@ -115,23 +130,26 @@ export async function writeNodeStream(
await new Promise((resolve, reject) => {
writeable.on("finish", resolve);
writeable.on("error", async (e) => {
writeable.on("error", async (e: unknown) => {
if (existsSync(filePath)) {
await fs.unlink(filePath);
}
reject(e);
});
});
}
};
/* preload: duplicated writeStream */
export async function writeStream(
filePath: string,
fileStream: ReadableStream<Uint8Array>,
) {
const readable = convertBrowserStreamToNode(fileStream);
await writeNodeStream(filePath, readable);
}
// - Export
const exists = (path: string) => existsSync(path);
const checkExistsAndCreateDir = (dirPath: string) =>
fs.mkdir(dirPath, { recursive: true });
const saveStreamToDisk = writeStream;
const saveFileToDisk = (path: string, contents: string) =>
fs.writeFile(path, contents);
// -
@ -154,9 +172,7 @@ async function moveFile(
}
// check if destination folder exists
const destinationFolder = path.dirname(destinationPath);
if (!existsSync(destinationFolder)) {
await fs.mkdir(destinationFolder, { recursive: true });
}
await fs.mkdir(destinationFolder, { recursive: true });
await fs.rename(sourcePath, destinationPath);
}
@ -183,7 +199,8 @@ async function deleteFolder(folderPath: string): Promise<void> {
if (!existsSync(folderPath)) {
return;
}
if (!fs.statSync(folderPath).isDirectory()) {
const stat = await fs.stat(folderPath);
if (!stat.isDirectory()) {
throw new Error("Path is not a folder");
}
// check if folder is empty
@ -201,17 +218,18 @@ async function rename(oldPath: string, newPath: string) {
await fs.rename(oldPath, newPath);
}
function deleteFile(filePath: string): void {
const deleteFile = async (filePath: string) => {
if (!existsSync(filePath)) {
return;
}
if (!fs.statSync(filePath).isFile()) {
const stat = await fs.stat(filePath);
if (!stat.isFile()) {
throw new Error("Path is not a file");
}
fs.rmSync(filePath);
}
return fs.rm(filePath);
};
// -
// - ML
/* preload: duplicated Model */
export enum Model {
@ -315,7 +333,7 @@ const parseExecError = (err: any) => {
}
};
// -
// - General
const selectDirectory = async (): Promise<string> => {
try {
@ -349,7 +367,7 @@ const clearElectronStore = () => {
ipcRenderer.send("clear-electron-store");
};
// -
// - App update
const updateAndRestart = () => {
ipcRenderer.send("update-and-restart");
@ -410,10 +428,12 @@ setupLogging();
// running out of memory, causing the app to crash as it copies it over across
// the processes.
contextBridge.exposeInMainWorld("ElectronAPIs", {
// - Export
exists,
checkExistsAndCreateDir,
saveStreamToDisk,
saveFileToDisk,
selectDirectory,
clearElectronStore,
readTextFile,
@ -438,20 +458,29 @@ contextBridge.exposeInMainWorld("ElectronAPIs", {
updateWatchMappingIgnoredFiles,
logToDisk,
convertToJPEG,
openLogDirectory,
registerUpdateEventListener,
updateAndRestart,
skipAppUpdate,
getAppVersion,
runFFmpegCmd,
muteUpdateNotification,
generateImageThumbnail,
registerForegroundEventListener,
openDirectory,
moveFile,
deleteFolder,
rename,
deleteFile,
// General
getAppVersion,
openDirectory,
// Logging
openLogDirectory,
// - App update
updateAndRestart,
skipAppUpdate,
muteUpdateNotification,
// - ML
computeImageEmbedding,
computeTextEmbedding,
});

View file

@ -2,10 +2,8 @@ import { compareVersions } from "compare-versions";
import { app, BrowserWindow } from "electron";
import { default as ElectronLog, default as log } from "electron-log";
import { autoUpdater } from "electron-updater";
import fetch from "node-fetch";
import { setIsAppQuitting, setIsUpdateAvailable } from "../main";
import { AppUpdateInfo, GetFeatureFlagResponse } from "../types";
import { isPlatform } from "../utils/common/platform";
import { AppUpdateInfo } from "../types";
import { logErrorSentry } from "./sentry";
import {
clearMuteUpdateNotificationVersion,
@ -64,56 +62,42 @@ async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
);
return;
}
const desktopCutoffVersion = await getDesktopCutoffVersion();
let timeout: NodeJS.Timeout;
log.debug("attempting auto update");
autoUpdater.downloadUpdate();
const muteUpdateNotificationVersion =
getMuteUpdateNotificationVersion();
if (
desktopCutoffVersion &&
isPlatform("mac") &&
compareVersions(
updateCheckResult.updateInfo.version,
desktopCutoffVersion,
) > 0
muteUpdateNotificationVersion &&
updateCheckResult.updateInfo.version ===
muteUpdateNotificationVersion
) {
log.debug("auto update not possible due to key change");
log.info(
"user chose to mute update notification for version ",
updateCheckResult.updateInfo.version,
);
return;
}
autoUpdater.on("update-downloaded", () => {
timeout = setTimeout(
() =>
showUpdateDialog(mainWindow, {
autoUpdatable: true,
version: updateCheckResult.updateInfo.version,
}),
FIVE_MIN_IN_MICROSECOND,
);
});
autoUpdater.on("error", (error) => {
clearTimeout(timeout);
logErrorSentry(error, "auto update failed");
showUpdateDialog(mainWindow, {
autoUpdatable: false,
version: updateCheckResult.updateInfo.version,
});
} else {
let timeout: NodeJS.Timeout;
log.debug("attempting auto update");
autoUpdater.downloadUpdate();
const muteUpdateNotificationVersion =
getMuteUpdateNotificationVersion();
if (
muteUpdateNotificationVersion &&
updateCheckResult.updateInfo.version ===
muteUpdateNotificationVersion
) {
log.info(
"user chose to mute update notification for version ",
updateCheckResult.updateInfo.version,
);
return;
}
autoUpdater.on("update-downloaded", () => {
timeout = setTimeout(
() =>
showUpdateDialog(mainWindow, {
autoUpdatable: true,
version: updateCheckResult.updateInfo.version,
}),
FIVE_MIN_IN_MICROSECOND,
);
});
autoUpdater.on("error", (error) => {
clearTimeout(timeout);
logErrorSentry(error, "auto update failed");
showUpdateDialog(mainWindow, {
autoUpdatable: false,
version: updateCheckResult.updateInfo.version,
});
});
}
});
setIsUpdateAvailable(true);
} catch (e) {
logErrorSentry(e, "checkForUpdateAndNotify failed");
@ -138,18 +122,6 @@ export function muteUpdateNotification(version: string) {
setMuteUpdateNotificationVersion(version);
}
async function getDesktopCutoffVersion() {
try {
const featureFlags = (
await fetch("https://static.ente.io/feature_flags.json")
).json() as GetFeatureFlagResponse;
return featureFlags.desktopCutoffVersion;
} catch (e) {
logErrorSentry(e, "failed to get feature flags");
return undefined;
}
}
function showUpdateDialog(
mainWindow: BrowserWindow,
updateInfo: AppUpdateInfo,

View file

@ -1,17 +1,15 @@
import { app } from "electron";
import * as log from "electron-log";
import { app, net } from "electron/main";
import { existsSync } from "fs";
import fs from "fs/promises";
import fetch from "node-fetch";
import path from "path";
import { readFile } from "promise-fs";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import util from "util";
import { CustomErrors } from "../constants/errors";
import { Model } from "../types";
import Tokenizer from "../utils/clip-bpe-ts/mod";
import { isDev } from "../utils/common";
import { getPlatform } from "../utils/common/platform";
import { writeNodeStream } from "./fs";
import { writeStream } from "./fs";
import { logErrorSentry } from "./sentry";
const shellescape = require("any-shell-escape");
const execAsync = util.promisify(require("child_process").exec);
@ -80,13 +78,11 @@ function getModelSavePath(modelName: string) {
async function downloadModel(saveLocation: string, url: string) {
// confirm that the save location exists
const saveDir = path.dirname(saveLocation);
if (!existsSync(saveDir)) {
log.info("creating model save dir");
await fs.mkdir(saveDir, { recursive: true });
}
await fs.mkdir(saveDir, { recursive: true });
log.info("downloading clip model");
const resp = await fetch(url);
await writeNodeStream(saveLocation, resp.body);
const res = await net.fetch(url);
if (!res.ok) throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`);
await writeStream(saveLocation, res.body);
log.info("clip model downloaded");
}

View file

@ -1,5 +1,6 @@
import path from "path";
import { existsSync, stat, unlink } from "promise-fs";
import { existsSync } from "node:fs";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import DiskLRUService from "../services/diskLRU";
import { LimitedCache } from "../types/cache";
import { getFileStream, writeStream } from "./fs";
@ -28,19 +29,19 @@ export class DiskCache implements LimitedCache {
): Promise<Response> {
const cachePath = path.join(this.cacheBucketDir, cacheKey);
if (existsSync(cachePath)) {
const fileStats = await stat(cachePath);
const fileStats = await fs.stat(cachePath);
if (sizeInBytes && fileStats.size !== sizeInBytes) {
logError(
Error(),
"Cache key exists but size does not match. Deleting cache key.",
);
unlink(cachePath).catch((e) => {
fs.unlink(cachePath).catch((e) => {
if (e.code === "ENOENT") return;
logError(e, "Failed to delete cache key");
});
return undefined;
}
DiskLRUService.touch(cachePath);
DiskLRUService.markUse(cachePath);
return new Response(await getFileStream(cachePath));
} else {
return undefined;
@ -49,7 +50,7 @@ export class DiskCache implements LimitedCache {
async delete(cacheKey: string): Promise<boolean> {
const cachePath = path.join(this.cacheBucketDir, cacheKey);
if (existsSync(cachePath)) {
await unlink(cachePath);
await fs.unlink(cachePath);
return true;
} else {
return false;

View file

@ -1,6 +1,6 @@
import getFolderSize from "get-folder-size";
import path from "path";
import { close, open, readdir, stat, unlink, utimes } from "promise-fs";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { logError } from "../services/logging";
export interface LeastRecentlyUsedResult {
@ -12,19 +12,10 @@ class DiskLRUService {
private isRunning: Promise<any> = null;
private reRun: boolean = false;
async touch(path: string) {
try {
const time = new Date();
await utimes(path, time, time);
} catch (err) {
logError(err, "utimes method touch failed");
try {
await close(await open(path, "w"));
} catch (e) {
logError(e, "open-close method touch failed");
}
// log and ignore
}
/** Mark "use" of a given file by updating its modified time */
async markUse(path: string) {
const now = new Date();
await fs.utimes(path, now, now);
}
enforceCacheSizeLimit(cacheDir: string, maxSize: number) {
@ -53,7 +44,7 @@ class DiskLRUService {
const leastRecentlyUsed =
await this.findLeastRecentlyUsed(cacheDir);
try {
await unlink(leastRecentlyUsed.path);
await fs.unlink(leastRecentlyUsed.path);
} catch (e) {
// ENOENT: File not found
// which can be ignored as we are trying to delete the file anyway
@ -81,16 +72,15 @@ class DiskLRUService {
): Promise<LeastRecentlyUsedResult> {
result = result || { atime: new Date(), path: "" };
const files = await readdir(dir);
const files = await fs.readdir(dir);
for (const file of files) {
const newBase = path.join(dir, file);
const stats = await stat(newBase);
if (stats.isDirectory()) {
const st = await fs.stat(newBase);
if (st.isDirectory()) {
result = await this.findLeastRecentlyUsed(newBase, result);
} else {
const { atime } = await stat(newBase);
if (atime.getTime() < result.atime.getTime()) {
const { atime } = st;
if (st.atime.getTime() < result.atime.getTime()) {
result = {
atime,
path: newBase,

View file

@ -1,17 +1,15 @@
import log from "electron-log";
import pathToFfmpeg from "ffmpeg-static";
import { existsSync } from "fs";
import { readFile, rmSync, writeFile } from "promise-fs";
import { existsSync } from "node:fs";
import * as fs from "node:fs/promises";
import util from "util";
import { promiseWithTimeout } from "../utils/common";
import { CustomErrors } from "../constants/errors";
import { generateTempFilePath, getTempDirPath } from "../utils/temp";
import { logErrorSentry } from "./sentry";
const shellescape = require("any-shell-escape");
const execAsync = util.promisify(require("child_process").exec);
const FFMPEG_EXECUTION_WAIT_TIME = 30 * 1000;
const INPUT_PATH_PLACEHOLDER = "INPUT";
const FFMPEG_PLACEHOLDER = "FFMPEG";
const OUTPUT_PATH_PLACEHOLDER = "OUTPUT";
@ -70,10 +68,7 @@ export async function runFFmpegCmd(
if (dontTimeout) {
await execAsync(escapedCmd);
} else {
await promiseWithTimeout(
execAsync(escapedCmd),
FFMPEG_EXECUTION_WAIT_TIME,
);
await promiseWithTimeout(execAsync(escapedCmd), 30 * 1000);
}
if (!existsSync(tempOutputFilePath)) {
throw new Error("ffmpeg output file not found");
@ -85,14 +80,14 @@ export async function runFFmpegCmd(
"ms",
);
const outputFile = await readFile(tempOutputFilePath);
const outputFile = await fs.readFile(tempOutputFilePath);
return new Uint8Array(outputFile);
} catch (e) {
logErrorSentry(e, "ffmpeg run command error");
throw e;
} finally {
try {
rmSync(tempOutputFilePath, { force: true });
await fs.rm(tempOutputFilePath, { force: true });
} catch (e) {
logErrorSentry(e, "failed to remove tempOutputFile");
}
@ -114,7 +109,7 @@ const ffmpegBinaryPath = () => {
export async function writeTempFile(fileStream: Uint8Array, fileName: string) {
const tempFilePath = await generateTempFilePath(fileName);
await writeFile(tempFilePath, fileStream);
await fs.writeFile(tempFilePath, fileStream);
return tempFilePath;
}
@ -126,5 +121,29 @@ export async function deleteTempFile(tempFilePath: string) {
"tried to delete a non temp file",
);
}
rmSync(tempFilePath, { force: true });
await fs.rm(tempFilePath, { force: true });
}
export const promiseWithTimeout = async <T>(
request: Promise<T>,
timeout: number,
): Promise<T> => {
const timeoutRef: {
current: NodeJS.Timeout;
} = { current: null };
const rejectOnTimeout = new Promise<null>((_, reject) => {
timeoutRef.current = setTimeout(
() => reject(Error(CustomErrors.WAIT_TIME_EXCEEDED)),
timeout,
);
});
const requestWithTimeOutCancellation = async () => {
const resp = await request;
clearTimeout(timeoutRef.current);
return resp;
};
return await Promise.race([
requestWithTimeOutCancellation(),
rejectOnTimeout,
]);
};

View file

@ -1,7 +1,7 @@
import { existsSync } from "fs";
import StreamZip from "node-stream-zip";
import path from "path";
import * as fs from "promise-fs";
import { createWriteStream, existsSync } from "node:fs";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { Readable } from "stream";
import { ElectronFile } from "../types";
import { logError } from "./logging";
@ -212,7 +212,7 @@ export async function writeNodeStream(
filePath: string,
fileStream: NodeJS.ReadableStream,
) {
const writeable = fs.createWriteStream(filePath);
const writeable = createWriteStream(filePath);
fileStream.on("error", (error) => {
writeable.destroy(error); // Close the writable stream with an error
@ -222,7 +222,7 @@ export async function writeNodeStream(
await new Promise((resolve, reject) => {
writeable.on("finish", resolve);
writeable.on("error", async (e) => {
writeable.on("error", async (e: unknown) => {
if (existsSync(filePath)) {
await fs.unlink(filePath);
}

View file

@ -2,9 +2,8 @@ import { exec } from "child_process";
import util from "util";
import log from "electron-log";
import { existsSync, rmSync } from "fs";
import * as fs from "node:fs/promises";
import path from "path";
import { readFile, writeFile } from "promise-fs";
import { CustomErrors } from "../constants/errors";
import { isDev } from "../utils/common";
import { isPlatform } from "../utils/common/platform";
@ -88,28 +87,22 @@ export async function convertToJPEG(
tempInputFilePath = await generateTempFilePath(filename);
tempOutputFilePath = await generateTempFilePath("output.jpeg");
await writeFile(tempInputFilePath, fileData);
await fs.writeFile(tempInputFilePath, fileData);
await runConvertCommand(tempInputFilePath, tempOutputFilePath);
if (!existsSync(tempOutputFilePath)) {
throw new Error("heic convert output file not found");
}
const convertedFileData = new Uint8Array(
await readFile(tempOutputFilePath),
);
return convertedFileData;
return new Uint8Array(await fs.readFile(tempOutputFilePath));
} catch (e) {
logErrorSentry(e, "failed to convert heic");
throw e;
} finally {
try {
rmSync(tempInputFilePath, { force: true });
await fs.rm(tempInputFilePath, { force: true });
} catch (e) {
logErrorSentry(e, "failed to remove tempInputFile");
}
try {
rmSync(tempOutputFilePath, { force: true });
await fs.rm(tempOutputFilePath, { force: true });
} catch (e) {
logErrorSentry(e, "failed to remove tempOutputFile");
}
@ -183,10 +176,7 @@ export async function generateImageThumbnail(
quality,
);
if (!existsSync(tempOutputFilePath)) {
throw new Error("output thumbnail file not found");
}
thumbnail = new Uint8Array(await readFile(tempOutputFilePath));
thumbnail = new Uint8Array(await fs.readFile(tempOutputFilePath));
quality -= 10;
} while (thumbnail.length > maxSize && quality > MIN_QUALITY);
return thumbnail;
@ -195,7 +185,7 @@ export async function generateImageThumbnail(
throw e;
} finally {
try {
rmSync(tempOutputFilePath, { force: true });
await fs.rm(tempOutputFilePath, { force: true });
} catch (e) {
logErrorSentry(e, "failed to remove tempOutputFile");
}

View file

@ -77,10 +77,6 @@ export interface AppUpdateInfo {
version: string;
}
export interface GetFeatureFlagResponse {
desktopCutoffVersion?: string;
}
export enum Model {
GGML_CLIP = "ggml-clip",
ONNX_CLIP = "onnx-clip",

View file

@ -1,27 +1,2 @@
import { app } from "electron";
import { CustomErrors } from "../../constants/errors";
export const isDev = !app.isPackaged;
export const promiseWithTimeout = async <T>(
request: Promise<T>,
timeout: number,
): Promise<T> => {
const timeoutRef: {
current: NodeJS.Timeout;
} = { current: null };
const rejectOnTimeout = new Promise<null>((_, reject) => {
timeoutRef.current = setTimeout(
() => reject(Error(CustomErrors.WAIT_TIME_EXCEEDED)),
timeout,
);
});
const requestWithTimeOutCancellation = async () => {
const resp = await request;
clearTimeout(timeoutRef.current);
return resp;
};
return await Promise.race([
requestWithTimeOutCancellation(),
rejectOnTimeout,
]);
};

View file

@ -1,8 +1,8 @@
import { app, BrowserWindow, Menu, nativeImage, Tray } from "electron";
import ElectronLog from "electron-log";
import { existsSync } from "node:fs";
import os from "os";
import path from "path";
import { existsSync } from "promise-fs";
import util from "util";
import { rendererURL } from "../main";
import { setupAutoUpdater } from "../services/appUpdater";

View file

@ -1,17 +1,14 @@
import { app } from "electron";
import { existsSync } from "node:fs";
import * as fs from "node:fs/promises";
import path from "path";
import { existsSync, mkdir } from "promise-fs";
const ENTE_TEMP_DIRECTORY = "ente";
const CHARACTERS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
export async function getTempDirPath() {
const tempDirPath = path.join(app.getPath("temp"), ENTE_TEMP_DIRECTORY);
if (!existsSync(tempDirPath)) {
await mkdir(tempDirPath);
}
const tempDirPath = path.join(app.getPath("temp"), "ente");
await fs.mkdir(tempDirPath, { recursive: true });
return tempDirPath;
}

View file

@ -205,11 +205,6 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@octetstream/promisify@2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@octetstream/promisify/-/promisify-2.0.2.tgz#29ac3bd7aefba646db670227f895d812c1a19615"
integrity sha512-7XHoRB61hxsz8lBQrjC1tq/3OEIgpvGWg6DKAdwi7WRzruwkmsdwmOoUXbU4Dtd4RSOMDwed0SkP3y8UlMt1Bg==
"@pkgr/core@^0.1.0":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
@ -293,14 +288,6 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
"@types/node-fetch@^2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da"
integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "18.0.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.3.tgz#463fc47f13ec0688a33aec75d078a0541a447199"
@ -329,13 +316,6 @@
"@types/node" "*"
xmlbuilder ">=11.0.1"
"@types/promise-fs@^2.1.1":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@types/promise-fs/-/promise-fs-2.1.2.tgz#7ef6ab00c7fbc68081e34e560d2f008d3dd27fd2"
integrity sha512-s3YON1LmplAUVrvTT2d1I0m2Rk0hSgc/1l5/krnU96YpP4NG9VEN/qopaFv8yk5a2Z+AgYzafS1LCP+kQH0MYw==
dependencies:
"@types/node" "*"
"@types/responselike@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
@ -809,7 +789,7 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.2:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chokidar@^3.5.2, chokidar@^3.5.3:
chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
@ -1182,13 +1162,6 @@ electron-publish@24.5.0:
lazy-val "^1.0.5"
mime "^2.5.2"
electron-reload@^2.0.0-alpha.1:
version "2.0.0-alpha.1"
resolved "https://registry.yarnpkg.com/electron-reload/-/electron-reload-2.0.0-alpha.1.tgz#6cad98df96695ca1d5462dc9407f7c620028ce99"
integrity sha512-hTde7gv0TEqxbxlB3pj2CwoyCQ9sdiQrcP8GkpzhosxyVeYM3mZbMEVKCZK3L0fED7Mz5A9IWmK7zEvi4H3P1g==
dependencies:
chokidar "^3.5.2"
electron-store@^8.0.1:
version "8.0.2"
resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-8.0.2.tgz#95c8cf81c1e1cf48b24f3ceeea24b921c1ff62d7"
@ -1503,15 +1476,6 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2"
integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
@ -2246,13 +2210,6 @@ node-addon-api@^1.6.3:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d"
integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==
node-fetch@^2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"
node-stream-zip@^1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea"
@ -2485,13 +2442,6 @@ progress@^2.0.3:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise-fs@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/promise-fs/-/promise-fs-2.1.1.tgz#0b725a592c165ff16157d1f13640ba390637e557"
integrity sha512-43p7e4QzAQ3w6eyN0+gbBL7jXiZFWLWYITg9wIObqkBySu/a5K1EDcQ/S6UyB/bmiZWDA4NjTbcopKLTaKcGSw==
dependencies:
"@octetstream/promisify" "2.0.2"
promise-retry@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22"
@ -2964,11 +2914,6 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
tree-kill@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
@ -3092,19 +3037,6 @@ verror@^1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
which@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"

View file

@ -1150,7 +1150,7 @@ class ExportService {
videoStream,
);
} catch (e) {
ElectronAPIs.deleteFile(
await ElectronAPIs.deleteFile(
getFileExportPath(collectionExportPath, imageExportName),
);
throw e;

View file

@ -51,10 +51,7 @@ import {
import { FileTypeInfo } from "types/upload";
import { isPlaybackPossible } from "utils/photoFrame";
import {
default as ElectronAPIs,
default as ElectronFSService,
} from "@ente/shared/electron";
import { default as ElectronAPIs } from "@ente/shared/electron";
import { downloadUsingAnchor } from "@ente/shared/utils";
import { t } from "i18next";
import imageProcessor from "services/imageProcessor";
@ -801,7 +798,7 @@ export async function downloadFileDesktop(
videoStream,
);
} catch (e) {
ElectronFSService.deleteFile(
await ElectronAPIs.deleteFile(
getFileExportPath(downloadPath, imageExportName),
);
throw e;

View file

@ -96,7 +96,7 @@ export interface ElectronAPIsType {
openDirectory: (dirPath: string) => Promise<void>;
moveFile: (oldPath: string, newPath: string) => Promise<void>;
deleteFolder: (path: string) => Promise<void>;
deleteFile: (path: string) => void;
deleteFile: (path: string) => Promise<void>;
rename: (oldPath: string, newPath: string) => Promise<void>;
computeImageEmbedding: (
model: Model,