Compare commits

...

262 Commits

Author SHA1 Message Date
Prateek Sunal
cdbb9ab3c3 [fix] include libffi too as it's not included by default (#1387)
## Description

## Tests
2024-04-09 16:24:25 +05:30
Prateek Sunal
f3356147f5 chore: bump version 2024-04-09 16:23:26 +05:30
Ashil
628d13ea53 [mobile][photos] Use sqlite async for fetching all files for search (#1391)
## Description

Using [sqlite_async](https://pub.dev/packages/sqlite_async) has
increased query speed by about 5x and has removed GC logs like:
`Background concurrent copying GC freed 424845(20MB) AllocSpace objects,
183(4932KB) LOS objects, 37% free, 39MB/63MB, paused 144us,44us total
128.048ms`, which has improved performance of the app.

Tried using [sqlite3](https://pub.dev/packages/sqlite3), which reduced
query speed by 10x and removed the GC log, but introduced some jank
since it blocks the UI.

Converting the fetched rows to `EnteFile` now runs on an isolate to
avoid blocking the UI.

## Tests

Did manual testing to see difference in jank.
2024-04-09 15:17:48 +05:30
Manav Rathi
4f76cfb912 [web] New translations (#1380)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-web)
2024-04-09 13:23:39 +05:30
Manav Rathi
9dc4a17593 [web] The great electron renaming (#1390)
- Expose on the globalThis
- Rename the deprecated loggers too
2024-04-09 13:23:06 +05:30
Manav Rathi
b0fbd68d27 Fix errors 2024-04-09 13:13:37 +05:30
Vishnu Mohandas
4d09412f0a v0.8.77 (#1389)
## Description

## Tests
2024-04-09 13:00:56 +05:30
vishnukvmd
99b248f7fa v0.8.77 2024-04-09 13:00:39 +05:30
Neeraj Gupta
b62fc60eb5 [mob] Fix active plan highlight (#1388)
## Description

## Tests
Verified that free plan is highlighted for new sign ups


![image](https://github.com/ente-io/ente/assets/254676/8cf4a30d-57bc-47cc-ac54-acffbddf180c)
2024-04-09 12:59:25 +05:30
Manav Rathi
9127c48787 Remove duplicate files 2024-04-09 12:45:37 +05:30
Manav Rathi
5edca461f7 Final touches 2024-04-09 12:39:37 +05:30
Manav Rathi
4aa3d68e36 Wrap the assertion 2024-04-09 12:36:25 +05:30
Neeraj Gupta
8cb3bf255c [mob] Fix active plan highlight 2024-04-09 12:31:55 +05:30
Manav Rathi
c729516faf Final stretch 2024-04-09 12:21:32 +05:30
Manav Rathi
56ce5c0b0e More transition 2024-04-09 12:17:33 +05:30
Manav Rathi
d441418b5b Remove deprecated loggers 2024-04-09 12:06:46 +05:30
Manav Rathi
0d0e20f7c4 More manual tweaks 2024-04-09 11:58:44 +05:30
Prateek Sunal
7e5f01da18 fix: include libffi too as it's not included by default 2024-04-09 11:52:02 +05:30
Manav Rathi
1dc8f4617e log.error regex replacement
- logError\(e, (".+")\);
- log.error($1, e);

+ a whole bunch of manual tweakings
2024-04-09 11:32:34 +05:30
Neeraj Gupta
b8968d2904 [Server] Fix unique ott constraint for multiple apps (#1386)
## Description

## Tests
  Wrong attempt tracking is working fine
 Same ott can be issued for different app types
 For same app type, unique ott is issued
2024-04-09 11:25:29 +05:30
Neeraj Gupta
46188313ad Minor refactor 2024-04-09 11:20:45 +05:30
Neeraj Gupta
73eacfb30d [server] Use correct app while updating ott table 2024-04-09 11:14:44 +05:30
Neeraj Gupta
6bf22fa864 [server] Fix unique constraint for ott for multiple apps 2024-04-09 11:07:59 +05:30
Manav Rathi
2b7aa372bd Switch 2024-04-09 10:33:54 +05:30
Manav Rathi
3c9f11ee60 Tweak log format 2024-04-09 10:03:30 +05:30
Manav Rathi
72dea7eca6 Start using it 2024-04-09 09:53:28 +05:30
Neeraj Gupta
7c82b57ca0 [mob] Allow manual upload of videos when global video upload is disabled (#1384)
## Description
See commit message.

PS: Did not write code to clean up the list as I don't expect the list
to grow significantly.

## Tests
* Disabled Global Video Upload

**Before the change**
- Clicking on upload icon on video was not resulting in the upload of
the underlying video.

**After the change**
- Upload upload went through successfully
2024-04-09 09:42:21 +05:30
Vishnu Mohandas
9e36032019 [auth] v2.0.54 (#1385) 2024-04-09 09:41:35 +05:30
vishnukvmd
9555c86b5f [auth] v2.0.54 2024-04-09 09:40:29 +05:30
Manav Rathi
54a973c457 Expose on the globalThis
Note that the filename of the .d.ts has to be different from any existing file!
https://stackoverflow.com/questions/59728371/typescript-d-ts-file-not-recognized
2024-04-09 09:37:43 +05:30
Prateek Sunal
d06fede2b5 [FIX] updatedb command (#1383)
## Description

UpdateDb command has localpaths instead of database-root, this fixes
that.
2024-04-09 09:33:21 +05:30
Neeraj Gupta
7cec46ef3d [mob] Allow manual upload of videos when global video upload is disabled 2024-04-09 09:32:37 +05:30
Prateek Sunal
11c80a6fa9 fix: updatedb command 2024-04-09 09:30:50 +05:30
Neeraj Gupta
c6a0af7cbc [mob]Hide file overflow item if no action is available 2024-04-09 09:16:11 +05:30
Vishnu Mohandas
542cd31655 [auth] v2.0.53 (#1381) 2024-04-09 08:32:36 +05:30
vishnukvmd
cdce7d5922 [auth] v2.0.53 2024-04-09 08:32:12 +05:30
Crowdin Bot
7dee92f44e New Crowdin translations by GitHub Action 2024-04-09 01:37:23 +00:00
Prateek Sunal
a45bf52a4d [FIX] Appimage build (#1377)
## Description

Fix build and unable to locate libtiff.so.5
2024-04-09 06:30:17 +05:30
Prateek Sunal
cc5558db5e fix: reload database of locate 2024-04-08 23:05:11 +05:30
Prateek Sunal
a2dfffd778 fix: app image build 2024-04-08 23:05:11 +05:30
Manav Rathi
f37c46935c [web] [desktop] Consolidate logging (#1376) 2024-04-08 21:10:48 +05:30
Manav Rathi
59bda25be2 Fix log entry format 2024-04-08 21:00:05 +05:30
Manav Rathi
f92a4c2a6e Fix errors 2024-04-08 20:57:13 +05:30
Manav Rathi
92a3650696 Dedup comlink 2024-04-08 20:53:27 +05:30
Manav Rathi
a1c9ceae6b Add temporary gateways 2024-04-08 20:47:47 +05:30
Manav Rathi
3e3712efb3 Transition 2024-04-08 20:43:42 +05:30
Manav Rathi
5339b1aa89 Merge 2024-04-08 20:39:53 +05:30
Manav Rathi
0be549c91b Transition 2024-04-08 20:21:49 +05:30
Manav Rathi
826cacd6bf Reroute 2024-04-08 20:00:24 +05:30
Manav Rathi
90a770c619 WIP 2024-04-08 18:09:08 +05:30
Manav Rathi
f4f041552f Move electron API types to lower layer 2024-04-08 16:51:48 +05:30
Vishnu Mohandas
730da7648c v0.8.76 (#1373) 2024-04-08 15:53:39 +05:30
vishnukvmd
9cface7902 v0.8.76 2024-04-08 15:53:21 +05:30
Vishnu Mohandas
a436a6c766 [auth] v2.0.52 (#1372) 2024-04-08 15:26:55 +05:30
vishnukvmd
ed10e3ec30 [auth] v2.0.52 2024-04-08 15:26:37 +05:30
Prateek Sunal
7d39c0645a [FIX] Compile of AppImage (#1369)
## Description

Appimage github action
2024-04-08 15:12:34 +05:30
Manav Rathi
48f741b792 Tweak docs 2024-04-08 15:02:32 +05:30
Vishnu Mohandas
8a115edef8 Uppercase auth (#1371) 2024-04-08 15:00:10 +05:30
vishnukvmd
1870c2a468 auth -> Ente Auth 2024-04-08 14:59:37 +05:30
vishnukvmd
669f428fa3 ente -> Ente 2024-04-08 14:58:35 +05:30
Manav Rathi
7be4b47e51 Tighten type checking progressively 2024-04-08 14:57:49 +05:30
Vishnu Mohandas
79250b9efa ente -> Ente (#1370) 2024-04-08 14:56:28 +05:30
vishnukvmd
191d19a0fc ente -> Ente 2024-04-08 14:55:28 +05:30
Prateek Sunal
231bc2fc66 fix: complile of appimage 2024-04-08 14:50:49 +05:30
Manav Rathi
f65e738507 Move to types 2024-04-08 14:42:44 +05:30
Manav Rathi
c8089fbb60 Inline uncaptured errors 2024-04-08 14:40:44 +05:30
Prateek Sunal
d10908458e [FIX] RPM build (#1368)
## Description

fixes #1308
2024-04-08 14:36:52 +05:30
Manav Rathi
15e290a993 api => services 2024-04-08 14:31:19 +05:30
Manav Rathi
12fa3be6c5 Event handler cleanup 2024-04-08 14:24:50 +05:30
Manav Rathi
5ae6d7d47b Don't wait for ready before logging startup banner
> The only hint is to call the code in main.ts without waiting for the ready event.
>
> https://github.com/megahertz/electron-log/issues/408
2024-04-08 14:17:02 +05:30
Prateek Sunal
47ab361494 fix: rpm build 2024-04-08 14:16:52 +05:30
Vishnu Mohandas
15d9c7da3b v2.0.51 (#1367) 2024-04-08 14:11:12 +05:30
vishnukvmd
e8f21a7247 v2.0.51 2024-04-08 14:10:52 +05:30
Manav Rathi
18e47b3d4e Synchronize startup banners 2024-04-08 14:09:59 +05:30
Manav Rathi
35736c447d Cleanup 2024-04-08 13:59:40 +05:30
Manav Rathi
c458b429a0 Let the web side log errors for the bridged methods 2024-04-08 13:58:28 +05:30
Vishnu Mohandas
adb796e35f [mobile][photos] Perf test on app init (#1366)
## Description

Test and script for performance profiling the app for a specified
duration from app init using integration testing.
2024-04-08 13:49:07 +05:30
Manav Rathi
c5bb479c4f Consolidate logging / desktop 2024-04-08 13:44:39 +05:30
ashilkn
feb8deb648 add comments 2024-04-08 13:37:00 +05:30
ashilkn
4a4a53f994 Move performance profiling script files to mobile/scripts 2024-04-08 13:33:24 +05:30
ashilkn
504f23fe4e add comments to app_init_test script 2024-04-08 13:30:52 +05:30
ashilkn
f81fb5b626 write perf test for app init 2024-04-08 13:21:29 +05:30
Manav Rathi
78f4f9b42d [web] Capture logs from web workers (#1365)
This started of as a refactoring of our logging layer, but turned into a
bug fix (+ refactorings) when I noticed that the logs in the web/worker
case were not being saved to the on disk file.

Refs:

- https://github.com/GoogleChromeLabs/comlink/issues/506
- https://github.com/GoogleChromeLabs/comlink/issues/568
2024-04-08 13:19:25 +05:30
Manav Rathi
84d21984e0 Test complete
Tested logging from both worker (in web app) and when running in Electron.

Refs:
- https://github.com/GoogleChromeLabs/comlink/issues/506
- https://github.com/GoogleChromeLabs/comlink/issues/568
2024-04-08 13:16:50 +05:30
Manav Rathi
da9a704094 fixing logging in workers WIP 2 2024-04-08 12:52:10 +05:30
Manav Rathi
a96ad6dfa2 WIP 2024-04-08 12:40:03 +05:30
Manav Rathi
44666d6772 [web] New translations (#1364)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-web)
2024-04-08 09:40:42 +05:30
Crowdin Bot
b3842dab04 New Crowdin translations by GitHub Action 2024-04-08 04:09:44 +00:00
Manav Rathi
873b158718 [web] Miscellaneous tweaks (#1363)
- Clean up environment detection code
- Remove l11n unsafe direct string manipulation
- Inline
- Remove isCanvasBlocked checker
- Remove unused stuff
2024-04-08 09:38:59 +05:30
Manav Rathi
7913d5ec2e lint 2024-04-08 09:37:08 +05:30
Neeraj Gupta
4ceaf7cf13 [auth] Prioritise issuer match in search result order (#1362)
## Description

## Tests
2024-04-08 09:18:42 +05:30
Neeraj Gupta
c728b3b8be [auth] Prioritize issuer match in search result 2024-04-08 09:15:33 +05:30
Neeraj Gupta
510a4a5978 [auth] iOS build changes 2024-04-08 09:15:16 +05:30
Manav Rathi
0b3165b812 Remove unused 2024-04-07 10:58:19 +05:30
Manav Rathi
99579fbf51 Inline 2024-04-07 10:56:15 +05:30
Manav Rathi
75d041dd9c Inline 2024-04-07 10:53:34 +05:30
Manav Rathi
d992085dbd Inline 2024-04-07 10:49:31 +05:30
Manav Rathi
cb6cfee9a3 Inline 2024-04-07 10:49:04 +05:30
Manav Rathi
60321111c2 Remove isCanvasBlocked checker
We don't really support running without canvas access, we need it for image
manipulation. The firefox feature seems poorly implemented -
https://bugzilla.mozilla.org/show_bug.cgi?id=1429865 - and if someone _really_
wishes to not add an exclusion for our open source web app, then they can
download our desktop app.
2024-04-07 10:45:30 +05:30
Manav Rathi
49b5bff042 Inline 2024-04-07 10:29:00 +05:30
Manav Rathi
7c0ab6dd8a Remove l11n unsafe direct string manipulation 2024-04-07 10:23:20 +05:30
Manav Rathi
01e6e79819 Cannot be a compile time constant 2024-04-07 10:18:30 +05:30
Manav Rathi
e5b2d737b4 Clean up environment detection code 2024-04-07 10:03:28 +05:30
Manav Rathi
5e6f057c4c [web] Add required transitive dependency of MUI datepicker (#1357)
Previously this was coming from react-datepicker that was otherwise
unused and thus removed.

Use the same major version as we had previously.

Ref:

https://stackoverflow.com/questions/71037974/module-not-found-error-cant-resolve-date-fns-adddays-in-c-users
2024-04-07 09:30:16 +05:30
Manav Rathi
3cb51184b3 [web] Add required transitive dependency of MUI datepicker
Previously this was coming from react-datepicker that was otherwise unused and thus removed.

Use the same major version as we had previously.

Ref:
https://stackoverflow.com/questions/71037974/module-not-found-error-cant-resolve-date-fns-adddays-in-c-users
2024-04-07 09:26:52 +05:30
Manav Rathi
9025ad3c57 [web] Remove unused ffmpeg from cast (#1355)
- Also attempt to reenable tsc, since this was previously failing at
this ffmpeg step. But that's still not possible, the photos package tsc
trips up at the same step
- Remove some other unused dependencies too
2024-04-07 08:45:06 +05:30
Manav Rathi
d317da6536 Remove unused package react-datepicker
+ another unused type
+ an transitive from code
2024-04-07 08:42:27 +05:30
Manav Rathi
0071182721 Revert "Attempt to reenable tsc"
This reverts commit 5700b101b2. We still need to
fix it in photos before re-enabling this.
2024-04-07 08:28:13 +05:30
Manav Rathi
5700b101b2 Attempt to reenable tsc
Partially reverts 6333792d64
2024-04-07 08:25:16 +05:30
Manav Rathi
02207ca96c Remove unused ffmpeg from cast 2024-04-07 08:18:36 +05:30
Manav Rathi
ddb2952b6a [docs] yarn pretty (#1349) 2024-04-06 22:19:43 +05:30
Manav Rathi
c983af0dea [docs] yarn pretty 2024-04-06 22:17:34 +05:30
Manav Rathi
363e2b116b [docs] Add more details in the server admin docs (#1348) 2024-04-06 22:15:56 +05:30
Manav Rathi
d8b7dd06f6 [docs] Add more details in the server admin docs 2024-04-06 22:14:19 +05:30
Manav Rathi
9b73cd2176 [docs] Add a troubleshooting guide for failing uploads (#1346) 2024-04-06 20:53:40 +05:30
Manav Rathi
1205b864d2 [docs] Add a troubleshooting guide for failing uploads 2024-04-06 20:49:04 +05:30
Manav Rathi
54a1b3ca3e [web] Fix cast CSS (#1345)
- Cleanups, remove unnecessary sass dependency
- Fix the height in the CSS
2024-04-06 19:28:36 +05:30
Manav Rathi
4b074f4475 Fix the CSS for cast 2024-04-06 19:15:42 +05:30
Manav Rathi
b650372d35 Cleanupxs 2024-04-06 19:09:02 +05:30
Manav Rathi
04c0fd0617 [web] New translations (#1344)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-web)
2024-04-06 12:42:49 +05:30
Crowdin Bot
e779ae5189 New Crowdin translations by GitHub Action 2024-04-06 07:12:09 +00:00
Manav Rathi
14fe0a46b0 [web] Bundle translations but lazily (#1343)
## Description

Muchos faster, muchos caching, and no duplication.

**Tested by**

Running locally in both dev and preview. Only the current language +
English is fetched. It can be made even faster by prebundling the
English translations as described in
https://github.com/i18next/i18next-resources-to-backend, but we can
profile first and see if that's even needed.
2024-04-06 12:41:25 +05:30
Manav Rathi
568f4540e3 Remove scripts
It has done its job, let it rest in peace
2024-04-06 12:12:15 +05:30
Manav Rathi
6bff42ad9b Remove unused keys 2024-04-06 12:11:29 +05:30
Manav Rathi
d00211964b Improve heuristic 2024-04-06 11:45:05 +05:30
Manav Rathi
8695f46b43 Improve script for finding unused keys 2024-04-06 11:43:56 +05:30
Manav Rathi
8020d83ced Fix crowdin paths 2024-04-06 11:30:31 +05:30
Manav Rathi
121df66ada Cleanup 2024-04-06 11:19:14 +05:30
Manav Rathi
a98a29800b [web] Bundle translations but load them lazily
Refs:
- https://github.com/i18next/i18next-resources-to-backend
- https://stackoverflow.com/questions/77251750/how-to-implement-lazy-loading-translations-in-i18next-with-react
- https://github.com/i18next/react-i18next/issues/525
- https://gist.github.com/SimeonC/6a738467c691eef7f21ebf96918cd95f
- https://www.i18next.com/overview/plugins-and-utils
2024-04-06 11:06:29 +05:30
Vishnu Mohandas
afe94b72ba [meta] Call out custom icons as a good starter task (#1342) 2024-04-06 08:46:23 +05:30
Manav Rathi
167e5a95ca [meta] Call out custom icons as a good starter task 2024-04-06 08:44:50 +05:30
Vishnu Mohandas
2bd02eac4b [FIX] Smart auth revert, issue #1279 (#1340)
## Description

Revert Pinput version to fix smart_auth and hence the fdroid build.

Fixes #1279
2024-04-05 23:17:26 +05:30
Prateek Sunal
bfc147b4d1 fix: revert pinput version 2024-04-05 23:13:40 +05:30
Manav Rathi
16cb63edfe [web] Run tsc when linting (#1339) 2024-04-05 22:10:07 +05:30
Manav Rathi
6333792d64 Temporarily disable tsc on CI 2024-04-05 22:03:39 +05:30
Manav Rathi
69bd822499 Attempt to fix tsc warning on CI
Error: src/services/wasm/ffmpeg.ts(6,38): error TS2307: Cannot find module 'ffmpeg-wasm' or its corresponding type declarations.
2024-04-05 21:59:18 +05:30
Manav Rathi
6a31331ac4 Fix lint issue when running on CI
> cast
    $ /home/runner/work/ente/ente/web/node_modules/.bin/tsc
    Error: src/components/FilledCircleCheck/index.tsx(2,20): error TS2307: Cannot find module './FilledCircleCheck.module.scss' or its corresponding type declarations.
2024-04-05 21:56:46 +05:30
Manav Rathi
e16834e52e Remove unused styled jsx tag
This caused the lint to fail on CI

    > auth
    $ /home/runner/work/ente/ente/web/node_modules/.bin/tsc
    Error: src/pages/auth/index.tsx(125,20): error TS2322: Type '{ children: string; jsx: true; }' is not assignable to type 'ClassAttributes<HTMLStyleElement> & StyleHTMLAttributes<HTMLStyleElement> & { css?: Interpolation<Theme>; }'.
      Property 'jsx' does not exist on type 'ClassAttributes<HTMLStyleElement> & StyleHTMLAttributes<HTMLStyleElement> & { css?: Interpolation<Theme>; }'.
    error Command failed with exit code 2.

From reading the code, this CSS doesn't seem to be coming into effect. Then I
also did a test to verify that the auth app is correctly switching to a 2 column
layout even without this.
2024-04-05 21:39:52 +05:30
Manav Rathi
10b0d9f533 Fix the yarn lint 2024-04-05 21:22:44 +05:30
Manav Rathi
791cc61ca7 Fix more leftover issues 2024-04-05 21:18:26 +05:30
Manav Rathi
7a674dcf95 lint-fix 2024-04-05 21:10:59 +05:30
Manav Rathi
5c313fb87d tsc when linting 2024-04-05 21:10:20 +05:30
Manav Rathi
411984ebdc Fix lint errors 2024-04-05 21:09:47 +05:30
Manav Rathi
8764e5cf4d [web] Miscellaneous code improvements (#1336) 2024-04-05 20:50:56 +05:30
Manav Rathi
cf27f3236c lint-fix 2024-04-05 20:48:23 +05:30
Manav Rathi
42a59f2fb5 Consolidate and deduplicate 2024-04-05 20:35:22 +05:30
Manav Rathi
7807d3a413 Inline 2024-04-05 20:03:23 +05:30
Manav Rathi
461430a972 Move up from shared 2024-04-05 20:01:28 +05:30
Manav Rathi
0b39759ad9 Inline 2024-04-05 19:58:45 +05:30
Manav Rathi
fe6215d0fd Replace context entry with useRouter 2024-04-05 19:58:45 +05:30
Manav Rathi
e680970cdf Inline 2024-04-05 19:58:45 +05:30
Manav Rathi
63387d8819 Use regular image
This is the only place we were using next/image
2024-04-05 19:58:45 +05:30
Manav Rathi
356ad6f004 Use regular link
Removing unnecessary dependencies on next specific components
2024-04-05 19:58:45 +05:30
Manav Rathi
7be2c66fb6 [server] Delete the subscriber on listmonk when needed (#1331) 2024-04-05 17:14:09 +05:30
vishnukvmd
c32badc82c [server] Delete the subscriber on listmonk when needed 2024-04-05 17:12:06 +05:30
Manav Rathi
dbdad18c96 [web] Uncomment accidentally activated env vars for localhost defaults (#1330) 2024-04-05 17:09:24 +05:30
Manav Rathi
ae51531bd0 [web] Uncomment accidentally activated env vars for localhost defaults 2024-04-05 17:08:47 +05:30
Manav Rathi
4ad060c4e4 [infra] Fix the listmonk upgrade invocation (#1329)
Tested on the actual instance
2024-04-05 17:03:03 +05:30
Manav Rathi
5266e5d1dc [infra] Fix the listmonk upgrade invocation 2024-04-05 16:51:08 +05:30
Vishnu Mohandas
5904e3dd2c [server] Allow a configurable sslmode (#1327)
Tested that

- [x] Existing defaults remain unchanged
- [x] Setting db.sslmode = require in museum.yaml is honoured (in the
docker compose environment, that causes the db connection to fail as
expected)
2024-04-05 13:58:17 +05:30
Manav Rathi
74decc32de [server] Allow a configurable sslmode
Tested

- Existing defaults remain unchanged
- Setting db.sslmode = require in museum.yaml is honoured (in the docker compose environment, that causes the db connection to fail as expected)
2024-04-05 11:52:22 +05:30
Vishnu Mohandas
5e8f41cbfe [mob] Change share page copy (#1323)
## Description

I thought the "Collect photos" line and the download icon in the share
page looked very confusing:

![old_share_page_copy](https://github.com/ente-io/ente/assets/81471280/cb6a8415-e3a0-403e-abd3-e94b6256a572)

So I changed the icon and copy slightly:

![new_share_page_copy](https://github.com/ente-io/ente/assets/81471280/2480bb43-21fb-487a-af00-c473dbe7439c)


## Tests

Just a copy change, no tests.
2024-04-05 11:48:29 +05:30
Manav Rathi
360aa2903f [web] Remove leftover console log (#1325) 2024-04-05 11:35:30 +05:30
Manav Rathi
0d48284b4f [web] Remove leftover console log 2024-04-05 11:33:57 +05:30
Manav Rathi
687efe506a [web] Fix an regression introduced in the previous PR (#1324)
Use the implementation instead of the accidental infinite loop
2024-04-05 11:32:32 +05:30
Manav Rathi
e0cfa36f08 Use the implementation instead of the accidental infinite loop 2024-04-05 10:51:04 +05:30
laurenspriem
858ba88c65 [mob] Change share page copy 2024-04-05 10:48:37 +05:30
Manav Rathi
e7e8ded1ed [web] Miscellaneous improvements (#1322)
- Remove unused knobs
- Inline
- Initial-scale=1 is still recommended
- Remove custom _document (with a longer term vision of next => vite)
2024-04-05 10:17:04 +05:30
Manav Rathi
fa7cbaea18 Remove custom _document
Looking towards a longer term transition out of next
2024-04-05 10:09:37 +05:30
Manav Rathi
9785bbcb26 We don't have full web app support yet
Plus, Apple doesn't do much for PWAs anyway. One reason for removing this until
we test it out in actuality is because there were reports of the semi-PWA
caching causing issues.
2024-04-05 09:38:48 +05:30
Manav Rathi
039387a84e Initial-scale=1 is still recommended
Ref:
https://teamtreehouse.com/community/is-initialscale-not-needed
2024-04-05 09:36:18 +05:30
Manav Rathi
866b52b002 Inline constant 2024-04-05 09:26:30 +05:30
Manav Rathi
2c098904fb Inline 2024-04-05 09:13:38 +05:30
Manav Rathi
a68dce35f6 Remove unused knob 2024-04-05 09:08:56 +05:30
Manav Rathi
1d0f30ad91 These values were never customized 2024-04-05 09:08:02 +05:30
Manav Rathi
7374fe2ecc [web] Fix nightly build (#1321)
Got broken by yesterday's changes to payments.
2024-04-05 08:58:17 +05:30
Manav Rathi
55454f9454 [web] Fix nightly build
Got broken by yesterday's changes to payments.
2024-04-05 08:57:24 +05:30
Manav Rathi
1aa39e83d7 [server] Build ARM images when publishing to ghcr.io (#1320)
Untested yet, will test alongside the next publish

Requested in https://github.com/ente-io/ente/discussions/1305
2024-04-04 21:59:47 +05:30
Manav Rathi
d84ee7223a [server] Build ARM images when publishing 2024-04-04 21:57:44 +05:30
Manav Rathi
4e5fcebb95 [infra] Setup listmonk (#1319) 2024-04-04 21:36:02 +05:30
Manav Rathi
e170b6811d Tweaks
Refs:
https://github.com/knadh/listmonk/blob/master/listmonk-simple.service#L16
2024-04-04 21:27:47 +05:30
Manav Rathi
3ed2186dcf Initial cut of listmonk setup 2024-04-04 20:23:25 +05:30
Manav Rathi
b6177a5bc3 Listmonk mailing list control (#1289)
## Description
Add listmonk mailing list subscribe and unsubscribe

## Tests
Tested getSubscriberID, unsubscribe, and subscribe using a locally
running listmonk server with different parameters.
- [x] A new subscriber is created and added to the listmonk campaign
mailing list on listmonkSubscribe() for the given list IDs
- [x] Subscriber is removed from listmonk campaign mailing list on
listmonkUnsubscribe() for the given list IDs
- [x] The old email address is unsubscribed, and the new email is
subscribed when a user updates the email
2024-04-04 17:21:55 +05:30
Manav Rathi
27410b2da9 [web] Add an example fetch to staff (#1315)
- Add schema validation of the response
- Add an example fetch
2024-04-04 16:22:49 +05:30
Manav Rathi
ae061d2a44 fetch 2024-04-04 16:19:46 +05:30
Manav Rathi
d9d03d8451 Add schema validation of the response 2024-04-04 16:12:39 +05:30
Manav Rathi
2f5abb6318 [web] Scaffold staff app (#1314) 2024-04-04 15:50:10 +05:30
Manav Rathi
e53c923675 Lint fix + update URL 2024-04-04 15:44:03 +05:30
Manav Rathi
39228270c1 Deploy 2024-04-04 15:40:17 +05:30
Manav Rathi
384ec365e8 Add starter staff app 2024-04-04 15:25:38 +05:30
Vishnu Mohandas
b2da2c7e88 Setup Triple-T configuration for F-Droid (#1313)
Potential fix for https://github.com/ente-io/ente/issues/1172.
2024-04-04 15:16:45 +05:30
vishnukvmd
c25cc6f8a4 Setup Triple-T configuration for F-Droid 2024-04-04 15:15:59 +05:30
Vishnu Mohandas
b2cf6be5f5 [photos] v0.8.75 (#1312) 2024-04-04 14:50:54 +05:30
vishnukvmd
fce68ba1be [photos] v0.8.75 2024-04-04 14:50:22 +05:30
Manav Rathi
29550317f7 Enable the jsx-runtime plugin for vite's ESLint
This prevents it complaining about a missing React import.

Enabling this is recommended by the vite starter itself:

> Install
  [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and
  add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends`
  list
2024-04-04 14:46:41 +05:30
Manav Rathi
876c5800f9 [web] Convert payments app to Vite (#1311)
This is the culmination of the previous few PRs. The payments app now
uses vite, which is what we want to give out a real shot for the smaller
of our apps.

**Tested by**

Local setup, and subscribing to a plan.
2024-04-04 14:36:36 +05:30
Manav Rathi
608cb6c85e Prevent double rendering in strict mode
The double invocation during dev mode, while harmless, is confusing, so add an
additional small check to insure this only runs once.
2024-04-04 14:25:04 +05:30
Manav Rathi
712b99b8f3 Fix lint issue 2024-04-04 14:15:42 +05:30
Manav Rathi
935e47fbca Fix the payments eslint 2024-04-04 14:10:16 +05:30
Manav Rathi
fcb26d39f1 Don't need default export 2024-04-04 14:02:31 +05:30
Manav Rathi
ff6d0d32cf Remove unused 404 handler
The default appType == 'spa' in vite redirects all (unclaimed) paths to /index.html.

If needed, this can be disabled:
https://stackoverflow.com/questions/69701743/how-can-i-configure-vites-dev-server-to-give-404-errors/69711988#69711988
2024-04-04 13:58:30 +05:30
Vishnu Mohandas
52c47234fd [Photos] Allow for configuring a custom server (#1302)
## Description
Users can now tap on the onboarding screen 7 times to bring up a page
where they can configure the endpoint the app should be connecting to.

![photos-selfhost](https://github.com/ente-io/ente/assets/1161789/42fda09a-07e4-4c4e-a658-ec4a2d3f1848)

## Tests
- [x] Verified that production flows are working as expected
- [x] Verified that configuring the endpoint to a local instance lets
you
  - [x] Connect to that instance
  - [x] Create an account
  - [x] Upload a photo
  - [x] Logout and log back in
2024-04-04 13:41:26 +05:30
Manav Rathi
756050ae8c Fix compilation 2024-04-04 13:02:08 +05:30
Manav Rathi
a2d39a46be [server] nginx configuration improvements (#1310)
- Use keepalives
- Update deprecated http2 syntax
- Document how to check config

Refs:
-
https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#no-keepalives
- https://github.com/nginxinc/kubernetes-ingress/issues/4237
-
https://stackoverflow.com/questions/65944578/how-to-get-nginx-running-in-docker-to-reload-nginx-conf-configuration
2024-04-04 12:42:10 +05:30
Manav Rathi
407eca5414 [server] nginx configuration improvements
- Use keepalives
- Update deprecated http2 syntax
- Document how to check config

Refs:
- https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#no-keepalives
- https://github.com/nginxinc/kubernetes-ingress/issues/4237
- https://stackoverflow.com/questions/65944578/how-to-get-nginx-running-in-docker-to-reload-nginx-conf-configuration
2024-04-04 12:36:39 +05:30
Manav Rathi
87dc7d76ca Remove middleman package, to get it to compile 2024-04-04 11:36:20 +05:30
Ashil
8b643549fe [mobile][photos] Remove unused global keys (#1309) 2024-04-04 11:29:23 +05:30
Vishal
d8190926fd Change if-else 2024-04-04 11:24:13 +05:30
Manav Rathi
4255e48abb Convert payments app to use Vite - Part 1
(Doesn't compile)
2024-04-04 11:10:34 +05:30
Manav Rathi
a8a5cc8b59 Inline spinner 2024-04-04 09:55:52 +05:30
Manav Rathi
949a42004f Remove the need for a separate page 2024-04-04 09:53:11 +05:30
Manav Rathi
cb94dd8b42 [web] Refactor payments (#1304)
This is a reduction in code generally before we give a shot to using
vite for Payments. Once that is done, will do an end-to-end test of the
payments pages on localhost.
2024-04-03 21:44:08 +05:30
Manav Rathi
56d500f4e8 Keep React in scope 2024-04-03 21:41:03 +05:30
Manav Rathi
7a41ba43a5 Another cleanup of billing-service 2024-04-03 21:32:38 +05:30
Manav Rathi
7a729183e2 Cleanup the pages 2024-04-03 20:03:02 +05:30
Manav Rathi
aa5422db6c Clean up CSS 2024-04-03 19:54:16 +05:30
Manav Rathi
c0fee7bc91 Clean up strings 2024-04-03 19:51:01 +05:30
Manav Rathi
1411ca6fad Continue refactoring 2024-04-03 19:46:15 +05:30
Vishal
92715b658c Change API parameter 2024-04-03 19:24:12 +05:30
Vishal
2ddf4c897c Rectify if else 2024-04-03 18:25:41 +05:30
Vishal
ffefae89a6 Redefine struct 2024-04-03 17:50:53 +05:30
vishnukvmd
9d7a342aa9 Ensure widget is updated when endpoint changes 2024-04-03 17:34:45 +05:30
vishnukvmd
ee33a3229f Update landing page to provide an option to update the app's endpoint 2024-04-03 17:24:44 +05:30
vishnukvmd
54c4862e71 Add widget that renders the current endpoint app is connecting to 2024-04-03 17:24:26 +05:30
vishnukvmd
b97839adae Update strings 2024-04-03 17:24:01 +05:30
vishnukvmd
37c4295df9 Update NetworkClient when configuration changes 2024-04-03 17:23:54 +05:30
vishnukvmd
089be79688 Add API within Configuration to update the endpoint 2024-04-03 17:23:23 +05:30
vishnukvmd
0034d880f9 Initialize Configuration before Network 2024-04-03 17:23:00 +05:30
vishnukvmd
81bdc0fe73 Add strings 2024-04-03 17:22:34 +05:30
vishnukvmd
76dca4d819 Update iOS config 2024-04-03 17:22:22 +05:30
Manav Rathi
d0f1bbfca7 Refactor billing service 2024-04-03 17:22:00 +05:30
Manav Rathi
8a00f1b85f Remove custom errors indirection 2024-04-03 15:45:20 +05:30
Manav Rathi
f10f751a2f Inline local storage calls
The methods are trivial, and we cannot centralize the keys since they will be
different for different apps. So an abstraction for this is not beneficial.

Also move the next specific dev build check to @/next
2024-04-03 14:21:11 +05:30
Manav Rathi
d28daece8a ignorePatters seems to be inherited (as we wished) when extending 2024-04-03 13:48:41 +05:30
Manav Rathi
24bce96d71 Shorten 2024-04-03 13:44:30 +05:30
Manav Rathi
ad6dea2ecb prettier markdown 2024-04-03 13:39:44 +05:30
Manav Rathi
212dcfb88a Tinker 2024-04-03 13:32:05 +05:30
Manav Rathi
a689aca4a6 Remove the eslint stuff from docs
(trying to remove the eslint-disables throughout)
2024-04-03 13:26:46 +05:30
Manav Rathi
e2fd88bff0 Remove (possibly) unnecessary tsconfigRootDir
Let's see what breaks
2024-04-03 13:15:12 +05:30
Manav Rathi
764b6bf2f3 Move react setup to react specific file 2024-04-03 13:02:09 +05:30
Vishal
39ec761949 fix warnings 2024-04-03 12:57:37 +05:30
Vishal
01f842c445 Rearrange methods 2024-04-03 12:41:18 +05:30
Neeraj Gupta
2fe703df92 [server] Increase embedding fetch limit (#1300)
## Description

Also use different semaphore than existing diff API

## Tests
2024-04-03 12:38:34 +05:30
Vishal
18c48c7e0a Fix typo in comment 2024-04-03 11:14:55 +05:30
Manav Rathi
ca688d0d46 [server] Add a notice that minio is only for getting started (#1299)
From our Discord, someone commented

> if minio's cautions about Single-Node, Single-Drive are to be taken
seriously:
>
> "SNSD deployments use a zero-parity erasure coded backend that
provides no
added reliability or availability beyond what the underlying storage
volume
  implements. These deployments are best suited for local testing and
evaluation, or for small-scale data workloads that do not have
availability or
  performance requirements."

MinIO was never meant as a production replacement, it was only to make
it easy
for people to get started. So add a notice in the docs re this.
2024-04-03 10:04:20 +05:30
Manav Rathi
885308471f [server] Add a notice that minio is only for getting started
From our Discord, someone commented

> if minio's cautions about Single-Node, Single-Drive are to be taken seriously:
>
> "SNSD deployments use a zero-parity erasure coded backend that provides no
  added reliability or availability beyond what the underlying storage volume
  implements. These deployments are best suited for local testing and
  evaluation, or for small-scale data workloads that do not have availability or
  performance requirements."

MinIO was never meant as a production replacement, it was only to make it easy
for people to get started. So add a notice in the docs re this.
2024-04-03 10:01:48 +05:30
Vishnu Mohandas
125f7bfece Update export docs 2024-04-03 08:52:34 +05:30
Neeraj Gupta
011aee20d5 [server] Fix handling of redundant auth update (#1298)
## Description
When client tries to send update request with same data, the actual db
update doesn't happen. This was resulting in 5xx error.

## Tests
2024-04-03 07:13:44 +05:30
Neeraj Gupta
85778bcdaa [server] Fix handling of redundant auth update 2024-04-03 07:05:03 +05:30
Manav Rathi
43e97d225e [web] Restructure shared package layouts (#1294)
- Merge @/ui into @/next
- Restructure eslint config
2024-04-02 20:55:05 +05:30
Manav Rathi
b3a86874db yarn.lock 2024-04-02 20:47:35 +05:30
Manav Rathi
5c1ed5be8f Restructure eslint config 2024-04-02 20:44:57 +05:30
Manav Rathi
14fde54d87 Not really 2024-04-02 20:04:23 +05:30
Manav Rathi
26b35cec9e Merge @/ui into @/next 2024-04-02 19:58:42 +05:30
Vishal
b8100b1273 rename functions 2024-04-02 17:45:30 +05:30
Vishal
a5fcbbf901 add listmonk mailing list control (subscribe/usubscribe) 2024-04-02 17:36:53 +05:30
Manav Rathi
6213628aee [web] Prefer .local files for local only configuration (#1280)
Refs: https://vitejs.dev/guide/env-and-mode.html
2024-04-02 17:22:32 +05:30
Manav Rathi
a7625cd83d [web] Enable Russian (#1288)
The translation percentage of Russian in crowdin is now 100%, it's time
to enable it as an option in the UI.

A big thank you to the translators ❤️
2024-04-02 17:22:15 +05:30
Manav Rathi
cc90dd7ba5 [web] Enable Russian
The translation percentage of Russian in crowdin is now 100%, it's time to
enable it as an option in the UI.

A big thank you to the translators.
2024-04-02 17:16:33 +05:30
Manav Rathi
b3630f9543 [desktop] Prevent the desktop app from getting stuck on viewing openstreetmap info (#1287)
Open the link in a new tab. This prevents the desktop app from getting
into a state where the user cannot navigate back.

Tested on the web app and desktop app.
2024-04-02 17:10:16 +05:30
Manav Rathi
9cb289e002 [desktop] Prevent the desktop app from getting stuck on viewing openstreetmap info
Open the link in a new tab. This prevents the desktop app from getting into a
state where the user cannot navigate back.
2024-04-02 17:05:23 +05:30
Manav Rathi
b95fc54adb [web] Prefer .local files for local only configuration
Refs:
https://vitejs.dev/guide/env-and-mode.html
2024-04-01 19:59:02 +05:30
592 changed files with 6663 additions and 39865 deletions

View File

@@ -85,7 +85,8 @@ jobs:
- name: Install dependencies for desktop build
run: |
sudo apt-get update -y
sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate appindicator3-0.1 libappindicator3-dev libffi7
sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate appindicator3-0.1 libappindicator3-dev libffi-dev libtiff5
sudo updatedb --localpaths='/usr/lib/x86_64-linux-gnu'
- name: Install appimagetool
run: |

View File

@@ -32,6 +32,8 @@ jobs:
image: server
registry: ghcr.io
enableBuildKit: true
multiPlatform: true
platform: linux/amd64,linux/arm64,linux/arm/v7
buildArgs: GIT_COMMIT=${{ inputs.commit }}
tags: ${{ inputs.commit }}, latest
username: ${{ github.actor }}

View File

@@ -5,7 +5,7 @@ on:
branches: [main]
paths:
# Run workflow when web's en-US/translation.json is changed
- "web/apps/photos/public/locales/en-US/translation.json"
- "web/packages/next/locales/en-US/translation.json"
# Or the workflow itself is changed
- ".github/workflows/web-crowdin.yml"
schedule:

View File

@@ -24,7 +24,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install

View File

@@ -24,7 +24,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install

View File

@@ -24,7 +24,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install

View File

@@ -24,7 +24,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install
@@ -39,5 +39,5 @@ jobs:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: deploy/payments
directory: web/apps/payments/out
directory: web/apps/payments/dist
wranglerVersion: "3"

View File

@@ -24,7 +24,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install

48
.github/workflows/web-deploy-staff.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: "Deploy (staff)"
on:
# Run on every push to main that changes web/apps/staff/
push:
branches: [main]
paths:
- "web/apps/staff/**"
- ".github/workflows/web-deploy-staff.yml"
# Also allow manually running the workflow
workflow_dispatch:
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: "web/yarn.lock"
- name: Install dependencies
run: yarn install
- name: Build staff
run: yarn build:staff
- name: Publish staff
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: deploy/staff
directory: web/apps/staff/dist
wranglerVersion: "3"

View File

@@ -34,7 +34,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install
@@ -88,7 +88,7 @@ jobs:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: n-payments
directory: web/apps/payments/out
directory: web/apps/payments/dist
wranglerVersion: "3"
- name: Build photos

View File

@@ -34,7 +34,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install

View File

@@ -59,7 +59,10 @@ See [docs/](docs/README.md) for how to edit these documents.
## Code contributions
If you'd like to contribute code, it is best to start small.
Code is a small aspect of community, and the ways mentioned above are more
important in helping us. But if you'd _really_ like to contribute code, it is
best to start small. Consider some well-scoped changes, say like adding more
[custom icons to auth](auth/docs/adding-icons.md).
Each of the individual product/platform specific directories in this repository
have instructions on setting up a dev environment and making changes. The issues

View File

@@ -0,0 +1,40 @@
Ente Auth helps you generate and store 2 step verification (2FA)
tokens on your mobile devices.
FEATURES
- Secure Backups
Auth provides end-to-end encrypted cloud backups so that you don't have to worry
about losing your tokens. We use the same protocols ente Photos uses to encrypt
and preserve your data.
- Multi Device Synchronization
Auth will automatically sync the 2FA tokens you add to your account, across all
your devices. Every new device you sign into will have access to these tokens.
- Web access
You can access your 2FA code from any web browser by visiting https://auth.ente.io .
- Offline Mode
Auth generates 2FA tokens offline, so your network connectivity will not get in
the way of your workflow.
- Import and Export Tokens
You can add tokens to Auth by one of the following methods:
1. Scanning a QR code
2. Manually entering (copy-pasting) a 2FA secret
3. Bulk importing from a file that contains a list of codes in the following format:
otpauth://totp/provider.com:you@email.com?secret=YOUR_SECRET
The codes maybe separated by new lines or commas.
You can also export the codes you have added to Auth, to an **unencrypted** text
file, that adheres to the above format.
SUPPORT
If you need help, please reach out to support@ente.io, and a human will get in touch with you.
If you have feature requests, please create an issue @ https://github.com/ente-io/ente

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -0,0 +1 @@
Auth is a FOSS authenticator app that provides end-to-end encrypted backups for your 2FA secrets.

View File

@@ -0,0 +1 @@
Ente Auth

View File

@@ -67,8 +67,6 @@ PODS:
- Toast
- local_auth_darwin (0.0.1):
- Flutter
- local_auth_ios (0.0.1):
- Flutter
- move_to_background (0.0.1):
- Flutter
- MTBBarcodeScanner (5.0.11)
@@ -99,8 +97,6 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- smart_auth (0.0.1):
- Flutter
- sodium_libs (2.2.1):
- Flutter
- sqflite (0.0.3):
@@ -142,7 +138,6 @@ DEPENDENCIES:
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
@@ -151,7 +146,6 @@ DEPENDENCIES:
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- smart_auth (from `.symlinks/plugins/smart_auth/ios`)
- sodium_libs (from `.symlinks/plugins/sodium_libs/ios`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
@@ -202,8 +196,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/fluttertoast/ios"
local_auth_darwin:
:path: ".symlinks/plugins/local_auth_darwin/darwin"
local_auth_ios:
:path: ".symlinks/plugins/local_auth_ios/ios"
move_to_background:
:path: ".symlinks/plugins/move_to_background/ios"
package_info_plus:
@@ -220,8 +212,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
smart_auth:
:path: ".symlinks/plugins/smart_auth/ios"
sodium_libs:
:path: ".symlinks/plugins/sodium_libs/ios"
sqflite:
@@ -245,11 +235,10 @@ SPEC CHECKSUMS:
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
@@ -264,7 +253,6 @@ SPEC CHECKSUMS:
SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2
sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3: 73b7fc691fdc43277614250e04d183740cb15078

View File

@@ -365,7 +365,7 @@
DEVELOPMENT_TEAM = 6Z68YJY9Q2;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = auth;
INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -439,7 +439,7 @@
DEVELOPMENT_TEAM = 6Z68YJY9Q2;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = auth;
INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -513,7 +513,7 @@
DEVELOPMENT_TEAM = 6Z68YJY9Q2;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = auth;
INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -587,7 +587,7 @@
DEVELOPMENT_TEAM = 6Z68YJY9Q2;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = auth;
INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -661,7 +661,7 @@
DEVELOPMENT_TEAM = 6Z68YJY9Q2;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = auth;
INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",

View File

@@ -78,14 +78,14 @@
"data": "Data",
"importCodes": "Import codes",
"importTypePlainText": "Plain text",
"importTypeEnteEncrypted": "ente Encrypted export",
"importTypeEnteEncrypted": "Ente Encrypted export",
"passwordForDecryptingExport": "Password to decrypt export",
"passwordEmptyError": "Password can not be empty",
"importFromApp": "Import codes from {appName}",
"importGoogleAuthGuide": "Export your accounts from Google Authenticator to a QR code using the \"Transfer Accounts\" option. Then using another device, scan the QR code.\n\nTip: You can use your laptop's webcam to take a picture of the QR code.",
"importSelectJsonFile": "Select JSON file",
"importSelectAppExport": "Select {appName} export file",
"importEnteEncGuide": "Select the encrypted JSON file exported from ente",
"importEnteEncGuide": "Select the encrypted JSON file exported from Ente",
"importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.",
"importBitwardenGuide": "Use the \"Export vault\" option within Bitwarden Tools and import the unencrypted JSON file.",
"importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.",
@@ -115,22 +115,22 @@
"copied": "Copied",
"pleaseTryAgain": "Please try again",
"existingUser": "Existing User",
"newUser": "New to ente",
"newUser": "New to Ente",
"delete": "Delete",
"enterYourPasswordHint": "Enter your password",
"forgotPassword": "Forgot password",
"oops": "Oops",
"suggestFeatures": "Suggest features",
"faq": "FAQ",
"faq_q_1": "How secure is ente Auth?",
"faq_a_1": "All codes you backup via ente is stored end-to-end encrypted. This means only you can access your codes. Our apps are open source and our cryptography has been externally audited.",
"faq_q_1": "How secure is Auth?",
"faq_a_1": "All codes you backup via Auth is stored end-to-end encrypted. This means only you can access your codes. Our apps are open source and our cryptography has been externally audited.",
"faq_q_2": "Can I access my codes on desktop?",
"faq_a_2": "You can access your codes on the web @ auth.ente.io.",
"faq_q_3": "How can I delete codes?",
"faq_a_3": "You can delete a code by swiping left on that item.",
"faq_q_4": "How can I support this project?",
"faq_a_4": "You can support the development of this project by subscribing to our Photos app @ ente.io.",
"faq_q_5": "How can I enable FaceID lock in ente Auth",
"faq_q_5": "How can I enable FaceID lock in Auth",
"faq_a_5": "You can enable FaceID lock under Settings → Security → Lockscreen.",
"somethingWentWrongMessage": "Something went wrong, please try again",
"leaveFamily": "Leave family",
@@ -350,7 +350,7 @@
"deleteCodeAuthMessage": "Authenticate to delete code",
"showQRAuthMessage": "Authenticate to show QR code",
"confirmAccountDeleteTitle": "Confirm account deletion",
"confirmAccountDeleteMessage": "This account is linked to other ente apps, if you use any.\n\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
"confirmAccountDeleteMessage": "This account is linked to other Ente apps, if you use any.\n\nYour uploaded data, across all Ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
"androidBiometricHint": "Verify identity",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."

View File

@@ -93,12 +93,22 @@ class _HomePageState extends State<HomePage> {
void _applyFilteringAndRefresh() {
if (_searchText.isNotEmpty && _showSearchBox) {
final String val = _searchText.toLowerCase();
_filteredCodes = _codes
.where(
(element) => (element.account.toLowerCase().contains(val) ||
element.issuer.toLowerCase().contains(val)),
)
.toList();
// Prioritize issuer match above account for better UX while searching
// for a specific TOTP for email providers. Searching for "emailProvider" like (gmail, proton) should
// show the email provider first instead of other accounts where protonmail
// is the account name.
final List<Code> issuerMatch = [];
final List<Code> accountMatch = [];
for (final Code code in _codes) {
if (code.issuer.toLowerCase().contains(val)) {
issuerMatch.add(code);
} else if (code.account.toLowerCase().contains(val)) {
accountMatch.add(code);
}
}
_filteredCodes = issuerMatch;
_filteredCodes.addAll(accountMatch);
} else {
_filteredCodes = _codes;
}

View File

@@ -4,8 +4,7 @@ import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/lifecycle_event_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pinput/pinput.dart';
import 'package:pinput/pin_put/pin_put.dart';
class TwoFactorAuthenticationPage extends StatefulWidget {
final String sessionID;
@@ -20,6 +19,10 @@ class TwoFactorAuthenticationPage extends StatefulWidget {
class _TwoFactorAuthenticationPageState
extends State<TwoFactorAuthenticationPage> {
final _pinController = TextEditingController();
final _pinPutDecoration = BoxDecoration(
border: Border.all(color: const Color.fromRGBO(45, 194, 98, 1.0)),
borderRadius: BorderRadius.circular(15.0),
);
String _code = "";
late LifecycleEventHandler _lifecycleEventHandler;
@@ -60,16 +63,6 @@ class _TwoFactorAuthenticationPageState
Widget _getBody() {
final l10n = context.l10n;
final pinPutDecoration = BoxDecoration(
border: Border.all(
color: Theme.of(context)
.inputDecorationTheme
.focusedBorder!
.borderSide
.color,
),
borderRadius: BorderRadius.circular(15.0),
);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
@@ -86,31 +79,32 @@ class _TwoFactorAuthenticationPageState
const Padding(padding: EdgeInsets.all(32)),
Padding(
padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
child: Pinput(
onSubmitted: (String code) {
child: PinPut(
fieldsCount: 6,
onSubmit: (String code) {
_verifyTwoFactorCode(code);
},
length: 6,
defaultPinTheme: const PinTheme(),
submittedPinTheme: PinTheme(
decoration: pinPutDecoration.copyWith(
borderRadius: BorderRadius.circular(20.0),
),
),
focusedPinTheme: PinTheme(
decoration: pinPutDecoration,
),
followingPinTheme: PinTheme(
decoration: pinPutDecoration.copyWith(
borderRadius: BorderRadius.circular(5.0),
),
),
onChanged: (String pin) {
setState(() {
_code = pin;
});
},
controller: _pinController,
submittedFieldDecoration: _pinPutDecoration.copyWith(
borderRadius: BorderRadius.circular(20.0),
),
selectedFieldDecoration: _pinPutDecoration,
followingFieldDecoration: _pinPutDecoration.copyWith(
borderRadius: BorderRadius.circular(5.0),
border: Border.all(
color: const Color.fromRGBO(45, 194, 98, 0.5),
),
),
inputDecoration: const InputDecoration(
focusedBorder: InputBorder.none,
border: InputBorder.none,
counterText: '',
),
autofocus: true,
),
),

View File

@@ -13,7 +13,6 @@
#include <gtk/gtk_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <smart_auth/smart_auth_plugin.h>
#include <sodium_libs/sodium_libs_plugin.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <tray_manager/tray_manager_plugin.h>
@@ -42,9 +41,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
g_autoptr(FlPluginRegistrar) smart_auth_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SmartAuthPlugin");
smart_auth_plugin_register_with_registrar(smart_auth_registrar);
g_autoptr(FlPluginRegistrar) sodium_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SodiumLibsPlugin");
sodium_libs_plugin_register_with_registrar(sodium_libs_registrar);

View File

@@ -10,7 +10,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
gtk
screen_retriever
sentry_flutter
smart_auth
sodium_libs
sqlite3_flutter_libs
tray_manager

View File

@@ -25,3 +25,4 @@ startup_notify: false
# - libcurl.so.4
include:
- libffi.so.7
- libtiff.so.5

View File

@@ -9,7 +9,7 @@ url: https://github.com/ente-io/ente
display_name: Auth
dependencies:
requires:
- libsqlite3x
- webkit2gtk-4.0
- libsodium

View File

@@ -20,7 +20,6 @@ import screen_retriever
import sentry_flutter
import share_plus
import shared_preferences_foundation
import smart_auth
import sodium_libs
import sqflite
import sqlite3_flutter_libs
@@ -44,7 +43,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SmartAuthPlugin.register(with: registry.registrar(forPlugin: "SmartAuthPlugin"))
SodiumLibsPlugin.register(with: registry.registrar(forPlugin: "SodiumLibsPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))

View File

@@ -1109,10 +1109,10 @@ packages:
dependency: "direct main"
description:
name: pinput
sha256: a92b55ecf9c25d1b9e100af45905385d5bc34fc9b6b04177a9e82cb88fe4d805
sha256: "27eb69042f75755bdb6544f6e79a50a6ed09d6e97e2d75c8421744df1e392949"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "1.2.2"
platform:
dependency: transitive
description:
@@ -1334,14 +1334,6 @@ packages:
description: flutter
source: sdk
version: "0.0.99"
smart_auth:
dependency: transitive
description:
name: smart_auth
sha256: a25229b38c02f733d0a4e98d941b42bed91a976cb589e934895e60ccfa674cf6
url: "https://pub.dev"
source: hosted
version: "1.1.1"
sodium:
dependency: transitive
description:
@@ -1551,14 +1543,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.2"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc
url: "https://pub.dev"
source: hosted
version: "1.0.0+1"
url_launcher:
dependency: "direct main"
description:

View File

@@ -1,6 +1,6 @@
name: ente_auth
description: ente two-factor authenticator
version: 2.0.50+250
version: 2.0.55+255
publish_to: none
environment:
@@ -75,7 +75,7 @@ dependencies:
password_strength: ^0.2.0
path: ^1.8.3
path_provider: ^2.0.11
pinput: ^3.0.1
pinput: ^1.2.2
pointycastle: ^3.7.3
privacy_screen: ^0.0.6
protobuf: ^3.0.0

View File

@@ -16,7 +16,6 @@
#include <screen_retriever/screen_retriever_plugin.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <smart_auth/smart_auth_plugin.h>
#include <sodium_libs/sodium_libs_plugin_c_api.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <tray_manager/tray_manager_plugin.h>
@@ -44,8 +43,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
SmartAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SmartAuthPlugin"));
SodiumLibsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SodiumLibsPluginCApi"));
Sqlite3FlutterLibsPluginRegisterWithRegistrar(

View File

@@ -13,7 +13,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
screen_retriever
sentry_flutter
share_plus
smart_auth
sodium_libs
sqlite3_flutter_libs
tray_manager

View File

@@ -7,11 +7,6 @@ module.exports = {
// "plugin:@typescript-eslint/strict-type-checked",
// "plugin:@typescript-eslint/stylistic-type-checked",
],
/* Temporarily disable some rules
Enhancement: Remove me */
rules: {
"no-unused-vars": "off",
},
/* Temporarily add a global
Enhancement: Remove me */
globals: {

View File

@@ -61,15 +61,15 @@ Electron process. This allows us to directly use the output produced by
### Others
* [any-shell-escape](https://github.com/boazy/any-shell-escape) is for
escaping shell commands before we execute them (e.g. say when invoking the
embedded ffmpeg CLI).
- [any-shell-escape](https://github.com/boazy/any-shell-escape) is for
escaping shell commands before we execute them (e.g. say when invoking the
embedded ffmpeg CLI).
* [auto-launch](https://github.com/Teamwork/node-auto-launch) is for
automatically starting our app on login, if the user so wishes.
- [auto-launch](https://github.com/Teamwork/node-auto-launch) is for
automatically starting our app on login, if the user so wishes.
* [electron-store](https://github.com/sindresorhus/electron-store) is used for
persisting user preferences and other arbitrary data.
- [electron-store](https://github.com/sindresorhus/electron-store) is used for
persisting user preferences and other arbitrary data.
## Dev
@@ -79,12 +79,12 @@ are similar to that in the web code.
Some extra ones specific to the code here are:
* [concurrently](https://github.com/open-cli-tools/concurrently) for spawning
parallel tasks when we do `yarn dev`.
- [concurrently](https://github.com/open-cli-tools/concurrently) for spawning
parallel tasks when we do `yarn dev`.
* [shx](https://github.com/shelljs/shx) for providing a portable way to use Unix
commands in our `package.json` scripts. This allows us to use the same
commands (like `ln`) across different platforms like Linux and Windows.
- [shx](https://github.com/shelljs/shx) for providing a portable way to use
Unix commands in our `package.json` scripts. This allows us to use the same
commands (like `ln`) across different platforms like Linux and Windows.
## Functionality
@@ -111,11 +111,11 @@ watcher for the watch folders functionality.
### AI/ML
* [onnxruntime-node](https://github.com/Microsoft/onnxruntime)
* html-entities is used by the bundled clip-bpe-ts.
* GGML binaries are bundled
* We also use [jpeg-js](https://github.com/jpeg-js/jpeg-js#readme) for
conversion of all images to JPEG before processing.
- [onnxruntime-node](https://github.com/Microsoft/onnxruntime)
- html-entities is used by the bundled clip-bpe-ts.
- GGML binaries are bundled
- We also use [jpeg-js](https://github.com/jpeg-js/jpeg-js#readme) for
conversion of all images to JPEG before processing.
## ZIP

View File

@@ -1,17 +0,0 @@
import { logError } from "../main/log";
import { keysStore } from "../stores/keys.store";
import { safeStorageStore } from "../stores/safeStorage.store";
import { uploadStatusStore } from "../stores/upload.store";
import { watchStore } from "../stores/watch.store";
export const clearElectronStore = () => {
try {
uploadStatusStore.clear();
keysStore.clear();
safeStorageStore.clear();
watchStore.clear();
} catch (e) {
logError(e, "error while clearing electron store");
throw e;
}
};

View File

@@ -1,28 +0,0 @@
import { safeStorage } from "electron/main";
import { logError } from "../main/log";
import { safeStorageStore } from "../stores/safeStorage.store";
export async function setEncryptionKey(encryptionKey: string) {
try {
const encryptedKey: Buffer =
await safeStorage.encryptString(encryptionKey);
const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64");
safeStorageStore.set("encryptionKey", b64EncryptedKey);
} catch (e) {
logError(e, "setEncryptionKey failed");
throw e;
}
}
export async function getEncryptionKey(): Promise<string> {
try {
const b64EncryptedKey = safeStorageStore.get("encryptionKey");
if (b64EncryptedKey) {
const keyBuffer = Buffer.from(b64EncryptedKey, "base64");
return await safeStorage.decryptString(keyBuffer);
}
} catch (e) {
logError(e, "getEncryptionKey failed");
throw e;
}
}

View File

@@ -1,41 +0,0 @@
import { getElectronFile } from "../services/fs";
import {
getElectronFilesFromGoogleZip,
getSavedFilePaths,
} from "../services/upload";
import { uploadStatusStore } from "../stores/upload.store";
import { ElectronFile, FILE_PATH_TYPE } from "../types/ipc";
export const getPendingUploads = async () => {
const filePaths = getSavedFilePaths(FILE_PATH_TYPE.FILES);
const zipPaths = getSavedFilePaths(FILE_PATH_TYPE.ZIPS);
const collectionName = uploadStatusStore.get("collectionName");
let files: ElectronFile[] = [];
let type: FILE_PATH_TYPE;
if (zipPaths.length) {
type = FILE_PATH_TYPE.ZIPS;
for (const zipPath of zipPaths) {
files = [
...files,
...(await getElectronFilesFromGoogleZip(zipPath)),
];
}
const pendingFilePaths = new Set(filePaths);
files = files.filter((file) => pendingFilePaths.has(file.path));
} else if (filePaths.length) {
type = FILE_PATH_TYPE.FILES;
files = await Promise.all(filePaths.map(getElectronFile));
}
return {
files,
collectionName,
type,
};
};
export {
getElectronFilesFromGoogleZip,
setToUploadCollection,
setToUploadFiles,
} from "../services/upload";

View File

@@ -1,26 +0,0 @@
/**
* [Note: Custom errors across Electron/Renderer boundary]
*
* We need to use the `message` field to disambiguate between errors thrown by
* the main process when invoked from the renderer process. This is because:
*
* > Errors thrown throw `handle` in the main process are not transparent as
* > they are serialized and only the `message` property from the original error
* > is provided to the renderer process.
* >
* > - https://www.electronjs.org/docs/latest/tutorial/ipc
* >
* > Ref: https://github.com/electron/electron/issues/24427
*/
export const CustomErrors = {
WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED:
"Windows native image processing is not supported",
INVALID_OS: (os: string) => `Invalid OS - ${os}`,
WAIT_TIME_EXCEEDED: "Wait time exceeded",
UNSUPPORTED_PLATFORM: (platform: string, arch: string) =>
`Unsupported platform - ${platform} ${arch}`,
MODEL_DOWNLOAD_PENDING:
"Model download pending, skipping clip search request",
INVALID_FILE_PATH: "Invalid file path",
INVALID_CLIP_MODEL: (model: string) => `Invalid Clip model - ${model}`,
};

View File

@@ -12,6 +12,7 @@ import { app, BrowserWindow, Menu } from "electron/main";
import serveNextAt from "next-electron-server";
import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import {
addAllowOriginHeader,
@@ -19,7 +20,6 @@ import {
handleDockIconHideOnAutoLaunch,
handleDownloads,
handleExternalLinks,
logStartupBanner,
setupMacWindowOnDockIconClick,
setupTrayItem,
} from "./main/init";
@@ -72,6 +72,21 @@ const setupRendererServer = () => {
serveNextAt(rendererURL);
};
/**
* Log a standard startup banner.
*
* This helps us identify app starts and other environment details in the logs.
*/
const logStartupBanner = () => {
const version = isDev ? "dev" : app.getVersion();
log.info(`Starting ente-photos-desktop ${version}`);
const platform = process.platform;
const osRelease = os.release();
const systemVersion = process.getSystemVersion();
log.info("Running on", { platform, osRelease, systemVersion });
};
function enableSharedArrayBufferSupport() {
app.commandLine.appendSwitch("enable-features", "SharedArrayBuffer");
}
@@ -126,12 +141,12 @@ const deleteLegacyDiskCacheDirIfExists = async () => {
}
};
function setupAppEventEmitter(mainWindow: BrowserWindow) {
// fire event when mainWindow is in foreground
mainWindow.on("focus", () => {
mainWindow.webContents.send("app-in-foreground");
});
}
const attachEventHandlers = (mainWindow: BrowserWindow) => {
// Let ipcRenderer know when mainWindow is in the foreground.
mainWindow.on("focus", () =>
mainWindow.webContents.send("app-in-foreground"),
);
};
const main = () => {
const gotTheLock = app.requestSingleInstanceLock();
@@ -144,6 +159,7 @@ const main = () => {
initLogging();
setupRendererServer();
logStartupBanner();
handleDockIconHideOnAutoLaunch();
increaseDiskCache();
enableSharedArrayBufferSupport();
@@ -163,7 +179,6 @@ const main = () => {
//
// Note that some Electron APIs can only be used after this event occurs.
app.on("ready", async () => {
logStartupBanner();
mainWindow = await createWindow();
const watcher = initWatcher(mainWindow);
setupTrayItem(mainWindow);
@@ -175,13 +190,13 @@ const main = () => {
handleDownloads(mainWindow);
handleExternalLinks(mainWindow);
addAllowOriginHeader(mainWindow);
setupAppEventEmitter(mainWindow);
attachEventHandlers(mainWindow);
try {
deleteLegacyDiskCacheDirIfExists();
} catch (e) {
// Log but otherwise ignore errors during non-critical startup
// actions
// actions.
log.error("Ignoring startup error", e);
}
});

View File

@@ -1,6 +1,5 @@
import { app, BrowserWindow, nativeImage, Tray } from "electron";
import { existsSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { isAppQuitting, rendererURL } from "../main";
import autoLauncher from "../services/autoLauncher";
@@ -77,8 +76,6 @@ export const createWindow = async () => {
return mainWindow;
};
export async function handleUpdates(mainWindow: BrowserWindow) {}
export const setupTrayItem = (mainWindow: BrowserWindow) => {
const iconName = isPlatform("mac")
? "taskbar-icon-Template.png"
@@ -149,16 +146,6 @@ export async function handleDockIconHideOnAutoLaunch() {
}
}
export function logStartupBanner() {
const version = isDev ? "dev" : app.getVersion();
log.info(`Hello from ente-photos-desktop ${version}`);
const platform = process.platform;
const osRelease = os.release();
const systemVersion = process.getSystemVersion();
log.info("Running on", { platform, osRelease, systemVersion });
}
function lowerCaseHeaders(responseHeaders: Record<string, string[]>) {
const headers: Record<string, string[]> = {};
for (const key of Object.keys(responseHeaders)) {

View File

@@ -10,14 +10,6 @@
import type { FSWatcher } from "chokidar";
import { ipcMain } from "electron/main";
import { clearElectronStore } from "../api/electronStore";
import { getEncryptionKey, setEncryptionKey } from "../api/safeStorage";
import {
getElectronFilesFromGoogleZip,
getPendingUploads,
setToUploadCollection,
setToUploadFiles,
} from "../api/upload";
import {
appVersion,
muteUpdateNotification,
@@ -34,6 +26,17 @@ import {
convertToJPEG,
generateImageThumbnail,
} from "../services/imageProcessor";
import {
clearElectronStore,
getEncryptionKey,
setEncryptionKey,
} from "../services/store";
import {
getElectronFilesFromGoogleZip,
getPendingUploads,
setToUploadCollection,
setToUploadFiles,
} from "../services/upload";
import {
addWatchMapping,
getWatchMappings,
@@ -91,16 +94,16 @@ export const attachIPCHandlers = () => {
// - General
ipcMain.handle("appVersion", (_) => appVersion());
ipcMain.handle("appVersion", () => appVersion());
ipcMain.handle("openDirectory", (_, dirPath) => openDirectory(dirPath));
ipcMain.handle("openLogDirectory", (_) => openLogDirectory());
ipcMain.handle("openLogDirectory", () => openLogDirectory());
// See [Note: Catching exception during .send/.on]
ipcMain.on("logToDisk", (_, message) => logToDisk(message));
ipcMain.on("clear-electron-store", (_) => {
ipcMain.on("clear-electron-store", () => {
clearElectronStore();
});
@@ -108,11 +111,11 @@ export const attachIPCHandlers = () => {
setEncryptionKey(encryptionKey),
);
ipcMain.handle("getEncryptionKey", (_) => getEncryptionKey());
ipcMain.handle("getEncryptionKey", () => getEncryptionKey());
// - App update
ipcMain.on("update-and-restart", (_) => updateAndRestart());
ipcMain.on("update-and-restart", () => updateAndRestart());
ipcMain.on("skip-app-update", (_, version) => skipAppUpdate(version));
@@ -157,13 +160,13 @@ export const attachIPCHandlers = () => {
// - File selection
ipcMain.handle("selectDirectory", (_) => selectDirectory());
ipcMain.handle("selectDirectory", () => selectDirectory());
ipcMain.handle("showUploadFilesDialog", (_) => showUploadFilesDialog());
ipcMain.handle("showUploadFilesDialog", () => showUploadFilesDialog());
ipcMain.handle("showUploadDirsDialog", (_) => showUploadDirsDialog());
ipcMain.handle("showUploadDirsDialog", () => showUploadDirsDialog());
ipcMain.handle("showUploadZipDialog", (_) => showUploadZipDialog());
ipcMain.handle("showUploadZipDialog", () => showUploadZipDialog());
// - FS
@@ -177,12 +180,12 @@ export const attachIPCHandlers = () => {
ipcMain.handle(
"saveStreamToDisk",
(_, path: string, fileStream: ReadableStream<any>) =>
(_, path: string, fileStream: ReadableStream) =>
saveStreamToDisk(path, fileStream),
);
ipcMain.handle("saveFileToDisk", (_, path: string, file: any) =>
saveFileToDisk(path, file),
ipcMain.handle("saveFileToDisk", (_, path: string, contents: string) =>
saveFileToDisk(path, contents),
);
ipcMain.handle("readTextFile", (_, path: string) => readTextFile(path));
@@ -203,7 +206,7 @@ export const attachIPCHandlers = () => {
// - Upload
ipcMain.handle("getPendingUploads", (_) => getPendingUploads());
ipcMain.handle("getPendingUploads", () => getPendingUploads());
ipcMain.handle(
"setToUploadFiles",
@@ -252,7 +255,7 @@ export const attachFSWatchIPCHandlers = (watcher: FSWatcher) => {
removeWatchMapping(watcher, folderPath),
);
ipcMain.handle("getWatchMappings", (_) => getWatchMappings());
ipcMain.handle("getWatchMappings", () => getWatchMappings());
ipcMain.handle(
"updateWatchMappingSyncedFiles",

View File

@@ -15,7 +15,7 @@ import { isDev } from "./util";
*/
export const initLogging = () => {
log.transports.file.fileName = "ente.log";
log.transports.file.maxSize = 50 * 1024 * 1024; // 50MB;
log.transports.file.maxSize = 50 * 1024 * 1024; // 50 MB
log.transports.file.format = "[{y}-{m}-{d}T{h}:{i}:{s}{z}] {text}";
log.transports.console.level = false;
@@ -31,25 +31,7 @@ export const logToDisk = (message: string) => {
log.info(`[rndr] ${message}`);
};
export const logError = logErrorSentry;
/** Deprecated, but no alternative yet */
export function logErrorSentry(
error: any,
msg: string,
info?: Record<string, unknown>,
) {
logToDisk(
`error: ${error?.name} ${error?.message} ${
error?.stack
} msg: ${msg} info: ${JSON.stringify(info)}`,
);
if (isDev) {
console.log(error, { msg, info });
}
}
const logError1 = (message: string, e?: unknown) => {
const logError = (message: string, e?: unknown) => {
if (!e) {
logError_(message);
return;
@@ -78,7 +60,7 @@ const logInfo = (...params: any[]) => {
.map((p) => (typeof p == "string" ? p : util.inspect(p)))
.join(" ");
log.info(`[main] ${message}`);
if (isDev) console.log(message);
if (isDev) console.log(`[info] ${message}`);
};
const logDebug = (param: () => any) => {
@@ -98,12 +80,13 @@ export default {
* Log an error message with an optional associated error object.
*
* {@link e} is generally expected to be an `instanceof Error` but it can be
* any arbitrary object that we obtain, say, when in a try-catch handler.
* any arbitrary object that we obtain, say, when in a try-catch handler (in
* JavaScript any arbitrary value can be thrown).
*
* The log is written to disk. In development builds, the log is also
* printed to the (Node.js process') console.
* printed to the main (Node.js) process console.
*/
error: logError1,
error: logError,
/**
* Log a message.
*
@@ -111,7 +94,7 @@ export default {
* arbitrary number of arbitrary parameters that it then serializes.
*
* The log is written to disk. In development builds, the log is also
* printed to the (Node.js process') console.
* printed to the main (Node.js) process console.
*/
info: logInfo,
/**
@@ -121,11 +104,11 @@ export default {
* function to call to get the log message instead of directly taking the
* message. The provided function will only be called in development builds.
*
* The function can return an arbitrary value which is serialied before
* The function can return an arbitrary value which is serialized before
* being logged.
*
* This log is not written to disk. It is printed to the (Node.js process')
* console only on development builds.
* This log is NOT written to disk. And it is printed to the main (Node.js)
* process console, but only on development builds.
*/
debug: logDebug,
};

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-unused-vars */
/**
* @file The preload script
*
@@ -31,9 +32,9 @@
* and when changing one of them, remember to see if the other two also need
* changing:
*
* - [renderer] web/packages/shared/electron/types.ts contains docs
* - [preload] desktop/src/preload.ts ↕︎
* - [main] desktop/src/main/ipc.ts contains impl
* - [renderer] web/packages/next/types/electron.ts contains docs
* - [preload] desktop/src/preload.ts ↕︎
* - [main] desktop/src/main/ipc.ts contains impl
*/
import { contextBridge, ipcRenderer } from "electron/renderer";
@@ -53,7 +54,7 @@ import type {
const appVersion = (): Promise<string> => ipcRenderer.invoke("appVersion");
const openDirectory = (dirPath: string): Promise<void> =>
ipcRenderer.invoke("openDirectory");
ipcRenderer.invoke("openDirectory", dirPath);
const openLogDirectory = (): Promise<void> =>
ipcRenderer.invoke("openLogDirectory");
@@ -68,9 +69,7 @@ const fsExists = (path: string): Promise<boolean> =>
const registerForegroundEventListener = (onForeground: () => void) => {
ipcRenderer.removeAllListeners("app-in-foreground");
ipcRenderer.on("app-in-foreground", () => {
onForeground();
});
ipcRenderer.on("app-in-foreground", onForeground);
};
const clearElectronStore = () => {
@@ -228,11 +227,11 @@ const checkExistsAndCreateDir = (dirPath: string): Promise<void> =>
const saveStreamToDisk = (
path: string,
fileStream: ReadableStream<any>,
fileStream: ReadableStream,
): Promise<void> => ipcRenderer.invoke("saveStreamToDisk", path, fileStream);
const saveFileToDisk = (path: string, file: any): Promise<void> =>
ipcRenderer.invoke("saveFileToDisk", path, file);
const saveFileToDisk = (path: string, contents: string): Promise<void> =>
ipcRenderer.invoke("saveFileToDisk", path, contents);
const readTextFile = (path: string): Promise<string> =>
ipcRenderer.invoke("readTextFile", path);
@@ -308,7 +307,7 @@ const getDirFiles = (dirPath: string): Promise<ElectronFile[]> =>
//
// The copy itself is relatively fast, but the problem with transfering large
// amounts of data is potentially running out of memory during the copy.
contextBridge.exposeInMainWorld("ElectronAPIs", {
contextBridge.exposeInMainWorld("electron", {
// - General
appVersion,
openDirectory,

View File

@@ -1,9 +1,9 @@
import { compareVersions } from "compare-versions";
import { app, BrowserWindow } from "electron";
import { default as ElectronLog, default as log } from "electron-log";
import { default as electronLog } from "electron-log";
import { autoUpdater } from "electron-updater";
import { setIsAppQuitting, setIsUpdateAvailable } from "../main";
import { logErrorSentry } from "../main/log";
import log from "../main/log";
import { AppUpdateInfo } from "../types/ipc";
import {
clearMuteUpdateNotificationVersion,
@@ -18,7 +18,7 @@ const FIVE_MIN_IN_MICROSECOND = 5 * 60 * 1000;
const ONE_DAY_IN_MICROSECOND = 1 * 24 * 60 * 60 * 1000;
export function setupAutoUpdater(mainWindow: BrowserWindow) {
autoUpdater.logger = log;
autoUpdater.logger = electronLog;
autoUpdater.autoDownload = false;
checkForUpdateAndNotify(mainWindow);
setInterval(
@@ -33,49 +33,36 @@ export function forceCheckForUpdateAndNotify(mainWindow: BrowserWindow) {
clearMuteUpdateNotificationVersion();
checkForUpdateAndNotify(mainWindow);
} catch (e) {
logErrorSentry(e, "forceCheckForUpdateAndNotify failed");
log.error("forceCheckForUpdateAndNotify failed", e);
}
}
async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
try {
log.debug("checkForUpdateAndNotify called");
const updateCheckResult = await autoUpdater.checkForUpdates();
log.debug("update version", updateCheckResult.updateInfo.version);
if (
compareVersions(
updateCheckResult.updateInfo.version,
app.getVersion(),
) <= 0
) {
log.debug("already at latest version");
log.debug(() => "checkForUpdateAndNotify");
const { updateInfo } = await autoUpdater.checkForUpdates();
log.debug(() => `Update version ${updateInfo.version}`);
if (compareVersions(updateInfo.version, app.getVersion()) <= 0) {
log.debug(() => "Skipping update, already at latest version");
return;
}
const skipAppVersion = getSkipAppVersion();
if (
skipAppVersion &&
updateCheckResult.updateInfo.version === skipAppVersion
) {
log.info(
"user chose to skip version ",
updateCheckResult.updateInfo.version,
);
if (skipAppVersion && updateInfo.version === skipAppVersion) {
log.info(`User chose to skip version ${updateInfo.version}`);
return;
}
let timeout: NodeJS.Timeout;
log.debug("attempting auto update");
log.debug(() => "Attempting auto update");
autoUpdater.downloadUpdate();
const muteUpdateNotificationVersion =
getMuteUpdateNotificationVersion();
if (
muteUpdateNotificationVersion &&
updateCheckResult.updateInfo.version ===
muteUpdateNotificationVersion
updateInfo.version === muteUpdateNotificationVersion
) {
log.info(
"user chose to mute update notification for version ",
updateCheckResult.updateInfo.version,
`User has muted update notifications for version ${updateInfo.version}`,
);
return;
}
@@ -84,28 +71,28 @@ async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
() =>
showUpdateDialog(mainWindow, {
autoUpdatable: true,
version: updateCheckResult.updateInfo.version,
version: updateInfo.version,
}),
FIVE_MIN_IN_MICROSECOND,
);
});
autoUpdater.on("error", (error) => {
clearTimeout(timeout);
logErrorSentry(error, "auto update failed");
log.error("Auto update failed", error);
showUpdateDialog(mainWindow, {
autoUpdatable: false,
version: updateCheckResult.updateInfo.version,
version: updateInfo.version,
});
});
setIsUpdateAvailable(true);
} catch (e) {
logErrorSentry(e, "checkForUpdateAndNotify failed");
log.error("checkForUpdateAndNotify failed", e);
}
}
export function updateAndRestart() {
ElectronLog.log("user quit the app");
log.info("user quit the app");
setIsAppQuitting(true);
autoUpdater.quitAndInstall();
}

View File

@@ -1,7 +1,7 @@
import chokidar from "chokidar";
import { BrowserWindow } from "electron";
import path from "path";
import { logError } from "../main/log";
import log from "../main/log";
import { getWatchMappings } from "../services/watch";
import { getElectronFile } from "./fs";
@@ -38,7 +38,7 @@ export function initWatcher(mainWindow: BrowserWindow) {
);
})
.on("error", (error) => {
logError(error, "error while watching files");
log.error("Error while watching files", error);
});
return watcher;

View File

@@ -2,11 +2,10 @@ import { app, net } from "electron/main";
import { existsSync } from "fs";
import fs from "node:fs/promises";
import path from "node:path";
import { CustomErrors } from "../constants/errors";
import { writeStream } from "../main/fs";
import log, { logErrorSentry } from "../main/log";
import log from "../main/log";
import { execAsync, isDev } from "../main/util";
import { Model } from "../types/ipc";
import { CustomErrors, Model, isModel } from "../types/ipc";
import Tokenizer from "../utils/clip-bpe-ts/mod";
import { getPlatform } from "../utils/common/platform";
import { generateTempFilePath } from "../utils/temp";
@@ -78,7 +77,7 @@ async function downloadModel(saveLocation: string, url: string) {
let imageModelDownloadInProgress: Promise<void> = null;
export async function getClipImageModelPath(type: "ggml" | "onnx") {
const getClipImageModelPath = async (type: "ggml" | "onnx") => {
try {
const modelSavePath = getModelSavePath(IMAGE_MODEL_NAME[type]);
if (imageModelDownloadInProgress) {
@@ -86,7 +85,7 @@ export async function getClipImageModelPath(type: "ggml" | "onnx") {
await imageModelDownloadInProgress;
} else {
if (!existsSync(modelSavePath)) {
log.info("clip image model not found, downloading");
log.info("CLIP image model not found, downloading");
imageModelDownloadInProgress = downloadModel(
modelSavePath,
IMAGE_MODEL_DOWNLOAD_URL[type],
@@ -96,7 +95,7 @@ export async function getClipImageModelPath(type: "ggml" | "onnx") {
const localFileSize = (await fs.stat(modelSavePath)).size;
if (localFileSize !== IMAGE_MODEL_SIZE_IN_BYTES[type]) {
log.info(
`clip image model size mismatch, downloading again got: ${localFileSize}`,
`CLIP image model size mismatch, downloading again got: ${localFileSize}`,
);
imageModelDownloadInProgress = downloadModel(
modelSavePath,
@@ -110,21 +109,22 @@ export async function getClipImageModelPath(type: "ggml" | "onnx") {
} finally {
imageModelDownloadInProgress = null;
}
}
};
let textModelDownloadInProgress: boolean = false;
export async function getClipTextModelPath(type: "ggml" | "onnx") {
const getClipTextModelPath = async (type: "ggml" | "onnx") => {
const modelSavePath = getModelSavePath(TEXT_MODEL_NAME[type]);
if (textModelDownloadInProgress) {
throw Error(CustomErrors.MODEL_DOWNLOAD_PENDING);
} else {
if (!existsSync(modelSavePath)) {
log.info("clip text model not found, downloading");
log.info("CLIP text model not found, downloading");
textModelDownloadInProgress = true;
downloadModel(modelSavePath, TEXT_MODEL_DOWNLOAD_URL[type])
.catch(() => {
// ignore
.catch((e) => {
// log but otherwise ignore
log.error("CLIP text model download failed", e);
})
.finally(() => {
textModelDownloadInProgress = false;
@@ -134,12 +134,13 @@ export async function getClipTextModelPath(type: "ggml" | "onnx") {
const localFileSize = (await fs.stat(modelSavePath)).size;
if (localFileSize !== TEXT_MODEL_SIZE_IN_BYTES[type]) {
log.info(
`clip text model size mismatch, downloading again got: ${localFileSize}`,
`CLIP text model size mismatch, downloading again got: ${localFileSize}`,
);
textModelDownloadInProgress = true;
downloadModel(modelSavePath, TEXT_MODEL_DOWNLOAD_URL[type])
.catch(() => {
// ignore
.catch((e) => {
// log but otherwise ignore
log.error("CLIP text model download failed", e);
})
.finally(() => {
textModelDownloadInProgress = false;
@@ -149,7 +150,7 @@ export async function getClipTextModelPath(type: "ggml" | "onnx") {
}
}
return modelSavePath;
}
};
function getGGMLClipPath() {
return isDev
@@ -198,6 +199,8 @@ export const computeImageEmbedding = async (
model: Model,
imageData: Uint8Array,
): Promise<Float32Array> => {
if (!isModel(model)) throw new Error(`Invalid CLIP model ${model}`);
let tempInputFilePath = null;
try {
tempInputFilePath = await generateTempFilePath("");
@@ -243,180 +246,69 @@ async function computeImageEmbedding_(
inputFilePath: string,
): Promise<Float32Array> {
if (!existsSync(inputFilePath)) {
throw Error(CustomErrors.INVALID_FILE_PATH);
throw new Error("Invalid file path");
}
if (model === Model.GGML_CLIP) {
return await computeGGMLImageEmbedding(inputFilePath);
} else if (model === Model.ONNX_CLIP) {
return await computeONNXImageEmbedding(inputFilePath);
} else {
throw Error(CustomErrors.INVALID_CLIP_MODEL(model));
switch (model) {
case "ggml-clip":
return await computeGGMLImageEmbedding(inputFilePath);
case "onnx-clip":
return await computeONNXImageEmbedding(inputFilePath);
}
}
export async function computeGGMLImageEmbedding(
const computeGGMLImageEmbedding = async (
inputFilePath: string,
): Promise<Float32Array> {
try {
const clipModelPath = await getClipImageModelPath("ggml");
const ggmlclipPath = getGGMLClipPath();
const cmd = IMAGE_EMBEDDING_EXTRACT_CMD.map((cmdPart) => {
if (cmdPart === GGMLCLIP_PATH_PLACEHOLDER) {
return ggmlclipPath;
} else if (cmdPart === CLIP_MODEL_PATH_PLACEHOLDER) {
return clipModelPath;
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
return inputFilePath;
} else {
return cmdPart;
}
});
): Promise<Float32Array> => {
const clipModelPath = await getClipImageModelPath("ggml");
const ggmlclipPath = getGGMLClipPath();
const cmd = IMAGE_EMBEDDING_EXTRACT_CMD.map((cmdPart) => {
if (cmdPart === GGMLCLIP_PATH_PLACEHOLDER) {
return ggmlclipPath;
} else if (cmdPart === CLIP_MODEL_PATH_PLACEHOLDER) {
return clipModelPath;
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
return inputFilePath;
} else {
return cmdPart;
}
});
const { stdout } = await execAsync(cmd);
// parse stdout and return embedding
// get the last line of stdout
const lines = stdout.split("\n");
const lastLine = lines[lines.length - 1];
const embedding = JSON.parse(lastLine);
const embeddingArray = new Float32Array(embedding);
return embeddingArray;
} catch (err) {
log.error("Failed to compute GGML image embedding", err);
throw err;
}
}
const { stdout } = await execAsync(cmd);
// parse stdout and return embedding
// get the last line of stdout
const lines = stdout.split("\n");
const lastLine = lines[lines.length - 1];
const embedding = JSON.parse(lastLine);
const embeddingArray = new Float32Array(embedding);
return embeddingArray;
};
export async function computeONNXImageEmbedding(
const computeONNXImageEmbedding = async (
inputFilePath: string,
): Promise<Float32Array> {
try {
const imageSession = await getOnnxImageSession();
const t1 = Date.now();
const rgbData = await getRGBData(inputFilePath);
const feeds = {
input: new ort.Tensor("float32", rgbData, [1, 3, 224, 224]),
};
const t2 = Date.now();
const results = await imageSession.run(feeds);
log.info(
`onnx image embedding time: ${Date.now() - t1} ms (prep:${
t2 - t1
} ms, extraction: ${Date.now() - t2} ms)`,
);
const imageEmbedding = results["output"].data; // Float32Array
return normalizeEmbedding(imageEmbedding);
} catch (err) {
log.error("Failed to compute ONNX image embedding", err);
throw err;
}
}
export async function computeTextEmbedding(
model: Model,
text: string,
): Promise<Float32Array> {
try {
const embedding = computeTextEmbedding_(model, text);
return embedding;
} catch (err) {
if (isExecError(err)) {
const parsedExecError = parseExecError(err);
throw Error(parsedExecError);
} else {
throw err;
}
}
}
async function computeTextEmbedding_(
model: Model,
text: string,
): Promise<Float32Array> {
if (model === Model.GGML_CLIP) {
return await computeGGMLTextEmbedding(text);
} else {
return await computeONNXTextEmbedding(text);
}
}
export async function computeGGMLTextEmbedding(
text: string,
): Promise<Float32Array> {
try {
const clipModelPath = await getClipTextModelPath("ggml");
const ggmlclipPath = getGGMLClipPath();
const cmd = TEXT_EMBEDDING_EXTRACT_CMD.map((cmdPart) => {
if (cmdPart === GGMLCLIP_PATH_PLACEHOLDER) {
return ggmlclipPath;
} else if (cmdPart === CLIP_MODEL_PATH_PLACEHOLDER) {
return clipModelPath;
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
return text;
} else {
return cmdPart;
}
});
const { stdout } = await execAsync(cmd);
// parse stdout and return embedding
// get the last line of stdout
const lines = stdout.split("\n");
const lastLine = lines[lines.length - 1];
const embedding = JSON.parse(lastLine);
const embeddingArray = new Float32Array(embedding);
return embeddingArray;
} catch (err) {
if (err.message === CustomErrors.MODEL_DOWNLOAD_PENDING) {
log.info(CustomErrors.MODEL_DOWNLOAD_PENDING);
} else {
log.error("Failed to compute GGML text embedding", err);
}
throw err;
}
}
export async function computeONNXTextEmbedding(
text: string,
): Promise<Float32Array> {
try {
const imageSession = await getOnnxTextSession();
const t1 = Date.now();
const tokenizer = getTokenizer();
const tokenizedText = Int32Array.from(tokenizer.encodeForCLIP(text));
const feeds = {
input: new ort.Tensor("int32", tokenizedText, [1, 77]),
};
const t2 = Date.now();
const results = await imageSession.run(feeds);
log.info(
`onnx text embedding time: ${Date.now() - t1} ms (prep:${
t2 - t1
} ms, extraction: ${Date.now() - t2} ms)`,
);
const textEmbedding = results["output"].data; // Float32Array
return normalizeEmbedding(textEmbedding);
} catch (err) {
if (err.message === CustomErrors.MODEL_DOWNLOAD_PENDING) {
log.info(CustomErrors.MODEL_DOWNLOAD_PENDING);
} else {
logErrorSentry(err, "Error in computeONNXTextEmbedding");
}
throw err;
}
}
): Promise<Float32Array> => {
const imageSession = await getOnnxImageSession();
const t1 = Date.now();
const rgbData = await getRGBData(inputFilePath);
const feeds = {
input: new ort.Tensor("float32", rgbData, [1, 3, 224, 224]),
};
const t2 = Date.now();
const results = await imageSession.run(feeds);
log.info(
`onnx image embedding time: ${Date.now() - t1} ms (prep:${
t2 - t1
} ms, extraction: ${Date.now() - t2} ms)`,
);
const imageEmbedding = results["output"].data; // Float32Array
return normalizeEmbedding(imageEmbedding);
};
async function getRGBData(inputFilePath: string) {
const jpegData = await fs.readFile(inputFilePath);
let rawImageData;
try {
rawImageData = jpeg.decode(jpegData, {
useTArray: true,
formatAsRGBA: false,
});
} catch (err) {
logErrorSentry(err, "JPEG decode error");
throw err;
}
const rawImageData = jpeg.decode(jpegData, {
useTArray: true,
formatAsRGBA: false,
});
const nx: number = rawImageData.width;
const ny: number = rawImageData.height;
@@ -479,21 +371,7 @@ async function getRGBData(inputFilePath: string) {
return result;
}
export const computeClipMatchScore = async (
imageEmbedding: Float32Array,
textEmbedding: Float32Array,
) => {
if (imageEmbedding.length !== textEmbedding.length) {
throw Error("imageEmbedding and textEmbedding length mismatch");
}
let score = 0;
for (let index = 0; index < imageEmbedding.length; index++) {
score += imageEmbedding[index] * textEmbedding[index];
}
return score;
};
export const normalizeEmbedding = (embedding: Float32Array) => {
const normalizeEmbedding = (embedding: Float32Array) => {
let normalization = 0;
for (let index = 0; index < embedding.length; index++) {
normalization += embedding[index] * embedding[index];
@@ -504,3 +382,82 @@ export const normalizeEmbedding = (embedding: Float32Array) => {
}
return embedding;
};
export async function computeTextEmbedding(
model: Model,
text: string,
): Promise<Float32Array> {
if (!isModel(model)) throw new Error(`Invalid CLIP model ${model}`);
try {
const embedding = computeTextEmbedding_(model, text);
return embedding;
} catch (err) {
if (isExecError(err)) {
const parsedExecError = parseExecError(err);
throw Error(parsedExecError);
} else {
throw err;
}
}
}
async function computeTextEmbedding_(
model: Model,
text: string,
): Promise<Float32Array> {
switch (model) {
case "ggml-clip":
return await computeGGMLTextEmbedding(text);
case "onnx-clip":
return await computeONNXTextEmbedding(text);
}
}
export async function computeGGMLTextEmbedding(
text: string,
): Promise<Float32Array> {
const clipModelPath = await getClipTextModelPath("ggml");
const ggmlclipPath = getGGMLClipPath();
const cmd = TEXT_EMBEDDING_EXTRACT_CMD.map((cmdPart) => {
if (cmdPart === GGMLCLIP_PATH_PLACEHOLDER) {
return ggmlclipPath;
} else if (cmdPart === CLIP_MODEL_PATH_PLACEHOLDER) {
return clipModelPath;
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
return text;
} else {
return cmdPart;
}
});
const { stdout } = await execAsync(cmd);
// parse stdout and return embedding
// get the last line of stdout
const lines = stdout.split("\n");
const lastLine = lines[lines.length - 1];
const embedding = JSON.parse(lastLine);
const embeddingArray = new Float32Array(embedding);
return embeddingArray;
}
export async function computeONNXTextEmbedding(
text: string,
): Promise<Float32Array> {
const imageSession = await getOnnxTextSession();
const t1 = Date.now();
const tokenizer = getTokenizer();
const tokenizedText = Int32Array.from(tokenizer.encodeForCLIP(text));
const feeds = {
input: new ort.Tensor("int32", tokenizedText, [1, 77]),
};
const t2 = Date.now();
const results = await imageSession.run(feeds);
log.info(
`onnx text embedding time: ${Date.now() - t1} ms (prep:${
t2 - t1
} ms, extraction: ${Date.now() - t2} ms)`,
);
const textEmbedding = results["output"].data; // Float32Array
return normalizeEmbedding(textEmbedding);
}

View File

@@ -1,7 +1,6 @@
import pathToFfmpeg from "ffmpeg-static";
import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import { CustomErrors } from "../constants/errors";
import { writeStream } from "../main/fs";
import log from "../main/log";
import { execAsync } from "../main/util";
@@ -146,7 +145,7 @@ const promiseWithTimeout = async <T>(
} = { current: null };
const rejectOnTimeout = new Promise<null>((_, reject) => {
timeoutRef.current = setTimeout(
() => reject(Error(CustomErrors.WAIT_TIME_EXCEEDED)),
() => reject(new Error("Operation timed out")),
timeout,
);
});

View File

@@ -2,7 +2,7 @@ import StreamZip from "node-stream-zip";
import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { logError } from "../main/log";
import log from "../main/log";
import { ElectronFile } from "../types/ipc";
const FILE_STREAM_CHUNK_SIZE: number = 4 * 1024 * 1024;
@@ -115,7 +115,9 @@ export const getZipFileStream = async (
const inProgress = {
current: false,
};
// eslint-disable-next-line no-unused-vars
let resolveObj: (value?: any) => void = null;
// eslint-disable-next-line no-unused-vars
let rejectObj: (reason?: any) => void = null;
stream.on("readable", () => {
try {
@@ -179,7 +181,7 @@ export const getZipFileStream = async (
controller.close();
}
} catch (e) {
logError(e, "readableStream pull failed");
log.error("Failed to pull from readableStream", e);
controller.close();
}
},

View File

@@ -1,11 +1,10 @@
import { existsSync } from "fs";
import fs from "node:fs/promises";
import path from "path";
import { CustomErrors } from "../constants/errors";
import { writeStream } from "../main/fs";
import { logError, logErrorSentry } from "../main/log";
import log from "../main/log";
import { execAsync, isDev } from "../main/util";
import { ElectronFile } from "../types/ipc";
import { CustomErrors, ElectronFile } from "../types/ipc";
import { isPlatform } from "../utils/common/platform";
import { generateTempFilePath } from "../utils/temp";
import { deleteTempFile } from "./ffmpeg";
@@ -103,18 +102,21 @@ async function convertToJPEG_(
return new Uint8Array(await fs.readFile(tempOutputFilePath));
} catch (e) {
logErrorSentry(e, "failed to convert heic");
log.error("Failed to convert HEIC", e);
throw e;
} finally {
try {
await fs.rm(tempInputFilePath, { force: true });
} catch (e) {
logErrorSentry(e, "failed to remove tempInputFile");
log.error(`Failed to remove tempInputFile ${tempInputFilePath}`, e);
}
try {
await fs.rm(tempOutputFilePath, { force: true });
} catch (e) {
logErrorSentry(e, "failed to remove tempOutputFile");
log.error(
`Failed to remove tempOutputFile ${tempOutputFilePath}`,
e,
);
}
}
}
@@ -150,7 +152,7 @@ function constructConvertCommand(
},
);
} else {
throw Error(CustomErrors.INVALID_OS(process.platform));
throw new Error(`Unsupported OS ${process.platform}`);
}
return convertCmd;
}
@@ -187,7 +189,7 @@ export async function generateImageThumbnail(
try {
await deleteTempFile(inputFilePath);
} catch (e) {
logError(e, "failed to deleteTempFile");
log.error(`Failed to deleteTempFile ${inputFilePath}`, e);
}
}
}
@@ -217,13 +219,16 @@ async function generateImageThumbnail_(
} while (thumbnail.length > maxSize && quality > MIN_QUALITY);
return thumbnail;
} catch (e) {
logErrorSentry(e, "generate image thumbnail failed");
log.error("Failed to generate image thumbnail", e);
throw e;
} finally {
try {
await fs.rm(tempOutputFilePath, { force: true });
} catch (e) {
logErrorSentry(e, "failed to remove tempOutputFile");
log.error(
`Failed to remove tempOutputFile ${tempOutputFilePath}`,
e,
);
}
}
}
@@ -283,7 +288,7 @@ function constructThumbnailGenerationCommand(
return cmdPart;
});
} else {
throw Error(CustomErrors.INVALID_OS(process.platform));
throw new Error(`Unsupported OS ${process.platform}`);
}
return thumbnailGenerationCmd;
}

View File

@@ -0,0 +1,26 @@
import { safeStorage } from "electron/main";
import { keysStore } from "../stores/keys.store";
import { safeStorageStore } from "../stores/safeStorage.store";
import { uploadStatusStore } from "../stores/upload.store";
import { watchStore } from "../stores/watch.store";
export const clearElectronStore = () => {
uploadStatusStore.clear();
keysStore.clear();
safeStorageStore.clear();
watchStore.clear();
};
export async function setEncryptionKey(encryptionKey: string) {
const encryptedKey: Buffer = await safeStorage.encryptString(encryptionKey);
const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64");
safeStorageStore.set("encryptionKey", b64EncryptedKey);
}
export async function getEncryptionKey(): Promise<string> {
const b64EncryptedKey = safeStorageStore.get("encryptionKey");
if (b64EncryptedKey) {
const keyBuffer = Buffer.from(b64EncryptedKey, "base64");
return await safeStorage.decryptString(keyBuffer);
}
}

View File

@@ -1,10 +1,39 @@
import StreamZip from "node-stream-zip";
import path from "path";
import { getElectronFile } from "../services/fs";
import { uploadStatusStore } from "../stores/upload.store";
import { ElectronFile, FILE_PATH_TYPE } from "../types/ipc";
import { FILE_PATH_KEYS } from "../types/main";
import { getValidPaths, getZipFileStream } from "./fs";
export const getPendingUploads = async () => {
const filePaths = getSavedFilePaths(FILE_PATH_TYPE.FILES);
const zipPaths = getSavedFilePaths(FILE_PATH_TYPE.ZIPS);
const collectionName = uploadStatusStore.get("collectionName");
let files: ElectronFile[] = [];
let type: FILE_PATH_TYPE;
if (zipPaths.length) {
type = FILE_PATH_TYPE.ZIPS;
for (const zipPath of zipPaths) {
files = [
...files,
...(await getElectronFilesFromGoogleZip(zipPath)),
];
}
const pendingFilePaths = new Set(filePaths);
files = files.filter((file) => pendingFilePaths.has(file.path));
} else if (filePaths.length) {
type = FILE_PATH_TYPE.FILES;
files = await Promise.all(filePaths.map(getElectronFile));
}
return {
files,
collectionName,
type,
};
};
export const getSavedFilePaths = (type: FILE_PATH_TYPE) => {
const paths =
getValidPaths(

View File

@@ -19,6 +19,7 @@
* curl -v -H "Location;" -H "User-Agent: FooBar's so-called ""Browser""" "http://www.daveeddy.com/?name=dave&age=24"
Which is suitable for being executed by the shell.
*/
/* eslint-disable no-unused-vars */
declare module "any-shell-escape" {
declare const shellescape: (args: readonly string | string[]) => string;
export default shellescape;

View File

@@ -4,6 +4,32 @@
* This file is manually kept in sync with the renderer code.
* See [Note: types.ts <-> preload.ts <-> ipc.ts]
*/
/**
* Errors that have special semantics on the web side.
*
* [Note: Custom errors across Electron/Renderer boundary]
*
* We need to use the `message` field to disambiguate between errors thrown by
* the main process when invoked from the renderer process. This is because:
*
* > Errors thrown throw `handle` in the main process are not transparent as
* > they are serialized and only the `message` property from the original error
* > is provided to the renderer process.
* >
* > - https://www.electronjs.org/docs/latest/tutorial/ipc
* >
* > Ref: https://github.com/electron/electron/issues/24427
*/
export const CustomErrors = {
WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED:
"Windows native image processing is not supported",
UNSUPPORTED_PLATFORM: (platform: string, arch: string) =>
`Unsupported platform - ${platform} ${arch}`,
MODEL_DOWNLOAD_PENDING:
"Model download pending, skipping clip search request",
};
/**
* Deprecated - Use File + webUtils.getPathForFile instead
*
@@ -45,6 +71,7 @@ export interface WatchStoreType {
}
export enum FILE_PATH_TYPE {
/* eslint-disable no-unused-vars */
FILES = "files",
ZIPS = "zips",
}
@@ -54,7 +81,6 @@ export interface AppUpdateInfo {
version: string;
}
export enum Model {
GGML_CLIP = "ggml-clip",
ONNX_CLIP = "onnx-clip",
}
export type Model = "ggml-clip" | "onnx-clip";
export const isModel = (s: unknown) => s == "ggml-clip" || s == "onnx-clip";

View File

@@ -18,6 +18,7 @@ export interface KeysStoreType {
};
}
/* eslint-disable no-unused-vars */
export const FILE_PATH_KEYS: {
[k in FILE_PATH_TYPE]: keyof UploadStoreType;
} = {

View File

@@ -202,6 +202,10 @@ export const sidebar = [
{
text: "Troubleshooting",
items: [
{
text: "Uploads",
link: "/self-hosting/troubleshooting/uploads",
},
{
text: "Yarn",
link: "/self-hosting/troubleshooting/yarn",
@@ -219,80 +223,3 @@ export const sidebar = [
link: "/about/contribute",
},
];
function sidebarOld() {
return [
{
text: "Welcome",
items: [
{
text: "Features",
collapsed: true,
items: [
{
text: "Family Plan",
link: "/photos/features/family-plan",
},
{ text: "Albums", link: "/photos/features/albums" },
{ text: "Archive", link: "/photos/features/archive" },
{ text: "Hidden", link: "/photos/features/hidden" },
{ text: "Map", link: "/photos/features/map" },
{
text: "Location Tags",
link: "/photos/features/location",
},
{
text: "Collect Photos",
link: "/photos/features/collect",
},
{
text: "Public links",
link: "/photos/features/public-links",
},
{
text: "Quick link",
link: "/photos/features/quick-link",
},
{
text: "Watch folder",
link: "/photos/features/watch-folders",
},
{ text: "Trash", link: "/photos/features/trash" },
{
text: "Uncategorized",
link: "/photos/features/uncategorized",
},
{
text: "Referral Plan",
link: "/photos/features/referral",
},
{
text: "Live & Motion Photos",
link: "/photos/features/live-photos",
},
{ text: "Cast", link: "/photos/features/cast" },
],
},
{
text: "Troubleshoot",
collapsed: true,
link: "/photos/troubleshooting/files-not-uploading",
items: [
{
text: "Files not uploading",
link: "/photos/troubleshooting/files-not-uploading",
},
{
text: "Failed to play video",
link: "/photos/troubleshooting/video-not-playing",
},
{
text: "Report bug",
link: "/photos/troubleshooting/report-bug",
},
],
},
],
},
];
}

View File

@@ -12,15 +12,17 @@ in a local drive or NAS of your choice. This way, you can use Ente in your day
to day use, but will have an additional guarantee that a copy of your original
photos and videos are always available in normal directories and files.
* You can use [Ente's CLI](https://github.com/ente-io/ente/tree/main/cli#export)
to export your data in a cron job to a location of your choice. The exports
are incremental, and will also gracefully handle interruptions.
- You can use
[Ente's CLI](https://github.com/ente-io/ente/tree/main/cli#export) to export
your data in a cron job to a location of your choice. The exports are
incremental, and will also gracefully handle interruptions.
* Similarly, you can use Ente's [desktop app](https://ente.io/download/desktop)
to export your data to a folder of your choice. The desktop app also supports
"continuous" exports, where it will automatically export new items in the
background without you needing to run any other cron jobs. See
[migration/export](/photos/migration/export/) for more details.
- Similarly, you can use Ente's
[desktop app](https://ente.io/download/desktop) to export your data to a
folder of your choice. The desktop app also supports "continuous" exports,
where it will automatically export new items in the background without you
needing to run any other cron jobs. See
[migration/export](/photos/migration/export/) for more details.
## Does the exported data from Ente photos preserve the same folder and album structure as in the app?

View File

@@ -77,26 +77,35 @@ It's like cafe 😊. kaf-_ay_. en-_tay_.
## Does Ente apply compression to uploaded photos?
Ente does not apply compression to uploaded photos. The file size of your photos in Ente will be similar to the original file sizes you have.
Ente does not apply compression to uploaded photos. The file size of your photos
in Ente will be similar to the original file sizes you have.
## Can I add photos from a shared album to albums that I created in Ente?
Currently, Ente does not support adding photos from a shared album to your personal albums. If you want to include photos from a shared album in your own albums, you will need to ask the owner of the photos to add them to your album.
Currently, Ente does not support adding photos from a shared album to your
personal albums. If you want to include photos from a shared album in your own
albums, you will need to ask the owner of the photos to add them to your album.
## How do I ensure that the Ente desktop app stays up to date on my system?
Ente desktop includes an auto-update feature, ensuring that whenever updates are deployed, the app will automatically download and install them. You don't need to manually update the software.
Ente desktop includes an auto-update feature, ensuring that whenever updates are
deployed, the app will automatically download and install them. You don't need
to manually update the software.
## Can I sync a folder containing multiple subfolders, each representing an album?
Yes, when you drag and drop the folder onto the desktop app, the app will detect the multiple folders and prompt you to choose whether you want to create a single album or separate albums for each folder.
Yes, when you drag and drop the folder onto the desktop app, the app will detect
the multiple folders and prompt you to choose whether you want to create a
single album or separate albums for each folder.
## What is the difference between **Magic** and **Content** search results on the desktop?
**Magic** is where you can search for long queries. Like, "baby in red dress", or "dog playing at the beach".
**Magic** is where you can search for long queries. Like, "baby in red dress",
or "dog playing at the beach".
**Content** is where you can search for single-words. Like, "car" or "pizza".
## How do I identify which files experienced upload issues within the desktop app?
Check the sections within the upload progress bar for "Failed Uploads," "Ignored Uploads," and "Unsuccessful Uploads."
Check the sections within the upload progress bar for "Failed Uploads," "Ignored
Uploads," and "Unsuccessful Uploads."

View File

@@ -159,4 +159,6 @@ We do offer a generous free trial for you to experience the product.
## Will I need to pay for Ente Auth after my Ente Photos free plan expires?
No, you will not need to pay for Ente Auth after your Ente Photos free plan expires. Ente Auth is completely free to use, and the expiration of your Ente Photos free plan will not impact your ability to access or use Ente Auth.
No, you will not need to pay for Ente Auth after your Ente Photos free plan
expires. Ente Auth is completely free to use, and the expiration of your Ente
Photos free plan will not impact your ability to access or use Ente Auth.

View File

@@ -13,7 +13,7 @@ videos you have uploaded to Ente.
![Ente - Sign in to export data](sign-in.png)
2. Open the side bar, and select the option to **export data**.
2. Open the side bar, and select the option to **Export Data**.
![Ente - Export data](export-1.png)
@@ -33,7 +33,7 @@ videos you have uploaded to Ente.
</div>
5. Wait for the export to get completed.
5. Wait for the export to complete.
<div align="center">
@@ -42,7 +42,7 @@ videos you have uploaded to Ente.
</div>
6. In case your download gets interrupted, Ente will resume from where it left
off. Simply select **export data** again and click on **Resync**.
off. Simply select **Export Data** again and click on **Resync**.
<div align="center">
@@ -50,18 +50,20 @@ videos you have uploaded to Ente.
</div>
7. **Sync continuously** : You can utilize Continuous Sync to eliminate manual
exports each time new photos are added to Ente. This feature automatically
detects new files and runs exports accordingly, It also ensures that exported
data reflects the latest album states with new files, moves, and deletions.
### Sync continuously
![Ente - Continuous sync](continuous-sync.webp)
You can switch on the toggle to **Sync continuously** to eliminate manual
exports each time new photos are added to Ente. This feature automatically
detects new files and runs exports accordingly. It also ensures that exported
data reflects the latest album states with new files, moves, and deletions.
![Ente - Continuous sync](continuous-sync.webp)
---
If you run into any issues during your data export, please reach out to
[support@ente.io](mailto:support@ente.io) and we will be happy to help you!
Note that we also provide a [CLI
tool](https://github.com/ente-io/ente/tree/main/cli#export) to export your data.
Some more details are in this [FAQ entry](/photos/faq/export).
Please find more details [here](/photos/faq/export).

View File

@@ -24,18 +24,32 @@ and subsequently increase the
[storage and account validity](https://github.com/ente-io/ente/blob/main/cli/docs/generated/ente_admin_update-subscription.md)
using the CLI.
For the admin actions, you can create `server/museum.yaml`, and whitelist add
the admin userID `internal.admins`. See
[local.yaml](https://github.com/ente-io/ente/blob/main/server/configurations/local.yaml#L211C1-L232C1)
For security purposes, we need to whitelist the user IDs that can perform admin
actions on the server. To do this,
- Create a `museum.yaml` in the directory where you're starting museum from.
For example, if you're running using `docker compose up`, then this file
should be in the same directory as `compose.yaml` (generally,
`server/museum.yaml`).
> Docker might've created an empty `museum.yaml` _directory_ on your machine
> previously. If so, delete that empty directory and create a new file named
> `museum.yaml`.
- In this `museum.yaml` we can add overrides over the default configuration.
For whitelisting the admin userIDs we need to define an `internal.admins`. See
the "internal" section in
[local.yaml](https://github.com/ente-io/ente/blob/main/server/configurations/local.yaml)
in the server source code for details about how to define this.
```yaml
....
internal:
admins:
# - 1580559962386440
Here is an example. Suppose we wanted to whitelist a user with ID
`1580559962386440`, we can create the following `museum.yaml`
....
```yaml
internal:
admins:
- 1580559962386440
```
You can use

View File

@@ -26,8 +26,8 @@ docker compose up --build
> [!TIP]
>
> You can also use a pre-built Docker image from `ghcr.io/ente-io/server` ([More
> info](https://github.com/ente-io/ente/blob/main/server/docs/docker.md))
> You can also use a pre-built Docker image from `ghcr.io/ente-io/server`
> ([More info](https://github.com/ente-io/ente/blob/main/server/docs/docker.md))
Then in a separate terminal, you can run (e.g) the web client

View File

@@ -0,0 +1,13 @@
---
title: Uploads failing
description: Fixing upload errors when trying to self host Ente
---
# Uploads failing
If uploads to your self-hosted server are failing, make sure that
`credentials.yaml` has `yourserverip:3200` for all three minio locations.
By default it is `localhost:3200`, and it needs to be changed to an IP that is
accessible from both where you are running the Ente clients (e.g. the mobile
app) and also from within the Docker compose cluster.

View File

@@ -0,0 +1,54 @@
# Listmonk
We use [Listmonk](https://listmonk.app/) to manage our mailing lists.
- Museum lets Listmonk know about new users and account deletion (this allows
Listmonk to create corresponding accounts).
- Subsequently, Listmonk handles user subscription / unsubscription etc
(Listmonk stores its data in an external Postgres).
## Installing
Install [nginx](../nginx/README.md).
Add Listmonk's configuration.
```sh
sudo mkdir -p /root/listmonk
sudo tee /root/listmonk/config.toml
```
Add the service definition and nginx configuration.
```sh
scp services/listmonk/listmonk.* <instance>:
sudo mv listmonk.service /etc/systemd/system/
sudo mv listmonk.nginx.conf /root/nginx/conf.d
```
> The very first time we ran Listmonk, at this point we also needed to get it to
> install the tables it needs in the Postgres DB. For this, we used the
> `initialize-db.sh` script.
>
> ```sh
> scp services/listmonk/initialize-db.sh <instance>:
>
> sudo sh initialize-db.sh
> rm initialize-db.sh
> ```
Tell systemd to pick up new service definitions, enable the unit (so that it
automatically starts on boot), and start it this time around.
```sh
sudo systemctl daemon-reload
sudo systemctl enable --now listmonk
```
Tell nginx to pick up the new configuration.
```sh
sudo systemctl reload nginx
```

View File

@@ -0,0 +1,14 @@
#!/bin/sh
# This script needs to be manually run the once (and only once) before starting
# Listmonk for the first time. It uses the provided credentials to initialize
# its database.
set -o errexit
set -o xtrace
docker pull listmonk/listmonk
docker run -it --rm --name listmonk \
-v /root/listmonk/config.toml:/listmonk/config.toml:ro \
listmonk/listmonk ./listmonk --install

View File

@@ -0,0 +1,26 @@
# This file gets loaded in a top level http block by the default nginx.conf
# See infra/services/nginx/README.md for more details.
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;
server_name lists.ente.io;
location / {
proxy_pass http://host.docker.internal:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Use HTTP/1.1 when talking to upstream
# Also, while not necessary (AFAIK), also allow websockets.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

View File

@@ -0,0 +1,19 @@
[Unit]
Documentation=https://listmonk.app/docs/installation/
Requires=docker.service
After=docker.service
[Install]
WantedBy=multi-user.target
[Service]
ExecStartPre=docker pull listmonk/listmonk
ExecStartPre=-docker stop listmonk
ExecStartPre=-docker rm listmonk
ExecStartPre=-docker run --rm --name listmonk \
-v /root/listmonk/config.toml:/listmonk/config.toml:ro \
listmonk/listmonk ./listmonk --upgrade --yes
ExecStart=docker run --name listmonk \
-p 9000:9000 \
-v /root/listmonk/config.toml:/listmonk/config.toml:ro \
listmonk/listmonk

View File

@@ -62,3 +62,12 @@ We can see this in the default configuration of nginx:
This is a [handy tool](https://nginx-playground.wizardzines.com) to check the
syntax of the configuration files. Alternatively, you can run `docker exec nginx
nginx -t` on the instance to ask nginx to check the configuration.
## Updating configuration
Nginx configuration files can be changed without needing to restart anything.
1. Update the configuration file at `/root/nginx/conf.d/museum.conf`
2. Verify that there are no errors in the configuration by using `sudo docker
exec nginx nginx -t`.
3. Ask nginx to reload the configuration `sudo systemctl reload nginx`.

View File

@@ -2,8 +2,9 @@
# See infra/services/nginx/README.md for more details.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;

View File

@@ -0,0 +1,36 @@
ente ist eine einfache App, um Ihre Fotos und Videos automatisch zu sichern und zu organisieren.
Wenn Sie auf der Suche nach einer datenschutzfreundlichen Alternative zu Google Fotos sind, sind Sie an der richtigen Stelle. Mit Ente werden Ihre Fotos Ende-zu-Ende-verschlüsselt gespeichert (e2ee). Dies bedeutet, dass nur Sie sie sehen können.
Ihre Fotos werden verschlüsselt (e2ee) zwischen allen Geräten synchronisiert.
ente ermöglicht es, deine Alben simpel & schnell mit deinen Geliebten zu teilen. Sie können öffentlich einsehbare Links teilen, sodass andere sogar ohne einen Account oder eine App Ihr Album sehen und darin zusammenarbeiten können, indem sie Fotos hinzufügen.
Ihre verschlüsselten Daten werden an 3 verschiedenen Orten gespeichert, unter anderem in einem Schutzbunker in Paris. Wir nehmen die Erhaltung der Nachwelt ernst und machen es Ihnen leicht, dafür zu sorgen, dass Ihre Erinnerungen Sie überdauern.
Wir sind hier, um die sicherste Foto-App aller Zeiten zu entwickeln, begleite uns auf unserem Weg!
FEATURES
- Sicherungen in Originalqualität, weil jeder Pixel zählt
- Familien-Abos, damit Sie den Speicherplatz mit Ihrer Familie teilen können
- Kollaborative Alben, sodass Sie nach einer Reise Fotos sammeln können
- Geteilte Ordner für den Fall, dass Ihr Partner Ihre "Kamera" Klicks genießen soll
- Album-Links, die mit einem Passwort geschützt werden können
- Möglichkeit, Speicherplatz freizugeben, indem bereits gesicherte Daten auf dem Gerät entfernt werden
- Menschlicher Support, denn Sie sind es wert
- Beschreibungen, damit Sie Ihre Erinnerungen beschriften und leicht wiederfinden können
- Foto-Editor, um Ihren Fotos den Feinschliff zu verpassen
- Favorisieren, verstecken und erleben Sie Ihre Erinnerungen, denn sie sind kostbar
- Ein-Klick-Import von Google, Apple, Ihrer Festplatte und mehr
- Dunkles Theme, weil Ihre Fotos darin gut aussehen
- 2FA, 3FA, biometrische Authentifizierung
- und noch VIELES mehr!
BERECHTIGUNGEN
Diese können unter folgendem Link überprüft werden: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
PREIS
Wir bieten keine lebenslang kostenlosen Abonnements an, da es für uns wichtig ist, einen nachhaltigen Service anzubieten. Wir bieten jedoch bezahlbare Abonemments an, welche auch mit der Familie geteilt werden können. Mehr Informationen sind auf ente.io zu finden.
SUPPORT
Wir sind stolz darauf, einen persönlichen Support anzubieten. Falls Sie ein Abonnement besitzen, können Sie sich mit Ihrem Anliegen via E-Mail an team@ente.io wenden und erhalten eine Antwort innerhalb von 24 Stunden.

View File

@@ -0,0 +1 @@
ente ist eine Ende-zu-Ende-verschlüsselte Fotospeicher-App

View File

@@ -0,0 +1 @@
ente - verschlüsselter Fotospeicher

View File

@@ -0,0 +1,36 @@
Ente is a simple app to backup and share your photos and videos.
If you've been looking for a privacy-friendly alternative to Google Photos, you've come to the right place. With Ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them.
We have open-source apps across Android, iOS, web and desktop, and your photos will seamlessly sync between all of them in an end-to-end encrypted (e2ee) manner.
Ente also makes it simple to share your albums with your loved ones, even if they aren't on Ente. You can share publicly viewable links, where they can view your album and collaborate by adding photos to it, even without an account or app.
Your encrypted data is replicated to 3 different locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
We are here to make the safest photos app ever, come join our journey!
FEATURES
- Original quality backups, because every pixel is important
- Family plans, so you can share storage with your family
- Collaborative albums, so you can pool together photos after a trip
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
- Album links, that can be protected with a password
- Ability to free up space, by removing files that have been safely backed up
- Human support, because you're worth it
- Descriptions, so you can caption your memories and find them easily
- Image editor, to add finishing touches
- Favorite, hide and relive your memories, for they are precious
- One-click import from Google, Apple, your hard drive and more
- Dark theme, because your photos look good in it
- 2FA, 3FA, biometric auth
- and a LOT more!
PERMISSIONS
Ente requests for certain permissions to serve the purpose of a photo storage provider, which can be reviewed here: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
PRICING
We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io.
SUPPORT
We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -0,0 +1 @@
Ente Photos is an open source photos app, that provides end-to-end encrypted backups for your photos and videos.

View File

@@ -0,0 +1 @@
Ente Photos - Open source, end-to-end encrypted alternative to Google Photos

View File

@@ -0,0 +1,36 @@
ente es una aplicación simple para hacer copias de seguridad y compartir tus fotos y videos.
Si has estado buscando una alternativa a Google Photos que sea amigable con la privacidad, has llegado al lugar correcto. Con Ente, se almacenan cifradas de extremo a extremo (e2ee). Esto significa que solo tú puedes verlas.
Tenemos aplicaciones en Android, iOS, web y escritorio, y tus fotos se sincronizarán perfectamente entre todos tus dispositivos encriptadas de extremo a extremo (e2ee).
ente también hace fácil compartir tus álbumes con tus seres queridos, incluso si no están en ente. Puedes compartir enlaces visibles públicamente, donde pueden ver tu álbum y colaborar añadiendo fotos a él, incluso sin una cuenta o aplicación.
Sus datos cifrados se replican en 3 ubicaciones diferentes, incluyendo un bunker en París. Nos tomamos la posteridad en serio y facilitamos que sus recuerdos sobrevivan a usted.
Estamos aquí para hacer la aplicación de fotos más segura jamás creada, ¡únete a nuestro viaje!
CARACTERÍSTICAS
- Copias de seguridad con la calidad original, porque cada pixel es importante
- Planes familiares, para que puedas compartir el almacenamiento con tu familia
- Álbumes colaborativos, para que puedas juntar fotos después de un viaje
- Carpetas compartidas, por si quieres que tu pareja disfrute de tus fotos
- Enlaces al álbum, que se pueden proteger con una contraseña
- Capacidad para liberar espacio, eliminando archivos de los que ya tienes una copia de seguridad
- Apoyo humano, porque tú lo vales
- Descripciones, para que puedas encontrar tus recuerdos fácilmente
- Editor de imagen, para añadir retoques finales
- Marca como favoritos, oculta y revive tus recuerdos, porque son preciosos
- Importa en un click desde Google, Apple, tu disco duro y más
- Tema oscuro, porque tus fotos quedan bien con él
- 2FA, 3FA, autenticación biométrica
- ¡Y mucho más!
PERMISOS
ente solicita ciertos permisos para servir al propósito de un proveedor de almacenamiento de fotos, que puede ser revisado aquí: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
PRECIOS
No ofrecemos planes gratis para siempre, porque es importante para nosotros seguir siendo sostenibles y resistir a la prueba del tiempo. En su lugar, ofrecemos planes asequibles que puedes compartir libremente con tu familia. Puedes encontrar más información en ente.io.
SOPORTE
Estamos orgullosos de ofrecer apoyo humano. Si eres un cliente de pago, puedes contactar con team@ente.io y esperar una respuesta de nuestro equipo en 24 horas.

View File

@@ -0,0 +1 @@
ente es una aplicación de almacenamiento de fotos cifrado de extremo a extremo

View File

@@ -0,0 +1 @@
ente - almacenamiento de fotos encriptado

View File

@@ -0,0 +1,36 @@
entre est une application simple qui permet de sauvegarder et partager vos photos et vidéos.
Si vous êtes à la recherche d'une alternative à Google Photos respectueuse de la vie privée, vous êtes au bon endroit. Avec ente, ils sont stockés chiffrés de bout-en-bout (e2ee). Cela signifie que vous-seul pouvez les voir.
Nous avons des applications sur Android, iOS, Web et Ordinateur, et vos photos seront synchronisées de manière transparente entre tous vos appareils chiffrée de bout en bout (e2ee).
ente vous permet également de partager vos albums avec vos proches, même s'ils ne sont pas sur ente. Vous pouvez partager des liens visibles publiquement, où ils peuvent voir votre album et collaborer en y ajoutant des photos, même sans compte ou application.
Vos données chiffrées sont répliqué à 3 endroits différents, dont un abri antiatomique à Paris. Nous prenons la postérité au sérieux et facilitons la conservation de vos souvenirs.
Nous sommes là pour faire l'application photo la plus sûre de tous les temps, rejoignez-nous !
CARACTÉRISTIQUES
- Sauvegardes de qualité originales, car chaque pixel est important
- Abonnement familiaux, pour que vous puissiez partager l'espace de stockage avec votre famille
- Albums collaboratifs, pour que vous puissiez regrouper des photos après un voyage
- Dossiers partagés, si vous voulez que votre partenaire profite de vos clichés
- Liens ves les albums qui peuvent être protégés par un mot de passe
- Possibilité de libérer de l'espace en supprimant les fichiers qui ont été sauvegardés en toute sécurité
- Support humain, car vous en valez la peine
- Descriptions, afin que vous puissiez légender vos souvenirs et les retrouver facilement
- Éditeur d'images, pour ajouter des touches de finition
- Favoriser, cacher et revivre vos souvenirs, car ils sont précieux
- Importation en un clic depuis Google, Apple, votre disque dur et plus encore
- Thème sombre, parce que vos photos y sont jolies
- 2FA, 3FA, authentification biométrique
- et beaucoup de choses encore !
PERMISSIONS
ente sollicite diverses autorisations dans le but de fonctionner en tant que service de stockage de photos, et ces autorisations sont détaillées ici : https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
PRIX
Nous ne proposons pas d'abonnement gratuits pour toujours, car il est important pour nous de rester durables et de résister à l'épreuve du temps. Au lieu de cela, nous vous proposons des abonnements abordables que vous pouvez partager librement avec votre famille. Vous pouvez trouver plus d'informations sur ente.io.
ASSISTANCE
Nous sommes fiers d'offrir un support humain. Si vous êtes un abonné, vous pouvez contacter team@ente.io et vous recevrez une réponse de notre équipe dans les 24 heures.

View File

@@ -0,0 +1 @@
ente est une application de stockage de photos chiffrées de bout en bout

View File

@@ -0,0 +1 @@
ente - stockage de photos chiffré

View File

@@ -0,0 +1,36 @@
האפליקציה Ente היא אפליקציה פשוטה לגיבוי ושיתוף של התמונות והסרטונים שלך.
אם חיפשת אלטרנטיבה ידידותית לפרטיות לGoogle Photos, הגעת למקום הנכון. עם Ente, התמונות והסרטונים מאוחסנים בצורה מאובטחת באמצעות הצפנה קצה-אל-קצה (e2ee). זה אומר שרק אתה יכול לצפות בהם.
יש לנו אפלקציות קוד פתוח זמינות לAndroid, iOS, רשת ולמחשב, וכל התמונות שלך ייסתנכרנו באופן חלק בין כולם באופן מאובטח על ידי הצפנה קצה-אל-קצה (e2ee).
ente גם מקל על שיתוף האלבומים שלך עם קרובך, גם אם הם אינם ב-ente. תוכל לשתף קישורים שניתן לצפות בהם בצורה פומבית, שבאמצעותם יתאפשר להם לצפות באלבום שלך ולשתף פעולה על ידי הוספת תמונות אליו, גם בלי חשבון או האפליקציה.
הנתונים המוצפנים שלך מאוחסנים ב3 מקומות שונים, כולל מקלט גרעיני בפריז. אנחנו מתייחסים ברצינות לעתידות ומקלים עליך לוודא שזכרונותיך ישרדו אחרייך.
הגענו לכאן כדי ליצור את היישומון לתמונות המאובטח ביותר אי פעם, הצטרפו אלינו למסע!
מאפיינים
- גיבויים באיכות המקורית, כי כל פיקסל חשוב
- תוכניות משפחתיות, כך שתוכלו לשתף אחסון עם המשפחה שלכם
- אלבומים משותפים, כך שתוכל לאגד יחד תמונות אחרי טיול
- תיקיות משותפות, במקרה ותרצה שהבן זוג שלך יהנה מהקליקים של ה"מצלמה" שלך
- קישורי אלבום, המאובטחים בעזרת סיסמא
- יכולת לשחרר מקום, על ידי הסרת קבצים שכבר גובו באופן מאובטח
- תמיכה אנושית, כי אתה שווה את זה
- תיאורים, כך שתוכל לתאר את הזכרונות שלך ולמצוא אותם בקלות
- עורך תמונות, להוסיף למראה הסופי
- סמן כמועדפים, הסתר ולחזור על זכרונות שלך, כי הם יקרים ללבך
- ייבוא בלחיצה אחת מ-Google, Apple, הכונן הקשיח שלך ועוד
- ערכת נושא כהה, כי התמונות שלך נראות יפות בה
- 2FA, 3FA, אימות ביומטרי
- ועוד הרבה יותר!
הרשאות
ente מבקש הרשאות מסוימות כדי לספק שירותי אחסון תמונות, וניתן לסקור אותן כאן: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
מחיר
אנחנו לא מציעים תוכניות בחינם לתמיד, משום שזה חשוב לנו להיות עמידים ולעמוד במבחן הזמן. במקום זאת אנחנו מציעים תוכניות במחיר סביר כדי שתוכל לשתף באופן חופשי עם המשפחה שלך. ניתן למצוא עוד מידע ב-ente.io.
תמיכה
אנחנו גאים להציע תמיכה אנושית. אם אתה לקום משלם, אתה יכול לפנות אלינו בכתובת team@ente.io ולצפות לתשובה תוך 24 שעות.

View File

@@ -0,0 +1 @@
ente הוא אפליקציה לאחסון תמונות המשתמשת בהצפנה קצה-אל-קצה

View File

@@ -0,0 +1 @@
ente - אחסון תמונות באופן מוצפן

View File

@@ -0,0 +1,36 @@
ente è una semplice app per il backup e la condivisione di foto e video.
Se siete alla ricerca di un'alternativa rispettosa della privacy a Google Photos, siete nel posto giusto. Con ente, sono memorizzati con crittografia end-to-end (e2ee). Questo significa che solo tu puoi vederli.
Abbiamo applicazioni open-source su Android, iOS, web e desktop, e le tue foto saranno sincronizzate tra tutti i dispositivi utilizzando la crittografia end-to-end (e2ee).
ente rende anche semplice condividere i tuoi album con i tuoi cari, anche se non sono utenti ente. Puoi condividere link visualizzabili pubblicamente, dove possono visualizzare il tuo album e collaborare aggiungendo le foto, anche senza un account o un'app installata.
I tuoi dati crittografati vengono replicati in 3 luoghi diversi, tra cui un rifugio antiatomico a Parigi. I tuoi ricordi continueranno a vivere anche quando non ci sarai più.
Siamo qui per creare l'app per la gestione di foto e video più sicura di sempre, unisciti al nostro viaggio!
CARATTERISTICHE
- Backup di qualità originale, perché ogni pixel è importante
- Piani famiglia, in modo da poter condividere lo spazio disponibile con la tua famiglia
- Album collaborativi, per poter mettere insieme le foto dopo un viaggio
- Cartelle condivise, nel caso in cui desideri condividere le tue foto subito con il tuo o la tua partner
- Collegamenti di album, che possono essere anche protetti con una password
- Possibilità di liberare spazio, rimuovendo i file che sono stati salvati in modo sicuro
- Supporto umano, perché ne vale la pena
- Descrizioni, in modo da poter descrivere i tuoi ricordi e trovarli facilmente
- Editor di immagini, per ritocchi finali
- Preferiti, nascondi e rivivi i tuoi ricordi, perché sono preziosi
- Importa da Google, Apple o dal tuo hard disk con un semplice clic
- Tema scuro, per valorizzare le tue foto
- 2FA, 3FA, Autenticazione biometrica
- e molto altro ancora!
PERMESSI
ente richiede alcune autorizzazioni per servire lo scopo di un provider di storage fotografico, che può essere esaminato qui: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
PREZZO
Non offriamo piani gratuiti per sempre, perché per noi è importante rimanere sostenibili e resistere alla prova del tempo. Offriamo invece piani accessibili che si possono condividere liberamente con la propria famiglia. Puoi trovare maggiori informazioni su ente.io.
SUPPORTO
Siamo orgogliosi di offrire supporto umano. Se sei un nostro cliente a pagamento, puoi contattare team@ente.io e aspettarti una risposta dal nostro team entro 24 ore.

View File

@@ -0,0 +1 @@
ente è un'applicazione di archiviazione foto e video crittografata end-to-end

View File

@@ -0,0 +1 @@
ente - archivio fotografico crittografato

View File

@@ -0,0 +1,36 @@
ente is een eenvoudige app om jouw foto's en video's automatisch te back-uppen en delen.
Als je op zoek bent naar een privacy-vriendelijk alternatief voor Google Photos, dan ben je hier op de juiste plaats. Bij ente worden ze end-to-end encrypted (e2ee). Dit betekent dat alleen jij ze kunt bekijken.
We hebben open-source apps op Android, iOS, web en Desktop, en je foto's zullen naadloos synchroniseren tussen al je apparaten op een end-to-end versleutelde (e2ee) manier.
ente maakt het ook simpeler om album te delen met je dierbaren, zelfs als die ente niet gebruiken. Je kunt openbaar zichtbare links delen, waar anderen jouw album kunnen bekijken en er foto's aan toe kunnen voegen, zelfs zonder account of app.
Jouw versleutelde gegevens worden drievoudig opgeslagen op meerdere locaties, waaronder een kernbunker in Parijs. Wij nemen opslag voor de lange termijn serieus, en zorgen ervoor dat je herinneringen minstens je hele leven bewaard worden.
Ons doel is om de veiligste foto app ooit te maken, sluit je bij ons aan!
FUNCTIES
- Backups van originele kwaliteit, omdat elke pixel belangrijk is
- Familieplannen, zodat je de opslag kunt delen met je familie
- Gezamenlijke albums, zodat je foto's kunt samenvoegen na een reis
- Gedeelde mappen, voor het geval je jouw partner wilt laten meegenieten van jouw "Camera" klikjes
- Album links, die met een wachtwoord beschermd kunnen worden
- Mogelijkheid om ruimte vrij te maken op je apparaat, door bestanden die veilig zijn geback-upt te verwijderen
- Menselijke klantenservice, omdat je het waard bent
- Beschrijvingen, zodat je je herinneringen kunt bijhouden en ze gemakkelijk kunt vinden
- Fotobewerker om de laatste finishing touches toe te voegen
- Favorieten, verbergen en herleven van je herinneringen, want ze zijn kostbaar
- Met één klik importeren vanuit Google, Apple, je harde schijf en meer
- Donker thema, omdat je foto's er goed in uit zien
- 2FA, 3FA, biometrische authenticatie
- en nog veel meer!
TOESTEMMINGEN
ente heeft bepaalde machtigingen nodig om uw foto's op te slaan, die hier bekeken kunnen worden: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
PRIJZEN
We bieden geen oneindig gratis plannen aan, omdat het voor ons belangrijk is dat we duurzaam blijven en de tand des tijds weerstaan. In plaats daarvan bieden we betaalbare plannen aan die je vrij kunt delen met je familie. Je kunt meer informatie vinden op ente.io.
KLANTENSERVICE
Wij zijn trots op het bieden van menselijke klantenservice. Als je een betaalde klant bent, kun je contact opnemen met team@ente.io en binnen 24 uur een antwoord van ons verwachten.

View File

@@ -0,0 +1 @@
ente is een end-to-end versteutelde app voor foto opslag

View File

@@ -0,0 +1 @@
ente - versleutelde foto opslag

Some files were not shown because too many files have changed in this diff Show More