Use the web native cache only - desktop side changes
This commit is contained in:
parent
22e57669fb
commit
3dbf82552d
12 changed files with 97 additions and 263 deletions
|
@ -28,7 +28,6 @@
|
|||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "^4.3.8",
|
||||
"ffmpeg-static": "^5.1.0",
|
||||
"get-folder-size": "^2.0.1",
|
||||
"html-entities": "^2.4.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"next-electron-server": "^1",
|
||||
|
@ -38,7 +37,6 @@
|
|||
"devDependencies": {
|
||||
"@types/auto-launch": "^5.0.2",
|
||||
"@types/ffmpeg-static": "^3.0.1",
|
||||
"@types/get-folder-size": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7",
|
||||
"@typescript-eslint/parser": "^7",
|
||||
"concurrently": "^8",
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
import { ipcRenderer } from "electron/renderer";
|
||||
import { existsSync } from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import path from "path";
|
||||
import { DiskCache } from "../services/diskCache";
|
||||
|
||||
const ENTE_CACHE_DIR_NAME = "ente";
|
||||
|
||||
const getCacheDirectory = async () => {
|
||||
const defaultSystemCacheDir = await ipcRenderer.invoke("get-path", "cache");
|
||||
return path.join(defaultSystemCacheDir, ENTE_CACHE_DIR_NAME);
|
||||
};
|
||||
|
||||
const getCacheBucketDir = async (cacheName: string) => {
|
||||
const cacheDir = await getCacheDirectory();
|
||||
const cacheBucketDir = path.join(cacheDir, cacheName);
|
||||
return cacheBucketDir;
|
||||
};
|
||||
|
||||
export async function openDiskCache(
|
||||
cacheName: string,
|
||||
cacheLimitInBytes?: number,
|
||||
) {
|
||||
const cacheBucketDir = await getCacheBucketDir(cacheName);
|
||||
await fs.mkdir(cacheBucketDir, { recursive: true });
|
||||
return new DiskCache(cacheBucketDir, cacheLimitInBytes);
|
||||
}
|
||||
|
||||
export async function deleteDiskCache(cacheName: string) {
|
||||
const cacheBucketDir = await getCacheBucketDir(cacheName);
|
||||
if (existsSync(cacheBucketDir)) {
|
||||
await fs.rm(cacheBucketDir, { recursive: true, force: true });
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,21 @@
|
|||
import { app, BrowserWindow } from "electron";
|
||||
/**
|
||||
* @file Entry point for the main (Node.js) process of our Electron app.
|
||||
*
|
||||
* The code in this file is invoked by Electron when our app starts -
|
||||
* Conceptually (after all the transpilation etc has happened) this can be
|
||||
* thought of `electron main.ts`. We're running in the context of the so called
|
||||
* "main" process which runs in a Node.js environment.
|
||||
*
|
||||
* https://www.electronjs.org/docs/latest/tutorial/process-model#the-main-process
|
||||
*/
|
||||
import * as 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 { initWatcher } from "./services/chokidar";
|
||||
import { logErrorSentry } from "./services/sentry";
|
||||
import { isDev } from "./utils/common";
|
||||
import { addAllowOriginHeader } from "./utils/cors";
|
||||
import { createWindow } from "./utils/createWindow";
|
||||
|
@ -8,7 +23,6 @@ import { setupAppEventEmitter } from "./utils/events";
|
|||
import setupIpcComs from "./utils/ipcComms";
|
||||
import { setupLogging } from "./utils/logging";
|
||||
import {
|
||||
enableSharedArrayBufferSupport,
|
||||
handleDockIconHideOnAutoLaunch,
|
||||
handleDownloads,
|
||||
handleExternalLinks,
|
||||
|
@ -19,8 +33,6 @@ import {
|
|||
setupTrayItem,
|
||||
} from "./utils/main";
|
||||
|
||||
let mainWindow: BrowserWindow;
|
||||
|
||||
let appIsQuitting = false;
|
||||
|
||||
let updateIsAvailable = false;
|
||||
|
@ -63,15 +75,68 @@ const setupRendererServer = () => {
|
|||
serveNextAt(rendererURL);
|
||||
};
|
||||
|
||||
setupRendererServer();
|
||||
setupLogging(isDev);
|
||||
function enableSharedArrayBufferSupport() {
|
||||
app.commandLine.appendSwitch("enable-features", "SharedArrayBuffer");
|
||||
}
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
app.quit();
|
||||
} else {
|
||||
/**
|
||||
* [Note: Increased disk cache for the desktop app]
|
||||
*
|
||||
* Set the "disk-cache-size" command line flag to ask the Chromium process to
|
||||
* use a larger size for the caches that it keeps on disk. This allows us to use
|
||||
* the same web-native caching mechanism on both the web and the desktop app,
|
||||
* just ask the embedded Chromium to be a bit more generous in disk usage when
|
||||
* running as the desktop app.
|
||||
*
|
||||
* The size we provide is in bytes. We set it to a large value, 5 GB (5 * 1024 *
|
||||
* 1024 * 1024 = 5368709120)
|
||||
* https://www.electronjs.org/docs/latest/api/command-line-switches#--disk-cache-sizesize
|
||||
*
|
||||
* Note that increasing the disk cache size does not guarantee that Chromium
|
||||
* will respect in verbatim, it uses its own heuristics atop this hint.
|
||||
* https://superuser.com/questions/378991/what-is-chrome-default-cache-size-limit/1577693#1577693
|
||||
*/
|
||||
const increaseDiskCache = () => {
|
||||
app.commandLine.appendSwitch("disk-cache-size", "5368709120");
|
||||
};
|
||||
|
||||
/**
|
||||
* 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]).
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
const deleteLegacyDiskCacheDirIfExists = async () => {
|
||||
// The existing code was passing "cache" as a parameter to getPath. This was
|
||||
// incorrect, cache is not a valid value. However, we replicate that
|
||||
// behaviour so that we get back the same path that the old got was getting.
|
||||
// @ts-expect-error
|
||||
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 });
|
||||
}
|
||||
};
|
||||
|
||||
const main = () => {
|
||||
setupLogging(isDev);
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
app.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
let mainWindow: BrowserWindow;
|
||||
|
||||
setupRendererServer();
|
||||
handleDockIconHideOnAutoLaunch();
|
||||
increaseDiskCache();
|
||||
enableSharedArrayBufferSupport();
|
||||
|
||||
app.on("second-instance", () => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (mainWindow) {
|
||||
|
@ -99,7 +164,17 @@ if (!gotTheLock) {
|
|||
handleExternalLinks(mainWindow);
|
||||
addAllowOriginHeader(mainWindow);
|
||||
setupAppEventEmitter(mainWindow);
|
||||
|
||||
try {
|
||||
deleteLegacyDiskCacheDirIfExists();
|
||||
} catch (e) {
|
||||
// Log but otherwise ignore errors during non-critical startup
|
||||
// actions
|
||||
logErrorSentry(e, "Ignoring startup error");
|
||||
}
|
||||
});
|
||||
|
||||
app.on("before-quit", () => setIsAppQuitting(true));
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
|
|
|
@ -32,7 +32,6 @@ import { createWriteStream, existsSync } from "node:fs";
|
|||
import * as fs from "node:fs/promises";
|
||||
import { Readable } from "node:stream";
|
||||
import path from "path";
|
||||
import { deleteDiskCache, openDiskCache } from "./api/cache";
|
||||
import { logToDisk, openLogDirectory } from "./api/common";
|
||||
import { runFFmpegCmd } from "./api/ffmpeg";
|
||||
import { getDirFiles } from "./api/fs";
|
||||
|
@ -446,8 +445,6 @@ contextBridge.exposeInMainWorld("ElectronAPIs", {
|
|||
setToUploadCollection,
|
||||
getEncryptionKey,
|
||||
setEncryptionKey,
|
||||
openDiskCache,
|
||||
deleteDiskCache,
|
||||
getDirFiles,
|
||||
getWatchMappings,
|
||||
addWatchMapping,
|
||||
|
|
|
@ -63,17 +63,9 @@ const TEXT_MODEL_SIZE_IN_BYTES = {
|
|||
onnx: 64173509, // 61.2 MB
|
||||
};
|
||||
|
||||
const MODEL_SAVE_FOLDER = "models";
|
||||
|
||||
function getModelSavePath(modelName: string) {
|
||||
let userDataDir: string;
|
||||
if (isDev) {
|
||||
userDataDir = ".";
|
||||
} else {
|
||||
userDataDir = app.getPath("userData");
|
||||
}
|
||||
return path.join(userDataDir, MODEL_SAVE_FOLDER, modelName);
|
||||
}
|
||||
/** Return the path where the given {@link modelName} is meant to be saved */
|
||||
const getModelSavePath = (modelName: string) =>
|
||||
path.join(app.getPath("userData"), "models", modelName);
|
||||
|
||||
async function downloadModel(saveLocation: string, url: string) {
|
||||
// confirm that the save location exists
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import { existsSync } from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import DiskLRUService from "../services/diskLRU";
|
||||
import { LimitedCache } from "../types/cache";
|
||||
import { getFileStream, writeStream } from "./fs";
|
||||
import { logError } from "./logging";
|
||||
|
||||
const DEFAULT_CACHE_LIMIT = 1000 * 1000 * 1000; // 1GB
|
||||
|
||||
export class DiskCache implements LimitedCache {
|
||||
constructor(
|
||||
private cacheBucketDir: string,
|
||||
private cacheLimit = DEFAULT_CACHE_LIMIT,
|
||||
) {}
|
||||
|
||||
async put(cacheKey: string, response: Response): Promise<void> {
|
||||
const cachePath = path.join(this.cacheBucketDir, cacheKey);
|
||||
await writeStream(cachePath, response.body);
|
||||
DiskLRUService.enforceCacheSizeLimit(
|
||||
this.cacheBucketDir,
|
||||
this.cacheLimit,
|
||||
);
|
||||
}
|
||||
|
||||
async match(
|
||||
cacheKey: string,
|
||||
{ sizeInBytes }: { sizeInBytes?: number } = {},
|
||||
): Promise<Response> {
|
||||
const cachePath = path.join(this.cacheBucketDir, cacheKey);
|
||||
if (existsSync(cachePath)) {
|
||||
const fileStats = await fs.stat(cachePath);
|
||||
if (sizeInBytes && fileStats.size !== sizeInBytes) {
|
||||
logError(
|
||||
Error(),
|
||||
"Cache key exists but size does not match. Deleting cache key.",
|
||||
);
|
||||
fs.unlink(cachePath).catch((e) => {
|
||||
if (e.code === "ENOENT") return;
|
||||
logError(e, "Failed to delete cache key");
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
DiskLRUService.markUse(cachePath);
|
||||
return new Response(await getFileStream(cachePath));
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
async delete(cacheKey: string): Promise<boolean> {
|
||||
const cachePath = path.join(this.cacheBucketDir, cacheKey);
|
||||
if (existsSync(cachePath)) {
|
||||
await fs.unlink(cachePath);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
import getFolderSize from "get-folder-size";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { logError } from "../services/logging";
|
||||
|
||||
export interface LeastRecentlyUsedResult {
|
||||
atime: Date;
|
||||
path: string;
|
||||
}
|
||||
|
||||
class DiskLRUService {
|
||||
private isRunning: Promise<any> = null;
|
||||
private reRun: boolean = false;
|
||||
|
||||
/** Mark "use" of a given file by updating its modified time */
|
||||
async markUse(path: string) {
|
||||
const now = new Date();
|
||||
await fs.utimes(path, now, now);
|
||||
}
|
||||
|
||||
enforceCacheSizeLimit(cacheDir: string, maxSize: number) {
|
||||
if (!this.isRunning) {
|
||||
this.isRunning = this.evictLeastRecentlyUsed(cacheDir, maxSize);
|
||||
this.isRunning.then(() => {
|
||||
this.isRunning = null;
|
||||
if (this.reRun) {
|
||||
this.reRun = false;
|
||||
this.enforceCacheSizeLimit(cacheDir, maxSize);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.reRun = true;
|
||||
}
|
||||
}
|
||||
|
||||
async evictLeastRecentlyUsed(cacheDir: string, maxSize: number) {
|
||||
try {
|
||||
await new Promise((resolve) => {
|
||||
getFolderSize(cacheDir, async (err, size) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
if (size >= maxSize) {
|
||||
const leastRecentlyUsed =
|
||||
await this.findLeastRecentlyUsed(cacheDir);
|
||||
try {
|
||||
await fs.unlink(leastRecentlyUsed.path);
|
||||
} catch (e) {
|
||||
// ENOENT: File not found
|
||||
// which can be ignored as we are trying to delete the file anyway
|
||||
if (e.code !== "ENOENT") {
|
||||
logError(
|
||||
e,
|
||||
"Failed to evict least recently used",
|
||||
);
|
||||
}
|
||||
// ignoring the error, as it would get retried on the next run
|
||||
}
|
||||
this.evictLeastRecentlyUsed(cacheDir, maxSize);
|
||||
}
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
logError(e, "evictLeastRecentlyUsed failed");
|
||||
}
|
||||
}
|
||||
|
||||
private async findLeastRecentlyUsed(
|
||||
dir: string,
|
||||
result?: LeastRecentlyUsedResult,
|
||||
): Promise<LeastRecentlyUsedResult> {
|
||||
result = result || { atime: new Date(), path: "" };
|
||||
|
||||
const files = await fs.readdir(dir);
|
||||
for (const file of files) {
|
||||
const newBase = path.join(dir, file);
|
||||
const st = await fs.stat(newBase);
|
||||
if (st.isDirectory()) {
|
||||
result = await this.findLeastRecentlyUsed(newBase, result);
|
||||
} else {
|
||||
const { atime } = st;
|
||||
if (st.atime.getTime() < result.atime.getTime()) {
|
||||
result = {
|
||||
atime,
|
||||
path: newBase,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default new DiskLRUService();
|
|
@ -1,8 +0,0 @@
|
|||
export interface LimitedCache {
|
||||
match: (
|
||||
key: string,
|
||||
options?: { sizeInBytes?: number },
|
||||
) => Promise<Response>;
|
||||
put: (key: string, data: Response) => Promise<void>;
|
||||
delete: (key: string) => Promise<boolean>;
|
||||
}
|
|
@ -95,7 +95,13 @@ export default function setupIpcComs(
|
|||
clearElectronStore();
|
||||
});
|
||||
|
||||
ipcMain.handle("get-path", (_, message) => {
|
||||
ipcMain.handle("convert-to-jpeg", (_, fileData, filename) => {
|
||||
return convertToJPEG(fileData, filename);
|
||||
});
|
||||
|
||||
ipcMain.handle("open-log-dir", () => {
|
||||
// [Note: Electron app paths]
|
||||
//
|
||||
// By default, these paths are at the following locations:
|
||||
//
|
||||
// * macOS: `~/Library/Application Support/ente`
|
||||
|
@ -104,14 +110,6 @@ export default function setupIpcComs(
|
|||
// * Windows: C:\Users\<you>\AppData\Local\<Your App Name>
|
||||
//
|
||||
// https://www.electronjs.org/docs/latest/api/app
|
||||
return app.getPath(message);
|
||||
});
|
||||
|
||||
ipcMain.handle("convert-to-jpeg", (_, fileData, filename) => {
|
||||
return convertToJPEG(fileData, filename);
|
||||
});
|
||||
|
||||
ipcMain.handle("open-log-dir", () => {
|
||||
shell.openPath(app.getPath("logs"));
|
||||
});
|
||||
|
||||
|
|
|
@ -94,10 +94,6 @@ export async function handleDockIconHideOnAutoLaunch() {
|
|||
}
|
||||
}
|
||||
|
||||
export function enableSharedArrayBufferSupport() {
|
||||
app.commandLine.appendSwitch("enable-features", "SharedArrayBuffer");
|
||||
}
|
||||
|
||||
export function logSystemInfo() {
|
||||
const systemVersion = process.getSystemVersion();
|
||||
const osName = process.platform;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { app } from "electron";
|
||||
import { app } from "electron/main";
|
||||
import { existsSync } from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import path from "path";
|
||||
|
|
|
@ -261,11 +261,6 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/get-folder-size@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/get-folder-size/-/get-folder-size-2.0.0.tgz#acbb5bf5999410c375b2739863a9d2f9483fabf6"
|
||||
integrity sha512-6VKKrDB20E/6ovi2Pfpy9Pcz8Me1ue/tReaZrwrz9mfVdsr6WAMiDZ+F1oAAcss4U5n2k673i1leDIx2aEBDFQ==
|
||||
|
||||
"@types/http-cache-semantics@*":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.2.tgz#abe102d06ccda1efdf0ed98c10ccf7f36a785a41"
|
||||
|
@ -1535,24 +1530,11 @@ function-bind@^1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
gar@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/gar/-/gar-1.0.4.tgz#f777bc7db425c0572fdeb52676172ca1ae9888b8"
|
||||
integrity sha512-w4n9cPWyP7aHxKxYHFQMegj7WIAsL/YX/C4Bs5Rr8s1H9M1rNtRWRsw+ovYMkXDQ5S4ZbYHsHAPmevPjPgw44w==
|
||||
|
||||
get-caller-file@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-folder-size@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-folder-size/-/get-folder-size-2.0.1.tgz#3fe0524dd3bad05257ef1311331417bcd020a497"
|
||||
integrity sha512-+CEb+GDCM7tkOS2wdMKTn9vU7DgnKUTuDlehkNJKNSovdCOVxs14OfKCk4cvSaR3za4gj+OBdl9opPN9xrJ0zA==
|
||||
dependencies:
|
||||
gar "^1.0.4"
|
||||
tiny-each-async "2.0.3"
|
||||
|
||||
get-intrinsic@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598"
|
||||
|
@ -2888,11 +2870,6 @@ text-table@^0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
||||
|
||||
tiny-each-async@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-each-async/-/tiny-each-async-2.0.3.tgz#8ebbbfd6d6295f1370003fbb37162afe5a0a51d1"
|
||||
integrity sha512-5ROII7nElnAirvFn8g7H7MtpfV1daMcyfTGQwsn/x2VtyV+VPiO5CjReCJtWLvoKTDEDmZocf3cNPraiMnBXLA==
|
||||
|
||||
tmp-promise@^3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7"
|
||||
|
|
Loading…
Add table
Reference in a new issue