[photos-desktop] Update to latest Electron (#1213)
- And update to latest everything else too - Fix the duplication in preload - ... and many more cleanup changes (see commit titles for details) Should not have major functional impact, and whatever IPC actions got broken by the switch to context-isolation will still not be working. This is just an intermediate step, and more PRs will continue fixing the IPC APIs.
This commit is contained in:
commit
a78e2892cd
39 changed files with 839 additions and 827 deletions
|
@ -1,50 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ente Photos</title>
|
||||
</head>
|
||||
|
||||
<body style="background-color: black">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 90vh;
|
||||
"
|
||||
>
|
||||
<div style="width: 64px">
|
||||
<svg
|
||||
version="1.1"
|
||||
id="L9"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 100 100"
|
||||
enable-background="new 0 0 0 0"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<path
|
||||
fill="#2dc262"
|
||||
d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50"
|
||||
>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
dur="1s"
|
||||
from="0 50 50"
|
||||
to="360 50 50"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -27,7 +27,7 @@ There is also a third environment that gets temporarily created:
|
|||
- The [preload script](../src/preload.ts) acts as a gateway between the _main_
|
||||
and the _renderer_ process. It runs in its own isolated environment.
|
||||
|
||||
### electron-builder
|
||||
### Packaging
|
||||
|
||||
[Electron Builder](https://www.electron.build) is used for packaging the app for
|
||||
distribution.
|
||||
|
@ -36,15 +36,44 @@ During the build it uses
|
|||
[electron-builder-notarize](https://github.com/karaggeorge/electron-builder-notarize)
|
||||
to notarize the macOS binary.
|
||||
|
||||
### Updates
|
||||
|
||||
[electron-updater](https://www.electron.build/auto-update#debugging), while a
|
||||
separate package, is also a part of Electron Builder. It provides an alternative
|
||||
to Electron's built in auto updater, with a more flexible API. It supports auto
|
||||
updates for the DMG, AppImage, DEB, RPM and NSIS packages.
|
||||
|
||||
[compare-versions](https://github.com/omichelsen/compare-versions) is used for
|
||||
semver comparisons when we decide when to trigger updates.
|
||||
|
||||
### Logging
|
||||
|
||||
[electron-log](https://github.com/megahertz/electron-log) is used for logging.
|
||||
Specifically, it allows us to log to a file (in addition to the console of the
|
||||
Node.js process), and also handles log rotation and limiting the size of the log
|
||||
files.
|
||||
|
||||
### next-electron-server
|
||||
|
||||
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.
|
||||
|
||||
## DX
|
||||
### Others
|
||||
|
||||
See [web/docs/dependencies#DX](../../web/docs/dependencies.md#dx) for the
|
||||
* [any-shell-escape](https://github.com/boazy/any-shell-escape) is for
|
||||
escaping shell commands before we execute them (e.g. say when invoking the
|
||||
embedded ffmpeg CLI).
|
||||
|
||||
* [auto-launch](https://github.com/Teamwork/node-auto-launch) is for
|
||||
automatically starting our app on login, if the user so wishes.
|
||||
|
||||
* [electron-store](https://github.com/sindresorhus/electron-store) is used for
|
||||
persisting user preferences and other arbitrary data.
|
||||
|
||||
## Dev
|
||||
|
||||
See [web/docs/dependencies#DX](../../web/docs/dependencies.md#dev) for the
|
||||
general development experience related dependencies like TypeScript etc, which
|
||||
are similar to that in the web code.
|
||||
|
||||
|
@ -52,6 +81,43 @@ Some extra ones specific to the code here are:
|
|||
|
||||
* [concurrently](https://github.com/open-cli-tools/concurrently) for spawning
|
||||
parallel tasks when we do `yarn dev`.
|
||||
* [shx](https://github.com/shelljs/shx) for providing a portable way to use
|
||||
Unix commands in scripts. This allows us to use the same commands across
|
||||
different platforms like Linux and Windows.
|
||||
|
||||
* [shx](https://github.com/shelljs/shx) for providing a portable way to use Unix
|
||||
commands in our `package.json` scripts. This allows us to use the same
|
||||
commands (like `ln`) across different platforms like Linux and Windows.
|
||||
|
||||
## Functionality
|
||||
|
||||
### Conversion
|
||||
|
||||
The main tool we use is for arbitrary conversions is FFMPEG. To bundle a
|
||||
(platform specific) static binary of ffmpeg with our app, we use
|
||||
[ffmpeg-static](https://github.com/eugeneware/ffmpeg-static).
|
||||
|
||||
> There is a significant (~20x) speed difference between using the compiled
|
||||
> FFMPEG binary and using the WASM one (that our renderer process already has).
|
||||
> Which is why we bundle it to speed up operations on the desktop app.
|
||||
|
||||
In addition, we also bundle a static Linux binary of imagemagick in our extra
|
||||
resources (`build`) folder. This is used for thumbnail generation on Linux.
|
||||
|
||||
On macOS, we use the `sips` CLI tool for conversion, but that is already
|
||||
available on the host machine, and is not bundled with our app.
|
||||
|
||||
### Watch Folders
|
||||
|
||||
[chokidar](https://github.com/paulmillr/chokidar) is used as a file system
|
||||
watcher for the watch folders functionality.
|
||||
|
||||
### AI/ML
|
||||
|
||||
* [onnxruntime-node](https://github.com/Microsoft/onnxruntime)
|
||||
* html-entities is used by the bundled clip-bpe-ts.
|
||||
* GGML binaries are bundled
|
||||
* We also use [jpeg-js](https://github.com/jpeg-js/jpeg-js#readme) for
|
||||
conversion of all images to JPEG before processing.
|
||||
|
||||
## ZIP
|
||||
|
||||
[node-stream-zip](https://github.com/antelle/node-stream-zip) is used for
|
||||
reading of large ZIP files (e.g. during imports of Google Takeout ZIPs).
|
||||
|
|
|
@ -8,12 +8,7 @@ Launch the app in development mode:
|
|||
|
||||
- Transpiles the files in `src/` and starts the main process.
|
||||
|
||||
- Runs a development server for the renderer (with hot module reload).
|
||||
|
||||
- Starts tsc in watch mode to recompile the JS files used by the main process.
|
||||
Note that the main process is not restarted on changes automatically, you'll
|
||||
still need to restart the app manually – running tsc in watch mode is still
|
||||
useful to notice any errors.
|
||||
- Runs a development server for the renderer, with hot module reload.
|
||||
|
||||
### yarn build
|
||||
|
||||
|
|
|
@ -11,43 +11,42 @@
|
|||
"build-main:quick": "tsc && electron-builder --dir --config.compression=store --config.mac.identity=null",
|
||||
"build-renderer": "cd ../web && yarn install && yarn build:photos && cd ../desktop && shx rm -f out && shx ln -sf ../web/apps/photos/out out",
|
||||
"build:quick": "yarn build-renderer && yarn build-main:quick",
|
||||
"dev": "concurrently --names 'main,rndr,tscw' \"yarn dev-main\" \"yarn dev-renderer\" \"yarn dev-main-watch\"",
|
||||
"dev": "concurrently --names 'main,rndr' \"yarn dev-main\" \"yarn dev-renderer\"",
|
||||
"dev-main": "tsc && electron app/main.js",
|
||||
"dev-main-watch": "tsc --watch --preserveWatchOutput",
|
||||
"dev-renderer": "cd ../web && yarn install && yarn dev:photos",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"lint": "yarn prettier --check . && eslint --ext .ts src",
|
||||
"lint-fix": "yarn prettier --write . && eslint --fix --ext .ts src"
|
||||
},
|
||||
"dependencies": {
|
||||
"any-shell-escape": "^0.1.1",
|
||||
"auto-launch": "^5.0.5",
|
||||
"chokidar": "^3.5.3",
|
||||
"compare-versions": "^6.1.0",
|
||||
"electron-log": "^4.3.5",
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "^4.3.8",
|
||||
"ffmpeg-static": "^5.1.0",
|
||||
"html-entities": "^2.4.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"any-shell-escape": "^0.1",
|
||||
"auto-launch": "^5.0",
|
||||
"chokidar": "^3.6",
|
||||
"compare-versions": "^6.1",
|
||||
"electron-log": "^5.1",
|
||||
"electron-store": "^8.2",
|
||||
"electron-updater": "^6.1",
|
||||
"ffmpeg-static": "^5.2",
|
||||
"html-entities": "^2.5",
|
||||
"jpeg-js": "^0.4",
|
||||
"next-electron-server": "^1",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"onnxruntime-node": "^1.16.3"
|
||||
"node-stream-zip": "^1.15",
|
||||
"onnxruntime-node": "^1.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/auto-launch": "^5.0.2",
|
||||
"@types/ffmpeg-static": "^3.0.1",
|
||||
"@types/auto-launch": "^5.0",
|
||||
"@types/ffmpeg-static": "^3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7",
|
||||
"@typescript-eslint/parser": "^7",
|
||||
"concurrently": "^8",
|
||||
"electron": "^25.8.4",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron-builder-notarize": "^1.2.0",
|
||||
"electron": "^29",
|
||||
"electron-builder": "^24",
|
||||
"electron-builder-notarize": "^1.5",
|
||||
"eslint": "^8",
|
||||
"prettier": "^3",
|
||||
"prettier-plugin-organize-imports": "^3.2",
|
||||
"prettier-plugin-packagejson": "^2.4",
|
||||
"shx": "^0.3.4",
|
||||
"shx": "^0.3",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"productName": "ente"
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { getElectronFile } from "../services/fs";
|
||||
import {
|
||||
getElectronFilesFromGoogleZip,
|
||||
getSavedFilePaths,
|
||||
} from "../services/upload";
|
||||
import { uploadStatusStore } from "../stores/upload.store";
|
||||
import { ElectronFile, FILE_PATH_TYPE } from "../types";
|
||||
import { getElectronFile } from "./../services/fs";
|
||||
import { ElectronFile, FILE_PATH_TYPE } from "../types/ipc";
|
||||
|
||||
export const getPendingUploads = async () => {
|
||||
const filePaths = getSavedFilePaths(FILE_PATH_TYPE.FILES);
|
||||
|
|
|
@ -8,19 +8,16 @@
|
|||
*
|
||||
* https://www.electronjs.org/docs/latest/tutorial/process-model#the-main-process
|
||||
*/
|
||||
import * as log from "electron-log";
|
||||
import log from "electron-log";
|
||||
import { app, BrowserWindow } from "electron/main";
|
||||
import serveNextAt from "next-electron-server";
|
||||
import { existsSync } from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { isDev } from "./main/general";
|
||||
import { attachFSWatchIPCHandlers, attachIPCHandlers } from "./main/ipc";
|
||||
import { logErrorSentry, setupLogging } from "./main/log";
|
||||
import { initWatcher } from "./services/chokidar";
|
||||
import { addAllowOriginHeader } from "./utils/cors";
|
||||
import { createWindow } from "./utils/createWindow";
|
||||
import {
|
||||
addAllowOriginHeader,
|
||||
createWindow,
|
||||
handleDockIconHideOnAutoLaunch,
|
||||
handleDownloads,
|
||||
handleExternalLinks,
|
||||
|
@ -29,7 +26,10 @@ import {
|
|||
setupMacWindowOnDockIconClick,
|
||||
setupMainMenu,
|
||||
setupTrayItem,
|
||||
} from "./utils/main";
|
||||
} from "./main/init";
|
||||
import { attachFSWatchIPCHandlers, attachIPCHandlers } from "./main/ipc";
|
||||
import { logErrorSentry, setupLogging } from "./main/log";
|
||||
import { initWatcher } from "./services/chokidar";
|
||||
|
||||
let appIsQuitting = false;
|
||||
|
||||
|
@ -100,8 +100,9 @@ const increaseDiskCache = () => {
|
|||
|
||||
/**
|
||||
* Older versions of our app used to maintain a cache dir using the main
|
||||
* process. This has been deprecated in favor of using a normal web cache (See:
|
||||
* [Note: Increased disk cache for the desktop app]).
|
||||
* process. This has been deprecated in favor of using a normal web cache.
|
||||
*
|
||||
* See [Note: Increased disk cache for the desktop app]
|
||||
*
|
||||
* Delete the old cache dir if it exists. This code was added March 2024, and
|
||||
* can be removed after some time once most people have upgraded to newer
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { dialog } from "electron/main";
|
||||
import * as path from "node:path";
|
||||
import path from "node:path";
|
||||
import { getDirFilePaths, getElectronFile } from "../services/fs";
|
||||
import { getElectronFilesFromGoogleZip } from "../services/upload";
|
||||
import type { ElectronFile } from "../types";
|
||||
import type { ElectronFile } from "../types/ipc";
|
||||
|
||||
export const selectDirectory = async () => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
* @file file system related functions exposed over the context bridge.
|
||||
*/
|
||||
import { createWriteStream, existsSync } from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { Readable } from "node:stream";
|
||||
import { logError } from "./log";
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { shell } from "electron"; /* TODO(MR): Why is this not in /main? */
|
||||
import { app } from "electron/main";
|
||||
import * as path from "node:path";
|
||||
import path from "node:path";
|
||||
|
||||
/** `true` if the app is running in development mode. */
|
||||
export const isDev = !app.isPackaged;
|
||||
|
|
|
@ -4,15 +4,94 @@ import { existsSync } from "node:fs";
|
|||
import os from "os";
|
||||
import path from "path";
|
||||
import util from "util";
|
||||
import { rendererURL } from "../main";
|
||||
import { isDev } from "../main/general";
|
||||
import { isAppQuitting, rendererURL } from "../main";
|
||||
import { setupAutoUpdater } from "../services/appUpdater";
|
||||
import autoLauncher from "../services/autoLauncher";
|
||||
import { getHideDockIconPreference } from "../services/userPreference";
|
||||
import { isPlatform } from "./common/platform";
|
||||
import { buildContextMenu, buildMenuBar } from "./menu";
|
||||
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);
|
||||
|
||||
/**
|
||||
* Create an return the {@link BrowserWindow} that will form our app's UI.
|
||||
*
|
||||
* This window will show the HTML served from {@link rendererURL}.
|
||||
*/
|
||||
export const createWindow = async () => {
|
||||
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
|
||||
show: false,
|
||||
});
|
||||
|
||||
const wasAutoLaunched = await autoLauncher.wasAutoLaunched();
|
||||
if (wasAutoLaunched) {
|
||||
// Keep the macOS dock icon hidden if we were auto launched.
|
||||
if (process.platform == "darwin") app.dock.hide();
|
||||
} else {
|
||||
// Show our window (maximizing it) if this is not an auto-launch on
|
||||
// login.
|
||||
mainWindow.maximize();
|
||||
}
|
||||
|
||||
mainWindow.loadURL(rendererURL);
|
||||
|
||||
// Open the DevTools automatically when running in dev mode
|
||||
if (isDev) mainWindow.webContents.openDevTools();
|
||||
|
||||
mainWindow.webContents.on("render-process-gone", (event, 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", () => {
|
||||
mainWindow.webContents.forcefullyCrashRenderer();
|
||||
ElectronLog.log("webContents event unresponsive");
|
||||
});
|
||||
|
||||
mainWindow.on("close", function (event) {
|
||||
if (!isAppQuitting()) {
|
||||
event.preventDefault();
|
||||
mainWindow.hide();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
mainWindow.on("hide", () => {
|
||||
// On macOS, also hide the app's icon in the dock if the user has
|
||||
// selected the Settings > Hide dock icon checkbox.
|
||||
const shouldHideDockIcon = getHideDockIconPreference();
|
||||
if (process.platform == "darwin" && shouldHideDockIcon) {
|
||||
app.dock.hide();
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on("show", () => {
|
||||
if (process.platform == "darwin") app.dock.show();
|
||||
});
|
||||
|
||||
return mainWindow;
|
||||
};
|
||||
|
||||
export async function handleUpdates(mainWindow: BrowserWindow) {
|
||||
const isInstalledViaBrew = await checkIfInstalledViaBrew();
|
||||
if (!isDev && !isInstalledViaBrew) {
|
||||
|
@ -116,3 +195,23 @@ export async function checkIfInstalledViaBrew() {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function lowerCaseHeaders(responseHeaders: Record<string, string[]>) {
|
||||
const headers: Record<string, string[]> = {};
|
||||
for (const key of Object.keys(responseHeaders)) {
|
||||
headers[key.toLowerCase()] = responseHeaders[key];
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
export function addAllowOriginHeader(mainWindow: BrowserWindow) {
|
||||
mainWindow.webContents.session.webRequest.onHeadersReceived(
|
||||
(details, callback) => {
|
||||
details.responseHeaders = lowerCaseHeaders(details.responseHeaders);
|
||||
details.responseHeaders["access-control-allow-origin"] = ["*"];
|
||||
callback({
|
||||
responseHeaders: details.responseHeaders,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
*
|
||||
* This file is meant as a sibling to `preload.ts`, but this one runs in the
|
||||
* context of the main process, and can import other files from `src/`.
|
||||
*
|
||||
* See [Note: types.ts <-> preload.ts <-> ipc.ts]
|
||||
*/
|
||||
|
||||
import type { FSWatcher } from "chokidar";
|
||||
|
@ -44,7 +46,7 @@ import type {
|
|||
FILE_PATH_TYPE,
|
||||
Model,
|
||||
WatchMapping,
|
||||
} from "../types";
|
||||
} from "../types/ipc";
|
||||
import {
|
||||
selectDirectory,
|
||||
showUploadDirsDialog,
|
||||
|
@ -95,7 +97,7 @@ export const attachIPCHandlers = () => {
|
|||
|
||||
ipcMain.handle("openLogDirectory", (_) => openLogDirectory());
|
||||
|
||||
// See: [Note: Catching exception during .send/.on]
|
||||
// See [Note: Catching exception during .send/.on]
|
||||
ipcMain.on("logToDisk", (_, message) => logToDisk(message));
|
||||
|
||||
ipcMain.on("clear-electron-store", (_) => {
|
||||
|
|
|
@ -24,47 +24,43 @@
|
|||
* file. However, since this is just boilerplate code providing a bridge between
|
||||
* the main and renderer, we avoid introducing another moving part into the mix
|
||||
* and just keep the entire preload setup in this single file.
|
||||
*
|
||||
* [Note: types.ts <-> preload.ts <-> ipc.ts]
|
||||
*
|
||||
* The following three files are boilerplatish linkage of the same functions,
|
||||
* and when changing one of them, remember to see if the other two also need
|
||||
* changing:
|
||||
*
|
||||
* - [renderer] web/packages/shared/electron/types.ts contains docs
|
||||
* - [preload] desktop/src/preload.ts ↕︎
|
||||
* - [main] desktop/src/main/ipc.ts contains impl
|
||||
*/
|
||||
|
||||
import { contextBridge, ipcRenderer } from "electron";
|
||||
import type { ElectronFile } from "./types";
|
||||
import { contextBridge, ipcRenderer } from "electron/renderer";
|
||||
|
||||
// TODO (MR): Uncomment and FIXME once preload is getting loaded.
|
||||
// import { setupLogging } from "./main/log";
|
||||
// setupLogging();
|
||||
// While we can't import other code, we can import types since they're just
|
||||
// needed when compiling and will not be needed / looked around for at runtime.
|
||||
import type {
|
||||
AppUpdateInfo,
|
||||
ElectronFile,
|
||||
FILE_PATH_TYPE,
|
||||
Model,
|
||||
WatchMapping,
|
||||
} from "./types/ipc";
|
||||
|
||||
// - General
|
||||
|
||||
/** Return the version of the desktop app. */
|
||||
const appVersion = (): Promise<string> => ipcRenderer.invoke("appVersion");
|
||||
|
||||
/**
|
||||
* Open the given {@link dirPath} in the system's folder viewer.
|
||||
*
|
||||
* For example, on macOS this'll open {@link dirPath} in Finder.
|
||||
*/
|
||||
const openDirectory = (dirPath: string): Promise<void> =>
|
||||
ipcRenderer.invoke("openDirectory");
|
||||
|
||||
/**
|
||||
* Open the app's log directory in the system's folder viewer.
|
||||
*
|
||||
* @see {@link openDirectory}
|
||||
*/
|
||||
const openLogDirectory = (): Promise<void> =>
|
||||
ipcRenderer.invoke("openLogDirectory");
|
||||
|
||||
/**
|
||||
* Log the given {@link message} to the on-disk log file maintained by the
|
||||
* desktop app.
|
||||
*/
|
||||
const logToDisk = (message: string): void =>
|
||||
ipcRenderer.send("logToDisk", message);
|
||||
|
||||
/**
|
||||
* Return true if there is a file or directory at the given
|
||||
* {@link path}.
|
||||
*/
|
||||
const fsExists = (path: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke("fsExists", path);
|
||||
|
||||
|
@ -89,12 +85,6 @@ const getEncryptionKey = (): Promise<string> =>
|
|||
|
||||
// - App update
|
||||
|
||||
/* preload: duplicated */
|
||||
interface AppUpdateInfo {
|
||||
autoUpdatable: boolean;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const registerUpdateEventListener = (
|
||||
showUpdateDialog: (updateInfo: AppUpdateInfo) => void,
|
||||
) => {
|
||||
|
@ -152,12 +142,6 @@ const runFFmpegCmd = (
|
|||
|
||||
// - ML
|
||||
|
||||
/* preload: duplicated Model */
|
||||
enum Model {
|
||||
GGML_CLIP = "ggml-clip",
|
||||
ONNX_CLIP = "onnx-clip",
|
||||
}
|
||||
|
||||
const computeImageEmbedding = (
|
||||
model: Model,
|
||||
imageData: Uint8Array,
|
||||
|
@ -222,22 +206,6 @@ const addWatchMapping = (
|
|||
const removeWatchMapping = (folderPath: string): Promise<void> =>
|
||||
ipcRenderer.invoke("removeWatchMapping", folderPath);
|
||||
|
||||
/* preload: duplicated WatchMappingSyncedFile */
|
||||
interface WatchMappingSyncedFile {
|
||||
path: string;
|
||||
uploadedFileID: number;
|
||||
collectionID: number;
|
||||
}
|
||||
|
||||
/* preload: duplicated WatchMapping */
|
||||
interface WatchMapping {
|
||||
rootFolderName: string;
|
||||
uploadStrategy: number;
|
||||
folderPath: string;
|
||||
syncedFiles: WatchMappingSyncedFile[];
|
||||
ignoredFiles: string[];
|
||||
}
|
||||
|
||||
const getWatchMappings = (): Promise<WatchMapping[]> =>
|
||||
ipcRenderer.invoke("getWatchMappings");
|
||||
|
||||
|
@ -292,12 +260,6 @@ const getPendingUploads = (): Promise<{
|
|||
type: string;
|
||||
}> => ipcRenderer.invoke("getPendingUploads");
|
||||
|
||||
/* preload: duplicated FILE_PATH_TYPE */
|
||||
enum FILE_PATH_TYPE {
|
||||
FILES = "files",
|
||||
ZIPS = "zips",
|
||||
}
|
||||
|
||||
const setToUploadFiles = (
|
||||
type: FILE_PATH_TYPE,
|
||||
filePaths: string[],
|
||||
|
@ -331,7 +293,7 @@ const getDirFiles = (dirPath: string): Promise<ElectronFile[]> =>
|
|||
// Algorithm to serialize objects passed between processes.
|
||||
// https://www.electronjs.org/docs/latest/tutorial/ipc#object-serialization
|
||||
//
|
||||
// In particular, both ArrayBuffer is eligible for structured cloning.
|
||||
// In particular, ArrayBuffer is eligible for structured cloning.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
|
||||
//
|
||||
// Also, ArrayBuffer is "transferable", which means it is a zero-copy operation
|
||||
|
|
|
@ -4,7 +4,7 @@ import { default as ElectronLog, default as log } from "electron-log";
|
|||
import { autoUpdater } from "electron-updater";
|
||||
import { setIsAppQuitting, setIsUpdateAvailable } from "../main";
|
||||
import { logErrorSentry } from "../main/log";
|
||||
import { AppUpdateInfo } from "../types";
|
||||
import { AppUpdateInfo } from "../types/ipc";
|
||||
import {
|
||||
clearMuteUpdateNotificationVersion,
|
||||
clearSkipAppVersion,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AutoLauncherClient } from "../types/autoLauncher";
|
||||
import { AutoLauncherClient } from "../types/main";
|
||||
import { isPlatform } from "../utils/common/platform";
|
||||
import linuxAndWinAutoLauncher from "./autoLauncherClients/linuxAndWinAutoLauncher";
|
||||
import macAutoLauncher from "./autoLauncherClients/macAutoLauncher";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import AutoLaunch from "auto-launch";
|
||||
import { app } from "electron";
|
||||
import { AutoLauncherClient } from "../../types/autoLauncher";
|
||||
import { AutoLauncherClient } from "../../types/main";
|
||||
|
||||
const LAUNCHED_AS_HIDDEN_FLAG = "hidden";
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { app } from "electron";
|
||||
import { AutoLauncherClient } from "../../types/autoLauncher";
|
||||
import { AutoLauncherClient } from "../../types/main";
|
||||
|
||||
class MacAutoLauncher implements AutoLauncherClient {
|
||||
async isEnabled() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import chokidar from "chokidar";
|
||||
import { BrowserWindow } from "electron";
|
||||
import * as path from "path";
|
||||
import path from "path";
|
||||
import { logError } from "../main/log";
|
||||
import { getWatchMappings } from "../services/watch";
|
||||
import { getElectronFile } from "./fs";
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import * as log from "electron-log";
|
||||
import log from "electron-log";
|
||||
import { app, net } from "electron/main";
|
||||
import { existsSync } from "fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
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 { isDev } from "../main/general";
|
||||
import { logErrorSentry } from "../main/log";
|
||||
import { Model } from "../types";
|
||||
import { Model } from "../types/ipc";
|
||||
import Tokenizer from "../utils/clip-bpe-ts/mod";
|
||||
import { getPlatform } from "../utils/common/platform";
|
||||
import { generateTempFilePath } from "../utils/temp";
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import log from "electron-log";
|
||||
import pathToFfmpeg from "ffmpeg-static";
|
||||
import { existsSync } from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
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 { ElectronFile } from "../types";
|
||||
import { ElectronFile } from "../types/ipc";
|
||||
import { generateTempFilePath, getTempDirPath } from "../utils/temp";
|
||||
|
||||
const shellescape = require("any-shell-escape");
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import StreamZip from "node-stream-zip";
|
||||
import { existsSync } from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { logError } from "../main/log";
|
||||
import { ElectronFile } from "../types";
|
||||
import { ElectronFile } from "../types/ipc";
|
||||
|
||||
const FILE_STREAM_CHUNK_SIZE: number = 4 * 1024 * 1024;
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { exec } from "child_process";
|
||||
import log from "electron-log";
|
||||
import { existsSync } from "fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
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 { isDev } from "../main/general";
|
||||
import { logError, logErrorSentry } from "../main/log";
|
||||
import { ElectronFile } from "../types";
|
||||
import { ElectronFile } from "../types/ipc";
|
||||
import { isPlatform } from "../utils/common/platform";
|
||||
import { generateTempFilePath } from "../utils/temp";
|
||||
import { deleteTempFile } from "./ffmpeg";
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import StreamZip from "node-stream-zip";
|
||||
import path from "path";
|
||||
import { uploadStatusStore } from "../stores/upload.store";
|
||||
import { ElectronFile, FILE_PATH_KEYS, FILE_PATH_TYPE } from "../types";
|
||||
import { ElectronFile, FILE_PATH_TYPE } from "../types/ipc";
|
||||
import { FILE_PATH_KEYS } from "../types/main";
|
||||
import { getValidPaths, getZipFileStream } from "./fs";
|
||||
|
||||
export const getSavedFilePaths = (type: FILE_PATH_TYPE) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { FSWatcher } from "chokidar";
|
||||
import ElectronLog from "electron-log";
|
||||
import { watchStore } from "../stores/watch.store";
|
||||
import { WatchMapping, WatchStoreType } from "../types";
|
||||
import { WatchMapping, WatchStoreType } from "../types/ipc";
|
||||
import { isMappingPresent } from "../utils/watch";
|
||||
|
||||
export const addWatchMapping = async (
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Store, { Schema } from "electron-store";
|
||||
import { KeysStoreType } from "../types";
|
||||
import type { KeysStoreType } from "../types/main";
|
||||
|
||||
const keysStoreSchema: Schema<KeysStoreType> = {
|
||||
AnonymizeUserID: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Store, { Schema } from "electron-store";
|
||||
import { SafeStorageStoreType } from "../types";
|
||||
import type { SafeStorageStoreType } from "../types/main";
|
||||
|
||||
const safeStorageSchema: Schema<SafeStorageStoreType> = {
|
||||
encryptionKey: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Store, { Schema } from "electron-store";
|
||||
import { UploadStoreType } from "../types";
|
||||
import type { UploadStoreType } from "../types/main";
|
||||
|
||||
const uploadStoreSchema: Schema<UploadStoreType> = {
|
||||
filePaths: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Store, { Schema } from "electron-store";
|
||||
import { UserPreferencesType } from "../types";
|
||||
import type { UserPreferencesType } from "../types/main";
|
||||
|
||||
const userPreferencesSchema: Schema<UserPreferencesType> = {
|
||||
hideDockIcon: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Store, { Schema } from "electron-store";
|
||||
import { WatchStoreType } from "../types";
|
||||
import { WatchStoreType } from "../types/ipc";
|
||||
|
||||
const watchStoreSchema: Schema<WatchStoreType> = {
|
||||
mappings: {
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
export interface AutoLauncherClient {
|
||||
isEnabled: () => Promise<boolean>;
|
||||
toggleAutoLaunch: () => Promise<void>;
|
||||
wasAutoLaunched: () => Promise<boolean>;
|
||||
}
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @file types that are shared across the IPC boundary with the renderer process
|
||||
*
|
||||
* This file is manually kept in sync with the renderer code.
|
||||
* See [Note: types.ts <-> preload.ts <-> ipc.ts]
|
||||
*/
|
||||
/**
|
||||
* Deprecated - Use File + webUtils.getPathForFile instead
|
||||
*
|
||||
|
@ -20,18 +26,6 @@ export interface ElectronFile {
|
|||
arrayBuffer: () => Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
export interface UploadStoreType {
|
||||
filePaths: string[];
|
||||
zipPaths: string[];
|
||||
collectionName: string;
|
||||
}
|
||||
|
||||
export interface KeysStoreType {
|
||||
AnonymizeUserID: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface WatchMappingSyncedFile {
|
||||
path: string;
|
||||
uploadedFileID: number;
|
||||
|
@ -55,23 +49,6 @@ export enum FILE_PATH_TYPE {
|
|||
ZIPS = "zips",
|
||||
}
|
||||
|
||||
export const FILE_PATH_KEYS: {
|
||||
[k in FILE_PATH_TYPE]: keyof UploadStoreType;
|
||||
} = {
|
||||
[FILE_PATH_TYPE.ZIPS]: "zipPaths",
|
||||
[FILE_PATH_TYPE.FILES]: "filePaths",
|
||||
};
|
||||
|
||||
export interface SafeStorageStoreType {
|
||||
encryptionKey: string;
|
||||
}
|
||||
|
||||
export interface UserPreferencesType {
|
||||
hideDockIcon: boolean;
|
||||
skipAppVersion: string;
|
||||
muteUpdateNotificationVersion: string;
|
||||
}
|
||||
|
||||
export interface AppUpdateInfo {
|
||||
autoUpdatable: boolean;
|
||||
version: string;
|
36
desktop/src/types/main.ts
Normal file
36
desktop/src/types/main.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { FILE_PATH_TYPE } from "./ipc";
|
||||
|
||||
export interface AutoLauncherClient {
|
||||
isEnabled: () => Promise<boolean>;
|
||||
toggleAutoLaunch: () => Promise<void>;
|
||||
wasAutoLaunched: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface UploadStoreType {
|
||||
filePaths: string[];
|
||||
zipPaths: string[];
|
||||
collectionName: string;
|
||||
}
|
||||
|
||||
export interface KeysStoreType {
|
||||
AnonymizeUserID: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const FILE_PATH_KEYS: {
|
||||
[k in FILE_PATH_TYPE]: keyof UploadStoreType;
|
||||
} = {
|
||||
[FILE_PATH_TYPE.ZIPS]: "zipPaths",
|
||||
[FILE_PATH_TYPE.FILES]: "filePaths",
|
||||
};
|
||||
|
||||
export interface SafeStorageStoreType {
|
||||
encryptionKey: string;
|
||||
}
|
||||
|
||||
export interface UserPreferencesType {
|
||||
hideDockIcon: boolean;
|
||||
skipAppVersion: string;
|
||||
muteUpdateNotificationVersion: string;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { BrowserWindow } from "electron";
|
||||
|
||||
function lowerCaseHeaders(responseHeaders: Record<string, string[]>) {
|
||||
const headers: Record<string, string[]> = {};
|
||||
for (const key of Object.keys(responseHeaders)) {
|
||||
headers[key.toLowerCase()] = responseHeaders[key];
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
export function addAllowOriginHeader(mainWindow: BrowserWindow) {
|
||||
mainWindow.webContents.session.webRequest.onHeadersReceived(
|
||||
(details, callback) => {
|
||||
details.responseHeaders = lowerCaseHeaders(details.responseHeaders);
|
||||
details.responseHeaders["access-control-allow-origin"] = ["*"];
|
||||
callback({
|
||||
responseHeaders: details.responseHeaders,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
import { app, BrowserWindow, nativeImage } from "electron";
|
||||
import ElectronLog from "electron-log";
|
||||
import * as path from "path";
|
||||
import { isAppQuitting, rendererURL } from "../main";
|
||||
import autoLauncher from "../services/autoLauncher";
|
||||
import { logErrorSentry } from "../main/log";
|
||||
import { getHideDockIconPreference } from "../services/userPreference";
|
||||
import { isDev } from "../main/general";
|
||||
import { isPlatform } from "./common/platform";
|
||||
|
||||
/**
|
||||
* Create an return the {@link BrowserWindow} that will form our app's UI.
|
||||
*
|
||||
* This window will show the HTML served from {@link rendererURL}.
|
||||
*/
|
||||
export const createWindow = async () => {
|
||||
const appImgPath = isDev
|
||||
? "resources/window-icon.png"
|
||||
: path.join(process.resourcesPath, "window-icon.png");
|
||||
const appIcon = nativeImage.createFromPath(appImgPath);
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "../preload.js"),
|
||||
},
|
||||
icon: appIcon,
|
||||
show: false, // don't show the main window on load,
|
||||
});
|
||||
const wasAutoLaunched = await autoLauncher.wasAutoLaunched();
|
||||
ElectronLog.log("wasAutoLaunched", wasAutoLaunched);
|
||||
|
||||
const splash = new BrowserWindow({
|
||||
transparent: true,
|
||||
show: false,
|
||||
});
|
||||
if (isPlatform("mac") && wasAutoLaunched) {
|
||||
app.dock.hide();
|
||||
}
|
||||
if (!wasAutoLaunched) {
|
||||
splash.maximize();
|
||||
splash.show();
|
||||
}
|
||||
|
||||
if (isDev) {
|
||||
splash.loadFile(`../resources/splash.html`);
|
||||
mainWindow.loadURL(rendererURL);
|
||||
// Open the DevTools.
|
||||
mainWindow.webContents.openDevTools();
|
||||
} else {
|
||||
splash.loadURL(
|
||||
`file://${path.join(process.resourcesPath, "splash.html")}`,
|
||||
);
|
||||
mainWindow.loadURL(rendererURL);
|
||||
}
|
||||
mainWindow.once("ready-to-show", async () => {
|
||||
try {
|
||||
splash.destroy();
|
||||
if (!wasAutoLaunched) {
|
||||
mainWindow.maximize();
|
||||
mainWindow.show();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
mainWindow.webContents.on("render-process-gone", (event, 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", () => {
|
||||
mainWindow.webContents.forcefullyCrashRenderer();
|
||||
ElectronLog.log("webContents event unresponsive");
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
splash.destroy();
|
||||
if (!wasAutoLaunched) {
|
||||
mainWindow.maximize();
|
||||
mainWindow.show();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}, 2000);
|
||||
mainWindow.on("close", function (event) {
|
||||
if (!isAppQuitting()) {
|
||||
event.preventDefault();
|
||||
mainWindow.hide();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
mainWindow.on("hide", () => {
|
||||
const shouldHideDockIcon = getHideDockIconPreference();
|
||||
if (isPlatform("mac") && shouldHideDockIcon) {
|
||||
app.dock.hide();
|
||||
}
|
||||
});
|
||||
mainWindow.on("show", () => {
|
||||
if (isPlatform("mac")) {
|
||||
app.dock.show();
|
||||
}
|
||||
});
|
||||
return mainWindow;
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import { app } from "electron/main";
|
||||
import { existsSync } from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "path";
|
||||
|
||||
const CHARACTERS =
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { WatchMapping } from "../types";
|
||||
import { WatchMapping } from "../types/ipc";
|
||||
|
||||
export function isMappingPresent(
|
||||
watchMappings: WatchMapping[],
|
||||
|
|
|
@ -48,14 +48,6 @@
|
|||
|
||||
/* Emit the generated JS into `app/` */
|
||||
"outDir": "app",
|
||||
/* Generate source maps */
|
||||
"sourceMap": true,
|
||||
/* Allow absolute imports starting with src as root */
|
||||
"baseUrl": "src",
|
||||
/* Allow imports of paths from node_modules */
|
||||
"paths": {
|
||||
"*": ["node_modules/*"]
|
||||
},
|
||||
|
||||
/* Temporary overrides to get things to compile with the older config */
|
||||
"strict": false,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
# Dependencies
|
||||
|
||||
## DX
|
||||
## Dev
|
||||
|
||||
These are some global dev dependencies in the root `package.json`. These set the
|
||||
baseline for how our code be in all the workspaces in this (yarn) monorepo.
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { ElectronFile } from "@ente/shared/upload/types";
|
||||
import { WatchMapping } from "@ente/shared/watchFolder/types";
|
||||
// Following are types shared with the Electron process. This list is manually
|
||||
// kept in sync with `desktop/src/types/ipc.ts`.
|
||||
//
|
||||
// See [Note: types.ts <-> preload.ts <-> ipc.ts]
|
||||
|
||||
import type { ElectronFile } from "@ente/shared/upload/types";
|
||||
import type { WatchMapping } from "@ente/shared/watchFolder/types";
|
||||
|
||||
export interface AppUpdateInfo {
|
||||
autoUpdatable: boolean;
|
||||
|
@ -26,7 +31,8 @@ export enum PICKED_UPLOAD_TYPE {
|
|||
* Extra APIs provided by the Node.js layer when our code is running in Electron
|
||||
*
|
||||
* This list is manually kept in sync with `desktop/src/preload.ts`. In case of
|
||||
* a mismatch, the types may lie.
|
||||
* a mismatch, the types may lie. See also: [Note: types.ts <-> preload.ts <->
|
||||
* ipc.ts]
|
||||
*
|
||||
* These extra objects and functions will only be available when our code is
|
||||
* running as the renderer process in Electron. So something in the code path
|
||||
|
@ -57,7 +63,7 @@ export interface ElectronAPIsType {
|
|||
* desktop app.
|
||||
*
|
||||
* Note: Unlike the other functions exposed over the Electron bridge,
|
||||
* logToDisk is fire-and-forge and does not return a promise.
|
||||
* logToDisk is fire-and-forget and does not return a promise.
|
||||
*/
|
||||
logToDisk: (message: string) => void;
|
||||
|
||||
|
|
Loading…
Reference in a new issue