Compare commits
40 Commits
auth-v2.0.
...
auth-v2.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
542cd31655 | ||
|
|
cdce7d5922 | ||
|
|
a45bf52a4d | ||
|
|
cc5558db5e | ||
|
|
a2dfffd778 | ||
|
|
f37c46935c | ||
|
|
59bda25be2 | ||
|
|
f92a4c2a6e | ||
|
|
92a3650696 | ||
|
|
a1c9ceae6b | ||
|
|
3e3712efb3 | ||
|
|
5339b1aa89 | ||
|
|
0be549c91b | ||
|
|
826cacd6bf | ||
|
|
90a770c619 | ||
|
|
f4f041552f | ||
|
|
730da7648c | ||
|
|
9cface7902 | ||
|
|
a436a6c766 | ||
|
|
ed10e3ec30 | ||
|
|
7d39c0645a | ||
|
|
48f741b792 | ||
|
|
8a115edef8 | ||
|
|
1870c2a468 | ||
|
|
669f428fa3 | ||
|
|
7be4b47e51 | ||
|
|
79250b9efa | ||
|
|
191d19a0fc | ||
|
|
231bc2fc66 | ||
|
|
f65e738507 | ||
|
|
c8089fbb60 | ||
|
|
d10908458e | ||
|
|
15e290a993 | ||
|
|
12fa3be6c5 | ||
|
|
5ae6d7d47b | ||
|
|
47ab361494 | ||
|
|
18e47b3d4e | ||
|
|
35736c447d | ||
|
|
c458b429a0 | ||
|
|
c5bb479c4f |
3
.github/workflows/auth-release.yml
vendored
3
.github/workflows/auth-release.yml
vendored
@@ -85,7 +85,8 @@ jobs:
|
||||
- name: Install dependencies for desktop build
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate appindicator3-0.1 libappindicator3-dev libffi7
|
||||
sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate appindicator3-0.1 libappindicator3-dev libffi-dev libtiff5
|
||||
sudo updatedb --database-root='/usr/lib/x86_64-linux-gnu'
|
||||
|
||||
- name: Install appimagetool
|
||||
run: |
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
DEVELOPMENT_TEAM = 6Z68YJY9Q2;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = auth;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -439,7 +439,7 @@
|
||||
DEVELOPMENT_TEAM = 6Z68YJY9Q2;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = auth;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -513,7 +513,7 @@
|
||||
DEVELOPMENT_TEAM = 6Z68YJY9Q2;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = auth;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -587,7 +587,7 @@
|
||||
DEVELOPMENT_TEAM = 6Z68YJY9Q2;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = auth;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -661,7 +661,7 @@
|
||||
DEVELOPMENT_TEAM = 6Z68YJY9Q2;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = auth;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
||||
@@ -78,14 +78,14 @@
|
||||
"data": "Data",
|
||||
"importCodes": "Import codes",
|
||||
"importTypePlainText": "Plain text",
|
||||
"importTypeEnteEncrypted": "ente Encrypted export",
|
||||
"importTypeEnteEncrypted": "Ente Encrypted export",
|
||||
"passwordForDecryptingExport": "Password to decrypt export",
|
||||
"passwordEmptyError": "Password can not be empty",
|
||||
"importFromApp": "Import codes from {appName}",
|
||||
"importGoogleAuthGuide": "Export your accounts from Google Authenticator to a QR code using the \"Transfer Accounts\" option. Then using another device, scan the QR code.\n\nTip: You can use your laptop's webcam to take a picture of the QR code.",
|
||||
"importSelectJsonFile": "Select JSON file",
|
||||
"importSelectAppExport": "Select {appName} export file",
|
||||
"importEnteEncGuide": "Select the encrypted JSON file exported from ente",
|
||||
"importEnteEncGuide": "Select the encrypted JSON file exported from Ente",
|
||||
"importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.",
|
||||
"importBitwardenGuide": "Use the \"Export vault\" option within Bitwarden Tools and import the unencrypted JSON file.",
|
||||
"importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.",
|
||||
@@ -115,22 +115,22 @@
|
||||
"copied": "Copied",
|
||||
"pleaseTryAgain": "Please try again",
|
||||
"existingUser": "Existing User",
|
||||
"newUser": "New to ente",
|
||||
"newUser": "New to Ente",
|
||||
"delete": "Delete",
|
||||
"enterYourPasswordHint": "Enter your password",
|
||||
"forgotPassword": "Forgot password",
|
||||
"oops": "Oops",
|
||||
"suggestFeatures": "Suggest features",
|
||||
"faq": "FAQ",
|
||||
"faq_q_1": "How secure is ente Auth?",
|
||||
"faq_a_1": "All codes you backup via ente is stored end-to-end encrypted. This means only you can access your codes. Our apps are open source and our cryptography has been externally audited.",
|
||||
"faq_q_1": "How secure is Auth?",
|
||||
"faq_a_1": "All codes you backup via Auth is stored end-to-end encrypted. This means only you can access your codes. Our apps are open source and our cryptography has been externally audited.",
|
||||
"faq_q_2": "Can I access my codes on desktop?",
|
||||
"faq_a_2": "You can access your codes on the web @ auth.ente.io.",
|
||||
"faq_q_3": "How can I delete codes?",
|
||||
"faq_a_3": "You can delete a code by swiping left on that item.",
|
||||
"faq_q_4": "How can I support this project?",
|
||||
"faq_a_4": "You can support the development of this project by subscribing to our Photos app @ ente.io.",
|
||||
"faq_q_5": "How can I enable FaceID lock in ente Auth",
|
||||
"faq_q_5": "How can I enable FaceID lock in Auth",
|
||||
"faq_a_5": "You can enable FaceID lock under Settings → Security → Lockscreen.",
|
||||
"somethingWentWrongMessage": "Something went wrong, please try again",
|
||||
"leaveFamily": "Leave family",
|
||||
@@ -350,7 +350,7 @@
|
||||
"deleteCodeAuthMessage": "Authenticate to delete code",
|
||||
"showQRAuthMessage": "Authenticate to show QR code",
|
||||
"confirmAccountDeleteTitle": "Confirm account deletion",
|
||||
"confirmAccountDeleteMessage": "This account is linked to other ente apps, if you use any.\n\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
|
||||
"confirmAccountDeleteMessage": "This account is linked to other Ente apps, if you use any.\n\nYour uploaded data, across all Ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
|
||||
"androidBiometricHint": "Verify identity",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
|
||||
@@ -24,4 +24,4 @@ startup_notify: false
|
||||
# include:
|
||||
# - libcurl.so.4
|
||||
include:
|
||||
- libffi.so.7
|
||||
- libtiff.so.5
|
||||
|
||||
@@ -9,7 +9,7 @@ url: https://github.com/ente-io/ente
|
||||
|
||||
display_name: Auth
|
||||
|
||||
dependencies:
|
||||
requires:
|
||||
- libsqlite3x
|
||||
- webkit2gtk-4.0
|
||||
- libsodium
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: ente_auth
|
||||
description: ente two-factor authenticator
|
||||
version: 2.0.51+251
|
||||
version: 2.0.53+253
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
||||
@@ -7,11 +7,6 @@ module.exports = {
|
||||
// "plugin:@typescript-eslint/strict-type-checked",
|
||||
// "plugin:@typescript-eslint/stylistic-type-checked",
|
||||
],
|
||||
/* Temporarily disable some rules
|
||||
Enhancement: Remove me */
|
||||
rules: {
|
||||
"no-unused-vars": "off",
|
||||
},
|
||||
/* Temporarily add a global
|
||||
Enhancement: Remove me */
|
||||
globals: {
|
||||
|
||||
@@ -61,15 +61,15 @@ Electron process. This allows us to directly use the output produced by
|
||||
|
||||
### Others
|
||||
|
||||
* [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).
|
||||
- [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.
|
||||
- [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.
|
||||
- [electron-store](https://github.com/sindresorhus/electron-store) is used for
|
||||
persisting user preferences and other arbitrary data.
|
||||
|
||||
## Dev
|
||||
|
||||
@@ -79,12 +79,12 @@ are similar to that in the web code.
|
||||
|
||||
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`.
|
||||
- [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 our `package.json` scripts. This allows us to use the same
|
||||
commands (like `ln`) 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
|
||||
|
||||
@@ -111,11 +111,11 @@ 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.
|
||||
- [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
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { logError } from "../main/log";
|
||||
import { keysStore } from "../stores/keys.store";
|
||||
import { safeStorageStore } from "../stores/safeStorage.store";
|
||||
import { uploadStatusStore } from "../stores/upload.store";
|
||||
import { watchStore } from "../stores/watch.store";
|
||||
|
||||
export const clearElectronStore = () => {
|
||||
try {
|
||||
uploadStatusStore.clear();
|
||||
keysStore.clear();
|
||||
safeStorageStore.clear();
|
||||
watchStore.clear();
|
||||
} catch (e) {
|
||||
logError(e, "error while clearing electron store");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { safeStorage } from "electron/main";
|
||||
import { logError } from "../main/log";
|
||||
import { safeStorageStore } from "../stores/safeStorage.store";
|
||||
|
||||
export async function setEncryptionKey(encryptionKey: string) {
|
||||
try {
|
||||
const encryptedKey: Buffer =
|
||||
await safeStorage.encryptString(encryptionKey);
|
||||
const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64");
|
||||
safeStorageStore.set("encryptionKey", b64EncryptedKey);
|
||||
} catch (e) {
|
||||
logError(e, "setEncryptionKey failed");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEncryptionKey(): Promise<string> {
|
||||
try {
|
||||
const b64EncryptedKey = safeStorageStore.get("encryptionKey");
|
||||
if (b64EncryptedKey) {
|
||||
const keyBuffer = Buffer.from(b64EncryptedKey, "base64");
|
||||
return await safeStorage.decryptString(keyBuffer);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, "getEncryptionKey failed");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { getElectronFile } from "../services/fs";
|
||||
import {
|
||||
getElectronFilesFromGoogleZip,
|
||||
getSavedFilePaths,
|
||||
} from "../services/upload";
|
||||
import { uploadStatusStore } from "../stores/upload.store";
|
||||
import { ElectronFile, FILE_PATH_TYPE } from "../types/ipc";
|
||||
|
||||
export const getPendingUploads = async () => {
|
||||
const filePaths = getSavedFilePaths(FILE_PATH_TYPE.FILES);
|
||||
const zipPaths = getSavedFilePaths(FILE_PATH_TYPE.ZIPS);
|
||||
const collectionName = uploadStatusStore.get("collectionName");
|
||||
|
||||
let files: ElectronFile[] = [];
|
||||
let type: FILE_PATH_TYPE;
|
||||
if (zipPaths.length) {
|
||||
type = FILE_PATH_TYPE.ZIPS;
|
||||
for (const zipPath of zipPaths) {
|
||||
files = [
|
||||
...files,
|
||||
...(await getElectronFilesFromGoogleZip(zipPath)),
|
||||
];
|
||||
}
|
||||
const pendingFilePaths = new Set(filePaths);
|
||||
files = files.filter((file) => pendingFilePaths.has(file.path));
|
||||
} else if (filePaths.length) {
|
||||
type = FILE_PATH_TYPE.FILES;
|
||||
files = await Promise.all(filePaths.map(getElectronFile));
|
||||
}
|
||||
return {
|
||||
files,
|
||||
collectionName,
|
||||
type,
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
getElectronFilesFromGoogleZip,
|
||||
setToUploadCollection,
|
||||
setToUploadFiles,
|
||||
} from "../services/upload";
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* [Note: Custom errors across Electron/Renderer boundary]
|
||||
*
|
||||
* We need to use the `message` field to disambiguate between errors thrown by
|
||||
* the main process when invoked from the renderer process. This is because:
|
||||
*
|
||||
* > Errors thrown throw `handle` in the main process are not transparent as
|
||||
* > they are serialized and only the `message` property from the original error
|
||||
* > is provided to the renderer process.
|
||||
* >
|
||||
* > - https://www.electronjs.org/docs/latest/tutorial/ipc
|
||||
* >
|
||||
* > Ref: https://github.com/electron/electron/issues/24427
|
||||
*/
|
||||
export const CustomErrors = {
|
||||
WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED:
|
||||
"Windows native image processing is not supported",
|
||||
INVALID_OS: (os: string) => `Invalid OS - ${os}`,
|
||||
WAIT_TIME_EXCEEDED: "Wait time exceeded",
|
||||
UNSUPPORTED_PLATFORM: (platform: string, arch: string) =>
|
||||
`Unsupported platform - ${platform} ${arch}`,
|
||||
MODEL_DOWNLOAD_PENDING:
|
||||
"Model download pending, skipping clip search request",
|
||||
INVALID_FILE_PATH: "Invalid file path",
|
||||
INVALID_CLIP_MODEL: (model: string) => `Invalid Clip model - ${model}`,
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import { app, BrowserWindow, Menu } 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 {
|
||||
addAllowOriginHeader,
|
||||
@@ -19,7 +20,6 @@ import {
|
||||
handleDockIconHideOnAutoLaunch,
|
||||
handleDownloads,
|
||||
handleExternalLinks,
|
||||
logStartupBanner,
|
||||
setupMacWindowOnDockIconClick,
|
||||
setupTrayItem,
|
||||
} from "./main/init";
|
||||
@@ -72,6 +72,21 @@ const setupRendererServer = () => {
|
||||
serveNextAt(rendererURL);
|
||||
};
|
||||
|
||||
/**
|
||||
* Log a standard startup banner.
|
||||
*
|
||||
* This helps us identify app starts and other environment details in the logs.
|
||||
*/
|
||||
const logStartupBanner = () => {
|
||||
const version = isDev ? "dev" : app.getVersion();
|
||||
log.info(`Starting ente-photos-desktop ${version}`);
|
||||
|
||||
const platform = process.platform;
|
||||
const osRelease = os.release();
|
||||
const systemVersion = process.getSystemVersion();
|
||||
log.info("Running on", { platform, osRelease, systemVersion });
|
||||
};
|
||||
|
||||
function enableSharedArrayBufferSupport() {
|
||||
app.commandLine.appendSwitch("enable-features", "SharedArrayBuffer");
|
||||
}
|
||||
@@ -126,12 +141,12 @@ const deleteLegacyDiskCacheDirIfExists = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
function setupAppEventEmitter(mainWindow: BrowserWindow) {
|
||||
// fire event when mainWindow is in foreground
|
||||
mainWindow.on("focus", () => {
|
||||
mainWindow.webContents.send("app-in-foreground");
|
||||
});
|
||||
}
|
||||
const attachEventHandlers = (mainWindow: BrowserWindow) => {
|
||||
// Let ipcRenderer know when mainWindow is in the foreground.
|
||||
mainWindow.on("focus", () =>
|
||||
mainWindow.webContents.send("app-in-foreground"),
|
||||
);
|
||||
};
|
||||
|
||||
const main = () => {
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
@@ -144,6 +159,7 @@ const main = () => {
|
||||
|
||||
initLogging();
|
||||
setupRendererServer();
|
||||
logStartupBanner();
|
||||
handleDockIconHideOnAutoLaunch();
|
||||
increaseDiskCache();
|
||||
enableSharedArrayBufferSupport();
|
||||
@@ -163,7 +179,6 @@ const main = () => {
|
||||
//
|
||||
// Note that some Electron APIs can only be used after this event occurs.
|
||||
app.on("ready", async () => {
|
||||
logStartupBanner();
|
||||
mainWindow = await createWindow();
|
||||
const watcher = initWatcher(mainWindow);
|
||||
setupTrayItem(mainWindow);
|
||||
@@ -175,13 +190,13 @@ const main = () => {
|
||||
handleDownloads(mainWindow);
|
||||
handleExternalLinks(mainWindow);
|
||||
addAllowOriginHeader(mainWindow);
|
||||
setupAppEventEmitter(mainWindow);
|
||||
attachEventHandlers(mainWindow);
|
||||
|
||||
try {
|
||||
deleteLegacyDiskCacheDirIfExists();
|
||||
} catch (e) {
|
||||
// Log but otherwise ignore errors during non-critical startup
|
||||
// actions
|
||||
// actions.
|
||||
log.error("Ignoring startup error", e);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { app, BrowserWindow, nativeImage, Tray } from "electron";
|
||||
import { existsSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { isAppQuitting, rendererURL } from "../main";
|
||||
import autoLauncher from "../services/autoLauncher";
|
||||
@@ -77,8 +76,6 @@ export const createWindow = async () => {
|
||||
return mainWindow;
|
||||
};
|
||||
|
||||
export async function handleUpdates(mainWindow: BrowserWindow) {}
|
||||
|
||||
export const setupTrayItem = (mainWindow: BrowserWindow) => {
|
||||
const iconName = isPlatform("mac")
|
||||
? "taskbar-icon-Template.png"
|
||||
@@ -149,16 +146,6 @@ export async function handleDockIconHideOnAutoLaunch() {
|
||||
}
|
||||
}
|
||||
|
||||
export function logStartupBanner() {
|
||||
const version = isDev ? "dev" : app.getVersion();
|
||||
log.info(`Hello from ente-photos-desktop ${version}`);
|
||||
|
||||
const platform = process.platform;
|
||||
const osRelease = os.release();
|
||||
const systemVersion = process.getSystemVersion();
|
||||
log.info("Running on", { platform, osRelease, systemVersion });
|
||||
}
|
||||
|
||||
function lowerCaseHeaders(responseHeaders: Record<string, string[]>) {
|
||||
const headers: Record<string, string[]> = {};
|
||||
for (const key of Object.keys(responseHeaders)) {
|
||||
|
||||
@@ -10,14 +10,6 @@
|
||||
|
||||
import type { FSWatcher } from "chokidar";
|
||||
import { ipcMain } from "electron/main";
|
||||
import { clearElectronStore } from "../api/electronStore";
|
||||
import { getEncryptionKey, setEncryptionKey } from "../api/safeStorage";
|
||||
import {
|
||||
getElectronFilesFromGoogleZip,
|
||||
getPendingUploads,
|
||||
setToUploadCollection,
|
||||
setToUploadFiles,
|
||||
} from "../api/upload";
|
||||
import {
|
||||
appVersion,
|
||||
muteUpdateNotification,
|
||||
@@ -34,6 +26,17 @@ import {
|
||||
convertToJPEG,
|
||||
generateImageThumbnail,
|
||||
} from "../services/imageProcessor";
|
||||
import {
|
||||
clearElectronStore,
|
||||
getEncryptionKey,
|
||||
setEncryptionKey,
|
||||
} from "../services/store";
|
||||
import {
|
||||
getElectronFilesFromGoogleZip,
|
||||
getPendingUploads,
|
||||
setToUploadCollection,
|
||||
setToUploadFiles,
|
||||
} from "../services/upload";
|
||||
import {
|
||||
addWatchMapping,
|
||||
getWatchMappings,
|
||||
@@ -91,16 +94,16 @@ export const attachIPCHandlers = () => {
|
||||
|
||||
// - General
|
||||
|
||||
ipcMain.handle("appVersion", (_) => appVersion());
|
||||
ipcMain.handle("appVersion", () => appVersion());
|
||||
|
||||
ipcMain.handle("openDirectory", (_, dirPath) => openDirectory(dirPath));
|
||||
|
||||
ipcMain.handle("openLogDirectory", (_) => openLogDirectory());
|
||||
ipcMain.handle("openLogDirectory", () => openLogDirectory());
|
||||
|
||||
// See [Note: Catching exception during .send/.on]
|
||||
ipcMain.on("logToDisk", (_, message) => logToDisk(message));
|
||||
|
||||
ipcMain.on("clear-electron-store", (_) => {
|
||||
ipcMain.on("clear-electron-store", () => {
|
||||
clearElectronStore();
|
||||
});
|
||||
|
||||
@@ -108,11 +111,11 @@ export const attachIPCHandlers = () => {
|
||||
setEncryptionKey(encryptionKey),
|
||||
);
|
||||
|
||||
ipcMain.handle("getEncryptionKey", (_) => getEncryptionKey());
|
||||
ipcMain.handle("getEncryptionKey", () => getEncryptionKey());
|
||||
|
||||
// - App update
|
||||
|
||||
ipcMain.on("update-and-restart", (_) => updateAndRestart());
|
||||
ipcMain.on("update-and-restart", () => updateAndRestart());
|
||||
|
||||
ipcMain.on("skip-app-update", (_, version) => skipAppUpdate(version));
|
||||
|
||||
@@ -157,13 +160,13 @@ export const attachIPCHandlers = () => {
|
||||
|
||||
// - File selection
|
||||
|
||||
ipcMain.handle("selectDirectory", (_) => selectDirectory());
|
||||
ipcMain.handle("selectDirectory", () => selectDirectory());
|
||||
|
||||
ipcMain.handle("showUploadFilesDialog", (_) => showUploadFilesDialog());
|
||||
ipcMain.handle("showUploadFilesDialog", () => showUploadFilesDialog());
|
||||
|
||||
ipcMain.handle("showUploadDirsDialog", (_) => showUploadDirsDialog());
|
||||
ipcMain.handle("showUploadDirsDialog", () => showUploadDirsDialog());
|
||||
|
||||
ipcMain.handle("showUploadZipDialog", (_) => showUploadZipDialog());
|
||||
ipcMain.handle("showUploadZipDialog", () => showUploadZipDialog());
|
||||
|
||||
// - FS
|
||||
|
||||
@@ -177,12 +180,12 @@ export const attachIPCHandlers = () => {
|
||||
|
||||
ipcMain.handle(
|
||||
"saveStreamToDisk",
|
||||
(_, path: string, fileStream: ReadableStream<any>) =>
|
||||
(_, path: string, fileStream: ReadableStream) =>
|
||||
saveStreamToDisk(path, fileStream),
|
||||
);
|
||||
|
||||
ipcMain.handle("saveFileToDisk", (_, path: string, file: any) =>
|
||||
saveFileToDisk(path, file),
|
||||
ipcMain.handle("saveFileToDisk", (_, path: string, contents: string) =>
|
||||
saveFileToDisk(path, contents),
|
||||
);
|
||||
|
||||
ipcMain.handle("readTextFile", (_, path: string) => readTextFile(path));
|
||||
@@ -203,7 +206,7 @@ export const attachIPCHandlers = () => {
|
||||
|
||||
// - Upload
|
||||
|
||||
ipcMain.handle("getPendingUploads", (_) => getPendingUploads());
|
||||
ipcMain.handle("getPendingUploads", () => getPendingUploads());
|
||||
|
||||
ipcMain.handle(
|
||||
"setToUploadFiles",
|
||||
@@ -252,7 +255,7 @@ export const attachFSWatchIPCHandlers = (watcher: FSWatcher) => {
|
||||
removeWatchMapping(watcher, folderPath),
|
||||
);
|
||||
|
||||
ipcMain.handle("getWatchMappings", (_) => getWatchMappings());
|
||||
ipcMain.handle("getWatchMappings", () => getWatchMappings());
|
||||
|
||||
ipcMain.handle(
|
||||
"updateWatchMappingSyncedFiles",
|
||||
|
||||
@@ -15,7 +15,7 @@ import { isDev } from "./util";
|
||||
*/
|
||||
export const initLogging = () => {
|
||||
log.transports.file.fileName = "ente.log";
|
||||
log.transports.file.maxSize = 50 * 1024 * 1024; // 50MB;
|
||||
log.transports.file.maxSize = 50 * 1024 * 1024; // 50 MB
|
||||
log.transports.file.format = "[{y}-{m}-{d}T{h}:{i}:{s}{z}] {text}";
|
||||
|
||||
log.transports.console.level = false;
|
||||
@@ -31,25 +31,7 @@ export const logToDisk = (message: string) => {
|
||||
log.info(`[rndr] ${message}`);
|
||||
};
|
||||
|
||||
export const logError = logErrorSentry;
|
||||
|
||||
/** Deprecated, but no alternative yet */
|
||||
export function logErrorSentry(
|
||||
error: any,
|
||||
msg: string,
|
||||
info?: Record<string, unknown>,
|
||||
) {
|
||||
logToDisk(
|
||||
`error: ${error?.name} ${error?.message} ${
|
||||
error?.stack
|
||||
} msg: ${msg} info: ${JSON.stringify(info)}`,
|
||||
);
|
||||
if (isDev) {
|
||||
console.log(error, { msg, info });
|
||||
}
|
||||
}
|
||||
|
||||
const logError1 = (message: string, e?: unknown) => {
|
||||
const logError = (message: string, e?: unknown) => {
|
||||
if (!e) {
|
||||
logError_(message);
|
||||
return;
|
||||
@@ -82,7 +64,7 @@ const logInfo = (...params: any[]) => {
|
||||
};
|
||||
|
||||
const logDebug = (param: () => any) => {
|
||||
if (isDev) console.log(`[debug] ${util.inspect(param())}`);
|
||||
if (isDev) console.log(`[main] [debug] ${util.inspect(param())}`);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -98,12 +80,13 @@ export default {
|
||||
* Log an error message with an optional associated error object.
|
||||
*
|
||||
* {@link e} is generally expected to be an `instanceof Error` but it can be
|
||||
* any arbitrary object that we obtain, say, when in a try-catch handler.
|
||||
* any arbitrary object that we obtain, say, when in a try-catch handler (in
|
||||
* JavaScript any arbitrary value can be thrown).
|
||||
*
|
||||
* The log is written to disk. In development builds, the log is also
|
||||
* printed to the (Node.js process') console.
|
||||
* printed to the main (Node.js) process console.
|
||||
*/
|
||||
error: logError1,
|
||||
error: logError,
|
||||
/**
|
||||
* Log a message.
|
||||
*
|
||||
@@ -111,7 +94,7 @@ export default {
|
||||
* arbitrary number of arbitrary parameters that it then serializes.
|
||||
*
|
||||
* The log is written to disk. In development builds, the log is also
|
||||
* printed to the (Node.js process') console.
|
||||
* printed to the main (Node.js) process console.
|
||||
*/
|
||||
info: logInfo,
|
||||
/**
|
||||
@@ -121,11 +104,11 @@ export default {
|
||||
* function to call to get the log message instead of directly taking the
|
||||
* message. The provided function will only be called in development builds.
|
||||
*
|
||||
* The function can return an arbitrary value which is serialied before
|
||||
* The function can return an arbitrary value which is serialized before
|
||||
* being logged.
|
||||
*
|
||||
* This log is not written to disk. It is printed to the (Node.js process')
|
||||
* console only on development builds.
|
||||
* This log is NOT written to disk. And it is printed to the main (Node.js)
|
||||
* process console, but only on development builds.
|
||||
*/
|
||||
debug: logDebug,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
/**
|
||||
* @file The preload script
|
||||
*
|
||||
@@ -31,9 +32,9 @@
|
||||
* 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
|
||||
* - [renderer] web/packages/next/types/electron.ts contains docs
|
||||
* - [preload] desktop/src/preload.ts ↕︎
|
||||
* - [main] desktop/src/main/ipc.ts contains impl
|
||||
*/
|
||||
|
||||
import { contextBridge, ipcRenderer } from "electron/renderer";
|
||||
@@ -53,7 +54,7 @@ import type {
|
||||
const appVersion = (): Promise<string> => ipcRenderer.invoke("appVersion");
|
||||
|
||||
const openDirectory = (dirPath: string): Promise<void> =>
|
||||
ipcRenderer.invoke("openDirectory");
|
||||
ipcRenderer.invoke("openDirectory", dirPath);
|
||||
|
||||
const openLogDirectory = (): Promise<void> =>
|
||||
ipcRenderer.invoke("openLogDirectory");
|
||||
@@ -68,9 +69,7 @@ const fsExists = (path: string): Promise<boolean> =>
|
||||
|
||||
const registerForegroundEventListener = (onForeground: () => void) => {
|
||||
ipcRenderer.removeAllListeners("app-in-foreground");
|
||||
ipcRenderer.on("app-in-foreground", () => {
|
||||
onForeground();
|
||||
});
|
||||
ipcRenderer.on("app-in-foreground", onForeground);
|
||||
};
|
||||
|
||||
const clearElectronStore = () => {
|
||||
@@ -228,11 +227,11 @@ const checkExistsAndCreateDir = (dirPath: string): Promise<void> =>
|
||||
|
||||
const saveStreamToDisk = (
|
||||
path: string,
|
||||
fileStream: ReadableStream<any>,
|
||||
fileStream: ReadableStream,
|
||||
): Promise<void> => ipcRenderer.invoke("saveStreamToDisk", path, fileStream);
|
||||
|
||||
const saveFileToDisk = (path: string, file: any): Promise<void> =>
|
||||
ipcRenderer.invoke("saveFileToDisk", path, file);
|
||||
const saveFileToDisk = (path: string, contents: string): Promise<void> =>
|
||||
ipcRenderer.invoke("saveFileToDisk", path, contents);
|
||||
|
||||
const readTextFile = (path: string): Promise<string> =>
|
||||
ipcRenderer.invoke("readTextFile", path);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { compareVersions } from "compare-versions";
|
||||
import { app, BrowserWindow } from "electron";
|
||||
import { default as ElectronLog, default as log } from "electron-log";
|
||||
import { default as electronLog } from "electron-log";
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import { setIsAppQuitting, setIsUpdateAvailable } from "../main";
|
||||
import { logErrorSentry } from "../main/log";
|
||||
import log from "../main/log";
|
||||
import { AppUpdateInfo } from "../types/ipc";
|
||||
import {
|
||||
clearMuteUpdateNotificationVersion,
|
||||
@@ -18,7 +18,7 @@ const FIVE_MIN_IN_MICROSECOND = 5 * 60 * 1000;
|
||||
const ONE_DAY_IN_MICROSECOND = 1 * 24 * 60 * 60 * 1000;
|
||||
|
||||
export function setupAutoUpdater(mainWindow: BrowserWindow) {
|
||||
autoUpdater.logger = log;
|
||||
autoUpdater.logger = electronLog;
|
||||
autoUpdater.autoDownload = false;
|
||||
checkForUpdateAndNotify(mainWindow);
|
||||
setInterval(
|
||||
@@ -33,49 +33,36 @@ export function forceCheckForUpdateAndNotify(mainWindow: BrowserWindow) {
|
||||
clearMuteUpdateNotificationVersion();
|
||||
checkForUpdateAndNotify(mainWindow);
|
||||
} catch (e) {
|
||||
logErrorSentry(e, "forceCheckForUpdateAndNotify failed");
|
||||
log.error("forceCheckForUpdateAndNotify failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
|
||||
try {
|
||||
log.debug("checkForUpdateAndNotify called");
|
||||
const updateCheckResult = await autoUpdater.checkForUpdates();
|
||||
log.debug("update version", updateCheckResult.updateInfo.version);
|
||||
if (
|
||||
compareVersions(
|
||||
updateCheckResult.updateInfo.version,
|
||||
app.getVersion(),
|
||||
) <= 0
|
||||
) {
|
||||
log.debug("already at latest version");
|
||||
log.debug(() => "checkForUpdateAndNotify");
|
||||
const { updateInfo } = await autoUpdater.checkForUpdates();
|
||||
log.debug(() => `Update version ${updateInfo.version}`);
|
||||
if (compareVersions(updateInfo.version, app.getVersion()) <= 0) {
|
||||
log.debug(() => "Skipping update, already at latest version");
|
||||
return;
|
||||
}
|
||||
const skipAppVersion = getSkipAppVersion();
|
||||
if (
|
||||
skipAppVersion &&
|
||||
updateCheckResult.updateInfo.version === skipAppVersion
|
||||
) {
|
||||
log.info(
|
||||
"user chose to skip version ",
|
||||
updateCheckResult.updateInfo.version,
|
||||
);
|
||||
if (skipAppVersion && updateInfo.version === skipAppVersion) {
|
||||
log.info(`User chose to skip version ${updateInfo.version}`);
|
||||
return;
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout;
|
||||
log.debug("attempting auto update");
|
||||
log.debug(() => "Attempting auto update");
|
||||
autoUpdater.downloadUpdate();
|
||||
const muteUpdateNotificationVersion =
|
||||
getMuteUpdateNotificationVersion();
|
||||
if (
|
||||
muteUpdateNotificationVersion &&
|
||||
updateCheckResult.updateInfo.version ===
|
||||
muteUpdateNotificationVersion
|
||||
updateInfo.version === muteUpdateNotificationVersion
|
||||
) {
|
||||
log.info(
|
||||
"user chose to mute update notification for version ",
|
||||
updateCheckResult.updateInfo.version,
|
||||
`User has muted update notifications for version ${updateInfo.version}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -84,28 +71,28 @@ async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
|
||||
() =>
|
||||
showUpdateDialog(mainWindow, {
|
||||
autoUpdatable: true,
|
||||
version: updateCheckResult.updateInfo.version,
|
||||
version: updateInfo.version,
|
||||
}),
|
||||
FIVE_MIN_IN_MICROSECOND,
|
||||
);
|
||||
});
|
||||
autoUpdater.on("error", (error) => {
|
||||
clearTimeout(timeout);
|
||||
logErrorSentry(error, "auto update failed");
|
||||
log.error("Auto update failed", error);
|
||||
showUpdateDialog(mainWindow, {
|
||||
autoUpdatable: false,
|
||||
version: updateCheckResult.updateInfo.version,
|
||||
version: updateInfo.version,
|
||||
});
|
||||
});
|
||||
|
||||
setIsUpdateAvailable(true);
|
||||
} catch (e) {
|
||||
logErrorSentry(e, "checkForUpdateAndNotify failed");
|
||||
log.error("checkForUpdateAndNotify failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateAndRestart() {
|
||||
ElectronLog.log("user quit the app");
|
||||
log.info("user quit the app");
|
||||
setIsAppQuitting(true);
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import chokidar from "chokidar";
|
||||
import { BrowserWindow } from "electron";
|
||||
import path from "path";
|
||||
import { logError } from "../main/log";
|
||||
import log from "../main/log";
|
||||
import { getWatchMappings } from "../services/watch";
|
||||
import { getElectronFile } from "./fs";
|
||||
|
||||
@@ -38,7 +38,7 @@ export function initWatcher(mainWindow: BrowserWindow) {
|
||||
);
|
||||
})
|
||||
.on("error", (error) => {
|
||||
logError(error, "error while watching files");
|
||||
log.error("Error while watching files", error);
|
||||
});
|
||||
|
||||
return watcher;
|
||||
|
||||
@@ -2,11 +2,10 @@ import { app, net } from "electron/main";
|
||||
import { existsSync } from "fs";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { CustomErrors } from "../constants/errors";
|
||||
import { writeStream } from "../main/fs";
|
||||
import log, { logErrorSentry } from "../main/log";
|
||||
import log from "../main/log";
|
||||
import { execAsync, isDev } from "../main/util";
|
||||
import { Model } from "../types/ipc";
|
||||
import { CustomErrors, Model, isModel } from "../types/ipc";
|
||||
import Tokenizer from "../utils/clip-bpe-ts/mod";
|
||||
import { getPlatform } from "../utils/common/platform";
|
||||
import { generateTempFilePath } from "../utils/temp";
|
||||
@@ -78,7 +77,7 @@ async function downloadModel(saveLocation: string, url: string) {
|
||||
|
||||
let imageModelDownloadInProgress: Promise<void> = null;
|
||||
|
||||
export async function getClipImageModelPath(type: "ggml" | "onnx") {
|
||||
const getClipImageModelPath = async (type: "ggml" | "onnx") => {
|
||||
try {
|
||||
const modelSavePath = getModelSavePath(IMAGE_MODEL_NAME[type]);
|
||||
if (imageModelDownloadInProgress) {
|
||||
@@ -86,7 +85,7 @@ export async function getClipImageModelPath(type: "ggml" | "onnx") {
|
||||
await imageModelDownloadInProgress;
|
||||
} else {
|
||||
if (!existsSync(modelSavePath)) {
|
||||
log.info("clip image model not found, downloading");
|
||||
log.info("CLIP image model not found, downloading");
|
||||
imageModelDownloadInProgress = downloadModel(
|
||||
modelSavePath,
|
||||
IMAGE_MODEL_DOWNLOAD_URL[type],
|
||||
@@ -96,7 +95,7 @@ export async function getClipImageModelPath(type: "ggml" | "onnx") {
|
||||
const localFileSize = (await fs.stat(modelSavePath)).size;
|
||||
if (localFileSize !== IMAGE_MODEL_SIZE_IN_BYTES[type]) {
|
||||
log.info(
|
||||
`clip image model size mismatch, downloading again got: ${localFileSize}`,
|
||||
`CLIP image model size mismatch, downloading again got: ${localFileSize}`,
|
||||
);
|
||||
imageModelDownloadInProgress = downloadModel(
|
||||
modelSavePath,
|
||||
@@ -110,21 +109,22 @@ export async function getClipImageModelPath(type: "ggml" | "onnx") {
|
||||
} finally {
|
||||
imageModelDownloadInProgress = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let textModelDownloadInProgress: boolean = false;
|
||||
|
||||
export async function getClipTextModelPath(type: "ggml" | "onnx") {
|
||||
const getClipTextModelPath = async (type: "ggml" | "onnx") => {
|
||||
const modelSavePath = getModelSavePath(TEXT_MODEL_NAME[type]);
|
||||
if (textModelDownloadInProgress) {
|
||||
throw Error(CustomErrors.MODEL_DOWNLOAD_PENDING);
|
||||
} else {
|
||||
if (!existsSync(modelSavePath)) {
|
||||
log.info("clip text model not found, downloading");
|
||||
log.info("CLIP text model not found, downloading");
|
||||
textModelDownloadInProgress = true;
|
||||
downloadModel(modelSavePath, TEXT_MODEL_DOWNLOAD_URL[type])
|
||||
.catch(() => {
|
||||
// ignore
|
||||
.catch((e) => {
|
||||
// log but otherwise ignore
|
||||
log.error("CLIP text model download failed", e);
|
||||
})
|
||||
.finally(() => {
|
||||
textModelDownloadInProgress = false;
|
||||
@@ -134,12 +134,13 @@ export async function getClipTextModelPath(type: "ggml" | "onnx") {
|
||||
const localFileSize = (await fs.stat(modelSavePath)).size;
|
||||
if (localFileSize !== TEXT_MODEL_SIZE_IN_BYTES[type]) {
|
||||
log.info(
|
||||
`clip text model size mismatch, downloading again got: ${localFileSize}`,
|
||||
`CLIP text model size mismatch, downloading again got: ${localFileSize}`,
|
||||
);
|
||||
textModelDownloadInProgress = true;
|
||||
downloadModel(modelSavePath, TEXT_MODEL_DOWNLOAD_URL[type])
|
||||
.catch(() => {
|
||||
// ignore
|
||||
.catch((e) => {
|
||||
// log but otherwise ignore
|
||||
log.error("CLIP text model download failed", e);
|
||||
})
|
||||
.finally(() => {
|
||||
textModelDownloadInProgress = false;
|
||||
@@ -149,7 +150,7 @@ export async function getClipTextModelPath(type: "ggml" | "onnx") {
|
||||
}
|
||||
}
|
||||
return modelSavePath;
|
||||
}
|
||||
};
|
||||
|
||||
function getGGMLClipPath() {
|
||||
return isDev
|
||||
@@ -198,6 +199,8 @@ export const computeImageEmbedding = async (
|
||||
model: Model,
|
||||
imageData: Uint8Array,
|
||||
): Promise<Float32Array> => {
|
||||
if (!isModel(model)) throw new Error(`Invalid CLIP model ${model}`);
|
||||
|
||||
let tempInputFilePath = null;
|
||||
try {
|
||||
tempInputFilePath = await generateTempFilePath("");
|
||||
@@ -243,180 +246,69 @@ async function computeImageEmbedding_(
|
||||
inputFilePath: string,
|
||||
): Promise<Float32Array> {
|
||||
if (!existsSync(inputFilePath)) {
|
||||
throw Error(CustomErrors.INVALID_FILE_PATH);
|
||||
throw new Error("Invalid file path");
|
||||
}
|
||||
if (model === Model.GGML_CLIP) {
|
||||
return await computeGGMLImageEmbedding(inputFilePath);
|
||||
} else if (model === Model.ONNX_CLIP) {
|
||||
return await computeONNXImageEmbedding(inputFilePath);
|
||||
} else {
|
||||
throw Error(CustomErrors.INVALID_CLIP_MODEL(model));
|
||||
switch (model) {
|
||||
case "ggml-clip":
|
||||
return await computeGGMLImageEmbedding(inputFilePath);
|
||||
case "onnx-clip":
|
||||
return await computeONNXImageEmbedding(inputFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
export async function computeGGMLImageEmbedding(
|
||||
const computeGGMLImageEmbedding = async (
|
||||
inputFilePath: string,
|
||||
): Promise<Float32Array> {
|
||||
try {
|
||||
const clipModelPath = await getClipImageModelPath("ggml");
|
||||
const ggmlclipPath = getGGMLClipPath();
|
||||
const cmd = IMAGE_EMBEDDING_EXTRACT_CMD.map((cmdPart) => {
|
||||
if (cmdPart === GGMLCLIP_PATH_PLACEHOLDER) {
|
||||
return ggmlclipPath;
|
||||
} else if (cmdPart === CLIP_MODEL_PATH_PLACEHOLDER) {
|
||||
return clipModelPath;
|
||||
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
|
||||
return inputFilePath;
|
||||
} else {
|
||||
return cmdPart;
|
||||
}
|
||||
});
|
||||
): Promise<Float32Array> => {
|
||||
const clipModelPath = await getClipImageModelPath("ggml");
|
||||
const ggmlclipPath = getGGMLClipPath();
|
||||
const cmd = IMAGE_EMBEDDING_EXTRACT_CMD.map((cmdPart) => {
|
||||
if (cmdPart === GGMLCLIP_PATH_PLACEHOLDER) {
|
||||
return ggmlclipPath;
|
||||
} else if (cmdPart === CLIP_MODEL_PATH_PLACEHOLDER) {
|
||||
return clipModelPath;
|
||||
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
|
||||
return inputFilePath;
|
||||
} else {
|
||||
return cmdPart;
|
||||
}
|
||||
});
|
||||
|
||||
const { stdout } = await execAsync(cmd);
|
||||
// parse stdout and return embedding
|
||||
// get the last line of stdout
|
||||
const lines = stdout.split("\n");
|
||||
const lastLine = lines[lines.length - 1];
|
||||
const embedding = JSON.parse(lastLine);
|
||||
const embeddingArray = new Float32Array(embedding);
|
||||
return embeddingArray;
|
||||
} catch (err) {
|
||||
log.error("Failed to compute GGML image embedding", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
const { stdout } = await execAsync(cmd);
|
||||
// parse stdout and return embedding
|
||||
// get the last line of stdout
|
||||
const lines = stdout.split("\n");
|
||||
const lastLine = lines[lines.length - 1];
|
||||
const embedding = JSON.parse(lastLine);
|
||||
const embeddingArray = new Float32Array(embedding);
|
||||
return embeddingArray;
|
||||
};
|
||||
|
||||
export async function computeONNXImageEmbedding(
|
||||
const computeONNXImageEmbedding = async (
|
||||
inputFilePath: string,
|
||||
): Promise<Float32Array> {
|
||||
try {
|
||||
const imageSession = await getOnnxImageSession();
|
||||
const t1 = Date.now();
|
||||
const rgbData = await getRGBData(inputFilePath);
|
||||
const feeds = {
|
||||
input: new ort.Tensor("float32", rgbData, [1, 3, 224, 224]),
|
||||
};
|
||||
const t2 = Date.now();
|
||||
const results = await imageSession.run(feeds);
|
||||
log.info(
|
||||
`onnx image embedding time: ${Date.now() - t1} ms (prep:${
|
||||
t2 - t1
|
||||
} ms, extraction: ${Date.now() - t2} ms)`,
|
||||
);
|
||||
const imageEmbedding = results["output"].data; // Float32Array
|
||||
return normalizeEmbedding(imageEmbedding);
|
||||
} catch (err) {
|
||||
log.error("Failed to compute ONNX image embedding", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function computeTextEmbedding(
|
||||
model: Model,
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
try {
|
||||
const embedding = computeTextEmbedding_(model, text);
|
||||
return embedding;
|
||||
} catch (err) {
|
||||
if (isExecError(err)) {
|
||||
const parsedExecError = parseExecError(err);
|
||||
throw Error(parsedExecError);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function computeTextEmbedding_(
|
||||
model: Model,
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
if (model === Model.GGML_CLIP) {
|
||||
return await computeGGMLTextEmbedding(text);
|
||||
} else {
|
||||
return await computeONNXTextEmbedding(text);
|
||||
}
|
||||
}
|
||||
|
||||
export async function computeGGMLTextEmbedding(
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
try {
|
||||
const clipModelPath = await getClipTextModelPath("ggml");
|
||||
const ggmlclipPath = getGGMLClipPath();
|
||||
const cmd = TEXT_EMBEDDING_EXTRACT_CMD.map((cmdPart) => {
|
||||
if (cmdPart === GGMLCLIP_PATH_PLACEHOLDER) {
|
||||
return ggmlclipPath;
|
||||
} else if (cmdPart === CLIP_MODEL_PATH_PLACEHOLDER) {
|
||||
return clipModelPath;
|
||||
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
|
||||
return text;
|
||||
} else {
|
||||
return cmdPart;
|
||||
}
|
||||
});
|
||||
|
||||
const { stdout } = await execAsync(cmd);
|
||||
// parse stdout and return embedding
|
||||
// get the last line of stdout
|
||||
const lines = stdout.split("\n");
|
||||
const lastLine = lines[lines.length - 1];
|
||||
const embedding = JSON.parse(lastLine);
|
||||
const embeddingArray = new Float32Array(embedding);
|
||||
return embeddingArray;
|
||||
} catch (err) {
|
||||
if (err.message === CustomErrors.MODEL_DOWNLOAD_PENDING) {
|
||||
log.info(CustomErrors.MODEL_DOWNLOAD_PENDING);
|
||||
} else {
|
||||
log.error("Failed to compute GGML text embedding", err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function computeONNXTextEmbedding(
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
try {
|
||||
const imageSession = await getOnnxTextSession();
|
||||
const t1 = Date.now();
|
||||
const tokenizer = getTokenizer();
|
||||
const tokenizedText = Int32Array.from(tokenizer.encodeForCLIP(text));
|
||||
const feeds = {
|
||||
input: new ort.Tensor("int32", tokenizedText, [1, 77]),
|
||||
};
|
||||
const t2 = Date.now();
|
||||
const results = await imageSession.run(feeds);
|
||||
log.info(
|
||||
`onnx text embedding time: ${Date.now() - t1} ms (prep:${
|
||||
t2 - t1
|
||||
} ms, extraction: ${Date.now() - t2} ms)`,
|
||||
);
|
||||
const textEmbedding = results["output"].data; // Float32Array
|
||||
return normalizeEmbedding(textEmbedding);
|
||||
} catch (err) {
|
||||
if (err.message === CustomErrors.MODEL_DOWNLOAD_PENDING) {
|
||||
log.info(CustomErrors.MODEL_DOWNLOAD_PENDING);
|
||||
} else {
|
||||
logErrorSentry(err, "Error in computeONNXTextEmbedding");
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
): Promise<Float32Array> => {
|
||||
const imageSession = await getOnnxImageSession();
|
||||
const t1 = Date.now();
|
||||
const rgbData = await getRGBData(inputFilePath);
|
||||
const feeds = {
|
||||
input: new ort.Tensor("float32", rgbData, [1, 3, 224, 224]),
|
||||
};
|
||||
const t2 = Date.now();
|
||||
const results = await imageSession.run(feeds);
|
||||
log.info(
|
||||
`onnx image embedding time: ${Date.now() - t1} ms (prep:${
|
||||
t2 - t1
|
||||
} ms, extraction: ${Date.now() - t2} ms)`,
|
||||
);
|
||||
const imageEmbedding = results["output"].data; // Float32Array
|
||||
return normalizeEmbedding(imageEmbedding);
|
||||
};
|
||||
|
||||
async function getRGBData(inputFilePath: string) {
|
||||
const jpegData = await fs.readFile(inputFilePath);
|
||||
let rawImageData;
|
||||
try {
|
||||
rawImageData = jpeg.decode(jpegData, {
|
||||
useTArray: true,
|
||||
formatAsRGBA: false,
|
||||
});
|
||||
} catch (err) {
|
||||
logErrorSentry(err, "JPEG decode error");
|
||||
throw err;
|
||||
}
|
||||
const rawImageData = jpeg.decode(jpegData, {
|
||||
useTArray: true,
|
||||
formatAsRGBA: false,
|
||||
});
|
||||
|
||||
const nx: number = rawImageData.width;
|
||||
const ny: number = rawImageData.height;
|
||||
@@ -479,21 +371,7 @@ async function getRGBData(inputFilePath: string) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export const computeClipMatchScore = async (
|
||||
imageEmbedding: Float32Array,
|
||||
textEmbedding: Float32Array,
|
||||
) => {
|
||||
if (imageEmbedding.length !== textEmbedding.length) {
|
||||
throw Error("imageEmbedding and textEmbedding length mismatch");
|
||||
}
|
||||
let score = 0;
|
||||
for (let index = 0; index < imageEmbedding.length; index++) {
|
||||
score += imageEmbedding[index] * textEmbedding[index];
|
||||
}
|
||||
return score;
|
||||
};
|
||||
|
||||
export const normalizeEmbedding = (embedding: Float32Array) => {
|
||||
const normalizeEmbedding = (embedding: Float32Array) => {
|
||||
let normalization = 0;
|
||||
for (let index = 0; index < embedding.length; index++) {
|
||||
normalization += embedding[index] * embedding[index];
|
||||
@@ -504,3 +382,82 @@ export const normalizeEmbedding = (embedding: Float32Array) => {
|
||||
}
|
||||
return embedding;
|
||||
};
|
||||
|
||||
export async function computeTextEmbedding(
|
||||
model: Model,
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
if (!isModel(model)) throw new Error(`Invalid CLIP model ${model}`);
|
||||
|
||||
try {
|
||||
const embedding = computeTextEmbedding_(model, text);
|
||||
return embedding;
|
||||
} catch (err) {
|
||||
if (isExecError(err)) {
|
||||
const parsedExecError = parseExecError(err);
|
||||
throw Error(parsedExecError);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function computeTextEmbedding_(
|
||||
model: Model,
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
switch (model) {
|
||||
case "ggml-clip":
|
||||
return await computeGGMLTextEmbedding(text);
|
||||
case "onnx-clip":
|
||||
return await computeONNXTextEmbedding(text);
|
||||
}
|
||||
}
|
||||
|
||||
export async function computeGGMLTextEmbedding(
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
const clipModelPath = await getClipTextModelPath("ggml");
|
||||
const ggmlclipPath = getGGMLClipPath();
|
||||
const cmd = TEXT_EMBEDDING_EXTRACT_CMD.map((cmdPart) => {
|
||||
if (cmdPart === GGMLCLIP_PATH_PLACEHOLDER) {
|
||||
return ggmlclipPath;
|
||||
} else if (cmdPart === CLIP_MODEL_PATH_PLACEHOLDER) {
|
||||
return clipModelPath;
|
||||
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
|
||||
return text;
|
||||
} else {
|
||||
return cmdPart;
|
||||
}
|
||||
});
|
||||
|
||||
const { stdout } = await execAsync(cmd);
|
||||
// parse stdout and return embedding
|
||||
// get the last line of stdout
|
||||
const lines = stdout.split("\n");
|
||||
const lastLine = lines[lines.length - 1];
|
||||
const embedding = JSON.parse(lastLine);
|
||||
const embeddingArray = new Float32Array(embedding);
|
||||
return embeddingArray;
|
||||
}
|
||||
|
||||
export async function computeONNXTextEmbedding(
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
const imageSession = await getOnnxTextSession();
|
||||
const t1 = Date.now();
|
||||
const tokenizer = getTokenizer();
|
||||
const tokenizedText = Int32Array.from(tokenizer.encodeForCLIP(text));
|
||||
const feeds = {
|
||||
input: new ort.Tensor("int32", tokenizedText, [1, 77]),
|
||||
};
|
||||
const t2 = Date.now();
|
||||
const results = await imageSession.run(feeds);
|
||||
log.info(
|
||||
`onnx text embedding time: ${Date.now() - t1} ms (prep:${
|
||||
t2 - t1
|
||||
} ms, extraction: ${Date.now() - t2} ms)`,
|
||||
);
|
||||
const textEmbedding = results["output"].data; // Float32Array
|
||||
return normalizeEmbedding(textEmbedding);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import pathToFfmpeg from "ffmpeg-static";
|
||||
import { existsSync } from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import { CustomErrors } from "../constants/errors";
|
||||
import { writeStream } from "../main/fs";
|
||||
import log from "../main/log";
|
||||
import { execAsync } from "../main/util";
|
||||
@@ -146,7 +145,7 @@ const promiseWithTimeout = async <T>(
|
||||
} = { current: null };
|
||||
const rejectOnTimeout = new Promise<null>((_, reject) => {
|
||||
timeoutRef.current = setTimeout(
|
||||
() => reject(Error(CustomErrors.WAIT_TIME_EXCEEDED)),
|
||||
() => reject(new Error("Operation timed out")),
|
||||
timeout,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import StreamZip from "node-stream-zip";
|
||||
import { existsSync } from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { logError } from "../main/log";
|
||||
import log from "../main/log";
|
||||
import { ElectronFile } from "../types/ipc";
|
||||
|
||||
const FILE_STREAM_CHUNK_SIZE: number = 4 * 1024 * 1024;
|
||||
@@ -115,7 +115,9 @@ export const getZipFileStream = async (
|
||||
const inProgress = {
|
||||
current: false,
|
||||
};
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let resolveObj: (value?: any) => void = null;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let rejectObj: (reason?: any) => void = null;
|
||||
stream.on("readable", () => {
|
||||
try {
|
||||
@@ -179,7 +181,7 @@ export const getZipFileStream = async (
|
||||
controller.close();
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, "readableStream pull failed");
|
||||
log.error("Failed to pull from readableStream", e);
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { existsSync } from "fs";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "path";
|
||||
import { CustomErrors } from "../constants/errors";
|
||||
import { writeStream } from "../main/fs";
|
||||
import { logError, logErrorSentry } from "../main/log";
|
||||
import log from "../main/log";
|
||||
import { execAsync, isDev } from "../main/util";
|
||||
import { ElectronFile } from "../types/ipc";
|
||||
import { CustomErrors, ElectronFile } from "../types/ipc";
|
||||
import { isPlatform } from "../utils/common/platform";
|
||||
import { generateTempFilePath } from "../utils/temp";
|
||||
import { deleteTempFile } from "./ffmpeg";
|
||||
@@ -103,18 +102,21 @@ async function convertToJPEG_(
|
||||
|
||||
return new Uint8Array(await fs.readFile(tempOutputFilePath));
|
||||
} catch (e) {
|
||||
logErrorSentry(e, "failed to convert heic");
|
||||
log.error("Failed to convert HEIC", e);
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
await fs.rm(tempInputFilePath, { force: true });
|
||||
} catch (e) {
|
||||
logErrorSentry(e, "failed to remove tempInputFile");
|
||||
log.error(`Failed to remove tempInputFile ${tempInputFilePath}`, e);
|
||||
}
|
||||
try {
|
||||
await fs.rm(tempOutputFilePath, { force: true });
|
||||
} catch (e) {
|
||||
logErrorSentry(e, "failed to remove tempOutputFile");
|
||||
log.error(
|
||||
`Failed to remove tempOutputFile ${tempOutputFilePath}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,7 +152,7 @@ function constructConvertCommand(
|
||||
},
|
||||
);
|
||||
} else {
|
||||
throw Error(CustomErrors.INVALID_OS(process.platform));
|
||||
throw new Error(`Unsupported OS ${process.platform}`);
|
||||
}
|
||||
return convertCmd;
|
||||
}
|
||||
@@ -187,7 +189,7 @@ export async function generateImageThumbnail(
|
||||
try {
|
||||
await deleteTempFile(inputFilePath);
|
||||
} catch (e) {
|
||||
logError(e, "failed to deleteTempFile");
|
||||
log.error(`Failed to deleteTempFile ${inputFilePath}`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,13 +219,16 @@ async function generateImageThumbnail_(
|
||||
} while (thumbnail.length > maxSize && quality > MIN_QUALITY);
|
||||
return thumbnail;
|
||||
} catch (e) {
|
||||
logErrorSentry(e, "generate image thumbnail failed");
|
||||
log.error("Failed to generate image thumbnail", e);
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
await fs.rm(tempOutputFilePath, { force: true });
|
||||
} catch (e) {
|
||||
logErrorSentry(e, "failed to remove tempOutputFile");
|
||||
log.error(
|
||||
`Failed to remove tempOutputFile ${tempOutputFilePath}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +288,7 @@ function constructThumbnailGenerationCommand(
|
||||
return cmdPart;
|
||||
});
|
||||
} else {
|
||||
throw Error(CustomErrors.INVALID_OS(process.platform));
|
||||
throw new Error(`Unsupported OS ${process.platform}`);
|
||||
}
|
||||
return thumbnailGenerationCmd;
|
||||
}
|
||||
|
||||
26
desktop/src/services/store.ts
Normal file
26
desktop/src/services/store.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { safeStorage } from "electron/main";
|
||||
import { keysStore } from "../stores/keys.store";
|
||||
import { safeStorageStore } from "../stores/safeStorage.store";
|
||||
import { uploadStatusStore } from "../stores/upload.store";
|
||||
import { watchStore } from "../stores/watch.store";
|
||||
|
||||
export const clearElectronStore = () => {
|
||||
uploadStatusStore.clear();
|
||||
keysStore.clear();
|
||||
safeStorageStore.clear();
|
||||
watchStore.clear();
|
||||
};
|
||||
|
||||
export async function setEncryptionKey(encryptionKey: string) {
|
||||
const encryptedKey: Buffer = await safeStorage.encryptString(encryptionKey);
|
||||
const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64");
|
||||
safeStorageStore.set("encryptionKey", b64EncryptedKey);
|
||||
}
|
||||
|
||||
export async function getEncryptionKey(): Promise<string> {
|
||||
const b64EncryptedKey = safeStorageStore.get("encryptionKey");
|
||||
if (b64EncryptedKey) {
|
||||
const keyBuffer = Buffer.from(b64EncryptedKey, "base64");
|
||||
return await safeStorage.decryptString(keyBuffer);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,39 @@
|
||||
import StreamZip from "node-stream-zip";
|
||||
import path from "path";
|
||||
import { getElectronFile } from "../services/fs";
|
||||
import { uploadStatusStore } from "../stores/upload.store";
|
||||
import { ElectronFile, FILE_PATH_TYPE } from "../types/ipc";
|
||||
import { FILE_PATH_KEYS } from "../types/main";
|
||||
import { getValidPaths, getZipFileStream } from "./fs";
|
||||
|
||||
export const getPendingUploads = async () => {
|
||||
const filePaths = getSavedFilePaths(FILE_PATH_TYPE.FILES);
|
||||
const zipPaths = getSavedFilePaths(FILE_PATH_TYPE.ZIPS);
|
||||
const collectionName = uploadStatusStore.get("collectionName");
|
||||
|
||||
let files: ElectronFile[] = [];
|
||||
let type: FILE_PATH_TYPE;
|
||||
if (zipPaths.length) {
|
||||
type = FILE_PATH_TYPE.ZIPS;
|
||||
for (const zipPath of zipPaths) {
|
||||
files = [
|
||||
...files,
|
||||
...(await getElectronFilesFromGoogleZip(zipPath)),
|
||||
];
|
||||
}
|
||||
const pendingFilePaths = new Set(filePaths);
|
||||
files = files.filter((file) => pendingFilePaths.has(file.path));
|
||||
} else if (filePaths.length) {
|
||||
type = FILE_PATH_TYPE.FILES;
|
||||
files = await Promise.all(filePaths.map(getElectronFile));
|
||||
}
|
||||
return {
|
||||
files,
|
||||
collectionName,
|
||||
type,
|
||||
};
|
||||
};
|
||||
|
||||
export const getSavedFilePaths = (type: FILE_PATH_TYPE) => {
|
||||
const paths =
|
||||
getValidPaths(
|
||||
|
||||
1
desktop/src/types/any-shell-escape.d.ts
vendored
1
desktop/src/types/any-shell-escape.d.ts
vendored
@@ -19,6 +19,7 @@
|
||||
* curl -v -H "Location;" -H "User-Agent: FooBar's so-called ""Browser""" "http://www.daveeddy.com/?name=dave&age=24"
|
||||
Which is suitable for being executed by the shell.
|
||||
*/
|
||||
/* eslint-disable no-unused-vars */
|
||||
declare module "any-shell-escape" {
|
||||
declare const shellescape: (args: readonly string | string[]) => string;
|
||||
export default shellescape;
|
||||
|
||||
@@ -4,6 +4,32 @@
|
||||
* This file is manually kept in sync with the renderer code.
|
||||
* See [Note: types.ts <-> preload.ts <-> ipc.ts]
|
||||
*/
|
||||
|
||||
/**
|
||||
* Errors that have special semantics on the web side.
|
||||
*
|
||||
* [Note: Custom errors across Electron/Renderer boundary]
|
||||
*
|
||||
* We need to use the `message` field to disambiguate between errors thrown by
|
||||
* the main process when invoked from the renderer process. This is because:
|
||||
*
|
||||
* > Errors thrown throw `handle` in the main process are not transparent as
|
||||
* > they are serialized and only the `message` property from the original error
|
||||
* > is provided to the renderer process.
|
||||
* >
|
||||
* > - https://www.electronjs.org/docs/latest/tutorial/ipc
|
||||
* >
|
||||
* > Ref: https://github.com/electron/electron/issues/24427
|
||||
*/
|
||||
export const CustomErrors = {
|
||||
WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED:
|
||||
"Windows native image processing is not supported",
|
||||
UNSUPPORTED_PLATFORM: (platform: string, arch: string) =>
|
||||
`Unsupported platform - ${platform} ${arch}`,
|
||||
MODEL_DOWNLOAD_PENDING:
|
||||
"Model download pending, skipping clip search request",
|
||||
};
|
||||
|
||||
/**
|
||||
* Deprecated - Use File + webUtils.getPathForFile instead
|
||||
*
|
||||
@@ -45,6 +71,7 @@ export interface WatchStoreType {
|
||||
}
|
||||
|
||||
export enum FILE_PATH_TYPE {
|
||||
/* eslint-disable no-unused-vars */
|
||||
FILES = "files",
|
||||
ZIPS = "zips",
|
||||
}
|
||||
@@ -54,7 +81,6 @@ export interface AppUpdateInfo {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export enum Model {
|
||||
GGML_CLIP = "ggml-clip",
|
||||
ONNX_CLIP = "onnx-clip",
|
||||
}
|
||||
export type Model = "ggml-clip" | "onnx-clip";
|
||||
|
||||
export const isModel = (s: unknown) => s == "ggml-clip" || s == "onnx-clip";
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface KeysStoreType {
|
||||
};
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
export const FILE_PATH_KEYS: {
|
||||
[k in FILE_PATH_TYPE]: keyof UploadStoreType;
|
||||
} = {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"sendEmail": "Send email",
|
||||
"deleteRequestSLAText": "Your request will be processed within 72 hours.",
|
||||
"deleteEmailRequest": "Please send an email to <warning>account-deletion@ente.io</warning> from your registered email address.",
|
||||
"entePhotosPerm": "ente <i>needs permission to</i> preserve your photos",
|
||||
"entePhotosPerm": "Ente <i>needs permission to</i> preserve your photos",
|
||||
"ok": "Ok",
|
||||
"createAccount": "Create account",
|
||||
"createNewAccount": "Create new account",
|
||||
@@ -225,17 +225,17 @@
|
||||
},
|
||||
"description": "Number of participants in an album, including the album owner."
|
||||
},
|
||||
"collabLinkSectionDescription": "Create a link to allow people to add and view photos in your shared album without needing an ente app or account. Great for collecting event photos.",
|
||||
"collabLinkSectionDescription": "Create a link to allow people to add and view photos in your shared album without needing an Ente app or account. Great for collecting event photos.",
|
||||
"collectPhotos": "Collect photos",
|
||||
"collaborativeLink": "Collaborative link",
|
||||
"shareWithNonenteUsers": "Share with non-ente users",
|
||||
"shareWithNonenteUsers": "Share with non-Ente users",
|
||||
"createPublicLink": "Create public link",
|
||||
"sendLink": "Send link",
|
||||
"copyLink": "Copy link",
|
||||
"linkHasExpired": "Link has expired",
|
||||
"publicLinkEnabled": "Public link enabled",
|
||||
"shareALink": "Share a link",
|
||||
"sharedAlbumSectionDescription": "Create shared and collaborative albums with other ente users, including users on free plans.",
|
||||
"sharedAlbumSectionDescription": "Create shared and collaborative albums with other Ente users, including users on free plans.",
|
||||
"shareWithPeopleSectionTitle": "{numberOfPeople, plural, =0 {Share with specific people} =1 {Shared with 1 person} other {Shared with {numberOfPeople} people}}",
|
||||
"@shareWithPeopleSectionTitle": {
|
||||
"placeholders": {
|
||||
@@ -259,12 +259,12 @@
|
||||
},
|
||||
"verificationId": "Verification ID",
|
||||
"verifyEmailID": "Verify {email}",
|
||||
"emailNoEnteAccount": "{email} does not have an ente account.\n\nSend them an invite to share photos.",
|
||||
"emailNoEnteAccount": "{email} does not have an Ente account.\n\nSend them an invite to share photos.",
|
||||
"shareMyVerificationID": "Here's my verification ID: {verificationID} for ente.io.",
|
||||
"shareTextConfirmOthersVerificationID": "Hey, can you confirm that this is your ente.io verification ID: {verificationID}",
|
||||
"somethingWentWrong": "Something went wrong",
|
||||
"sendInvite": "Send invite",
|
||||
"shareTextRecommendUsingEnte": "Download ente so we can easily share original quality photos and videos\n\nhttps://ente.io",
|
||||
"shareTextRecommendUsingEnte": "Download Ente so we can easily share original quality photos and videos\n\nhttps://ente.io",
|
||||
"done": "Done",
|
||||
"applyCodeTitle": "Apply code",
|
||||
"enterCodeDescription": "Enter the code provided by your friend to claim free storage for both of you",
|
||||
@@ -281,7 +281,7 @@
|
||||
"claimMore": "Claim more!",
|
||||
"theyAlsoGetXGb": "They also get {storageAmountInGB} GB",
|
||||
"freeStorageOnReferralSuccess": "{storageAmountInGB} GB each time someone signs up for a paid plan and applies your code",
|
||||
"shareTextReferralCode": "ente referral code: {referralCode} \n\nApply it in Settings → General → Referrals to get {referralStorageInGB} GB free after you signup for a paid plan\n\nhttps://ente.io",
|
||||
"shareTextReferralCode": "Ente referral code: {referralCode} \n\nApply it in Settings → General → Referrals to get {referralStorageInGB} GB free after you signup for a paid plan\n\nhttps://ente.io",
|
||||
"claimFreeStorage": "Claim free storage",
|
||||
"inviteYourFriends": "Invite your friends",
|
||||
"failedToFetchReferralDetails": "Unable to fetch referral details. Please try again later.",
|
||||
@@ -334,7 +334,7 @@
|
||||
"removeParticipantBody": "{userEmail} will be removed from this shared album\n\nAny photos added by them will also be removed from the album",
|
||||
"keepPhotos": "Keep Photos",
|
||||
"deletePhotos": "Delete photos",
|
||||
"inviteToEnte": "Invite to ente",
|
||||
"inviteToEnte": "Invite to Ente",
|
||||
"removePublicLink": "Remove public link",
|
||||
"disableLinkMessage": "This will remove the public link for accessing \"{albumName}\".",
|
||||
"sharing": "Sharing...",
|
||||
@@ -350,10 +350,10 @@
|
||||
"videoSmallCase": "video",
|
||||
"photoSmallCase": "photo",
|
||||
"singleFileDeleteHighlight": "It will be deleted from all albums.",
|
||||
"singleFileInBothLocalAndRemote": "This {fileType} is in both ente and your device.",
|
||||
"singleFileInRemoteOnly": "This {fileType} will be deleted from ente.",
|
||||
"singleFileInBothLocalAndRemote": "This {fileType} is in both Ente and your device.",
|
||||
"singleFileInRemoteOnly": "This {fileType} will be deleted from Ente.",
|
||||
"singleFileDeleteFromDevice": "This {fileType} will be deleted from your device.",
|
||||
"deleteFromEnte": "Delete from ente",
|
||||
"deleteFromEnte": "Delete from Ente",
|
||||
"yesDelete": "Yes, delete",
|
||||
"movedToTrash": "Moved to trash",
|
||||
"deleteFromDevice": "Delete from device",
|
||||
@@ -445,7 +445,7 @@
|
||||
"backupOverMobileData": "Backup over mobile data",
|
||||
"backupVideos": "Backup videos",
|
||||
"disableAutoLock": "Disable auto lock",
|
||||
"deviceLockExplanation": "Disable the device screen lock when ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster.",
|
||||
"deviceLockExplanation": "Disable the device screen lock when Ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster.",
|
||||
"about": "About",
|
||||
"weAreOpenSource": "We are open source!",
|
||||
"privacy": "Privacy",
|
||||
@@ -465,7 +465,7 @@
|
||||
"authToInitiateAccountDeletion": "Please authenticate to initiate account deletion",
|
||||
"areYouSureYouWantToLogout": "Are you sure you want to logout?",
|
||||
"yesLogout": "Yes, logout",
|
||||
"aNewVersionOfEnteIsAvailable": "A new version of ente is available.",
|
||||
"aNewVersionOfEnteIsAvailable": "A new version of Ente is available.",
|
||||
"update": "Update",
|
||||
"installManually": "Install manually",
|
||||
"criticalUpdateAvailable": "Critical update available",
|
||||
@@ -554,11 +554,11 @@
|
||||
"systemTheme": "System",
|
||||
"freeTrial": "Free trial",
|
||||
"selectYourPlan": "Select your plan",
|
||||
"enteSubscriptionPitch": "ente preserves your memories, so they're always available to you, even if you lose your device.",
|
||||
"enteSubscriptionPitch": "Ente preserves your memories, so they're always available to you, even if you lose your device.",
|
||||
"enteSubscriptionShareWithFamily": "Your family can be added to your plan as well.",
|
||||
"currentUsageIs": "Current usage is ",
|
||||
"@currentUsageIs": {
|
||||
"description": "This text is followed by storage usaged",
|
||||
"description": "This text is followed by storage usage",
|
||||
"examples": [
|
||||
"Current usage is 1.2 GB"
|
||||
],
|
||||
@@ -620,7 +620,7 @@
|
||||
"appleId": "Apple ID",
|
||||
"playstoreSubscription": "PlayStore subscription",
|
||||
"appstoreSubscription": "AppStore subscription",
|
||||
"subAlreadyLinkedErrMessage": "Your {id} is already linked to another ente account.\nIf you would like to use your {id} with this account, please contact our support''",
|
||||
"subAlreadyLinkedErrMessage": "Your {id} is already linked to another Ente account.\nIf you would like to use your {id} with this account, please contact our support''",
|
||||
"visitWebToManage": "Please visit web.ente.io to manage your subscription",
|
||||
"couldNotUpdateSubscription": "Could not update subscription",
|
||||
"pleaseContactSupportAndWeWillBeHappyToHelp": "Please contact support@ente.io and we will be happy to help!",
|
||||
@@ -665,9 +665,9 @@
|
||||
"everywhere": "everywhere",
|
||||
"androidIosWebDesktop": "Android, iOS, Web, Desktop",
|
||||
"mobileWebDesktop": "Mobile, Web, Desktop",
|
||||
"newToEnte": "New to ente",
|
||||
"newToEnte": "New to Ente",
|
||||
"pleaseLoginAgain": "Please login again",
|
||||
"devAccountChanged": "The developer account we use to publish ente on App Store has changed. Because of this, you will need to login again.\n\nOur apologies for the inconvenience, but this was unavoidable.",
|
||||
"devAccountChanged": "The developer account we use to publish Ente on App Store has changed. Because of this, you will need to login again.\n\nOur apologies for the inconvenience, but this was unavoidable.",
|
||||
"yourSubscriptionHasExpired": "Your subscription has expired",
|
||||
"storageLimitExceeded": "Storage limit exceeded",
|
||||
"upgrade": "Upgrade",
|
||||
@@ -678,12 +678,12 @@
|
||||
},
|
||||
"backupFailed": "Backup failed",
|
||||
"couldNotBackUpTryLater": "We could not backup your data.\nWe will retry later.",
|
||||
"enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "ente can encrypt and preserve files only if you grant access to them",
|
||||
"enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "Ente can encrypt and preserve files only if you grant access to them",
|
||||
"pleaseGrantPermissions": "Please grant permissions",
|
||||
"grantPermission": "Grant permission",
|
||||
"privateSharing": "Private sharing",
|
||||
"shareOnlyWithThePeopleYouWant": "Share only with the people you want",
|
||||
"usePublicLinksForPeopleNotOnEnte": "Use public links for people not on ente",
|
||||
"usePublicLinksForPeopleNotOnEnte": "Use public links for people not on Ente",
|
||||
"allowPeopleToAddPhotos": "Allow people to add photos",
|
||||
"shareAnAlbumNow": "Share an album now",
|
||||
"collectEventPhotos": "Collect event photos",
|
||||
@@ -695,7 +695,7 @@
|
||||
},
|
||||
"onDevice": "On device",
|
||||
"@onEnte": {
|
||||
"description": "The text displayed above albums backed up to ente",
|
||||
"description": "The text displayed above albums backed up to Ente",
|
||||
"type": "text"
|
||||
},
|
||||
"onEnte": "On <branding>ente</branding>",
|
||||
@@ -741,7 +741,7 @@
|
||||
"saveCollage": "Save collage",
|
||||
"collageSaved": "Collage saved to gallery",
|
||||
"collageLayout": "Layout",
|
||||
"addToEnte": "Add to ente",
|
||||
"addToEnte": "Add to Ente",
|
||||
"addToAlbum": "Add to album",
|
||||
"delete": "Delete",
|
||||
"hide": "Hide",
|
||||
@@ -806,10 +806,10 @@
|
||||
"photosAddedByYouWillBeRemovedFromTheAlbum": "Photos added by you will be removed from the album",
|
||||
"youveNoFilesInThisAlbumThatCanBeDeleted": "You've no files in this album that can be deleted",
|
||||
"youDontHaveAnyArchivedItems": "You don't have any archived items.",
|
||||
"ignoredFolderUploadReason": "Some files in this album are ignored from upload because they had previously been deleted from ente.",
|
||||
"ignoredFolderUploadReason": "Some files in this album are ignored from upload because they had previously been deleted from Ente.",
|
||||
"resetIgnoredFiles": "Reset ignored files",
|
||||
"deviceFilesAutoUploading": "Files added to this device album will automatically get uploaded to ente.",
|
||||
"turnOnBackupForAutoUpload": "Turn on backup to automatically upload files added to this device folder to ente.",
|
||||
"deviceFilesAutoUploading": "Files added to this device album will automatically get uploaded to Ente.",
|
||||
"turnOnBackupForAutoUpload": "Turn on backup to automatically upload files added to this device folder to Ente.",
|
||||
"noHiddenPhotosOrVideos": "No hidden photos or videos",
|
||||
"toHideAPhotoOrVideo": "To hide a photo or video",
|
||||
"openTheItem": "• Open the item",
|
||||
@@ -886,7 +886,7 @@
|
||||
"@freeUpSpaceSaving": {
|
||||
"description": "Text to tell user how much space they can free up by deleting items from the device"
|
||||
},
|
||||
"freeUpAccessPostDelete": "You can still access {count, plural, one {it} other {them}} on ente as long as you have an active subscription",
|
||||
"freeUpAccessPostDelete": "You can still access {count, plural, one {it} other {them}} on Ente as long as you have an active subscription",
|
||||
"@freeUpAccessPostDelete": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
@@ -937,7 +937,7 @@
|
||||
"renameFile": "Rename file",
|
||||
"enterFileName": "Enter file name",
|
||||
"filesDeleted": "Files deleted",
|
||||
"selectedFilesAreNotOnEnte": "Selected files are not on ente",
|
||||
"selectedFilesAreNotOnEnte": "Selected files are not on Ente",
|
||||
"thisActionCannotBeUndone": "This action cannot be undone",
|
||||
"emptyTrash": "Empty trash?",
|
||||
"permDeleteWarning": "All items in trash will be permanently deleted\n\nThis action cannot be undone",
|
||||
@@ -946,7 +946,7 @@
|
||||
"permanentlyDeleteFromDevice": "Permanently delete from device?",
|
||||
"someOfTheFilesYouAreTryingToDeleteAre": "Some of the files you are trying to delete are only available on your device and cannot be recovered if deleted",
|
||||
"theyWillBeDeletedFromAllAlbums": "They will be deleted from all albums.",
|
||||
"someItemsAreInBothEnteAndYourDevice": "Some items are in both ente and your device.",
|
||||
"someItemsAreInBothEnteAndYourDevice": "Some items are in both Ente and your device.",
|
||||
"selectedItemsWillBeDeletedFromAllAlbumsAndMoved": "Selected items will be deleted from all albums and moved to trash.",
|
||||
"theseItemsWillBeDeletedFromYourDevice": "These items will be deleted from your device.",
|
||||
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.",
|
||||
@@ -1052,7 +1052,7 @@
|
||||
},
|
||||
"setRadius": "Set radius",
|
||||
"familyPlanPortalTitle": "Family",
|
||||
"familyPlanOverview": "Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other's files unless they're shared.\n\nFamily plans are available to customers who have a paid ente subscription.\n\nSubscribe now to get started!",
|
||||
"familyPlanOverview": "Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other's files unless they're shared.\n\nFamily plans are available to customers who have a paid Ente subscription.\n\nSubscribe now to get started!",
|
||||
"androidBiometricHint": "Verify identity",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
@@ -1130,7 +1130,7 @@
|
||||
"noAlbumsSharedByYouYet": "No albums shared by you yet",
|
||||
"sharedWithYou": "Shared with you",
|
||||
"sharedByYou": "Shared by you",
|
||||
"inviteYourFriendsToEnte": "Invite your friends to ente",
|
||||
"inviteYourFriendsToEnte": "Invite your friends to Ente",
|
||||
"failedToDownloadVideo": "Failed to download video",
|
||||
"hiding": "Hiding...",
|
||||
"unhiding": "Unhiding...",
|
||||
@@ -1140,7 +1140,7 @@
|
||||
"addToHiddenAlbum": "Add to hidden album",
|
||||
"moveToHiddenAlbum": "Move to hidden album",
|
||||
"fileTypes": "File types",
|
||||
"deleteConfirmDialogBody": "This account is linked to other ente apps, if you use any. Your uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
|
||||
"deleteConfirmDialogBody": "This account is linked to other Ente apps, if you use any. Your uploaded data, across all Ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
|
||||
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
|
||||
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
|
||||
"viewAddOnButton": "View add-ons",
|
||||
|
||||
@@ -12,7 +12,7 @@ description: ente photos application
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
|
||||
version: 0.8.75+595
|
||||
version: 0.8.76+596
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CustomHead } from "@/next/components/Head";
|
||||
import { setupI18n } from "@/next/i18n";
|
||||
import { logStartupBanner } from "@/next/log-web";
|
||||
import {
|
||||
APPS,
|
||||
APP_TITLES,
|
||||
@@ -16,15 +17,12 @@ import { MessageContainer } from "@ente/shared/components/MessageContainer";
|
||||
import AppNavbar from "@ente/shared/components/Navbar/app";
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import { useLocalState } from "@ente/shared/hooks/useLocalState";
|
||||
import {
|
||||
clearLogsIfLocalStorageLimitExceeded,
|
||||
logStartupMessage,
|
||||
} from "@ente/shared/logging/web";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { LS_KEYS } from "@ente/shared/storage/localStorage";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import { getTheme } from "@ente/shared/themes";
|
||||
import { THEME_COLOR } from "@ente/shared/themes/constants";
|
||||
import { SetTheme } from "@ente/shared/themes/types";
|
||||
import type { User } from "@ente/shared/user/types";
|
||||
import { CssBaseline, useMediaQuery } from "@mui/material";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { t } from "i18next";
|
||||
@@ -67,15 +65,12 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
//setup i18n
|
||||
setupI18n().finally(() => setIsI18nReady(true));
|
||||
// set client package name in headers
|
||||
const userId = (getData(LS_KEYS.USER) as User)?.id;
|
||||
logStartupBanner(APPS.AUTH, userId);
|
||||
HTTPService.setHeaders({
|
||||
"X-Client-Package": CLIENT_PACKAGE_NAMES.get(APPS.AUTH),
|
||||
});
|
||||
// setup logging
|
||||
clearLogsIfLocalStorageLimitExceeded();
|
||||
logStartupMessage(APPS.AUTH);
|
||||
}, []);
|
||||
|
||||
const setUserOnline = () => setOffline(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import log from "@/next/log";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { boxSealOpen, toB64 } from "@ente/shared/crypto/internal/libsodium";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import castGateway from "@ente/shared/network/cast";
|
||||
import LargeType from "components/LargeType";
|
||||
import _sodium from "libsodium-wrappers";
|
||||
@@ -60,7 +60,7 @@ export default function PairingMode() {
|
||||
);
|
||||
context.start(options);
|
||||
} catch (e) {
|
||||
addLogLine(e, "failed to create cast context");
|
||||
log.error("failed to create cast context", e);
|
||||
}
|
||||
setIsCastReady(true);
|
||||
return () => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import ComlinkCryptoWorker from "@ente/shared/crypto";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { getCastFileURL } from "@ente/shared/network/api";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import { EnteFile } from "types/file";
|
||||
import ComlinkCryptoWorker from "utils/comlink/ComlinkCryptoWorker";
|
||||
import { generateStreamFromArrayBuffer } from "utils/file";
|
||||
|
||||
class CastDownloadManager {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { convertBytesToHumanReadable } from "@/next/file";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
|
||||
export async function getUint8ArrayView(file: Blob): Promise<Uint8Array> {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { convertBytesToHumanReadable } from "@/next/file";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import {
|
||||
KNOWN_NON_MEDIA_FORMATS,
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Remote } from "comlink";
|
||||
import { DedicatedCryptoWorker } from "worker/crypto.worker";
|
||||
import { ComlinkWorker } from "./comlinkWorker";
|
||||
|
||||
class ComlinkCryptoWorker {
|
||||
private comlinkWorkerInstance: Promise<Remote<DedicatedCryptoWorker>>;
|
||||
|
||||
async getInstance() {
|
||||
if (!this.comlinkWorkerInstance) {
|
||||
const comlinkWorker = getDedicatedCryptoWorker();
|
||||
this.comlinkWorkerInstance = comlinkWorker.remote;
|
||||
}
|
||||
return this.comlinkWorkerInstance;
|
||||
}
|
||||
}
|
||||
|
||||
export const getDedicatedCryptoWorker = () => {
|
||||
const cryptoComlinkWorker = new ComlinkWorker<typeof DedicatedCryptoWorker>(
|
||||
"ente-crypto-worker",
|
||||
new Worker(new URL("worker/crypto.worker.ts", import.meta.url)),
|
||||
);
|
||||
return cryptoComlinkWorker;
|
||||
};
|
||||
|
||||
export default new ComlinkCryptoWorker();
|
||||
@@ -1,25 +0,0 @@
|
||||
import { addLocalLog } from "@ente/shared/logging";
|
||||
import { Remote, wrap } from "comlink";
|
||||
|
||||
export class ComlinkWorker<T extends new () => InstanceType<T>> {
|
||||
public remote: Promise<Remote<InstanceType<T>>>;
|
||||
private worker: Worker;
|
||||
private name: string;
|
||||
|
||||
constructor(name: string, worker: Worker) {
|
||||
this.name = name;
|
||||
this.worker = worker;
|
||||
|
||||
this.worker.onerror = (errorEvent) => {
|
||||
console.error("Got error event from worker", errorEvent);
|
||||
};
|
||||
addLocalLog(() => `Initiated ${this.name}`);
|
||||
const comlink = wrap<T>(this.worker);
|
||||
this.remote = new comlink() as Promise<Remote<InstanceType<T>>>;
|
||||
}
|
||||
|
||||
public terminate() {
|
||||
this.worker.terminate();
|
||||
addLocalLog(() => `Terminated ${this.name}`);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import ComlinkCryptoWorker from "@ente/shared/crypto";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { FILE_TYPE, RAW_FORMATS } from "constants/file";
|
||||
import CastDownloadManager from "services/castDownloadManager";
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
FileMagicMetadata,
|
||||
FilePublicMagicMetadata,
|
||||
} from "types/file";
|
||||
import ComlinkCryptoWorker from "utils/comlink/ComlinkCryptoWorker";
|
||||
|
||||
export function sortFiles(files: EnteFile[], sortAsc = false) {
|
||||
// sort based on the time of creation time of the file,
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
import * as libsodium from "@ente/shared/crypto/internal/libsodium";
|
||||
import * as Comlink from "comlink";
|
||||
import { StateAddress } from "libsodium-wrappers";
|
||||
|
||||
const textDecoder = new TextDecoder();
|
||||
const textEncoder = new TextEncoder();
|
||||
export class DedicatedCryptoWorker {
|
||||
async decryptMetadata(
|
||||
encryptedMetadata: string,
|
||||
header: string,
|
||||
key: string,
|
||||
) {
|
||||
const encodedMetadata = await libsodium.decryptChaChaOneShot(
|
||||
await libsodium.fromB64(encryptedMetadata),
|
||||
await libsodium.fromB64(header),
|
||||
key,
|
||||
);
|
||||
return JSON.parse(textDecoder.decode(encodedMetadata));
|
||||
}
|
||||
|
||||
async decryptThumbnail(
|
||||
fileData: Uint8Array,
|
||||
header: Uint8Array,
|
||||
key: string,
|
||||
) {
|
||||
return libsodium.decryptChaChaOneShot(fileData, header, key);
|
||||
}
|
||||
|
||||
async decryptEmbedding(
|
||||
encryptedEmbedding: string,
|
||||
header: string,
|
||||
key: string,
|
||||
) {
|
||||
const encodedEmbedding = await libsodium.decryptChaChaOneShot(
|
||||
await libsodium.fromB64(encryptedEmbedding),
|
||||
await libsodium.fromB64(header),
|
||||
key,
|
||||
);
|
||||
return Float32Array.from(
|
||||
JSON.parse(textDecoder.decode(encodedEmbedding)),
|
||||
);
|
||||
}
|
||||
|
||||
async decryptFile(fileData: Uint8Array, header: Uint8Array, key: string) {
|
||||
return libsodium.decryptChaCha(fileData, header, key);
|
||||
}
|
||||
|
||||
async encryptMetadata(metadata: Object, key: string) {
|
||||
const encodedMetadata = textEncoder.encode(JSON.stringify(metadata));
|
||||
|
||||
const { file: encryptedMetadata } =
|
||||
await libsodium.encryptChaChaOneShot(encodedMetadata, key);
|
||||
const { encryptedData, ...other } = encryptedMetadata;
|
||||
return {
|
||||
file: {
|
||||
encryptedData: await libsodium.toB64(encryptedData),
|
||||
...other,
|
||||
},
|
||||
key,
|
||||
};
|
||||
}
|
||||
|
||||
async encryptThumbnail(fileData: Uint8Array, key: string) {
|
||||
return libsodium.encryptChaChaOneShot(fileData, key);
|
||||
}
|
||||
|
||||
async encryptEmbedding(embedding: Float32Array, key: string) {
|
||||
const encodedEmbedding = textEncoder.encode(
|
||||
JSON.stringify(Array.from(embedding)),
|
||||
);
|
||||
const { file: encryptEmbedding } = await libsodium.encryptChaChaOneShot(
|
||||
encodedEmbedding,
|
||||
key,
|
||||
);
|
||||
const { encryptedData, ...other } = encryptEmbedding;
|
||||
return {
|
||||
file: {
|
||||
encryptedData: await libsodium.toB64(encryptedData),
|
||||
...other,
|
||||
},
|
||||
key,
|
||||
};
|
||||
}
|
||||
|
||||
async encryptFile(fileData: Uint8Array) {
|
||||
return libsodium.encryptChaCha(fileData);
|
||||
}
|
||||
|
||||
async encryptFileChunk(
|
||||
data: Uint8Array,
|
||||
pushState: StateAddress,
|
||||
isFinalChunk: boolean,
|
||||
) {
|
||||
return libsodium.encryptFileChunk(data, pushState, isFinalChunk);
|
||||
}
|
||||
|
||||
async initChunkEncryption() {
|
||||
return libsodium.initChunkEncryption();
|
||||
}
|
||||
|
||||
async initChunkDecryption(header: Uint8Array, key: Uint8Array) {
|
||||
return libsodium.initChunkDecryption(header, key);
|
||||
}
|
||||
|
||||
async decryptFileChunk(fileData: Uint8Array, pullState: StateAddress) {
|
||||
return libsodium.decryptFileChunk(fileData, pullState);
|
||||
}
|
||||
|
||||
async initChunkHashing() {
|
||||
return libsodium.initChunkHashing();
|
||||
}
|
||||
|
||||
async hashFileChunk(hashState: StateAddress, chunk: Uint8Array) {
|
||||
return libsodium.hashFileChunk(hashState, chunk);
|
||||
}
|
||||
|
||||
async completeChunkHashing(hashState: StateAddress) {
|
||||
return libsodium.completeChunkHashing(hashState);
|
||||
}
|
||||
|
||||
async deriveKey(
|
||||
passphrase: string,
|
||||
salt: string,
|
||||
opsLimit: number,
|
||||
memLimit: number,
|
||||
) {
|
||||
return libsodium.deriveKey(passphrase, salt, opsLimit, memLimit);
|
||||
}
|
||||
|
||||
async deriveSensitiveKey(passphrase: string, salt: string) {
|
||||
return libsodium.deriveSensitiveKey(passphrase, salt);
|
||||
}
|
||||
|
||||
async deriveInteractiveKey(passphrase: string, salt: string) {
|
||||
return libsodium.deriveInteractiveKey(passphrase, salt);
|
||||
}
|
||||
|
||||
async decryptB64(data: string, nonce: string, key: string) {
|
||||
return libsodium.decryptB64(data, nonce, key);
|
||||
}
|
||||
|
||||
async decryptToUTF8(data: string, nonce: string, key: string) {
|
||||
return libsodium.decryptToUTF8(data, nonce, key);
|
||||
}
|
||||
|
||||
async encryptToB64(data: string, key: string) {
|
||||
return libsodium.encryptToB64(data, key);
|
||||
}
|
||||
|
||||
async generateKeyAndEncryptToB64(data: string) {
|
||||
return libsodium.generateKeyAndEncryptToB64(data);
|
||||
}
|
||||
|
||||
async encryptUTF8(data: string, key: string) {
|
||||
return libsodium.encryptUTF8(data, key);
|
||||
}
|
||||
|
||||
async generateEncryptionKey() {
|
||||
return libsodium.generateEncryptionKey();
|
||||
}
|
||||
|
||||
async generateSaltToDeriveKey() {
|
||||
return libsodium.generateSaltToDeriveKey();
|
||||
}
|
||||
|
||||
async generateKeyPair() {
|
||||
return libsodium.generateKeyPair();
|
||||
}
|
||||
|
||||
async boxSealOpen(input: string, publicKey: string, secretKey: string) {
|
||||
return libsodium.boxSealOpen(input, publicKey, secretKey);
|
||||
}
|
||||
|
||||
async boxSeal(input: string, publicKey: string) {
|
||||
return libsodium.boxSeal(input, publicKey);
|
||||
}
|
||||
|
||||
async generateSubKey(
|
||||
key: string,
|
||||
subKeyLength: number,
|
||||
subKeyID: number,
|
||||
context: string,
|
||||
) {
|
||||
return libsodium.generateSubKey(key, subKeyLength, subKeyID, context);
|
||||
}
|
||||
|
||||
async fromUTF8(string: string) {
|
||||
return libsodium.fromUTF8(string);
|
||||
}
|
||||
async toUTF8(data: string) {
|
||||
return libsodium.toUTF8(data);
|
||||
}
|
||||
|
||||
async toB64(data: Uint8Array) {
|
||||
return libsodium.toB64(data);
|
||||
}
|
||||
|
||||
async toURLSafeB64(data: Uint8Array) {
|
||||
return libsodium.toURLSafeB64(data);
|
||||
}
|
||||
|
||||
async fromB64(string: string) {
|
||||
return libsodium.fromB64(string);
|
||||
}
|
||||
|
||||
async toHex(string: string) {
|
||||
return libsodium.toHex(string);
|
||||
}
|
||||
|
||||
async fromHex(string: string) {
|
||||
return libsodium.fromHex(string);
|
||||
}
|
||||
}
|
||||
|
||||
Comlink.expose(DedicatedCryptoWorker, self);
|
||||
@@ -20,7 +20,6 @@
|
||||
"blazeface-back": "^0.0.9",
|
||||
"bs58": "^5.0.0",
|
||||
"chrono-node": "^2.2.6",
|
||||
"comlink": "^4.3.0",
|
||||
"date-fns": "^2",
|
||||
"debounce": "^2.0.0",
|
||||
"density-clustering": "^1.3.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import log from "@/next/log";
|
||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import EnteButton from "@ente/shared/components/EnteButton";
|
||||
@@ -6,9 +7,7 @@ import SingleInputForm, {
|
||||
SingleInputFormProps,
|
||||
} from "@ente/shared/components/SingleInputForm";
|
||||
import { boxSeal } from "@ente/shared/crypto/internal/libsodium";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import castGateway from "@ente/shared/network/cast";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { Link, Typography } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -105,7 +104,7 @@ export default function AlbumCastDialog(props: Props) {
|
||||
await instance.requestSession();
|
||||
} catch (e) {
|
||||
setView("auto-cast-error");
|
||||
logError(e, "Error requesting session");
|
||||
log.error("Error requesting session", e);
|
||||
return;
|
||||
}
|
||||
const session = instance.getCurrentSession();
|
||||
@@ -124,7 +123,7 @@ export default function AlbumCastDialog(props: Props) {
|
||||
})
|
||||
.catch((e) => {
|
||||
setView("auto-cast-error");
|
||||
logError(e, "Error casting to TV");
|
||||
log.error("Error casting to TV", e);
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -133,10 +132,10 @@ export default function AlbumCastDialog(props: Props) {
|
||||
session
|
||||
.sendMessage("urn:x-cast:pair-request", {})
|
||||
.then(() => {
|
||||
addLogLine("Message sent successfully");
|
||||
log.debug(() => "Message sent successfully");
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(error, "Error sending message");
|
||||
.catch((e) => {
|
||||
log.error("Error sending message", e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import LinkButton from "@ente/shared/components/LinkButton";
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { Tooltip } from "@mui/material";
|
||||
import { styled } from "@mui/material/styles";
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import log from "@/next/log";
|
||||
import {
|
||||
SpaceBetweenFlex,
|
||||
VerticallyCenteredFlex,
|
||||
} from "@ente/shared/components/Container";
|
||||
import DialogTitleWithCloseButton from "@ente/shared/components/DialogBox/TitleWithCloseButton";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -68,7 +67,7 @@ export default function ExportModal(props: Props) {
|
||||
setContinuousExport(exportSettings?.continuousExport ?? false);
|
||||
void syncExportRecord(exportSettings?.folder);
|
||||
} catch (e) {
|
||||
logError(e, "export on mount useEffect failed");
|
||||
log.error("export on mount useEffect failed", e);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -123,7 +122,7 @@ export default function ExportModal(props: Props) {
|
||||
setPendingExports(pendingExports);
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.EXPORT_FOLDER_DOES_NOT_EXIST) {
|
||||
logError(e, "syncExportRecord failed");
|
||||
log.error("syncExportRecord failed", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -135,12 +134,12 @@ export default function ExportModal(props: Props) {
|
||||
const handleChangeExportDirectoryClick = async () => {
|
||||
try {
|
||||
const newFolder = await exportService.changeExportDirectory();
|
||||
addLogLine(`Export folder changed to ${newFolder}`);
|
||||
log.info(`Export folder changed to ${newFolder}`);
|
||||
updateExportFolder(newFolder);
|
||||
void syncExportRecord(newFolder);
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.SELECT_FOLDER_ABORTED) {
|
||||
logError(e, "handleChangeExportDirectoryClick failed");
|
||||
log.error("handleChangeExportDirectoryClick failed", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -156,7 +155,7 @@ export default function ExportModal(props: Props) {
|
||||
}
|
||||
updateContinuousExport(newContinuousExport);
|
||||
} catch (e) {
|
||||
logError(e, "onContinuousExportChange failed");
|
||||
log.error("onContinuousExportChange failed", e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -166,7 +165,7 @@ export default function ExportModal(props: Props) {
|
||||
await exportService.scheduleExport();
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.EXPORT_FOLDER_DOES_NOT_EXIST) {
|
||||
logError(e, "scheduleExport failed");
|
||||
log.error("scheduleExport failed", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import Notification from "components/Notification";
|
||||
import { t } from "i18next";
|
||||
import isElectron from "is-electron";
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Skeleton, styled } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import log from "@/next/log";
|
||||
import { cached } from "@ente/shared/storage/cacheStorage/helpers";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import { User } from "@ente/shared/user/types";
|
||||
import { Skeleton, styled } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import machineLearningService from "services/machineLearning/machineLearningService";
|
||||
import { imageBitmapToBlob } from "utils/image";
|
||||
|
||||
@@ -44,9 +42,9 @@ export function ImageCacheView(props: {
|
||||
props.url,
|
||||
async () => {
|
||||
try {
|
||||
addLogLine(
|
||||
"ImageCacheView: regenerate face crop",
|
||||
props.faceID,
|
||||
log.debug(
|
||||
() =>
|
||||
`ImageCacheView: regenerate face crop for ${props.faceID}`,
|
||||
);
|
||||
return machineLearningService.regenerateFaceCrop(
|
||||
user.token,
|
||||
@@ -54,9 +52,9 @@ export function ImageCacheView(props: {
|
||||
props.faceID,
|
||||
);
|
||||
} catch (e) {
|
||||
logError(
|
||||
e,
|
||||
log.error(
|
||||
"ImageCacheView: regenerate face crop failed",
|
||||
e,
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -65,7 +63,7 @@ export function ImageCacheView(props: {
|
||||
|
||||
!didCancel && setImageBlob(blob);
|
||||
} catch (e) {
|
||||
logError(e, "ImageCacheView useEffect failed");
|
||||
log.error("ImageCacheView useEffect failed", e);
|
||||
}
|
||||
}
|
||||
loadImage();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { convertBytesToHumanReadable } from "@/next/file";
|
||||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import { Box, styled } from "@mui/material";
|
||||
import {
|
||||
DATE_CONTAINER_HEIGHT,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { convertBytesToHumanReadable } from "@/next/file";
|
||||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import { formatDate, getDate, isSameDay } from "@ente/shared/time/format";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import { Box, Checkbox, Link, Typography, styled } from "@mui/material";
|
||||
import {
|
||||
DATE_CONTAINER_HEIGHT,
|
||||
|
||||
@@ -16,9 +16,9 @@ import {
|
||||
isSupportedRawFormat,
|
||||
} from "utils/file";
|
||||
|
||||
import log from "@/next/log";
|
||||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { addLocalLog } from "@ente/shared/logging";
|
||||
import AlbumOutlined from "@mui/icons-material/AlbumOutlined";
|
||||
import ChevronLeft from "@mui/icons-material/ChevronLeft";
|
||||
import ChevronRight from "@mui/icons-material/ChevronRight";
|
||||
@@ -171,7 +171,7 @@ function PhotoViewer(props: Iprops) {
|
||||
return;
|
||||
}
|
||||
|
||||
addLocalLog(() => "Event: " + event.key);
|
||||
log.debug(() => "Event: " + event.key);
|
||||
|
||||
switch (event.key) {
|
||||
case "i":
|
||||
|
||||
@@ -3,9 +3,9 @@ import { AppContext } from "pages/_app";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { savedLogs } from "@/next/log-web";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { getDebugLogs } from "@ente/shared/logging/web";
|
||||
import { downloadAsFile } from "@ente/shared/utils";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { EnteMenuItem } from "components/Menu/EnteMenuItem";
|
||||
@@ -38,22 +38,17 @@ export default function DebugSection() {
|
||||
proceed: {
|
||||
text: t("DOWNLOAD"),
|
||||
variant: "accent",
|
||||
action: downloadDebugLogs,
|
||||
action: downloadLogs,
|
||||
},
|
||||
close: {
|
||||
text: t("CANCEL"),
|
||||
},
|
||||
});
|
||||
|
||||
const downloadDebugLogs = () => {
|
||||
addLogLine("exporting logs");
|
||||
if (isElectron()) {
|
||||
ElectronAPIs.openLogDirectory();
|
||||
} else {
|
||||
const logs = getDebugLogs();
|
||||
|
||||
downloadAsFile(`debug_logs_${Date.now()}.txt`, logs);
|
||||
}
|
||||
const downloadLogs = () => {
|
||||
addLogLine("Downloading logs");
|
||||
if (isElectron()) ElectronAPIs.openLogDirectory();
|
||||
else downloadAsFile(`debug_logs_${Date.now()}.txt`, savedLogs());
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
|
||||
@@ -6,8 +6,8 @@ import watchFolderService from "services/watchFolder/watchFolderService";
|
||||
import { WatchMapping } from "types/watchFolder";
|
||||
import { MappingList } from "./mappingList";
|
||||
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import DialogTitleWithCloseButton from "@ente/shared/components/DialogBox/TitleWithCloseButton";
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import UploadStrategyChoiceModal from "components/Upload/UploadStrategyChoiceModal";
|
||||
import { PICKED_UPLOAD_TYPE, UPLOAD_STRATEGY } from "constants/upload";
|
||||
import isElectron from "is-electron";
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { CustomHead } from "@/next/components/Head";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { setupI18n } from "@/next/i18n";
|
||||
import { logStartupBanner } from "@/next/log-web";
|
||||
import { AppUpdateInfo } from "@/next/types/ipc";
|
||||
import {
|
||||
APPS,
|
||||
APP_TITLES,
|
||||
@@ -20,16 +23,10 @@ import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { MessageContainer } from "@ente/shared/components/MessageContainer";
|
||||
import AppNavbar from "@ente/shared/components/Navbar/app";
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import { AppUpdateInfo } from "@ente/shared/electron/types";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { Events, eventBus } from "@ente/shared/events";
|
||||
import { useLocalState } from "@ente/shared/hooks/useLocalState";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import {
|
||||
clearLogsIfLocalStorageLimitExceeded,
|
||||
logStartupMessage,
|
||||
} from "@ente/shared/logging/web";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
@@ -41,6 +38,7 @@ import {
|
||||
import { getTheme } from "@ente/shared/themes";
|
||||
import { THEME_COLOR } from "@ente/shared/themes/constants";
|
||||
import { SetTheme } from "@ente/shared/themes/types";
|
||||
import type { User } from "@ente/shared/user/types";
|
||||
import ArrowForward from "@mui/icons-material/ArrowForward";
|
||||
import { CssBaseline, useMediaQuery } from "@mui/material";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
@@ -149,15 +147,12 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
//setup i18n
|
||||
setupI18n().finally(() => setIsI18nReady(true));
|
||||
// set client package name in headers
|
||||
const userId = (getData(LS_KEYS.USER) as User)?.id;
|
||||
logStartupBanner(APPS.PHOTOS, userId);
|
||||
HTTPService.setHeaders({
|
||||
"X-Client-Package": CLIENT_PACKAGE_NAMES.get(APPS.PHOTOS),
|
||||
});
|
||||
// setup logging
|
||||
clearLogsIfLocalStorageLimitExceeded();
|
||||
logStartupMessage(APPS.PHOTOS);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -89,9 +89,9 @@ import {
|
||||
splitNormalAndHiddenCollections,
|
||||
} from "utils/collection";
|
||||
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { CenteredFlex } from "@ente/shared/components/Container";
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import useFileInput from "@ente/shared/hooks/useFileInput";
|
||||
import useMemoSingleThreaded from "@ente/shared/hooks/useMemoSingleThreaded";
|
||||
import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import Login from "@ente/accounts/components/Login";
|
||||
import SignUp from "@ente/accounts/components/SignUp";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
@@ -5,7 +6,6 @@ import { EnteLogo } from "@ente/shared/components/EnteLogo";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import { saveKeyInSessionStore } from "@ente/shared/crypto/helpers";
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import { getAlbumsURL } from "@ente/shared/network/api";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import localForage from "@ente/shared/storage/localForage";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import ComlinkCryptoWorker from "@ente/shared/crypto";
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { Events, eventBus } from "@ente/shared/events";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
} from "utils/file";
|
||||
import { decodeLivePhoto } from "../livePhotoService";
|
||||
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { Events, eventBus } from "@ente/shared/events";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { addLocalLog, addLogLine } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import log from "@/next/log";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import { User } from "@ente/shared/user/types";
|
||||
import { sleep } from "@ente/shared/utils";
|
||||
@@ -52,25 +51,25 @@ export async function migrateExport(
|
||||
updateProgress: (progress: ExportProgress) => void,
|
||||
) {
|
||||
try {
|
||||
addLogLine(`current export version: ${exportRecord.version}`);
|
||||
log.info(`current export version: ${exportRecord.version}`);
|
||||
if (exportRecord.version === 0) {
|
||||
addLogLine("migrating export to version 1");
|
||||
log.info("migrating export to version 1");
|
||||
await migrationV0ToV1(exportDir, exportRecord as ExportRecordV0);
|
||||
exportRecord = await exportService.updateExportRecord(exportDir, {
|
||||
version: 1,
|
||||
});
|
||||
addLogLine("migration to version 1 complete");
|
||||
log.info("migration to version 1 complete");
|
||||
}
|
||||
if (exportRecord.version === 1) {
|
||||
addLogLine("migrating export to version 2");
|
||||
log.info("migrating export to version 2");
|
||||
await migrationV1ToV2(exportRecord as ExportRecordV1, exportDir);
|
||||
exportRecord = await exportService.updateExportRecord(exportDir, {
|
||||
version: 2,
|
||||
});
|
||||
addLogLine("migration to version 2 complete");
|
||||
log.info("migration to version 2 complete");
|
||||
}
|
||||
if (exportRecord.version === 2) {
|
||||
addLogLine("migrating export to version 3");
|
||||
log.info("migrating export to version 3");
|
||||
await migrationV2ToV3(
|
||||
exportDir,
|
||||
exportRecord as ExportRecordV2,
|
||||
@@ -79,28 +78,28 @@ export async function migrateExport(
|
||||
exportRecord = await exportService.updateExportRecord(exportDir, {
|
||||
version: 3,
|
||||
});
|
||||
addLogLine("migration to version 3 complete");
|
||||
log.info("migration to version 3 complete");
|
||||
}
|
||||
|
||||
if (exportRecord.version === 3) {
|
||||
addLogLine("migrating export to version 4");
|
||||
log.info("migrating export to version 4");
|
||||
await migrationV3ToV4(exportDir, exportRecord as ExportRecord);
|
||||
exportRecord = await exportService.updateExportRecord(exportDir, {
|
||||
version: 4,
|
||||
});
|
||||
addLogLine("migration to version 4 complete");
|
||||
log.info("migration to version 4 complete");
|
||||
}
|
||||
if (exportRecord.version === 4) {
|
||||
addLogLine("migrating export to version 5");
|
||||
log.info("migrating export to version 5");
|
||||
await migrationV4ToV5(exportDir, exportRecord as ExportRecord);
|
||||
exportRecord = await exportService.updateExportRecord(exportDir, {
|
||||
version: 5,
|
||||
});
|
||||
addLogLine("migration to version 5 complete");
|
||||
log.info("migration to version 5 complete");
|
||||
}
|
||||
addLogLine(`Record at latest version`);
|
||||
log.info(`Record at latest version`);
|
||||
} catch (e) {
|
||||
logError(e, "export record migration failed");
|
||||
log.error("export record migration failed", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -321,9 +320,8 @@ async function getFileExportNamesFromExportedFiles(
|
||||
if (!exportedFiles.length) {
|
||||
return;
|
||||
}
|
||||
addLogLine(
|
||||
"updating exported files to exported file paths property",
|
||||
`got ${exportedFiles.length} files`,
|
||||
log.info(
|
||||
`updating exported files to exported file paths property, got ${exportedFiles.length} files`,
|
||||
);
|
||||
let exportedFileNames: FileExportNames;
|
||||
const usedFilePaths = new Map<string, Set<string>>();
|
||||
@@ -334,7 +332,7 @@ async function getFileExportNamesFromExportedFiles(
|
||||
for (const file of exportedFiles) {
|
||||
await sleep(0);
|
||||
const collectionPath = exportedCollectionPaths.get(file.collectionID);
|
||||
addLocalLog(
|
||||
log.debug(
|
||||
() =>
|
||||
`collection path for ${file.collectionID} is ${collectionPath}`,
|
||||
);
|
||||
@@ -367,7 +365,7 @@ async function getFileExportNamesFromExportedFiles(
|
||||
usedFilePaths,
|
||||
);
|
||||
}
|
||||
addLocalLog(
|
||||
log.debug(
|
||||
() =>
|
||||
`file export name for ${file.metadata.title} is ${fileExportName}`,
|
||||
);
|
||||
@@ -419,7 +417,7 @@ async function addCollectionExportedRecordV1(
|
||||
|
||||
await exportService.updateExportRecord(folder, exportRecord);
|
||||
} catch (e) {
|
||||
logError(e, "addCollectionExportedRecord failed");
|
||||
log.error("addCollectionExportedRecord failed", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import isElectron from "is-electron";
|
||||
import { ElectronFile } from "types/upload";
|
||||
import ComlinkFFmpegWorker from "utils/comlink/ComlinkFFmpegWorker";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { convertBytesToHumanReadable } from "@/next/file";
|
||||
import { ComlinkWorker } from "@/next/worker/comlink-worker";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { retryAsyncFunction } from "@ente/shared/utils";
|
||||
import QueueProcessor from "@ente/shared/utils/queueProcessor";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import { ComlinkWorker } from "@ente/shared/worker/comlinkWorker";
|
||||
import { getDedicatedConvertWorker } from "utils/comlink/ComlinkConvertWorker";
|
||||
import { DedicatedConvertWorker } from "worker/convert.worker";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { PICKED_UPLOAD_TYPE } from "constants/upload";
|
||||
import { Collection } from "types/collection";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { haveWindow } from "@/next/env";
|
||||
import { ComlinkWorker } from "@/next/worker/comlink-worker";
|
||||
import { getDedicatedCryptoWorker } from "@ente/shared/crypto";
|
||||
import { DedicatedCryptoWorker } from "@ente/shared/crypto/internal/crypto.worker";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { ComlinkWorker } from "@ente/shared/worker/comlinkWorker";
|
||||
import PQueue from "p-queue";
|
||||
import { EnteFile } from "types/file";
|
||||
import {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ComlinkWorker } from "@/next/worker/comlink-worker";
|
||||
import { eventBus, Events } from "@ente/shared/events";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { getToken, getUserID } from "@ente/shared/storage/localStorage/helpers";
|
||||
import { ComlinkWorker } from "@ente/shared/worker/comlinkWorker";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import debounce from "debounce";
|
||||
import PQueue from "p-queue";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { convertBytesToHumanReadable } from "@/next/file";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import { ElectronFile } from "types/upload";
|
||||
|
||||
export async function getUint8ArrayView(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { convertBytesToHumanReadable } from "@/next/file";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import {
|
||||
KNOWN_NON_MEDIA_FORMATS,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getFileNameSize } from "@/next/file";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { getFileNameSize } from "@ente/shared/logging/web";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { FILE_READER_CHUNK_SIZE, MULTIPART_PART_SIZE } from "constants/upload";
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getFileNameSize } from "@/next/file";
|
||||
import { DedicatedCryptoWorker } from "@ente/shared/crypto/internal/crypto.worker";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { getFileNameSize } from "@ente/shared/logging/web";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { Remote } from "comlink";
|
||||
import { FILE_READER_CHUNK_SIZE } from "constants/upload";
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { convertBytesToHumanReadable, getFileNameSize } from "@/next/file";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { getFileNameSize } from "@ente/shared/logging/web";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import { BLACK_THUMBNAIL_BASE64 } from "constants/upload";
|
||||
import isElectron from "is-electron";
|
||||
|
||||
@@ -24,11 +24,11 @@ import UIService from "./uiService";
|
||||
import UploadService from "./uploadService";
|
||||
import uploader from "./uploader";
|
||||
|
||||
import { getFileNameSize } from "@/next/file";
|
||||
import { ComlinkWorker } from "@/next/worker/comlink-worker";
|
||||
import { getDedicatedCryptoWorker } from "@ente/shared/crypto";
|
||||
import { DedicatedCryptoWorker } from "@ente/shared/crypto/internal/crypto.worker";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { getFileNameSize } from "@ente/shared/logging/web";
|
||||
import { ComlinkWorker } from "@ente/shared/worker/comlinkWorker";
|
||||
import { Remote } from "comlink";
|
||||
import { UPLOAD_RESULT, UPLOAD_STAGES } from "constants/upload";
|
||||
import isElectron from "is-electron";
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { convertBytesToHumanReadable } from "@/next/file";
|
||||
import log from "@/next/log";
|
||||
import { DedicatedCryptoWorker } from "@ente/shared/crypto/internal/crypto.worker";
|
||||
import { CustomError, handleUploadError } from "@ente/shared/error";
|
||||
import { addLocalLog, addLogLine } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { sleep } from "@ente/shared/utils";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import { Remote } from "comlink";
|
||||
import { MAX_FILE_SIZE_SUPPORTED, UPLOAD_RESULT } from "constants/upload";
|
||||
import { addToCollection } from "services/collectionService";
|
||||
@@ -40,7 +39,7 @@ export default async function uploader(
|
||||
fileWithCollection,
|
||||
)}_${convertBytesToHumanReadable(UploadService.getAssetSize(uploadAsset))}`;
|
||||
|
||||
addLogLine(`uploader called for ${fileNameSize}`);
|
||||
log.info(`uploader called for ${fileNameSize}`);
|
||||
UIService.setFileProgress(localID, 0);
|
||||
await sleep(0);
|
||||
let fileTypeInfo: FileTypeInfo;
|
||||
@@ -50,13 +49,13 @@ export default async function uploader(
|
||||
if (fileSize >= MAX_FILE_SIZE_SUPPORTED) {
|
||||
return { fileUploadResult: UPLOAD_RESULT.TOO_LARGE };
|
||||
}
|
||||
addLogLine(`getting filetype for ${fileNameSize}`);
|
||||
log.info(`getting filetype for ${fileNameSize}`);
|
||||
fileTypeInfo = await UploadService.getAssetFileType(uploadAsset);
|
||||
addLogLine(
|
||||
log.info(
|
||||
`got filetype for ${fileNameSize} - ${JSON.stringify(fileTypeInfo)}`,
|
||||
);
|
||||
|
||||
addLogLine(`extracting metadata ${fileNameSize}`);
|
||||
log.info(`extracting metadata ${fileNameSize}`);
|
||||
const { metadata, publicMagicMetadata } =
|
||||
await UploadService.extractAssetMetadata(
|
||||
worker,
|
||||
@@ -69,7 +68,7 @@ export default async function uploader(
|
||||
existingFiles,
|
||||
metadata,
|
||||
);
|
||||
addLocalLog(
|
||||
log.debug(
|
||||
() =>
|
||||
`matchedFileList: ${matchingExistingFiles
|
||||
.map((f) => `${f.id}-${f.metadata.title}`)
|
||||
@@ -78,13 +77,13 @@ export default async function uploader(
|
||||
if (matchingExistingFiles?.length) {
|
||||
const matchingExistingFilesCollectionIDs =
|
||||
matchingExistingFiles.map((e) => e.collectionID);
|
||||
addLocalLog(
|
||||
log.debug(
|
||||
() =>
|
||||
`matched file collectionIDs:${matchingExistingFilesCollectionIDs}
|
||||
and collectionID:${collection.id}`,
|
||||
);
|
||||
if (matchingExistingFilesCollectionIDs.includes(collection.id)) {
|
||||
addLogLine(
|
||||
log.info(
|
||||
`file already present in the collection , skipped upload for ${fileNameSize}`,
|
||||
);
|
||||
const sameCollectionMatchingExistingFile =
|
||||
@@ -96,7 +95,7 @@ export default async function uploader(
|
||||
uploadedFile: sameCollectionMatchingExistingFile,
|
||||
};
|
||||
} else {
|
||||
addLogLine(
|
||||
log.info(
|
||||
`same file in ${matchingExistingFilesCollectionIDs.length} collection found for ${fileNameSize} ,adding symlink`,
|
||||
);
|
||||
// any of the matching file can used to add a symlink
|
||||
@@ -112,7 +111,7 @@ export default async function uploader(
|
||||
if (uploadCancelService.isUploadCancelationRequested()) {
|
||||
throw Error(CustomError.UPLOAD_CANCELLED);
|
||||
}
|
||||
addLogLine(`reading asset ${fileNameSize}`);
|
||||
log.info(`reading asset ${fileNameSize}`);
|
||||
|
||||
const file = await UploadService.readAsset(fileTypeInfo, uploadAsset);
|
||||
|
||||
@@ -137,7 +136,7 @@ export default async function uploader(
|
||||
if (uploadCancelService.isUploadCancelationRequested()) {
|
||||
throw Error(CustomError.UPLOAD_CANCELLED);
|
||||
}
|
||||
addLogLine(`encryptAsset ${fileNameSize}`);
|
||||
log.info(`encryptAsset ${fileNameSize}`);
|
||||
const encryptedFile = await UploadService.encryptAsset(
|
||||
worker,
|
||||
fileWithMetadata,
|
||||
@@ -147,9 +146,9 @@ export default async function uploader(
|
||||
if (uploadCancelService.isUploadCancelationRequested()) {
|
||||
throw Error(CustomError.UPLOAD_CANCELLED);
|
||||
}
|
||||
addLogLine(`uploadToBucket ${fileNameSize}`);
|
||||
log.info(`uploadToBucket ${fileNameSize}`);
|
||||
const logger: Logger = (message: string) => {
|
||||
addLogLine(message, `fileNameSize: ${fileNameSize}`);
|
||||
log.info(message, `fileNameSize: ${fileNameSize}`);
|
||||
};
|
||||
const backupedFile: BackupedFile = await UploadService.uploadToBucket(
|
||||
logger,
|
||||
@@ -161,11 +160,11 @@ export default async function uploader(
|
||||
backupedFile,
|
||||
encryptedFile.fileKey,
|
||||
);
|
||||
addLogLine(`uploading file to server ${fileNameSize}`);
|
||||
log.info(`uploading file to server ${fileNameSize}`);
|
||||
|
||||
const uploadedFile = await UploadService.uploadFile(uploadFile);
|
||||
|
||||
addLogLine(`${fileNameSize} successfully uploaded`);
|
||||
log.info(`${fileNameSize} successfully uploaded`);
|
||||
|
||||
return {
|
||||
fileUploadResult: metadata.hasStaticThumbnail
|
||||
@@ -174,15 +173,18 @@ export default async function uploader(
|
||||
uploadedFile: uploadedFile,
|
||||
};
|
||||
} catch (e) {
|
||||
addLogLine(`upload failed for ${fileNameSize} ,error: ${e.message}`);
|
||||
log.info(`upload failed for ${fileNameSize} ,error: ${e.message}`);
|
||||
if (
|
||||
e.message !== CustomError.UPLOAD_CANCELLED &&
|
||||
e.message !== CustomError.UNSUPPORTED_FILE_FORMAT
|
||||
) {
|
||||
logError(e, "file upload failed", {
|
||||
fileFormat: fileTypeInfo?.exactType,
|
||||
fileSize: convertBytesToHumanReadable(fileSize),
|
||||
});
|
||||
log.error(
|
||||
`file upload failed - ${JSON.stringify({
|
||||
fileFormat: fileTypeInfo?.exactType,
|
||||
fileSize: convertBytesToHumanReadable(fileSize),
|
||||
})}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
const error = handleUploadError(e);
|
||||
switch (error.message) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getFileNameSize } from "@/next/file";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { getFileNameSize } from "@ente/shared/logging/web";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { NULL_EXTRACTED_METADATA } from "constants/upload";
|
||||
import * as ffmpegService from "services/ffmpeg/ffmpegService";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import { addLocalLog, addLogLine } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import log from "@/next/log";
|
||||
import { UPLOAD_RESULT, UPLOAD_STRATEGY } from "constants/upload";
|
||||
import debounce from "debounce";
|
||||
import uploadManager from "services/upload/uploadManager";
|
||||
@@ -66,7 +65,7 @@ class watchFolderService {
|
||||
this.setupWatcherFunctions();
|
||||
await this.getAndSyncDiffOfFiles();
|
||||
} catch (e) {
|
||||
logError(e, "error while initializing watch service");
|
||||
log.error("error while initializing watch service", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +89,7 @@ class watchFolderService {
|
||||
this.trashDiffOfFiles(mapping, filesOnDisk);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, "error while getting and syncing diff of files");
|
||||
log.error("error while getting and syncing diff of files", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +192,7 @@ class watchFolderService {
|
||||
);
|
||||
this.getAndSyncDiffOfFiles();
|
||||
} catch (e) {
|
||||
logError(e, "error while adding watch mapping");
|
||||
log.error("error while adding watch mapping", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +200,7 @@ class watchFolderService {
|
||||
try {
|
||||
await ElectronAPIs.removeWatchMapping(folderPath);
|
||||
} catch (e) {
|
||||
logError(e, "error while removing watch mapping");
|
||||
log.error("error while removing watch mapping", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +208,7 @@ class watchFolderService {
|
||||
try {
|
||||
return (await ElectronAPIs.getWatchMappings()) ?? [];
|
||||
} catch (e) {
|
||||
logError(e, "error while getting watch mappings");
|
||||
log.error("error while getting watch mappings", e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -230,7 +229,7 @@ class watchFolderService {
|
||||
}
|
||||
|
||||
const event = this.clubSameCollectionEvents();
|
||||
addLogLine(
|
||||
log.info(
|
||||
`running event type:${event.type} collectionName:${event.collectionName} folderPath:${event.folderPath} , fileCount:${event.files?.length} pathsCount: ${event.paths?.length}`,
|
||||
);
|
||||
const mappings = await this.getWatchMappings();
|
||||
@@ -240,12 +239,12 @@ class watchFolderService {
|
||||
if (!mapping) {
|
||||
throw Error("no Mapping found for event");
|
||||
}
|
||||
addLogLine(
|
||||
log.info(
|
||||
`mapping for event rootFolder: ${mapping.rootFolderName} folderPath: ${mapping.folderPath} uploadStrategy: ${mapping.uploadStrategy} syncedFilesCount: ${mapping.syncedFiles.length} ignoredFilesCount ${mapping.ignoredFiles.length}`,
|
||||
);
|
||||
if (event.type === "upload") {
|
||||
event.files = getValidFilesToUpload(event.files, mapping);
|
||||
addLogLine(`valid files count: ${event.files?.length}`);
|
||||
log.info(`valid files count: ${event.files?.length}`);
|
||||
if (event.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -262,7 +261,7 @@ class watchFolderService {
|
||||
setTimeout(() => this.runNextEvent(), 0);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, "runNextEvent failed");
|
||||
log.error("runNextEvent failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +272,7 @@ class watchFolderService {
|
||||
this.setCollectionName(this.currentEvent.collectionName);
|
||||
this.setElectronFiles(this.currentEvent.files);
|
||||
} catch (e) {
|
||||
logError(e, "error while running next upload");
|
||||
log.error("error while running next upload", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +281,7 @@ class watchFolderService {
|
||||
fileWithCollection: FileWithCollection,
|
||||
file: EncryptedEnteFile,
|
||||
) {
|
||||
addLocalLog(() => `onFileUpload called`);
|
||||
log.debug(() => `onFileUpload called`);
|
||||
if (!this.isUploadRunning()) {
|
||||
return;
|
||||
}
|
||||
@@ -338,7 +337,7 @@ class watchFolderService {
|
||||
collections: Collection[],
|
||||
) {
|
||||
try {
|
||||
addLocalLog(
|
||||
log.debug(
|
||||
() =>
|
||||
`allFileUploadsDone,${JSON.stringify(
|
||||
filesWithCollection,
|
||||
@@ -348,8 +347,8 @@ class watchFolderService {
|
||||
(collection) =>
|
||||
collection.id === filesWithCollection[0].collectionID,
|
||||
);
|
||||
addLocalLog(() => `got collection ${!!collection}`);
|
||||
addLocalLog(
|
||||
log.debug(() => `got collection ${!!collection}`);
|
||||
log.debug(
|
||||
() =>
|
||||
`${this.isEventRunning} ${this.currentEvent.collectionName} ${collection?.name}`,
|
||||
);
|
||||
@@ -371,8 +370,8 @@ class watchFolderService {
|
||||
);
|
||||
}
|
||||
|
||||
addLocalLog(() => `syncedFiles ${JSON.stringify(syncedFiles)}`);
|
||||
addLocalLog(() => `ignoredFiles ${JSON.stringify(ignoredFiles)}`);
|
||||
log.debug(() => `syncedFiles ${JSON.stringify(syncedFiles)}`);
|
||||
log.debug(() => `ignoredFiles ${JSON.stringify(ignoredFiles)}`);
|
||||
|
||||
if (syncedFiles.length > 0) {
|
||||
this.currentlySyncedMapping.syncedFiles = [
|
||||
@@ -397,7 +396,7 @@ class watchFolderService {
|
||||
|
||||
this.runPostUploadsAction();
|
||||
} catch (e) {
|
||||
logError(e, "error while running all file uploads done");
|
||||
log.error("error while running all file uploads done", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +441,7 @@ class watchFolderService {
|
||||
};
|
||||
syncedFiles.push(imageFile);
|
||||
syncedFiles.push(videoFile);
|
||||
addLocalLog(
|
||||
log.debug(
|
||||
() =>
|
||||
`added image ${JSON.stringify(
|
||||
imageFile,
|
||||
@@ -456,7 +455,7 @@ class watchFolderService {
|
||||
) {
|
||||
ignoredFiles.push(imagePath);
|
||||
ignoredFiles.push(videoPath);
|
||||
addLocalLog(
|
||||
log.debug(
|
||||
() =>
|
||||
`added image ${imagePath} and video file ${videoPath} to rejectedFiles`,
|
||||
);
|
||||
@@ -476,10 +475,10 @@ class watchFolderService {
|
||||
.collectionID,
|
||||
};
|
||||
syncedFiles.push(file);
|
||||
addLocalLog(() => `added file ${JSON.stringify(file)} `);
|
||||
log.debug(() => `added file ${JSON.stringify(file)}`);
|
||||
} else if (this.unUploadableFilePaths.has(filePath)) {
|
||||
ignoredFiles.push(filePath);
|
||||
addLocalLog(() => `added file ${filePath} to rejectedFiles`);
|
||||
log.debug(() => `added file ${filePath} to rejectedFiles`);
|
||||
}
|
||||
this.filePathToUploadedFileIDMap.delete(filePath);
|
||||
}
|
||||
@@ -509,7 +508,7 @@ class watchFolderService {
|
||||
this.currentlySyncedMapping.syncedFiles,
|
||||
);
|
||||
} catch (e) {
|
||||
logError(e, "error while running next trash");
|
||||
log.error("error while running next trash", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,7 +538,7 @@ class watchFolderService {
|
||||
}
|
||||
this.syncWithRemote();
|
||||
} catch (e) {
|
||||
logError(e, "error while trashing by IDs");
|
||||
log.error("error while trashing by IDs", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,7 +580,7 @@ class watchFolderService {
|
||||
folderPath: mapping.folderPath,
|
||||
};
|
||||
} catch (e) {
|
||||
logError(e, "error while getting collection name");
|
||||
log.error("error while getting collection name", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,7 +598,7 @@ class watchFolderService {
|
||||
const folderPath = await ElectronAPIs.selectDirectory();
|
||||
return folderPath;
|
||||
} catch (e) {
|
||||
logError(e, "error while selecting folder");
|
||||
log.error("error while selecting folder", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +626,7 @@ class watchFolderService {
|
||||
const isFolder = await ElectronAPIs.isFolder(folderPath);
|
||||
return isFolder;
|
||||
} catch (e) {
|
||||
logError(e, "error while checking if folder exists");
|
||||
log.error("error while checking if folder exists", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { getAlbumsURL } from "@ente/shared/network/api";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { haveWindow } from "@/next/env";
|
||||
import { ComlinkWorker } from "@ente/shared/worker/comlinkWorker";
|
||||
import { ComlinkWorker } from "@/next/worker/comlink-worker";
|
||||
import { Remote } from "comlink";
|
||||
import { DedicatedConvertWorker } from "worker/convert.worker";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ComlinkWorker } from "@ente/shared/worker/comlinkWorker";
|
||||
import { ComlinkWorker } from "@/next/worker/comlink-worker";
|
||||
import { Remote } from "comlink";
|
||||
import { DedicatedFFmpegWorker } from "worker/ffmpeg.worker";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { haveWindow } from "@/next/env";
|
||||
import { ComlinkWorker } from "@ente/shared/worker/comlinkWorker";
|
||||
import { ComlinkWorker } from "@/next/worker/comlink-worker";
|
||||
import { DedicatedMLWorker } from "worker/ml.worker";
|
||||
|
||||
export const getDedicatedMLWorker = (name: string) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { haveWindow } from "@/next/env";
|
||||
import { ComlinkWorker } from "@ente/shared/worker/comlinkWorker";
|
||||
import { ComlinkWorker } from "@/next/worker/comlink-worker";
|
||||
import { Remote } from "comlink";
|
||||
import { DedicatedSearchWorker } from "worker/search.worker";
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { convertBytesToHumanReadable } from "@/next/file";
|
||||
import log from "@/next/log";
|
||||
import { workerBridge } from "@/next/worker/worker-bridge";
|
||||
import ComlinkCryptoWorker from "@ente/shared/crypto";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { isPlaybackPossible } from "@ente/shared/media/video-playback";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import { User } from "@ente/shared/user/types";
|
||||
import { downloadUsingAnchor } from "@ente/shared/utils";
|
||||
@@ -11,11 +17,20 @@ import {
|
||||
TYPE_JPEG,
|
||||
TYPE_JPG,
|
||||
} from "constants/file";
|
||||
import { t } from "i18next";
|
||||
import isElectron from "is-electron";
|
||||
import { moveToHiddenCollection } from "services/collectionService";
|
||||
import DownloadManager, {
|
||||
LivePhotoSourceURL,
|
||||
SourceURLs,
|
||||
} from "services/download";
|
||||
import * as ffmpegService from "services/ffmpeg/ffmpegService";
|
||||
import {
|
||||
deleteFromTrash,
|
||||
trashFiles,
|
||||
updateFileMagicMetadata,
|
||||
updateFilePublicMagicMetadata,
|
||||
} from "services/fileService";
|
||||
import heicConversionService from "services/heicConversionService";
|
||||
import { decodeLivePhoto } from "services/livePhotoService";
|
||||
import { getFileType } from "services/typeDetectionService";
|
||||
@@ -35,27 +50,9 @@ import {
|
||||
SetFilesDownloadProgressAttributesCreator,
|
||||
} from "types/gallery";
|
||||
import { VISIBILITY_STATE } from "types/magicMetadata";
|
||||
import { isArchivedFile, updateMagicMetadata } from "utils/magicMetadata";
|
||||
|
||||
import ComlinkCryptoWorker from "@ente/shared/crypto";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { addLocalLog, addLogLine } from "@ente/shared/logging";
|
||||
import { isPlaybackPossible } from "@ente/shared/media/video-playback";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import isElectron from "is-electron";
|
||||
import { moveToHiddenCollection } from "services/collectionService";
|
||||
import {
|
||||
deleteFromTrash,
|
||||
trashFiles,
|
||||
updateFileMagicMetadata,
|
||||
updateFilePublicMagicMetadata,
|
||||
} from "services/fileService";
|
||||
import { FileTypeInfo } from "types/upload";
|
||||
|
||||
import { default as ElectronAPIs } from "@ente/shared/electron";
|
||||
import { workerBridge } from "@ente/shared/worker/worker-bridge";
|
||||
import { t } from "i18next";
|
||||
import { getFileExportPath, getUniqueFileExportName } from "utils/export";
|
||||
import { isArchivedFile, updateMagicMetadata } from "utils/magicMetadata";
|
||||
|
||||
const WAIT_TIME_IMAGE_CONVERSION = 30 * 1000;
|
||||
|
||||
@@ -128,7 +125,7 @@ export async function downloadFile(file: EnteFile) {
|
||||
downloadUsingAnchor(tempURL, file.metadata.title);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, "failed to download file");
|
||||
log.error("failed to download file", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -244,7 +241,7 @@ export async function decryptFile(
|
||||
pubMagicMetadata: filePubMagicMetadata,
|
||||
};
|
||||
} catch (e) {
|
||||
logError(e, "file decryption failed");
|
||||
log.error("file decryption failed", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -413,19 +410,17 @@ export async function getPlayableVideo(
|
||||
if (!forceConvert && !runOnWeb && !isElectron()) {
|
||||
return null;
|
||||
}
|
||||
addLogLine(
|
||||
"video format not supported, converting it name:",
|
||||
videoNameTitle,
|
||||
log.info(
|
||||
`video format not supported, converting it name: ${videoNameTitle}`,
|
||||
);
|
||||
const mp4ConvertedVideo = await ffmpegService.convertToMP4(
|
||||
new File([videoBlob], videoNameTitle),
|
||||
);
|
||||
addLogLine("video successfully converted", videoNameTitle);
|
||||
log.info(`video successfully converted ${videoNameTitle}`);
|
||||
return new Blob([await mp4ConvertedVideo.arrayBuffer()]);
|
||||
}
|
||||
} catch (e) {
|
||||
addLogLine("video conversion failed", videoNameTitle);
|
||||
logError(e, "video conversion failed");
|
||||
log.error("video conversion failed", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -435,7 +430,7 @@ export async function getRenderableImage(fileName: string, imageBlob: Blob) {
|
||||
try {
|
||||
const tempFile = new File([imageBlob], fileName);
|
||||
fileTypeInfo = await getFileType(tempFile);
|
||||
addLocalLog(() => `file type info: ${JSON.stringify(fileTypeInfo)}`);
|
||||
log.debug(() => `file type info: ${JSON.stringify(fileTypeInfo)}`);
|
||||
const { exactType } = fileTypeInfo;
|
||||
let convertedImageBlob: Blob;
|
||||
if (isRawFile(exactType)) {
|
||||
@@ -447,7 +442,7 @@ export async function getRenderableImage(fileName: string, imageBlob: Blob) {
|
||||
if (!isElectron()) {
|
||||
throw Error(CustomError.NOT_AVAILABLE_ON_WEB);
|
||||
}
|
||||
addLogLine(
|
||||
log.info(
|
||||
`RawConverter called for ${fileName}-${convertBytesToHumanReadable(
|
||||
imageBlob.size,
|
||||
)}`,
|
||||
@@ -456,20 +451,20 @@ export async function getRenderableImage(fileName: string, imageBlob: Blob) {
|
||||
imageBlob,
|
||||
fileName,
|
||||
);
|
||||
addLogLine(`${fileName} successfully converted`);
|
||||
log.info(`${fileName} successfully converted`);
|
||||
} catch (e) {
|
||||
try {
|
||||
if (!isFileHEIC(exactType)) {
|
||||
throw e;
|
||||
}
|
||||
addLogLine(
|
||||
log.info(
|
||||
`HEICConverter called for ${fileName}-${convertBytesToHumanReadable(
|
||||
imageBlob.size,
|
||||
)}`,
|
||||
);
|
||||
convertedImageBlob =
|
||||
await heicConversionService.convert(imageBlob);
|
||||
addLogLine(`${fileName} successfully converted`);
|
||||
log.info(`${fileName} successfully converted`);
|
||||
} catch (e) {
|
||||
throw Error(CustomError.NON_PREVIEWABLE_FILE);
|
||||
}
|
||||
@@ -479,7 +474,10 @@ export async function getRenderableImage(fileName: string, imageBlob: Blob) {
|
||||
return imageBlob;
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, "get Renderable Image failed", { fileTypeInfo });
|
||||
log.error(
|
||||
`Failed to get renderable image for ${JSON.stringify(fileTypeInfo)}`,
|
||||
e,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -491,11 +489,10 @@ const convertToJPEGInElectron = async (
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const inputFileData = new Uint8Array(await fileBlob.arrayBuffer());
|
||||
const convertedFileData = await workerBridge.convertToJPEG(
|
||||
inputFileData,
|
||||
filename,
|
||||
);
|
||||
addLogLine(
|
||||
const convertedFileData = isElectron()
|
||||
? await ElectronAPIs.convertToJPEG(inputFileData, filename)
|
||||
: await workerBridge.convertToJPEG(inputFileData, filename);
|
||||
log.info(
|
||||
`originalFileSize:${convertBytesToHumanReadable(
|
||||
fileBlob?.size,
|
||||
)},convertedFileSize:${convertBytesToHumanReadable(
|
||||
@@ -508,7 +505,7 @@ const convertToJPEGInElectron = async (
|
||||
e.message !==
|
||||
CustomError.WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED
|
||||
) {
|
||||
logError(e, "failed to convert to jpeg natively");
|
||||
log.error("failed to convert to jpeg natively", e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
@@ -761,7 +758,7 @@ export async function downloadFiles(
|
||||
await downloadFile(file);
|
||||
progressBarUpdater?.increaseSuccess();
|
||||
} catch (e) {
|
||||
logError(e, "download fail for file");
|
||||
log.error("download fail for file", e);
|
||||
progressBarUpdater?.increaseFailed();
|
||||
}
|
||||
}
|
||||
@@ -785,7 +782,7 @@ export async function downloadFilesDesktop(
|
||||
await downloadFileDesktop(fileReader, file, downloadPath);
|
||||
progressBarUpdater?.increaseSuccess();
|
||||
} catch (e) {
|
||||
logError(e, "download fail for file");
|
||||
log.error("download fail for file", e);
|
||||
progressBarUpdater?.increaseFailed();
|
||||
}
|
||||
}
|
||||
@@ -890,7 +887,7 @@ export const copyFileToClipboard = async (fileUrl: string) => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
} catch (e) {
|
||||
void logError(e, "failed to copy to clipboard");
|
||||
log.error("failed to copy to clipboard", e);
|
||||
reject(e);
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
@@ -905,7 +902,7 @@ export const copyFileToClipboard = async (fileUrl: string) => {
|
||||
|
||||
await navigator.clipboard
|
||||
.write([new ClipboardItem({ "image/png": blobPromise })])
|
||||
.catch((e) => logError(e, "failed to copy to clipboard"));
|
||||
.catch((e) => log.error("failed to copy to clipboard", e));
|
||||
};
|
||||
|
||||
export function getLatestVersionFiles(files: EnteFile[]) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { AppUpdateInfo } from "@/next/types/ipc";
|
||||
import { logoutUser } from "@ente/accounts/services/user";
|
||||
import { DialogBoxAttributes } from "@ente/shared/components/DialogBox/types";
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import { AppUpdateInfo } from "@ente/shared/electron/types";
|
||||
import AutoAwesomeOutlinedIcon from "@mui/icons-material/AutoAwesomeOutlined";
|
||||
import InfoOutlined from "@mui/icons-material/InfoRounded";
|
||||
import { Link } from "@mui/material";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import { getFileNameSize } from "@ente/shared/logging/web";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { getFileNameSize } from "@/next/file";
|
||||
import { FILE_READER_CHUNK_SIZE, PICKED_UPLOAD_TYPE } from "constants/upload";
|
||||
import isElectron from "is-electron";
|
||||
import { getElectronFileStream, getFileStream } from "services/readerService";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import log from "@/next/log";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
|
||||
import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
|
||||
@@ -5,7 +6,6 @@ import LinkButton from "@ente/shared/components/LinkButton";
|
||||
import SingleInputForm, {
|
||||
SingleInputFormProps,
|
||||
} from "@ente/shared/components/SingleInputForm";
|
||||
import { addLocalLog } from "@ente/shared/logging";
|
||||
import { LS_KEYS, setData } from "@ente/shared/storage/localStorage";
|
||||
import { Input } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
@@ -29,9 +29,7 @@ export default function Login(props: LoginProps) {
|
||||
try {
|
||||
setData(LS_KEYS.USER, { email });
|
||||
const srpAttributes = await getSRPAttributes(email);
|
||||
addLocalLog(
|
||||
() => ` srpAttributes: ${JSON.stringify(srpAttributes)}`,
|
||||
);
|
||||
log.debug(() => ` srpAttributes: ${JSON.stringify(srpAttributes)}`);
|
||||
if (!srpAttributes || srpAttributes.isEmailMFAEnabled) {
|
||||
await sendOtt(props.appName, email);
|
||||
router.push(PAGES.VERIFY);
|
||||
|
||||
@@ -1,29 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { t } from "i18next";
|
||||
|
||||
import {
|
||||
decryptAndStoreToken,
|
||||
generateAndSaveIntermediateKeyAttributes,
|
||||
generateLoginSubKey,
|
||||
saveKeyInSessionStore,
|
||||
} from "@ente/shared/crypto/helpers";
|
||||
import {
|
||||
LS_KEYS,
|
||||
clearData,
|
||||
getData,
|
||||
setData,
|
||||
} from "@ente/shared/storage/localStorage";
|
||||
import {
|
||||
SESSION_KEYS,
|
||||
getKey,
|
||||
removeKey,
|
||||
setKey,
|
||||
} from "@ente/shared/storage/sessionStorage";
|
||||
import { PAGES } from "../constants/pages";
|
||||
import { generateSRPSetupAttributes } from "../services/srp";
|
||||
import { logoutUser } from "../services/user";
|
||||
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import log from "@/next/log";
|
||||
import { APP_HOMES } from "@ente/shared/apps/constants";
|
||||
import { PageProps } from "@ente/shared/apps/types";
|
||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||
@@ -36,23 +12,47 @@ import VerifyMasterPasswordForm, {
|
||||
VerifyMasterPasswordFormProps,
|
||||
} from "@ente/shared/components/VerifyMasterPasswordForm";
|
||||
import ComlinkCryptoWorker from "@ente/shared/crypto";
|
||||
import {
|
||||
decryptAndStoreToken,
|
||||
generateAndSaveIntermediateKeyAttributes,
|
||||
generateLoginSubKey,
|
||||
saveKeyInSessionStore,
|
||||
} from "@ente/shared/crypto/helpers";
|
||||
import { B64EncryptionResult } from "@ente/shared/crypto/types";
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { addLocalLog } from "@ente/shared/logging";
|
||||
import { getAccountsURL } from "@ente/shared/network/api";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
|
||||
import {
|
||||
LS_KEYS,
|
||||
clearData,
|
||||
getData,
|
||||
setData,
|
||||
} from "@ente/shared/storage/localStorage";
|
||||
import {
|
||||
getToken,
|
||||
isFirstLogin,
|
||||
setIsFirstLogin,
|
||||
} from "@ente/shared/storage/localStorage/helpers";
|
||||
import {
|
||||
SESSION_KEYS,
|
||||
getKey,
|
||||
removeKey,
|
||||
setKey,
|
||||
} from "@ente/shared/storage/sessionStorage";
|
||||
import { KeyAttributes, User } from "@ente/shared/user/types";
|
||||
import { t } from "i18next";
|
||||
import isElectron from "is-electron";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getSRPAttributes } from "../api/srp";
|
||||
import { configureSRP, loginViaSRP } from "../services/srp";
|
||||
import { PAGES } from "../constants/pages";
|
||||
import {
|
||||
configureSRP,
|
||||
generateSRPSetupAttributes,
|
||||
loginViaSRP,
|
||||
} from "../services/srp";
|
||||
import { logoutUser } from "../services/user";
|
||||
import { SRPAttributes } from "../types/srp";
|
||||
|
||||
export default function Credentials({ appContext, appName }: PageProps) {
|
||||
@@ -230,7 +230,7 @@ export default function Credentials({ appContext, appName }: PageProps) {
|
||||
setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes);
|
||||
}
|
||||
}
|
||||
addLocalLog(() => `userSRPSetupPending ${!srpAttributes}`);
|
||||
log.debug(() => `userSRPSetupPending ${!srpAttributes}`);
|
||||
if (!srpAttributes) {
|
||||
const loginSubKey = await generateLoginSubKey(kek);
|
||||
const srpSetupAttributes =
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { SRP, SrpClient } from "fast-srp-hap";
|
||||
|
||||
import { SRPAttributes, SRPSetupAttributes } from "../types/srp";
|
||||
|
||||
import log from "@/next/log";
|
||||
import { UserVerificationResponse } from "@ente/accounts/types/user";
|
||||
import ComlinkCryptoWorker from "@ente/shared/crypto";
|
||||
import { generateLoginSubKey } from "@ente/shared/crypto/helpers";
|
||||
import { addLocalLog } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
|
||||
import { getToken } from "@ente/shared/storage/localStorage/helpers";
|
||||
import { SRP, SrpClient } from "fast-srp-hap";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import {
|
||||
completeSRPSetup,
|
||||
@@ -16,6 +13,7 @@ import {
|
||||
startSRPSetup,
|
||||
verifySRPSession,
|
||||
} from "../api/srp";
|
||||
import { SRPAttributes, SRPSetupAttributes } from "../types/srp";
|
||||
import { convertBase64ToBuffer, convertBufferToBase64 } from "../utils";
|
||||
|
||||
const SRP_PARAMS = SRP.params["4096"];
|
||||
@@ -42,7 +40,7 @@ export const configureSRP = async ({
|
||||
|
||||
const srpA = convertBufferToBase64(srpClient.computeA());
|
||||
|
||||
addLocalLog(() => `srp a: ${srpA}`);
|
||||
log.debug(() => `srp a: ${srpA}`);
|
||||
const token = getToken();
|
||||
const { setupID, srpB } = await startSRPSetup(token, {
|
||||
srpA,
|
||||
@@ -62,7 +60,7 @@ export const configureSRP = async ({
|
||||
|
||||
srpClient.checkM2(convertBase64ToBuffer(srpM2));
|
||||
} catch (e) {
|
||||
logError(e, "srp configure failed");
|
||||
log.error("Failed to configure SRP", e);
|
||||
throw e;
|
||||
} finally {
|
||||
InMemoryStore.set(MS_KEYS.SRP_CONFIGURE_IN_PROGRESS, false);
|
||||
@@ -87,22 +85,18 @@ export const generateSRPSetupAttributes = async (
|
||||
|
||||
const srpVerifier = convertBufferToBase64(srpVerifierBuffer);
|
||||
|
||||
addLocalLog(
|
||||
() => `SRP setup attributes generated',
|
||||
${JSON.stringify({
|
||||
srpSalt,
|
||||
srpUserID,
|
||||
srpVerifier,
|
||||
loginSubKey,
|
||||
})}`,
|
||||
);
|
||||
|
||||
return {
|
||||
const result = {
|
||||
srpUserID,
|
||||
srpSalt,
|
||||
srpVerifier,
|
||||
loginSubKey,
|
||||
};
|
||||
|
||||
log.debug(
|
||||
() => `SRP setup attributes generated: ${JSON.stringify(result)}`,
|
||||
);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const loginViaSRP = async (
|
||||
@@ -124,17 +118,17 @@ export const loginViaSRP = async (
|
||||
srpClient.setB(convertBase64ToBuffer(srpB));
|
||||
|
||||
const m1 = srpClient.computeM1();
|
||||
addLocalLog(() => `srp m1: ${convertBufferToBase64(m1)}`);
|
||||
log.debug(() => `srp m1: ${convertBufferToBase64(m1)}`);
|
||||
const { srpM2, ...rest } = await verifySRPSession(
|
||||
sessionID,
|
||||
srpAttributes.srpUserID,
|
||||
convertBufferToBase64(m1),
|
||||
);
|
||||
addLocalLog(() => `srp verify session successful,srpM2: ${srpM2}`);
|
||||
log.debug(() => `srp verify session successful,srpM2: ${srpM2}`);
|
||||
|
||||
srpClient.checkM2(convertBase64ToBuffer(srpM2));
|
||||
|
||||
addLocalLog(() => `srp server verify successful`);
|
||||
log.debug(() => `srp server verify successful`);
|
||||
return rest;
|
||||
} catch (e) {
|
||||
logError(e, "srp verify failed");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { Events, eventBus } from "@ente/shared/events";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import InMemoryStore from "@ente/shared/storage/InMemoryStore";
|
||||
|
||||
10
web/packages/next/electron.ts
Normal file
10
web/packages/next/electron.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { ElectronAPIsType } from "./types/ipc";
|
||||
|
||||
// TODO (MR):
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const ElectronAPIs = (globalThis as unknown as any)[
|
||||
// eslint-disable-next-line @typescript-eslint/dot-notation, @typescript-eslint/no-unsafe-member-access
|
||||
"ElectronAPIs"
|
||||
] as ElectronAPIsType;
|
||||
|
||||
export default ElectronAPIs;
|
||||
@@ -1,3 +1,9 @@
|
||||
import type { ElectronFile } from "./types/file";
|
||||
|
||||
export function getFileNameSize(file: File | ElectronFile) {
|
||||
return `${file.name}_${convertBytesToHumanReadable(file.size)}`;
|
||||
}
|
||||
|
||||
export function convertBytesToHumanReadable(
|
||||
bytes: number,
|
||||
precision = 2,
|
||||
81
web/packages/next/log-web.ts
Normal file
81
web/packages/next/log-web.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { isDevBuild } from "@/next/env";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
|
||||
/**
|
||||
* Log a standard startup banner.
|
||||
*
|
||||
* This helps us identify app starts and other environment details in the logs.
|
||||
*
|
||||
* @param appId An identifier of the app that is starting.
|
||||
* @param userId The uid for the currently logged in user, if any.
|
||||
*/
|
||||
export const logStartupBanner = (appId: string, userId?: number) => {
|
||||
// TODO (MR): Remove the need to lowercase it, change the enum itself.
|
||||
const appIdL = appId.toLowerCase();
|
||||
const sha = process.env.GIT_SHA;
|
||||
const buildId = isDevBuild ? "dev " : sha ? `git ${sha} ` : "";
|
||||
|
||||
addLogLine(`Starting ente-${appIdL}-web ${buildId}uid ${userId ?? 0}`);
|
||||
};
|
||||
|
||||
interface LogEntry {
|
||||
timestamp: number;
|
||||
logLine: string;
|
||||
}
|
||||
|
||||
const lsKey = "logs";
|
||||
|
||||
/**
|
||||
* Record {@link message} in a persistent log storage.
|
||||
*
|
||||
* These strings, alongwith associated timestamps, get added to a small ring
|
||||
* buffer, whose contents can be later be retrieved by using {@link savedLogs}.
|
||||
*
|
||||
* This ring buffer is persisted in the browser's local storage.
|
||||
*/
|
||||
export const logToDisk = (message: string) => {
|
||||
const maxCount = 1000;
|
||||
const log: LogEntry = { logLine: message, timestamp: Date.now() };
|
||||
try {
|
||||
const logs = logEntries();
|
||||
if (logs.length > maxCount) {
|
||||
logs.slice(logs.length - maxCount);
|
||||
}
|
||||
logs.push(log);
|
||||
localStorage.setItem(lsKey, JSON.stringify({ logs }));
|
||||
} catch (e) {
|
||||
console.error("Failed to persist log", e);
|
||||
if (e instanceof Error && e.name === "QuotaExceededError") {
|
||||
localStorage.removeItem(lsKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const logEntries = (): unknown[] => {
|
||||
const s = localStorage.getItem("logs");
|
||||
if (!s) return [];
|
||||
const o: unknown = JSON.parse(s);
|
||||
if (!(o && typeof o == "object" && "logs" in o && Array.isArray(o.logs))) {
|
||||
console.error("Unexpected log entries obtained from local storage", o);
|
||||
return [];
|
||||
}
|
||||
return o.logs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a string containing all recently saved log messages.
|
||||
*
|
||||
* @see {@link persistLog}.
|
||||
*/
|
||||
export const savedLogs = () => logEntries().map(formatEntry).join("\n");
|
||||
|
||||
const formatEntry = (e: unknown) => {
|
||||
if (e && typeof e == "object" && "timestamp" in e && "logLine" in e) {
|
||||
const timestamp = e.timestamp;
|
||||
const logLine = e.logLine;
|
||||
if (typeof timestamp == "number" && typeof logLine == "string") {
|
||||
return `[${new Date(timestamp).toISOString()}] ${logLine}`;
|
||||
}
|
||||
}
|
||||
return String(e);
|
||||
};
|
||||
118
web/packages/next/log.ts
Normal file
118
web/packages/next/log.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { inWorker } from "@/next/env";
|
||||
import isElectron from "is-electron";
|
||||
import ElectronAPIs from "./electron";
|
||||
import { isDevBuild } from "./env";
|
||||
import { logToDisk as webLogToDisk } from "./log-web";
|
||||
import { workerBridge } from "./worker/worker-bridge";
|
||||
|
||||
/**
|
||||
* Write a {@link message} to the on-disk log.
|
||||
*
|
||||
* This is used by the renderer process (via the contextBridge) to add entries
|
||||
* in the log that is saved on disk.
|
||||
*/
|
||||
export const logToDisk = (message: string) => {
|
||||
if (isElectron()) ElectronAPIs.logToDisk(message);
|
||||
else if (inWorker()) workerLogToDisk(message);
|
||||
else webLogToDisk(message);
|
||||
};
|
||||
|
||||
const workerLogToDisk = (message: string) => {
|
||||
workerBridge.logToDisk(message).catch((e) => {
|
||||
console.error(
|
||||
"Failed to log a message from worker",
|
||||
e,
|
||||
"\nThe message was",
|
||||
message,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const logError = (message: string, e?: unknown) => {
|
||||
if (!e) {
|
||||
logError_(message);
|
||||
return;
|
||||
}
|
||||
|
||||
let es: string;
|
||||
if (e instanceof Error) {
|
||||
// In practice, we expect ourselves to be called with Error objects, so
|
||||
// this is the happy path so to say.
|
||||
es = `${e.name}: ${e.message}\n${e.stack}`;
|
||||
} else {
|
||||
// For the rest rare cases, use the default string serialization of e.
|
||||
es = String(e);
|
||||
}
|
||||
|
||||
logError_(`${message}: ${es}`);
|
||||
};
|
||||
|
||||
const logError_ = (message: string) => {
|
||||
const m = `[error] ${message}`;
|
||||
if (isDevBuild) console.error(m);
|
||||
logToDisk(m);
|
||||
};
|
||||
|
||||
const logInfo = (...params: unknown[]) => {
|
||||
const message = params
|
||||
.map((p) => (typeof p == "string" ? p : JSON.stringify(p)))
|
||||
.join(" ");
|
||||
const m = `[info] ${message}`;
|
||||
if (isDevBuild) console.log(m);
|
||||
logToDisk(m);
|
||||
};
|
||||
|
||||
const logDebug = (param: () => unknown) => {
|
||||
if (isDevBuild) console.log("[debug]", param());
|
||||
};
|
||||
|
||||
/**
|
||||
* Ente's logger.
|
||||
*
|
||||
* This is an object that provides three functions to log at the corresponding
|
||||
* levels - error, info or debug.
|
||||
*
|
||||
* Whenever we need to save a log message to disk,
|
||||
*
|
||||
* - When running under electron these messages are saved to the log maintained
|
||||
* by the electron app we're running under.
|
||||
*
|
||||
* - Otherwise such messages are written to a ring buffer in local storage.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Log an error message with an optional associated error object.
|
||||
*
|
||||
* {@link e} is generally expected to be an `instanceof Error` but it can be
|
||||
* any arbitrary object that we obtain, say, when in a try-catch handler (in
|
||||
* JavaScript any arbitrary value can be thrown).
|
||||
*
|
||||
* The log is written to disk. In development builds, the log is also
|
||||
* printed to the browser console.
|
||||
*/
|
||||
error: logError,
|
||||
/**
|
||||
* Log a message.
|
||||
*
|
||||
* This is meant as a replacement of {@link console.log}, and takes an
|
||||
* arbitrary number of arbitrary parameters that it then serializes.
|
||||
*
|
||||
* The log is written to disk. In development builds, the log is also
|
||||
* printed to the browser console.
|
||||
*/
|
||||
info: logInfo,
|
||||
/**
|
||||
* Log a debug message.
|
||||
*
|
||||
* To avoid running unnecessary code in release builds, this takes a
|
||||
* function to call to get the log message instead of directly taking the
|
||||
* message. The provided function will only be called in development builds.
|
||||
*
|
||||
* The function can return an arbitrary value which is serialized before
|
||||
* being logged.
|
||||
*
|
||||
* This log is NOT written to disk. And it is printed to the browser
|
||||
* console, but only in development builds.
|
||||
*/
|
||||
debug: logDebug,
|
||||
};
|
||||
@@ -7,6 +7,7 @@
|
||||
"@emotion/styled": "^11.11",
|
||||
"@mui/icons-material": "^5.15",
|
||||
"@mui/material": "^5.15",
|
||||
"comlink": "^4.4",
|
||||
"get-user-locale": "^2.3",
|
||||
"i18next": "^23.10",
|
||||
"i18next-resources-to-backend": "^1.2.0",
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export enum UPLOAD_STRATEGY {
|
||||
SINGLE_COLLECTION,
|
||||
COLLECTION_PER_FOLDER,
|
||||
}
|
||||
|
||||
/*
|
||||
* ElectronFile is a custom interface that is used to represent
|
||||
* any file on disk as a File-like object in the Electron desktop app.
|
||||
@@ -20,3 +25,25 @@ export interface DataStream {
|
||||
stream: ReadableStream<Uint8Array>;
|
||||
chunkCount: number;
|
||||
}
|
||||
|
||||
export interface WatchMappingSyncedFile {
|
||||
path: string;
|
||||
uploadedFileID: number;
|
||||
collectionID: number;
|
||||
}
|
||||
|
||||
export interface WatchMapping {
|
||||
rootFolderName: string;
|
||||
folderPath: string;
|
||||
uploadStrategy: UPLOAD_STRATEGY;
|
||||
syncedFiles: WatchMappingSyncedFile[];
|
||||
ignoredFiles: string[];
|
||||
}
|
||||
|
||||
export interface EventQueueItem {
|
||||
type: "upload" | "trash";
|
||||
folderPath: string;
|
||||
collectionName?: string;
|
||||
paths?: string[];
|
||||
files?: ElectronFile[];
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
//
|
||||
// See [Note: types.ts <-> preload.ts <-> ipc.ts]
|
||||
|
||||
import type { ElectronFile } from "@ente/shared/upload/types";
|
||||
import type { WatchMapping } from "@ente/shared/watchFolder/types";
|
||||
import type { ElectronFile, WatchMapping } from "./file";
|
||||
|
||||
export interface AppUpdateInfo {
|
||||
autoUpdatable: boolean;
|
||||
@@ -199,9 +198,9 @@ export interface ElectronAPIsType {
|
||||
checkExistsAndCreateDir: (dirPath: string) => Promise<void>;
|
||||
saveStreamToDisk: (
|
||||
path: string,
|
||||
fileStream: ReadableStream<any>,
|
||||
fileStream: ReadableStream,
|
||||
) => Promise<void>;
|
||||
saveFileToDisk: (path: string, file: any) => Promise<void>;
|
||||
saveFileToDisk: (path: string, contents: string) => Promise<void>;
|
||||
readTextFile: (path: string) => Promise<string>;
|
||||
isFolder: (dirPath: string) => Promise<boolean>;
|
||||
moveFile: (oldPath: string, newPath: string) => Promise<void>;
|
||||
@@ -1,7 +1,6 @@
|
||||
import { addLocalLog, logToDisk } from "@ente/shared/logging";
|
||||
import { Remote, expose, wrap } from "comlink";
|
||||
import ElectronAPIs from "../electron";
|
||||
import { logError } from "../sentry";
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import log, { logToDisk } from "@/next/log";
|
||||
import { expose, wrap, type Remote } from "comlink";
|
||||
|
||||
export class ComlinkWorker<T extends new () => InstanceType<T>> {
|
||||
public remote: Promise<Remote<InstanceType<T>>>;
|
||||
@@ -12,13 +11,15 @@ export class ComlinkWorker<T extends new () => InstanceType<T>> {
|
||||
this.name = name;
|
||||
this.worker = worker;
|
||||
|
||||
this.worker.onerror = (errorEvent) => {
|
||||
logError(Error(errorEvent.message), "Got error event from worker", {
|
||||
errorEvent: JSON.stringify(errorEvent),
|
||||
name: this.name,
|
||||
});
|
||||
this.worker.onerror = (ev) => {
|
||||
log.error(
|
||||
`Got error event from worker: ${JSON.stringify({
|
||||
errorEvent: JSON.stringify(ev),
|
||||
name: this.name,
|
||||
})}`,
|
||||
);
|
||||
};
|
||||
addLocalLog(() => `Initiated ${this.name}`);
|
||||
log.debug(() => `Initiated ${this.name}`);
|
||||
const comlink = wrap<T>(this.worker);
|
||||
this.remote = new comlink() as Promise<Remote<InstanceType<T>>>;
|
||||
expose(workerBridge, worker);
|
||||
@@ -30,7 +31,7 @@ export class ComlinkWorker<T extends new () => InstanceType<T>> {
|
||||
|
||||
public terminate() {
|
||||
this.worker.terminate();
|
||||
addLocalLog(() => `Terminated ${this.name}`);
|
||||
log.debug(() => `Terminated ${this.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +40,7 @@ export class ComlinkWorker<T extends new () => InstanceType<T>> {
|
||||
* create.
|
||||
*
|
||||
* Inside the worker's code, this can be accessed by using the sibling
|
||||
* `workerBridge` object by importing `worker-bridge.ts`.
|
||||
* `workerBridge` object after importing it from `worker-bridge.ts`.
|
||||
*/
|
||||
const workerBridge = {
|
||||
logToDisk,
|
||||
@@ -1,5 +1,5 @@
|
||||
import { wrap } from "comlink";
|
||||
import type { WorkerBridge } from "./comlinkWorker";
|
||||
import type { WorkerBridge } from "./comlink-worker";
|
||||
|
||||
/**
|
||||
* The web worker side handle to the {@link WorkerBridge} exposed by the main
|
||||
@@ -7,6 +7,6 @@ import type { WorkerBridge } from "./comlinkWorker";
|
||||
*
|
||||
* This file is meant to be run inside a worker. Accessing the properties of
|
||||
* this object will be transparently (but asynchrorously) relayed to the
|
||||
* implementation of the {@link WorkerBridge} in `comlinkWorker.ts`.
|
||||
* implementation of the {@link WorkerBridge} in `comlink-worker.ts`.
|
||||
*/
|
||||
export const workerBridge = wrap<WorkerBridge>(globalThis);
|
||||
@@ -1,5 +1,5 @@
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import LinkButton from "@ente/shared/components/LinkButton";
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { Tooltip } from "@mui/material";
|
||||
import { styled } from "@mui/material/styles";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ElectronAPIs from "@/next/electron";
|
||||
import { setRecoveryKey } from "@ente/accounts/api/user";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
|
||||
@@ -7,7 +8,6 @@ import { getActualKey } from "@ente/shared/user";
|
||||
import { KeyAttributes } from "@ente/shared/user/types";
|
||||
import isElectron from "is-electron";
|
||||
import ComlinkCryptoWorker from ".";
|
||||
import ElectronAPIs from "../electron";
|
||||
import { addLogLine } from "../logging";
|
||||
|
||||
const LOGIN_SUB_KEY_LENGTH = 32;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ComlinkWorker } from "@/next/worker/comlink-worker";
|
||||
import { Remote } from "comlink";
|
||||
import { ComlinkWorker } from "../worker/comlinkWorker";
|
||||
import { DedicatedCryptoWorker } from "./internal/crypto.worker";
|
||||
|
||||
class ComlinkCryptoWorker {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DataStream } from "@ente/shared/upload/types";
|
||||
import { DataStream } from "@/next/types/file";
|
||||
|
||||
export interface LocalFileAttributes<
|
||||
T extends string | Uint8Array | DataStream,
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { ElectronAPIsType } from "./types";
|
||||
|
||||
const ElectronAPIs: ElectronAPIsType = globalThis["ElectronAPIs"];
|
||||
|
||||
export default ElectronAPIs;
|
||||
@@ -1,57 +1,9 @@
|
||||
import { inWorker, isDevBuild } from "@/next/env";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import isElectron from "is-electron";
|
||||
import ElectronAPIs from "../electron";
|
||||
import { workerBridge } from "../worker/worker-bridge";
|
||||
import { formatLog, logWeb } from "./web";
|
||||
|
||||
export const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB
|
||||
export const MAX_LOG_LINES = 1000;
|
||||
|
||||
export const logToDisk = (message: string) => {
|
||||
if (isElectron()) {
|
||||
ElectronAPIs.logToDisk(message);
|
||||
} else {
|
||||
logWeb(message);
|
||||
}
|
||||
};
|
||||
import log from "@/next/log";
|
||||
|
||||
export function addLogLine(
|
||||
log: string | number | boolean,
|
||||
msg: string | number | boolean,
|
||||
...optionalParams: (string | number | boolean)[]
|
||||
) {
|
||||
try {
|
||||
const completeLog = [log, ...optionalParams].join(" ");
|
||||
if (isDevBuild) {
|
||||
console.log(completeLog);
|
||||
}
|
||||
if (inWorker()) {
|
||||
workerBridge
|
||||
.logToDisk(completeLog)
|
||||
.catch((e) =>
|
||||
console.error(
|
||||
"Failed to log a message from worker",
|
||||
e,
|
||||
"\nThe message was",
|
||||
completeLog,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
logToDisk(completeLog);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, "failed to addLogLine", undefined, true);
|
||||
// ignore
|
||||
}
|
||||
const completeLog = [msg, ...optionalParams].join(" ");
|
||||
log.info(completeLog);
|
||||
}
|
||||
|
||||
export const addLocalLog = (getLog: () => string) => {
|
||||
if (isDevBuild) {
|
||||
console.log(
|
||||
formatLog({
|
||||
logLine: getLog(),
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import { isDevBuild } from "@/next/env";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import {
|
||||
LS_KEYS,
|
||||
getData,
|
||||
removeData,
|
||||
setData,
|
||||
} from "@ente/shared/storage/localStorage";
|
||||
import { addLogLine } from ".";
|
||||
import { formatDateTimeShort } from "../time/format";
|
||||
import { ElectronFile } from "../upload/types";
|
||||
import type { User } from "../user/types";
|
||||
import { convertBytesToHumanReadable } from "../utils/size";
|
||||
|
||||
export const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB
|
||||
export const MAX_LOG_LINES = 1000;
|
||||
|
||||
export interface Log {
|
||||
timestamp: number;
|
||||
logLine: string;
|
||||
}
|
||||
|
||||
export function logWeb(logLine: string) {
|
||||
try {
|
||||
const log: Log = { logLine, timestamp: Date.now() };
|
||||
const logs = getLogs();
|
||||
if (logs.length > MAX_LOG_LINES) {
|
||||
logs.slice(logs.length - MAX_LOG_LINES);
|
||||
}
|
||||
logs.push(log);
|
||||
setLogs(logs);
|
||||
} catch (e) {
|
||||
if (e.name === "QuotaExceededError") {
|
||||
deleteLogs();
|
||||
logWeb("logs cleared");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getDebugLogs() {
|
||||
return combineLogLines(getLogs());
|
||||
}
|
||||
|
||||
export function getFileNameSize(file: File | ElectronFile) {
|
||||
return `${file.name}_${convertBytesToHumanReadable(file.size)}`;
|
||||
}
|
||||
|
||||
export const clearLogsIfLocalStorageLimitExceeded = () => {
|
||||
try {
|
||||
const logs = getDebugLogs();
|
||||
const logSize = getStringSize(logs);
|
||||
if (logSize > MAX_LOG_SIZE) {
|
||||
deleteLogs();
|
||||
logWeb("Logs cleared due to size limit exceeded");
|
||||
} else {
|
||||
try {
|
||||
logWeb(`app started`);
|
||||
} catch (e) {
|
||||
deleteLogs();
|
||||
}
|
||||
}
|
||||
logWeb(`logs size: ${convertBytesToHumanReadable(logSize)}`);
|
||||
} catch (e) {
|
||||
logError(
|
||||
e,
|
||||
"failed to clearLogsIfLocalStorageLimitExceeded",
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const logStartupMessage = async (appId: string) => {
|
||||
// TODO (MR): Remove the need to lowercase it, change the enum itself.
|
||||
const appIdL = appId.toLowerCase();
|
||||
const userID = (getData(LS_KEYS.USER) as User)?.id;
|
||||
const sha = process.env.GIT_SHA;
|
||||
const buildId = isDevBuild ? "dev " : sha ? `git ${sha} ` : "";
|
||||
|
||||
addLogLine(`ente-${appIdL}-web ${buildId}uid ${userID}`);
|
||||
};
|
||||
|
||||
function getLogs(): Log[] {
|
||||
return getData(LS_KEYS.LOGS)?.logs ?? [];
|
||||
}
|
||||
|
||||
function setLogs(logs: Log[]) {
|
||||
setData(LS_KEYS.LOGS, { logs });
|
||||
}
|
||||
|
||||
function deleteLogs() {
|
||||
removeData(LS_KEYS.LOGS);
|
||||
}
|
||||
|
||||
function getStringSize(str: string) {
|
||||
return new Blob([str]).size;
|
||||
}
|
||||
|
||||
export function formatLog(log: Log) {
|
||||
return `[${formatDateTimeShort(log.timestamp)}] ${log.logLine}`;
|
||||
}
|
||||
|
||||
function combineLogLines(logs: Log[]) {
|
||||
return logs.map(formatLog).join("\n");
|
||||
}
|
||||
@@ -14,13 +14,13 @@ export enum LS_KEYS {
|
||||
EXPORT = "export",
|
||||
THUMBNAIL_FIX_STATE = "thumbnailFixState",
|
||||
LIVE_PHOTO_INFO_SHOWN_COUNT = "livePhotoInfoShownCount",
|
||||
LOGS = "logs",
|
||||
// LOGS = "logs",
|
||||
USER_DETAILS = "userDetails",
|
||||
COLLECTION_SORT_BY = "collectionSortBy",
|
||||
THEME = "theme",
|
||||
WAIT_TIME = "waitTime",
|
||||
API_ENDPOINT = "apiEndpoint",
|
||||
// Moved to the new wrapper @/utils/local-storage
|
||||
// Moved to the new wrapper @/next/local-storage
|
||||
// LOCALE = 'locale',
|
||||
MAP_ENABLED = "mapEnabled",
|
||||
SRP_SETUP_ATTRIBUTES = "srpSetupAttributes",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user