Merge branch 'main' into feature/web-passkey-recovery
3
.gitattributes
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Set line endings of shell scripts to LF, even on Windows, otherwise execution
|
||||
# within Docker fails.
|
||||
*.sh text eol=lf
|
6
.github/workflows/auth-lint.yml
vendored
|
@ -1,11 +1,9 @@
|
|||
name: "Lint (auth)"
|
||||
|
||||
on:
|
||||
# Run on every push to branches (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes auth/
|
||||
push:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "auth/**"
|
||||
- ".github/workflows/auth-lint.yml"
|
||||
|
|
6
.github/workflows/mobile-lint.yml
vendored
|
@ -1,11 +1,9 @@
|
|||
name: "Lint (mobile)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes mobile/
|
||||
push:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "mobile/**"
|
||||
- ".github/workflows/mobile-lint.yml"
|
||||
|
|
6
.github/workflows/server-lint.yml
vendored
|
@ -1,11 +1,9 @@
|
|||
name: "Lint (server)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes server/
|
||||
push:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "server/**"
|
||||
- ".github/workflows/server-lint.yml"
|
||||
|
|
15
.github/workflows/web-lint.yml
vendored
|
@ -1,20 +1,9 @@
|
|||
name: "Lint (web)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes web/
|
||||
push:
|
||||
# [Note: Specify branch when specifying a path filter]
|
||||
#
|
||||
# Path filters are ignored for tag pushes, which causes this workflow to
|
||||
# always run when we push a tag. Defining an explicit branch solves the
|
||||
# issue. From GitHub's docs:
|
||||
#
|
||||
# > if you define both branches/branches-ignore and paths/paths-ignore,
|
||||
# > the workflow will only run when both filters are satisfied.
|
||||
#
|
||||
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "web/**"
|
||||
- ".github/workflows/web-lint.yml"
|
||||
|
|
52
.github/workflows/web-preview.yml
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
name: "Preview (web)"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
app:
|
||||
description: "App to build and deploy"
|
||||
type: choice
|
||||
required: true
|
||||
default: "photos"
|
||||
options:
|
||||
- "accounts"
|
||||
- "auth"
|
||||
- "cast"
|
||||
- "photos"
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "docs/yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build ${{ inputs.app }}
|
||||
run: yarn build:${{ inputs.app }}
|
||||
|
||||
- name: Publish ${{ inputs.app }} to preview
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: preview
|
||||
directory: web/apps/${{ inputs.app }}/out
|
||||
wranglerVersion: "3"
|
4
.gitmodules
vendored
|
@ -9,10 +9,6 @@
|
|||
[submodule "auth/assets/simple-icons"]
|
||||
path = auth/assets/simple-icons
|
||||
url = https://github.com/simple-icons/simple-icons.git
|
||||
[submodule "desktop/thirdparty/next-electron-server"]
|
||||
path = desktop/thirdparty/next-electron-server
|
||||
url = https://github.com/ente-io/next-electron-server.git
|
||||
branch = desktop
|
||||
[submodule "mobile/thirdparty/flutter"]
|
||||
path = mobile/thirdparty/flutter
|
||||
url = https://github.com/flutter/flutter.git
|
||||
|
|
3
desktop/.github/workflows/build.yml
vendored
|
@ -52,7 +52,4 @@ jobs:
|
|||
# macOS notarization API key
|
||||
API_KEY_ID: ${{ secrets.api_key_id }}
|
||||
API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id}}
|
||||
# setry crash reporting token
|
||||
SENTRY_AUTH_TOKEN: ${{secrets.sentry_auth_token}}
|
||||
NEXT_PUBLIC_DISABLE_SENTRY: ${{secrets.next_public_disable_sentry}}
|
||||
USE_HARD_LINKS: false
|
||||
|
|
9
desktop/.gitignore
vendored
|
@ -11,10 +11,11 @@ node_modules/
|
|||
.env
|
||||
.env.*.local
|
||||
|
||||
# Generated code during build
|
||||
# - tsc transpiles src/**/*.ts and emits the generated JS into build/app
|
||||
# - The out dir from the photos web app is symlinked to build/out
|
||||
build/
|
||||
# tsc transpiles src/**/*.ts and emits the generated JS into app
|
||||
app/
|
||||
|
||||
# out is a symlink to the photos web app's dir
|
||||
out
|
||||
|
||||
# electron-builder
|
||||
dist/
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
thirdparty/
|
|
@ -4,128 +4,128 @@
|
|||
|
||||
### New
|
||||
|
||||
- Option to select file download location.
|
||||
- Add support for searching popular cities
|
||||
- Sorted duplicates in desecending order of size
|
||||
- Add Counter to upload section
|
||||
- Display full name and collection name on hover on dedupe screen photos
|
||||
- Option to select file download location.
|
||||
- Add support for searching popular cities
|
||||
- Sorted duplicates in desecending order of size
|
||||
- Add Counter to upload section
|
||||
- Display full name and collection name on hover on dedupe screen photos
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix add to album padding issue
|
||||
- Fix double uncategorized album issue
|
||||
- Hide Hidden collection files from all section
|
||||
- Fix add to album padding issue
|
||||
- Fix double uncategorized album issue
|
||||
- Hide Hidden collection files from all section
|
||||
|
||||
## v1.6.62
|
||||
|
||||
### New
|
||||
|
||||
- Integrated onnx clip runner
|
||||
- Integrated onnx clip runner
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixes login button requiring double click issue
|
||||
- Fixes Collection sort state not preserved issue
|
||||
- Fixes continuous export causing app crash
|
||||
- Improves ML related copies for better distinction from clip
|
||||
- Added Better favicon for light mode
|
||||
- Fixed face indexing issues
|
||||
- Fixed thumbnail load issue
|
||||
- Fixes login button requiring double click issue
|
||||
- Fixes Collection sort state not preserved issue
|
||||
- Fixes continuous export causing app crash
|
||||
- Improves ML related copies for better distinction from clip
|
||||
- Added Better favicon for light mode
|
||||
- Fixed face indexing issues
|
||||
- Fixed thumbnail load issue
|
||||
|
||||
## v1.6.60
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix Thumbnail Orientation issue
|
||||
- Fix ML logging issue
|
||||
- Fix Thumbnail Orientation issue
|
||||
- Fix ML logging issue
|
||||
|
||||
## v1.6.59
|
||||
|
||||
### New
|
||||
|
||||
- Added arm64 builds for linux
|
||||
- Added arm64 builds for linux
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix Editor file not loading issue
|
||||
- Fix ML results missing thumbnail issue
|
||||
- Fix Editor file not loading issue
|
||||
- Fix ML results missing thumbnail issue
|
||||
|
||||
## v1.6.58
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix File load issue
|
||||
- Fix File load issue
|
||||
|
||||
## v1.6.57
|
||||
|
||||
### New Features
|
||||
|
||||
- Added encrypted Disk caching for files
|
||||
- Added option to customize cache folder location
|
||||
- Added encrypted Disk caching for files
|
||||
- Added option to customize cache folder location
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed caching issue,causing multiple download of file during ml sync
|
||||
- Fixed caching issue,causing multiple download of file during ml sync
|
||||
|
||||
## v1.6.55
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Added manage family portal option if add-on is active
|
||||
- Fixed filename date parsing issue
|
||||
- Fixed storage limit ui glitch
|
||||
- Fixed dedupe page layout issue
|
||||
- Fixed ElectronAPI refactoring issue
|
||||
- Fixed Search related issues
|
||||
- Added manage family portal option if add-on is active
|
||||
- Fixed filename date parsing issue
|
||||
- Fixed storage limit ui glitch
|
||||
- Fixed dedupe page layout issue
|
||||
- Fixed ElectronAPI refactoring issue
|
||||
- Fixed Search related issues
|
||||
|
||||
## v1.6.54
|
||||
|
||||
### New Features
|
||||
|
||||
- Added support for HEIC and raw image in photo editor
|
||||
- Added support for HEIC and raw image in photo editor
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed 16bit HDR HEIC images support
|
||||
- Fixed blocked login due safe storage issue
|
||||
- Fixed Search related issues
|
||||
- Fixed issue of watch folder not cleared on logout
|
||||
- other under the hood ui/ux improvements
|
||||
- Fixed 16bit HDR HEIC images support
|
||||
- Fixed blocked login due safe storage issue
|
||||
- Fixed Search related issues
|
||||
- Fixed issue of watch folder not cleared on logout
|
||||
- other under the hood ui/ux improvements
|
||||
|
||||
## v1.6.53
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed watch folder disabled issue
|
||||
- Fixed BF Add on related issues
|
||||
- Fixed clip sync issue and added better logging
|
||||
- Fixed mov file upload
|
||||
- Fixed clip extraction related issue
|
||||
- Fixed watch folder disabled issue
|
||||
- Fixed BF Add on related issues
|
||||
- Fixed clip sync issue and added better logging
|
||||
- Fixed mov file upload
|
||||
- Fixed clip extraction related issue
|
||||
|
||||
## v1.6.52
|
||||
|
||||
### New Features
|
||||
|
||||
- Added Clip Desktop on windows
|
||||
- Added Clip Desktop on windows
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- fixed google json matching issue
|
||||
- other under-the-hood changes to improve performance and bug fixes
|
||||
- fixed google json matching issue
|
||||
- other under-the-hood changes to improve performance and bug fixes
|
||||
|
||||
## v1.6.50
|
||||
|
||||
### New Features
|
||||
|
||||
- Added Clip desktop
|
||||
- Added Clip desktop
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed desktop downloaded file had extra dot in the name
|
||||
- Cleanup error messages
|
||||
- fix the motion photo clustering issue
|
||||
- Add option to disable cf proxy locally
|
||||
- other under-the-hood changes to improve UX
|
||||
- Fixed desktop downloaded file had extra dot in the name
|
||||
- Cleanup error messages
|
||||
- fix the motion photo clustering issue
|
||||
- Add option to disable cf proxy locally
|
||||
- other under-the-hood changes to improve UX
|
||||
|
||||
## v1.6.49
|
||||
|
||||
|
@ -137,54 +137,54 @@ Check out our [blog](https://ente.io/blog/introducing-web-desktop-photo-editor/)
|
|||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed misaligned icons in photo-viewer
|
||||
- Fixed issue with Motion photo upload
|
||||
- Fixed issue with Live-photo upload
|
||||
- other minor ux improvement
|
||||
- Fixed misaligned icons in photo-viewer
|
||||
- Fixed issue with Motion photo upload
|
||||
- Fixed issue with Live-photo upload
|
||||
- other minor ux improvement
|
||||
|
||||
## v1.6.46
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixes OOM crashes during file upload [#1379](https://github.com/ente-io/photos-web/pull/1379)
|
||||
- Fixes OOM crashes during file upload [#1379](https://github.com/ente-io/photos-web/pull/1379)
|
||||
|
||||
## v1.6.45
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed app keeps reloading issue [#235](https://github.com/ente-io/photos-desktop/pull/235)
|
||||
- Fixed dng and arw preview issue [#1378](https://github.com/ente-io/photos-web/pull/1378)
|
||||
- Added view crash report option (help menu) for user to share electron crash report locally
|
||||
- Fixed app keeps reloading issue [#235](https://github.com/ente-io/photos-desktop/pull/235)
|
||||
- Fixed dng and arw preview issue [#1378](https://github.com/ente-io/photos-web/pull/1378)
|
||||
- Added view crash report option (help menu) for user to share electron crash report locally
|
||||
|
||||
## v1.6.44
|
||||
|
||||
- Upgraded electron to get latest security patches and other improvements.
|
||||
- Upgraded electron to get latest security patches and other improvements.
|
||||
|
||||
## v1.6.43
|
||||
|
||||
### Added
|
||||
|
||||
- #### Check for update and changelog option
|
||||
- #### Check for update and changelog option
|
||||
|
||||
Added options to check for update manually and a view changelog via the app menubar
|
||||
|
||||
- #### Opt out of crash reporting
|
||||
- #### Opt out of crash reporting
|
||||
|
||||
Added option to out of a crash reporting, it can accessed from the settings -> preferences -> disable crash reporting
|
||||
|
||||
- #### Type search
|
||||
- #### Type search
|
||||
|
||||
Added new search option to search files based on file type i.e, image, video, live-photo.
|
||||
|
||||
- #### Manual Convert Button
|
||||
- #### Manual Convert Button
|
||||
|
||||
In case the video is not playable, Now there is a convert button which can be used to trigger conversion of the video to supported format.
|
||||
|
||||
- #### File Download Progress
|
||||
- #### File Download Progress
|
||||
|
||||
The file loader now also shows the exact percentage download progress, instead of just a simple loader.
|
||||
|
||||
- #### Bug fixes & other enhancements
|
||||
- #### Bug fixes & other enhancements
|
||||
|
||||
We have squashed a few pesky bugs that were reported by our community
|
||||
|
||||
|
@ -192,21 +192,21 @@ Check out our [blog](https://ente.io/blog/introducing-web-desktop-photo-editor/)
|
|||
|
||||
### Added
|
||||
|
||||
- #### Hidden albums
|
||||
- #### Hidden albums
|
||||
|
||||
You can now hide albums, just like individual memories.
|
||||
|
||||
- #### Email verification
|
||||
- #### Email verification
|
||||
|
||||
We have now made email verification optional, so you can sign in with just your email address and password, without waiting for a verification code.
|
||||
|
||||
You can opt in / out of email verification from Settings > Security.
|
||||
|
||||
- #### Download Album
|
||||
- #### Download Album
|
||||
|
||||
You can now chose the download location for downloading albums. Along with that we have also added progress bar for album download.
|
||||
|
||||
- #### Bug fixes & other enhancements
|
||||
- #### Bug fixes & other enhancements
|
||||
|
||||
We have squashed a few pesky bugs that were reported by our community
|
||||
|
||||
|
|
|
@ -10,40 +10,28 @@ To know more about Ente, see [our main README](../README.md) or visit
|
|||
|
||||
## Building from source
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> We moved a few things around when switching to a monorepo recently, and the
|
||||
> desktop app is not currently building with these instructions below. Hang
|
||||
> tight, we're on it, and will fix soon.
|
||||
|
||||
Fetch submodules
|
||||
|
||||
```sh
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
|
||||
```sh
|
||||
yarn install
|
||||
```
|
||||
|
||||
Run the app
|
||||
Run in development mode (with hot reload)
|
||||
|
||||
```sh
|
||||
yarn dev
|
||||
```
|
||||
|
||||
To recompile automatically using electron-reload, run this in a separate
|
||||
terminal:
|
||||
> [!CAUTION]
|
||||
>
|
||||
> `yarn dev` is currently not working (we'll fix soon). If you just want to
|
||||
> build from source and use the generated binary, use `yarn build`.
|
||||
|
||||
```bash
|
||||
yarn watch
|
||||
```
|
||||
|
||||
`yarn dev` is handy during development, but if you wish, you can also create a
|
||||
binary for your platform by using
|
||||
Or create a binary for your platform
|
||||
|
||||
```sh
|
||||
yarn build
|
||||
```
|
||||
|
||||
That's the gist of it. For more development related documentation, see
|
||||
[docs](docs/README.md).
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
Before Width: | Height: | Size: 989 B After Width: | Height: | Size: 989 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 607 B After Width: | Height: | Size: 607 B |
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 259 B |
Before Width: | Height: | Size: 458 B After Width: | Height: | Size: 458 B |
Before Width: | Height: | Size: 655 B After Width: | Height: | Size: 655 B |
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Electron Updater Example</title>
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
|
@ -4,3 +4,11 @@ See [web/docs/dependencies.md](../../web/docs/dependencies.md) for general web
|
|||
specific dependencies. See [electron.md](electron.md) for our main dependency,
|
||||
Electron. The rest of this document describes the remaining, desktop specific
|
||||
dependencies that are used by the Photos desktop app.
|
||||
|
||||
## Electron related
|
||||
|
||||
### next-electron-server
|
||||
|
||||
This spins up a server for serving files using a protocol handler inside our
|
||||
Electron process. This allows us to directly use the output produced by `next
|
||||
build` for loading into our renderer process.
|
||||
|
|
4
desktop/docs/dev.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Development tips
|
||||
|
||||
- `yarn build:quick` is a variant of `yarn build` that uses the
|
||||
`--config.compression=store` flag to (slightly) speed up electron-builder.
|
|
@ -7,15 +7,15 @@ Electron embeds Chromium and Node.js in the generated app's binary. The
|
|||
generated app thus consists of two separate processes - the _main_ process, and
|
||||
a _renderer_ process.
|
||||
|
||||
* The _main_ process is runs the embedded node. This process can deal with the
|
||||
host OS - it is conceptually like a `node` repl running on your machine. In our
|
||||
case, the TypeScript code (in the `src/` directory) gets transpiled by `tsc`
|
||||
into JavaScript in the `build/app/` directory, which gets bundled in the
|
||||
generated app's binary and is loaded by the node (main) process when the app
|
||||
starts.
|
||||
- The _main_ process is runs the embedded node. This process can deal with the
|
||||
host OS - it is conceptually like a `node` repl running on your machine. In our
|
||||
case, the TypeScript code (in the `src/` directory) gets transpiled by `tsc`
|
||||
into JavaScript in the `build/app/` directory, which gets bundled in the
|
||||
generated app's binary and is loaded by the node (main) process when the app
|
||||
starts.
|
||||
|
||||
* The _renderer_ process is a regular web app that gets loaded into the embedded
|
||||
Chromium. When the main process starts, it creates a new "window" that shows
|
||||
this embedded Chromium. In our case, we build and bundle a static export of
|
||||
the [Photos web app](../web/README.md) in the generated app. This gets loaded
|
||||
by the embedded Chromium at runtime, acting as the app's UI.
|
||||
- The _renderer_ process is a regular web app that gets loaded into the embedded
|
||||
Chromium. When the main process starts, it creates a new "window" that shows
|
||||
this embedded Chromium. In our case, we build and bundle a static export of
|
||||
the [Photos web app](../web/README.md) in the generated app. This gets loaded
|
||||
by the embedded Chromium at runtime, acting as the app's UI.
|
||||
|
|
|
@ -4,21 +4,22 @@
|
|||
"private": true,
|
||||
"description": "Desktop client for Ente Photos",
|
||||
"author": "Ente <code@ente.io>",
|
||||
"main": "build/app/main.js",
|
||||
"main": "app/main.js",
|
||||
"scripts": {
|
||||
"build": "mkdir -p build && yarn build-renderer && yarn build-main",
|
||||
"build-main": "tsc && electron-builder --config.compression=store",
|
||||
"build-renderer": "cd ../web && yarn install && yarn build:photos && cd ../desktop/build && rm -f out && ln -sf ../../web/apps/photos/out",
|
||||
"dev": "concurrently \"yarn dev-main\" \"yarn dev-renderer\"",
|
||||
"dev-main": "tsc && electron build/app/main.js",
|
||||
"dev-renderer": "cd ../web && yarn install && yarn dev:photos && cd ../desktop/build && rm -f out && ln -sf ../../web/apps/photos/out",
|
||||
"build": "yarn build-renderer && yarn build-main",
|
||||
"build-main": "tsc && electron-builder",
|
||||
"build-main:quick": "tsc && electron-builder --config.compression=store",
|
||||
"build-renderer": "cd ../web && yarn install && yarn build:photos && cd ../desktop && rm -f out && ln -sf ../web/apps/photos/out",
|
||||
"build:quick": "yarn build-renderer && yarn build-main:quick",
|
||||
"dev": "concurrently --names 'main,rndr,tscw' \"yarn dev-main\" \"yarn dev-renderer\" \"yarn dev-main-watch\"",
|
||||
"dev-main": "tsc && electron app/main.js",
|
||||
"dev-main-watch": "tsc --watch --preserveWatchOutput",
|
||||
"dev-renderer": "cd ../web && yarn install && yarn dev:photos",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"lint": "yarn prettier --check . && eslint \"src/**/*.ts\"",
|
||||
"lint-fix": "yarn prettier --write . && eslint --fix .",
|
||||
"watch": "tsc -w"
|
||||
"lint-fix": "yarn prettier --write . && eslint --fix src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/electron": "^2.5.1",
|
||||
"any-shell-escape": "^0.1.1",
|
||||
"auto-launch": "^5.0.5",
|
||||
"chokidar": "^3.5.3",
|
||||
|
@ -31,14 +32,13 @@
|
|||
"get-folder-size": "^2.0.1",
|
||||
"html-entities": "^2.4.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"next-electron-server": "file:./thirdparty/next-electron-server",
|
||||
"next-electron-server": "^1",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"onnxruntime-node": "^1.16.3",
|
||||
"promise-fs": "^2.1.1"
|
||||
},
|
||||
"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",
|
||||
|
@ -55,7 +55,7 @@
|
|||
"eslint": "^7.23.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "2.5.1",
|
||||
"prettier": "^3",
|
||||
"prettier-plugin-organize-imports": "^3.2",
|
||||
"prettier-plugin-packagejson": "^2.4",
|
||||
"typescript": "^4.2.3"
|
||||
|
@ -112,26 +112,20 @@
|
|||
"x64ArchFiles": "Contents/Resources/ggmlclip-mac"
|
||||
},
|
||||
"afterSign": "electron-builder-notarize",
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "resources",
|
||||
"to": "resources",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"asarUnpack": [
|
||||
"node_modules/ffmpeg-static/bin/${os}/${arch}/ffmpeg",
|
||||
"node_modules/ffmpeg-static/index.js",
|
||||
"node_modules/ffmpeg-static/package.json"
|
||||
],
|
||||
"files": [
|
||||
"build/app/**/*",
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "build/out",
|
||||
"to": "out"
|
||||
"from": "build",
|
||||
"to": "resources"
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"app/**/*",
|
||||
"out"
|
||||
]
|
||||
},
|
||||
"productName": "ente",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
defaults.url=https://sentry.ente.io/
|
||||
defaults.org=ente
|
||||
defaults.project=desktop-photos
|
|
@ -22,7 +22,7 @@ const getCacheBucketDir = async (cacheName: string) => {
|
|||
|
||||
export async function openDiskCache(
|
||||
cacheName: string,
|
||||
cacheLimitInBytes?: number
|
||||
cacheLimitInBytes?: number,
|
||||
) {
|
||||
const cacheBucketDir = await getCacheBucketDir(cacheName);
|
||||
if (!existsSync(cacheBucketDir)) {
|
||||
|
@ -42,7 +42,7 @@ export async function deleteDiskCache(cacheName: string) {
|
|||
}
|
||||
|
||||
export async function setCustomCacheDirectory(
|
||||
directory: string
|
||||
directory: string,
|
||||
): Promise<void> {
|
||||
await ipcRenderer.invoke("set-custom-cache-directory", directory);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { isExecError, parseExecError } from "../utils/error";
|
|||
|
||||
export async function computeImageEmbedding(
|
||||
model: Model,
|
||||
imageData: Uint8Array
|
||||
imageData: Uint8Array,
|
||||
): Promise<Float32Array> {
|
||||
let tempInputFilePath = null;
|
||||
try {
|
||||
|
@ -15,7 +15,7 @@ export async function computeImageEmbedding(
|
|||
const embedding = await ipcRenderer.invoke(
|
||||
"compute-image-embedding",
|
||||
model,
|
||||
tempInputFilePath
|
||||
tempInputFilePath,
|
||||
);
|
||||
return embedding;
|
||||
} catch (err) {
|
||||
|
@ -34,13 +34,13 @@ export async function computeImageEmbedding(
|
|||
|
||||
export async function computeTextEmbedding(
|
||||
model: Model,
|
||||
text: string
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
try {
|
||||
const embedding = await ipcRenderer.invoke(
|
||||
"compute-text-embedding",
|
||||
model,
|
||||
text
|
||||
text,
|
||||
);
|
||||
return embedding;
|
||||
} catch (err) {
|
||||
|
|
|
@ -36,9 +36,4 @@ export const getPlatform = async (): Promise<"mac" | "windows" | "linux"> => {
|
|||
}
|
||||
};
|
||||
|
||||
export {
|
||||
getSentryUserID,
|
||||
logToDisk,
|
||||
openLogDirectory,
|
||||
updateOptOutOfCrashReports,
|
||||
} from "../services/logging";
|
||||
export { logToDisk, openLogDirectory } from "../services/logging";
|
||||
|
|
|
@ -2,7 +2,6 @@ import { logError } from "../services/logging";
|
|||
import { keysStore } from "../stores/keys.store";
|
||||
import { safeStorageStore } from "../stores/safeStorage.store";
|
||||
import { uploadStatusStore } from "../stores/upload.store";
|
||||
import { userPreferencesStore } from "../stores/userPreferences.store";
|
||||
import { watchStore } from "../stores/watch.store";
|
||||
|
||||
export const clearElectronStore = () => {
|
||||
|
@ -11,7 +10,6 @@ export const clearElectronStore = () => {
|
|||
keysStore.clear();
|
||||
safeStorageStore.clear();
|
||||
watchStore.clear();
|
||||
userPreferencesStore.delete("optOutOfCrashReports");
|
||||
} catch (e) {
|
||||
logError(e, "error while clearing electron store");
|
||||
throw e;
|
||||
|
|
|
@ -13,7 +13,7 @@ export const checkExistsAndCreateDir = async (dirPath: string) => {
|
|||
|
||||
export const saveStreamToDisk = async (
|
||||
filePath: string,
|
||||
fileStream: ReadableStream<Uint8Array>
|
||||
fileStream: ReadableStream<Uint8Array>,
|
||||
) => {
|
||||
await writeStream(filePath, fileStream);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ export async function runFFmpegCmd(
|
|||
cmd: string[],
|
||||
inputFile: File | ElectronFile,
|
||||
outputFileName: string,
|
||||
dontTimeout?: boolean
|
||||
dontTimeout?: boolean,
|
||||
) {
|
||||
let inputFilePath = null;
|
||||
let createdTempInputFile = null;
|
||||
|
@ -16,7 +16,7 @@ export async function runFFmpegCmd(
|
|||
if (!existsSync(inputFile.path)) {
|
||||
const tempFilePath = await ipcRenderer.invoke(
|
||||
"get-temp-file-path",
|
||||
inputFile.name
|
||||
inputFile.name,
|
||||
);
|
||||
await writeStream(tempFilePath, await inputFile.stream());
|
||||
inputFilePath = tempFilePath;
|
||||
|
@ -29,7 +29,7 @@ export async function runFFmpegCmd(
|
|||
cmd,
|
||||
inputFilePath,
|
||||
outputFileName,
|
||||
dontTimeout
|
||||
dontTimeout,
|
||||
);
|
||||
return new File([outputFileData], outputFileName);
|
||||
} finally {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { isPlatform } from "../utils/common/platform";
|
|||
|
||||
export async function convertToJPEG(
|
||||
fileData: Uint8Array,
|
||||
filename: string
|
||||
filename: string,
|
||||
): Promise<Uint8Array> {
|
||||
if (isPlatform("windows")) {
|
||||
throw Error(CustomErrors.WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED);
|
||||
|
@ -16,7 +16,7 @@ export async function convertToJPEG(
|
|||
const convertedFileData = await ipcRenderer.invoke(
|
||||
"convert-to-jpeg",
|
||||
fileData,
|
||||
filename
|
||||
filename,
|
||||
);
|
||||
return convertedFileData;
|
||||
}
|
||||
|
@ -24,20 +24,20 @@ export async function convertToJPEG(
|
|||
export async function generateImageThumbnail(
|
||||
inputFile: File | ElectronFile,
|
||||
maxDimension: number,
|
||||
maxSize: number
|
||||
maxSize: number,
|
||||
): Promise<Uint8Array> {
|
||||
let inputFilePath = null;
|
||||
let createdTempInputFile = null;
|
||||
try {
|
||||
if (isPlatform("windows")) {
|
||||
throw Error(
|
||||
CustomErrors.WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED
|
||||
CustomErrors.WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED,
|
||||
);
|
||||
}
|
||||
if (!existsSync(inputFile.path)) {
|
||||
const tempFilePath = await ipcRenderer.invoke(
|
||||
"get-temp-file-path",
|
||||
inputFile.name
|
||||
inputFile.name,
|
||||
);
|
||||
await writeStream(tempFilePath, await inputFile.stream());
|
||||
inputFilePath = tempFilePath;
|
||||
|
@ -49,7 +49,7 @@ export async function generateImageThumbnail(
|
|||
"generate-image-thumbnail",
|
||||
inputFilePath,
|
||||
maxDimension,
|
||||
maxSize
|
||||
maxSize,
|
||||
);
|
||||
return thumbnail;
|
||||
} finally {
|
||||
|
|
|
@ -6,7 +6,7 @@ export async function setEncryptionKey(encryptionKey: string) {
|
|||
try {
|
||||
const encryptedKey: Buffer = await ipcRenderer.invoke(
|
||||
"safeStorage-encrypt",
|
||||
encryptionKey
|
||||
encryptionKey,
|
||||
);
|
||||
const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64");
|
||||
safeStorageStore.set("encryptionKey", b64EncryptedKey);
|
||||
|
@ -21,7 +21,7 @@ export async function getEncryptionKey(): Promise<string> {
|
|||
const b64EncryptedKey = safeStorageStore.get("encryptionKey");
|
||||
if (b64EncryptedKey) {
|
||||
const keyBuffer = new Uint8Array(
|
||||
Buffer.from(b64EncryptedKey, "base64")
|
||||
Buffer.from(b64EncryptedKey, "base64"),
|
||||
);
|
||||
return await ipcRenderer.invoke("safeStorage-decrypt", keyBuffer);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ export const reloadWindow = () => {
|
|||
};
|
||||
|
||||
export const registerUpdateEventListener = (
|
||||
showUpdateDialog: (updateInfo: AppUpdateInfo) => void
|
||||
showUpdateDialog: (updateInfo: AppUpdateInfo) => void,
|
||||
) => {
|
||||
ipcRenderer.removeAllListeners("show-update-dialog");
|
||||
ipcRenderer.on("show-update-dialog", (_, updateInfo: AppUpdateInfo) => {
|
||||
|
|
|
@ -39,7 +39,7 @@ export const getPendingUploads = async () => {
|
|||
export const showUploadDirsDialog = async () => {
|
||||
try {
|
||||
const filePaths: string[] = await ipcRenderer.invoke(
|
||||
"show-upload-dirs-dialog"
|
||||
"show-upload-dirs-dialog",
|
||||
);
|
||||
const files = await Promise.all(filePaths.map(getElectronFile));
|
||||
return files;
|
||||
|
@ -51,7 +51,7 @@ export const showUploadDirsDialog = async () => {
|
|||
export const showUploadFilesDialog = async () => {
|
||||
try {
|
||||
const filePaths: string[] = await ipcRenderer.invoke(
|
||||
"show-upload-files-dialog"
|
||||
"show-upload-files-dialog",
|
||||
);
|
||||
const files = await Promise.all(filePaths.map(getElectronFile));
|
||||
return files;
|
||||
|
@ -63,7 +63,7 @@ export const showUploadFilesDialog = async () => {
|
|||
export const showUploadZipDialog = async () => {
|
||||
try {
|
||||
const filePaths: string[] = await ipcRenderer.invoke(
|
||||
"show-upload-zip-dialog"
|
||||
"show-upload-zip-dialog",
|
||||
);
|
||||
let files: ElectronFile[] = [];
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { isMappingPresent } from "../utils/watch";
|
|||
export async function addWatchMapping(
|
||||
rootFolderName: string,
|
||||
folderPath: string,
|
||||
uploadStrategy: number
|
||||
uploadStrategy: number,
|
||||
) {
|
||||
ElectronLog.log(`Adding watch mapping: ${folderPath}`);
|
||||
const watchMappings = getWatchMappings();
|
||||
|
@ -35,7 +35,7 @@ export async function addWatchMapping(
|
|||
export async function removeWatchMapping(folderPath: string) {
|
||||
let watchMappings = getWatchMappings();
|
||||
const watchMapping = watchMappings.find(
|
||||
(mapping) => mapping.folderPath === folderPath
|
||||
(mapping) => mapping.folderPath === folderPath,
|
||||
);
|
||||
|
||||
if (!watchMapping) {
|
||||
|
@ -47,7 +47,7 @@ export async function removeWatchMapping(folderPath: string) {
|
|||
});
|
||||
|
||||
watchMappings = watchMappings.filter(
|
||||
(mapping) => mapping.folderPath !== watchMapping.folderPath
|
||||
(mapping) => mapping.folderPath !== watchMapping.folderPath,
|
||||
);
|
||||
|
||||
setWatchMappings(watchMappings);
|
||||
|
@ -55,11 +55,11 @@ export async function removeWatchMapping(folderPath: string) {
|
|||
|
||||
export function updateWatchMappingSyncedFiles(
|
||||
folderPath: string,
|
||||
files: WatchMapping["syncedFiles"]
|
||||
files: WatchMapping["syncedFiles"],
|
||||
): void {
|
||||
const watchMappings = getWatchMappings();
|
||||
const watchMapping = watchMappings.find(
|
||||
(mapping) => mapping.folderPath === folderPath
|
||||
(mapping) => mapping.folderPath === folderPath,
|
||||
);
|
||||
|
||||
if (!watchMapping) {
|
||||
|
@ -72,11 +72,11 @@ export function updateWatchMappingSyncedFiles(
|
|||
|
||||
export function updateWatchMappingIgnoredFiles(
|
||||
folderPath: string,
|
||||
files: WatchMapping["ignoredFiles"]
|
||||
files: WatchMapping["ignoredFiles"],
|
||||
): void {
|
||||
const watchMappings = getWatchMappings();
|
||||
const watchMapping = watchMappings.find(
|
||||
(mapping) => mapping.folderPath === folderPath
|
||||
(mapping) => mapping.folderPath === folderPath,
|
||||
);
|
||||
|
||||
if (!watchMapping) {
|
||||
|
@ -90,7 +90,7 @@ export function updateWatchMappingIgnoredFiles(
|
|||
export function registerWatcherFunctions(
|
||||
addFile: (file: ElectronFile) => Promise<void>,
|
||||
removeFile: (path: string) => Promise<void>,
|
||||
removeFolder: (folderPath: string) => Promise<void>
|
||||
removeFolder: (folderPath: string) => Promise<void>,
|
||||
) {
|
||||
ipcRenderer.removeAllListeners("watch-add");
|
||||
ipcRenderer.removeAllListeners("watch-change");
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
const PROD_HOST_URL: string = "ente://app";
|
||||
const RENDERER_OUTPUT_DIR: string = "./out";
|
||||
const LOG_FILENAME = "ente.log";
|
||||
const MAX_LOG_SIZE = 50 * 1024 * 1024; // 50MB
|
||||
|
||||
const FILE_STREAM_CHUNK_SIZE: number = 4 * 1024 * 1024;
|
||||
|
||||
const SENTRY_DSN = "https://759d8498487a81ac33a0c2efa2a42c4f@sentry.ente.io/9";
|
||||
|
||||
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,
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
import { app, BrowserWindow } from "electron";
|
||||
import electronReload from "electron-reload";
|
||||
import serveNextAt from "next-electron-server";
|
||||
import { initWatcher } from "./services/chokidar";
|
||||
import { initSentry } from "./services/sentry";
|
||||
import { getOptOutOfCrashReports } from "./services/userPreference";
|
||||
import { isDev } from "./utils/common";
|
||||
import { addAllowOriginHeader } from "./utils/cors";
|
||||
import { createWindow } from "./utils/createWindow";
|
||||
|
@ -16,9 +16,7 @@ import {
|
|||
handleUpdates,
|
||||
logSystemInfo,
|
||||
setupMacWindowOnDockIconClick,
|
||||
setupMainHotReload,
|
||||
setupMainMenu,
|
||||
setupNextElectronServe,
|
||||
setupTrayItem,
|
||||
} from "./utils/main";
|
||||
import { setupMainProcessStatsLogger } from "./utils/processStats";
|
||||
|
@ -29,8 +27,6 @@ let appIsQuitting = false;
|
|||
|
||||
let updateIsAvailable = false;
|
||||
|
||||
let optedOutOfCrashReports = false;
|
||||
|
||||
export const isAppQuitting = (): boolean => {
|
||||
return appIsQuitting;
|
||||
};
|
||||
|
@ -42,22 +38,48 @@ export const setIsAppQuitting = (value: boolean): void => {
|
|||
export const isUpdateAvailable = (): boolean => {
|
||||
return updateIsAvailable;
|
||||
};
|
||||
|
||||
export const setIsUpdateAvailable = (value: boolean): void => {
|
||||
updateIsAvailable = value;
|
||||
};
|
||||
|
||||
export const hasOptedOutOfCrashReports = (): boolean => {
|
||||
return optedOutOfCrashReports;
|
||||
/**
|
||||
* Hot reload the main process if anything changes in the source directory that
|
||||
* we're running from.
|
||||
*
|
||||
* In particular, this gets triggered when the `tsc -w` rebuilds JS files in the
|
||||
* `app/` directory when we change the TS files in the `src/` directory.
|
||||
*/
|
||||
const setupMainHotReload = () => {
|
||||
if (isDev) {
|
||||
electronReload(__dirname, {});
|
||||
}
|
||||
};
|
||||
|
||||
export const updateOptOutOfCrashReports = (value: boolean): void => {
|
||||
optedOutOfCrashReports = value;
|
||||
/**
|
||||
* The URL where the renderer HTML is being served from.
|
||||
*/
|
||||
export const rendererURL = "next://app";
|
||||
|
||||
/**
|
||||
* next-electron-server allows up to directly use the output of `next build` in
|
||||
* production mode and `next dev` in development mode, whilst keeping the rest
|
||||
* of our code the same.
|
||||
*
|
||||
* It uses protocol handlers to serve files from the "next://app" protocol
|
||||
*
|
||||
* - In development this is proxied to http://localhost:3000
|
||||
* - In production it serves files from the `/out` directory
|
||||
*
|
||||
* For more details, see this comparison:
|
||||
* https://github.com/HaNdTriX/next-electron-server/issues/5
|
||||
*/
|
||||
const setupRendererServer = () => {
|
||||
serveNextAt(rendererURL);
|
||||
};
|
||||
|
||||
setupMainHotReload();
|
||||
|
||||
setupNextElectronServe();
|
||||
|
||||
setupRendererServer();
|
||||
setupLogging(isDev);
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
@ -83,11 +105,6 @@ if (!gotTheLock) {
|
|||
app.on("ready", async () => {
|
||||
logSystemInfo();
|
||||
setupMainProcessStatsLogger();
|
||||
const hasOptedOutOfCrashReports = getOptOutOfCrashReports();
|
||||
updateOptOutOfCrashReports(hasOptedOutOfCrashReports);
|
||||
if (!hasOptedOutOfCrashReports) {
|
||||
initSentry();
|
||||
}
|
||||
mainWindow = await createWindow();
|
||||
const tray = setupTrayItem(mainWindow);
|
||||
const watcher = initWatcher(mainWindow);
|
||||
|
|
|
@ -8,12 +8,10 @@ import { computeImageEmbedding, computeTextEmbedding } from "./api/clip";
|
|||
import {
|
||||
getAppVersion,
|
||||
getPlatform,
|
||||
getSentryUserID,
|
||||
logToDisk,
|
||||
openDirectory,
|
||||
openLogDirectory,
|
||||
selectDirectory,
|
||||
updateOptOutOfCrashReports,
|
||||
} from "./api/common";
|
||||
import { clearElectronStore } from "./api/electronStore";
|
||||
import {
|
||||
|
@ -61,13 +59,11 @@ import {
|
|||
updateWatchMappingSyncedFiles,
|
||||
} from "./api/watch";
|
||||
import { setupLogging } from "./utils/logging";
|
||||
import { fixHotReloadNext12 } from "./utils/preload";
|
||||
import {
|
||||
logRendererProcessMemoryUsage,
|
||||
setupRendererProcessStatsLogger,
|
||||
} from "./utils/processStats";
|
||||
|
||||
fixHotReloadNext12();
|
||||
setupLogging();
|
||||
setupRendererProcessStatsLogger();
|
||||
|
||||
|
@ -108,7 +104,6 @@ windowObject["ElectronAPIs"] = {
|
|||
registerUpdateEventListener,
|
||||
updateAndRestart,
|
||||
skipAppUpdate,
|
||||
getSentryUserID,
|
||||
getAppVersion,
|
||||
runFFmpegCmd,
|
||||
muteUpdateNotification,
|
||||
|
@ -120,7 +115,6 @@ windowObject["ElectronAPIs"] = {
|
|||
deleteFolder,
|
||||
rename,
|
||||
deleteFile,
|
||||
updateOptOutOfCrashReports,
|
||||
computeImageEmbedding,
|
||||
computeTextEmbedding,
|
||||
getPlatform,
|
||||
|
|
|
@ -25,7 +25,7 @@ export function setupAutoUpdater(mainWindow: BrowserWindow) {
|
|||
checkForUpdateAndNotify(mainWindow);
|
||||
setInterval(
|
||||
() => checkForUpdateAndNotify(mainWindow),
|
||||
ONE_DAY_IN_MICROSECOND
|
||||
ONE_DAY_IN_MICROSECOND,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
|
|||
if (
|
||||
compareVersions(
|
||||
updateCheckResult.updateInfo.version,
|
||||
app.getVersion()
|
||||
app.getVersion(),
|
||||
) <= 0
|
||||
) {
|
||||
log.debug("already at latest version");
|
||||
|
@ -60,7 +60,7 @@ async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
|
|||
) {
|
||||
log.info(
|
||||
"user chose to skip version ",
|
||||
updateCheckResult.updateInfo.version
|
||||
updateCheckResult.updateInfo.version,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
|
|||
isPlatform("mac") &&
|
||||
compareVersions(
|
||||
updateCheckResult.updateInfo.version,
|
||||
desktopCutoffVersion
|
||||
desktopCutoffVersion,
|
||||
) > 0
|
||||
) {
|
||||
log.debug("auto update not possible due to key change");
|
||||
|
@ -91,7 +91,7 @@ async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
|
|||
) {
|
||||
log.info(
|
||||
"user chose to mute update notification for version ",
|
||||
updateCheckResult.updateInfo.version
|
||||
updateCheckResult.updateInfo.version,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
|
|||
autoUpdatable: true,
|
||||
version: updateCheckResult.updateInfo.version,
|
||||
}),
|
||||
FIVE_MIN_IN_MICROSECOND
|
||||
FIVE_MIN_IN_MICROSECOND,
|
||||
);
|
||||
});
|
||||
autoUpdater.on("error", (error) => {
|
||||
|
@ -152,7 +152,7 @@ async function getDesktopCutoffVersion() {
|
|||
|
||||
function showUpdateDialog(
|
||||
mainWindow: BrowserWindow,
|
||||
updateInfo: AppUpdateInfo
|
||||
updateInfo: AppUpdateInfo,
|
||||
) {
|
||||
mainWindow.webContents.send("show-update-dialog", updateInfo);
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ export async function getClipImageModelPath(type: "ggml" | "onnx") {
|
|||
log.info("clip image model not found, downloading");
|
||||
imageModelDownloadInProgress = downloadModel(
|
||||
modelSavePath,
|
||||
IMAGE_MODEL_DOWNLOAD_URL[type]
|
||||
IMAGE_MODEL_DOWNLOAD_URL[type],
|
||||
);
|
||||
await imageModelDownloadInProgress;
|
||||
} else {
|
||||
|
@ -111,11 +111,11 @@ export async function getClipImageModelPath(type: "ggml" | "onnx") {
|
|||
if (localFileSize !== IMAGE_MODEL_SIZE_IN_BYTES[type]) {
|
||||
log.info(
|
||||
"clip image model size mismatch, downloading again got:",
|
||||
localFileSize
|
||||
localFileSize,
|
||||
);
|
||||
imageModelDownloadInProgress = downloadModel(
|
||||
modelSavePath,
|
||||
IMAGE_MODEL_DOWNLOAD_URL[type]
|
||||
IMAGE_MODEL_DOWNLOAD_URL[type],
|
||||
);
|
||||
await imageModelDownloadInProgress;
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ export async function getClipTextModelPath(type: "ggml" | "onnx") {
|
|||
if (localFileSize !== TEXT_MODEL_SIZE_IN_BYTES[type]) {
|
||||
log.info(
|
||||
"clip text model size mismatch, downloading again got:",
|
||||
localFileSize
|
||||
localFileSize,
|
||||
);
|
||||
textModelDownloadInProgress = true;
|
||||
downloadModel(modelSavePath, TEXT_MODEL_DOWNLOAD_URL[type])
|
||||
|
@ -212,7 +212,7 @@ function getTokenizer() {
|
|||
|
||||
export async function computeImageEmbedding(
|
||||
model: Model,
|
||||
inputFilePath: string
|
||||
inputFilePath: string,
|
||||
): Promise<Float32Array> {
|
||||
if (!existsSync(inputFilePath)) {
|
||||
throw Error(CustomErrors.INVALID_FILE_PATH);
|
||||
|
@ -227,7 +227,7 @@ export async function computeImageEmbedding(
|
|||
}
|
||||
|
||||
export async function computeGGMLImageEmbedding(
|
||||
inputFilePath: string
|
||||
inputFilePath: string,
|
||||
): Promise<Float32Array> {
|
||||
try {
|
||||
const clipModelPath = await getClipImageModelPath("ggml");
|
||||
|
@ -263,7 +263,7 @@ export async function computeGGMLImageEmbedding(
|
|||
}
|
||||
|
||||
export async function computeONNXImageEmbedding(
|
||||
inputFilePath: string
|
||||
inputFilePath: string,
|
||||
): Promise<Float32Array> {
|
||||
try {
|
||||
const imageSession = await getOnnxImageSession();
|
||||
|
@ -277,7 +277,7 @@ export async function computeONNXImageEmbedding(
|
|||
log.info(
|
||||
`onnx image embedding time: ${Date.now() - t1} ms (prep:${
|
||||
t2 - t1
|
||||
} ms, extraction: ${Date.now() - t2} ms)`
|
||||
} ms, extraction: ${Date.now() - t2} ms)`,
|
||||
);
|
||||
const imageEmbedding = results["output"].data; // Float32Array
|
||||
return normalizeEmbedding(imageEmbedding);
|
||||
|
@ -289,7 +289,7 @@ export async function computeONNXImageEmbedding(
|
|||
|
||||
export async function computeTextEmbedding(
|
||||
model: Model,
|
||||
text: string
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
if (model === Model.GGML_CLIP) {
|
||||
return await computeGGMLTextEmbedding(text);
|
||||
|
@ -299,7 +299,7 @@ export async function computeTextEmbedding(
|
|||
}
|
||||
|
||||
export async function computeGGMLTextEmbedding(
|
||||
text: string
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
try {
|
||||
const clipModelPath = await getClipTextModelPath("ggml");
|
||||
|
@ -339,7 +339,7 @@ export async function computeGGMLTextEmbedding(
|
|||
}
|
||||
|
||||
export async function computeONNXTextEmbedding(
|
||||
text: string
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
try {
|
||||
const imageSession = await getOnnxTextSession();
|
||||
|
@ -354,7 +354,7 @@ export async function computeONNXTextEmbedding(
|
|||
log.info(
|
||||
`onnx text embedding time: ${Date.now() - t1} ms (prep:${
|
||||
t2 - t1
|
||||
} ms, extraction: ${Date.now() - t2} ms)`
|
||||
} ms, extraction: ${Date.now() - t2} ms)`,
|
||||
);
|
||||
const textEmbedding = results["output"].data; // Float32Array
|
||||
return normalizeEmbedding(textEmbedding);
|
||||
|
@ -444,7 +444,7 @@ async function getRGBData(inputFilePath: string) {
|
|||
|
||||
export const computeClipMatchScore = async (
|
||||
imageEmbedding: Float32Array,
|
||||
textEmbedding: Float32Array
|
||||
textEmbedding: Float32Array,
|
||||
) => {
|
||||
if (imageEmbedding.length !== textEmbedding.length) {
|
||||
throw Error("imageEmbedding and textEmbedding length mismatch");
|
||||
|
|
|
@ -11,7 +11,7 @@ const DEFAULT_CACHE_LIMIT = 1000 * 1000 * 1000; // 1GB
|
|||
export class DiskCache implements LimitedCache {
|
||||
constructor(
|
||||
private cacheBucketDir: string,
|
||||
private cacheLimit = DEFAULT_CACHE_LIMIT
|
||||
private cacheLimit = DEFAULT_CACHE_LIMIT,
|
||||
) {}
|
||||
|
||||
async put(cacheKey: string, response: Response): Promise<void> {
|
||||
|
@ -19,13 +19,13 @@ export class DiskCache implements LimitedCache {
|
|||
await writeStream(cachePath, response.body);
|
||||
DiskLRUService.enforceCacheSizeLimit(
|
||||
this.cacheBucketDir,
|
||||
this.cacheLimit
|
||||
this.cacheLimit,
|
||||
);
|
||||
}
|
||||
|
||||
async match(
|
||||
cacheKey: string,
|
||||
{ sizeInBytes }: { sizeInBytes?: number } = {}
|
||||
{ sizeInBytes }: { sizeInBytes?: number } = {},
|
||||
): Promise<Response> {
|
||||
const cachePath = path.join(this.cacheBucketDir, cacheKey);
|
||||
if (existsSync(cachePath)) {
|
||||
|
@ -33,7 +33,7 @@ export class DiskCache implements LimitedCache {
|
|||
if (sizeInBytes && fileStats.size !== sizeInBytes) {
|
||||
logError(
|
||||
Error(),
|
||||
"Cache key exists but size does not match. Deleting cache key."
|
||||
"Cache key exists but size does not match. Deleting cache key.",
|
||||
);
|
||||
unlink(cachePath).catch((e) => {
|
||||
if (e.code === "ENOENT") return;
|
||||
|
@ -47,14 +47,14 @@ export class DiskCache implements LimitedCache {
|
|||
// add fallback for old cache keys
|
||||
const oldCachePath = getOldAssetCachePath(
|
||||
this.cacheBucketDir,
|
||||
cacheKey
|
||||
cacheKey,
|
||||
);
|
||||
if (existsSync(oldCachePath)) {
|
||||
const fileStats = await stat(oldCachePath);
|
||||
if (sizeInBytes && fileStats.size !== sizeInBytes) {
|
||||
logError(
|
||||
Error(),
|
||||
"Old cache key exists but size does not match. Deleting cache key."
|
||||
"Old cache key exists but size does not match. Deleting cache key.",
|
||||
);
|
||||
unlink(oldCachePath).catch((e) => {
|
||||
if (e.code === "ENOENT") return;
|
||||
|
|
|
@ -60,7 +60,7 @@ class DiskLRUService {
|
|||
if (e.code !== "ENOENT") {
|
||||
logError(
|
||||
e,
|
||||
"Failed to evict least recently used"
|
||||
"Failed to evict least recently used",
|
||||
);
|
||||
}
|
||||
// ignoring the error, as it would get retried on the next run
|
||||
|
@ -77,7 +77,7 @@ class DiskLRUService {
|
|||
|
||||
private async findLeastRecentlyUsed(
|
||||
dir: string,
|
||||
result?: LeastRecentlyUsedResult
|
||||
result?: LeastRecentlyUsedResult,
|
||||
): Promise<LeastRecentlyUsedResult> {
|
||||
result = result || { atime: new Date(), path: "" };
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ export async function runFFmpegCmd(
|
|||
cmd: string[],
|
||||
inputFilePath: string,
|
||||
outputFileName: string,
|
||||
dontTimeout = false
|
||||
dontTimeout = false,
|
||||
) {
|
||||
let tempOutputFilePath: string;
|
||||
try {
|
||||
|
@ -49,7 +49,7 @@ export async function runFFmpegCmd(
|
|||
} else {
|
||||
await promiseWithTimeout(
|
||||
execAsync(escapedCmd),
|
||||
FFMPEG_EXECUTION_WAIT_TIME
|
||||
FFMPEG_EXECUTION_WAIT_TIME,
|
||||
);
|
||||
}
|
||||
if (!existsSync(tempOutputFilePath)) {
|
||||
|
@ -59,7 +59,7 @@ export async function runFFmpegCmd(
|
|||
"ffmpeg command execution time ",
|
||||
escapedCmd,
|
||||
Date.now() - startTime,
|
||||
"ms"
|
||||
"ms",
|
||||
);
|
||||
|
||||
const outputFile = await readFile(tempOutputFilePath);
|
||||
|
@ -87,7 +87,7 @@ export async function deleteTempFile(tempFilePath: string) {
|
|||
if (!tempFilePath.startsWith(tempDirPath)) {
|
||||
logErrorSentry(
|
||||
Error("not a temp file"),
|
||||
"tried to delete a non temp file"
|
||||
"tried to delete a non temp file",
|
||||
);
|
||||
}
|
||||
rmSync(tempFilePath, { force: true });
|
||||
|
|
|
@ -3,10 +3,11 @@ import StreamZip from "node-stream-zip";
|
|||
import path from "path";
|
||||
import * as fs from "promise-fs";
|
||||
import { Readable } from "stream";
|
||||
import { FILE_STREAM_CHUNK_SIZE } from "../config";
|
||||
import { ElectronFile } from "../types";
|
||||
import { logError } from "./logging";
|
||||
|
||||
const FILE_STREAM_CHUNK_SIZE: number = 4 * 1024 * 1024;
|
||||
|
||||
// https://stackoverflow.com/a/63111390
|
||||
export const getDirFilePaths = async (dirPath: string) => {
|
||||
if (!(await fs.stat(dirPath)).isDirectory()) {
|
||||
|
@ -37,7 +38,7 @@ export const getFileStream = async (filePath: string) => {
|
|||
buff,
|
||||
0,
|
||||
FILE_STREAM_CHUNK_SIZE,
|
||||
offset
|
||||
offset,
|
||||
)) as unknown as number;
|
||||
offset += bytesRead;
|
||||
if (bytesRead === 0) {
|
||||
|
@ -102,7 +103,7 @@ export const getValidPaths = (paths: string[]) => {
|
|||
|
||||
export const getZipFileStream = async (
|
||||
zip: StreamZip.StreamZipAsync,
|
||||
filePath: string
|
||||
filePath: string,
|
||||
) => {
|
||||
const stream = await zip.stream(filePath);
|
||||
const done = {
|
||||
|
@ -203,7 +204,7 @@ export async function isFolder(dirPath: string) {
|
|||
}
|
||||
|
||||
export const convertBrowserStreamToNode = (
|
||||
fileStream: ReadableStream<Uint8Array>
|
||||
fileStream: ReadableStream<Uint8Array>,
|
||||
) => {
|
||||
const reader = fileStream.getReader();
|
||||
const rs = new Readable();
|
||||
|
@ -228,7 +229,7 @@ export const convertBrowserStreamToNode = (
|
|||
|
||||
export async function writeNodeStream(
|
||||
filePath: string,
|
||||
fileStream: NodeJS.ReadableStream
|
||||
fileStream: NodeJS.ReadableStream,
|
||||
) {
|
||||
const writeable = fs.createWriteStream(filePath);
|
||||
|
||||
|
@ -251,7 +252,7 @@ export async function writeNodeStream(
|
|||
|
||||
export async function writeStream(
|
||||
filePath: string,
|
||||
fileStream: ReadableStream<Uint8Array>
|
||||
fileStream: ReadableStream<Uint8Array>,
|
||||
) {
|
||||
const readable = convertBrowserStreamToNode(fileStream);
|
||||
await writeNodeStream(filePath, readable);
|
||||
|
@ -266,7 +267,7 @@ export async function readTextFile(filePath: string) {
|
|||
|
||||
export async function moveFile(
|
||||
sourcePath: string,
|
||||
destinationPath: string
|
||||
destinationPath: string,
|
||||
): Promise<void> {
|
||||
if (!existsSync(sourcePath)) {
|
||||
throw new Error("File does not exist");
|
||||
|
|
|
@ -80,7 +80,7 @@ function getImageMagickStaticPath() {
|
|||
|
||||
export async function convertToJPEG(
|
||||
fileData: Uint8Array,
|
||||
filename: string
|
||||
filename: string,
|
||||
): Promise<Uint8Array> {
|
||||
let tempInputFilePath: string;
|
||||
let tempOutputFilePath: string;
|
||||
|
@ -96,7 +96,7 @@ export async function convertToJPEG(
|
|||
throw new Error("heic convert output file not found");
|
||||
}
|
||||
const convertedFileData = new Uint8Array(
|
||||
await readFile(tempOutputFilePath)
|
||||
await readFile(tempOutputFilePath),
|
||||
);
|
||||
return convertedFileData;
|
||||
} catch (e) {
|
||||
|
@ -118,11 +118,11 @@ export async function convertToJPEG(
|
|||
|
||||
async function runConvertCommand(
|
||||
tempInputFilePath: string,
|
||||
tempOutputFilePath: string
|
||||
tempOutputFilePath: string,
|
||||
) {
|
||||
const convertCmd = constructConvertCommand(
|
||||
tempInputFilePath,
|
||||
tempOutputFilePath
|
||||
tempOutputFilePath,
|
||||
);
|
||||
const escapedCmd = shellescape(convertCmd);
|
||||
log.info("running convert command: " + escapedCmd);
|
||||
|
@ -131,7 +131,7 @@ async function runConvertCommand(
|
|||
|
||||
function constructConvertCommand(
|
||||
tempInputFilePath: string,
|
||||
tempOutputFilePath: string
|
||||
tempOutputFilePath: string,
|
||||
) {
|
||||
let convertCmd: string[];
|
||||
if (isPlatform("mac")) {
|
||||
|
@ -157,7 +157,7 @@ function constructConvertCommand(
|
|||
return tempOutputFilePath;
|
||||
}
|
||||
return cmdPart;
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
throw Error(CustomErrors.INVALID_OS(process.platform));
|
||||
|
@ -168,7 +168,7 @@ function constructConvertCommand(
|
|||
export async function generateImageThumbnail(
|
||||
inputFilePath: string,
|
||||
width: number,
|
||||
maxSize: number
|
||||
maxSize: number,
|
||||
): Promise<Uint8Array> {
|
||||
let tempOutputFilePath: string;
|
||||
let quality = MAX_QUALITY;
|
||||
|
@ -180,7 +180,7 @@ export async function generateImageThumbnail(
|
|||
inputFilePath,
|
||||
tempOutputFilePath,
|
||||
width,
|
||||
quality
|
||||
quality,
|
||||
);
|
||||
|
||||
if (!existsSync(tempOutputFilePath)) {
|
||||
|
@ -206,14 +206,14 @@ async function runThumbnailGenerationCommand(
|
|||
inputFilePath: string,
|
||||
tempOutputFilePath: string,
|
||||
maxDimension: number,
|
||||
quality: number
|
||||
quality: number,
|
||||
) {
|
||||
const thumbnailGenerationCmd: string[] =
|
||||
constructThumbnailGenerationCommand(
|
||||
inputFilePath,
|
||||
tempOutputFilePath,
|
||||
maxDimension,
|
||||
quality
|
||||
quality,
|
||||
);
|
||||
const escapedCmd = shellescape(thumbnailGenerationCmd);
|
||||
log.info("running thumbnail generation command: " + escapedCmd);
|
||||
|
@ -223,7 +223,7 @@ function constructThumbnailGenerationCommand(
|
|||
inputFilePath: string,
|
||||
tempOutputFilePath: string,
|
||||
maxDimension: number,
|
||||
quality: number
|
||||
quality: number,
|
||||
) {
|
||||
let thumbnailGenerationCmd: string[];
|
||||
if (isPlatform("mac")) {
|
||||
|
@ -242,7 +242,7 @@ function constructThumbnailGenerationCommand(
|
|||
return quality.toString();
|
||||
}
|
||||
return cmdPart;
|
||||
}
|
||||
},
|
||||
);
|
||||
} else if (isPlatform("linux")) {
|
||||
thumbnailGenerationCmd =
|
||||
|
@ -259,13 +259,13 @@ function constructThumbnailGenerationCommand(
|
|||
if (cmdPart.includes(SAMPLE_SIZE_PLACEHOLDER)) {
|
||||
return cmdPart.replaceAll(
|
||||
SAMPLE_SIZE_PLACEHOLDER,
|
||||
(2 * maxDimension).toString()
|
||||
(2 * maxDimension).toString(),
|
||||
);
|
||||
}
|
||||
if (cmdPart.includes(MAX_DIMENSION_PLACEHOLDER)) {
|
||||
return cmdPart.replaceAll(
|
||||
MAX_DIMENSION_PLACEHOLDER,
|
||||
maxDimension.toString()
|
||||
maxDimension.toString(),
|
||||
);
|
||||
}
|
||||
if (cmdPart === QUALITY_PLACEHOLDER) {
|
||||
|
|
|
@ -12,11 +12,3 @@ export function openLogDirectory() {
|
|||
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");
|
||||
}
|
||||
|
||||
export function updateOptOutOfCrashReports(optOut: boolean) {
|
||||
return ipcRenderer.invoke("update-opt-out-crash-reports", optOut);
|
||||
}
|
||||
|
|
|
@ -1,68 +1,18 @@
|
|||
import * as Sentry from "@sentry/electron/dist/main";
|
||||
import { RELEASE_VERSION, SENTRY_DSN } from "../config";
|
||||
import { hasOptedOutOfCrashReports } from "../main";
|
||||
import { keysStore } from "../stores/keys.store";
|
||||
import { isDev } from "../utils/common";
|
||||
import { makeID } from "../utils/logging";
|
||||
import { logToDisk } from "./logging";
|
||||
|
||||
const ENV_DEVELOPMENT = "development";
|
||||
|
||||
const isDEVSentryENV = () =>
|
||||
process.env.NEXT_PUBLIC_SENTRY_ENV === ENV_DEVELOPMENT;
|
||||
|
||||
export function initSentry(): void {
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
release: RELEASE_VERSION,
|
||||
environment: isDev ? "development" : "production",
|
||||
});
|
||||
Sentry.setUser({ id: getSentryUserID() });
|
||||
}
|
||||
|
||||
/** Deprecated, but no alternative yet */
|
||||
export function logErrorSentry(
|
||||
error: any,
|
||||
msg: string,
|
||||
info?: Record<string, unknown>
|
||||
info?: Record<string, unknown>,
|
||||
) {
|
||||
const err = errorWithContext(error, msg);
|
||||
logToDisk(
|
||||
`error: ${error?.name} ${error?.message} ${
|
||||
error?.stack
|
||||
} msg: ${msg} info: ${JSON.stringify(info)}`
|
||||
} msg: ${msg} info: ${JSON.stringify(info)}`,
|
||||
);
|
||||
if (isDEVSentryENV()) {
|
||||
if (isDev) {
|
||||
console.log(error, { msg, info });
|
||||
}
|
||||
if (hasOptedOutOfCrashReports()) {
|
||||
return;
|
||||
}
|
||||
Sentry.captureException(err, {
|
||||
level: Sentry.Severity.Info,
|
||||
user: { id: getSentryUserID() },
|
||||
contexts: {
|
||||
...(info && {
|
||||
info: info,
|
||||
}),
|
||||
rootCause: { message: error?.message },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function errorWithContext(originalError: Error, context: string) {
|
||||
const errorWithContext = new Error(context);
|
||||
errorWithContext.stack =
|
||||
errorWithContext.stack.split("\n").slice(2, 4).join("\n") +
|
||||
"\n" +
|
||||
originalError.stack;
|
||||
return errorWithContext;
|
||||
}
|
||||
|
||||
export function getSentryUserID() {
|
||||
let anonymizeUserID = keysStore.get("AnonymizeUserID")?.id;
|
||||
if (!anonymizeUserID) {
|
||||
anonymizeUserID = makeID(6);
|
||||
keysStore.set("AnonymizeUserID", { id: anonymizeUserID });
|
||||
}
|
||||
return anonymizeUserID;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { getValidPaths, getZipFileStream } from "./fs";
|
|||
export const getSavedFilePaths = (type: FILE_PATH_TYPE) => {
|
||||
const paths =
|
||||
getValidPaths(
|
||||
uploadStatusStore.get(FILE_PATH_KEYS[type]) as string[]
|
||||
uploadStatusStore.get(FILE_PATH_KEYS[type]) as string[],
|
||||
) ?? [];
|
||||
|
||||
setToUploadFiles(type, paths);
|
||||
|
@ -17,7 +17,7 @@ export const getSavedFilePaths = (type: FILE_PATH_TYPE) => {
|
|||
export async function getZipEntryAsElectronFile(
|
||||
zipName: string,
|
||||
zip: StreamZip.StreamZipAsync,
|
||||
entry: StreamZip.ZipEntry
|
||||
entry: StreamZip.ZipEntry,
|
||||
): Promise<ElectronFile> {
|
||||
return {
|
||||
path: path
|
||||
|
|
|
@ -24,14 +24,6 @@ export function setMuteUpdateNotificationVersion(version: string) {
|
|||
userPreferencesStore.set("muteUpdateNotificationVersion", version);
|
||||
}
|
||||
|
||||
export function getOptOutOfCrashReports() {
|
||||
return userPreferencesStore.get("optOutOfCrashReports") ?? false;
|
||||
}
|
||||
|
||||
export function setOptOutOfCrashReports(optOut: boolean) {
|
||||
userPreferencesStore.set("optOutOfCrashReports", optOut);
|
||||
}
|
||||
|
||||
export function clearSkipAppVersion() {
|
||||
userPreferencesStore.delete("skipAppVersion");
|
||||
}
|
||||
|
|
|
@ -11,9 +11,6 @@ const userPreferencesSchema: Schema<UserPreferencesType> = {
|
|||
muteUpdateNotificationVersion: {
|
||||
type: "string",
|
||||
},
|
||||
optOutOfCrashReports: {
|
||||
type: "boolean",
|
||||
},
|
||||
customCacheDirectory: {
|
||||
type: "string",
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export interface LimitedCache {
|
||||
match: (
|
||||
key: string,
|
||||
options?: { sizeInBytes?: number }
|
||||
options?: { sizeInBytes?: number },
|
||||
) => Promise<Response>;
|
||||
put: (key: string, data: Response) => Promise<void>;
|
||||
delete: (key: string) => Promise<boolean>;
|
||||
|
|
|
@ -58,7 +58,6 @@ export interface UserPreferencesType {
|
|||
hideDockIcon: boolean;
|
||||
skipAppVersion: string;
|
||||
muteUpdateNotificationVersion: string;
|
||||
optOutOfCrashReports: boolean;
|
||||
customCacheDirectory: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
# CLIP Byte Pair Encoding JavaScript Port
|
||||
|
||||
A JavaScript port of [OpenAI's CLIP byte-pair-encoding tokenizer](https://github.com/openai/CLIP/blob/3bee28119e6b28e75b82b811b87b56935314e6a5/clip/simple_tokenizer.py).
|
||||
|
||||
```js
|
||||
import Tokenizer from "https://deno.land/x/clip_bpe@v0.0.6/mod.js";
|
||||
let t = new Tokenizer();
|
||||
|
||||
t.encode("hello") // [3306]
|
||||
t.encode("magnificent") // [10724]
|
||||
t.encode("magnificently") // [9725, 2922]
|
||||
t.decode(t.encode("HELLO")) // "hello "
|
||||
t.decode(t.encode("abc123")) // "abc 1 2 3 "
|
||||
t.decode(st.encode("let's see here")) // "let 's see here "
|
||||
t.encode("hello world!") // [3306, 1002, 256]
|
||||
t.encode("hello"); // [3306]
|
||||
t.encode("magnificent"); // [10724]
|
||||
t.encode("magnificently"); // [9725, 2922]
|
||||
t.decode(t.encode("HELLO")); // "hello "
|
||||
t.decode(t.encode("abc123")); // "abc 1 2 3 "
|
||||
t.decode(st.encode("let's see here")); // "let 's see here "
|
||||
t.encode("hello world!"); // [3306, 1002, 256]
|
||||
|
||||
// to encode for CLIP (trims to maximum of 77 tokens and adds start and end token, and pads with zeros if less than 77 tokens):
|
||||
t.encodeForCLIP("hello world!") // [49406,3306,1002,256,49407,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
||||
t.encodeForCLIP("hello world!"); // [49406,3306,1002,256,49407,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
||||
```
|
||||
|
||||
This encoder/decoder behaves differently to the the GPT-2/3 tokenizer (JavaScript version of that [here](https://github.com/latitudegames/GPT-3-Encoder)). For example, it doesn't preserve capital letters, as shown above.
|
||||
|
|
|
@ -75,7 +75,7 @@ export default class {
|
|||
constructor() {
|
||||
this.byteEncoder = bytesToUnicode();
|
||||
this.byteDecoder = Object.fromEntries(
|
||||
Object.entries(this.byteEncoder).map(([k, v]) => [v, Number(k)])
|
||||
Object.entries(this.byteEncoder).map(([k, v]) => [v, Number(k)]),
|
||||
);
|
||||
let merges = bpeVocabData.text.split("\n");
|
||||
merges = merges.slice(1, 49152 - 256 - 2 + 1);
|
||||
|
@ -346,10 +346,10 @@ export default class {
|
|||
vocab.push("<|startoftext|>", "<|endoftext|>");
|
||||
this.encoder = Object.fromEntries(vocab.map((v, i) => [v, i]));
|
||||
this.decoder = Object.fromEntries(
|
||||
Object.entries(this.encoder).map(([k, v]) => [v, k])
|
||||
Object.entries(this.encoder).map(([k, v]) => [v, k]),
|
||||
);
|
||||
this.bpeRanks = Object.fromEntries(
|
||||
mergedMerges.map((v, i) => [v.join("·😎·"), i])
|
||||
mergedMerges.map((v, i) => [v.join("·😎·"), i]),
|
||||
); // ·😎· because js doesn't yet have tuples
|
||||
this.cache = {
|
||||
"<|startoftext|>": "<|startoftext|>",
|
||||
|
@ -436,7 +436,7 @@ export default class {
|
|||
bpeTokens.push(
|
||||
...this.bpe(token)
|
||||
.split(" ")
|
||||
.map((bpeToken: string) => this.encoder[bpeToken])
|
||||
.map((bpeToken: string) => this.encoder[bpeToken]),
|
||||
);
|
||||
}
|
||||
return bpeTokens;
|
||||
|
|
|
@ -4,7 +4,7 @@ export const isDev = !app.isPackaged;
|
|||
|
||||
export const promiseWithTimeout = async <T>(
|
||||
request: Promise<T>,
|
||||
timeout: number
|
||||
timeout: number,
|
||||
): Promise<T> => {
|
||||
const timeoutRef: {
|
||||
current: NodeJS.Timeout;
|
||||
|
@ -12,7 +12,7 @@ export const promiseWithTimeout = async <T>(
|
|||
const rejectOnTimeout = new Promise<null>((_, reject) => {
|
||||
timeoutRef.current = setTimeout(
|
||||
() => reject(Error(CustomErrors.WAIT_TIME_EXCEEDED)),
|
||||
timeout
|
||||
timeout,
|
||||
);
|
||||
});
|
||||
const requestWithTimeOutCancellation = async () => {
|
||||
|
|
|
@ -16,6 +16,6 @@ export function addAllowOriginHeader(mainWindow: BrowserWindow) {
|
|||
callback({
|
||||
responseHeaders: details.responseHeaders,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { app, BrowserWindow, nativeImage } from "electron";
|
||||
import ElectronLog from "electron-log";
|
||||
import * as path from "path";
|
||||
import { PROD_HOST_URL } from "../config";
|
||||
import { isAppQuitting } from "../main";
|
||||
import { isAppQuitting, rendererURL } from "../main";
|
||||
import autoLauncher from "../services/autoLauncher";
|
||||
import { logErrorSentry } from "../services/sentry";
|
||||
import { getHideDockIconPreference } from "../services/userPreference";
|
||||
|
@ -41,21 +40,21 @@ export async function createWindow(): Promise<BrowserWindow> {
|
|||
|
||||
if (isDev) {
|
||||
splash.loadFile(`../resources/splash.html`);
|
||||
mainWindow.loadURL(PROD_HOST_URL);
|
||||
mainWindow.loadURL(rendererURL);
|
||||
// Open the DevTools.
|
||||
mainWindow.webContents.openDevTools();
|
||||
} else {
|
||||
splash.loadURL(
|
||||
`file://${path.join(process.resourcesPath, "splash.html")}`
|
||||
`file://${path.join(process.resourcesPath, "splash.html")}`,
|
||||
);
|
||||
mainWindow.loadURL(PROD_HOST_URL);
|
||||
mainWindow.loadURL(rendererURL);
|
||||
}
|
||||
mainWindow.webContents.on("did-fail-load", () => {
|
||||
splash.close();
|
||||
isDev
|
||||
? mainWindow.loadFile(`../resources/error.html`)
|
||||
: splash.loadURL(
|
||||
`file://${path.join(process.resourcesPath, "error.html")}`
|
||||
`file://${path.join(process.resourcesPath, "error.html")}`,
|
||||
);
|
||||
mainWindow.maximize();
|
||||
mainWindow.show();
|
||||
|
@ -76,7 +75,7 @@ export async function createWindow(): Promise<BrowserWindow> {
|
|||
logErrorSentry(
|
||||
Error("render-process-gone"),
|
||||
"webContents event render-process-gone",
|
||||
{ details }
|
||||
{ details },
|
||||
);
|
||||
ElectronLog.log("webContents event render-process-gone", details);
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ export const parseExecError = (err: any) => {
|
|||
if (errMessage.includes("Bad CPU type in executable")) {
|
||||
return CustomErrors.UNSUPPORTED_PLATFORM(
|
||||
process.platform,
|
||||
process.arch
|
||||
process.arch,
|
||||
);
|
||||
} else {
|
||||
return errMessage;
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
Tray,
|
||||
} from "electron";
|
||||
import path from "path";
|
||||
import { updateOptOutOfCrashReports } from "../main";
|
||||
import {
|
||||
getAppVersion,
|
||||
muteUpdateNotification,
|
||||
|
@ -27,11 +26,10 @@ import {
|
|||
convertToJPEG,
|
||||
generateImageThumbnail,
|
||||
} from "../services/imageProcessor";
|
||||
import { getSentryUserID, logErrorSentry } from "../services/sentry";
|
||||
import { logErrorSentry } from "../services/sentry";
|
||||
import {
|
||||
getCustomCacheDirectory,
|
||||
setCustomCacheDirectory,
|
||||
setOptOutOfCrashReports,
|
||||
} from "../services/userPreference";
|
||||
import { getPlatform } from "./common/platform";
|
||||
import { createWindow } from "./createWindow";
|
||||
|
@ -40,7 +38,7 @@ import { generateTempFilePath } from "./temp";
|
|||
export default function setupIpcComs(
|
||||
tray: Tray,
|
||||
mainWindow: BrowserWindow,
|
||||
watcher: chokidar.FSWatcher
|
||||
watcher: chokidar.FSWatcher,
|
||||
): void {
|
||||
ipcMain.handle("select-dir", async () => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
|
@ -146,9 +144,6 @@ export default function setupIpcComs(
|
|||
ipcMain.on("mute-update-notification", (_, version) => {
|
||||
muteUpdateNotification(version);
|
||||
});
|
||||
ipcMain.handle("get-sentry-id", () => {
|
||||
return getSentryUserID();
|
||||
});
|
||||
|
||||
ipcMain.handle("get-app-version", () => {
|
||||
return getAppVersion();
|
||||
|
@ -161,9 +156,9 @@ export default function setupIpcComs(
|
|||
cmd,
|
||||
inputFilePath,
|
||||
outputFileName,
|
||||
dontTimeout
|
||||
dontTimeout,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
ipcMain.handle("get-temp-file-path", (_, formatSuffix) => {
|
||||
return generateTempFilePath(formatSuffix);
|
||||
|
@ -176,13 +171,9 @@ export default function setupIpcComs(
|
|||
"generate-image-thumbnail",
|
||||
(_, fileData, maxDimension, maxSize) => {
|
||||
return generateImageThumbnail(fileData, maxDimension, maxSize);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle("update-opt-out-crash-reports", (_, optOut) => {
|
||||
setOptOutOfCrashReports(optOut);
|
||||
updateOptOutOfCrashReports(optOut);
|
||||
});
|
||||
ipcMain.handle("compute-image-embedding", (_, model, inputFilePath) => {
|
||||
return computeImageEmbedding(model, inputFilePath);
|
||||
});
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import log from "electron-log";
|
||||
import { LOG_FILENAME, MAX_LOG_SIZE } from "../config";
|
||||
|
||||
export function setupLogging(isDev?: boolean) {
|
||||
log.transports.file.fileName = LOG_FILENAME;
|
||||
log.transports.file.maxSize = MAX_LOG_SIZE;
|
||||
log.transports.file.fileName = "ente.log";
|
||||
log.transports.file.maxSize = 50 * 1024 * 1024; // 50MB;
|
||||
if (!isDev) {
|
||||
log.transports.console.level = false;
|
||||
}
|
||||
|
@ -11,22 +10,9 @@ export function setupLogging(isDev?: boolean) {
|
|||
"[{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;
|
||||
}
|
||||
|
||||
export function convertBytesToHumanReadable(
|
||||
bytes: number,
|
||||
precision = 2
|
||||
precision = 2,
|
||||
): string {
|
||||
if (bytes === 0 || isNaN(bytes)) {
|
||||
return "0 MB";
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { app, BrowserWindow, Menu, nativeImage, Tray } from "electron";
|
||||
import ElectronLog from "electron-log";
|
||||
import electronReload from "electron-reload";
|
||||
import serveNextAt from "next-electron-server";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { existsSync } from "promise-fs";
|
||||
import util from "util";
|
||||
import { PROD_HOST_URL, RENDERER_OUTPUT_DIR } from "../config";
|
||||
import { rendererURL } from "../main";
|
||||
import { setupAutoUpdater } from "../services/appUpdater";
|
||||
import autoLauncher from "../services/autoLauncher";
|
||||
import { getHideDockIconPreference } from "../services/userPreference";
|
||||
|
@ -27,7 +25,7 @@ export function setupTrayItem(mainWindow: BrowserWindow) {
|
|||
: "taskbar-icon.png";
|
||||
const trayImgPath = path.join(
|
||||
isDev ? "build" : process.resourcesPath,
|
||||
iconName
|
||||
iconName,
|
||||
);
|
||||
const trayIcon = nativeImage.createFromPath(trayImgPath);
|
||||
const tray = new Tray(trayIcon);
|
||||
|
@ -39,11 +37,22 @@ export function setupTrayItem(mainWindow: BrowserWindow) {
|
|||
export function handleDownloads(mainWindow: BrowserWindow) {
|
||||
mainWindow.webContents.session.on("will-download", (_, item) => {
|
||||
item.setSavePath(
|
||||
getUniqueSavePath(item.getFilename(), app.getPath("downloads"))
|
||||
getUniqueSavePath(item.getFilename(), app.getPath("downloads")),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function handleExternalLinks(mainWindow: BrowserWindow) {
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
if (!url.startsWith(rendererURL)) {
|
||||
require("electron").shell.openExternal(url);
|
||||
return { action: "deny" };
|
||||
} else {
|
||||
return { action: "allow" };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getUniqueSavePath(filename: string, directory: string): string {
|
||||
let uniqueFileSavePath = path.join(directory, filename);
|
||||
const { name: filenameWithoutExtension, ext: extension } =
|
||||
|
@ -76,18 +85,6 @@ export async function setupMainMenu(mainWindow: BrowserWindow) {
|
|||
Menu.setApplicationMenu(await buildMenuBar(mainWindow));
|
||||
}
|
||||
|
||||
export function setupMainHotReload() {
|
||||
if (isDev) {
|
||||
electronReload(__dirname, {});
|
||||
}
|
||||
}
|
||||
|
||||
export function setupNextElectronServe() {
|
||||
serveNextAt(PROD_HOST_URL, {
|
||||
outputDir: RENDERER_OUTPUT_DIR,
|
||||
});
|
||||
}
|
||||
|
||||
export async function handleDockIconHideOnAutoLaunch() {
|
||||
const shouldHideDockIcon = getHideDockIconPreference();
|
||||
const wasAutoLaunched = await autoLauncher.wasAutoLaunched();
|
||||
|
@ -110,17 +107,6 @@ export function logSystemInfo() {
|
|||
ElectronLog.info({ appVersion });
|
||||
}
|
||||
|
||||
export function handleExternalLinks(mainWindow: BrowserWindow) {
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
if (!url.startsWith(PROD_HOST_URL)) {
|
||||
require("electron").shell.openExternal(url);
|
||||
return { action: "deny" };
|
||||
} else {
|
||||
return { action: "allow" };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function checkIfInstalledViaBrew() {
|
||||
if (!isPlatform("mac")) {
|
||||
return false;
|
||||
|
|
|
@ -64,7 +64,7 @@ export async function buildMenuBar(mainWindow: BrowserWindow): Promise<Menu> {
|
|||
label: "View Changelog",
|
||||
click: () => {
|
||||
shell.openExternal(
|
||||
"https://github.com/ente-io/ente/blob/main/desktop/CHANGELOG.md"
|
||||
"https://github.com/ente-io/ente/blob/main/desktop/CHANGELOG.md",
|
||||
);
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import { webFrame } from "electron";
|
||||
|
||||
export const fixHotReloadNext12 = () => {
|
||||
webFrame.executeJavaScript(`Object.defineProperty(globalThis, 'WebSocket', {
|
||||
value: new Proxy(WebSocket, {
|
||||
construct: (Target, [url, protocols]) => {
|
||||
if (url.endsWith('/_next/webpack-hmr')) {
|
||||
// Fix the Next.js hmr client url
|
||||
return new Target("ws://localhost:3000/_next/webpack-hmr", protocols)
|
||||
} else {
|
||||
return new Target(url, protocols)
|
||||
}
|
||||
}
|
||||
})
|
||||
});`);
|
||||
};
|
|
@ -16,11 +16,11 @@ const HIGH_RENDERER_MEMORY_USAGE_THRESHOLD_IN_KILOBYTES = 1024 * 1024; // 1 GB
|
|||
|
||||
async function logMainProcessStats() {
|
||||
const processMemoryInfo = await getNormalizedProcessMemoryInfo(
|
||||
await process.getProcessMemoryInfo()
|
||||
await process.getProcessMemoryInfo(),
|
||||
);
|
||||
const cpuUsage = process.getCPUUsage();
|
||||
const heapStatistics = getNormalizedHeapStatistics(
|
||||
process.getHeapStatistics()
|
||||
process.getHeapStatistics(),
|
||||
);
|
||||
|
||||
ElectronLog.log("main process stats", {
|
||||
|
@ -42,11 +42,11 @@ async function logSpikeMainMemoryUsage() {
|
|||
const processMemoryInfo = await process.getProcessMemoryInfo();
|
||||
const currentMemoryUsage = Math.max(
|
||||
processMemoryInfo.residentSet ?? 0,
|
||||
processMemoryInfo.private
|
||||
processMemoryInfo.private,
|
||||
);
|
||||
const previousMemoryUsage = Math.max(
|
||||
previousMainProcessMemoryInfo.residentSet ?? 0,
|
||||
previousMainProcessMemoryInfo.private
|
||||
previousMainProcessMemoryInfo.private,
|
||||
);
|
||||
const isSpiking =
|
||||
currentMemoryUsage - previousMemoryUsage >=
|
||||
|
@ -66,7 +66,7 @@ async function logSpikeMainMemoryUsage() {
|
|||
await getNormalizedProcessMemoryInfo(previousMainProcessMemoryInfo);
|
||||
const cpuUsage = process.getCPUUsage();
|
||||
const heapStatistics = getNormalizedHeapStatistics(
|
||||
process.getHeapStatistics()
|
||||
process.getHeapStatistics(),
|
||||
);
|
||||
|
||||
ElectronLog.log("reporting main memory usage spike", {
|
||||
|
@ -94,12 +94,12 @@ async function logSpikeRendererMemoryUsage() {
|
|||
const processMemoryInfo = await process.getProcessMemoryInfo();
|
||||
const currentMemoryUsage = Math.max(
|
||||
processMemoryInfo.residentSet ?? 0,
|
||||
processMemoryInfo.private
|
||||
processMemoryInfo.private,
|
||||
);
|
||||
|
||||
const previousMemoryUsage = Math.max(
|
||||
previousRendererProcessMemoryInfo.private,
|
||||
previousRendererProcessMemoryInfo.residentSet ?? 0
|
||||
previousRendererProcessMemoryInfo.residentSet ?? 0,
|
||||
);
|
||||
const isSpiking =
|
||||
currentMemoryUsage - previousMemoryUsage >=
|
||||
|
@ -117,11 +117,11 @@ async function logSpikeRendererMemoryUsage() {
|
|||
await getNormalizedProcessMemoryInfo(processMemoryInfo);
|
||||
const normalizedPreviousProcessMemoryInfo =
|
||||
await getNormalizedProcessMemoryInfo(
|
||||
previousRendererProcessMemoryInfo
|
||||
previousRendererProcessMemoryInfo,
|
||||
);
|
||||
const cpuUsage = process.getCPUUsage();
|
||||
const heapStatistics = getNormalizedHeapStatistics(
|
||||
process.getHeapStatistics()
|
||||
process.getHeapStatistics(),
|
||||
);
|
||||
|
||||
ElectronLog.log("reporting renderer memory usage spike", {
|
||||
|
@ -140,11 +140,11 @@ async function logSpikeRendererMemoryUsage() {
|
|||
async function logRendererProcessStats() {
|
||||
const blinkMemoryInfo = getNormalizedBlinkMemoryInfo();
|
||||
const heapStatistics = getNormalizedHeapStatistics(
|
||||
process.getHeapStatistics()
|
||||
process.getHeapStatistics(),
|
||||
);
|
||||
const webFrameResourceUsage = getNormalizedWebFrameResourceUsage();
|
||||
const processMemoryInfo = await getNormalizedProcessMemoryInfo(
|
||||
await process.getProcessMemoryInfo()
|
||||
await process.getProcessMemoryInfo(),
|
||||
);
|
||||
ElectronLog.log("renderer process stats", {
|
||||
blinkMemoryInfo,
|
||||
|
@ -157,7 +157,7 @@ async function logRendererProcessStats() {
|
|||
export function setupMainProcessStatsLogger() {
|
||||
setInterval(
|
||||
logSpikeMainMemoryUsage,
|
||||
SPIKE_DETECTION_INTERVAL_IN_MICROSECONDS
|
||||
SPIKE_DETECTION_INTERVAL_IN_MICROSECONDS,
|
||||
);
|
||||
setInterval(logMainProcessStats, LOGGING_INTERVAL_IN_MICROSECONDS);
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ export function setupMainProcessStatsLogger() {
|
|||
export function setupRendererProcessStatsLogger() {
|
||||
setInterval(
|
||||
logSpikeRendererMemoryUsage,
|
||||
SPIKE_DETECTION_INTERVAL_IN_MICROSECONDS
|
||||
SPIKE_DETECTION_INTERVAL_IN_MICROSECONDS,
|
||||
);
|
||||
setInterval(logRendererProcessStats, LOGGING_INTERVAL_IN_MICROSECONDS);
|
||||
}
|
||||
|
@ -174,21 +174,21 @@ export async function logRendererProcessMemoryUsage(message: string) {
|
|||
const processMemoryInfo = await process.getProcessMemoryInfo();
|
||||
const processMemory = Math.max(
|
||||
processMemoryInfo.private,
|
||||
processMemoryInfo.residentSet ?? 0
|
||||
processMemoryInfo.residentSet ?? 0,
|
||||
);
|
||||
ElectronLog.log(
|
||||
"renderer ProcessMemory",
|
||||
message,
|
||||
convertBytesToHumanReadable(processMemory * 1024)
|
||||
convertBytesToHumanReadable(processMemory * 1024),
|
||||
);
|
||||
}
|
||||
|
||||
const getNormalizedProcessMemoryInfo = async (
|
||||
processMemoryInfo: Electron.ProcessMemoryInfo
|
||||
processMemoryInfo: Electron.ProcessMemoryInfo,
|
||||
) => {
|
||||
return {
|
||||
residentSet: convertBytesToHumanReadable(
|
||||
processMemoryInfo.residentSet * 1024
|
||||
processMemoryInfo.residentSet * 1024,
|
||||
),
|
||||
private: convertBytesToHumanReadable(processMemoryInfo.private * 1024),
|
||||
shared: convertBytesToHumanReadable(processMemoryInfo.shared * 1024),
|
||||
|
@ -199,40 +199,40 @@ const getNormalizedBlinkMemoryInfo = () => {
|
|||
const blinkMemoryInfo = process.getBlinkMemoryInfo();
|
||||
return {
|
||||
allocated: convertBytesToHumanReadable(
|
||||
blinkMemoryInfo.allocated * 1024
|
||||
blinkMemoryInfo.allocated * 1024,
|
||||
),
|
||||
total: convertBytesToHumanReadable(blinkMemoryInfo.total * 1024),
|
||||
};
|
||||
};
|
||||
|
||||
const getNormalizedHeapStatistics = (
|
||||
heapStatistics: Electron.HeapStatistics
|
||||
heapStatistics: Electron.HeapStatistics,
|
||||
) => {
|
||||
return {
|
||||
totalHeapSize: convertBytesToHumanReadable(
|
||||
heapStatistics.totalHeapSize * 1024
|
||||
heapStatistics.totalHeapSize * 1024,
|
||||
),
|
||||
totalHeapSizeExecutable: convertBytesToHumanReadable(
|
||||
heapStatistics.totalHeapSizeExecutable * 1024
|
||||
heapStatistics.totalHeapSizeExecutable * 1024,
|
||||
),
|
||||
totalPhysicalSize: convertBytesToHumanReadable(
|
||||
heapStatistics.totalPhysicalSize * 1024
|
||||
heapStatistics.totalPhysicalSize * 1024,
|
||||
),
|
||||
totalAvailableSize: convertBytesToHumanReadable(
|
||||
heapStatistics.totalAvailableSize * 1024
|
||||
heapStatistics.totalAvailableSize * 1024,
|
||||
),
|
||||
usedHeapSize: convertBytesToHumanReadable(
|
||||
heapStatistics.usedHeapSize * 1024
|
||||
heapStatistics.usedHeapSize * 1024,
|
||||
),
|
||||
|
||||
heapSizeLimit: convertBytesToHumanReadable(
|
||||
heapStatistics.heapSizeLimit * 1024
|
||||
heapStatistics.heapSizeLimit * 1024,
|
||||
),
|
||||
mallocedMemory: convertBytesToHumanReadable(
|
||||
heapStatistics.mallocedMemory * 1024
|
||||
heapStatistics.mallocedMemory * 1024,
|
||||
),
|
||||
peakMallocedMemory: convertBytesToHumanReadable(
|
||||
heapStatistics.peakMallocedMemory * 1024
|
||||
heapStatistics.peakMallocedMemory * 1024,
|
||||
),
|
||||
doesZapGarbage: heapStatistics.doesZapGarbage,
|
||||
};
|
||||
|
@ -244,51 +244,51 @@ const getNormalizedWebFrameResourceUsage = () => {
|
|||
images: {
|
||||
count: webFrameResourceUsage.images.count,
|
||||
size: convertBytesToHumanReadable(
|
||||
webFrameResourceUsage.images.size
|
||||
webFrameResourceUsage.images.size,
|
||||
),
|
||||
liveSize: convertBytesToHumanReadable(
|
||||
webFrameResourceUsage.images.liveSize
|
||||
webFrameResourceUsage.images.liveSize,
|
||||
),
|
||||
},
|
||||
scripts: {
|
||||
count: webFrameResourceUsage.scripts.count,
|
||||
size: convertBytesToHumanReadable(
|
||||
webFrameResourceUsage.scripts.size
|
||||
webFrameResourceUsage.scripts.size,
|
||||
),
|
||||
liveSize: convertBytesToHumanReadable(
|
||||
webFrameResourceUsage.scripts.liveSize
|
||||
webFrameResourceUsage.scripts.liveSize,
|
||||
),
|
||||
},
|
||||
cssStyleSheets: {
|
||||
count: webFrameResourceUsage.cssStyleSheets.count,
|
||||
size: convertBytesToHumanReadable(
|
||||
webFrameResourceUsage.cssStyleSheets.size
|
||||
webFrameResourceUsage.cssStyleSheets.size,
|
||||
),
|
||||
liveSize: convertBytesToHumanReadable(
|
||||
webFrameResourceUsage.cssStyleSheets.liveSize
|
||||
webFrameResourceUsage.cssStyleSheets.liveSize,
|
||||
),
|
||||
},
|
||||
xslStyleSheets: {
|
||||
count: webFrameResourceUsage.xslStyleSheets.count,
|
||||
size: convertBytesToHumanReadable(
|
||||
webFrameResourceUsage.xslStyleSheets.size
|
||||
webFrameResourceUsage.xslStyleSheets.size,
|
||||
),
|
||||
liveSize: convertBytesToHumanReadable(
|
||||
webFrameResourceUsage.xslStyleSheets.liveSize
|
||||
webFrameResourceUsage.xslStyleSheets.liveSize,
|
||||
),
|
||||
},
|
||||
fonts: {
|
||||
count: webFrameResourceUsage.fonts.count,
|
||||
size: convertBytesToHumanReadable(webFrameResourceUsage.fonts.size),
|
||||
liveSize: convertBytesToHumanReadable(
|
||||
webFrameResourceUsage.fonts.liveSize
|
||||
webFrameResourceUsage.fonts.liveSize,
|
||||
),
|
||||
},
|
||||
other: {
|
||||
count: webFrameResourceUsage.other.count,
|
||||
size: convertBytesToHumanReadable(webFrameResourceUsage.other.size),
|
||||
liveSize: convertBytesToHumanReadable(
|
||||
webFrameResourceUsage.other.liveSize
|
||||
webFrameResourceUsage.other.liveSize,
|
||||
),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@ function generateTempName(length: number) {
|
|||
const charactersLength = CHARACTERS.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += CHARACTERS.charAt(
|
||||
Math.floor(Math.random() * charactersLength)
|
||||
Math.floor(Math.random() * charactersLength),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -2,10 +2,10 @@ import { WatchMapping } from "../types";
|
|||
|
||||
export function isMappingPresent(
|
||||
watchMappings: WatchMapping[],
|
||||
folderPath: string
|
||||
folderPath: string,
|
||||
) {
|
||||
const watchMapping = watchMappings?.find(
|
||||
(mapping) => mapping.folderPath === folderPath
|
||||
(mapping) => mapping.folderPath === folderPath,
|
||||
);
|
||||
return !!watchMapping;
|
||||
}
|
||||
|
|
1
desktop/thirdparty/next-electron-server
vendored
|
@ -1 +0,0 @@
|
|||
Subproject commit a88030295c89dd8f43d9e3a45025678d95c78a45
|
|
@ -3,8 +3,8 @@
|
|||
"target": "es2021",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
/* Emit the generated JS into build/app */
|
||||
"outDir": "build/app",
|
||||
/* Emit the generated JS into app */
|
||||
"outDir": "app",
|
||||
"noImplicitAny": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": "src",
|
||||
|
|
|
@ -188,110 +188,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
|
||||
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
|
||||
|
||||
"@sentry/browser@6.7.1":
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.7.1.tgz#e01144a08984a486ecc91d7922cc457e9c9bd6b7"
|
||||
integrity sha512-R5PYx4TTvifcU790XkK6JVGwavKaXwycDU0MaAwfc4Vf7BLm5KCNJCsDySu1RPAap/017MVYf54p6dWvKiRviA==
|
||||
dependencies:
|
||||
"@sentry/core" "6.7.1"
|
||||
"@sentry/types" "6.7.1"
|
||||
"@sentry/utils" "6.7.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/cli@^1.68.0":
|
||||
version "1.74.4"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.74.4.tgz#7df82f68045a155e1885bfcbb5d303e5259eb18e"
|
||||
integrity sha512-BMfzYiedbModsNBJlKeBOLVYUtwSi99LJ8gxxE4Bp5N8hyjNIN0WVrozAVZ27mqzAuy6151Za3dpmOLO86YlGw==
|
||||
dependencies:
|
||||
https-proxy-agent "^5.0.0"
|
||||
mkdirp "^0.5.5"
|
||||
node-fetch "^2.6.7"
|
||||
npmlog "^4.1.2"
|
||||
progress "^2.0.3"
|
||||
proxy-from-env "^1.1.0"
|
||||
which "^2.0.2"
|
||||
|
||||
"@sentry/core@6.7.1":
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.7.1.tgz#c3aaa6415d06bec65ac446b13b84f073805633e3"
|
||||
integrity sha512-VAv8OR/7INn2JfiLcuop4hfDcyC7mfL9fdPndQEhlacjmw8gRrgXjR7qyhnCTgzFLkHI7V5bcdIzA83TRPYQpA==
|
||||
dependencies:
|
||||
"@sentry/hub" "6.7.1"
|
||||
"@sentry/minimal" "6.7.1"
|
||||
"@sentry/types" "6.7.1"
|
||||
"@sentry/utils" "6.7.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/electron@^2.5.1":
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/electron/-/electron-2.5.4.tgz#337b2f7e228e805a3e4eb3611c7b12c6cf87c618"
|
||||
integrity sha512-tCCK+P581TmdjsDpHBQz7qYcldzGdUk1Fd6FPxPy1JKGzeY4uf/uSLKzR80Lzs5kTpEZFOwiMHSA8yjwFp5qoA==
|
||||
dependencies:
|
||||
"@sentry/browser" "6.7.1"
|
||||
"@sentry/core" "6.7.1"
|
||||
"@sentry/minimal" "6.7.1"
|
||||
"@sentry/node" "6.7.1"
|
||||
"@sentry/types" "6.7.1"
|
||||
"@sentry/utils" "6.7.1"
|
||||
tslib "^2.2.0"
|
||||
|
||||
"@sentry/hub@6.7.1":
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.7.1.tgz#d46d24deec67f0731a808ca16796e6765b371bc1"
|
||||
integrity sha512-eVCTWvvcp6xa0A5GGNHMQEWslmKPlisE5rGmsV/kjvSUv3zSrI0eIDfb51ikdnCiBjHpK2NBWP8Vy8cZOEJegg==
|
||||
dependencies:
|
||||
"@sentry/types" "6.7.1"
|
||||
"@sentry/utils" "6.7.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/minimal@6.7.1":
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.7.1.tgz#babf85ee2f167e9dcf150d750d7a0b250c98449c"
|
||||
integrity sha512-HDDPEnQRD6hC0qaHdqqKDStcdE1KhkFh0RCtJNMCDn0zpav8Dj9AteF70x6kLSlliAJ/JFwi6AmQrLz+FxPexw==
|
||||
dependencies:
|
||||
"@sentry/hub" "6.7.1"
|
||||
"@sentry/types" "6.7.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/node@6.7.1":
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.7.1.tgz#b09e2eca8e187168feba7bd865a23935bf0f5cc0"
|
||||
integrity sha512-rtZo1O8ROv4lZwuljQz3iFZW89oXSlgXCG2VqkxQyRspPWu89abROpxLjYzsWwQ8djnur1XjFv51kOLDUTS6Qw==
|
||||
dependencies:
|
||||
"@sentry/core" "6.7.1"
|
||||
"@sentry/hub" "6.7.1"
|
||||
"@sentry/tracing" "6.7.1"
|
||||
"@sentry/types" "6.7.1"
|
||||
"@sentry/utils" "6.7.1"
|
||||
cookie "^0.4.1"
|
||||
https-proxy-agent "^5.0.0"
|
||||
lru_map "^0.3.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/tracing@6.7.1":
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.7.1.tgz#b11f0c17a6c5dc14ef44733e5436afb686683268"
|
||||
integrity sha512-wyS3nWNl5mzaC1qZ2AIp1hjXnfO9EERjMIJjCihs2LWBz1r3efxrHxJHs8wXlNWvrT3KLhq/7vvF5CdU82uPeQ==
|
||||
dependencies:
|
||||
"@sentry/hub" "6.7.1"
|
||||
"@sentry/minimal" "6.7.1"
|
||||
"@sentry/types" "6.7.1"
|
||||
"@sentry/utils" "6.7.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/types@6.7.1":
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.7.1.tgz#c8263e1886df5e815570c4668eb40a1cfaa1c88b"
|
||||
integrity sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA==
|
||||
|
||||
"@sentry/utils@6.7.1":
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.7.1.tgz#909184ad580f0f6375e1e4d4a6ffd33dfe64a4d1"
|
||||
integrity sha512-Tq2otdbWlHAkctD+EWTYKkEx6BL1Qn3Z/imkO06/PvzpWvVhJWQ5qHAzz5XnwwqNHyV03KVzYB6znq1Bea9HuA==
|
||||
dependencies:
|
||||
"@sentry/types" "6.7.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sindresorhus/is@^4.0.0":
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
|
||||
|
@ -662,19 +558,6 @@ applescript@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/applescript/-/applescript-1.0.0.tgz#bb87af568cad034a4e48c4bdaf6067a3a2701317"
|
||||
integrity sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ==
|
||||
|
||||
aproba@^1.0.3:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
||||
integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
|
||||
|
||||
are-we-there-yet@~1.1.2:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146"
|
||||
integrity sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==
|
||||
dependencies:
|
||||
delegates "^1.0.0"
|
||||
readable-stream "^2.0.6"
|
||||
|
||||
argparse@^1.0.7:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||
|
@ -1090,16 +973,6 @@ config-file-ts@^0.2.4:
|
|||
glob "^7.1.6"
|
||||
typescript "^4.0.2"
|
||||
|
||||
console-control-strings@^1.0.0, console-control-strings@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
|
||||
|
||||
cookie@^0.4.1:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
|
||||
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
|
||||
|
||||
core-util-is@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
|
@ -1201,11 +1074,6 @@ delayed-stream@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||
|
||||
delegates@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
|
||||
|
||||
detect-indent@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-7.0.1.tgz#cbb060a12842b9c4d333f1cac4aa4da1bb66bc25"
|
||||
|
@ -1848,20 +1716,6 @@ gar@^1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/gar/-/gar-1.0.4.tgz#f777bc7db425c0572fdeb52676172ca1ae9888b8"
|
||||
integrity sha512-w4n9cPWyP7aHxKxYHFQMegj7WIAsL/YX/C4Bs5Rr8s1H9M1rNtRWRsw+ovYMkXDQ5S4ZbYHsHAPmevPjPgw44w==
|
||||
|
||||
gauge@~2.7.3:
|
||||
version "2.7.4"
|
||||
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
|
||||
integrity sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==
|
||||
dependencies:
|
||||
aproba "^1.0.3"
|
||||
console-control-strings "^1.0.0"
|
||||
has-unicode "^2.0.0"
|
||||
object-assign "^4.1.0"
|
||||
signal-exit "^3.0.0"
|
||||
string-width "^1.0.1"
|
||||
strip-ansi "^3.0.1"
|
||||
wide-align "^1.1.0"
|
||||
|
||||
get-caller-file@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
|
@ -2033,11 +1887,6 @@ has-symbols@^1.0.3:
|
|||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
|
||||
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
|
||||
|
||||
has-unicode@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||
integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==
|
||||
|
||||
has@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||
|
@ -2164,7 +2013,7 @@ inflight@^1.0.4:
|
|||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
|
||||
inherits@2, inherits@^2.0.3, inherits@~2.0.1:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
@ -2249,11 +2098,6 @@ isarray@0.0.1:
|
|||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
|
||||
|
||||
isbinaryfile@^4.0.8:
|
||||
version "4.0.10"
|
||||
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3"
|
||||
|
@ -2462,11 +2306,6 @@ lru-cache@^6.0.0:
|
|||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
lru_map@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
|
||||
integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==
|
||||
|
||||
matcher@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
|
||||
|
@ -2570,7 +2409,7 @@ minizlib@^2.1.1:
|
|||
minipass "^3.0.0"
|
||||
yallist "^4.0.0"
|
||||
|
||||
mkdirp@^0.5.1, mkdirp@^0.5.5:
|
||||
mkdirp@^0.5.1:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
|
||||
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
|
||||
|
@ -2602,8 +2441,10 @@ natural-compare@^1.4.0:
|
|||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
"next-electron-server@file:./thirdparty/next-electron-server":
|
||||
version "0.0.8"
|
||||
next-electron-server@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/next-electron-server/-/next-electron-server-1.0.0.tgz#03e133ed64a5ef671b6c6409f908c4901b1828cb"
|
||||
integrity sha512-fTUaHwT0Jry2fbdUSIkAiIqgDAInI5BJFF4/j90/okvZCYlyx6yxpXB30KpzmOG6TN/ESwyvsFJVvS2WHT8PAA==
|
||||
|
||||
node-addon-api@^1.6.3:
|
||||
version "1.7.2"
|
||||
|
@ -2642,16 +2483,6 @@ normalize-url@^6.0.1:
|
|||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
||||
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
||||
|
||||
npmlog@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
|
||||
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
|
||||
dependencies:
|
||||
are-we-there-yet "~1.1.2"
|
||||
console-control-strings "~1.1.0"
|
||||
gauge "~2.7.3"
|
||||
set-blocking "~2.0.0"
|
||||
|
||||
nugget@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.2.tgz#398b591377b740b3dd308fabecd5ea09cf3443da"
|
||||
|
@ -2675,11 +2506,6 @@ oauth-sign@~0.9.0:
|
|||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
||||
|
||||
object-assign@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
object-keys@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||
|
@ -2873,21 +2699,16 @@ prettier-plugin-packagejson@^2.4:
|
|||
sort-package-json "2.8.0"
|
||||
synckit "0.9.0"
|
||||
|
||||
prettier@2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
|
||||
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
|
||||
prettier@^3:
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368"
|
||||
integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
|
||||
|
||||
pretty-bytes@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
|
||||
integrity sha512-yJAF+AjbHKlxQ8eezMd/34Mnj/YTQ3i6kLzvVsH4l/BfIFtp444n0wVbnsn66JimZ9uBofv815aRp1zCppxlWw==
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||
|
||||
progress-stream@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77"
|
||||
|
@ -2916,11 +2737,6 @@ promise-retry@^2.0.1:
|
|||
err-code "^2.0.2"
|
||||
retry "^0.12.0"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
psl@^1.1.28:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
|
||||
|
@ -2995,19 +2811,6 @@ read-pkg@^5.2.0:
|
|||
parse-json "^5.0.0"
|
||||
type-fest "^0.6.0"
|
||||
|
||||
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==
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
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"
|
||||
|
@ -3149,11 +2952,6 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
|||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
|
@ -3207,11 +3005,6 @@ serialize-error@^7.0.1:
|
|||
dependencies:
|
||||
type-fest "^0.13.1"
|
||||
|
||||
set-blocking@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
|
||||
|
||||
shebang-command@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
||||
|
@ -3229,11 +3022,6 @@ shell-quote@^1.7.3:
|
|||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123"
|
||||
integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==
|
||||
|
||||
signal-exit@^3.0.0:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||
|
||||
simple-update-notifier@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb"
|
||||
|
@ -3387,7 +3175,7 @@ string-width@^1.0.1:
|
|||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
|
@ -3408,14 +3196,7 @@ string_decoder@~0.10.x:
|
|||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
|
||||
strip-ansi@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||
integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==
|
||||
|
@ -3587,12 +3368,12 @@ truncate-utf8-bytes@^1.0.0:
|
|||
dependencies:
|
||||
utf8-byte-length "^1.0.1"
|
||||
|
||||
tslib@^1.8.1, tslib@^1.9.3:
|
||||
tslib@^1.8.1:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.1.0, tslib@^2.2.0:
|
||||
tslib@^2.1.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||
|
@ -3695,7 +3476,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==
|
||||
|
@ -3749,20 +3530,13 @@ whatwg-url@^5.0.0:
|
|||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
which@^2.0.1, which@^2.0.2:
|
||||
which@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wide-align@^1.1.0:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
|
||||
integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
|
||||
dependencies:
|
||||
string-width "^1.0.2 || 2 || 3 || 4"
|
||||
|
||||
winreg@1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b"
|
||||
|
|
1
mobile/.gitignore
vendored
|
@ -34,6 +34,7 @@ lib/generated_plugin_registrant.dart
|
|||
|
||||
android/key.properties
|
||||
android/app/.settings/*
|
||||
android/.settings/
|
||||
.env
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# CHANGELOG
|
||||
|
||||
|
||||
## v0.8.66
|
||||
## v0.8.67
|
||||
|
||||
### Added
|
||||
* #### Home Widget ✨
|
||||
|
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 216 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 343 KiB |
|
@ -7,17 +7,24 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:home_widget/home_widget.dart' as hw;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:media_extension/media_extension_action_types.dart';
|
||||
import 'package:photos/ente_theme_data.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/l10n/l10n.dart";
|
||||
import "package:photos/models/collection/collection_items.dart";
|
||||
import 'package:photos/services/app_lifecycle_service.dart';
|
||||
import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/services/favorites_service.dart";
|
||||
import "package:photos/services/home_widget_service.dart";
|
||||
import "package:photos/services/machine_learning/machine_learning_controller.dart";
|
||||
import 'package:photos/services/sync_service.dart';
|
||||
import 'package:photos/ui/tabs/home_widget.dart';
|
||||
import "package:photos/ui/viewer/actions/file_viewer.dart";
|
||||
import "package:photos/ui/viewer/gallery/collection_page.dart";
|
||||
import "package:photos/utils/intent_util.dart";
|
||||
import "package:photos/utils/navigation_util.dart";
|
||||
|
||||
class EnteApp extends StatefulWidget {
|
||||
final Future<void> Function(String) runBackgroundTask;
|
||||
|
@ -55,6 +62,46 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
|
|||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_checkForWidgetLaunch();
|
||||
hw.HomeWidget.widgetClicked.listen(_launchedFromWidget);
|
||||
}
|
||||
|
||||
void _checkForWidgetLaunch() {
|
||||
hw.HomeWidget.initiallyLaunchedFromHomeWidget().then(_launchedFromWidget);
|
||||
}
|
||||
|
||||
Future<void> _launchedFromWidget(Uri? uri) async {
|
||||
if (uri == null) return;
|
||||
final collectionID =
|
||||
await FavoritesService.instance.getFavoriteCollectionID();
|
||||
if (collectionID == null) {
|
||||
return;
|
||||
}
|
||||
final collection = CollectionsService.instance.getCollectionByID(
|
||||
collectionID,
|
||||
);
|
||||
if (collection == null) {
|
||||
return;
|
||||
}
|
||||
unawaited(HomeWidgetService.instance.initHomeWidget());
|
||||
|
||||
final thumbnail = await CollectionsService.instance.getCover(collection);
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
CollectionPage(
|
||||
CollectionWithThumbnail(
|
||||
collection,
|
||||
thumbnail,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
setLocale(Locale newLocale) {
|
||||
setState(() {
|
||||
locale = newLocale;
|
||||
|
|
|
@ -24,6 +24,7 @@ import 'package:photos/models/private_key_attributes.dart';
|
|||
import 'package:photos/services/billing_service.dart';
|
||||
import 'package:photos/services/collections_service.dart';
|
||||
import 'package:photos/services/favorites_service.dart';
|
||||
import "package:photos/services/home_widget_service.dart";
|
||||
import 'package:photos/services/ignored_files_service.dart';
|
||||
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
|
||||
import 'package:photos/services/memories_service.dart';
|
||||
|
@ -31,7 +32,6 @@ import 'package:photos/services/search_service.dart';
|
|||
import 'package:photos/services/sync_service.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import 'package:photos/utils/file_uploader.dart';
|
||||
import "package:photos/utils/home_widget_util.dart";
|
||||
import 'package:photos/utils/validator_util.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import "package:tuple/tuple.dart";
|
||||
|
@ -175,7 +175,7 @@ class Configuration {
|
|||
MemoriesService.instance.clearCache();
|
||||
BillingService.instance.clearCache();
|
||||
SearchService.instance.clearCache();
|
||||
unawaited(clearHomeWidget());
|
||||
unawaited(HomeWidgetService.instance.clearHomeWidget());
|
||||
Bus.instance.fire(UserLoggedOutEvent());
|
||||
} else {
|
||||
await _preferences.setBool("auto_logout", true);
|
||||
|
|
|
@ -27,6 +27,7 @@ import 'package:photos/services/collections_service.dart';
|
|||
import "package:photos/services/entity_service.dart";
|
||||
import 'package:photos/services/favorites_service.dart';
|
||||
import 'package:photos/services/feature_flag_service.dart';
|
||||
import 'package:photos/services/home_widget_service.dart';
|
||||
import 'package:photos/services/local_file_update_service.dart';
|
||||
import 'package:photos/services/local_sync_service.dart';
|
||||
import "package:photos/services/location_service.dart";
|
||||
|
@ -46,7 +47,6 @@ import 'package:photos/ui/tools/app_lock.dart';
|
|||
import 'package:photos/ui/tools/lock_screen.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import 'package:photos/utils/file_uploader.dart';
|
||||
import "package:photos/utils/home_widget_util.dart";
|
||||
import 'package:photos/utils/local_settings.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
@ -110,8 +110,8 @@ ThemeMode _themeMode(AdaptiveThemeMode? savedThemeMode) {
|
|||
Future<void> _homeWidgetSync() async {
|
||||
if (!Platform.isAndroid) return;
|
||||
try {
|
||||
if (await countHomeWidgets() != 0) {
|
||||
await initHomeWidget();
|
||||
if (await HomeWidgetService.instance.countHomeWidgets() != 0) {
|
||||
await HomeWidgetService.instance.initHomeWidget();
|
||||
}
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error in initSlideshowWidget", e, s);
|
||||
|
@ -210,6 +210,12 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
|
|||
LocalFileUpdateService.instance.init(preferences);
|
||||
SearchService.instance.init();
|
||||
StorageBonusService.instance.init(preferences);
|
||||
if (!isBackground &&
|
||||
Platform.isAndroid &&
|
||||
await HomeWidgetService.instance.countHomeWidgets() == 0) {
|
||||
unawaited(HomeWidgetService.instance.initHomeWidget());
|
||||
}
|
||||
|
||||
if (Platform.isIOS) {
|
||||
// ignore: unawaited_futures
|
||||
PushService.instance.init().then((_) {
|
||||
|
@ -275,9 +281,15 @@ Future<void> _scheduleHeartBeat(
|
|||
}
|
||||
|
||||
Future<void> _scheduleFGHomeWidgetSync() async {
|
||||
Future.delayed(kFGHomeWidgetSyncFrequency, () async {
|
||||
unawaited(_homeWidgetSyncPeriodic());
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _homeWidgetSyncPeriodic() async {
|
||||
await _homeWidgetSync();
|
||||
Future.delayed(kFGHomeWidgetSyncFrequency, () async {
|
||||
unawaited(_scheduleFGHomeWidgetSync());
|
||||
unawaited(_homeWidgetSyncPeriodic());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
173
mobile/lib/services/home_widget_service.dart
Normal file
|
@ -0,0 +1,173 @@
|
|||
import "dart:math";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:home_widget/home_widget.dart' as hw;
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/core/configuration.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/db/files_db.dart";
|
||||
import "package:photos/models/file/file_type.dart";
|
||||
import "package:photos/services/favorites_service.dart";
|
||||
import "package:photos/utils/file_util.dart";
|
||||
import "package:photos/utils/preload_util.dart";
|
||||
|
||||
class HomeWidgetService {
|
||||
final Logger _logger = Logger((HomeWidgetService).toString());
|
||||
|
||||
HomeWidgetService._privateConstructor();
|
||||
|
||||
static final HomeWidgetService instance =
|
||||
HomeWidgetService._privateConstructor();
|
||||
|
||||
Future<void> initHomeWidget() async {
|
||||
final isLoggedIn = Configuration.instance.isLoggedIn();
|
||||
|
||||
if (!isLoggedIn) {
|
||||
await clearHomeWidget();
|
||||
_logger.info("user not logged in");
|
||||
return;
|
||||
}
|
||||
|
||||
final collectionID =
|
||||
await FavoritesService.instance.getFavoriteCollectionID();
|
||||
if (collectionID == null) {
|
||||
await clearHomeWidget();
|
||||
_logger.info("Favorite collection not found");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await hw.HomeWidget.setAppGroupId(iOSGroupID);
|
||||
final res = await FilesDB.instance.getFilesInCollection(
|
||||
collectionID,
|
||||
galleryLoadStartTime,
|
||||
galleryLoadEndTime,
|
||||
);
|
||||
|
||||
final previousGeneratedId =
|
||||
await hw.HomeWidget.getWidgetData<int>("home_widget_last_img");
|
||||
|
||||
if (res.files.length == 1 &&
|
||||
res.files[0].generatedID == previousGeneratedId) {
|
||||
_logger
|
||||
.info("Only one image found and it's the same as the previous one");
|
||||
return;
|
||||
}
|
||||
if (res.files.isEmpty) {
|
||||
await clearHomeWidget();
|
||||
_logger.info("No images found");
|
||||
return;
|
||||
}
|
||||
final files = res.files.where(
|
||||
(element) =>
|
||||
element.generatedID != previousGeneratedId &&
|
||||
element.fileType == FileType.image,
|
||||
);
|
||||
|
||||
final randomNumber = Random().nextInt(files.length);
|
||||
final randomFile = files.elementAt(randomNumber);
|
||||
final fullImage = await getFileFromServer(randomFile);
|
||||
if (fullImage == null) throw Exception("File not found");
|
||||
|
||||
final image = await decodeImageFromList(await fullImage.readAsBytes());
|
||||
final width = image.width.toDouble();
|
||||
final height = image.height.toDouble();
|
||||
final size = min(min(width, height), 1024.0);
|
||||
final aspectRatio = width / height;
|
||||
late final int cacheWidth;
|
||||
late final int cacheHeight;
|
||||
if (aspectRatio > 1) {
|
||||
cacheWidth = 1024;
|
||||
cacheHeight = (1024 / aspectRatio).round();
|
||||
} else if (aspectRatio < 1) {
|
||||
cacheHeight = 1024;
|
||||
cacheWidth = (1024 * aspectRatio).round();
|
||||
} else {
|
||||
cacheWidth = 1024;
|
||||
cacheHeight = 1024;
|
||||
}
|
||||
final Image img = Image.file(
|
||||
fullImage,
|
||||
fit: BoxFit.cover,
|
||||
cacheWidth: cacheWidth,
|
||||
cacheHeight: cacheHeight,
|
||||
);
|
||||
|
||||
await PreloadImage.loadImage(img.image);
|
||||
|
||||
final widget = ClipRRect(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
child: Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
image: DecorationImage(image: img.image, fit: BoxFit.cover),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await hw.HomeWidget.renderFlutterWidget(
|
||||
widget,
|
||||
logicalSize: Size(size, size),
|
||||
key: "slideshow",
|
||||
);
|
||||
|
||||
if (randomFile.generatedID != null) {
|
||||
await hw.HomeWidget.saveWidgetData<int>(
|
||||
"home_widget_last_img",
|
||||
randomFile.generatedID!,
|
||||
);
|
||||
}
|
||||
|
||||
await hw.HomeWidget.updateWidget(
|
||||
name: 'SlideshowWidgetProvider',
|
||||
androidName: 'SlideshowWidgetProvider',
|
||||
qualifiedAndroidName: 'io.ente.photos.SlideshowWidgetProvider',
|
||||
iOSName: 'SlideshowWidget',
|
||||
);
|
||||
_logger.info(
|
||||
">>> OG size of SlideshowWidget image: ${width} x $height",
|
||||
);
|
||||
_logger.info(
|
||||
">>> SlideshowWidget image rendered with size ${cacheWidth} x $cacheHeight",
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe("Error rendering widget", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> countHomeWidgets() async {
|
||||
return await hw.HomeWidget.getWidgetCount(
|
||||
name: 'SlideshowWidgetProvider',
|
||||
androidName: 'SlideshowWidgetProvider',
|
||||
qualifiedAndroidName: 'io.ente.photos.SlideshowWidgetProvider',
|
||||
iOSName: 'SlideshowWidget',
|
||||
) ??
|
||||
0;
|
||||
}
|
||||
|
||||
Future<void> clearHomeWidget() async {
|
||||
final previousGeneratedId =
|
||||
await hw.HomeWidget.getWidgetData<int>("home_widget_last_img");
|
||||
if (previousGeneratedId == null) return;
|
||||
|
||||
_logger.info("Clearing SlideshowWidget");
|
||||
await hw.HomeWidget.saveWidgetData(
|
||||
"slideshow",
|
||||
null,
|
||||
);
|
||||
|
||||
await hw.HomeWidget.updateWidget(
|
||||
name: 'SlideshowWidgetProvider',
|
||||
androidName: 'SlideshowWidgetProvider',
|
||||
qualifiedAndroidName: 'io.ente.photos.SlideshowWidgetProvider',
|
||||
iOSName: 'SlideshowWidget',
|
||||
);
|
||||
await hw.HomeWidget.saveWidgetData<int>(
|
||||
"home_widget_last_img",
|
||||
null,
|
||||
);
|
||||
_logger.info(">>> SlideshowWidget cleared");
|
||||
}
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
import "dart:math";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:home_widget/home_widget.dart' as hw;
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/core/configuration.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/db/files_db.dart";
|
||||
import "package:photos/models/file/file_type.dart";
|
||||
import "package:photos/services/favorites_service.dart";
|
||||
import "package:photos/utils/file_util.dart";
|
||||
import "package:photos/utils/preload_util.dart";
|
||||
|
||||
Future<int> countHomeWidgets() async {
|
||||
return await hw.HomeWidget.getWidgetCount(
|
||||
name: 'SlideshowWidgetProvider',
|
||||
androidName: 'SlideshowWidgetProvider',
|
||||
qualifiedAndroidName: 'io.ente.photos.SlideshowWidgetProvider',
|
||||
iOSName: 'SlideshowWidget',
|
||||
) ??
|
||||
0;
|
||||
}
|
||||
|
||||
Future<void> initHomeWidget() async {
|
||||
final Logger logger = Logger("initHomeWidget");
|
||||
final user = Configuration.instance.getUserID();
|
||||
|
||||
if (user == null) {
|
||||
await clearHomeWidget();
|
||||
throw Exception("User not found");
|
||||
}
|
||||
|
||||
final collectionID =
|
||||
await FavoritesService.instance.getFavoriteCollectionID();
|
||||
if (collectionID == null) {
|
||||
await clearHomeWidget();
|
||||
throw Exception("Collection not found");
|
||||
}
|
||||
|
||||
try {
|
||||
await hw.HomeWidget.setAppGroupId(iOSGroupID);
|
||||
final res = await FilesDB.instance.getFilesInCollection(
|
||||
collectionID,
|
||||
galleryLoadStartTime,
|
||||
galleryLoadEndTime,
|
||||
);
|
||||
|
||||
final previousGeneratedId =
|
||||
await hw.HomeWidget.getWidgetData<int>("home_widget_last_img");
|
||||
|
||||
if (res.files.length == 1 &&
|
||||
res.files[0].generatedID == previousGeneratedId) {
|
||||
logger.info("Only one image found and it's the same as the previous one");
|
||||
return;
|
||||
}
|
||||
if (res.files.isEmpty) {
|
||||
await clearHomeWidget();
|
||||
return;
|
||||
}
|
||||
final files = res.files.where(
|
||||
(element) =>
|
||||
element.generatedID != previousGeneratedId &&
|
||||
element.fileType == FileType.image,
|
||||
);
|
||||
|
||||
final randomNumber = Random().nextInt(files.length);
|
||||
final randomFile = files.elementAt(randomNumber);
|
||||
final fullImage = await getFileFromServer(randomFile);
|
||||
if (fullImage == null) throw Exception("File not found");
|
||||
|
||||
Image img = Image.file(fullImage);
|
||||
var imgProvider = img.image;
|
||||
await PreloadImage.loadImage(imgProvider);
|
||||
|
||||
img = Image.file(fullImage);
|
||||
imgProvider = img.image;
|
||||
|
||||
final image = await decodeImageFromList(await fullImage.readAsBytes());
|
||||
final width = image.width.toDouble();
|
||||
final height = image.height.toDouble();
|
||||
final size = min(min(width, height), 1024.0);
|
||||
|
||||
final widget = ClipRRect(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
child: Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
image: DecorationImage(image: imgProvider, fit: BoxFit.cover),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await hw.HomeWidget.renderFlutterWidget(
|
||||
widget,
|
||||
logicalSize: Size(size, size),
|
||||
key: "slideshow",
|
||||
);
|
||||
|
||||
await hw.HomeWidget.updateWidget(
|
||||
name: 'SlideshowWidgetProvider',
|
||||
androidName: 'SlideshowWidgetProvider',
|
||||
qualifiedAndroidName: 'io.ente.photos.SlideshowWidgetProvider',
|
||||
iOSName: 'SlideshowWidget',
|
||||
);
|
||||
|
||||
if (randomFile.generatedID != null) {
|
||||
await hw.HomeWidget.saveWidgetData<int>(
|
||||
"home_widget_last_img",
|
||||
randomFile.generatedID!,
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
">>> SlideshowWidget rendered with size ${width}x$height",
|
||||
);
|
||||
} catch (_) {
|
||||
throw Exception("Error rendering widget");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearHomeWidget() async {
|
||||
final previousGeneratedId =
|
||||
await hw.HomeWidget.getWidgetData<int>("home_widget_last_img");
|
||||
if (previousGeneratedId == null) return;
|
||||
|
||||
final Logger logger = Logger("clearHomeWidget");
|
||||
|
||||
logger.info("Clearing SlideshowWidget");
|
||||
await hw.HomeWidget.saveWidgetData(
|
||||
"slideshow",
|
||||
null,
|
||||
);
|
||||
|
||||
await hw.HomeWidget.updateWidget(
|
||||
name: 'SlideshowWidgetProvider',
|
||||
androidName: 'SlideshowWidgetProvider',
|
||||
qualifiedAndroidName: 'io.ente.photos.SlideshowWidgetProvider',
|
||||
iOSName: 'SlideshowWidget',
|
||||
);
|
||||
await hw.HomeWidget.saveWidgetData<int>(
|
||||
"home_widget_last_img",
|
||||
null,
|
||||
);
|
||||
logger.info(">>> SlideshowWidget cleared");
|
||||
}
|
|
@ -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.66+586
|
||||
version: 0.8.67+587
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
|
|
@ -176,11 +176,6 @@ type SubscriptionUpdateResponse struct {
|
|||
ClientSecret string `json:"clientSecret"`
|
||||
}
|
||||
|
||||
type StripeSubscriptionInfo struct {
|
||||
PlanCountry string
|
||||
AccountCountry StripeAccountCountry
|
||||
}
|
||||
|
||||
type StripeEventLog struct {
|
||||
UserID int64
|
||||
StripeSubscription stripe.Subscription
|
||||
|
|
|
@ -14,7 +14,7 @@ type Passkey struct {
|
|||
var MaxPasskeys = 10
|
||||
|
||||
type SetPasskeyRecoveryRequest struct {
|
||||
Secret uuid.UUID `json:"secret" binding:"required"`
|
||||
Secret string `json:"secret" binding:"required"`
|
||||
// The UserSecretCipher has SkipSecret encrypted with the user's recoveryKey
|
||||
// If the user sends the correct UserSecretCipher, we can be sure that the user has the recoveryKey,
|
||||
// and we can allow the user to recover their MFA.
|
||||
|
|
|
@ -85,30 +85,27 @@ func (c *BillingController) GetPlansV2(countryCode string, stripeAccountCountry
|
|||
// GetStripeAccountCountry returns the stripe account country the user's existing plan is from
|
||||
// if he doesn't have a stripe subscription then ente.DefaultStripeAccountCountry is returned
|
||||
func (c *BillingController) GetStripeAccountCountry(userID int64) (ente.StripeAccountCountry, error) {
|
||||
stipeSubInfo, hasStripeSub, err := c.GetUserStripeSubscriptionInfo(userID)
|
||||
subscription, err := c.BillingRepo.GetUserSubscription(userID)
|
||||
if err != nil {
|
||||
return "", stacktrace.Propagate(err, "")
|
||||
}
|
||||
if hasStripeSub {
|
||||
return stipeSubInfo.AccountCountry, nil
|
||||
} else {
|
||||
if subscription.PaymentProvider != ente.Stripe {
|
||||
//if user doesn't have a stripe subscription, return the default stripe account country
|
||||
return ente.DefaultStripeAccountCountry, nil
|
||||
} else {
|
||||
return subscription.Attributes.StripeAccountCountry, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserPlans returns the active plans for a user
|
||||
func (c *BillingController) GetUserPlans(ctx *gin.Context, userID int64) ([]ente.BillingPlan, error) {
|
||||
stripeSubInfo, hasStripeSub, err := c.GetUserStripeSubscriptionInfo(userID)
|
||||
stripeAccountCountry, err := c.GetStripeAccountCountry(userID)
|
||||
if err != nil {
|
||||
return []ente.BillingPlan{}, stacktrace.Propagate(err, "Failed to get user's subscription country and stripe account")
|
||||
}
|
||||
if hasStripeSub {
|
||||
return c.GetPlansV2(stripeSubInfo.PlanCountry, stripeSubInfo.AccountCountry), nil
|
||||
} else {
|
||||
// user doesn't have a stipe subscription, so return the default account plans for the country the user is from
|
||||
return c.GetPlansV2(network.GetClientCountry(ctx), ente.DefaultStripeAccountCountry), nil
|
||||
return []ente.BillingPlan{}, stacktrace.Propagate(err, "Failed to get user's country stripe account")
|
||||
}
|
||||
// always return the plans based on the user's country determined by the IP
|
||||
return c.GetPlansV2(network.GetClientCountry(ctx), stripeAccountCountry), nil
|
||||
|
||||
}
|
||||
|
||||
// GetSubscription returns the current subscription for a user if any
|
||||
|
@ -208,23 +205,6 @@ func (c *BillingController) HasActiveSelfOrFamilySubscription(userID int64) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *BillingController) GetUserStripeSubscriptionInfo(userID int64) (ente.StripeSubscriptionInfo, bool, error) {
|
||||
s, err := c.BillingRepo.GetUserSubscription(userID)
|
||||
if err != nil {
|
||||
return ente.StripeSubscriptionInfo{}, false, stacktrace.Propagate(err, "")
|
||||
}
|
||||
// skipping country code extraction for non-stripe subscriptions
|
||||
// as they have same product id across countries and hence can't be distinquished
|
||||
if s.PaymentProvider != ente.Stripe {
|
||||
return ente.StripeSubscriptionInfo{}, false, nil
|
||||
}
|
||||
_, countryCode, err := c.getPlanWithCountry(s)
|
||||
if err != nil {
|
||||
return ente.StripeSubscriptionInfo{}, false, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return ente.StripeSubscriptionInfo{PlanCountry: countryCode, AccountCountry: s.Attributes.StripeAccountCountry}, true, nil
|
||||
}
|
||||
|
||||
// VerifySubscription verifies and returns the verified subscription
|
||||
func (c *BillingController) VerifySubscription(
|
||||
userID int64,
|
||||
|
|