This commit is contained in:
Manav Rathi 2024-04-26 17:12:35 +05:30
parent 1c59a36c73
commit 9f41539330
No known key found for this signature in database
5 changed files with 78 additions and 63 deletions

View file

@ -8,14 +8,15 @@
*
* https://www.electronjs.org/docs/latest/tutorial/process-model#the-main-process
*/
import { nativeImage } from "electron";
import { app, BrowserWindow, Menu, protocol, Tray } from "electron/main";
import { nativeImage, shell } from "electron/common";
import type { WebContents } from "electron/main";
import { BrowserWindow, Menu, Tray, app, protocol } from "electron/main";
import serveNextAt from "next-electron-server";
import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { handleDownloads, handleExternalLinks } from "./main/init";
import { attachFSWatchIPCHandlers, attachIPCHandlers } from "./main/ipc";
import log, { initLogging } from "./main/log";
import { createApplicationMenu, createTrayContextMenu } from "./main/menu";
@ -30,7 +31,7 @@ import { isDev } from "./main/utils-electron";
/**
* The URL where the renderer HTML is being served from.
*/
export const rendererURL = "ente://app";
const rendererURL = "ente://app";
/**
* We want to hide our window instead of closing it when the user presses the
@ -205,7 +206,7 @@ const createMainWindow = async () => {
// webContents is not responding to input messages for > 30 seconds."
window.webContents.on("unresponsive", () => {
log.error(
"Main window's webContents are unresponsive, will restart the renderer process",
"MainWindow's webContents are unresponsive, will restart the renderer process",
);
window.webContents.forcefullyCrashRenderer();
});
@ -236,6 +237,58 @@ const createMainWindow = async () => {
return window;
};
/**
* Automatically set the save path for user initiated downloads to the system's
* "downloads" directory instead of asking the user to select a save location.
*/
export const setDownloadPath = (webContents: WebContents) => {
webContents.session.on("will-download", (_, item) => {
item.setSavePath(
uniqueSavePath(app.getPath("downloads"), item.getFilename()),
);
});
};
const uniqueSavePath = (dirPath: string, fileName: string) => {
const { name, ext } = path.parse(fileName);
let savePath = path.join(dirPath, fileName);
let n = 1;
while (existsSync(savePath)) {
const suffixedName = [`${name}(${n})`, ext].filter((x) => x).join(".");
savePath = path.join(dirPath, suffixedName);
n++;
}
return savePath;
};
/**
* Allow opening external links, e.g. when the user clicks on the "Feature
* requests" button in the sidebar (to open our GitHub repository), or when they
* click the "Support" button to send an email to support.
*
* @param webContents The renderer to configure.
*/
export const allowExternalLinks = (webContents: WebContents) => {
// By default, if the user were open a link, say
// https://github.com/ente-io/ente/discussions, then it would open a _new_
// BrowserWindow within our app.
//
// This is not the behaviour we want; what we want is to ask the system to
// handle the link (e.g. open the URL in the default browser, or if it is a
// mailto: link, then open the user's mail client).
//
// Returning `action` "deny" accomplishes this.
webContents.setWindowOpenHandler(({ url }) => {
if (!url.startsWith(rendererURL)) {
shell.openExternal(url);
return { action: "deny" };
} else {
return { action: "allow" };
}
});
};
/**
* Add an icon for our app in the system tray.
*
@ -338,23 +391,26 @@ const main = () => {
//
// Note that some Electron APIs can only be used after this event occurs.
app.on("ready", async () => {
// Create window and prepare for renderer
// Create window and prepare for the renderer.
mainWindow = await createMainWindow();
attachIPCHandlers();
attachFSWatchIPCHandlers(createWatcher(mainWindow));
registerStreamProtocol();
handleDownloads(mainWindow);
handleExternalLinks(mainWindow);
// Configure the renderer's environment.
setDownloadPath(mainWindow.webContents);
allowExternalLinks(mainWindow.webContents);
// TODO(MR): Remove or resurrect
// The commit that introduced this header override had the message
// "fix cors issue for uploads". Not sure what that means, so disabling
// it for now to see why exactly this is required.
// addAllowOriginHeader(mainWindow);
// Start loading the renderer
// Start loading the renderer.
mainWindow.loadURL(rendererURL);
// Continue on with the rest of the startup sequence
// Continue on with the rest of the startup sequence.
Menu.setApplicationMenu(await createApplicationMenu(mainWindow));
setupTrayItem(mainWindow);
if (!isDev) setupAutoUpdater(mainWindow);

View file

@ -1,46 +1,4 @@
import { BrowserWindow, app, shell } from "electron";
import { existsSync } from "node:fs";
import path from "node:path";
import { rendererURL } from "../main";
export function handleDownloads(mainWindow: BrowserWindow) {
mainWindow.webContents.session.on("will-download", (_, item) => {
item.setSavePath(
getUniqueSavePath(item.getFilename(), app.getPath("downloads")),
);
});
}
function getUniqueSavePath(filename: string, directory: string): string {
let uniqueFileSavePath = path.join(directory, filename);
const { name: filenameWithoutExtension, ext: extension } =
path.parse(filename);
let n = 0;
while (existsSync(uniqueFileSavePath)) {
n++;
// filter need to remove undefined extension from the array
// else [`${fileName}`, undefined].join(".") will lead to `${fileName}.` as joined string
const fileNameWithNumberedSuffix = [
`${filenameWithoutExtension}(${n})`,
extension,
]
.filter((x) => x) // filters out undefined/null values
.join("");
uniqueFileSavePath = path.join(directory, fileNameWithNumberedSuffix);
}
return uniqueFileSavePath;
}
export function handleExternalLinks(mainWindow: BrowserWindow) {
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
if (!url.startsWith(rendererURL)) {
shell.openExternal(url);
return { action: "deny" };
} else {
return { action: "allow" };
}
});
}
import { BrowserWindow } from "electron";
export function addAllowOriginHeader(mainWindow: BrowserWindow) {
mainWindow.webContents.session.webRequest.onHeadersReceived(

View file

@ -150,7 +150,7 @@ export const clipTextEmbeddingIfAvailable = async (text: string) => {
// Don't wait for the download to complete
if (typeof sessionOrStatus == "string") {
console.log(
log.info(
"Ignoring CLIP text embedding request because model download is pending",
);
return undefined;

View file

@ -57,12 +57,14 @@ const handleRead = async (path: string) => {
try {
const res = await net.fetch(pathToFileURL(path).toString());
if (res.ok) {
// `net.fetch` already seems to add "Content-Type" and
// "Last-Modified" headers. But since we already are stat-ting the
// file for the "Content-Length", we explicitly add the
// "X-Last-Modified-Ms" too, (a) guaranteeing its presence, and (b)
// having it be in the exact format we want (no string <-> date
// conversions) and (c) keeping milliseconds.
// net.fetch already seems to add "Content-Type" and "Last-Modified"
// headers, but I couldn't find documentation for this. In any case,
// since we already are stat-ting the file for the "Content-Length",
// we explicitly add the "X-Last-Modified-Ms" too,
// 1. guaranteeing its presence
// 2. having it be in the exact format we want (no string <-> date
// conversions)
// 3. Retaining milliseconds.
const stat = await fs.stat(path);

View file

@ -93,8 +93,7 @@ const readInitialChunkOfFile = async (file: File) => {
const detectFileTypeFromBuffer = async (buffer: Uint8Array) => {
const result = await FileType.fromBuffer(buffer);
if (!result?.ext || !result?.mime) {
throw Error(`Could not deduce file type from buffer`);
}
if (!result)
throw Error("Could not deduce file type from the file's contents");
return result;
};