Handle nightly builds (or versions without changelogs)

This commit is contained in:
Manav Rathi
2024-06-13 16:00:57 +05:30
parent b2154429f3
commit 2bbf33287b
10 changed files with 102 additions and 88 deletions

View File

@@ -21,7 +21,6 @@ import {
attachFSWatchIPCHandlers,
attachIPCHandlers,
attachLogoutIPCHandler,
attachWindowIPCHandlers,
} from "./main/ipc";
import log, { initLogging } from "./main/log";
import { createApplicationMenu, createTrayContextMenu } from "./main/menu";
@@ -117,7 +116,6 @@ const main = () => {
// Setup IPC and streams.
const watcher = createWatcher(mainWindow);
attachIPCHandlers();
attachWindowIPCHandlers(mainWindow);
attachFSWatchIPCHandlers(watcher);
attachLogoutIPCHandler(watcher);
registerStreamProtocol();

View File

@@ -9,7 +9,7 @@
*/
import type { FSWatcher } from "chokidar";
import { ipcMain, type BrowserWindow } from "electron/main";
import { ipcMain } from "electron/main";
import type {
CollectionMapping,
FolderWatch,
@@ -19,8 +19,6 @@ import type {
import { logToDisk } from "./log";
import {
appVersion,
canShowWhatsNew,
didShowWhatsNew,
skipAppUpdate,
updateAndRestart,
updateOnNextRestart,
@@ -48,7 +46,12 @@ import {
computeCLIPTextEmbeddingIfAvailable,
} from "./services/ml-clip";
import { computeFaceEmbeddings, detectFaces } from "./services/ml-face";
import { encryptionKey, saveEncryptionKey } from "./services/store";
import {
encryptionKey,
lastShownChangelogVersion,
saveEncryptionKey,
setLastShownChangelogVersion,
} from "./services/store";
import {
clearPendingUploads,
listZipItems,
@@ -103,11 +106,19 @@ export const attachIPCHandlers = () => {
ipcMain.handle("selectDirectory", () => selectDirectory());
ipcMain.handle("encryptionKey", () => encryptionKey());
ipcMain.handle("saveEncryptionKey", (_, encryptionKey: string) =>
saveEncryptionKey(encryptionKey),
);
ipcMain.handle("encryptionKey", () => encryptionKey());
ipcMain.handle("lastShownChangelogVersion", () =>
lastShownChangelogVersion(),
);
ipcMain.handle("setLastShownChangelogVersion", (_, version: number) =>
setLastShownChangelogVersion(version),
);
// - App update
@@ -119,8 +130,6 @@ export const attachIPCHandlers = () => {
ipcMain.on("skipAppUpdate", (_, version: string) => skipAppUpdate(version));
ipcMain.on("didShowWhatsNew", () => didShowWhatsNew());
// - FS
ipcMain.handle("fsExists", (_, path: string) => fsExists(path));
@@ -220,19 +229,6 @@ export const attachIPCHandlers = () => {
ipcMain.handle("clearPendingUploads", () => clearPendingUploads());
};
/**
* Sibling of {@link attachIPCHandlers} that attaches handlers that need access
* to the main window for their functioning.
*
* @param mainWindow Our app's main {@link BrowserWindow}. It is usually needed
* by these handler to send messages back to the main window's `webContents`.
*/
export const attachWindowIPCHandlers = (mainWindow: BrowserWindow) => {
// - App update
ipcMain.on("canShowWhatsNew", () => canShowWhatsNew(mainWindow));
};
/**
* Sibling of {@link attachIPCHandlers} that attaches handlers specific to the
* watch folder functionality.

View File

@@ -180,26 +180,3 @@ export const updateOnNextRestart = (version: string) =>
export const skipAppUpdate = (version: string) =>
userPreferences.set("skipAppVersion", version);
/**
* Invoked when the renderer attaches a callback for {@link onShowWhatsNew}.
* This can be taken as a signal that the UI is ready and will be able to show
* the What's New screen if we need to tell it to.
*
* See: [Note: Conditions for showing the "What's new" screen]
*/
export const canShowWhatsNew = (mainWindow: BrowserWindow) => {
const version = userPreferences.get("whatsNewShownVersion");
if (version && compareVersions(app.getVersion(), version) <= 0) {
// We have shown What's New earlier, but it was for a version same as or
// newer than the current version. Nothing to do now.
return;
}
mainWindow.webContents.send("showWhatsNew");
};
/**
* End the sequence started by {@link canShowWhatsNew}.
*/
export const didShowWhatsNew = () =>
userPreferences.set("whatsNewShownVersion", app.getVersion());

View File

@@ -1,12 +1,16 @@
import { safeStorage } from "electron/main";
import { safeStorageStore } from "../stores/safe-storage";
import { uploadStatusStore } from "../stores/upload-status";
import { userPreferences } from "../stores/user-preferences";
import { watchStore } from "../stores/watch";
/**
* Clear all stores except user preferences.
*
* This is useful to reset state when the user logs out.
* This function is useful to reset state when the user logs out. User
* preferences are preserved since they contain things tied to the person using
* the app or other machine specific state not tied to the account they were
* using inside the app.
*/
export const clearStores = () => {
safeStorageStore.clear();
@@ -32,3 +36,9 @@ export const encryptionKey = (): string | undefined => {
const keyBuffer = Buffer.from(b64EncryptedKey, "base64");
return safeStorage.decryptString(keyBuffer);
};
export const lastShownChangelogVersion = (): number | undefined =>
userPreferences.get("lastShownChangelogVersion");
export const setLastShownChangelogVersion = (version: number) =>
userPreferences.set("lastShownChangelogVersion", version);

View File

@@ -10,9 +10,11 @@ interface UserPreferences {
skipAppVersion?: string;
muteUpdateNotificationVersion?: string;
/**
* The app version for which we last showed the What's New screen.
* The changelog version for which we last showed the "What's new" screen.
*
* See: [Note: Conditions for showing "What's new"]
*/
whatsNewShownVersion?: string;
lastShownChangelogVersion?: number;
/**
* The last position and size of our app's window.
*
@@ -37,7 +39,7 @@ const userPreferencesSchema: Schema<UserPreferences> = {
hideDockIcon: { type: "boolean" },
skipAppVersion: { type: "string" },
muteUpdateNotificationVersion: { type: "string" },
whatsNewShownVersion: { type: "string" },
lastShownChangelogVersion: { type: "number" },
windowBounds: {
properties: {
x: { type: "number" },

View File

@@ -72,6 +72,12 @@ const encryptionKey = () => ipcRenderer.invoke("encryptionKey");
const saveEncryptionKey = (encryptionKey: string) =>
ipcRenderer.invoke("saveEncryptionKey", encryptionKey);
const lastShownChangelogVersion = () =>
ipcRenderer.invoke("lastShownChangelogVersion");
const setLastShownChangelogVersion = (version: number) =>
ipcRenderer.invoke("setLastShownChangelogVersion", version);
const onMainWindowFocus = (cb: (() => void) | undefined) => {
ipcRenderer.removeAllListeners("mainWindowFocus");
if (cb) ipcRenderer.on("mainWindowFocus", cb);
@@ -104,18 +110,6 @@ const skipAppUpdate = (version: string) => {
ipcRenderer.send("skipAppUpdate", version);
};
const onShowWhatsNew = (cb: (() => boolean) | undefined) => {
ipcRenderer.removeAllListeners("showWhatsNew");
if (cb) {
ipcRenderer.on("showWhatsNew", () => {
if (cb()) {
ipcRenderer.send("didShowWhatsNew");
}
});
ipcRenderer.send("canShowWhatsNew");
}
};
// - FS
const fsExists = (path: string) => ipcRenderer.invoke("fsExists", path);
@@ -323,6 +317,8 @@ contextBridge.exposeInMainWorld("electron", {
logout,
encryptionKey,
saveEncryptionKey,
lastShownChangelogVersion,
setLastShownChangelogVersion,
onMainWindowFocus,
onOpenURL,
@@ -332,7 +328,6 @@ contextBridge.exposeInMainWorld("electron", {
updateAndRestart,
updateOnNextRestart,
skipAppUpdate,
onShowWhatsNew,
// - FS

View File

@@ -1,4 +1,5 @@
import { WhatsNew } from "@/new/photos/components/WhatsNew";
import { shouldShowWhatsNew } from "@/new/photos/services/changelog";
import { fetchAndSaveFeatureFlagsIfNeeded } from "@/new/photos/services/feature-flags";
import log from "@/next/log";
import { CenteredFlex } from "@ente/shared/components/Container";
@@ -391,17 +392,13 @@ export default function Gallery() {
if (electron) {
// void clipService.setupOnFileUploadListener();
electron.onMainWindowFocus(() => syncWithRemote(false, true));
electron.onShowWhatsNew(() => {
setOpenWhatsNew(true);
return true;
});
if (await shouldShowWhatsNew()) setOpenWhatsNew(true);
}
};
main();
return () => {
clearInterval(syncInterval.current);
if (electron) {
electron.onShowWhatsNew(undefined);
electron.onMainWindowFocus(undefined);
clipService.removeOnFileUploadListener();
}

View File

@@ -12,7 +12,8 @@ import {
} from "@mui/material";
import Slide from "@mui/material/Slide";
import type { TransitionProps } from "@mui/material/transitions";
import React from "react";
import React, { useEffect } from "react";
import { didShowWhatsNew } from "../services/changelog";
interface WhatsNewProps {
/** If `true`, then the dialog is shown. */
@@ -22,12 +23,16 @@ interface WhatsNewProps {
}
/**
* Show a dialog showing a short summary of interesting-for-the-user things in
* this release of the desktop app.
* Show a dialog showing a short summary of interesting-for-the-user things
* since the last time this dialog was shown.
*/
export const WhatsNew: React.FC<WhatsNewProps> = ({ open, onClose }) => {
const fullScreen = useMediaQuery("(max-width: 428px)");
useEffect(() => {
void didShowWhatsNew();
}, []);
return (
<Dialog
{...{ open, fullScreen }}

View File

@@ -0,0 +1,39 @@
import { ensureElectron } from "@/next/electron";
/**
* The current changelog version.
*
* [Note: Conditions for showing "What's new"]
*
* We maintain a "changelog version". This version is an incrementing positive
* integer, we increment it whenever we want to show this dialog again. Usually
* we'd do this for each app update, but not necessarily.
*
* The "What's new" dialog is shown when either we do not have a previously
* saved changelog version, or if the saved changelog version is less than the
* current {@link changelogVersion}.
*
* The shown changelog version is persisted on the Node.js layer since there we
* can store it in the user preferences store, which is not cleared on logout.
*
* On app start, the Node.js layer waits for the {@link onShowWhatsNew} callback
* to get attached. When a callback is attached, it checks the above conditions
* and if they are satisfied, it invokes the callback. The callback should
* return the current {@link changelogVersion} to allow the Node.js layer to
* update the persisted state.
*/
const changelogVersion = 1;
/**
* Return true if we should show the {@link WhatsNew} dialog.
*/
export const shouldShowWhatsNew = async () => {
const electron = globalThis.electron;
if (!electron) return false;
const lastShownVersion = (await electron.lastShownChangelogVersion()) ?? 0;
return lastShownVersion < changelogVersion;
};
export const didShowWhatsNew = async () =>
// We should only have been called if we're in electron.
ensureElectron().setLastShownChangelogVersion(changelogVersion);

View File

@@ -149,26 +149,21 @@ export interface Electron {
skipAppUpdate: (version: string) => void;
/**
* Set or clear the callback {@link cb} to invoke when the app should show
* the "What's new" screen for the current version.
* Get the persisted version for the last shown changelog.
*
* Setting a callback clears any previous callbacks.
*
* [Note: Conditions for showing the "What's new" screen]
*
* This screen is shown only once per update, and only when the current
* version is (sem-versionally) greater than the previous version for which
* this dialog was shown (if any). The state about whether or not this
* dialog has already been shown is persisted on the Node.js, in the user
* preferences store (which is not cleared on logout).
*
* If the Node.js layer notices an attached callback (and the above
* conditions are satisfied), then it invokes the callback. The callback
* should return `true` to indicate that the whats new screen was shown so
* that the Node.js layer can update the persisted state to avoid showing it
* again.
* See: [Note: Conditions for showing "What's new"]
*/
onShowWhatsNew: (cb: (() => boolean) | undefined) => void;
lastShownChangelogVersion: () => Promise<number | undefined>;
/**
* Save the given {@link version} to disk as the version of the last shown
* changelog.
*
* The value is saved to a store which is not cleared during logout.
*
* @see {@link lastShownChangelogVersion}
*/
setLastShownChangelogVersion: (version: number) => Promise<void>;
// - FS