diff --git a/desktop/.github/workflows/desktop-draft-release.yml b/desktop/.github/workflows/desktop-draft-release.yml
new file mode 100644
index 000000000..8c0652dfc
--- /dev/null
+++ b/desktop/.github/workflows/desktop-draft-release.yml
@@ -0,0 +1,70 @@
+name: "Draft release"
+
+# Build the desktop/draft-release branch and update the existing draft release
+# with the resultant artifacts.
+#
+# This is meant for doing tests that require the app to be signed and packaged.
+# Such releases should not be published to end users.
+#
+# Workflow:
+#
+# 1. Push your changes to the "desktop/draft-release" branch on
+# https://github.com/ente-io/ente.
+#
+# 2. Create a draft release with tag equal to the version in the `package.json`.
+#
+# 3. Trigger this workflow. You can trigger it multiple times, each time it'll
+# just update the artifacts attached to the same draft.
+#
+# 4. Once testing is done delete the draft.
+
+on:
+ # Trigger manually or `gh workflow run desktop-draft-release.yml`.
+ workflow_dispatch:
+
+jobs:
+ release:
+ runs-on: macos-latest
+
+ defaults:
+ run:
+ working-directory: desktop
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ repository: ente-io/ente
+ ref: desktop/draft-release
+ submodules: recursive
+
+ - name: Setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Build
+ uses: ente-io/action-electron-builder@v1.0.0
+ with:
+ package_root: desktop
+
+ # GitHub token, automatically provided to the action
+ # (No need to define this secret in the repo settings)
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+
+ # If the commit is tagged with a version (e.g. "v1.0.0"),
+ # release the app after building.
+ release: ${{ startsWith(github.ref, 'refs/tags/v') }}
+
+ mac_certs: ${{ secrets.MAC_CERTS }}
+ mac_certs_password: ${{ secrets.MAC_CERTS_PASSWORD }}
+ env:
+ # macOS notarization credentials key details
+ APPLE_ID: ${{ secrets.APPLE_ID }}
+ APPLE_APP_SPECIFIC_PASSWORD:
+ ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
+ APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
+ USE_HARD_LINKS: false
diff --git a/desktop/electron-builder.yml b/desktop/electron-builder.yml
index f62033fb9..298b1c5f3 100644
--- a/desktop/electron-builder.yml
+++ b/desktop/electron-builder.yml
@@ -29,5 +29,4 @@ mac:
arch: [universal]
category: public.app-category.photography
hardenedRuntime: true
- notarize: true
afterSign: electron-builder-notarize
diff --git a/desktop/src/main.ts b/desktop/src/main.ts
index 49b316206..9cba9178d 100644
--- a/desktop/src/main.ts
+++ b/desktop/src/main.ts
@@ -142,7 +142,7 @@ const createMainWindow = () => {
// Create the main window. This'll show our web content.
const window = new BrowserWindow({
webPreferences: {
- preload: path.join(app.getAppPath(), "preload.js"),
+ preload: path.join(__dirname, "preload.js"),
sandbox: true,
},
// The color to show in the window until the web content gets loaded.
@@ -287,13 +287,29 @@ const setupTrayItem = (mainWindow: BrowserWindow) => {
/**
* 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.
+ * process. This has been removed in favor of cache on the web layer.
*
- * 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
- * versions.
+ * Delete the old cache dir if it exists.
+ *
+ * This will happen in two phases. The cache had three subdirectories:
+ *
+ * - Two of them, "thumbs" and "files", will be removed now (v1.7.0, May 2024).
+ *
+ * - The third one, "face-crops" will be removed once we finish the face search
+ * changes. See: [Note: Legacy face crops].
+ *
+ * This migration code can be removed after some time once most people have
+ * upgraded to newer versions.
*/
const deleteLegacyDiskCacheDirIfExists = async () => {
+ const removeIfExists = async (dirPath: string) => {
+ if (existsSync(dirPath)) {
+ log.info(`Removing legacy disk cache from ${dirPath}`);
+ await fs.rm(dirPath, { recursive: true });
+ }
+ };
+ // [Note: Getting the cache path]
+ //
// The existing code was passing "cache" as a parameter to getPath.
//
// However, "cache" is not a valid parameter to getPath. It works! (for
@@ -309,8 +325,8 @@ const deleteLegacyDiskCacheDirIfExists = async () => {
// @ts-expect-error "cache" works but is not part of the public API.
const cacheDir = path.join(app.getPath("cache"), "ente");
if (existsSync(cacheDir)) {
- log.info(`Removing legacy disk cache from ${cacheDir}`);
- await fs.rm(cacheDir, { recursive: true });
+ await removeIfExists(path.join(cacheDir, "thumbs"));
+ await removeIfExists(path.join(cacheDir, "files"));
}
};
@@ -375,7 +391,7 @@ const main = () => {
// Continue on with the rest of the startup sequence.
Menu.setApplicationMenu(await createApplicationMenu(mainWindow));
setupTrayItem(mainWindow);
- if (!isDev) setupAutoUpdater(mainWindow);
+ setupAutoUpdater(mainWindow);
try {
await deleteLegacyDiskCacheDirIfExists();
diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts
index f59969202..1393f4bfd 100644
--- a/desktop/src/main/ipc.ts
+++ b/desktop/src/main/ipc.ts
@@ -24,6 +24,7 @@ import {
updateOnNextRestart,
} from "./services/app-update";
import {
+ legacyFaceCrop,
openDirectory,
openLogDirectory,
selectDirectory,
@@ -198,6 +199,10 @@ export const attachIPCHandlers = () => {
faceEmbedding(input),
);
+ ipcMain.handle("legacyFaceCrop", (_, faceID: string) =>
+ legacyFaceCrop(faceID),
+ );
+
// - Upload
ipcMain.handle("listZipItems", (_, zipPath: string) =>
diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts
index b6fa7acfe..45cbd6362 100644
--- a/desktop/src/main/menu.ts
+++ b/desktop/src/main/menu.ts
@@ -10,7 +10,6 @@ import { forceCheckForAppUpdates } from "./services/app-update";
import autoLauncher from "./services/auto-launcher";
import { openLogDirectory } from "./services/dir";
import { userPreferences } from "./stores/user-preferences";
-import { isDev } from "./utils/electron";
/** Create and return the entries in the app's main menu bar */
export const createApplicationMenu = async (mainWindow: BrowserWindow) => {
@@ -24,9 +23,6 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => {
const macOSOnly = (options: MenuItemConstructorOptions[]) =>
process.platform == "darwin" ? options : [];
- const devOnly = (options: MenuItemConstructorOptions[]) =>
- isDev ? options : [];
-
const handleCheckForUpdates = () => forceCheckForAppUpdates(mainWindow);
const handleViewChangelog = () =>
@@ -130,11 +126,11 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => {
submenu: [
{
role: "startSpeaking",
- label: "start speaking",
+ label: "Start Speaking",
},
{
role: "stopSpeaking",
- label: "stop speaking",
+ label: "Stop Speaking",
},
],
},
@@ -145,9 +141,7 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => {
label: "View",
submenu: [
{ label: "Reload", role: "reload" },
- ...devOnly([
- { label: "Toggle Dev Tools", role: "toggleDevTools" },
- ]),
+ { label: "Toggle Dev Tools", role: "toggleDevTools" },
{ type: "separator" },
{ label: "Toggle Full Screen", role: "togglefullscreen" },
],
diff --git a/desktop/src/main/services/app-update.ts b/desktop/src/main/services/app-update.ts
index 8d66cb8c3..5788b9b27 100644
--- a/desktop/src/main/services/app-update.ts
+++ b/desktop/src/main/services/app-update.ts
@@ -6,11 +6,20 @@ import { allowWindowClose } from "../../main";
import { AppUpdate } from "../../types/ipc";
import log from "../log";
import { userPreferences } from "../stores/user-preferences";
+import { isDev } from "../utils/electron";
export const setupAutoUpdater = (mainWindow: BrowserWindow) => {
autoUpdater.logger = electronLog;
autoUpdater.autoDownload = false;
+ // Skip checking for updates automatically in dev builds. Installing an
+ // update would fail anyway since (at least on macOS), the auto update
+ // process requires signed builds.
+ //
+ // Even though this is skipped on app start, we can still use the "Check for
+ // updates..." menu option to trigger the update if we wish in dev builds.
+ if (isDev) return;
+
const oneDay = 1 * 24 * 60 * 60 * 1000;
setInterval(() => void checkForUpdatesAndNotify(mainWindow), oneDay);
void checkForUpdatesAndNotify(mainWindow);
diff --git a/desktop/src/main/services/auto-launcher.ts b/desktop/src/main/services/auto-launcher.ts
index 4e97a0225..0942a4935 100644
--- a/desktop/src/main/services/auto-launcher.ts
+++ b/desktop/src/main/services/auto-launcher.ts
@@ -27,14 +27,14 @@ class AutoLauncher {
}
async toggleAutoLaunch() {
- const isEnabled = await this.isEnabled();
+ const wasEnabled = await this.isEnabled();
const autoLaunch = this.autoLaunch;
if (autoLaunch) {
- if (isEnabled) await autoLaunch.disable();
+ if (wasEnabled) await autoLaunch.disable();
else await autoLaunch.enable();
} else {
- if (isEnabled) app.setLoginItemSettings({ openAtLogin: false });
- else app.setLoginItemSettings({ openAtLogin: true });
+ const openAtLogin = !wasEnabled;
+ app.setLoginItemSettings({ openAtLogin });
}
}
@@ -42,8 +42,7 @@ class AutoLauncher {
if (this.autoLaunch) {
return app.commandLine.hasSwitch("hidden");
} else {
- // TODO(MR): This apparently doesn't work anymore.
- return app.getLoginItemSettings().wasOpenedAtLogin;
+ return app.getLoginItemSettings().openAtLogin;
}
}
}
diff --git a/desktop/src/main/services/dir.ts b/desktop/src/main/services/dir.ts
index d375648f6..293a720f0 100644
--- a/desktop/src/main/services/dir.ts
+++ b/desktop/src/main/services/dir.ts
@@ -1,5 +1,7 @@
import { shell } from "electron/common";
import { app, dialog } from "electron/main";
+import { existsSync } from "fs";
+import fs from "node:fs/promises";
import path from "node:path";
import { posixPath } from "../utils/electron";
@@ -38,14 +40,50 @@ export const openLogDirectory = () => openDirectory(logDirectoryPath());
*
* [Note: Electron app paths]
*
- * By default, these paths are at the following locations:
+ * There are three paths we need to be aware of usually.
*
- * - macOS: `~/Library/Application Support/ente`
+ * First is the "appData". We can obtain this with `app.getPath("appData")`.
+ * This is per-user application data directory. This is usually the following:
+ *
+ * - Windows: `%APPDATA%`, e.g. `C:\Users\