Merge branch 'main' into bundle-ml-demo
This commit is contained in:
commit
67177bbdff
43 changed files with 976 additions and 195 deletions
57
.github/workflows/build-old.yml
vendored
Normal file
57
.github/workflows/build-old.yml
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
name: Build/release-old
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Prepare for app notarization
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
# Import Apple API key for app notarization on macOS
|
||||
run: |
|
||||
mkdir -p ~/private_keys/
|
||||
echo '${{ secrets.api_key_old }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id_old }}.p8
|
||||
|
||||
- name: Install libarchive-tools for pacman build # Related https://github.com/electron-userland/electron-builder/issues/4181
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: sudo apt-get install libarchive-tools
|
||||
|
||||
- name: Electron Builder Action
|
||||
uses: samuelmeuli/action-electron-builder@v1.6.0
|
||||
with:
|
||||
# GitHub token, automatically provided to the action
|
||||
# (No need to define this secret in the repo settings)
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
||||
# If the commit is tagged with a version (e.g. "v1.0.0"),
|
||||
# release the app after building
|
||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
|
||||
mac_certs: ${{ secrets.mac_certs_old }}
|
||||
mac_certs_password: ${{ secrets.mac_certs_password_old }}
|
||||
env:
|
||||
# macOS notarization API key
|
||||
API_KEY_ID: ${{ secrets.api_key_id_old }}
|
||||
API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id_old }}
|
||||
# setry crash reporting token
|
||||
SENTRY_AUTH_TOKEN: ${{secrets.sentry_auth_token}}
|
||||
USE_HARD_LINKS: false
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,3 +7,5 @@ buildingSteps.md
|
|||
.DS_Store
|
||||
.idea/
|
||||
build/.DS_Store
|
||||
.env
|
||||
.electron-symbols/
|
||||
|
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,7 +1,7 @@
|
|||
[submodule "bada-frame"]
|
||||
path = ui
|
||||
url = https://github.com/ente-io/bada-frame
|
||||
branch = demo
|
||||
branch = release
|
||||
[submodule "thirdparty/next-electron-server"]
|
||||
path = thirdparty/next-electron-server
|
||||
url = https://github.com/ente-io/next-electron-server.git
|
||||
|
|
14
README.md
14
README.md
|
@ -1,4 +1,4 @@
|
|||
# bhari-frame (heavy-frame)
|
||||
# ente Photos - Desktop
|
||||
|
||||
Desktop app for [ente.io](https://ente.io) build with [electron](https://electronjs.org) and loads of ❤️.
|
||||
|
||||
|
@ -10,7 +10,7 @@ The goal of this app was to
|
|||
1. provide a stable environment for customers to back up large amounts of data reliably
|
||||
2. export uploaded data from our servers to their local hard drives.
|
||||
|
||||
Electron was the best way to reuse our battle tested code from [bada-frame](https://github.com/ente-io/bada-frame) that powers [web.ente.io](https://web.ente.io).
|
||||
Electron was the best way to reuse our battle tested code from [photos-web](https://github.com/ente-io/photos-web) that powers [web.ente.io](https://web.ente.io).
|
||||
|
||||
As an archival solution built by a small team, we are hopeful that this project will help us keep our stack lean, while ensuring a painfree life for our customers.
|
||||
|
||||
|
@ -18,12 +18,8 @@ If you are running into issues with this app, please drop a mail to [support@ent
|
|||
|
||||
## Download
|
||||
|
||||
- [Latest Release](https://github.com/ente-io/bhari-frame/releases/latest)
|
||||
- [Latest Release](https://github.com/ente-io/photos-desktop/releases/latest)
|
||||
|
||||
*User contributed ports*
|
||||
|
||||
- [AUR](https://aur.archlinux.org/packages/ente-desktop-appimage):
|
||||
`yay -S ente-desktop-appimage`
|
||||
|
||||
## Building from source
|
||||
|
||||
|
@ -33,10 +29,10 @@ fetch and build from source.
|
|||
|
||||
```bash
|
||||
# Clone this repository
|
||||
git clone https://github.com/ente-io/bhari-frame
|
||||
git clone https://github.com/ente-io/photos-desktop
|
||||
|
||||
# Go into the repository
|
||||
cd bhari-frame
|
||||
cd photos-desktop
|
||||
|
||||
# Clone submodules (recursively)
|
||||
git submodule update --init --recursive
|
||||
|
|
BIN
build/image-magick
Executable file
BIN
build/image-magick
Executable file
Binary file not shown.
BIN
build/taskbar-icon-Template.png
Normal file
BIN
build/taskbar-icon-Template.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 989 B |
BIN
build/taskbar-icon-Template@2x.png
Normal file
BIN
build/taskbar-icon-Template@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
build/taskbar-icon-Template@3x.png
Normal file
BIN
build/taskbar-icon-Template@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 607 B |
28
package.json
28
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "ente",
|
||||
"productName": "ente",
|
||||
"version": "1.7.0-alpha.7",
|
||||
"version": "1.6.16",
|
||||
"private": true,
|
||||
"description": "Desktop client for ente.io",
|
||||
"main": "app/main.js",
|
||||
|
@ -39,6 +39,11 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"asarUnpack": [
|
||||
"node_modules/ffmpeg-static/bin/${os}/${arch}/ffmpeg",
|
||||
"node_modules/ffmpeg-static/index.js",
|
||||
"node_modules/ffmpeg-static/package.json"
|
||||
],
|
||||
"files": [
|
||||
"app/**/*",
|
||||
{
|
||||
|
@ -63,17 +68,22 @@
|
|||
"start": "concurrently \"yarn start-main\" \"yarn start-renderer\"",
|
||||
"build-renderer": "cd ui && yarn install && yarn build && cd ..",
|
||||
"build": "yarn build-renderer && yarn build-main",
|
||||
"test-release": "yarn build && electron-builder"
|
||||
"test-release": "yarn build && electron-builder --config.compression=store"
|
||||
},
|
||||
"author": "ente <code@ente.io>",
|
||||
"devDependencies": {
|
||||
"@sentry/cli": "^1.68.0",
|
||||
"@types/auto-launch": "^5.0.2",
|
||||
"@types/ffmpeg-static": "^3.0.1",
|
||||
"@types/get-folder-size": "^2.0.0",
|
||||
"@types/node": "^16.18.3",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/promise-fs": "^2.1.1",
|
||||
"@types/semver-compare": "^1.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.28.0",
|
||||
"@typescript-eslint/parser": "^5.28.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"electron": "^15.3.0",
|
||||
"electron": "^21.2.2",
|
||||
"electron-builder": "^23.0.3",
|
||||
"electron-builder-notarize": "^1.2.0",
|
||||
"electron-download": "^4.1.1",
|
||||
|
@ -87,18 +97,20 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@sentry/electron": "^2.5.1",
|
||||
"@types/node": "^14.14.37",
|
||||
"@types/promise-fs": "^2.1.1",
|
||||
"chokidar": "^3.5.3",
|
||||
"any-shell-escape": "^0.1.1",
|
||||
"auto-launch": "^5.0.5",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron-log": "^4.3.5",
|
||||
"electron-reload": "^2.0.0-alpha.1",
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "^4.3.8",
|
||||
"ffmpeg-static": "^5.1.0",
|
||||
"get-folder-size": "^2.0.1",
|
||||
"next-electron-server": "file:./thirdparty/next-electron-server",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"promise-fs": "^2.1.1"
|
||||
"promise-fs": "^2.1.1",
|
||||
"semver-compare": "^1.0.0"
|
||||
},
|
||||
"standard": {
|
||||
"parser": "babel-eslint"
|
||||
|
@ -109,4 +121,4 @@
|
|||
"prettier --write --ignore-unknown"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,15 +12,13 @@ try {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
const VERSION = /\bv?(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?\b/i;
|
||||
const SYMBOL_CACHE_FOLDER = '.electron-symbols';
|
||||
const package = require('./package.json');
|
||||
const sentryCli = new SentryCli('./sentry.properties');
|
||||
|
||||
async function main() {
|
||||
let version = getElectronVersion();
|
||||
const version = getElectronVersion();
|
||||
if (!version) {
|
||||
console.error('Cannot detect electron version, check package.json');
|
||||
console.error('Cannot detect electron version, check that electron is installed');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -68,20 +66,11 @@ async function main() {
|
|||
}
|
||||
|
||||
function getElectronVersion() {
|
||||
if (!package) {
|
||||
return false;
|
||||
try {
|
||||
return require('electron/package.json').version;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let electronVersion =
|
||||
(package.dependencies && package.dependencies.electron) ||
|
||||
(package.devDependencies && package.devDependencies.electron);
|
||||
|
||||
if (!electronVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const matches = VERSION.exec(electronVersion);
|
||||
return matches ? matches[0] : false;
|
||||
}
|
||||
|
||||
async function downloadSymbols(options) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
defaults.url=https://sentry.ente.io/
|
||||
defaults.org=ente
|
||||
defaults.project=bhari-frame
|
||||
cli.executable=../../../../usr/local/lib/node_modules/@sentry/wizard/node_modules/@sentry/cli/bin/sentry-cli
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ipcRenderer } from 'electron/renderer';
|
||||
import { logError } from '../utils/logging';
|
||||
import { logError } from '../services/logging';
|
||||
|
||||
export const selectRootDirectory = async (): Promise<string> => {
|
||||
try {
|
||||
|
@ -8,3 +8,18 @@ export const selectRootDirectory = async (): Promise<string> => {
|
|||
logError(e, 'error while selecting root directory');
|
||||
}
|
||||
};
|
||||
|
||||
export const getAppVersion = async (): Promise<string> => {
|
||||
try {
|
||||
return await ipcRenderer.invoke('get-app-version');
|
||||
} catch (e) {
|
||||
logError(e, 'failed to get release version');
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
logToDisk,
|
||||
openLogDirectory,
|
||||
getSentryUserID,
|
||||
} from '../services/logging';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { keysStore } from '../stores/keys.store';
|
||||
import { safeStorageStore } from '../stores/safeStorage.store';
|
||||
import { uploadStatusStore } from '../stores/upload.store';
|
||||
import { logError } from '../utils/logging';
|
||||
import { logError } from '../services/logging';
|
||||
|
||||
export const clearElectronStore = () => {
|
||||
try {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { readTextFile, writeStream } from './../services/fs';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { logError } from '../utils/logging';
|
||||
import { logError } from '../services/logging';
|
||||
import * as fs from 'promise-fs';
|
||||
|
||||
export const exists = (path: string) => {
|
||||
|
|
41
src/api/ffmpeg.ts
Normal file
41
src/api/ffmpeg.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { ipcRenderer } from 'electron';
|
||||
import { existsSync } from 'fs';
|
||||
import { logError } from '../services/logging';
|
||||
import { ElectronFile } from '../types';
|
||||
|
||||
export async function runFFmpegCmd(
|
||||
cmd: string[],
|
||||
inputFile: File | ElectronFile,
|
||||
outputFileName: string
|
||||
) {
|
||||
let inputFilePath = null;
|
||||
let createdTempInputFile = null;
|
||||
try {
|
||||
if (!existsSync(inputFile.path)) {
|
||||
const inputFileData = new Uint8Array(await inputFile.arrayBuffer());
|
||||
inputFilePath = await ipcRenderer.invoke(
|
||||
'write-temp-file',
|
||||
inputFileData,
|
||||
inputFile.name
|
||||
);
|
||||
createdTempInputFile = true;
|
||||
} else {
|
||||
inputFilePath = inputFile.path;
|
||||
}
|
||||
const outputFileData = await ipcRenderer.invoke(
|
||||
'run-ffmpeg-cmd',
|
||||
cmd,
|
||||
inputFilePath,
|
||||
outputFileName
|
||||
);
|
||||
return new File([outputFileData], outputFileName);
|
||||
} finally {
|
||||
if (createdTempInputFile) {
|
||||
try {
|
||||
await ipcRenderer.invoke('remove-temp-file', inputFilePath);
|
||||
} catch (e) {
|
||||
logError(e, 'failed to deleteTempFile');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
src/api/heicConvert.ts
Normal file
9
src/api/heicConvert.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { ipcRenderer } from 'electron/renderer';
|
||||
|
||||
export async function convertHEIC(fileData: Uint8Array): Promise<Uint8Array> {
|
||||
const convertedFileData = await ipcRenderer.invoke(
|
||||
'convert-heic',
|
||||
fileData
|
||||
);
|
||||
return convertedFileData;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { ipcRenderer } from 'electron';
|
||||
import { safeStorageStore } from '../stores/safeStorage.store';
|
||||
import { logError } from '../utils/logging';
|
||||
import { logError } from '../services/logging';
|
||||
|
||||
export async function setEncryptionKey(encryptionKey: string) {
|
||||
try {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ipcRenderer } from 'electron';
|
||||
import { AppUpdateInfo } from '../types';
|
||||
|
||||
export const sendNotification = (content: string) => {
|
||||
ipcRenderer.send('send-notification', content);
|
||||
|
@ -9,3 +10,20 @@ export const showOnTray = (content: string) => {
|
|||
export const reloadWindow = () => {
|
||||
ipcRenderer.send('reload-window');
|
||||
};
|
||||
|
||||
export const registerUpdateEventListener = (
|
||||
showUpdateDialog: (updateInfo: AppUpdateInfo) => void
|
||||
) => {
|
||||
ipcRenderer.removeAllListeners('show-update-dialog');
|
||||
ipcRenderer.on('show-update-dialog', (_, updateInfo: AppUpdateInfo) => {
|
||||
showUpdateDialog(updateInfo);
|
||||
});
|
||||
};
|
||||
|
||||
export const updateAndRestart = () => {
|
||||
ipcRenderer.send('update-and-restart');
|
||||
};
|
||||
|
||||
export const skipAppVersion = (version: string) => {
|
||||
ipcRenderer.send('skip-app-version', version);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { getElectronFile } from './../services/fs';
|
||||
import { uploadStatusStore } from '../stores/upload.store';
|
||||
import { ElectronFile, FILE_PATH_TYPE } from '../types';
|
||||
import { logError } from '../utils/logging';
|
||||
import { logError } from '../services/logging';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import {
|
||||
getElectronFilesFromGoogleZip,
|
||||
|
@ -18,7 +18,10 @@ export const getPendingUploads = async () => {
|
|||
if (zipPaths.length) {
|
||||
type = FILE_PATH_TYPE.ZIPS;
|
||||
for (const zipPath of zipPaths) {
|
||||
files.push(...(await getElectronFilesFromGoogleZip(zipPath)));
|
||||
files = [
|
||||
...files,
|
||||
...(await getElectronFilesFromGoogleZip(zipPath)),
|
||||
];
|
||||
}
|
||||
const pendingFilePaths = new Set(filePaths);
|
||||
files = files.filter((file) => pendingFilePaths.has(file.path));
|
||||
|
@ -62,10 +65,13 @@ export const showUploadZipDialog = async () => {
|
|||
const filePaths: string[] = await ipcRenderer.invoke(
|
||||
'show-upload-zip-dialog'
|
||||
);
|
||||
const files: ElectronFile[] = [];
|
||||
let files: ElectronFile[] = [];
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
files.push(...(await getElectronFilesFromGoogleZip(filePath)));
|
||||
files = [
|
||||
...files,
|
||||
...(await getElectronFilesFromGoogleZip(filePath)),
|
||||
];
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,6 +1,20 @@
|
|||
const PROD_HOST_URL: string = 'ente://app';
|
||||
const RENDERER_OUTPUT_DIR: string = './ui/out';
|
||||
const LOG_FILENAME = 'ente.log';
|
||||
const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
const FILE_STREAM_CHUNK_SIZE: number = 4 * 1024 * 1024;
|
||||
|
||||
export { PROD_HOST_URL, RENDERER_OUTPUT_DIR, FILE_STREAM_CHUNK_SIZE };
|
||||
const SENTRY_DSN = 'https://e9268b784d1042a7a116f53c58ad2165@sentry.ente.io/5';
|
||||
|
||||
const RELEASE_VERSION = require('../../package.json').version;
|
||||
|
||||
export {
|
||||
PROD_HOST_URL,
|
||||
RENDERER_OUTPUT_DIR,
|
||||
FILE_STREAM_CHUNK_SIZE,
|
||||
LOG_FILENAME,
|
||||
MAX_LOG_SIZE,
|
||||
SENTRY_DSN,
|
||||
RELEASE_VERSION,
|
||||
};
|
||||
|
|
12
src/main.ts
12
src/main.ts
|
@ -13,8 +13,12 @@ import {
|
|||
setupNextElectronServe,
|
||||
enableSharedArrayBufferSupport,
|
||||
handleDockIconHideOnAutoLaunch,
|
||||
logSystemInfo,
|
||||
} from './utils/main';
|
||||
import { initSentry } from './services/sentry';
|
||||
import { setupLogging } from './utils/logging';
|
||||
import { isDev } from './utils/common';
|
||||
import { setupMainProcessStatsLogger } from './utils/processStats';
|
||||
|
||||
let mainWindow: BrowserWindow;
|
||||
|
||||
|
@ -41,6 +45,8 @@ setupMainHotReload();
|
|||
|
||||
setupNextElectronServe();
|
||||
|
||||
setupLogging(isDev);
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
app.quit();
|
||||
|
@ -62,14 +68,16 @@ if (!gotTheLock) {
|
|||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', async () => {
|
||||
logSystemInfo();
|
||||
setupMainProcessStatsLogger();
|
||||
initSentry();
|
||||
mainWindow = await createWindow();
|
||||
const tray = setupTrayItem(mainWindow);
|
||||
const watcher = initWatcher(mainWindow);
|
||||
setupMacWindowOnDockIconClick();
|
||||
initSentry();
|
||||
setupMainMenu();
|
||||
setupIpcComs(tray, mainWindow, watcher);
|
||||
handleUpdates(mainWindow, tray);
|
||||
handleUpdates(mainWindow);
|
||||
handleDownloads(mainWindow);
|
||||
addAllowOriginHeader(mainWindow);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { reloadWindow, sendNotification, showOnTray } from './api/system';
|
||||
import {
|
||||
registerUpdateEventListener,
|
||||
reloadWindow,
|
||||
sendNotification,
|
||||
showOnTray,
|
||||
updateAndRestart,
|
||||
skipAppVersion,
|
||||
} from './api/system';
|
||||
import {
|
||||
showUploadDirsDialog,
|
||||
showUploadFilesDialog,
|
||||
|
@ -32,11 +39,23 @@ import {
|
|||
setExportRecord,
|
||||
exists,
|
||||
} from './api/export';
|
||||
import { selectRootDirectory } from './api/common';
|
||||
import {
|
||||
selectRootDirectory,
|
||||
logToDisk,
|
||||
openLogDirectory,
|
||||
getSentryUserID,
|
||||
getAppVersion,
|
||||
} from './api/common';
|
||||
import { fixHotReloadNext12 } from './utils/preload';
|
||||
import { isFolder, getDirFiles } from './api/fs';
|
||||
import { convertHEIC } from './api/heicConvert';
|
||||
import { setupLogging } from './utils/logging';
|
||||
import { setupRendererProcessStatsLogger } from './utils/processStats';
|
||||
import { runFFmpegCmd } from './api/ffmpeg';
|
||||
|
||||
fixHotReloadNext12();
|
||||
setupLogging();
|
||||
setupRendererProcessStatsLogger();
|
||||
|
||||
const windowObject: any = window;
|
||||
|
||||
|
@ -76,4 +95,13 @@ windowObject['ElectronAPIs'] = {
|
|||
isFolder,
|
||||
updateWatchMappingSyncedFiles,
|
||||
updateWatchMappingIgnoredFiles,
|
||||
logToDisk,
|
||||
convertHEIC,
|
||||
openLogDirectory,
|
||||
registerUpdateEventListener,
|
||||
updateAndRestart,
|
||||
skipAppVersion,
|
||||
getSentryUserID,
|
||||
getAppVersion,
|
||||
runFFmpegCmd,
|
||||
};
|
||||
|
|
|
@ -1,39 +1,117 @@
|
|||
import { BrowserWindow, dialog, Tray } from 'electron';
|
||||
import { app, BrowserWindow } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import log from 'electron-log';
|
||||
import { setIsAppQuitting, setIsUpdateAvailable } from '../main';
|
||||
import { buildContextMenu } from '../utils/menu';
|
||||
import semVerCmp from 'semver-compare';
|
||||
import { AppUpdateInfo, GetFeatureFlagResponse } from '../types';
|
||||
import { getSkipAppVersion, setSkipAppVersion } from './userPreference';
|
||||
import fetch from 'node-fetch';
|
||||
import { logErrorSentry } from './sentry';
|
||||
import ElectronLog from 'electron-log';
|
||||
import { isPlatform } from '../utils/main';
|
||||
|
||||
class AppUpdater {
|
||||
constructor() {
|
||||
log.transports.file.level = 'debug';
|
||||
autoUpdater.logger = log;
|
||||
}
|
||||
const FIVE_MIN_IN_MICROSECOND = 5 * 60 * 1000;
|
||||
|
||||
async checkForUpdate(tray: Tray, mainWindow: BrowserWindow) {
|
||||
await autoUpdater.checkForUpdatesAndNotify();
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
showUpdateDialog();
|
||||
setIsUpdateAvailable(true);
|
||||
tray.setContextMenu(buildContextMenu(mainWindow));
|
||||
});
|
||||
export function setupAutoUpdater() {
|
||||
autoUpdater.logger = log;
|
||||
autoUpdater.autoDownload = false;
|
||||
}
|
||||
|
||||
export async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
|
||||
try {
|
||||
log.debug('checkForUpdateAndNotify called');
|
||||
const updateCheckResult = await autoUpdater.checkForUpdates();
|
||||
log.debug('update version', updateCheckResult.updateInfo.version);
|
||||
if (
|
||||
semVerCmp(updateCheckResult.updateInfo.version, app.getVersion()) <=
|
||||
0
|
||||
) {
|
||||
log.debug('already at latest version');
|
||||
return;
|
||||
}
|
||||
const skipAppVersion = getSkipAppVersion();
|
||||
if (
|
||||
skipAppVersion &&
|
||||
updateCheckResult.updateInfo.version === skipAppVersion
|
||||
) {
|
||||
log.info(
|
||||
'user chose to skip version ',
|
||||
updateCheckResult.updateInfo.version
|
||||
);
|
||||
return;
|
||||
}
|
||||
const desktopCutoffVersion = await getDesktopCutoffVersion();
|
||||
if (
|
||||
desktopCutoffVersion &&
|
||||
isPlatform('mac') &&
|
||||
semVerCmp(
|
||||
updateCheckResult.updateInfo.version,
|
||||
desktopCutoffVersion
|
||||
) > 0
|
||||
) {
|
||||
log.debug('auto update not possible due to key change');
|
||||
showUpdateDialog(mainWindow, {
|
||||
autoUpdatable: false,
|
||||
version: updateCheckResult.updateInfo.version,
|
||||
});
|
||||
} else {
|
||||
let timeout: NodeJS.Timeout;
|
||||
log.debug('attempting auto update');
|
||||
autoUpdater.downloadUpdate();
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
timeout = setTimeout(
|
||||
() =>
|
||||
showUpdateDialog(mainWindow, {
|
||||
autoUpdatable: true,
|
||||
version: updateCheckResult.updateInfo.version,
|
||||
}),
|
||||
FIVE_MIN_IN_MICROSECOND
|
||||
);
|
||||
});
|
||||
autoUpdater.on('error', (error) => {
|
||||
clearTimeout(timeout);
|
||||
logErrorSentry(error, 'auto update failed');
|
||||
showUpdateDialog(mainWindow, {
|
||||
autoUpdatable: false,
|
||||
version: updateCheckResult.updateInfo.version,
|
||||
});
|
||||
});
|
||||
}
|
||||
setIsUpdateAvailable(true);
|
||||
} catch (e) {
|
||||
logErrorSentry(e, 'checkForUpdateAndNotify failed');
|
||||
}
|
||||
}
|
||||
|
||||
export default new AppUpdater();
|
||||
export function updateAndRestart() {
|
||||
ElectronLog.log('user quit the app');
|
||||
setIsAppQuitting(true);
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
|
||||
export const showUpdateDialog = (): void => {
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: 'Install update',
|
||||
message: 'Restart to update to the latest version of ente',
|
||||
buttons: ['Later', 'Restart now'],
|
||||
})
|
||||
.then((buttonIndex) => {
|
||||
if (buttonIndex.response === 1) {
|
||||
setIsAppQuitting(true);
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
});
|
||||
};
|
||||
export function getAppVersion() {
|
||||
return `v${app.getVersion()}`;
|
||||
}
|
||||
|
||||
export function skipAppVersion(version: string) {
|
||||
setSkipAppVersion(version);
|
||||
}
|
||||
|
||||
async function getDesktopCutoffVersion() {
|
||||
try {
|
||||
const featureFlags = (
|
||||
await fetch('https://static.ente.io/feature_flags.json')
|
||||
).json() as GetFeatureFlagResponse;
|
||||
return featureFlags.desktopCutoffVersion;
|
||||
} catch (e) {
|
||||
logErrorSentry(e, 'failed to get feature flags');
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function showUpdateDialog(
|
||||
mainWindow: BrowserWindow,
|
||||
updateInfo: AppUpdateInfo
|
||||
) {
|
||||
mainWindow.webContents.send('show-update-dialog', updateInfo);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { isPlatform } from '../utils/main';
|
||||
import { AutoLauncherClient } from '../types/autoLauncher';
|
||||
import { isPlatformWindows, isPlatformMac } from '../utils/main';
|
||||
import linuxAutoLauncher from './autoLauncherClients/linuxAutoLauncher';
|
||||
import macAndWindowsAutoLauncher from './autoLauncherClients/macAndWindowsAutoLauncher';
|
||||
|
||||
class AutoLauncher {
|
||||
private client: AutoLauncherClient;
|
||||
init() {
|
||||
if (isPlatformMac() || isPlatformWindows()) {
|
||||
if (isPlatform('mac') || isPlatform('windows')) {
|
||||
this.client = macAndWindowsAutoLauncher;
|
||||
} else {
|
||||
this.client = linuxAutoLauncher;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import chokidar from 'chokidar';
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { logError } from '../utils/logging';
|
||||
import { logError } from '../services/logging';
|
||||
import { getWatchMappings } from '../api/watch';
|
||||
|
||||
export function initWatcher(mainWindow: BrowserWindow) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import path from 'path';
|
|||
import { readdir, stat, unlink } from 'promise-fs';
|
||||
import getFolderSize from 'get-folder-size';
|
||||
import { utimes, close, open } from 'promise-fs';
|
||||
import { logError } from '../utils/logging';
|
||||
import { logError } from '../services/logging';
|
||||
|
||||
export interface LeastRecentlyUsedResult {
|
||||
atime: Date;
|
||||
|
|
75
src/services/ffmpeg.ts
Normal file
75
src/services/ffmpeg.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
import pathToFfmpeg from 'ffmpeg-static';
|
||||
const shellescape = require('any-shell-escape');
|
||||
import util from 'util';
|
||||
import log from 'electron-log';
|
||||
import { readFile, rmSync, writeFile } from 'promise-fs';
|
||||
import { logErrorSentry } from './sentry';
|
||||
import { generateTempFilePath, getTempDirPath } from '../utils/temp';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
const execAsync = util.promisify(require('child_process').exec);
|
||||
|
||||
export const INPUT_PATH_PLACEHOLDER = 'INPUT';
|
||||
export const FFMPEG_PLACEHOLDER = 'FFMPEG';
|
||||
export const OUTPUT_PATH_PLACEHOLDER = 'OUTPUT';
|
||||
|
||||
function getFFmpegStaticPath() {
|
||||
return pathToFfmpeg.replace('app.asar', 'app.asar.unpacked');
|
||||
}
|
||||
|
||||
export async function runFFmpegCmd(
|
||||
cmd: string[],
|
||||
inputFilePath: string,
|
||||
outputFileName: string
|
||||
) {
|
||||
let tempOutputFilePath: string;
|
||||
try {
|
||||
tempOutputFilePath = await generateTempFilePath(outputFileName);
|
||||
|
||||
cmd = cmd.map((cmdPart) => {
|
||||
if (cmdPart === FFMPEG_PLACEHOLDER) {
|
||||
return getFFmpegStaticPath();
|
||||
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
|
||||
return inputFilePath;
|
||||
} else if (cmdPart === OUTPUT_PATH_PLACEHOLDER) {
|
||||
return tempOutputFilePath;
|
||||
} else {
|
||||
return cmdPart;
|
||||
}
|
||||
});
|
||||
cmd = shellescape(cmd);
|
||||
log.info('cmd', cmd);
|
||||
await execAsync(cmd);
|
||||
if (!existsSync(tempOutputFilePath)) {
|
||||
throw new Error('ffmpeg output file not found');
|
||||
}
|
||||
const outputFile = await readFile(tempOutputFilePath);
|
||||
return new Uint8Array(outputFile);
|
||||
} catch (e) {
|
||||
logErrorSentry(e, 'ffmpeg run command error');
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
rmSync(tempOutputFilePath, { force: true });
|
||||
} catch (e) {
|
||||
logErrorSentry(e, 'failed to remove tempOutputFile');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function writeTempFile(fileStream: Uint8Array, fileName: string) {
|
||||
const tempFilePath = await generateTempFilePath(fileName);
|
||||
await writeFile(tempFilePath, fileStream);
|
||||
return tempFilePath;
|
||||
}
|
||||
|
||||
export async function deleteTempFile(tempFilePath: string) {
|
||||
const tempDirPath = await getTempDirPath();
|
||||
if (!tempFilePath.startsWith(tempDirPath)) {
|
||||
logErrorSentry(
|
||||
Error('not a temp file'),
|
||||
'tried to delete a non temp file'
|
||||
);
|
||||
}
|
||||
rmSync(tempFilePath, { force: true });
|
||||
}
|
|
@ -4,6 +4,8 @@ import * as fs from 'promise-fs';
|
|||
import { ElectronFile } from '../types';
|
||||
import StreamZip from 'node-stream-zip';
|
||||
import { Readable } from 'stream';
|
||||
import { logError } from './logging';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
// https://stackoverflow.com/a/63111390
|
||||
export const getDirFilePaths = async (dirPath: string) => {
|
||||
|
@ -16,7 +18,7 @@ export const getDirFilePaths = async (dirPath: string) => {
|
|||
|
||||
for (const filePath of filePaths) {
|
||||
const absolute = path.join(dirPath, filePath);
|
||||
files = files.concat(await getDirFilePaths(absolute));
|
||||
files = [...files, ...(await getDirFilePaths(absolute))];
|
||||
}
|
||||
|
||||
return files;
|
||||
|
@ -48,6 +50,9 @@ export const getFileStream = async (filePath: string) => {
|
|||
await fs.close(file);
|
||||
}
|
||||
},
|
||||
async cancel() {
|
||||
await fs.close(file);
|
||||
},
|
||||
});
|
||||
return readableStream;
|
||||
};
|
||||
|
@ -60,13 +65,22 @@ export async function getElectronFile(filePath: string): Promise<ElectronFile> {
|
|||
size: fileStats.size,
|
||||
lastModified: fileStats.mtime.valueOf(),
|
||||
stream: async () => {
|
||||
if (!existsSync(filePath)) {
|
||||
throw new Error('electronFile does not exist');
|
||||
}
|
||||
return await getFileStream(filePath);
|
||||
},
|
||||
blob: async () => {
|
||||
if (!existsSync(filePath)) {
|
||||
throw new Error('electronFile does not exist');
|
||||
}
|
||||
const blob = await fs.readFile(filePath);
|
||||
return new Blob([new Uint8Array(blob)]);
|
||||
},
|
||||
arrayBuffer: async () => {
|
||||
if (!existsSync(filePath)) {
|
||||
throw new Error('electronFile does not exist');
|
||||
}
|
||||
const blob = await fs.readFile(filePath);
|
||||
return new Uint8Array(blob);
|
||||
},
|
||||
|
@ -94,31 +108,50 @@ export const getZipFileStream = async (
|
|||
const done = {
|
||||
current: false,
|
||||
};
|
||||
const inProgress = {
|
||||
current: false,
|
||||
};
|
||||
let resolveObj: (value?: any) => void = null;
|
||||
let rejectObj: (reason?: any) => void = null;
|
||||
stream.on('readable', () => {
|
||||
if (resolveObj) {
|
||||
const chunk = stream.read(FILE_STREAM_CHUNK_SIZE) as Buffer;
|
||||
|
||||
if (chunk) {
|
||||
resolveObj(new Uint8Array(chunk));
|
||||
resolveObj = null;
|
||||
try {
|
||||
if (resolveObj) {
|
||||
inProgress.current = true;
|
||||
const chunk = stream.read(FILE_STREAM_CHUNK_SIZE) as Buffer;
|
||||
if (chunk) {
|
||||
resolveObj(new Uint8Array(chunk));
|
||||
resolveObj = null;
|
||||
}
|
||||
inProgress.current = false;
|
||||
}
|
||||
} catch (e) {
|
||||
rejectObj(e);
|
||||
}
|
||||
});
|
||||
stream.on('end', () => {
|
||||
done.current = true;
|
||||
try {
|
||||
done.current = true;
|
||||
if (resolveObj && !inProgress.current) {
|
||||
resolveObj(null);
|
||||
resolveObj = null;
|
||||
}
|
||||
} catch (e) {
|
||||
rejectObj(e);
|
||||
}
|
||||
});
|
||||
stream.on('error', (e) => {
|
||||
done.current = true;
|
||||
|
||||
if (rejectObj) {
|
||||
try {
|
||||
done.current = true;
|
||||
if (rejectObj) {
|
||||
rejectObj(e);
|
||||
rejectObj = null;
|
||||
}
|
||||
} catch (e) {
|
||||
rejectObj(e);
|
||||
rejectObj = null;
|
||||
}
|
||||
});
|
||||
|
||||
const readStreamData = () => {
|
||||
const readStreamData = async () => {
|
||||
return new Promise<Uint8Array>((resolve, reject) => {
|
||||
const chunk = stream.read(FILE_STREAM_CHUNK_SIZE) as Buffer;
|
||||
|
||||
|
@ -142,6 +175,7 @@ export const getZipFileStream = async (
|
|||
controller.close();
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, 'readableStream pull failed');
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
|
@ -183,5 +217,8 @@ export function writeStream(filePath: string, fileStream: any) {
|
|||
}
|
||||
|
||||
export async function readTextFile(filePath: string) {
|
||||
if (!existsSync(filePath)) {
|
||||
throw new Error('File does not exist');
|
||||
}
|
||||
return await fs.readFile(filePath, 'utf-8');
|
||||
}
|
||||
|
|
72
src/services/heicConverter.ts
Normal file
72
src/services/heicConverter.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import util from 'util';
|
||||
import { exec } from 'child_process';
|
||||
|
||||
import { existsSync, rmSync } from 'fs';
|
||||
import { readFile, writeFile } from 'promise-fs';
|
||||
import { generateTempFilePath } from '../utils/temp';
|
||||
import { logErrorSentry } from './sentry';
|
||||
import { isPlatform } from '../utils/main';
|
||||
import { isDev } from '../utils/common';
|
||||
import path from 'path';
|
||||
|
||||
const asyncExec = util.promisify(exec);
|
||||
|
||||
function getImageMagickStaticPath() {
|
||||
return isDev
|
||||
? 'build/image-magick'
|
||||
: path.join(process.resourcesPath, 'image-magick');
|
||||
}
|
||||
|
||||
export async function convertHEIC(
|
||||
heicFileData: Uint8Array
|
||||
): Promise<Uint8Array> {
|
||||
let tempInputFilePath: string;
|
||||
let tempOutputFilePath: string;
|
||||
try {
|
||||
tempInputFilePath = await generateTempFilePath('.heic');
|
||||
tempOutputFilePath = await generateTempFilePath('.jpeg');
|
||||
|
||||
await writeFile(tempInputFilePath, heicFileData);
|
||||
|
||||
await runConvertCommand(tempInputFilePath, tempOutputFilePath);
|
||||
|
||||
if (!existsSync(tempOutputFilePath)) {
|
||||
throw new Error('heic convert output file not found');
|
||||
}
|
||||
const convertedFileData = new Uint8Array(
|
||||
await readFile(tempOutputFilePath)
|
||||
);
|
||||
return convertedFileData;
|
||||
} catch (e) {
|
||||
logErrorSentry(e, 'failed to convert heic');
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
rmSync(tempInputFilePath, { force: true });
|
||||
} catch (e) {
|
||||
logErrorSentry(e, 'failed to remove tempInputFile');
|
||||
}
|
||||
try {
|
||||
rmSync(tempOutputFilePath, { force: true });
|
||||
} catch (e) {
|
||||
logErrorSentry(e, 'failed to remove tempOutputFile');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function runConvertCommand(
|
||||
tempInputFilePath: string,
|
||||
tempOutputFilePath: string
|
||||
) {
|
||||
if (isPlatform('mac')) {
|
||||
await asyncExec(
|
||||
`sips -s format jpeg ${tempInputFilePath} --out ${tempOutputFilePath}`
|
||||
);
|
||||
} else if (isPlatform('linux')) {
|
||||
await asyncExec(
|
||||
`${getImageMagickStaticPath()} ${tempInputFilePath} -quality 100% ${tempOutputFilePath}`
|
||||
);
|
||||
} else {
|
||||
Error(`${process.platform} native heic convert not supported yet`);
|
||||
}
|
||||
}
|
18
src/services/logging.ts
Normal file
18
src/services/logging.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import log from 'electron-log';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export function logToDisk(logLine: string) {
|
||||
log.info(logLine);
|
||||
}
|
||||
|
||||
export function openLogDirectory() {
|
||||
ipcRenderer.invoke('open-log-dir');
|
||||
}
|
||||
|
||||
export function logError(error: Error, message: string, info?: string): void {
|
||||
ipcRenderer.invoke('log-error', error, message, info);
|
||||
}
|
||||
|
||||
export function getSentryUserID(): Promise<string> {
|
||||
return ipcRenderer.invoke('get-sentry-id');
|
||||
}
|
|
@ -1,19 +1,22 @@
|
|||
import * as Sentry from '@sentry/electron/dist/main';
|
||||
import { makeID } from '../utils/logging';
|
||||
import { keysStore } from '../stores/keys.store';
|
||||
|
||||
import { SENTRY_DSN, RELEASE_VERSION } from '../config';
|
||||
import { isDev } from '../utils/common';
|
||||
import { logToDisk } from './logging';
|
||||
|
||||
const SENTRY_DSN = 'https://e9268b784d1042a7a116f53c58ad2165@sentry.ente.io/5';
|
||||
const ENV_DEVELOPMENT = 'development';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const version = require('../../package.json').version;
|
||||
const isDEVSentryENV = () =>
|
||||
process.env.NEXT_PUBLIC_SENTRY_ENV === ENV_DEVELOPMENT;
|
||||
|
||||
export function initSentry(): void {
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
release: version,
|
||||
release: RELEASE_VERSION,
|
||||
environment: isDev ? 'development' : 'production',
|
||||
});
|
||||
Sentry.setUser({ id: getSentryUserID() });
|
||||
}
|
||||
|
||||
export function logErrorSentry(
|
||||
|
@ -22,12 +25,17 @@ export function logErrorSentry(
|
|||
info?: Record<string, unknown>
|
||||
) {
|
||||
const err = errorWithContext(error, msg);
|
||||
if (!process.env.NEXT_PUBLIC_SENTRY_ENV) {
|
||||
logToDisk(
|
||||
`error: ${error?.name} ${error?.message} ${
|
||||
error?.stack
|
||||
} msg: ${msg} info: ${JSON.stringify(info)}`
|
||||
);
|
||||
if (isDEVSentryENV()) {
|
||||
console.log(error, { msg, info });
|
||||
}
|
||||
Sentry.captureException(err, {
|
||||
level: Sentry.Severity.Info,
|
||||
user: { id: getUserAnonymizedID() },
|
||||
user: { id: getSentryUserID() },
|
||||
contexts: {
|
||||
...(info && {
|
||||
info: info,
|
||||
|
@ -46,7 +54,7 @@ function errorWithContext(originalError: Error, context: string) {
|
|||
return errorWithContext;
|
||||
}
|
||||
|
||||
function getUserAnonymizedID() {
|
||||
export function getSentryUserID() {
|
||||
let anonymizeUserID = keysStore.get('AnonymizeUserID')?.id;
|
||||
if (!anonymizeUserID) {
|
||||
anonymizeUserID = makeID(6);
|
||||
|
@ -54,16 +62,3 @@ function getUserAnonymizedID() {
|
|||
}
|
||||
return anonymizeUserID;
|
||||
}
|
||||
|
||||
function makeID(length: number) {
|
||||
let result = '';
|
||||
const characters =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const charactersLength = characters.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(
|
||||
Math.floor(Math.random() * charactersLength)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -15,11 +15,15 @@ export const getSavedFilePaths = (type: FILE_PATH_TYPE) => {
|
|||
};
|
||||
|
||||
export async function getZipEntryAsElectronFile(
|
||||
zipName: string,
|
||||
zip: StreamZip.StreamZipAsync,
|
||||
entry: StreamZip.ZipEntry
|
||||
): Promise<ElectronFile> {
|
||||
return {
|
||||
path: entry.name,
|
||||
path: path
|
||||
.join(zipName, entry.name)
|
||||
.split(path.sep)
|
||||
.join(path.posix.sep),
|
||||
name: path.basename(entry.name),
|
||||
size: entry.size,
|
||||
lastModified: entry.time,
|
||||
|
@ -58,6 +62,7 @@ export const getElectronFilesFromGoogleZip = async (filePath: string) => {
|
|||
const zip = new StreamZip.async({
|
||||
file: filePath,
|
||||
});
|
||||
const zipName = path.basename(filePath, '.zip');
|
||||
|
||||
const entries = await zip.entries();
|
||||
const files: ElectronFile[] = [];
|
||||
|
@ -65,7 +70,7 @@ export const getElectronFilesFromGoogleZip = async (filePath: string) => {
|
|||
for (const entry of Object.values(entries)) {
|
||||
const basename = path.basename(entry.name);
|
||||
if (entry.isFile && basename.length > 0 && basename[0] !== '.') {
|
||||
files.push(await getZipEntryAsElectronFile(zip, entry));
|
||||
files.push(await getZipEntryAsElectronFile(zipName, zip, entry));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import { userPreferencesStore } from '../stores/userPreferences.store';
|
||||
|
||||
export function getHideDockIconPreference() {
|
||||
const shouldHideDockIcon = userPreferencesStore.get('hideDockIcon');
|
||||
return shouldHideDockIcon;
|
||||
return userPreferencesStore.get('hideDockIcon');
|
||||
}
|
||||
|
||||
export function setHideDockIconPreference(shouldHideDockIcon: boolean) {
|
||||
userPreferencesStore.set('hideDockIcon', shouldHideDockIcon);
|
||||
}
|
||||
|
||||
export function getSkipAppVersion() {
|
||||
return userPreferencesStore.get('skipAppVersion');
|
||||
}
|
||||
export function setSkipAppVersion(version: string) {
|
||||
userPreferencesStore.set('skipAppVersion', version);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,9 @@ const userPreferencesSchema: Schema<UserPreferencesType> = {
|
|||
hideDockIcon: {
|
||||
type: 'boolean',
|
||||
},
|
||||
skipAppVersion: {
|
||||
type: 'string',
|
||||
},
|
||||
};
|
||||
|
||||
export const userPreferencesStore = new Store({
|
||||
|
|
|
@ -56,4 +56,14 @@ export interface SafeStorageStoreType {
|
|||
|
||||
export interface UserPreferencesType {
|
||||
hideDockIcon: boolean;
|
||||
skipAppVersion: string;
|
||||
}
|
||||
|
||||
export interface AppUpdateInfo {
|
||||
autoUpdatable: boolean;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface GetFeatureFlagResponse {
|
||||
desktopCutoffVersion?: string;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as path from 'path';
|
|||
import { isDev } from './common';
|
||||
import { isAppQuitting } from '../main';
|
||||
import { PROD_HOST_URL } from '../config';
|
||||
import { isPlatformMac } from './main';
|
||||
import { isPlatform } from './main';
|
||||
import { getHideDockIconPreference } from '../services/userPreference';
|
||||
import autoLauncher from '../services/autoLauncher';
|
||||
|
||||
|
@ -16,13 +16,13 @@ export async function createWindow(): Promise<BrowserWindow> {
|
|||
const mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 800,
|
||||
backgroundColor: '#111111',
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: path.join(__dirname, '../preload.js'),
|
||||
contextIsolation: false,
|
||||
},
|
||||
icon: appIcon,
|
||||
show: false, // don't show the main window on load
|
||||
show: false, // don't show the main window on load,
|
||||
});
|
||||
mainWindow.maximize();
|
||||
const wasAutoLaunched = await autoLauncher.wasAutoLaunched();
|
||||
|
@ -55,11 +55,25 @@ export async function createWindow(): Promise<BrowserWindow> {
|
|||
);
|
||||
});
|
||||
mainWindow.once('ready-to-show', async () => {
|
||||
splash.destroy();
|
||||
if (!wasAutoLaunched) {
|
||||
mainWindow.show();
|
||||
try {
|
||||
splash.destroy();
|
||||
if (!wasAutoLaunched) {
|
||||
mainWindow.show();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
try {
|
||||
splash.destroy();
|
||||
if (!wasAutoLaunched) {
|
||||
mainWindow.show();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}, 2000);
|
||||
mainWindow.on('close', function (event) {
|
||||
if (!isAppQuitting()) {
|
||||
event.preventDefault();
|
||||
|
@ -69,12 +83,12 @@ export async function createWindow(): Promise<BrowserWindow> {
|
|||
});
|
||||
mainWindow.on('hide', () => {
|
||||
const shouldHideDockIcon = getHideDockIconPreference();
|
||||
if (isPlatformMac() && shouldHideDockIcon) {
|
||||
if (isPlatform('mac') && shouldHideDockIcon) {
|
||||
app.dock.hide();
|
||||
}
|
||||
});
|
||||
mainWindow.on('show', () => {
|
||||
if (isPlatformMac()) {
|
||||
if (isPlatform('mac')) {
|
||||
app.dock.show();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,13 +6,25 @@ import {
|
|||
Notification,
|
||||
safeStorage,
|
||||
app,
|
||||
shell,
|
||||
} from 'electron';
|
||||
import { createWindow } from './createWindow';
|
||||
import { buildContextMenu } from './menu';
|
||||
import { logErrorSentry } from '../services/sentry';
|
||||
import { getSentryUserID, logErrorSentry } from '../services/sentry';
|
||||
import chokidar from 'chokidar';
|
||||
import path from 'path';
|
||||
import { getDirFilePaths } from '../services/fs';
|
||||
import { convertHEIC } from '../services/heicConverter';
|
||||
import {
|
||||
getAppVersion,
|
||||
skipAppVersion,
|
||||
updateAndRestart,
|
||||
} from '../services/appUpdater';
|
||||
import {
|
||||
deleteTempFile,
|
||||
runFFmpegCmd,
|
||||
writeTempFile,
|
||||
} from '../services/ffmpeg';
|
||||
|
||||
export default function setupIpcComs(
|
||||
tray: Tray,
|
||||
|
@ -67,7 +79,7 @@ export default function setupIpcComs(
|
|||
|
||||
let files: string[] = [];
|
||||
for (const dirPath of dir.filePaths) {
|
||||
files = files.concat(await getDirFilePaths(dirPath));
|
||||
files = [...files, ...(await getDirFilePaths(dirPath))];
|
||||
}
|
||||
|
||||
return files;
|
||||
|
@ -96,4 +108,42 @@ export default function setupIpcComs(
|
|||
ipcMain.handle('get-path', (_, message) => {
|
||||
return app.getPath(message);
|
||||
});
|
||||
|
||||
ipcMain.handle('convert-heic', (_, fileData) => {
|
||||
return convertHEIC(fileData);
|
||||
});
|
||||
|
||||
ipcMain.handle('open-log-dir', () => {
|
||||
shell.openPath(app.getPath('logs'));
|
||||
});
|
||||
|
||||
ipcMain.on('update-and-restart', () => {
|
||||
updateAndRestart();
|
||||
});
|
||||
ipcMain.on('skip-app-version', (_, version) => {
|
||||
skipAppVersion(version);
|
||||
});
|
||||
ipcMain.handle('get-sentry-id', () => {
|
||||
return getSentryUserID();
|
||||
});
|
||||
|
||||
ipcMain.handle('get-app-version', () => {
|
||||
return getAppVersion();
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
'run-ffmpeg-cmd',
|
||||
(_, cmd, inputFilePath, outputFileName) => {
|
||||
return runFFmpegCmd(cmd, inputFilePath, outputFileName);
|
||||
}
|
||||
);
|
||||
ipcMain.handle(
|
||||
'write-temp-file',
|
||||
(_, fileStream: Uint8Array, fileName: string) => {
|
||||
return writeTempFile(fileStream, fileName);
|
||||
}
|
||||
);
|
||||
ipcMain.handle('remove-temp-file', (_, tempFilePath: string) => {
|
||||
return deleteTempFile(tempFilePath);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,25 @@
|
|||
import { ipcRenderer } from 'electron';
|
||||
import log from 'electron-log';
|
||||
import { LOG_FILENAME, MAX_LOG_SIZE } from '../config';
|
||||
|
||||
export function logError(error: Error, message: string, info?: string): void {
|
||||
ipcRenderer.invoke('log-error', error, message, info);
|
||||
export function setupLogging(isDev?: boolean) {
|
||||
log.transports.file.fileName = LOG_FILENAME;
|
||||
log.transports.file.maxSize = MAX_LOG_SIZE;
|
||||
if (!isDev) {
|
||||
log.transports.console.level = false;
|
||||
}
|
||||
log.transports.file.format =
|
||||
'[{y}-{m}-{d}T{h}:{i}:{s}{z}] [{level}]{scope} {text}';
|
||||
}
|
||||
|
||||
export function makeID(length: number) {
|
||||
let result = '';
|
||||
const characters =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const charactersLength = characters.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(
|
||||
Math.floor(Math.random() * charactersLength)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -4,22 +4,31 @@ import electronReload from 'electron-reload';
|
|||
import serveNextAt from 'next-electron-server';
|
||||
import path from 'path';
|
||||
import { existsSync } from 'promise-fs';
|
||||
import appUpdater from '../services/appUpdater';
|
||||
import { isDev } from './common';
|
||||
import { buildContextMenu, buildMenuBar } from './menu';
|
||||
import autoLauncher from '../services/autoLauncher';
|
||||
import { getHideDockIconPreference } from '../services/userPreference';
|
||||
import {
|
||||
checkForUpdateAndNotify,
|
||||
setupAutoUpdater,
|
||||
} from '../services/appUpdater';
|
||||
import ElectronLog from 'electron-log';
|
||||
import os from 'os';
|
||||
|
||||
export function handleUpdates(mainWindow: BrowserWindow, tray: Tray) {
|
||||
export function handleUpdates(mainWindow: BrowserWindow) {
|
||||
if (!isDev) {
|
||||
appUpdater.checkForUpdate(tray, mainWindow);
|
||||
setupAutoUpdater();
|
||||
checkForUpdateAndNotify(mainWindow);
|
||||
}
|
||||
}
|
||||
|
||||
export function setupTrayItem(mainWindow: BrowserWindow) {
|
||||
const trayImgPath = isDev
|
||||
? 'build/taskbar-icon.png'
|
||||
: path.join(process.resourcesPath, 'taskbar-icon.png');
|
||||
const iconName = isPlatform('mac')
|
||||
? 'taskbar-icon-Template.png'
|
||||
: 'taskbar-icon.png';
|
||||
const trayImgPath = path.join(
|
||||
isDev ? 'build' : process.resourcesPath,
|
||||
iconName
|
||||
);
|
||||
const trayIcon = nativeImage.createFromPath(trayImgPath);
|
||||
const tray = new Tray(trayIcon);
|
||||
tray.setToolTip('ente');
|
||||
|
@ -79,19 +88,23 @@ export function setupNextElectronServe() {
|
|||
});
|
||||
}
|
||||
|
||||
export function isPlatformMac() {
|
||||
return process.platform === 'darwin';
|
||||
}
|
||||
|
||||
export function isPlatformWindows() {
|
||||
return process.platform === 'win32';
|
||||
export function isPlatform(platform: 'mac' | 'windows' | 'linux') {
|
||||
if (process.platform === 'darwin') {
|
||||
return platform === 'mac';
|
||||
} else if (process.platform === 'win32') {
|
||||
return platform === 'windows';
|
||||
} else if (process.platform === 'linux') {
|
||||
return platform === 'linux';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleDockIconHideOnAutoLaunch() {
|
||||
const shouldHideDockIcon = getHideDockIconPreference();
|
||||
const wasAutoLaunched = await autoLauncher.wasAutoLaunched();
|
||||
|
||||
if (isPlatformMac() && shouldHideDockIcon && wasAutoLaunched) {
|
||||
if (isPlatform('mac') && shouldHideDockIcon && wasAutoLaunched) {
|
||||
app.dock.hide();
|
||||
}
|
||||
}
|
||||
|
@ -99,3 +112,10 @@ export async function handleDockIconHideOnAutoLaunch() {
|
|||
export function enableSharedArrayBufferSupport() {
|
||||
app.commandLine.appendSwitch('enable-features', 'SharedArrayBuffer');
|
||||
}
|
||||
|
||||
export function logSystemInfo() {
|
||||
const systemVersion = process.getSystemVersion();
|
||||
const osName = process.platform;
|
||||
const osRelease = os.release();
|
||||
ElectronLog.info({ osName, osRelease, systemVersion });
|
||||
}
|
||||
|
|
|
@ -9,11 +9,10 @@ import {
|
|||
getHideDockIconPreference,
|
||||
setHideDockIconPreference,
|
||||
} from '../services/userPreference';
|
||||
import { isUpdateAvailable, setIsAppQuitting } from '../main';
|
||||
import { setIsAppQuitting } from '../main';
|
||||
import autoLauncher from '../services/autoLauncher';
|
||||
import { isPlatformMac } from './main';
|
||||
import { showUpdateDialog } from '../services/appUpdater';
|
||||
import { isDev } from './common';
|
||||
import { isPlatform } from './main';
|
||||
import ElectronLog from 'electron-log';
|
||||
|
||||
export function buildContextMenu(
|
||||
mainWindow: BrowserWindow,
|
||||
|
@ -26,15 +25,6 @@ export function buildContextMenu(
|
|||
paused,
|
||||
} = args;
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
...(isUpdateAvailable()
|
||||
? [
|
||||
{
|
||||
label: 'Update available',
|
||||
click: () => showUpdateDialog(),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ type: 'separator' },
|
||||
...(exportProgress
|
||||
? [
|
||||
{
|
||||
|
@ -87,6 +77,7 @@ export function buildContextMenu(
|
|||
{
|
||||
label: 'Quit ente',
|
||||
click: function () {
|
||||
ElectronLog.log('user quit the app');
|
||||
setIsAppQuitting(true);
|
||||
app.quit();
|
||||
},
|
||||
|
@ -97,7 +88,7 @@ export function buildContextMenu(
|
|||
|
||||
export async function buildMenuBar(): Promise<Menu> {
|
||||
let isAutoLaunchEnabled = await autoLauncher.isEnabled();
|
||||
const isMac = isPlatformMac();
|
||||
const isMac = isPlatform('mac');
|
||||
let shouldHideDockIcon = getHideDockIconPreference();
|
||||
const template: MenuItemConstructorOptions[] = [
|
||||
{
|
||||
|
@ -216,6 +207,7 @@ export async function buildMenuBar(): Promise<Menu> {
|
|||
{
|
||||
label: 'Window',
|
||||
submenu: [
|
||||
{ role: 'close', label: 'Close' },
|
||||
{ role: 'minimize', label: 'Minimize' },
|
||||
...((isMac
|
||||
? [
|
||||
|
@ -224,9 +216,31 @@ export async function buildMenuBar(): Promise<Menu> {
|
|||
{ type: 'separator' },
|
||||
{ role: 'window', label: 'ente' },
|
||||
]
|
||||
: [
|
||||
{ role: 'close', label: 'Close ente' },
|
||||
]) as MenuItemConstructorOptions[]),
|
||||
: []) as MenuItemConstructorOptions[]),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'FAQ',
|
||||
click: () => shell.openExternal('https://ente.io/faq/'),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Support',
|
||||
click: () => shell.openExternal('mailto:support@ente.io'),
|
||||
},
|
||||
{
|
||||
label: 'Product updates',
|
||||
click: () => shell.openExternal('https://ente.io/blog/'),
|
||||
},
|
||||
{
|
||||
label: 'View logs',
|
||||
click: () => {
|
||||
shell.openPath(app.getPath('logs'));
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
39
src/utils/processStats.ts
Normal file
39
src/utils/processStats.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import ElectronLog from 'electron-log';
|
||||
import { webFrame } from 'electron/renderer';
|
||||
|
||||
const FIVE_MINUTES_IN_MICROSECONDS = 30 * 1000;
|
||||
|
||||
async function logMainProcessStats() {
|
||||
const systemMemoryInfo = process.getSystemMemoryInfo();
|
||||
const cpuUsage = process.getCPUUsage();
|
||||
const processMemoryInfo = await process.getProcessMemoryInfo();
|
||||
const heapStatistics = process.getHeapStatistics();
|
||||
|
||||
ElectronLog.log('main process stats', {
|
||||
systemMemoryInfo,
|
||||
cpuUsage,
|
||||
processMemoryInfo,
|
||||
heapStatistics,
|
||||
});
|
||||
}
|
||||
|
||||
async function logRendererProcessStats() {
|
||||
const blinkMemoryInfo = process.getBlinkMemoryInfo();
|
||||
const heapStatistics = process.getHeapStatistics();
|
||||
const processMemoryInfo = process.getProcessMemoryInfo();
|
||||
const webFrameResourceUsage = webFrame.getResourceUsage();
|
||||
ElectronLog.log('renderer process stats', {
|
||||
blinkMemoryInfo,
|
||||
heapStatistics,
|
||||
processMemoryInfo,
|
||||
webFrameResourceUsage,
|
||||
});
|
||||
}
|
||||
|
||||
export function setupMainProcessStatsLogger() {
|
||||
setInterval(logMainProcessStats, FIVE_MINUTES_IN_MICROSECONDS);
|
||||
}
|
||||
|
||||
export function setupRendererProcessStatsLogger() {
|
||||
setInterval(logRendererProcessStats, FIVE_MINUTES_IN_MICROSECONDS);
|
||||
}
|
38
src/utils/temp.ts
Normal file
38
src/utils/temp.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { app } from 'electron';
|
||||
import path from 'path';
|
||||
import { existsSync, mkdir } from 'promise-fs';
|
||||
|
||||
const ENTE_TEMP_DIRECTORY = 'ente';
|
||||
|
||||
const CHARACTERS =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
export async function getTempDirPath() {
|
||||
const tempDirPath = path.join(app.getPath('temp'), ENTE_TEMP_DIRECTORY);
|
||||
if (!existsSync(tempDirPath)) {
|
||||
await mkdir(tempDirPath);
|
||||
}
|
||||
return tempDirPath;
|
||||
}
|
||||
|
||||
function generateTempName(length: number) {
|
||||
let result = '';
|
||||
|
||||
const charactersLength = CHARACTERS.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += CHARACTERS.charAt(
|
||||
Math.floor(Math.random() * charactersLength)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function generateTempFilePath(formatSuffix: string) {
|
||||
const tempDirPath = await getTempDirPath();
|
||||
const namePrefix = generateTempName(10);
|
||||
const tempFilePath = path.join(
|
||||
tempDirPath,
|
||||
namePrefix + '-' + formatSuffix
|
||||
);
|
||||
return tempFilePath;
|
||||
}
|
153
yarn.lock
153
yarn.lock
|
@ -35,6 +35,16 @@
|
|||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@derhuerst/http-basic@^8.2.0":
|
||||
version "8.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@derhuerst/http-basic/-/http-basic-8.2.4.tgz#d021ebb8f65d54bea681ae6f4a8733ce89e7f59b"
|
||||
integrity sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==
|
||||
dependencies:
|
||||
caseless "^0.12.0"
|
||||
concat-stream "^2.0.0"
|
||||
http-response-object "^3.0.1"
|
||||
parse-cache-control "^1.0.1"
|
||||
|
||||
"@develar/schema-utils@~2.6.5":
|
||||
version "2.6.5"
|
||||
resolved "https://registry.yarnpkg.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz#3ece22c5838402419a6e0425f85742b961d9b6c6"
|
||||
|
@ -43,7 +53,7 @@
|
|||
ajv "^6.12.0"
|
||||
ajv-keywords "^3.4.1"
|
||||
|
||||
"@electron/get@^1.13.0":
|
||||
"@electron/get@^1.14.1":
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40"
|
||||
integrity sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==
|
||||
|
@ -277,6 +287,11 @@
|
|||
dependencies:
|
||||
"@types/ms" "*"
|
||||
|
||||
"@types/ffmpeg-static@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/ffmpeg-static/-/ffmpeg-static-3.0.1.tgz#1003f003624bcd2f569b56185a62dcbacd935c39"
|
||||
integrity sha512-hEJdQMv/g1olk9qTiWqh23BfbKsDKE6Tc7DilNJWF1MgZsU9fYOPKrgQ448vfT7aP2Yt5re9vgJDVv9TXEoTyQ==
|
||||
|
||||
"@types/fs-extra@^9.0.11":
|
||||
version "9.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45"
|
||||
|
@ -312,15 +327,28 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
||||
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
||||
|
||||
"@types/node-fetch@^2.6.2":
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da"
|
||||
integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
form-data "^3.0.0"
|
||||
|
||||
"@types/node@*":
|
||||
version "18.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.3.tgz#463fc47f13ec0688a33aec75d078a0541a447199"
|
||||
integrity sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==
|
||||
|
||||
"@types/node@^14.14.37", "@types/node@^14.6.2":
|
||||
version "14.18.21"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.21.tgz#0155ee46f6be28b2ff0342ca1a9b9fd4468bef41"
|
||||
integrity sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==
|
||||
"@types/node@^10.0.3":
|
||||
version "10.17.60"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
|
||||
integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
|
||||
|
||||
"@types/node@^16.11.26", "@types/node@^16.18.3":
|
||||
version "16.18.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc"
|
||||
integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.1"
|
||||
|
@ -342,6 +370,11 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/semver-compare@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver-compare/-/semver-compare-1.0.1.tgz#17d1dc62c516c133ab01efb7803a537ee6eaf3d5"
|
||||
integrity sha512-wx2LQVvKlEkhXp/HoKIZ/aSL+TvfJdKco8i0xJS3aR877mg4qBHzNT6+B5a61vewZHo79EdZavskGnRXEC2H6A==
|
||||
|
||||
"@types/semver@^7.3.6":
|
||||
version "7.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.10.tgz#5f19ee40cbeff87d916eedc8c2bfe2305d957f73"
|
||||
|
@ -364,6 +397,13 @@
|
|||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
|
||||
integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.28.0":
|
||||
version "5.30.6"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz#9c6017b6c1d04894141b4a87816388967f64c359"
|
||||
|
@ -554,6 +594,11 @@ ansi-styles@^6.0.0:
|
|||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3"
|
||||
integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==
|
||||
|
||||
any-shell-escape@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/any-shell-escape/-/any-shell-escape-0.1.1.tgz#d55ab972244c71a9a5e1ab0879f30bf110806959"
|
||||
integrity sha512-36j4l5HVkboyRhIWgtMh1I9i8LTdFqVwDEHy1cp+QioJyKgAUG40X0W8s7jakWRta/Sjvm8mUG1fU6Tj8mWagQ==
|
||||
|
||||
anymatch@~3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||
|
@ -887,7 +932,7 @@ camelcase@^6.2.0:
|
|||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
|
||||
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
|
||||
|
||||
caseless@~0.12.0:
|
||||
caseless@^0.12.0, caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==
|
||||
|
@ -1066,14 +1111,14 @@ concat-map@0.0.1:
|
|||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||
|
||||
concat-stream@^1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
|
||||
concat-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1"
|
||||
integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.2.2"
|
||||
readable-stream "^3.0.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
concurrently@^7.0.0:
|
||||
|
@ -1194,7 +1239,7 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, d
|
|||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^2.1.3, debug@^2.2.0, debug@^2.6.8, debug@^2.6.9:
|
||||
debug@^2.1.3, debug@^2.2.0, debug@^2.6.8:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
|
@ -1469,14 +1514,14 @@ electron-updater@^4.3.8:
|
|||
lodash.isequal "^4.5.0"
|
||||
semver "^7.3.5"
|
||||
|
||||
electron@^15.3.0:
|
||||
version "15.5.7"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-15.5.7.tgz#aadb0081c504f2c2d8f81ea5fd23e38881afe86a"
|
||||
integrity sha512-n4mVlxoMc4eYx07wWFWGficL+iOMz5xZEf5dBtE/wwLm0fQpYVyW4AlknMFG9F8Css0MM0JSwNMOyRg5e1vDtg==
|
||||
electron@^21.2.2:
|
||||
version "21.3.0"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-21.3.0.tgz#e9905e240add950443dc115b4be13d36162f0a05"
|
||||
integrity sha512-MGRpshN8fBcx4IRuBABIsGDv0tB/MclIFsyFHFFXsBCUc+vIXaE/E6vuWaniGIFSz5WyeuapfTH5IeRb+7yIfw==
|
||||
dependencies:
|
||||
"@electron/get" "^1.13.0"
|
||||
"@types/node" "^14.6.2"
|
||||
extract-zip "^1.0.3"
|
||||
"@electron/get" "^1.14.1"
|
||||
"@types/node" "^16.11.26"
|
||||
extract-zip "^2.0.1"
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
|
@ -1705,15 +1750,16 @@ extend@~3.0.2:
|
|||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||
|
||||
extract-zip@^1.0.3:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927"
|
||||
integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
|
||||
extract-zip@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
|
||||
integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
|
||||
dependencies:
|
||||
concat-stream "^1.6.2"
|
||||
debug "^2.6.9"
|
||||
mkdirp "^0.5.4"
|
||||
debug "^4.1.1"
|
||||
get-stream "^5.1.0"
|
||||
yauzl "^2.10.0"
|
||||
optionalDependencies:
|
||||
"@types/yauzl" "^2.9.1"
|
||||
|
||||
extsprintf@1.3.0:
|
||||
version "1.3.0"
|
||||
|
@ -1765,6 +1811,16 @@ fd-slicer@~1.1.0:
|
|||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
ffmpeg-static@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ffmpeg-static/-/ffmpeg-static-5.1.0.tgz#133500f4566570c5a0e96795152b0526d8c936ad"
|
||||
integrity sha512-eEWOiGdbf7HKPeJI5PoJ0oCwkL0hckL2JdS4JOuB/gUETppwkEpq8nF0+e6VEQnDCo/iuoipbTUsn9QJmtpNkg==
|
||||
dependencies:
|
||||
"@derhuerst/http-basic" "^8.2.0"
|
||||
env-paths "^2.2.0"
|
||||
https-proxy-agent "^5.0.0"
|
||||
progress "^2.0.3"
|
||||
|
||||
file-entry-cache@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
|
||||
|
@ -1819,6 +1875,15 @@ forever-agent@~0.6.1:
|
|||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==
|
||||
|
||||
form-data@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
|
||||
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
|
@ -2147,6 +2212,13 @@ http-proxy-agent@^5.0.0:
|
|||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
http-response-object@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810"
|
||||
integrity sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==
|
||||
dependencies:
|
||||
"@types/node" "^10.0.3"
|
||||
|
||||
http-signature@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||
|
@ -2745,7 +2817,7 @@ minizlib@^2.1.1:
|
|||
minipass "^3.0.0"
|
||||
yallist "^4.0.0"
|
||||
|
||||
mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@^0.5.5:
|
||||
mkdirp@^0.5.1, mkdirp@^0.5.5:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
|
||||
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
|
||||
|
@ -2973,6 +3045,11 @@ parent-module@^1.0.0:
|
|||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
parse-cache-control@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e"
|
||||
integrity sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==
|
||||
|
||||
parse-json@^5.0.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
|
||||
|
@ -3193,7 +3270,7 @@ read-pkg@^5.2.0:
|
|||
parse-json "^5.0.0"
|
||||
type-fest "^0.6.0"
|
||||
|
||||
readable-stream@^2.0.6, readable-stream@^2.2.2:
|
||||
readable-stream@^2.0.6:
|
||||
version "2.3.7"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
||||
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
|
||||
|
@ -3206,6 +3283,15 @@ readable-stream@^2.0.6, readable-stream@^2.2.2:
|
|||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@^3.0.2:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@~1.1.9:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
|
@ -3350,7 +3436,7 @@ rxjs@^7.0.0, rxjs@^7.5.5:
|
|||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
@ -3594,6 +3680,13 @@ string-width@^5.0.0:
|
|||
emoji-regex "^9.2.2"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
string_decoder@~0.10.x:
|
||||
version "0.10.31"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
|
@ -3941,7 +4034,7 @@ utf8-byte-length@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61"
|
||||
integrity sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==
|
||||
|
||||
util-deprecate@~1.0.1:
|
||||
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
|
Loading…
Add table
Reference in a new issue