From 42679bc9daa4108681683079f4cb1f2832f4578c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 14:54:12 +0530 Subject: [PATCH 01/18] Remove duplicated icon Per https://www.electron.build/icons, a single 512x512 icon.png placed in the buildResources directory (default `build`) is enough to generate icons for all three - macOS, Windows and Linux. --- desktop/build/icon.icns | Bin 24644 -> 0 bytes desktop/build/window-icon.png | Bin 1068 -> 0 bytes desktop/electron-builder.yml | 1 - desktop/src/main/init.ts | 8 +------- 4 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 desktop/build/icon.icns delete mode 100644 desktop/build/window-icon.png diff --git a/desktop/build/icon.icns b/desktop/build/icon.icns deleted file mode 100644 index ab7eface7a5ffd4f68c208d5a252a6b09e508d88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24644 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 { - const appImgPath = isDev - ? "../build/window-icon.png" - : path.join(process.resourcesPath, "window-icon.png"); - const appIcon = nativeImage.createFromPath(appImgPath); - // Create the main window. This'll show our web content. const mainWindow = new BrowserWindow({ webPreferences: { preload: path.join(app.getAppPath(), "preload.js"), }, - icon: appIcon, // The color to show in the window until the web content gets loaded. // See: https://www.electronjs.org/docs/latest/api/browser-window#setting-the-backgroundcolor-property backgroundColor: "black", - // We'll show conditionally depending on `wasAutoLaunched` later + // We'll show it conditionally depending on `wasAutoLaunched` later. show: false, }); From 846c2af02b6d432894df1ee09e2eb21995e29b38 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 15:10:35 +0530 Subject: [PATCH 02/18] Outline a potential approach --- desktop/src/main/log.ts | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index 014465df1..320ff464a 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -32,3 +32,60 @@ export function logErrorSentry( console.log(error, { msg, info }); } } + +const logError1 = (message: string, e?: unknown) => { + if (e === undefined || e === null) { + log.error(message); + return; + } + + let es: string; + if (e instanceof Error) { + // In practice, we expect ourselves to be called with Error objects, so + // this is the happy path so to say. + es = `${e.name}: ${e.message}\n${e.stack}`; + } else { + // For the rest rare cases, use the default string serialization of e. + es = String(e); + } + + log.error(`${message}: ${es}`); +}; + +const logInfo = (message: string) => { + log.info(message); +}; + +const logDebug = (message: () => string) => { + if (isDev) log.debug(() => message); +}; + +export default { + /** + * Log an error message with an optional associated error object. + * + * {@link e} is generally expected to be an `instanceof Error` but it can be + * any arbitrary object too that we obtain, say, when in a try-catch + * handler. + * + * The log is written to disk, and is also printed to the console. + */ + error: logError1, + /** + * Log a message. + * + * The log is written to disk, and is also printed to the console. + */ + info: logInfo, + /** + * Log a debug message. + * + * The log is not written to disk. And it is printed to the console only on + * development builds. + * + * To avoid running unnecessary code in release builds, this function takes + * a function to call to get the log message instead of directly taking the + * message. This function will only be called in development builds. + */ + debug: logDebug, +}; From 27047da08b57665368266623cd95ae10439cba1c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 15:12:40 +0530 Subject: [PATCH 03/18] Use a truthy check --- desktop/src/main/log.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index 320ff464a..edffd7f34 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -34,7 +34,7 @@ export function logErrorSentry( } const logError1 = (message: string, e?: unknown) => { - if (e === undefined || e === null) { + if (!e) { log.error(message); return; } From c38542dbfb11f1a0a6c74d0c31d2a5e934792d19 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 15:34:29 +0530 Subject: [PATCH 04/18] Init logging --- desktop/src/main.ts | 12 +++++------- desktop/src/main/log.ts | 30 ++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index f5bbce835..e8c290af9 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -14,7 +14,6 @@ import serveNextAt from "next-electron-server"; import { existsSync } from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; -import { isDev } from "./main/general"; import { addAllowOriginHeader, createWindow, @@ -28,7 +27,7 @@ import { setupTrayItem, } from "./main/init"; import { attachFSWatchIPCHandlers, attachIPCHandlers } from "./main/ipc"; -import { logErrorSentry, setupLogging } from "./main/log"; +import { initLogging, logErrorSentry } from "./main/log"; import { initWatcher } from "./services/chokidar"; let appIsQuitting = false; @@ -135,8 +134,6 @@ function setupAppEventEmitter(mainWindow: BrowserWindow) { } const main = () => { - setupLogging(isDev); - const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); @@ -145,6 +142,7 @@ const main = () => { let mainWindow: BrowserWindow; + initLogging(); setupRendererServer(); handleDockIconHideOnAutoLaunch(); increaseDiskCache(); @@ -161,9 +159,9 @@ const main = () => { } }); - // This method will be called when Electron has finished - // initialization and is ready to create browser windows. - // Some APIs can only be used after this event occurs. + // Emitted once, when Electron has finished initializing. + // + // Note that some Electron APIs can only be used after this event occurs. app.on("ready", async () => { logSystemInfo(); mainWindow = await createWindow(); diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index edffd7f34..05f3d59c8 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -1,15 +1,25 @@ import log from "electron-log"; import { isDev } from "./general"; -export function setupLogging(isDev?: boolean) { +/** + * Initialize logging in the main process. + * + * This will set our underlying logger up to log to a file named `ente.log`, + * + * - on Linux at ~/.config/ente/logs/main.log + * - on macOS at ~/Library/Logs/ente/main.log + * - on Windows at %USERPROFILE%\AppData\Roaming\ente\logs\main.log + * + * On dev builds, it will also log to the console. + */ +const initLogging = () => { log.transports.file.fileName = "ente.log"; log.transports.file.maxSize = 50 * 1024 * 1024; // 50MB; - if (!isDev) { - log.transports.console.level = false; - } log.transports.file.format = - "[{y}-{m}-{d}T{h}:{i}:{s}{z}] [{level}]{scope} {text}"; -} + "[{y}-{m}-{d}T{h}:{i}:{s}{z}] [{level}] {text}"; + + if (!isDev) log.transports.console.level = false; +}; export const logToDisk = (message: string) => { log.info(message); @@ -60,6 +70,14 @@ const logDebug = (message: () => string) => { if (isDev) log.debug(() => message); }; +/** + * Ente's logger. + * + * This is an object that provides three functions to log at the corresponding + * levels - error, info or debug. + * + * {@link initLogging} needs to be called once before using any of these. + */ export default { /** * Log an error message with an optional associated error object. From e1c2c9fc9846fe2860df61d5d6f754b1250f3dce Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 15:52:12 +0530 Subject: [PATCH 05/18] Rearrange --- desktop/src/main/log.ts | 48 +++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index 05f3d59c8..323c71e97 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -12,17 +12,22 @@ import { isDev } from "./general"; * * On dev builds, it will also log to the console. */ -const initLogging = () => { +export const initLogging = () => { log.transports.file.fileName = "ente.log"; log.transports.file.maxSize = 50 * 1024 * 1024; // 50MB; - log.transports.file.format = - "[{y}-{m}-{d}T{h}:{i}:{s}{z}] [{level}] {text}"; + log.transports.file.format = "[{y}-{m}-{d}T{h}:{i}:{s}{z}] {text}"; - if (!isDev) log.transports.console.level = false; + log.transports.console.level = false; }; +/** + * Write a {@link message} to the on-disk log. + * + * This is used by the renderer process (via the contextBridge) to add entries + * in the log that is saved on disk. + */ export const logToDisk = (message: string) => { - log.info(message); + log.info(`[rndr] ${message}`); }; export const logError = logErrorSentry; @@ -45,7 +50,7 @@ export function logErrorSentry( const logError1 = (message: string, e?: unknown) => { if (!e) { - log.error(message); + logError_(message); return; } @@ -59,15 +64,21 @@ const logError1 = (message: string, e?: unknown) => { es = String(e); } - log.error(`${message}: ${es}`); + logError_(`${message}: ${es}`); +}; + +const logError_ = (message: string) => { + log.error(`[main] ${message}`); + if (isDev) console.error(message); }; const logInfo = (message: string) => { - log.info(message); + log.info(`[main] ${message}`); + if (isDev) console.log(message); }; const logDebug = (message: () => string) => { - if (isDev) log.debug(() => message); + if (isDev) console.log(message()); }; /** @@ -83,27 +94,28 @@ export default { * Log an error message with an optional associated error object. * * {@link e} is generally expected to be an `instanceof Error` but it can be - * any arbitrary object too that we obtain, say, when in a try-catch - * handler. + * any arbitrary object that we obtain, say, when in a try-catch handler. * - * The log is written to disk, and is also printed to the console. + * The log is written to disk. In development builds, the log is also + * printed to the (Node.js process') console. */ error: logError1, /** * Log a message. * - * The log is written to disk, and is also printed to the console. + * The log is written to disk. In development builds, the log is also + * printed to the (Node.js process') console. */ info: logInfo, /** * Log a debug message. * - * The log is not written to disk. And it is printed to the console only on - * development builds. + * The log is not written to disk. And it is printed to the (Node.js + * process') console only on development builds. * - * To avoid running unnecessary code in release builds, this function takes - * a function to call to get the log message instead of directly taking the - * message. This function will only be called in development builds. + * To avoid running unnecessary code in release builds, this takes a + * function to call to get the log message instead of directly taking the + * message. The provided function will only be called in development builds. */ debug: logDebug, }; From 3e61ebf1df5e41ea203f1b068f2429eb5a0a4d18 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 16:36:31 +0530 Subject: [PATCH 06/18] Add types for shellescape Refs: - https://github.com/boazy/any-shell-escape - https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/shell-escape/index.d.ts - https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html --- desktop/src/main.ts | 5 ++--- desktop/src/main/init.ts | 28 ++++++++++-------------- desktop/src/main/ipc.ts | 2 +- desktop/src/main/log.ts | 2 +- desktop/src/main/{general.ts => util.ts} | 21 ++++++++++++++++++ desktop/src/services/clipService.ts | 2 +- desktop/src/services/imageProcessor.ts | 2 +- desktop/src/types/any-shell-escape.d.ts | 25 +++++++++++++++++++++ desktop/src/utils/menu.ts | 6 ++--- 9 files changed, 66 insertions(+), 27 deletions(-) rename desktop/src/main/{general.ts => util.ts} (64%) create mode 100644 desktop/src/types/any-shell-escape.d.ts diff --git a/desktop/src/main.ts b/desktop/src/main.ts index e8c290af9..eafcc009a 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -8,7 +8,6 @@ * * https://www.electronjs.org/docs/latest/tutorial/process-model#the-main-process */ -import log from "electron-log"; import { app, BrowserWindow } from "electron/main"; import serveNextAt from "next-electron-server"; import { existsSync } from "node:fs"; @@ -27,7 +26,7 @@ import { setupTrayItem, } from "./main/init"; import { attachFSWatchIPCHandlers, attachIPCHandlers } from "./main/ipc"; -import { initLogging, logErrorSentry } from "./main/log"; +import log, { initLogging } from "./main/log"; import { initWatcher } from "./services/chokidar"; let appIsQuitting = false; @@ -182,7 +181,7 @@ const main = () => { } catch (e) { // Log but otherwise ignore errors during non-critical startup // actions - logErrorSentry(e, "Ignoring startup error"); + log.error("Ignoring startup error", e); } }); diff --git a/desktop/src/main/init.ts b/desktop/src/main/init.ts index 0a12da353..af79f39ad 100644 --- a/desktop/src/main/init.ts +++ b/desktop/src/main/init.ts @@ -1,18 +1,15 @@ 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 util from "util"; +import os from "node:os"; +import path from "node:path"; import { isAppQuitting, rendererURL } from "../main"; import { setupAutoUpdater } from "../services/appUpdater"; import autoLauncher from "../services/autoLauncher"; import { getHideDockIconPreference } from "../services/userPreference"; import { isPlatform } from "../utils/common/platform"; import { buildContextMenu, buildMenuBar } from "../utils/menu"; -import { isDev } from "./general"; -import { logErrorSentry } from "./log"; -const execAsync = util.promisify(require("child_process").exec); +import log from "./log"; +import { isDev } from "./util"; /** * Create an return the {@link BrowserWindow} that will form our app's UI. @@ -167,25 +164,22 @@ export async function handleDockIconHideOnAutoLaunch() { } } -export function logSystemInfo() { +export function logStartupBanner() { + const version = isDev ? "dev" : app.getVersion(); + log.info(`hello from ente-photos-desktop ${version}`); + const systemVersion = process.getSystemVersion(); const osName = process.platform; const osRelease = os.release(); - ElectronLog.info({ osName, osRelease, systemVersion }); - const appVersion = app.getVersion(); - ElectronLog.info({ appVersion }); + log.info(`system info ${{ osName, osRelease, systemVersion }}`); } -export async function checkIfInstalledViaBrew() { - if (!isPlatform("mac")) { - return false; - } +async function checkIfInstalledViaBrew() { + if (process.platform != "darwin") return false; try { await execAsync("brew list --cask ente"); - ElectronLog.info("ente installed via brew"); return true; } catch (e) { - ElectronLog.info("ente not installed via brew"); return false; } } diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index beac3b721..be9798186 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -65,8 +65,8 @@ import { saveFileToDisk, saveStreamToDisk, } from "./fs"; -import { openDirectory, openLogDirectory } from "./general"; import { logToDisk } from "./log"; +import { openDirectory, openLogDirectory } from "./util"; /** * Listen for IPC events sent/invoked by the renderer process, and route them to diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index 323c71e97..25e025d82 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -1,5 +1,5 @@ import log from "electron-log"; -import { isDev } from "./general"; +import { isDev } from "./util"; /** * Initialize logging in the main process. diff --git a/desktop/src/main/general.ts b/desktop/src/main/util.ts similarity index 64% rename from desktop/src/main/general.ts rename to desktop/src/main/util.ts index e0b7654fe..4933e5ff4 100644 --- a/desktop/src/main/general.ts +++ b/desktop/src/main/util.ts @@ -1,10 +1,31 @@ +import shellescape from "any-shell-escape"; import { shell } from "electron"; /* TODO(MR): Why is this not in /main? */ import { app } from "electron/main"; +import { exec } from "node:child_process"; import path from "node:path"; +import { promisify } from "node:util"; /** `true` if the app is running in development mode. */ export const isDev = !app.isPackaged; +/** + * Run a shell command asynchronously. + * + * This is a convenience promisified version of child_process.exec. It runs the + * command asynchronously and returns its stdout and stderr if there were no + * errors. + * + * It also shellescapes the command before running it. + * + * Note: This is not a 1-1 replacement of child_process.exec - if you're trying + * to run a trivial shell command, say something that produces a lot of output, + * this might not be the best option and it might be better to use the + * underlying functions. + */ +export const execAsync = (command: string) => execAsync_(shellescape(command)); + +const execAsync_ = promisify(exec); + /** * Open the given {@link dirPath} in the system's folder viewer. * diff --git a/desktop/src/services/clipService.ts b/desktop/src/services/clipService.ts index 049b706b6..697905450 100644 --- a/desktop/src/services/clipService.ts +++ b/desktop/src/services/clipService.ts @@ -6,8 +6,8 @@ import path from "node:path"; import util from "util"; import { CustomErrors } from "../constants/errors"; import { writeStream } from "../main/fs"; -import { isDev } from "../main/general"; import { logErrorSentry } from "../main/log"; +import { isDev } from "../main/util"; import { Model } from "../types/ipc"; import Tokenizer from "../utils/clip-bpe-ts/mod"; import { getPlatform } from "../utils/common/platform"; diff --git a/desktop/src/services/imageProcessor.ts b/desktop/src/services/imageProcessor.ts index ffb86edea..374afe9ad 100644 --- a/desktop/src/services/imageProcessor.ts +++ b/desktop/src/services/imageProcessor.ts @@ -5,8 +5,8 @@ import fs from "node:fs/promises"; import path from "path"; import util from "util"; import { CustomErrors } from "../constants/errors"; +import { isDev } from "../main"; import { writeStream } from "../main/fs"; -import { isDev } from "../main/general"; import { logError, logErrorSentry } from "../main/log"; import { ElectronFile } from "../types/ipc"; import { isPlatform } from "../utils/common/platform"; diff --git a/desktop/src/types/any-shell-escape.d.ts b/desktop/src/types/any-shell-escape.d.ts new file mode 100644 index 000000000..4172cdb1e --- /dev/null +++ b/desktop/src/types/any-shell-escape.d.ts @@ -0,0 +1,25 @@ +/** + * Escape and stringify an array of arguments to be executed on the shell. + * + * @example + * + * const shellescape = require('any-shell-escape'); + * + * const args = ['curl', '-v', '-H', 'Location;', '-H', "User-Agent: FooBar's so-called \"Browser\"", 'http://www.daveeddy.com/?name=dave&age=24']; + * + * const escaped = shellescape(args); + * console.log(escaped); + * + * yields (on POSIX shells): + * + * curl -v -H 'Location;' -H 'User-Agent: FoorBar'"'"'s so-called "Browser"' 'http://www.daveeddy.com/?name=dave&age=24' + * + * or (on Windows): + * + * curl -v -H "Location;" -H "User-Agent: FooBar's so-called ""Browser""" "http://www.daveeddy.com/?name=dave&age=24" +Which is suitable for being executed by the shell. + */ +declare module "any-shell-escape" { + declare const shellescape: (args: readonly string | string[]) => string; + export default shellescape; +} diff --git a/desktop/src/utils/menu.ts b/desktop/src/utils/menu.ts index 941d8ae25..5604cabce 100644 --- a/desktop/src/utils/menu.ts +++ b/desktop/src/utils/menu.ts @@ -6,8 +6,7 @@ import { shell, } from "electron"; import ElectronLog from "electron-log"; -import { setIsAppQuitting } from "../main"; -import { openDirectory, openLogDirectory } from "../main/general"; +import { openDirectory, openLogDirectory, setIsAppQuitting } from "../main"; import { forceCheckForUpdateAndNotify } from "../services/appUpdater"; import autoLauncher from "../services/autoLauncher"; import { @@ -188,7 +187,8 @@ export async function buildMenuBar(mainWindow: BrowserWindow): Promise { submenu: [ { label: "Ente Help", - click: () => shell.openExternal("https://help.ente.io/photos/"), + click: () => + shell.openExternal("https://help.ente.io/photos/"), }, { type: "separator" }, { From 08489c82373f3cd12761798eddd3d5c9985a7fe2 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 16:47:23 +0530 Subject: [PATCH 07/18] Tweak "render-process-gone" handler --- desktop/src/main/init.ts | 13 ++++--------- desktop/src/main/log.ts | 4 ++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/desktop/src/main/init.ts b/desktop/src/main/init.ts index af79f39ad..da2ffddc1 100644 --- a/desktop/src/main/init.ts +++ b/desktop/src/main/init.ts @@ -9,7 +9,7 @@ import { getHideDockIconPreference } from "../services/userPreference"; import { isPlatform } from "../utils/common/platform"; import { buildContextMenu, buildMenuBar } from "../utils/menu"; import log from "./log"; -import { isDev } from "./util"; +import { execAsync, isDev } from "./util"; /** * Create an return the {@link BrowserWindow} that will form our app's UI. @@ -44,19 +44,14 @@ export const createWindow = async () => { // Open the DevTools automatically when running in dev mode if (isDev) mainWindow.webContents.openDevTools(); - mainWindow.webContents.on("render-process-gone", (event, details) => { + mainWindow.webContents.on("render-process-gone", (_, details) => { + log.error(`render-process-gone: ${details}`); mainWindow.webContents.reload(); - logErrorSentry( - Error("render-process-gone"), - "webContents event render-process-gone", - { details }, - ); - ElectronLog.log("webContents event render-process-gone", details); }); mainWindow.webContents.on("unresponsive", () => { + log.error("webContents unresponsive"); mainWindow.webContents.forcefullyCrashRenderer(); - ElectronLog.log("webContents event unresponsive"); }); mainWindow.on("close", function (event) { diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index 25e025d82..e0b496eba 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -50,7 +50,7 @@ export function logErrorSentry( const logError1 = (message: string, e?: unknown) => { if (!e) { - logError_(message); + logError_(`Error: ${message}`); return; } @@ -64,7 +64,7 @@ const logError1 = (message: string, e?: unknown) => { es = String(e); } - logError_(`${message}: ${es}`); + logError_(`Error: ${message}: ${es}`); }; const logError_ = (message: string) => { From 175ea274c4a7ce67aca0bb75faba951db060c8c7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 17:07:56 +0530 Subject: [PATCH 08/18] Alternative formatting --- desktop/src/main/log.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index e0b496eba..2fdcccf93 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -50,7 +50,7 @@ export function logErrorSentry( const logError1 = (message: string, e?: unknown) => { if (!e) { - logError_(`Error: ${message}`); + logError_(message); return; } @@ -64,12 +64,12 @@ const logError1 = (message: string, e?: unknown) => { es = String(e); } - logError_(`Error: ${message}: ${es}`); + logError_(`${message}: ${es}`); }; const logError_ = (message: string) => { - log.error(`[main] ${message}`); - if (isDev) console.error(message); + log.error(`[main] [error] ${message}`); + if (isDev) console.error(`[error] ${message}`); }; const logInfo = (message: string) => { @@ -78,7 +78,7 @@ const logInfo = (message: string) => { }; const logDebug = (message: () => string) => { - if (isDev) console.log(message()); + if (isDev) console.log(`[debug] ${message()}`); }; /** From 8ffe1ece2d64a04137b34374b147d60940c67288 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 17:13:25 +0530 Subject: [PATCH 09/18] Relay isFolder error back to renderer --- desktop/src/main/fs.ts | 55 ++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/desktop/src/main/fs.ts b/desktop/src/main/fs.ts index cf34acb78..0da89fb00 100644 --- a/desktop/src/main/fs.ts +++ b/desktop/src/main/fs.ts @@ -5,7 +5,6 @@ import { createWriteStream, existsSync } from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; import { Readable } from "node:stream"; -import { logError } from "./log"; export const fsExists = (path: string) => existsSync(path); @@ -99,54 +98,36 @@ export const moveFile = async (sourcePath: string, destinationPath: string) => { }; export const isFolder = async (dirPath: string) => { - try { - const stats = await fs.stat(dirPath); - return stats.isDirectory(); - } catch (e) { - let err = e; - // if code is defined, it's an error from fs.stat - if (typeof e.code !== "undefined") { - // ENOENT means the file does not exist - if (e.code === "ENOENT") { - return false; - } - err = Error(`fs error code: ${e.code}`); - } - logError(err, "isFolder failed"); - return false; - } + if (!existsSync(dirPath)) return false; + const stats = await fs.stat(dirPath); + return stats.isDirectory(); }; export const deleteFolder = async (folderPath: string) => { - if (!existsSync(folderPath)) { - return; - } - const stat = await fs.stat(folderPath); - if (!stat.isDirectory()) { - throw new Error("Path is not a folder"); - } - // check if folder is empty + // Ensure it is folder + if (!isFolder(folderPath)) return; + + // Ensure folder is empty const files = await fs.readdir(folderPath); - if (files.length > 0) { - throw new Error("Folder is not empty"); - } + if (files.length > 0) throw new Error("Folder is not empty"); + + // rm -rf it await fs.rmdir(folderPath); }; export const rename = async (oldPath: string, newPath: string) => { - if (!existsSync(oldPath)) { - throw new Error("Path does not exist"); - } + if (!existsSync(oldPath)) throw new Error("Path does not exist"); await fs.rename(oldPath, newPath); }; export const deleteFile = async (filePath: string) => { - if (!existsSync(filePath)) { - return; - } + // Ensure it exists + if (!existsSync(filePath)) return; + + // And is a file const stat = await fs.stat(filePath); - if (!stat.isFile()) { - throw new Error("Path is not a file"); - } + if (!stat.isFile()) throw new Error("Path is not a file"); + + // rm it return fs.rm(filePath); }; From 937e09f6a3574b11ad78b0f34e5e6f768c8fd025 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 17:21:23 +0530 Subject: [PATCH 10/18] Fix import errors --- desktop/src/main.ts | 4 ++-- desktop/src/services/imageProcessor.ts | 2 +- desktop/src/utils/menu.ts | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index eafcc009a..505d5f1cc 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -20,7 +20,7 @@ import { handleDownloads, handleExternalLinks, handleUpdates, - logSystemInfo, + logStartupBanner, setupMacWindowOnDockIconClick, setupMainMenu, setupTrayItem, @@ -162,7 +162,7 @@ const main = () => { // // Note that some Electron APIs can only be used after this event occurs. app.on("ready", async () => { - logSystemInfo(); + logStartupBanner(); mainWindow = await createWindow(); const watcher = initWatcher(mainWindow); setupTrayItem(mainWindow); diff --git a/desktop/src/services/imageProcessor.ts b/desktop/src/services/imageProcessor.ts index 374afe9ad..5da1c204c 100644 --- a/desktop/src/services/imageProcessor.ts +++ b/desktop/src/services/imageProcessor.ts @@ -5,9 +5,9 @@ import fs from "node:fs/promises"; import path from "path"; import util from "util"; import { CustomErrors } from "../constants/errors"; -import { isDev } from "../main"; import { writeStream } from "../main/fs"; import { logError, logErrorSentry } from "../main/log"; +import { isDev } from "../main/util"; import { ElectronFile } from "../types/ipc"; import { isPlatform } from "../utils/common/platform"; import { generateTempFilePath } from "../utils/temp"; diff --git a/desktop/src/utils/menu.ts b/desktop/src/utils/menu.ts index 5604cabce..8f7315da7 100644 --- a/desktop/src/utils/menu.ts +++ b/desktop/src/utils/menu.ts @@ -6,7 +6,8 @@ import { shell, } from "electron"; import ElectronLog from "electron-log"; -import { openDirectory, openLogDirectory, setIsAppQuitting } from "../main"; +import { setIsAppQuitting } from "../main"; +import { openDirectory, openLogDirectory } from "../main/util"; import { forceCheckForUpdateAndNotify } from "../services/appUpdater"; import autoLauncher from "../services/autoLauncher"; import { From cf71d1477b801fc696bd41bfe43c637e9c1a93b5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 17:27:25 +0530 Subject: [PATCH 11/18] update to new functions in more places --- desktop/src/main/util.ts | 13 +++++- desktop/src/services/clipService.ts | 32 +++++---------- desktop/src/services/ffmpeg.ts | 49 ++++++---------------- desktop/src/services/imageProcessor.ts | 56 +++++--------------------- 4 files changed, 44 insertions(+), 106 deletions(-) diff --git a/desktop/src/main/util.ts b/desktop/src/main/util.ts index 4933e5ff4..a06b4580c 100644 --- a/desktop/src/main/util.ts +++ b/desktop/src/main/util.ts @@ -4,6 +4,7 @@ import { app } from "electron/main"; import { exec } from "node:child_process"; import path from "node:path"; import { promisify } from "node:util"; +import log from "./log"; /** `true` if the app is running in development mode. */ export const isDev = !app.isPackaged; @@ -22,7 +23,17 @@ export const isDev = !app.isPackaged; * this might not be the best option and it might be better to use the * underlying functions. */ -export const execAsync = (command: string) => execAsync_(shellescape(command)); +export const execAsync = (command: string | string[]) => { + const escapedCommand = shellescape(command); + const startTime = Date.now(); + log.debug(() => `Running shell command: ${escapedCommand}`); + const result = execAsync_(escapedCommand); + log.debug( + () => + `Completed in ${Math.round(Date.now() - startTime)} ms (${escapedCommand})`, + ); + return result; +}; const execAsync_ = promisify(exec); diff --git a/desktop/src/services/clipService.ts b/desktop/src/services/clipService.ts index 697905450..41e559a9b 100644 --- a/desktop/src/services/clipService.ts +++ b/desktop/src/services/clipService.ts @@ -1,20 +1,16 @@ -import log from "electron-log"; import { app, net } from "electron/main"; import { existsSync } from "fs"; import fs from "node:fs/promises"; import path from "node:path"; -import util from "util"; import { CustomErrors } from "../constants/errors"; import { writeStream } from "../main/fs"; -import { logErrorSentry } from "../main/log"; -import { isDev } from "../main/util"; +import log, { logErrorSentry } from "../main/log"; +import { execAsync, isDev } from "../main/util"; import { Model } from "../types/ipc"; import Tokenizer from "../utils/clip-bpe-ts/mod"; import { getPlatform } from "../utils/common/platform"; import { generateTempFilePath } from "../utils/temp"; import { deleteTempFile } from "./ffmpeg"; -const shellescape = require("any-shell-escape"); -const execAsync = util.promisify(require("child_process").exec); const jpeg = require("jpeg-js"); const CLIP_MODEL_PATH_PLACEHOLDER = "CLIP_MODEL"; @@ -100,8 +96,7 @@ export async function getClipImageModelPath(type: "ggml" | "onnx") { const localFileSize = (await fs.stat(modelSavePath)).size; if (localFileSize !== IMAGE_MODEL_SIZE_IN_BYTES[type]) { log.info( - "clip image model size mismatch, downloading again got:", - localFileSize, + `clip image model size mismatch, downloading again got: ${localFileSize}`, ); imageModelDownloadInProgress = downloadModel( modelSavePath, @@ -139,8 +134,7 @@ export async function getClipTextModelPath(type: "ggml" | "onnx") { const localFileSize = (await fs.stat(modelSavePath)).size; if (localFileSize !== TEXT_MODEL_SIZE_IN_BYTES[type]) { log.info( - "clip text model size mismatch, downloading again got:", - localFileSize, + `clip text model size mismatch, downloading again got: ${localFileSize}`, ); textModelDownloadInProgress = true; downloadModel(modelSavePath, TEXT_MODEL_DOWNLOAD_URL[type]) @@ -278,11 +272,7 @@ export async function computeGGMLImageEmbedding( } }); - const escapedCmd = shellescape(cmd); - log.info("running clip command", escapedCmd); - const startTime = Date.now(); - const { stdout } = await execAsync(escapedCmd); - log.info("clip command execution time ", Date.now() - startTime); + const { stdout } = await execAsync(cmd); // parse stdout and return embedding // get the last line of stdout const lines = stdout.split("\n"); @@ -291,7 +281,7 @@ export async function computeGGMLImageEmbedding( const embeddingArray = new Float32Array(embedding); return embeddingArray; } catch (err) { - logErrorSentry(err, "Error in computeGGMLImageEmbedding"); + log.error("Failed to compute GGML image embedding", err); throw err; } } @@ -316,7 +306,7 @@ export async function computeONNXImageEmbedding( const imageEmbedding = results["output"].data; // Float32Array return normalizeEmbedding(imageEmbedding); } catch (err) { - logErrorSentry(err, "Error in computeONNXImageEmbedding"); + log.error("Failed to compute ONNX image embedding", err); throw err; } } @@ -367,11 +357,7 @@ export async function computeGGMLTextEmbedding( } }); - const escapedCmd = shellescape(cmd); - log.info("running clip command", escapedCmd); - const startTime = Date.now(); - const { stdout } = await execAsync(escapedCmd); - log.info("clip command execution time ", Date.now() - startTime); + const { stdout } = await execAsync(cmd); // parse stdout and return embedding // get the last line of stdout const lines = stdout.split("\n"); @@ -383,7 +369,7 @@ export async function computeGGMLTextEmbedding( if (err.message === CustomErrors.MODEL_DOWNLOAD_PENDING) { log.info(CustomErrors.MODEL_DOWNLOAD_PENDING); } else { - logErrorSentry(err, "Error in computeGGMLTextEmbedding"); + log.error("Failed to compute GGML text embedding", err); } throw err; } diff --git a/desktop/src/services/ffmpeg.ts b/desktop/src/services/ffmpeg.ts index b6ac4dbda..ddb3361cf 100644 --- a/desktop/src/services/ffmpeg.ts +++ b/desktop/src/services/ffmpeg.ts @@ -1,18 +1,13 @@ -import log from "electron-log"; import pathToFfmpeg from "ffmpeg-static"; import { existsSync } from "node:fs"; import fs from "node:fs/promises"; -import util from "util"; import { CustomErrors } from "../constants/errors"; import { writeStream } from "../main/fs"; -import { logError, logErrorSentry } from "../main/log"; +import log from "../main/log"; +import { execAsync } from "../main/util"; import { ElectronFile } from "../types/ipc"; import { generateTempFilePath, getTempDirPath } from "../utils/temp"; -const shellescape = require("any-shell-escape"); - -const execAsync = util.promisify(require("child_process").exec); - const INPUT_PATH_PLACEHOLDER = "INPUT"; const FFMPEG_PLACEHOLDER = "FFMPEG"; const OUTPUT_PATH_PLACEHOLDER = "OUTPUT"; @@ -70,11 +65,7 @@ export async function runFFmpegCmd( return new File([outputFileData], outputFileName); } finally { if (createdTempInputFile) { - try { - await deleteTempFile(inputFilePath); - } catch (e) { - logError(e, "failed to deleteTempFile"); - } + await deleteTempFile(inputFilePath); } } } @@ -100,35 +91,23 @@ export async function runFFmpegCmd_( return cmdPart; } }); - const escapedCmd = shellescape(cmd); - log.info("running ffmpeg command", escapedCmd); - const startTime = Date.now(); + if (dontTimeout) { - await execAsync(escapedCmd); + await execAsync(cmd); } else { - await promiseWithTimeout(execAsync(escapedCmd), 30 * 1000); + await promiseWithTimeout(execAsync(cmd), 30 * 1000); } + if (!existsSync(tempOutputFilePath)) { throw new Error("ffmpeg output file not found"); } - log.info( - "ffmpeg command execution time ", - escapedCmd, - Date.now() - startTime, - "ms", - ); - const outputFile = await fs.readFile(tempOutputFilePath); return new Uint8Array(outputFile); } catch (e) { - logErrorSentry(e, "ffmpeg run command error"); + log.error("FFMPEG command failed", e); throw e; } finally { - try { - await fs.rm(tempOutputFilePath, { force: true }); - } catch (e) { - logErrorSentry(e, "failed to remove tempOutputFile"); - } + await deleteTempFile(tempOutputFilePath); } } @@ -153,16 +132,12 @@ export async function writeTempFile(fileStream: Uint8Array, fileName: string) { export async function deleteTempFile(tempFilePath: string) { const tempDirPath = await getTempDirPath(); - if (!tempFilePath.startsWith(tempDirPath)) { - logErrorSentry( - Error("not a temp file"), - "tried to delete a non temp file", - ); - } + if (!tempFilePath.startsWith(tempDirPath)) + log.error("Attempting to delete a non-temp file ${tempFilePath}"); await fs.rm(tempFilePath, { force: true }); } -export const promiseWithTimeout = async ( +const promiseWithTimeout = async ( request: Promise, timeout: number, ): Promise => { diff --git a/desktop/src/services/imageProcessor.ts b/desktop/src/services/imageProcessor.ts index 5da1c204c..f6a567f8c 100644 --- a/desktop/src/services/imageProcessor.ts +++ b/desktop/src/services/imageProcessor.ts @@ -1,22 +1,15 @@ -import { exec } from "child_process"; -import log from "electron-log"; import { existsSync } from "fs"; import fs from "node:fs/promises"; import path from "path"; -import util from "util"; import { CustomErrors } from "../constants/errors"; import { writeStream } from "../main/fs"; import { logError, logErrorSentry } from "../main/log"; -import { isDev } from "../main/util"; +import { execAsync, isDev } from "../main/util"; import { ElectronFile } from "../types/ipc"; import { isPlatform } from "../utils/common/platform"; import { generateTempFilePath } from "../utils/temp"; import { deleteTempFile } from "./ffmpeg"; -const shellescape = require("any-shell-escape"); - -const asyncExec = util.promisify(exec); - const IMAGE_MAGICK_PLACEHOLDER = "IMAGE_MAGICK"; const MAX_DIMENSION_PLACEHOLDER = "MAX_DIMENSION"; const SAMPLE_SIZE_PLACEHOLDER = "SAMPLE_SIZE"; @@ -104,7 +97,9 @@ async function convertToJPEG_( await fs.writeFile(tempInputFilePath, fileData); - await runConvertCommand(tempInputFilePath, tempOutputFilePath); + await execAsync( + constructConvertCommand(tempInputFilePath, tempOutputFilePath), + ); return new Uint8Array(await fs.readFile(tempOutputFilePath)); } catch (e) { @@ -124,19 +119,6 @@ async function convertToJPEG_( } } -async function runConvertCommand( - tempInputFilePath: string, - tempOutputFilePath: string, -) { - const convertCmd = constructConvertCommand( - tempInputFilePath, - tempOutputFilePath, - ); - const escapedCmd = shellescape(convertCmd); - log.info("running convert command: " + escapedCmd); - await asyncExec(escapedCmd); -} - function constructConvertCommand( tempInputFilePath: string, tempOutputFilePath: string, @@ -222,13 +204,14 @@ async function generateImageThumbnail_( tempOutputFilePath = await generateTempFilePath("thumb.jpeg"); let thumbnail: Uint8Array; do { - await runThumbnailGenerationCommand( - inputFilePath, - tempOutputFilePath, - width, - quality, + await execAsync( + constructThumbnailGenerationCommand( + inputFilePath, + tempOutputFilePath, + width, + quality, + ), ); - thumbnail = new Uint8Array(await fs.readFile(tempOutputFilePath)); quality -= 10; } while (thumbnail.length > maxSize && quality > MIN_QUALITY); @@ -245,23 +228,6 @@ async function generateImageThumbnail_( } } -async function runThumbnailGenerationCommand( - inputFilePath: string, - tempOutputFilePath: string, - maxDimension: number, - quality: number, -) { - const thumbnailGenerationCmd: string[] = - constructThumbnailGenerationCommand( - inputFilePath, - tempOutputFilePath, - maxDimension, - quality, - ); - const escapedCmd = shellescape(thumbnailGenerationCmd); - log.info("running thumbnail generation command: " + escapedCmd); - await asyncExec(escapedCmd); -} function constructThumbnailGenerationCommand( inputFilePath: string, tempOutputFilePath: string, From 3699118f0c7c1124dac8ad93f705840e8a3f9606 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 20:10:45 +0530 Subject: [PATCH 12/18] Don't shellescape fully formed commands --- desktop/src/main/util.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/desktop/src/main/util.ts b/desktop/src/main/util.ts index a06b4580c..d0c6699e9 100644 --- a/desktop/src/main/util.ts +++ b/desktop/src/main/util.ts @@ -16,15 +16,22 @@ export const isDev = !app.isPackaged; * command asynchronously and returns its stdout and stderr if there were no * errors. * - * It also shellescapes the command before running it. + * If the command is passed as a string, then it will be executed verbatim. * - * Note: This is not a 1-1 replacement of child_process.exec - if you're trying - * to run a trivial shell command, say something that produces a lot of output, - * this might not be the best option and it might be better to use the - * underlying functions. + * If the command is passed as an array, then the first argument will be treated + * as the executable and the remaining (optional) items as the command line + * parameters. This function will shellescape and join the array to form the + * command that finally gets executed. + * + * > Note: This is not a 1-1 replacement of child_process.exec - if you're + * > trying to run a trivial shell command, say something that produces a lot of + * > output, this might not be the best option and it might be better to use the + * > underlying functions. */ export const execAsync = (command: string | string[]) => { - const escapedCommand = shellescape(command); + const escapedCommand = Array.isArray(command) + ? shellescape(command) + : command; const startTime = Date.now(); log.debug(() => `Running shell command: ${escapedCommand}`); const result = execAsync_(escapedCommand); From 5ac4799ce1122ced222147cf022aa5026a368042 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 20:21:27 +0530 Subject: [PATCH 13/18] Support arbitrary parameters like console.log --- desktop/src/main/init.ts | 9 +++++---- desktop/src/main/log.ts | 22 ++++++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/desktop/src/main/init.ts b/desktop/src/main/init.ts index da2ffddc1..df4363bac 100644 --- a/desktop/src/main/init.ts +++ b/desktop/src/main/init.ts @@ -161,12 +161,13 @@ export async function handleDockIconHideOnAutoLaunch() { export function logStartupBanner() { const version = isDev ? "dev" : app.getVersion(); - log.info(`hello from ente-photos-desktop ${version}`); + log.info(`Hello from ente-photos-desktop ${version}`); - const systemVersion = process.getSystemVersion(); - const osName = process.platform; + const platform = process.platform; const osRelease = os.release(); - log.info(`system info ${{ osName, osRelease, systemVersion }}`); + const systemVersion = process.getSystemVersion(); + log.info("Running on", { platform, osRelease, systemVersion }); + log.debug(() => ({ platform, osRelease, systemVersion })); } async function checkIfInstalledViaBrew() { diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index 2fdcccf93..8787a530d 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -1,4 +1,5 @@ import log from "electron-log"; +import util from "node:util"; import { isDev } from "./util"; /** @@ -72,13 +73,16 @@ const logError_ = (message: string) => { if (isDev) console.error(`[error] ${message}`); }; -const logInfo = (message: string) => { +const logInfo = (...params: any[]) => { + const message = params + .map((p) => (typeof p == "string" ? p : util.inspect(p))) + .join(" "); log.info(`[main] ${message}`); if (isDev) console.log(message); }; -const logDebug = (message: () => string) => { - if (isDev) console.log(`[debug] ${message()}`); +const logDebug = (param: () => any) => { + if (isDev) console.log(`[debug] ${util.inspect(param())}`); }; /** @@ -103,6 +107,9 @@ export default { /** * Log a message. * + * This is meant as a replacement of {@link console.log}, and takes an + * arbitrary number of arbitrary parameters that it then serializes. + * * The log is written to disk. In development builds, the log is also * printed to the (Node.js process') console. */ @@ -110,12 +117,15 @@ export default { /** * Log a debug message. * - * The log is not written to disk. And it is printed to the (Node.js - * process') console only on development builds. - * * To avoid running unnecessary code in release builds, this takes a * function to call to get the log message instead of directly taking the * message. The provided function will only be called in development builds. + * + * The function can return an arbitrary value which is serialied before + * being logged. + * + * This log is not written to disk. It is printed to the (Node.js process') + * console only on development builds. */ debug: logDebug, }; From 95eec1f3d63ef06f23117492dcd352463bd78419 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 20:29:11 +0530 Subject: [PATCH 14/18] Remove brew special casing Brew Formulae support an `auto_updates true` flag which tells brew's auto update mechanism to stay out of the way. Ref: https://docs.brew.sh/FAQ#why-arent-some-apps-included-during-brew-upgrade Will need to open a PR to update our Formula though. https://github.com/Homebrew/homebrew-cask/blob/9241d331b69abd5af47332488ba1857075cb99fd/Casks/e/ente.rb#L9 --- desktop/src/main.ts | 5 +++-- desktop/src/main/init.ts | 16 +--------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 505d5f1cc..dc27c7435 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -19,7 +19,6 @@ import { handleDockIconHideOnAutoLaunch, handleDownloads, handleExternalLinks, - handleUpdates, logStartupBanner, setupMacWindowOnDockIconClick, setupMainMenu, @@ -27,6 +26,8 @@ import { } from "./main/init"; import { attachFSWatchIPCHandlers, attachIPCHandlers } from "./main/ipc"; import log, { initLogging } from "./main/log"; +import { isDev } from "./main/util"; +import { setupAutoUpdater } from "./services/appUpdater"; import { initWatcher } from "./services/chokidar"; let appIsQuitting = false; @@ -170,7 +171,7 @@ const main = () => { setupMainMenu(mainWindow); attachIPCHandlers(); attachFSWatchIPCHandlers(watcher); - await handleUpdates(mainWindow); + if (!isDev) setupAutoUpdater(mainWindow); handleDownloads(mainWindow); handleExternalLinks(mainWindow); addAllowOriginHeader(mainWindow); diff --git a/desktop/src/main/init.ts b/desktop/src/main/init.ts index df4363bac..270609bf7 100644 --- a/desktop/src/main/init.ts +++ b/desktop/src/main/init.ts @@ -9,7 +9,7 @@ import { getHideDockIconPreference } from "../services/userPreference"; import { isPlatform } from "../utils/common/platform"; import { buildContextMenu, buildMenuBar } from "../utils/menu"; import log from "./log"; -import { execAsync, isDev } from "./util"; +import { isDev } from "./util"; /** * Create an return the {@link BrowserWindow} that will form our app's UI. @@ -79,10 +79,6 @@ export const createWindow = async () => { }; export async function handleUpdates(mainWindow: BrowserWindow) { - const isInstalledViaBrew = await checkIfInstalledViaBrew(); - if (!isDev && !isInstalledViaBrew) { - setupAutoUpdater(mainWindow); - } } export const setupTrayItem = (mainWindow: BrowserWindow) => { @@ -170,16 +166,6 @@ export function logStartupBanner() { log.debug(() => ({ platform, osRelease, systemVersion })); } -async function checkIfInstalledViaBrew() { - if (process.platform != "darwin") return false; - try { - await execAsync("brew list --cask ente"); - return true; - } catch (e) { - return false; - } -} - function lowerCaseHeaders(responseHeaders: Record) { const headers: Record = {}; for (const key of Object.keys(responseHeaders)) { From 0dda25800ea45cbd2e568636472d54895f985a3e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 20:34:10 +0530 Subject: [PATCH 15/18] Update the node version number in tsconfig doc comments --- desktop/tsconfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/desktop/tsconfig.json b/desktop/tsconfig.json index 0f6ad6052..700ea3fa0 100644 --- a/desktop/tsconfig.json +++ b/desktop/tsconfig.json @@ -9,12 +9,12 @@ /* Recommended target, lib and other settings for code running in the version of Node.js bundled with Electron. - Currently, with Electron 25, this is Node.js 18 - https://www.electronjs.org/blog/electron-25-0 + Currently, with Electron 29, this is Node.js 20.9 + https://www.electronjs.org/blog/electron-29-0 Note that we cannot do - "extends": "@tsconfig/node18/tsconfig.json", + "extends": "@tsconfig/node20/tsconfig.json", because that sets "lib": ["es2023"]. However (and I don't fully understand what's going on here), that breaks our compilation since From c9f8ad3e88b9aa6a7d69647aed1465231ee866b4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 21:04:41 +0530 Subject: [PATCH 16/18] Tidy and prune the app's menu - Switch to title case (discussed with others that the app's main menu is an appropriate choice and exception to our sentence casing otherwise). - Prune --- desktop/src/main.ts | 6 +- desktop/src/main/init.ts | 14 +-- desktop/src/main/menu.ts | 222 ++++++++++++++++++++++++++++++++++++++ desktop/src/utils/menu.ts | 216 ------------------------------------- 4 files changed, 229 insertions(+), 229 deletions(-) create mode 100644 desktop/src/main/menu.ts delete mode 100644 desktop/src/utils/menu.ts diff --git a/desktop/src/main.ts b/desktop/src/main.ts index dc27c7435..4383fa73f 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -8,7 +8,7 @@ * * https://www.electronjs.org/docs/latest/tutorial/process-model#the-main-process */ -import { app, BrowserWindow } from "electron/main"; +import { app, BrowserWindow, Menu } from "electron/main"; import serveNextAt from "next-electron-server"; import { existsSync } from "node:fs"; import fs from "node:fs/promises"; @@ -21,11 +21,11 @@ import { handleExternalLinks, logStartupBanner, setupMacWindowOnDockIconClick, - setupMainMenu, setupTrayItem, } from "./main/init"; import { attachFSWatchIPCHandlers, attachIPCHandlers } from "./main/ipc"; import log, { initLogging } from "./main/log"; +import { createApplicationMenu } from "./main/menu"; import { isDev } from "./main/util"; import { setupAutoUpdater } from "./services/appUpdater"; import { initWatcher } from "./services/chokidar"; @@ -168,7 +168,7 @@ const main = () => { const watcher = initWatcher(mainWindow); setupTrayItem(mainWindow); setupMacWindowOnDockIconClick(); - setupMainMenu(mainWindow); + Menu.setApplicationMenu(await createApplicationMenu(mainWindow)); attachIPCHandlers(); attachFSWatchIPCHandlers(watcher); if (!isDev) setupAutoUpdater(mainWindow); diff --git a/desktop/src/main/init.ts b/desktop/src/main/init.ts index 270609bf7..6e463e4c4 100644 --- a/desktop/src/main/init.ts +++ b/desktop/src/main/init.ts @@ -1,14 +1,13 @@ -import { app, BrowserWindow, Menu, nativeImage, Tray } from "electron"; +import { app, BrowserWindow, nativeImage, Tray } from "electron"; import { existsSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import { isAppQuitting, rendererURL } from "../main"; -import { setupAutoUpdater } from "../services/appUpdater"; import autoLauncher from "../services/autoLauncher"; import { getHideDockIconPreference } from "../services/userPreference"; import { isPlatform } from "../utils/common/platform"; -import { buildContextMenu, buildMenuBar } from "../utils/menu"; import log from "./log"; +import { createTrayContextMenu } from "./menu"; import { isDev } from "./util"; /** @@ -78,8 +77,7 @@ export const createWindow = async () => { return mainWindow; }; -export async function handleUpdates(mainWindow: BrowserWindow) { -} +export async function handleUpdates(mainWindow: BrowserWindow) {} export const setupTrayItem = (mainWindow: BrowserWindow) => { const iconName = isPlatform("mac") @@ -92,7 +90,7 @@ export const setupTrayItem = (mainWindow: BrowserWindow) => { const trayIcon = nativeImage.createFromPath(trayImgPath); const tray = new Tray(trayIcon); tray.setToolTip("ente"); - tray.setContextMenu(buildContextMenu(mainWindow)); + tray.setContextMenu(createTrayContextMenu(mainWindow)); }; export function handleDownloads(mainWindow: BrowserWindow) { @@ -142,10 +140,6 @@ export function setupMacWindowOnDockIconClick() { }); } -export async function setupMainMenu(mainWindow: BrowserWindow) { - Menu.setApplicationMenu(await buildMenuBar(mainWindow)); -} - export async function handleDockIconHideOnAutoLaunch() { const shouldHideDockIcon = getHideDockIconPreference(); const wasAutoLaunched = await autoLauncher.wasAutoLaunched(); diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts new file mode 100644 index 000000000..ddaacf32d --- /dev/null +++ b/desktop/src/main/menu.ts @@ -0,0 +1,222 @@ +import { + app, + BrowserWindow, + Menu, + MenuItemConstructorOptions, + shell, +} from "electron"; +import { setIsAppQuitting } from "../main"; +import { forceCheckForUpdateAndNotify } from "../services/appUpdater"; +import autoLauncher from "../services/autoLauncher"; +import { + getHideDockIconPreference, + setHideDockIconPreference, +} from "../services/userPreference"; +import { openLogDirectory } from "./util"; + +/** Create and return the entries in the app's main menu bar */ +export const createApplicationMenu = async (mainWindow: BrowserWindow) => { + const isMac = process.platform == "darwin"; + + // The state of checkboxes + // + // Whenever the menu is redrawn the current value of these variables is used + // to set the checked state for the various settings checkboxes. + let isAutoLaunchEnabled = await autoLauncher.isEnabled(); + let shouldHideDockIcon = getHideDockIconPreference(); + + const handleCheckForUpdates = () => + forceCheckForUpdateAndNotify(mainWindow); + + const handleViewChangelog = () => + shell.openExternal( + "https://github.com/ente-io/ente/blob/main/desktop/CHANGELOG.md", + ); + + const toggleAutoLaunch = () => { + autoLauncher.toggleAutoLaunch(); + isAutoLaunchEnabled = !isAutoLaunchEnabled; + }; + + const toggleHideDockIcon = () => { + setHideDockIconPreference(!shouldHideDockIcon); + shouldHideDockIcon = !shouldHideDockIcon; + }; + + const handleHelp = () => shell.openExternal("https://help.ente.io/photos/"); + + const handleSupport = () => shell.openExternal("mailto:support@ente.io"); + + const handleBlog = () => shell.openExternal("https://ente.io/blog/"); + + const handleViewLogs = openLogDirectory; + + return Menu.buildFromTemplate([ + { + label: "ente", + submenu: [ + ...((isMac + ? [ + { + label: "About Ente", + role: "about", + }, + ] + : []) as MenuItemConstructorOptions[]), + { type: "separator" }, + { + label: "Check for Updates...", + click: handleCheckForUpdates, + }, + { + label: "View Changelog", + click: handleViewChangelog, + }, + { type: "separator" }, + + { + label: "Preferences", + submenu: [ + { + label: "Open Ente on Startup", + type: "checkbox", + checked: isAutoLaunchEnabled, + click: toggleAutoLaunch, + }, + { + label: "Hide Dock Icon", + type: "checkbox", + checked: shouldHideDockIcon, + click: toggleHideDockIcon, + }, + ], + }, + + { type: "separator" }, + ...((isMac + ? [ + { + label: "Hide Ente", + role: "hide", + }, + { + label: "Hide Others", + role: "hideOthers", + }, + ] + : []) as MenuItemConstructorOptions[]), + + { type: "separator" }, + { + label: "Quit", + role: "quit", + }, + ], + }, + { + label: "Edit", + submenu: [ + { label: "Undo", role: "undo" }, + { label: "Redo", role: "redo" }, + { type: "separator" }, + { label: "Cut", role: "cut" }, + { label: "Copy", role: "copy" }, + { label: "Paste", role: "paste" }, + { label: "Select All", role: "selectAll" }, + ...((isMac + ? [ + { type: "separator" }, + { + label: "Speech", + submenu: [ + { + role: "startSpeaking", + label: "start speaking", + }, + { + role: "stopSpeaking", + label: "stop speaking", + }, + ], + }, + ] + : []) as MenuItemConstructorOptions[]), + ], + }, + { + label: "View", + submenu: [ + { label: "Reload", role: "reload" }, + { label: "Toggle Dev Tools", role: "toggleDevTools" }, + { type: "separator" }, + { label: "Toggle Full Screen", role: "togglefullscreen" }, + ], + }, + { + label: "Window", + submenu: [ + { label: "Minimize", role: "minimize" }, + { label: "Zoom", role: "zoom" }, + { label: "Close", role: "close" }, + ...((isMac + ? [ + { type: "separator" }, + { label: "Bring All to Front", role: "front" }, + { type: "separator" }, + { label: "Ente", role: "window" }, + ] + : []) as MenuItemConstructorOptions[]), + ], + }, + { + label: "Help", + submenu: [ + { + label: "Ente Help", + click: handleHelp, + }, + { type: "separator" }, + { + label: "Support", + click: handleSupport, + }, + { + label: "Product Updates", + click: handleBlog, + }, + { type: "separator" }, + { + label: "View Logs", + click: handleViewLogs, + }, + ], + }, + ]); +}; + +/** + * Create and return a {@link Menu} that is shown when the user clicks on our + * system tray icon (e.g. the icon list at the top right of the screen on macOS) + */ +export const createTrayContextMenu = (mainWindow: BrowserWindow) => { + const handleOpen = () => { + mainWindow.maximize(); + mainWindow.show(); + }; + + const handleClose = () => { + setIsAppQuitting(true); + app.quit(); + }; + + return Menu.buildFromTemplate([ + { + label: "Open Ente", + click: handleOpen, + }, + { + label: "Quit Ente", + click: handleClose, + }, + ]); +}; diff --git a/desktop/src/utils/menu.ts b/desktop/src/utils/menu.ts deleted file mode 100644 index 8f7315da7..000000000 --- a/desktop/src/utils/menu.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { - app, - BrowserWindow, - Menu, - MenuItemConstructorOptions, - shell, -} from "electron"; -import ElectronLog from "electron-log"; -import { setIsAppQuitting } from "../main"; -import { openDirectory, openLogDirectory } from "../main/util"; -import { forceCheckForUpdateAndNotify } from "../services/appUpdater"; -import autoLauncher from "../services/autoLauncher"; -import { - getHideDockIconPreference, - setHideDockIconPreference, -} from "../services/userPreference"; -import { isPlatform } from "./common/platform"; - -export function buildContextMenu(mainWindow: BrowserWindow): Menu { - // eslint-disable-next-line camelcase - const contextMenu = Menu.buildFromTemplate([ - { - label: "Open ente", - click: function () { - mainWindow.maximize(); - mainWindow.show(); - }, - }, - { - label: "Quit ente", - click: function () { - ElectronLog.log("user quit the app"); - setIsAppQuitting(true); - app.quit(); - }, - }, - ]); - return contextMenu; -} - -export async function buildMenuBar(mainWindow: BrowserWindow): Promise { - let isAutoLaunchEnabled = await autoLauncher.isEnabled(); - const isMac = isPlatform("mac"); - let shouldHideDockIcon = getHideDockIconPreference(); - const template: MenuItemConstructorOptions[] = [ - { - label: "ente", - submenu: [ - ...((isMac - ? [ - { - label: "About ente", - role: "about", - }, - ] - : []) as MenuItemConstructorOptions[]), - { type: "separator" }, - { - label: "Check for updates...", - click: () => { - forceCheckForUpdateAndNotify(mainWindow); - }, - }, - { - label: "View Changelog", - click: () => { - shell.openExternal( - "https://github.com/ente-io/ente/blob/main/desktop/CHANGELOG.md", - ); - }, - }, - { type: "separator" }, - - { - label: "Preferences", - submenu: [ - { - label: "Open ente on startup", - type: "checkbox", - checked: isAutoLaunchEnabled, - click: () => { - autoLauncher.toggleAutoLaunch(); - isAutoLaunchEnabled = !isAutoLaunchEnabled; - }, - }, - { - label: "Hide dock icon", - type: "checkbox", - checked: shouldHideDockIcon, - click: () => { - setHideDockIconPreference(!shouldHideDockIcon); - shouldHideDockIcon = !shouldHideDockIcon; - }, - }, - ], - }, - - { type: "separator" }, - ...((isMac - ? [ - { - label: "Hide ente", - role: "hide", - }, - { - label: "Hide others", - role: "hideOthers", - }, - ] - : []) as MenuItemConstructorOptions[]), - - { type: "separator" }, - { - label: "Quit ente", - role: "quit", - }, - ], - }, - { - label: "Edit", - submenu: [ - { role: "undo", label: "Undo" }, - { role: "redo", label: "Redo" }, - { type: "separator" }, - { role: "cut", label: "Cut" }, - { role: "copy", label: "Copy" }, - { role: "paste", label: "Paste" }, - ...((isMac - ? [ - { - role: "pasteAndMatchStyle", - label: "Paste and match style", - }, - { role: "delete", label: "Delete" }, - { role: "selectAll", label: "Select all" }, - { type: "separator" }, - { - label: "Speech", - submenu: [ - { - role: "startSpeaking", - label: "start speaking", - }, - { - role: "stopSpeaking", - label: "stop speaking", - }, - ], - }, - ] - : [ - { type: "separator" }, - { role: "selectAll", label: "Select all" }, - ]) as MenuItemConstructorOptions[]), - ], - }, - { - label: "View", - submenu: [ - { role: "reload", label: "Reload" }, - { role: "forceReload", label: "Force reload" }, - { role: "toggleDevTools", label: "Toggle dev tools" }, - { type: "separator" }, - { role: "resetZoom", label: "Reset zoom" }, - { role: "zoomIn", label: "Zoom in" }, - { role: "zoomOut", label: "Zoom out" }, - { type: "separator" }, - { role: "togglefullscreen", label: "Toggle fullscreen" }, - ], - }, - { - label: "Window", - submenu: [ - { role: "close", label: "Close" }, - { role: "minimize", label: "Minimize" }, - ...((isMac - ? [ - { type: "separator" }, - { role: "front", label: "Bring to front" }, - { type: "separator" }, - { role: "window", label: "ente" }, - ] - : []) as MenuItemConstructorOptions[]), - ], - }, - { - label: "Help", - submenu: [ - { - label: "Ente Help", - click: () => - shell.openExternal("https://help.ente.io/photos/"), - }, - { type: "separator" }, - { - label: "Support", - click: () => shell.openExternal("mailto:support@ente.io"), - }, - { - label: "Product updates", - click: () => shell.openExternal("https://ente.io/blog/"), - }, - { type: "separator" }, - { - label: "View crash reports", - click: () => openDirectory(app.getPath("crashDumps")), - }, - { - label: "View logs", - click: openLogDirectory, - }, - ], - }, - ]; - return Menu.buildFromTemplate(template); -} From 1bb79854a5561a4881964c7218f7f8251d97da23 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 21:17:30 +0530 Subject: [PATCH 17/18] Mollify TypeScript --- desktop/src/main/menu.ts | 92 ++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index ddaacf32d..658932961 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -16,8 +16,6 @@ import { openLogDirectory } from "./util"; /** Create and return the entries in the app's main menu bar */ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { - const isMac = process.platform == "darwin"; - // The state of checkboxes // // Whenever the menu is redrawn the current value of these variables is used @@ -25,6 +23,9 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { let isAutoLaunchEnabled = await autoLauncher.isEnabled(); let shouldHideDockIcon = getHideDockIconPreference(); + const macOSOnly = (options: MenuItemConstructorOptions[]) => + process.platform == "darwin" ? options : []; + const handleCheckForUpdates = () => forceCheckForUpdateAndNotify(mainWindow); @@ -55,14 +56,12 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { { label: "ente", submenu: [ - ...((isMac - ? [ - { - label: "About Ente", - role: "about", - }, - ] - : []) as MenuItemConstructorOptions[]), + ...macOSOnly([ + { + label: "About Ente", + role: "about", + }, + ]), { type: "separator" }, { label: "Check for Updates...", @@ -93,20 +92,17 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { }, { type: "separator" }, - ...((isMac - ? [ - { - label: "Hide Ente", - role: "hide", - }, - { - label: "Hide Others", - role: "hideOthers", - }, - ] - : []) as MenuItemConstructorOptions[]), - - { type: "separator" }, + ...macOSOnly([ + { + label: "Hide Ente", + role: "hide", + }, + { + label: "Hide Others", + role: "hideOthers", + }, + { type: "separator" }, + ]), { label: "Quit", role: "quit", @@ -123,24 +119,22 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { { label: "Copy", role: "copy" }, { label: "Paste", role: "paste" }, { label: "Select All", role: "selectAll" }, - ...((isMac - ? [ - { type: "separator" }, - { - label: "Speech", - submenu: [ - { - role: "startSpeaking", - label: "start speaking", - }, - { - role: "stopSpeaking", - label: "stop speaking", - }, - ], - }, - ] - : []) as MenuItemConstructorOptions[]), + ...macOSOnly([ + { type: "separator" }, + { + label: "Speech", + submenu: [ + { + role: "startSpeaking", + label: "start speaking", + }, + { + role: "stopSpeaking", + label: "stop speaking", + }, + ], + }, + ]), ], }, { @@ -158,14 +152,12 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { { label: "Minimize", role: "minimize" }, { label: "Zoom", role: "zoom" }, { label: "Close", role: "close" }, - ...((isMac - ? [ - { type: "separator" }, - { label: "Bring All to Front", role: "front" }, - { type: "separator" }, - { label: "Ente", role: "window" }, - ] - : []) as MenuItemConstructorOptions[]), + ...macOSOnly([ + { type: "separator" }, + { label: "Bring All to Front", role: "front" }, + { type: "separator" }, + { label: "Ente", role: "window" }, + ]), ], }, { From 3706b99d36ee3eaa0453b345da104fc0f57c42fc Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 27 Mar 2024 21:21:31 +0530 Subject: [PATCH 18/18] Remove debug print --- desktop/src/main/init.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/desktop/src/main/init.ts b/desktop/src/main/init.ts index 6e463e4c4..0e94232aa 100644 --- a/desktop/src/main/init.ts +++ b/desktop/src/main/init.ts @@ -157,7 +157,6 @@ export function logStartupBanner() { const osRelease = os.release(); const systemVersion = process.getSystemVersion(); log.info("Running on", { platform, osRelease, systemVersion }); - log.debug(() => ({ platform, osRelease, systemVersion })); } function lowerCaseHeaders(responseHeaders: Record) {