Compare commits

...

84 Commits

Author SHA1 Message Date
laurenspriem
e5795198c8 More crash logging 2025-06-10 15:15:28 +05:30
laurenspriem
eed12c2089 Merge branch 'internal-15_06_2025' into usearch_again 2025-06-09 12:34:55 +05:30
laurenspriem
889aed6024 Bump for internal release 2025-06-09 12:34:29 +05:30
laurenspriem
ac7840cbfd Merge branch 'internal-15_06_2025' into usearch_again 2025-06-09 12:33:03 +05:30
laurenspriem
1f1304ca5b Upgrade usearch to fix Armv8-R issues 2025-06-09 12:31:38 +05:30
Neeraj Gupta
94098d8a07 Bump version 2025-06-06 12:35:30 +05:30
Neeraj Gupta
4b9c5fcb73 Merge branch 'internal-15_06_2025' of https://github.com/ente-io/auth into internal-15_06_2025 2025-06-06 12:33:48 +05:30
Neeraj Gupta
6ed16e5e02 Merge branch 'main' into internal-15_06_2025 2025-06-06 12:33:21 +05:30
laurenspriem
82a8e504af Merge branch 'internal-15_06_2025' into usearch_again 2025-06-04 22:14:26 +05:30
ashilkn
cc1660d9af bump up build number" 2025-06-04 18:24:59 +05:30
ashilkn
52b6fc108b Merge branch 'memory_improvement' into internal-15_06_2025 2025-06-04 18:19:37 +05:30
AmanRajSinghMourya
4d27341787 Refactor: remove unnecessary calls to _resetAnimation and handle reset in onFinalFileLoad 2025-06-04 16:56:52 +05:30
AmanRajSinghMourya
45d8c236fe Update onFinalFileLoad to remove unused parameter 2025-06-04 16:48:08 +05:30
AmanRajSinghMourya
4412b016ce Refactor: rename onFileLoad to onFinalFileLoad for consistency across file handling 2025-06-04 16:44:00 +05:30
AmanRajSinghMourya
3cea8e8a90 Hide seekbar and play/pause button for videos shown in memories & long press to play/pause video 2025-06-04 16:43:34 +05:30
AmanRajSinghMourya
2bb839e26c Refactor: remove scaling and make memories widget of same size 2025-06-03 13:49:02 +05:30
AmanRajSinghMourya
63d0f23742 Fix: reset animation when switching to next/previous page before starting the next/previous page animation 2025-06-03 12:44:59 +05:30
AmanRajSinghMourya
36e7a664ff Make zoom-in/zoom-out animation alternate 2025-06-03 11:37:07 +05:30
AmanRajSinghMourya
e9631c2eb2 Add zoom-in/out effect and background blur effect 2025-06-02 19:07:13 +05:30
Neeraj Gupta
8b3b20aa93 Remove unsued type 2025-06-02 18:38:45 +05:30
Neeraj Gupta
408b0bfe2d Merge branch 'internal-15_06_2025' of https://github.com/ente-io/auth into internal-15_06_2025 2025-06-02 17:55:33 +05:30
Neeraj Gupta
655be76428 Bump version 2025-06-02 17:55:06 +05:30
Neeraj Gupta
9fedf8d6b7 Merge branch 'main' into internal-15_06_2025 2025-06-02 17:54:47 +05:30
ashilkn
7c4e775872 Bump build number 2025-06-02 14:56:04 +05:30
AmanRajSinghMourya
27932679dd Add onFileLoad callback to file_widget for better loading of progress animation 2025-05-31 18:58:48 +05:30
AmanRajSinghMourya
09199180f3 Add isFromMemories parameter to handle gestures 2025-05-30 16:01:27 +05:30
AmanRajSinghMourya
d9f36f1949 Added with progress animation for FullScreenMemory 2025-05-30 16:00:33 +05:30
Neeraj Gupta
ecfa640c28 Bump version 2025-05-30 15:44:46 +05:30
Neeraj Gupta
1997eb20f3 Merge branch 'main' into internal-15_06_2025 2025-05-30 15:44:30 +05:30
AmanRajSinghMourya
e9b95cce62 Minor changes 2025-05-30 15:41:39 +05:30
AmanRajSinghMourya
3ea09df4c0 Add NewProgressIndicator widget for step progress animation for memories 2025-05-28 19:12:37 +05:30
laurenspriem
726425bbb6 Put vector db behind feature flag internal 2025-05-27 14:07:34 +05:30
laurenspriem
c068f26604 Aggressive logging of vectorDB migration 2025-05-22 11:32:36 +05:30
laurenspriem
e60c2b1192 GC hints 2025-05-22 11:19:19 +05:30
laurenspriem
beb049f817 Reduce batch size in migration 2025-05-22 10:38:43 +05:30
laurenspriem
7021c9fe02 Bump for internal release 2025-05-12 17:05:31 +05:30
laurenspriem
c2d5dece9e Merge branch 'main' into usearch_again 2025-05-12 17:04:42 +05:30
laurenspriem
b76d41b84d Specify rust version in readme 2025-05-12 15:48:27 +05:30
laurenspriem
3b9c76649d Update readme to include rust 2025-05-12 15:12:31 +05:30
laurenspriem
62ed8b6975 Log vector db stats when opening connection 2025-05-12 14:46:55 +05:30
laurenspriem
2422dba4d4 vector db more stats logging 2025-05-12 14:23:16 +05:30
laurenspriem
eb1916e3a3 integrate vector db in magic search 2025-05-12 11:39:02 +05:30
laurenspriem
df0d9137a6 Integration clip embeddings in vector db 2025-05-11 13:09:56 +05:30
laurenspriem
fc36b87965 Clip migration method 2025-05-11 13:09:20 +05:30
laurenspriem
63d90ea275 Class for vector db stats 2025-05-09 16:36:39 +05:30
laurenspriem
bb7f8a5eef More testing 2025-05-09 15:59:46 +05:30
laurenspriem
2f5a02ec43 delete table option 2025-05-09 12:57:44 +05:30
laurenspriem
d411d91966 vector db api ensure capacity safety 2025-05-09 12:56:59 +05:30
laurenspriem
54b712953a vector db api let clear include capacity reset 2025-05-09 10:49:03 +05:30
laurenspriem
27ad020adc Testing clip migration to vector DB 2025-05-08 17:40:01 +05:30
laurenspriem
ce112bd4d7 Index stats method 2025-05-08 15:23:23 +05:30
laurenspriem
2ffb73d053 Consistent method parameters 2025-05-08 15:07:50 +05:30
laurenspriem
6478d438b5 vector db api ensure never duplicate keys 2025-05-08 14:30:51 +05:30
laurenspriem
d87069eb4c vectordb api add documentation 2025-05-08 12:31:09 +05:30
laurenspriem
5447350ab1 vector db api add check for key 2025-05-08 12:29:41 +05:30
laurenspriem
ea1a2960bf First implementation of clip vector db 2025-05-08 12:08:55 +05:30
laurenspriem
832f2c451e Add bulk get method to vector db api 2025-05-08 11:47:50 +05:30
laurenspriem
715c7c23a7 Add bulk remove embeddings api 2025-05-08 10:29:25 +05:30
laurenspriem
e9c2e40a43 Update to latest usearch 2025-05-07 13:25:40 +05:30
laurenspriem
603c275c09 Update basic usearch test 2025-05-07 12:01:45 +05:30
laurenspriem
7b9d6df2fd Fix ios build issue 2025-05-07 11:32:53 +05:30
laurenspriem
a4afecef3d Fix ios config 2025-05-07 10:50:39 +05:30
laurenspriem
4d9bfb89ae macos config 2025-05-07 10:36:17 +05:30
laurenspriem
f2a74bd35e Merge branch 'main' into usearch_again 2025-05-06 15:34:59 +05:30
laurenspriem
8c65a21b86 don't generate for web 2025-04-10 13:03:52 +05:30
laurenspriem
a07e8477fb format 2025-04-09 15:34:06 +05:30
laurenspriem
8b489e9ced Give distances in bulk search 2025-04-09 15:31:03 +05:30
laurenspriem
77e2bb1d46 Stop our own vector comparisons in benchmark 2025-04-09 15:21:20 +05:30
laurenspriem
4ce24e080a logging of benchmarking 2025-04-09 14:22:39 +05:30
laurenspriem
4e5ca3dca6 Benchmark face embeddings 2025-04-09 13:43:39 +05:30
laurenspriem
2ed155ab47 ignore trailing commas in generated code 2025-04-09 13:14:26 +05:30
laurenspriem
65e71e3caf Reintroduce reset_index method 2025-04-09 10:49:50 +05:30
laurenspriem
ee5efbcfcc Don't consume index if not needed 2025-04-09 10:43:07 +05:30
laurenspriem
6cf4530f7d Remove reset index 2025-04-09 10:06:46 +05:30
laurenspriem
e6ee09ca30 Back to basic error handling 2025-04-08 17:03:41 +05:30
laurenspriem
6d2f53b86c Update usearch 2025-04-08 14:56:47 +05:30
laurenspriem
6500748c5a Make vector db stateful in rust 2025-04-08 14:48:30 +05:30
laurenspriem
120dbeb4fc Fix null pointer crash 2025-04-05 16:56:14 +05:30
laurenspriem
c42807487b Upgrade Android NDK to r28 latest stable 2025-04-05 16:11:03 +05:30
laurenspriem
e707e24da9 first integration of usearch 2025-04-05 16:10:39 +05:30
laurenspriem
af817ec439 Test rust 2025-04-04 11:49:54 +05:30
laurenspriem
ddb44d8fd7 Integrate flutter_rust_bridge 2025-03-31 15:56:03 +05:30
laurenspriem
778822b12d Upgrade sdk 2025-03-31 15:47:51 +05:30
laurenspriem
9599ec3236 dart format 2025-03-31 15:34:48 +05:30
130 changed files with 9604 additions and 476 deletions

View File

@@ -46,25 +46,27 @@ You can alternatively install the build from PlayStore or F-Droid.
## 🧑‍💻 Building from source
1. [Install Flutter v3.24.3](https://flutter.dev/docs/get-started/install).
1. Install [Flutter v3.24.3](https://flutter.dev/docs/get-started/install) and [Rust v1.85.1](https://www.rust-lang.org/tools/install).
2. Pull in all submodules with `git submodule update --init --recursive`
2. Install [Flutter Rust Bridge](https://cjycode.com/flutter_rust_bridge/) with `cargo install flutter_rust_bridge_codegen`
3. Enable repo git hooks `git config core.hooksPath hooks`
3. Pull in all submodules with `git submodule update --init --recursive`
4. If using Visual Studio Code, add the [Flutter
4. Enable repo git hooks `git config core.hooksPath hooks`
5. If using Visual Studio Code, add the [Flutter
Intl](https://marketplace.visualstudio.com/items?itemName=localizely.flutter-intl)
extension
5. On Android:
6. On Android:
* For development, run `flutter run -t lib/main.dart --flavor independent`
- For development, run `flutter run -t lib/main.dart --flavor independent`
* For building APK, [setup your
- For building APK, [setup your
keystore](https://docs.flutter.dev/deployment/android#create-an-upload-keystore)
and run `flutter build apk --release --flavor independent`
6. For iOS, run `flutter build ios`
7. For iOS, run `flutter build ios`
Some common issues and troubleshooting tips are in [docs/dev](docs/dev.md).
@@ -88,11 +90,12 @@ issue](https://github.com/ente-io/ente/issues/new?title=Request+for+New+Language
to have it added.
## Certificate Fingerprints
- **SHA1**: E1:60:10:18:B6:B0:2E:A3:74:6F:90:67:50:30:29:75:0E:EF:6D:39
- **SHA256**: 35:ED:56:81:B7:0B:B3:BD:35:D9:0D:85:6A:F5:69:4C:50:4D:EF:46:AA:D8:3F:77:7B:1C:67:5C:F4:51:35:0B
To verify these fingerprints, use the following command:
```bash
apksigner verify --print-certs <path_to_apk>
```

View File

@@ -31,7 +31,7 @@ if (keystorePropertiesFile.exists()) {
android {
namespace = "io.ente.photos"
compileSdk = 35
ndkVersion = flutter.ndkVersion
ndkVersion = "28.0.13004108"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8

View File

@@ -0,0 +1,8 @@
rust_input: crate::api
rust_root: rust/
dart_output: lib/src/rust
dart_preamble: |
// ignore_for_file: require_trailing_commas
web: false

View File

@@ -91,10 +91,9 @@ Future<void> dismissUpdateAppDialog(WidgetTester tester) async {
await tester.pumpAndSettle();
}
///Use this widget as floating action buttom in HomeWidget so that frames
///are built and rendered continuously so that timeline trace has continuous
///data. Change the duraiton in `_startTimer()` to control the duraiton of
///are built and rendered continuously so that timeline trace has continuous
///data. Change the duraiton in `_startTimer()` to control the duraiton of
///test on app init.
// class TempWidget extends StatefulWidget {
@@ -127,4 +126,4 @@ Future<void> dismissUpdateAppDialog(WidgetTester tester) async {
// ? const CircularProgressIndicator()
// : const SizedBox.shrink();
// }
// }
// }

View File

@@ -0,0 +1,13 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import "package:photos/src/rust/api/simple.dart";
import 'package:photos/src/rust/frb_generated.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async => await RustLib.init());
testWidgets('Can call rust function', (WidgetTester tester) async {
final testString = greet(name: "Tom");
expect(testString.contains('Tom'), true);
});
}

View File

@@ -129,6 +129,9 @@ PODS:
- libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0):
- libwebp/sharpyuv
- local_auth_darwin (0.0.1):
- Flutter
- FlutterMacOS
- local_auth_ios (0.0.1):
- Flutter
- Mantle (2.2.0):
@@ -183,6 +186,8 @@ PODS:
- PromisesObjC (2.4.0)
- receive_sharing_intent (1.8.1):
- Flutter
- rust_lib_photos (0.0.1):
- Flutter
- SDWebImage (5.21.0):
- SDWebImage/Core (= 5.21.0)
- SDWebImage/Core (5.21.0)
@@ -267,6 +272,7 @@ DEPENDENCIES:
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- launcher_icon_switcher (from `.symlinks/plugins/launcher_icon_switcher/ios`)
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
- maps_launcher (from `.symlinks/plugins/maps_launcher/ios`)
- media_extension (from `.symlinks/plugins/media_extension/ios`)
@@ -285,6 +291,7 @@ DEPENDENCIES:
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- privacy_screen (from `.symlinks/plugins/privacy_screen/ios`)
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
- rust_lib_photos (from `.symlinks/plugins/rust_lib_photos/ios`)
- 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`)
@@ -300,7 +307,7 @@ DEPENDENCIES:
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
https://github.com/ente-io/ffmpeg-kit-custom-repo-ios:
https://github.com/ente-io/ffmpeg-kit-custom-repo-ios.git:
- ffmpeg_kit_custom
trunk:
- Firebase
@@ -375,6 +382,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/integration_test/ios"
launcher_icon_switcher:
:path: ".symlinks/plugins/launcher_icon_switcher/ios"
local_auth_darwin:
:path: ".symlinks/plugins/local_auth_darwin/darwin"
local_auth_ios:
:path: ".symlinks/plugins/local_auth_ios/ios"
maps_launcher:
@@ -411,6 +420,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/privacy_screen/ios"
receive_sharing_intent:
:path: ".symlinks/plugins/receive_sharing_intent/ios"
rust_lib_photos:
:path: ".symlinks/plugins/rust_lib_photos/ios"
sentry_flutter:
:path: ".symlinks/plugins/sentry_flutter/ios"
share_plus:
@@ -439,82 +450,84 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
background_fetch: 94b36ee293e82972852dba8ede1fbcd3bd3d9d57
battery_info: 83f3aae7be2fccefab1d2bf06b8aa96f11c8bcdd
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
dart_ui_isolate: 46f6714abe6891313267153ef6f9748d8ecfcab1
device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89
app_links: f3e17e4ee5e357b39d8b95290a9b2c299fca71c6
background_fetch: 39f11371c0dce04b001c4bfd5e782bcccb0a85e2
battery_info: b6c551049266af31556b93c9d9b9452cfec0219f
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba
dart_ui_isolate: d5bcda83ca4b04f129d70eb90110b7a567aece14
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
ffmpeg_kit_custom: 682b4f2f1ff1f8abae5a92f6c3540f2441d5be99
ffmpeg_kit_flutter: 915b345acc97d4142e8a9a8549d177ff10f043f5
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
ffmpeg_kit_flutter: 9dce4803991478c78c6fb9f972703301101095fe
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
firebase_core: 6cbed78b4f298ed103a9fd034e6dbc846320480f
firebase_messaging: 5e0adf2eb18b0ee59aa0c109314c091a0497ecac
firebase_core: 6e223dfa350b2edceb729cea505eaaef59330682
firebase_messaging: 07fde77ae28c08616a1d4d870450efc2b38cf40d
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917
FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_email_sender: aa1e9772696691d02cd91fea829856c11efb8e58
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
flutter_native_splash: 6cad9122ea0fad137d23137dd14b937f3e90b145
flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418
flutter_sodium: 7e4621538491834eba53bd524547854bcbbd6987
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
flutter_email_sender: e03bdda7637bcd3539bfe718fddd980e9508efaa
flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f
flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
flutter_sodium: a00383520fc689c688b66fd3092984174712493e
flutter_timezone: ac3da59ac941ff1c98a2e1f0293420e020120282
fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
image_editor_common: 3de87e7c4804f4ae24c8f8a998362b98c105cac1
in_app_purchase_storekit: d1a48cb0f8b29dbf5f85f782f5dd79b21b90a5e6
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
launcher_icon_switcher: 84c218d233505aa7d8655d8fa61a3ba802c022da
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43
in_app_purchase_storekit: a1ce04056e23eecc666b086040239da7619cd783
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
launcher_icon_switcher: 8e0ad2131a20c51c1dd939896ee32e70cd845b37
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
local_auth_ios: f7a1841beef3151d140a967c2e46f30637cdf451
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
maps_launcher: edf829809ba9e894d70e569bab11c16352dedb45
media_extension: 671e2567880d96c95c65c9a82ccceed8f2e309fd
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
motion_sensors: 741e702c17467b9569a92165dda8d4d88c6167f1
motionphoto: 23e2aeb5c6380112f69468d71f970fa7438e5ed1
move_to_background: 7e3467dd2a1d1013e98c9c1cb93fd53cd7ef9d84
maps_launcher: 2e5b6a2d664ec6c27f82ffa81b74228d770ab203
media_extension: 6618f07abd762cdbfaadf1b0c56a287e820f0c84
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
motion_sensors: 03f55b7c637a7e365a0b5f9697a449f9059d5d91
motionphoto: 8b65ce50c7d7ff3c767534fc3768b2eed9ac24e4
move_to_background: cd3091014529ec7829e342ad2d75c0a11f4378a5
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
native_video_player: e363dd14f6a498ad8a8f7e6486a0db046ad19f13
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
onnxruntime: f9b296392c96c42882be020a59dbeac6310d81b2
native_video_player: 5d36066807b680e181473e6890dde643ac85380d
objective_c: 77e887b5ba1827970907e10e832eec1683f3431d
onnxruntime: e7c2ae44385191eaad5ae64c935a72debaddc997
onnxruntime-c: a909204639a1f035f575127ac406f781ac797c9c
onnxruntime-objc: b6fab0f1787aa6f7190c2013f03037df4718bd8b
open_mail_app: 7314a609e88eed22d53671279e189af7a0ab0f11
open_mail_app: 70273c53f768beefdafbe310c3d9086e4da3cb02
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
photo_manager: d2fbcc0f2d82458700ee6256a15018210a81d413
privacy_screen: 3159a541f5d3a31bea916cfd4e58f9dc722b3fd4
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
rust_lib_photos: 04d3901908d2972192944083310b65abf410774c
receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
sentry_flutter: 942017adbe00f963061cb11ec260414a990b7a42
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sentry_flutter: 6a134f9d381e49f22ea25a67736cf0cf4d02ec9c
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: 3c323550ef3b928bc0aa9513c841e45a7d242832
system_info_plus: 555ce7047fbbf29154726db942ae785c29211740
thermal: d4c48be750d1ddbab36b0e2dcb2471531bc8df41
ua_client_hints: 92fe0d139619b73ec9fcb46cc7e079a26178f586
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
video_thumbnail: 584ccfa55d8fd2f3d5507218b0a18d84c839c620
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa
thermal: a9261044101ae8f532fa29cab4e8270b51b3f55c
ua_client_hints: aeabd123262c087f0ce151ef96fa3ab77bfc8b38
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
video_thumbnail: 94ba6705afbaa120b77287080424930f23ea0c40
volume_controller: 2e3de73d6e7e81a0067310d17fb70f2f86d71ac7
wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56
PODFILE CHECKSUM: a8ef88ad74ba499756207e7592c6071a96756d18

View File

@@ -25,7 +25,7 @@
CEE6BE702D7AE7FD00E4048B /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DACD83C2B755B0600BA9516 /* WidgetKit.framework */; };
CEE6BE712D7AE7FD00E4048B /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DACD83E2B755B0600BA9516 /* SwiftUI.framework */; };
CEE6BE7C2D7AE7FE00E4048B /* EnteMemoryWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = CEE6BE6F2D7AE7FD00E4048B /* EnteMemoryWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
DA6BE5E826B3BC8600656280 /* (null) in Resources */ = {isa = PBXBuildFile; };
DA6BE5E826B3BC8600656280 /* BuildFile in Resources */ = {isa = PBXBuildFile; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -112,21 +112,21 @@
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
CEE166342DD5E7830012CF61 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
CEE166342DD5E7830012CF61 /* Exceptions for "EnteAlbumWidget" folder in "EnteAlbumWidgetExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = CEE166222DD5E7820012CF61 /* EnteAlbumWidgetExtension */;
};
CEE1668C2DD5F6F30012CF61 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
CEE1668C2DD5F6F30012CF61 /* Exceptions for "EntePeopleWidget" folder in "EntePeopleWidgetExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = CEE1667A2DD5F6F20012CF61 /* EntePeopleWidgetExtension */;
};
CEE6BE802D7AE7FE00E4048B /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
CEE6BE802D7AE7FE00E4048B /* Exceptions for "EnteMemoryWidget" folder in "EnteMemoryWidgetExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
@@ -136,9 +136,42 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
CEE166262DD5E7820012CF61 /* EnteAlbumWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CEE166342DD5E7830012CF61 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = EnteAlbumWidget; sourceTree = "<group>"; };
CEE1667E2DD5F6F20012CF61 /* EntePeopleWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CEE1668C2DD5F6F30012CF61 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = EntePeopleWidget; sourceTree = "<group>"; };
CEE6BE722D7AE7FD00E4048B /* EnteMemoryWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CEE6BE802D7AE7FE00E4048B /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = EnteMemoryWidget; sourceTree = "<group>"; };
CEE166262DD5E7820012CF61 /* EnteAlbumWidget */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
CEE166342DD5E7830012CF61 /* Exceptions for "EnteAlbumWidget" folder in "EnteAlbumWidgetExtension" target */,
);
explicitFileTypes = {
};
explicitFolders = (
);
path = EnteAlbumWidget;
sourceTree = "<group>";
};
CEE1667E2DD5F6F20012CF61 /* EntePeopleWidget */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
CEE1668C2DD5F6F30012CF61 /* Exceptions for "EntePeopleWidget" folder in "EntePeopleWidgetExtension" target */,
);
explicitFileTypes = {
};
explicitFolders = (
);
path = EntePeopleWidget;
sourceTree = "<group>";
};
CEE6BE722D7AE7FD00E4048B /* EnteMemoryWidget */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
CEE6BE802D7AE7FE00E4048B /* Exceptions for "EnteMemoryWidget" folder in "EnteMemoryWidgetExtension" target */,
);
explicitFileTypes = {
};
explicitFolders = (
);
path = EnteMemoryWidget;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@@ -414,7 +447,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
DA6BE5E826B3BC8600656280 /* (null) in Resources */,
DA6BE5E826B3BC8600656280 /* BuildFile in Resources */,
277218A0270F596900FFE3CC /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -516,6 +549,7 @@
"${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework",
"${BUILT_PRODUCTS_DIR}/launcher_icon_switcher/launcher_icon_switcher.framework",
"${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework",
"${BUILT_PRODUCTS_DIR}/local_auth_darwin/local_auth_darwin.framework",
"${BUILT_PRODUCTS_DIR}/local_auth_ios/local_auth_ios.framework",
"${BUILT_PRODUCTS_DIR}/maps_launcher/maps_launcher.framework",
"${BUILT_PRODUCTS_DIR}/media_extension/media_extension.framework",
@@ -533,6 +567,7 @@
"${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework",
"${BUILT_PRODUCTS_DIR}/privacy_screen/privacy_screen.framework",
"${BUILT_PRODUCTS_DIR}/receive_sharing_intent/receive_sharing_intent.framework",
"${BUILT_PRODUCTS_DIR}/rust_lib_photos/rust_lib_photos.framework",
"${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework",
"${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
"${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework",
@@ -611,6 +646,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/launcher_icon_switcher.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth_darwin.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth_ios.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/maps_launcher.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework",
@@ -628,6 +664,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/privacy_screen.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/receive_sharing_intent.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/rust_lib_photos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework",

View File

@@ -1,4 +1,3 @@
import "dart:io";
import 'package:photos/core/cache/lru_map.dart';

View File

@@ -58,7 +58,7 @@ bool isHandledSyncError(Object errObj) {
class LockAlreadyAcquiredError extends Error {}
class LockFreedError extends Error{}
class LockFreedError extends Error {}
class UnauthorizedError extends Error {}

View File

@@ -0,0 +1,238 @@
import "dart:typed_data" show Float32List;
import "package:flutter_rust_bridge/flutter_rust_bridge.dart" show Uint64List;
import "package:logging/logging.dart";
import "package:path/path.dart";
import "package:path_provider/path_provider.dart";
import "package:photos/models/ml/vector.dart";
import "package:photos/services/machine_learning/semantic_search/query_result.dart";
import "package:photos/src/rust/api/usearch_api.dart";
class ClipVectorDB {
static final Logger _logger = Logger("ClipVectorDB");
static const _databaseName = "ente.ml.vectordb.clip";
static final BigInt _embeddingDimension = BigInt.from(512);
static Logger get logger => _logger;
// Singleton pattern
ClipVectorDB._privateConstructor();
static final instance = ClipVectorDB._privateConstructor();
factory ClipVectorDB() => instance;
// only have a single app-wide reference to the database
static Future<VectorDb>? _vectorDbFuture;
Future<VectorDb> get _vectorDB async {
_vectorDbFuture ??= _initVectorDB();
return _vectorDbFuture!;
}
Future<VectorDb> _initVectorDB() async {
final documentsDirectory = await getApplicationDocumentsDirectory();
final String databaseDirectory =
join(documentsDirectory.path, _databaseName);
_logger.info("Opening vectorDB access: DB path " + databaseDirectory);
final vectorDB = VectorDb(
filePath: databaseDirectory,
dimensions: _embeddingDimension,
);
final stats = await getIndexStats(vectorDB);
_logger.info("VectorDB connection opened with stats: ${stats.toString()}");
return vectorDB;
}
Future<void> insertEmbedding({
required int fileID,
required List<double> embedding,
}) async {
final db = await _vectorDB;
try {
await db.addVector(key: BigInt.from(fileID), vector: embedding);
} catch (e, s) {
_logger.severe("Error inserting embedding", e, s);
rethrow;
}
}
Future<void> bulkInsertEmbeddings({
required List<int> fileIDs,
required List<Float32List> embeddings,
}) async {
final db = await _vectorDB;
final bigKeys = Uint64List.fromList(fileIDs);
try {
await db.bulkAddVectors(keys: bigKeys, vectors: embeddings);
} catch (e, s) {
_logger.severe("Error bulk inserting embeddings", e, s);
rethrow;
}
}
Future<List<EmbeddingVector>> getEmbeddings(List<int> fileIDs) async {
final db = await _vectorDB;
try {
final keys = Uint64List.fromList(fileIDs);
final vectors = await db.bulkGetVectors(keys: keys);
return List.generate(
vectors.length,
(index) => EmbeddingVector(
fileID: fileIDs[index],
embedding: vectors[index],
),
);
} catch (e, s) {
_logger.severe("Error getting embeddings", e, s);
rethrow;
}
}
Future<void> deleteEmbeddings(List<int> fileIDs) async {
final db = await _vectorDB;
try {
final deletedCount =
await db.bulkRemoveVectors(keys: Uint64List.fromList(fileIDs));
_logger.info(
"Deleted $deletedCount embeddings, from ${fileIDs.length} keys",
);
} catch (e, s) {
_logger.severe("Error bulk deleting specific embeddings", e, s);
rethrow;
}
}
Future<void> deleteAllEmbeddings() async {
final db = await _vectorDB;
try {
await db.resetIndex();
} catch (e, s) {
_logger.severe("Error deleting all embeddings", e, s);
rethrow;
}
}
Future<void> deleteIndex() async {
final db = await _vectorDB;
try {
await db.deleteIndex();
_vectorDbFuture = null;
} catch (e, s) {
_logger.severe("Error deleting index", e, s);
rethrow;
}
}
Future<VectorDbStats> getIndexStats([VectorDb? db]) async {
db ??= await _vectorDB;
try {
final stats = await db.getIndexStats();
return VectorDbStats(
size: stats.$1.toInt(),
capacity: stats.$2.toInt(),
dimensions: stats.$3.toInt(),
fileSize: stats.$4.toInt(),
memoryUsage: stats.$5.toInt(),
expansionAdd: stats.$6.toInt(),
expansionSearch: stats.$7.toInt(),
);
} catch (e, s) {
_logger.severe("Error getting index stats", e, s);
rethrow;
}
}
Future<(Uint64List, Float32List)> searchClosestVectors(
List<double> query,
int count,
) async {
final db = await _vectorDB;
try {
final result =
await db.searchVectors(query: query, count: BigInt.from(count));
return result;
} catch (e, s) {
_logger.severe("Error searching closest vectors", e, s);
rethrow;
}
}
Future<(BigInt, double)> searchClosestVector(
List<double> query,
) async {
final db = await _vectorDB;
try {
final result = await db.searchVectors(query: query, count: BigInt.one);
return (result.$1[0], result.$2[0]);
} catch (e, s) {
_logger.severe("Error searching closest vector", e, s);
rethrow;
}
}
Future<Map<String, List<QueryResult>>> computeBulkSimilarities(
Map<String, List<double>> textQueryToEmbeddingMap,
Map<String, double> minimumSimilarityMap,
) async {
try {
final queryToResults = <String, List<QueryResult>>{};
for (final MapEntry<String, List<double>> entry
in textQueryToEmbeddingMap.entries) {
final query = entry.key;
final minimumSimilarity = minimumSimilarityMap[query]!;
final textEmbedding = entry.value;
final (potentialFileIDs, distances) =
await searchClosestVectors(textEmbedding, 1000);
final queryResults = <QueryResult>[];
for (var i = 0; i < potentialFileIDs.length; i++) {
final similarity = 1 - distances[i];
if (similarity >= minimumSimilarity) {
queryResults
.add(QueryResult(potentialFileIDs[i].toInt(), similarity));
} else {
break;
}
}
queryToResults[query] = queryResults;
}
return queryToResults;
} catch (e, s) {
_logger.severe(
"Could not bulk find embeddings similarities using vector DB",
e,
s,
);
rethrow;
}
}
}
class VectorDbStats {
final int size;
final int capacity;
final int dimensions;
// in bytes
final int fileSize;
final int memoryUsage;
final int expansionAdd;
final int expansionSearch;
VectorDbStats({
required this.size,
required this.capacity,
required this.dimensions,
required this.fileSize,
required this.memoryUsage,
required this.expansionAdd,
required this.expansionSearch,
});
@override
String toString() {
return "VectorDbStats(size: $size, capacity: $capacity, dimensions: $dimensions, file size on disk (bytes): $fileSize, memory usage (bytes): $memoryUsage, expansionAdd: $expansionAdd, expansionSearch: $expansionSearch)";
}
}

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import "dart:io" show File;
import "dart:math";
import "package:collection/collection.dart";
@@ -9,6 +10,7 @@ import 'package:path_provider/path_provider.dart';
import "package:photos/core/event_bus.dart";
import "package:photos/db/common/base.dart";
import "package:photos/db/ml/base.dart";
import "package:photos/db/ml/clip_vector_db.dart";
import "package:photos/db/ml/db_model_mappers.dart";
import 'package:photos/db/ml/schema.dart';
import "package:photos/events/embedding_updated_event.dart";
@@ -18,6 +20,7 @@ import "package:photos/models/ml/face/face.dart";
import "package:photos/models/ml/face/face_with_embedding.dart";
import "package:photos/models/ml/ml_versions.dart";
import "package:photos/models/ml/vector.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart";
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart';
import "package:photos/services/machine_learning/ml_result.dart";
@@ -84,6 +87,8 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
"MLDataDB Migration took ${stopwatch.elapsedMilliseconds} ms",
);
stopwatch.stop();
_logger.info("Starting CLIP vector DB migration check unawaited");
if (flagService.enableVectorDb) unawaited(checkMigrateFillClipVectorDB());
return asyncDBConnection;
}
@@ -1219,6 +1224,130 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
return embeddings;
}
Future<void> checkMigrateFillClipVectorDB({bool force = false}) async {
_logger.info("Waiting for ClipVectorDB to be ready");
await Future.delayed(const Duration(milliseconds: 100));
_logger.info("Checking if ClipVectorDB migration is needed");
// Check if vector DB migration has run
_logger.info("Checking if ClipVectorDB migration has run");
final documentsDirectory = await getApplicationDocumentsDirectory();
final migrationFlagFile =
File(join(documentsDirectory.path, 'clip_vector_migration_done'));
if (await migrationFlagFile.exists() && !force) {
_logger.info("ClipVectorDB migration not needed, already done");
return;
}
// Get total count first to track progress
_logger.info("Getting total count of clip embeddings");
final db = await instance.asyncDB;
final countResult =
await db.getAll('SELECT COUNT($fileIDColumn) as total FROM $clipTable');
final totalCount = countResult.first['total'] as int;
if (totalCount == 0) {
_logger.info("No clip embeddings to migrate");
await migrationFlagFile.create();
return;
}
_logger.info("Total count of clip embeddings: $totalCount");
_logger.info("First time referencing ClipVectorDB in migration");
// TODO: lau: Remove this artificial delay later
_logger.info("Referencing ClipVectorDB in 500ms");
await Future.delayed(const Duration(milliseconds: 500));
final clipVectorDB = ClipVectorDB.instance;
_logger.info("ClipVectorDB referenced");
// TODO: lau: Remove this artificial delay later
_logger.info("Using ClipVectorDB to delete all embeddings in 500ms");
await Future.delayed(const Duration(milliseconds: 500));
await clipVectorDB.deleteAllEmbeddings();
_logger.info("ClipVectorDB all embeddings cleared");
_logger
.info("Starting migration of $totalCount clip embeddings to vector DB");
const batchSize = 1000;
int offset = 0;
int processedCount = 0;
int weirdCount = 0;
int whileCount = 0;
final stopwatch = Stopwatch()..start();
try {
while (true) {
whileCount++;
_logger.info("$whileCount st round of while loop");
// Allow some time for any GC to finish
await Future.delayed(const Duration(milliseconds: 100));
_logger.info("Reading $batchSize rows from DB");
final List<Map<String, dynamic>> results = await db.getAll('''
SELECT $fileIDColumn, $embeddingColumn
FROM $clipTable
ORDER BY $fileIDColumn DESC
LIMIT $batchSize OFFSET $offset
''');
_logger.info("Got ${results.length} results from DB");
if (results.isEmpty) {
_logger.info("No more results, breaking out of while loop");
break;
}
_logger.info("Processing ${results.length} results");
final List<int> fileIDs = [];
final List<Float32List> embeddings = [];
for (final result in results) {
final embedding =
Float32List.view((result[embeddingColumn] as Uint8List).buffer);
if (embedding.length == 512) {
fileIDs.add(result[fileIDColumn] as int);
embeddings.add(Float32List.view(result[embeddingColumn].buffer));
} else {
weirdCount++;
}
}
_logger.info(
"Got ${fileIDs.length} valid embeddings, $weirdCount weird embeddings",
);
// TODO: lau: Remove this artificial delay later
_logger.info("Using ClipVectorDB to bulk insert embeddings in 500ms");
await Future.delayed(const Duration(milliseconds: 500));
await ClipVectorDB.instance
.bulkInsertEmbeddings(fileIDs: fileIDs, embeddings: embeddings);
_logger.info("Inserted ${fileIDs.length} embeddings to ClipVectorDB");
processedCount += fileIDs.length;
offset += batchSize;
_logger.info(
"migrated $processedCount/$totalCount embeddings to ClipVectorDB",
);
if (processedCount >= totalCount) {
_logger.info("All embeddings migrated, breaking out of while loop");
break;
}
_logger.info("Clearing out embeddings and fileIDs");
embeddings.clear();
fileIDs.clear();
results.clear();
// Allow some time for any GC to finish
_logger.info("Waiting for 100ms for GC to finish");
await Future.delayed(const Duration(milliseconds: 100));
}
_logger.info(
"migrated all $totalCount embeddings to ClipVectorDB in ${stopwatch.elapsed.inMilliseconds} ms, with $weirdCount weird embeddings not migrated",
);
await migrationFlagFile.create();
_logger.info("ClipVectorDB migration done, flag file created");
} catch (e) {
_logger.severe(
"Error migrating ClipVectorDB after ${stopwatch.elapsed.inMilliseconds} ms, clearing out DB again",
e,
);
await clipVectorDB.deleteAllEmbeddings();
rethrow;
} finally {
stopwatch.stop();
}
}
// Get indexed FileIDs
@override
Future<Map<int, int>> clipIndexedFileWithVersion() async {
@@ -1252,12 +1381,37 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
'INSERT OR REPLACE INTO $clipTable ($fileIDColumn, $embeddingColumn, $mlVersionColumn) VALUES (?, ?, ?)',
_getRowFromEmbedding(embeddings.first),
);
if (flagService.enableVectorDb) {
// TODO: lau: Remove this artificial delay later
_logger.info("Using ClipVectorDB to insert embedding in 500ms");
await Future.delayed(const Duration(milliseconds: 500));
await ClipVectorDB.instance.insertEmbedding(
fileID: embeddings.first.fileID,
embedding: embeddings.first.embedding,
);
_logger.info(
"Inserted embedding for fileID ${embeddings.first.fileID} to ClipVectorDB",
);
}
} else {
final inputs = embeddings.map((e) => _getRowFromEmbedding(e)).toList();
await db.executeBatch(
'INSERT OR REPLACE INTO $clipTable ($fileIDColumn, $embeddingColumn, $mlVersionColumn) values(?, ?, ?)',
inputs,
);
if (flagService.enableVectorDb) {
// TODO: lau: Remove this artificial delay later
_logger.info("Using ClipVectorDB to bulk insert embedding in 500ms");
await Future.delayed(const Duration(milliseconds: 500));
await ClipVectorDB.instance.bulkInsertEmbeddings(
fileIDs: embeddings.map((e) => e.fileID).toList(),
embeddings:
embeddings.map((e) => Float32List.fromList(e.embedding)).toList(),
);
_logger.info(
"Inserted ${embeddings.length} embeddings to ClipVectorDB",
);
}
}
Bus.instance.fire(EmbeddingUpdatedEvent());
}
@@ -1268,6 +1422,15 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
await db.execute(
'DELETE FROM $clipTable WHERE $fileIDColumn IN (${fileIDs.join(", ")})',
);
if (flagService.enableVectorDb) {
// TODO: lau: Remove this artificial delay later
_logger.info("Using ClipVectorDB to delete certain embeddings in 500ms");
await Future.delayed(const Duration(milliseconds: 500));
await ClipVectorDB.instance.deleteEmbeddings(fileIDs);
_logger.info(
"Deleted ${fileIDs.length} embeddings from ClipVectorDB",
);
}
Bus.instance.fire(EmbeddingUpdatedEvent());
}
@@ -1275,6 +1438,13 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
Future<void> deleteClipIndexes() async {
final db = await instance.asyncDB;
await db.execute('DELETE FROM $clipTable');
if (flagService.enableVectorDb) {
// TODO: lau: Remove this artificial delay later
_logger.info("Using ClipVectorDB to delete all embeddings in 500ms");
await Future.delayed(const Duration(milliseconds: 500));
await ClipVectorDB.instance.deleteAllEmbeddings();
_logger.info("All embeddings deleted from ClipVectorDB");
}
Bus.instance.fire(EmbeddingUpdatedEvent());
}

View File

@@ -37,27 +37,32 @@ class CenterBox extends $pb.GeneratedMessage {
return $result;
}
CenterBox._() : super();
factory CenterBox.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory CenterBox.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
factory CenterBox.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory CenterBox.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'CenterBox', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'), createEmptyInstance: create)
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'CenterBox',
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'),
createEmptyInstance: create)
..a<$core.double>(1, _omitFieldNames ? '' : 'x', $pb.PbFieldType.OF)
..a<$core.double>(2, _omitFieldNames ? '' : 'y', $pb.PbFieldType.OF)
..a<$core.double>(3, _omitFieldNames ? '' : 'height', $pb.PbFieldType.OF)
..a<$core.double>(4, _omitFieldNames ? '' : 'width', $pb.PbFieldType.OF)
..hasRequiredFields = false
;
..hasRequiredFields = false;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
CenterBox clone() => CenterBox()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
CenterBox copyWith(void Function(CenterBox) updates) => super.copyWith((message) => updates(message as CenterBox)) as CenterBox;
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
CenterBox copyWith(void Function(CenterBox) updates) =>
super.copyWith((message) => updates(message as CenterBox)) as CenterBox;
$pb.BuilderInfo get info_ => _i;
@@ -66,13 +71,17 @@ class CenterBox extends $pb.GeneratedMessage {
CenterBox createEmptyInstance() => create();
static $pb.PbList<CenterBox> createRepeated() => $pb.PbList<CenterBox>();
@$core.pragma('dart2js:noInline')
static CenterBox getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CenterBox>(create);
static CenterBox getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CenterBox>(create);
static CenterBox? _defaultInstance;
@$pb.TagNumber(1)
$core.double get x => $_getN(0);
@$pb.TagNumber(1)
set x($core.double v) { $_setFloat(0, v); }
set x($core.double v) {
$_setFloat(0, v);
}
@$pb.TagNumber(1)
$core.bool hasX() => $_has(0);
@$pb.TagNumber(1)
@@ -81,7 +90,10 @@ class CenterBox extends $pb.GeneratedMessage {
@$pb.TagNumber(2)
$core.double get y => $_getN(1);
@$pb.TagNumber(2)
set y($core.double v) { $_setFloat(1, v); }
set y($core.double v) {
$_setFloat(1, v);
}
@$pb.TagNumber(2)
$core.bool hasY() => $_has(1);
@$pb.TagNumber(2)
@@ -90,7 +102,10 @@ class CenterBox extends $pb.GeneratedMessage {
@$pb.TagNumber(3)
$core.double get height => $_getN(2);
@$pb.TagNumber(3)
set height($core.double v) { $_setFloat(2, v); }
set height($core.double v) {
$_setFloat(2, v);
}
@$pb.TagNumber(3)
$core.bool hasHeight() => $_has(2);
@$pb.TagNumber(3)
@@ -99,13 +114,16 @@ class CenterBox extends $pb.GeneratedMessage {
@$pb.TagNumber(4)
$core.double get width => $_getN(3);
@$pb.TagNumber(4)
set width($core.double v) { $_setFloat(3, v); }
set width($core.double v) {
$_setFloat(3, v);
}
@$pb.TagNumber(4)
$core.bool hasWidth() => $_has(3);
@$pb.TagNumber(4)
void clearWidth() => clearField(4);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
const _omitMessageNames =
$core.bool.fromEnvironment('protobuf.omit_message_names');

View File

@@ -8,4 +8,3 @@
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View File

@@ -35,4 +35,3 @@ final $typed_data.Uint8List centerBoxDescriptor = $convert.base64Decode(
'CglDZW50ZXJCb3gSEQoBeBgBIAEoAkgAUgF4iAEBEhEKAXkYAiABKAJIAVIBeYgBARIbCgZoZW'
'lnaHQYAyABKAJIAlIGaGVpZ2h0iAEBEhkKBXdpZHRoGAQgASgCSANSBXdpZHRoiAEBQgQKAl94'
'QgQKAl95QgkKB19oZWlnaHRCCAoGX3dpZHRo');

View File

@@ -11,4 +11,3 @@
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'box.pb.dart';

View File

@@ -29,25 +29,30 @@ class EPoint extends $pb.GeneratedMessage {
return $result;
}
EPoint._() : super();
factory EPoint.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory EPoint.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
factory EPoint.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory EPoint.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EPoint', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'), createEmptyInstance: create)
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'EPoint',
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'),
createEmptyInstance: create)
..a<$core.double>(1, _omitFieldNames ? '' : 'x', $pb.PbFieldType.OF)
..a<$core.double>(2, _omitFieldNames ? '' : 'y', $pb.PbFieldType.OF)
..hasRequiredFields = false
;
..hasRequiredFields = false;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
EPoint clone() => EPoint()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EPoint copyWith(void Function(EPoint) updates) => super.copyWith((message) => updates(message as EPoint)) as EPoint;
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EPoint copyWith(void Function(EPoint) updates) =>
super.copyWith((message) => updates(message as EPoint)) as EPoint;
$pb.BuilderInfo get info_ => _i;
@@ -56,13 +61,17 @@ class EPoint extends $pb.GeneratedMessage {
EPoint createEmptyInstance() => create();
static $pb.PbList<EPoint> createRepeated() => $pb.PbList<EPoint>();
@$core.pragma('dart2js:noInline')
static EPoint getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EPoint>(create);
static EPoint getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EPoint>(create);
static EPoint? _defaultInstance;
@$pb.TagNumber(1)
$core.double get x => $_getN(0);
@$pb.TagNumber(1)
set x($core.double v) { $_setFloat(0, v); }
set x($core.double v) {
$_setFloat(0, v);
}
@$pb.TagNumber(1)
$core.bool hasX() => $_has(0);
@$pb.TagNumber(1)
@@ -71,13 +80,16 @@ class EPoint extends $pb.GeneratedMessage {
@$pb.TagNumber(2)
$core.double get y => $_getN(1);
@$pb.TagNumber(2)
set y($core.double v) { $_setFloat(1, v); }
set y($core.double v) {
$_setFloat(1, v);
}
@$pb.TagNumber(2)
$core.bool hasY() => $_has(1);
@$pb.TagNumber(2)
void clearY() => clearField(2);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
const _omitMessageNames =
$core.bool.fromEnvironment('protobuf.omit_message_names');

View File

@@ -8,4 +8,3 @@
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View File

@@ -30,4 +30,3 @@ const EPoint$json = {
final $typed_data.Uint8List ePointDescriptor = $convert.base64Decode(
'CgZFUG9pbnQSEQoBeBgBIAEoAkgAUgF4iAEBEhEKAXkYAiABKAJIAVIBeYgBAUIECgJfeEIECg'
'JfeQ==');

View File

@@ -11,4 +11,3 @@
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'point.pb.dart';

View File

@@ -26,24 +26,29 @@ class EVector extends $pb.GeneratedMessage {
return $result;
}
EVector._() : super();
factory EVector.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory EVector.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
factory EVector.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory EVector.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EVector', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'), createEmptyInstance: create)
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'EVector',
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'),
createEmptyInstance: create)
..p<$core.double>(1, _omitFieldNames ? '' : 'values', $pb.PbFieldType.KD)
..hasRequiredFields = false
;
..hasRequiredFields = false;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
EVector clone() => EVector()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EVector copyWith(void Function(EVector) updates) => super.copyWith((message) => updates(message as EVector)) as EVector;
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EVector copyWith(void Function(EVector) updates) =>
super.copyWith((message) => updates(message as EVector)) as EVector;
$pb.BuilderInfo get info_ => _i;
@@ -52,13 +57,14 @@ class EVector extends $pb.GeneratedMessage {
EVector createEmptyInstance() => create();
static $pb.PbList<EVector> createRepeated() => $pb.PbList<EVector>();
@$core.pragma('dart2js:noInline')
static EVector getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EVector>(create);
static EVector getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EVector>(create);
static EVector? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.double> get values => $_getList(0);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
const _omitMessageNames =
$core.bool.fromEnvironment('protobuf.omit_message_names');

View File

@@ -8,4 +8,3 @@
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View File

@@ -22,6 +22,5 @@ const EVector$json = {
};
/// Descriptor for `EVector`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List eVectorDescriptor = $convert.base64Decode(
'CgdFVmVjdG9yEhYKBnZhbHVlcxgBIAMoAVIGdmFsdWVz');
final $typed_data.Uint8List eVectorDescriptor =
$convert.base64Decode('CgdFVmVjdG9yEhYKBnZhbHVlcxgBIAMoAVIGdmFsdWVz');

View File

@@ -11,4 +11,3 @@
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'vector.pb.dart';

View File

@@ -31,25 +31,32 @@ class Detection extends $pb.GeneratedMessage {
return $result;
}
Detection._() : super();
factory Detection.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory Detection.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
factory Detection.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory Detection.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Detection', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
..aOM<$0.CenterBox>(1, _omitFieldNames ? '' : 'box', subBuilder: $0.CenterBox.create)
..aOM<$1.EPoint>(2, _omitFieldNames ? '' : 'landmarks', subBuilder: $1.EPoint.create)
..hasRequiredFields = false
;
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'Detection',
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'),
createEmptyInstance: create)
..aOM<$0.CenterBox>(1, _omitFieldNames ? '' : 'box',
subBuilder: $0.CenterBox.create)
..aOM<$1.EPoint>(2, _omitFieldNames ? '' : 'landmarks',
subBuilder: $1.EPoint.create)
..hasRequiredFields = false;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
Detection clone() => Detection()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
Detection copyWith(void Function(Detection) updates) => super.copyWith((message) => updates(message as Detection)) as Detection;
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
Detection copyWith(void Function(Detection) updates) =>
super.copyWith((message) => updates(message as Detection)) as Detection;
$pb.BuilderInfo get info_ => _i;
@@ -58,13 +65,17 @@ class Detection extends $pb.GeneratedMessage {
Detection createEmptyInstance() => create();
static $pb.PbList<Detection> createRepeated() => $pb.PbList<Detection>();
@$core.pragma('dart2js:noInline')
static Detection getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Detection>(create);
static Detection getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Detection>(create);
static Detection? _defaultInstance;
@$pb.TagNumber(1)
$0.CenterBox get box => $_getN(0);
@$pb.TagNumber(1)
set box($0.CenterBox v) { setField(1, v); }
set box($0.CenterBox v) {
setField(1, v);
}
@$pb.TagNumber(1)
$core.bool hasBox() => $_has(0);
@$pb.TagNumber(1)
@@ -75,7 +86,10 @@ class Detection extends $pb.GeneratedMessage {
@$pb.TagNumber(2)
$1.EPoint get landmarks => $_getN(1);
@$pb.TagNumber(2)
set landmarks($1.EPoint v) { setField(2, v); }
set landmarks($1.EPoint v) {
setField(2, v);
}
@$pb.TagNumber(2)
$core.bool hasLandmarks() => $_has(1);
@$pb.TagNumber(2)
@@ -103,26 +117,33 @@ class Face extends $pb.GeneratedMessage {
return $result;
}
Face._() : super();
factory Face.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory Face.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
factory Face.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory Face.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Face', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'Face',
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'id')
..aOM<Detection>(2, _omitFieldNames ? '' : 'detection', subBuilder: Detection.create)
..a<$core.double>(3, _omitFieldNames ? '' : 'confidence', $pb.PbFieldType.OF)
..hasRequiredFields = false
;
..aOM<Detection>(2, _omitFieldNames ? '' : 'detection',
subBuilder: Detection.create)
..a<$core.double>(
3, _omitFieldNames ? '' : 'confidence', $pb.PbFieldType.OF)
..hasRequiredFields = false;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
Face clone() => Face()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
Face copyWith(void Function(Face) updates) => super.copyWith((message) => updates(message as Face)) as Face;
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
Face copyWith(void Function(Face) updates) =>
super.copyWith((message) => updates(message as Face)) as Face;
$pb.BuilderInfo get info_ => _i;
@@ -131,13 +152,17 @@ class Face extends $pb.GeneratedMessage {
Face createEmptyInstance() => create();
static $pb.PbList<Face> createRepeated() => $pb.PbList<Face>();
@$core.pragma('dart2js:noInline')
static Face getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Face>(create);
static Face getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Face>(create);
static Face? _defaultInstance;
@$pb.TagNumber(1)
$core.String get id => $_getSZ(0);
@$pb.TagNumber(1)
set id($core.String v) { $_setString(0, v); }
set id($core.String v) {
$_setString(0, v);
}
@$pb.TagNumber(1)
$core.bool hasId() => $_has(0);
@$pb.TagNumber(1)
@@ -146,7 +171,10 @@ class Face extends $pb.GeneratedMessage {
@$pb.TagNumber(2)
Detection get detection => $_getN(1);
@$pb.TagNumber(2)
set detection(Detection v) { setField(2, v); }
set detection(Detection v) {
setField(2, v);
}
@$pb.TagNumber(2)
$core.bool hasDetection() => $_has(1);
@$pb.TagNumber(2)
@@ -157,13 +185,16 @@ class Face extends $pb.GeneratedMessage {
@$pb.TagNumber(3)
$core.double get confidence => $_getN(2);
@$pb.TagNumber(3)
set confidence($core.double v) { $_setFloat(2, v); }
set confidence($core.double v) {
$_setFloat(2, v);
}
@$pb.TagNumber(3)
$core.bool hasConfidence() => $_has(2);
@$pb.TagNumber(3)
void clearConfidence() => clearField(3);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
const _omitMessageNames =
$core.bool.fromEnvironment('protobuf.omit_message_names');

View File

@@ -8,4 +8,3 @@
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View File

@@ -17,8 +17,26 @@ import 'dart:typed_data' as $typed_data;
const Detection$json = {
'1': 'Detection',
'2': [
{'1': 'box', '3': 1, '4': 1, '5': 11, '6': '.ente.common.CenterBox', '9': 0, '10': 'box', '17': true},
{'1': 'landmarks', '3': 2, '4': 1, '5': 11, '6': '.ente.common.EPoint', '9': 1, '10': 'landmarks', '17': true},
{
'1': 'box',
'3': 1,
'4': 1,
'5': 11,
'6': '.ente.common.CenterBox',
'9': 0,
'10': 'box',
'17': true
},
{
'1': 'landmarks',
'3': 2,
'4': 1,
'5': 11,
'6': '.ente.common.EPoint',
'9': 1,
'10': 'landmarks',
'17': true
},
],
'8': [
{'1': '_box'},
@@ -37,8 +55,25 @@ const Face$json = {
'1': 'Face',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 9, '9': 0, '10': 'id', '17': true},
{'1': 'detection', '3': 2, '4': 1, '5': 11, '6': '.ente.ml.Detection', '9': 1, '10': 'detection', '17': true},
{'1': 'confidence', '3': 3, '4': 1, '5': 2, '9': 2, '10': 'confidence', '17': true},
{
'1': 'detection',
'3': 2,
'4': 1,
'5': 11,
'6': '.ente.ml.Detection',
'9': 1,
'10': 'detection',
'17': true
},
{
'1': 'confidence',
'3': 3,
'4': 1,
'5': 2,
'9': 2,
'10': 'confidence',
'17': true
},
],
'8': [
{'1': '_id'},
@@ -52,4 +87,3 @@ final $typed_data.Uint8List faceDescriptor = $convert.base64Decode(
'CgRGYWNlEhMKAmlkGAEgASgJSABSAmlkiAEBEjUKCWRldGVjdGlvbhgCIAEoCzISLmVudGUubW'
'wuRGV0ZWN0aW9uSAFSCWRldGVjdGlvbogBARIjCgpjb25maWRlbmNlGAMgASgCSAJSCmNvbmZp'
'ZGVuY2WIAQFCBQoDX2lkQgwKCl9kZXRlY3Rpb25CDQoLX2NvbmZpZGVuY2U=');

View File

@@ -11,4 +11,3 @@
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'face.pb.dart';

View File

@@ -31,25 +31,30 @@ class FileML extends $pb.GeneratedMessage {
return $result;
}
FileML._() : super();
factory FileML.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory FileML.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
factory FileML.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory FileML.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'FileML', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'FileML',
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'),
createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'id')
..p<$core.double>(2, _omitFieldNames ? '' : 'clip', $pb.PbFieldType.KD)
..hasRequiredFields = false
;
..hasRequiredFields = false;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
FileML clone() => FileML()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
FileML copyWith(void Function(FileML) updates) => super.copyWith((message) => updates(message as FileML)) as FileML;
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
FileML copyWith(void Function(FileML) updates) =>
super.copyWith((message) => updates(message as FileML)) as FileML;
$pb.BuilderInfo get info_ => _i;
@@ -58,13 +63,17 @@ class FileML extends $pb.GeneratedMessage {
FileML createEmptyInstance() => create();
static $pb.PbList<FileML> createRepeated() => $pb.PbList<FileML>();
@$core.pragma('dart2js:noInline')
static FileML getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FileML>(create);
static FileML getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FileML>(create);
static FileML? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get id => $_getI64(0);
@$pb.TagNumber(1)
set id($fixnum.Int64 v) { $_setInt64(0, v); }
set id($fixnum.Int64 v) {
$_setInt64(0, v);
}
@$pb.TagNumber(1)
$core.bool hasId() => $_has(0);
@$pb.TagNumber(1)
@@ -101,28 +110,34 @@ class FileFaces extends $pb.GeneratedMessage {
return $result;
}
FileFaces._() : super();
factory FileFaces.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory FileFaces.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
factory FileFaces.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory FileFaces.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'FileFaces', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
..pc<$2.Face>(1, _omitFieldNames ? '' : 'faces', $pb.PbFieldType.PM, subBuilder: $2.Face.create)
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'FileFaces',
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'),
createEmptyInstance: create)
..pc<$2.Face>(1, _omitFieldNames ? '' : 'faces', $pb.PbFieldType.PM,
subBuilder: $2.Face.create)
..a<$core.int>(2, _omitFieldNames ? '' : 'height', $pb.PbFieldType.O3)
..a<$core.int>(3, _omitFieldNames ? '' : 'width', $pb.PbFieldType.O3)
..a<$core.int>(4, _omitFieldNames ? '' : 'version', $pb.PbFieldType.O3)
..aOS(5, _omitFieldNames ? '' : 'error')
..hasRequiredFields = false
;
..hasRequiredFields = false;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
FileFaces clone() => FileFaces()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
FileFaces copyWith(void Function(FileFaces) updates) => super.copyWith((message) => updates(message as FileFaces)) as FileFaces;
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
FileFaces copyWith(void Function(FileFaces) updates) =>
super.copyWith((message) => updates(message as FileFaces)) as FileFaces;
$pb.BuilderInfo get info_ => _i;
@@ -131,7 +146,8 @@ class FileFaces extends $pb.GeneratedMessage {
FileFaces createEmptyInstance() => create();
static $pb.PbList<FileFaces> createRepeated() => $pb.PbList<FileFaces>();
@$core.pragma('dart2js:noInline')
static FileFaces getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FileFaces>(create);
static FileFaces getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FileFaces>(create);
static FileFaces? _defaultInstance;
@$pb.TagNumber(1)
@@ -140,7 +156,10 @@ class FileFaces extends $pb.GeneratedMessage {
@$pb.TagNumber(2)
$core.int get height => $_getIZ(1);
@$pb.TagNumber(2)
set height($core.int v) { $_setSignedInt32(1, v); }
set height($core.int v) {
$_setSignedInt32(1, v);
}
@$pb.TagNumber(2)
$core.bool hasHeight() => $_has(1);
@$pb.TagNumber(2)
@@ -149,7 +168,10 @@ class FileFaces extends $pb.GeneratedMessage {
@$pb.TagNumber(3)
$core.int get width => $_getIZ(2);
@$pb.TagNumber(3)
set width($core.int v) { $_setSignedInt32(2, v); }
set width($core.int v) {
$_setSignedInt32(2, v);
}
@$pb.TagNumber(3)
$core.bool hasWidth() => $_has(2);
@$pb.TagNumber(3)
@@ -158,7 +180,10 @@ class FileFaces extends $pb.GeneratedMessage {
@$pb.TagNumber(4)
$core.int get version => $_getIZ(3);
@$pb.TagNumber(4)
set version($core.int v) { $_setSignedInt32(3, v); }
set version($core.int v) {
$_setSignedInt32(3, v);
}
@$pb.TagNumber(4)
$core.bool hasVersion() => $_has(3);
@$pb.TagNumber(4)
@@ -167,13 +192,16 @@ class FileFaces extends $pb.GeneratedMessage {
@$pb.TagNumber(5)
$core.String get error => $_getSZ(4);
@$pb.TagNumber(5)
set error($core.String v) { $_setString(4, v); }
set error($core.String v) {
$_setString(4, v);
}
@$pb.TagNumber(5)
$core.bool hasError() => $_has(4);
@$pb.TagNumber(5)
void clearError() => clearField(5);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
const _omitMessageNames =
$core.bool.fromEnvironment('protobuf.omit_message_names');

View File

@@ -8,4 +8,3 @@
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View File

@@ -34,10 +34,25 @@ final $typed_data.Uint8List fileMLDescriptor = $convert.base64Decode(
const FileFaces$json = {
'1': 'FileFaces',
'2': [
{'1': 'faces', '3': 1, '4': 3, '5': 11, '6': '.ente.ml.Face', '10': 'faces'},
{
'1': 'faces',
'3': 1,
'4': 3,
'5': 11,
'6': '.ente.ml.Face',
'10': 'faces'
},
{'1': 'height', '3': 2, '4': 1, '5': 5, '9': 0, '10': 'height', '17': true},
{'1': 'width', '3': 3, '4': 1, '5': 5, '9': 1, '10': 'width', '17': true},
{'1': 'version', '3': 4, '4': 1, '5': 5, '9': 2, '10': 'version', '17': true},
{
'1': 'version',
'3': 4,
'4': 1,
'5': 5,
'9': 2,
'10': 'version',
'17': true
},
{'1': 'error', '3': 5, '4': 1, '5': 9, '9': 3, '10': 'error', '17': true},
],
'8': [
@@ -54,4 +69,3 @@ final $typed_data.Uint8List fileFacesDescriptor = $convert.base64Decode(
'dodBgCIAEoBUgAUgZoZWlnaHSIAQESGQoFd2lkdGgYAyABKAVIAVIFd2lkdGiIAQESHQoHdmVy'
'c2lvbhgEIAEoBUgCUgd2ZXJzaW9uiAEBEhkKBWVycm9yGAUgASgJSANSBWVycm9yiAEBQgkKB1'
'9oZWlnaHRCCAoGX3dpZHRoQgoKCF92ZXJzaW9uQggKBl9lcnJvcg==');

View File

@@ -11,4 +11,3 @@
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'fileml.pb.dart';

View File

@@ -44,6 +44,7 @@ import 'package:photos/services/sync/local_sync_service.dart';
import 'package:photos/services/sync/remote_sync_service.dart';
import "package:photos/services/sync/sync_service.dart";
import "package:photos/services/wake_lock_service.dart";
import "package:photos/src/rust/frb_generated.dart";
import 'package:photos/ui/tools/app_lock.dart';
import 'package:photos/ui/tools/lock_screen.dart';
import "package:photos/utils/email_util.dart";
@@ -65,6 +66,7 @@ const kFGTaskDeathTimeoutInMicroseconds = 5000000;
void main() async {
debugRepaintRainbowEnabled = false;
await RustLib.init();
WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized();
@@ -455,3 +457,4 @@ void _scheduleSuicide(Duration duration, [String? taskID]) {
_killBGTask(taskID);
});
}

View File

@@ -5,7 +5,6 @@ class SetupSRPRequest {
final String srpA;
final bool isUpdate;
SetupSRPRequest({
required this.srpUserID,
required this.srpSalt,
@@ -82,6 +81,7 @@ class CompleteSRPSetupRequest {
);
}
}
class SrpAttributes {
final String srpUserID;
final String srpSalt;

View File

@@ -507,7 +507,8 @@ ClusteringResult _runCompleteClustering(Map args) {
EVector.fromBuffer(entry.value).values,
dtype: DType.float32,
),
fileCreationTime: fileIDToCreationTime?[getFileIdFromFaceId<int>(entry.key)],
fileCreationTime:
fileIDToCreationTime?[getFileIdFromFaceId<int>(entry.key)],
),
);
}

View File

@@ -82,7 +82,11 @@ class FaceDetectionService extends MlModel {
'Face detection is finished, in ${inferenceTime.difference(startTime).inMilliseconds} ms (preprocessing: $preprocessingMs ms, inference: $inferenceMs ms)',
);
} catch (e, s) {
_logger.severe('Error while running inference (PlatformPlugin: ${MlModel.usePlatformPlugin})', e, s);
_logger.severe(
'Error while running inference (PlatformPlugin: ${MlModel.usePlatformPlugin})',
e,
s,
);
throw YOLOFaceInterpreterRunException();
}
try {

View File

@@ -1 +1 @@
const imageEmbeddingsKey = "imageEmbeddings";
const imageEmbeddingsKey = "imageEmbeddings";

View File

@@ -1,4 +1,3 @@
class GeneralFaceMlException implements Exception {
final String message;
@@ -26,4 +25,4 @@ class CouldNotRunFaceDetector implements Exception {}
class CouldNotWarpAffine implements Exception {}
class CouldNotRunFaceEmbeddor implements Exception {}
class CouldNotRunFaceEmbeddor implements Exception {}

View File

@@ -3,4 +3,4 @@ class QueryResult {
final double score;
QueryResult(this.id, this.score);
}
}

View File

@@ -8,6 +8,7 @@ import "package:logging/logging.dart";
import "package:photos/core/cache/lru_map.dart";
import "package:photos/core/event_bus.dart";
import "package:photos/db/files_db.dart";
import "package:photos/db/ml/clip_vector_db.dart";
import "package:photos/db/ml/db.dart";
import 'package:photos/events/embedding_updated_event.dart';
import "package:photos/models/file/file.dart";
@@ -265,10 +266,28 @@ class SemanticSearchService {
required Map<String, double> minimumSimilarityMap,
}) async {
final startTime = DateTime.now();
await _cacheClipVectors();
final Map<String, List<QueryResult>> queryResults = await MLComputer
.instance
.computeBulkSimilarities(textQueryToEmbeddingMap, minimumSimilarityMap);
if (kDebugMode) {
for (final queryText in textQueryToEmbeddingMap.keys) {
final embedding = textQueryToEmbeddingMap[queryText]!;
dev.log("CLIPTEXT Query: $queryText, embedding: $embedding");
}
}
late final Map<String, List<QueryResult>> queryResults;
if (flagService.enableVectorDb) {
// TODO: lau: Remove this artificial delay later
_logger.info("Using ClipVectorDB for similarity computation in 500ms");
await Future.delayed(const Duration(milliseconds: 500));
queryResults = await ClipVectorDB.instance.computeBulkSimilarities(
textQueryToEmbeddingMap,
minimumSimilarityMap,
);
} else {
await _cacheClipVectors();
queryResults = await MLComputer.instance.computeBulkSimilarities(
textQueryToEmbeddingMap,
minimumSimilarityMap,
);
}
final endTime = DateTime.now();
_logger.info(
"computingSimilarities took for ${textQueryToEmbeddingMap.length} queries " +

View File

@@ -0,0 +1,12 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.9.0.
// ignore_for_file: require_trailing_commas
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
import 'package:photos/src/rust/frb_generated.dart';
String greet({required String name}) =>
RustLib.instance.api.crateApiSimpleGreet(name: name);

View File

@@ -0,0 +1,48 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.9.0.
// ignore_for_file: require_trailing_commas
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
import 'package:photos/src/rust/frb_generated.dart';
// These functions are ignored because they are not marked as `pub`: `ensure_capacity`, `save_index`
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<VectorDB>>
abstract class VectorDb implements RustOpaqueInterface {
Future<void> addVector({required BigInt key, required List<double> vector});
Future<void> bulkAddVectors(
{required Uint64List keys, required List<Float32List> vectors});
Future<List<Float32List>> bulkGetVectors({required Uint64List keys});
Future<BigInt> bulkRemoveVectors({required Uint64List keys});
Future<(List<Uint64List>, List<Float32List>)> bulkSearchVectors(
{required List<Float32List> queries, required BigInt count});
/// Check if a vector with the given key exists in the index.
/// `true` if the index contains the vector with the given key, `false` otherwise.
Future<bool> containsVector({required BigInt key});
Future<void> deleteIndex();
Future<(BigInt, BigInt, BigInt, BigInt, BigInt, BigInt, BigInt)>
getIndexStats();
Future<Float32List> getVector({required BigInt key});
factory VectorDb({required String filePath, required BigInt dimensions}) =>
RustLib.instance.api.crateApiUsearchApiVectorDbNew(
filePath: filePath, dimensions: dimensions);
Future<BigInt> removeVector({required BigInt key});
Future<void> resetIndex();
Future<(Uint64List, Float32List)> searchVectors(
{required List<double> query, required BigInt count});
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,293 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.9.0.
// ignore_for_file: require_trailing_commas
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
import 'dart:async';
import 'dart:convert';
import 'dart:ffi' as ffi;
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart';
import 'package:photos/src/rust/api/simple.dart';
import 'package:photos/src/rust/api/usearch_api.dart';
import 'package:photos/src/rust/frb_generated.dart';
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
RustLibApiImplPlatform({
required super.handler,
required super.wire,
required super.generalizedFrbRustBinding,
required super.portManager,
});
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_VectorDbPtr => wire
._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDBPtr;
@protected
VectorDb
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
dynamic raw);
@protected
VectorDb
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
dynamic raw);
@protected
VectorDb
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
dynamic raw);
@protected
String dco_decode_String(dynamic raw);
@protected
bool dco_decode_bool(dynamic raw);
@protected
double dco_decode_f_32(dynamic raw);
@protected
List<Float32List> dco_decode_list_list_prim_f_32_strict(dynamic raw);
@protected
List<Uint64List> dco_decode_list_list_prim_u_64_strict(dynamic raw);
@protected
List<double> dco_decode_list_prim_f_32_loose(dynamic raw);
@protected
Float32List dco_decode_list_prim_f_32_strict(dynamic raw);
@protected
Uint64List dco_decode_list_prim_u_64_strict(dynamic raw);
@protected
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
@protected
(List<Uint64List>, List<Float32List>)
dco_decode_record_list_list_prim_u_64_strict_list_list_prim_f_32_strict(
dynamic raw);
@protected
(
Uint64List,
Float32List
) dco_decode_record_list_prim_u_64_strict_list_prim_f_32_strict(dynamic raw);
@protected
(BigInt, BigInt, BigInt, BigInt, BigInt, BigInt, BigInt)
dco_decode_record_usize_usize_usize_usize_usize_usize_usize(dynamic raw);
@protected
BigInt dco_decode_u_64(dynamic raw);
@protected
int dco_decode_u_8(dynamic raw);
@protected
void dco_decode_unit(dynamic raw);
@protected
BigInt dco_decode_usize(dynamic raw);
@protected
VectorDb
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
SseDeserializer deserializer);
@protected
VectorDb
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
SseDeserializer deserializer);
@protected
VectorDb
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
SseDeserializer deserializer);
@protected
String sse_decode_String(SseDeserializer deserializer);
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
double sse_decode_f_32(SseDeserializer deserializer);
@protected
List<Float32List> sse_decode_list_list_prim_f_32_strict(
SseDeserializer deserializer);
@protected
List<Uint64List> sse_decode_list_list_prim_u_64_strict(
SseDeserializer deserializer);
@protected
List<double> sse_decode_list_prim_f_32_loose(SseDeserializer deserializer);
@protected
Float32List sse_decode_list_prim_f_32_strict(SseDeserializer deserializer);
@protected
Uint64List sse_decode_list_prim_u_64_strict(SseDeserializer deserializer);
@protected
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
@protected
(List<Uint64List>, List<Float32List>)
sse_decode_record_list_list_prim_u_64_strict_list_list_prim_f_32_strict(
SseDeserializer deserializer);
@protected
(Uint64List, Float32List)
sse_decode_record_list_prim_u_64_strict_list_prim_f_32_strict(
SseDeserializer deserializer);
@protected
(BigInt, BigInt, BigInt, BigInt, BigInt, BigInt, BigInt)
sse_decode_record_usize_usize_usize_usize_usize_usize_usize(
SseDeserializer deserializer);
@protected
BigInt sse_decode_u_64(SseDeserializer deserializer);
@protected
int sse_decode_u_8(SseDeserializer deserializer);
@protected
void sse_decode_unit(SseDeserializer deserializer);
@protected
BigInt sse_decode_usize(SseDeserializer deserializer);
@protected
int sse_decode_i_32(SseDeserializer deserializer);
@protected
void
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
VectorDb self, SseSerializer serializer);
@protected
void
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
VectorDb self, SseSerializer serializer);
@protected
void
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
VectorDb self, SseSerializer serializer);
@protected
void sse_encode_String(String self, SseSerializer serializer);
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
@protected
void sse_encode_f_32(double self, SseSerializer serializer);
@protected
void sse_encode_list_list_prim_f_32_strict(
List<Float32List> self, SseSerializer serializer);
@protected
void sse_encode_list_list_prim_u_64_strict(
List<Uint64List> self, SseSerializer serializer);
@protected
void sse_encode_list_prim_f_32_loose(
List<double> self, SseSerializer serializer);
@protected
void sse_encode_list_prim_f_32_strict(
Float32List self, SseSerializer serializer);
@protected
void sse_encode_list_prim_u_64_strict(
Uint64List self, SseSerializer serializer);
@protected
void sse_encode_list_prim_u_8_strict(
Uint8List self, SseSerializer serializer);
@protected
void sse_encode_record_list_list_prim_u_64_strict_list_list_prim_f_32_strict(
(List<Uint64List>, List<Float32List>) self, SseSerializer serializer);
@protected
void sse_encode_record_list_prim_u_64_strict_list_prim_f_32_strict(
(Uint64List, Float32List) self, SseSerializer serializer);
@protected
void sse_encode_record_usize_usize_usize_usize_usize_usize_usize(
(BigInt, BigInt, BigInt, BigInt, BigInt, BigInt, BigInt) self,
SseSerializer serializer);
@protected
void sse_encode_u_64(BigInt self, SseSerializer serializer);
@protected
void sse_encode_u_8(int self, SseSerializer serializer);
@protected
void sse_encode_unit(void self, SseSerializer serializer);
@protected
void sse_encode_usize(BigInt self, SseSerializer serializer);
@protected
void sse_encode_i_32(int self, SseSerializer serializer);
}
// Section: wire_class
class RustLibWire implements BaseWire {
factory RustLibWire.fromExternalLibrary(ExternalLibrary lib) =>
RustLibWire(lib.ffiDynamicLibrary);
/// Holds the symbol lookup function.
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
_lookup;
/// The symbols are looked up in [dynamicLibrary].
RustLibWire(ffi.DynamicLibrary dynamicLibrary)
: _lookup = dynamicLibrary.lookup;
void
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
ffi.Pointer<ffi.Void> ptr,
) {
return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
ptr,
);
}
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDBPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
'frbgen_photos_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB');
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB =
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDBPtr
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
void
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
ffi.Pointer<ffi.Void> ptr,
) {
return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
ptr,
);
}
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDBPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
'frbgen_photos_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB');
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB =
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDBPtr
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
}

View File

@@ -18,8 +18,9 @@ class BottomOfTitleBarWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: showCloseButton ? MainAxisAlignment.spaceBetween :
MainAxisAlignment.start,
mainAxisAlignment: showCloseButton
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.start,
children: [
Flexible(
child: Padding(

View File

@@ -10,6 +10,7 @@ import 'package:photos/ui/components/buttons/icon_button_widget.dart';
import "package:photos/ui/settings/backup/backup_folder_selection_page.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/navigation_util.dart";
class HomeHeaderWidget extends StatefulWidget {
final Widget centerWidget;
const HomeHeaderWidget({required this.centerWidget, super.key});

View File

@@ -100,7 +100,7 @@ class ExtentsPageView extends StatefulWidget {
int? itemCount,
this.dragStartBehavior = DragStartBehavior.start,
this.openDrawer,
}) : childrenDelegate = SliverChildBuilderDelegate(
}) : childrenDelegate = SliverChildBuilderDelegate(
itemBuilder,
childCount: itemCount,
addAutomaticKeepAlives: false,
@@ -198,7 +198,7 @@ class ExtentsPageView extends StatefulWidget {
required this.childrenDelegate,
this.dragStartBehavior = DragStartBehavior.start,
this.openDrawer,
}) : extents = 0;
}) : extents = 0;
/// The number of pages to build off screen.
///

View File

@@ -9,8 +9,7 @@ import "package:photos/utils/navigation_util.dart";
class HeaderErrorWidget extends StatelessWidget {
final Error? _error;
const HeaderErrorWidget({super.key, required Error? error})
: _error = error;
const HeaderErrorWidget({super.key, required Error? error}) : _error = error;
@override
Widget build(BuildContext context) {

View File

@@ -1,20 +1,25 @@
import "dart:async";
import "dart:io";
import "dart:math";
import "dart:ui";
import "package:flutter/cupertino.dart";
import "package:flutter/material.dart";
import "package:photos/core/configuration.dart";
import "package:photos/models/file/file_type.dart";
import "package:photos/models/memories/memory.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/smart_memories_service.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/theme/text_style.dart";
import "package:photos/ui/actions/file/file_actions.dart";
import "package:photos/ui/home/memories/memory_progress_indicator.dart";
import "package:photos/ui/viewer/file/file_widget.dart";
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
import "package:photos/ui/viewer/file_details/favorite_widget.dart";
import "package:photos/utils/file_util.dart";
import "package:photos/utils/share_util.dart";
import "package:step_progress_indicator/step_progress_indicator.dart";
// import "package:step_progress_indicator/step_progress_indicator.dart";
//There are two states of variables that FullScreenMemory depends on:
//1. The list of memories
@@ -131,6 +136,10 @@ class FullScreenMemory extends StatefulWidget {
class _FullScreenMemoryState extends State<FullScreenMemory> {
PageController? _pageController;
final _showTitle = ValueNotifier<bool>(true);
AnimationController? _progressAnimationController;
AnimationController? _zoomAnimationController;
final ValueNotifier<Duration> durationNotifier =
ValueNotifier(const Duration(seconds: 5));
@override
void initState() {
@@ -148,13 +157,48 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
void dispose() {
_pageController?.dispose();
_showTitle.dispose();
durationNotifier.dispose();
super.dispose();
}
void _toggleAnimation(bool pause) {
if (_progressAnimationController != null) {
if (pause) {
_progressAnimationController!.stop();
} else {
_progressAnimationController!.forward();
}
}
if (_zoomAnimationController != null) {
if (pause) {
_zoomAnimationController!.stop();
} else {
_zoomAnimationController!.forward();
}
}
}
void onFinalFileLoad(int duration) {
if (_progressAnimationController!.isAnimating == true) {
_progressAnimationController!.stop();
}
durationNotifier.value = Duration(seconds: duration);
_progressAnimationController
?..stop()
..reset()
..duration = durationNotifier.value
..forward();
_zoomAnimationController
?..stop()
..reset()
..forward();
}
@override
Widget build(BuildContext context) {
final inheritedData = FullScreenMemoryData.of(context)!;
final showStepProgressIndicator = inheritedData.memories.length < 60;
return Scaffold(
backgroundColor: Colors.black,
extendBodyBehindAppBar: true,
@@ -180,17 +224,34 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
showStepProgressIndicator
? StepProgressIndicator(
totalSteps: inheritedData.memories.length,
currentStep: value + 1,
size: 2,
selectedColor: Colors.white, //same for both themes
unselectedColor: Colors.white.withOpacity(0.4),
? ValueListenableBuilder<Duration>(
valueListenable: durationNotifier,
builder: (context, duration, _) {
return NewProgressIndicator(
totalSteps: inheritedData.memories.length,
currentIndex: value,
selectedColor: Colors.white,
unselectedColor: Colors.white.withOpacity(0.4),
duration: duration,
animationController: (controller) {
_progressAnimationController = controller;
},
onComplete: () {
final currentIndex =
inheritedData.indexNotifier.value;
if (currentIndex <
inheritedData.memories.length - 1) {
_pageController!.nextPage(
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);
}
},
);
},
)
: const SizedBox.shrink(),
const SizedBox(
height: 10,
),
const SizedBox(height: 10),
Row(
children: [
child!,
@@ -231,16 +292,20 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
body: Stack(
alignment: Alignment.bottomCenter,
children: [
const MemoryBackDrop(),
PageView.builder(
controller: _pageController ??= PageController(
initialPage: widget.initialIndex,
),
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
if (index < inheritedData.memories.length - 1) {
final nextFile = inheritedData.memories[index + 1].file;
preloadThumbnail(nextFile);
preloadFile(nextFile);
}
final currentFile = inheritedData.memories[index].file;
final isVideo = currentFile.fileType == FileType.video;
return GestureDetector(
onTapDown: (TapDownDetails details) {
final screenWidth = MediaQuery.of(context).size.width;
@@ -262,17 +327,39 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
}
}
},
child: FileWidget(
inheritedData.memories[index].file,
autoPlay: false,
tagPrefix: "memories",
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
onLongPress: () {
_toggleAnimation(true);
},
onLongPressUp: () {
_toggleAnimation(false);
},
child: MemoriesZoomWidget(
scaleController: (controller) {
_zoomAnimationController = controller;
},
zoomIn: index % 2 == 0,
isVideo: isVideo,
child: FileWidget(
inheritedData.memories[index].file,
autoPlay: false,
tagPrefix: "memories",
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
isFromMemories: true,
playbackCallback: (isPlaying) {
isPlaying
? _toggleAnimation(false)
: _toggleAnimation(true);
},
onFinalFileLoad: (duration) {
onFinalFileLoad(duration);
},
),
),
);
},
onPageChanged: (index) {
onPageChanged: (index) async {
unawaited(
memoriesCacheService.markMemoryAsSeen(
inheritedData.memories[index],
@@ -450,3 +537,148 @@ class BottomGradient extends StatelessWidget {
);
}
}
class MemoryBackDrop extends StatelessWidget {
const MemoryBackDrop({super.key});
@override
Widget build(BuildContext context) {
final inheritedData = FullScreenMemoryData.of(context)!;
return ValueListenableBuilder(
valueListenable: inheritedData.indexNotifier,
builder: (context, value, _) {
final currentFile = inheritedData.memories[value].file;
if (currentFile.fileType == FileType.video ||
currentFile.fileType == FileType.livePhoto) {
return const SizedBox.shrink();
}
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
child: Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
color: Colors.transparent,
),
ThumbnailWidget(
currentFile,
shouldShowSyncStatus: false,
shouldShowFavoriteIcon: false,
),
BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 100,
sigmaY: 100,
),
child: Container(
color: Colors.transparent,
),
),
],
),
);
},
);
}
}
class MemoriesZoomWidget extends StatefulWidget {
final Widget child;
final bool isVideo;
final void Function(AnimationController)? scaleController;
final bool zoomIn;
const MemoriesZoomWidget({
super.key,
required this.child,
required this.isVideo,
required this.zoomIn,
this.scaleController,
});
@override
State<MemoriesZoomWidget> createState() => _MemoriesZoomWidgetState();
}
class _MemoriesZoomWidgetState extends State<MemoriesZoomWidget>
with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<Offset> _panAnimation;
Random random = Random();
@override
void initState() {
super.initState();
_initAnimation();
}
void _initAnimation() {
_controller = AnimationController(
vsync: this,
duration: const Duration(
seconds: 5,
),
);
final startScale = widget.zoomIn ? 1.05 : 1.15;
final endScale = widget.zoomIn ? 1.15 : 1.05;
final startX = (random.nextDouble() - 0.5) * 0.1;
final startY = (random.nextDouble() - 0.5) * 0.1;
final endX = (random.nextDouble() - 0.5) * 0.1;
final endY = (random.nextDouble() - 0.5) * 0.1;
_scaleAnimation = Tween<double>(
begin: startScale,
end: endScale,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
_panAnimation = Tween<Offset>(
begin: Offset(startX, startY),
end: Offset(endX, endY),
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
if (widget.scaleController != null) {
widget.scaleController!(_controller);
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.isVideo
? widget.child
: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Transform.translate(
offset: Offset(
_panAnimation.value.dx * 100,
_panAnimation.value.dy * 100,
),
child: widget.child,
),
);
},
);
}
}

View File

@@ -130,17 +130,11 @@ class _MemoriesWidgetState extends State<MemoriesWidget> {
controller: _controller,
itemCount: collatedMemories.length,
itemBuilder: (context, itemIndex) {
final maxScaleOffsetX =
_maxWidth + MemoryCoverWidget.horizontalPadding * 2;
final offsetOfItem =
(_maxWidth + MemoryCoverWidget.horizontalPadding * 2) * itemIndex;
return MemoryCoverWidget(
memories: collatedMemories[itemIndex].$1,
controller: _controller,
offsetOfItem: offsetOfItem,
maxHeight: _maxHeight,
maxWidth: _maxWidth,
maxScaleOffsetX: maxScaleOffsetX,
title: collatedMemories[itemIndex].$2,
);
},

View File

@@ -11,22 +11,18 @@ import "package:photos/utils/navigation_util.dart";
class MemoryCoverWidget extends StatefulWidget {
final List<Memory> memories;
final ScrollController controller;
final double offsetOfItem;
final double maxHeight;
final double maxWidth;
static const outerStrokeWidth = 1.0;
static const aspectRatio = 0.68;
static const horizontalPadding = 2.5;
final double maxScaleOffsetX;
final String title;
const MemoryCoverWidget({
required this.memories,
required this.controller,
required this.offsetOfItem,
required this.maxHeight,
required this.maxWidth,
required this.maxScaleOffsetX,
required this.title,
super.key,
});
@@ -44,7 +40,6 @@ class _MemoryCoverWidgetState extends State<MemoryCoverWidget> {
return const SizedBox.shrink();
}
final widthOfScreen = MediaQuery.sizeOf(context).width;
final index = _getNextMemoryIndex();
final title = widget.title;
@@ -56,9 +51,6 @@ class _MemoryCoverWidgetState extends State<MemoryCoverWidget> {
return AnimatedBuilder(
animation: widget.controller,
builder: (context, child) {
final diff = (widget.controller.offset - widget.offsetOfItem) +
widget.maxScaleOffsetX;
final scale = 1 - (diff / widthOfScreen).abs() / 3.7;
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: MemoryCoverWidget.horizontalPadding,
@@ -81,8 +73,8 @@ class _MemoryCoverWidgetState extends State<MemoryCoverWidget> {
child: Row(
children: [
Container(
height: widget.maxHeight * scale,
width: widget.maxWidth * scale,
height: widget.maxHeight ,
width: widget.maxWidth ,
decoration: BoxDecoration(
boxShadow: brightness == Brightness.dark
? [
@@ -122,29 +114,26 @@ class _MemoryCoverWidgetState extends State<MemoryCoverWidget> {
),
),
Positioned(
bottom: 8 * scale,
child: Transform.scale(
scale: scale,
child: SizedBox(
width: widget.maxWidth,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
),
child: Hero(
tag: title,
child: Center(
child: Text(
title,
style: getEnteTextTheme(context)
.miniBold
.copyWith(
color: isSeen
? textFaintDark
: Colors.white,
),
textAlign: TextAlign.left,
),
bottom: 8 ,
child: SizedBox(
width: widget.maxWidth,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
),
child: Hero(
tag: title,
child: Center(
child: Text(
title,
style: getEnteTextTheme(context)
.miniBold
.copyWith(
color: isSeen
? textFaintDark
: Colors.white,
),
textAlign: TextAlign.left,
),
),
),
@@ -173,27 +162,24 @@ class _MemoryCoverWidgetState extends State<MemoryCoverWidget> {
),
),
Positioned(
bottom: 8 * scale,
child: Transform.scale(
scale: scale,
child: SizedBox(
width: widget.maxWidth,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
),
child: Hero(
tag: title,
child: Center(
child: Text(
title,
style: getEnteTextTheme(context)
.miniBold
.copyWith(
color: Colors.white,
),
textAlign: TextAlign.left,
),
bottom: 8 ,
child: SizedBox(
width: widget.maxWidth,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
),
child: Hero(
tag: title,
child: Center(
child: Text(
title,
style: getEnteTextTheme(context)
.miniBold
.copyWith(
color: Colors.white,
),
textAlign: TextAlign.left,
),
),
),

View File

@@ -0,0 +1,106 @@
import "package:flutter/material.dart";
class NewProgressIndicator extends StatefulWidget {
final int totalSteps;
final int currentIndex;
final Duration duration;
final Color selectedColor;
final Color unselectedColor;
final double height;
final double gap;
final void Function(AnimationController)? animationController;
final VoidCallback? onComplete;
const NewProgressIndicator({
super.key,
required this.totalSteps,
required this.currentIndex,
this.duration = const Duration(seconds: 5),
this.selectedColor = Colors.white,
this.unselectedColor = Colors.white54,
this.height = 2.0,
this.gap = 4.0,
this.animationController,
this.onComplete,
});
@override
State<NewProgressIndicator> createState() => _NewProgressIndicatorState();
}
class _NewProgressIndicatorState extends State<NewProgressIndicator>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: widget.duration,
);
_animation =
Tween<double>(begin: 0.0, end: 1.0).animate(_animationController);
if (widget.animationController != null) {
widget.animationController!(_animationController);
}
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed && widget.onComplete != null) {
widget.onComplete!();
}
});
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Row(
children: List.generate(widget.totalSteps, (index) {
return Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 4, right: 4),
child: index < widget.currentIndex
? Container(
height: widget.height,
decoration: BoxDecoration(
color: widget.selectedColor,
borderRadius: BorderRadius.circular(2),
),
)
: index == widget.currentIndex
? AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return LinearProgressIndicator(
value: _animation.value,
backgroundColor: widget.unselectedColor,
valueColor: AlwaysStoppedAnimation<Color>(
widget.selectedColor,
),
minHeight: widget.height,
borderRadius: BorderRadius.circular(2),
);
},
)
: Container(
height: widget.height,
decoration: BoxDecoration(
color: widget.unselectedColor,
borderRadius: BorderRadius.circular(2),
),
),
),
);
}),
);
}
}

View File

@@ -1,10 +1,19 @@
import "dart:async";
import "dart:math" show Random;
import "dart:typed_data" show Float32List;
import "package:flutter/foundation.dart" show kDebugMode;
import 'package:flutter/material.dart';
import "package:flutter_rust_bridge/flutter_rust_bridge.dart";
import "package:logging/logging.dart";
import "package:ml_linalg/linalg.dart";
import "package:path_provider/path_provider.dart";
import "package:photos/core/event_bus.dart";
import "package:photos/db/ml/clip_vector_db.dart";
import "package:photos/db/ml/db.dart";
import "package:photos/events/people_changed_event.dart";
import "package:photos/extensions/stop_watch.dart";
import "package:photos/generated/protos/ente/common/vector.pb.dart";
import "package:photos/models/ml/face/person.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
@@ -13,6 +22,8 @@ import 'package:photos/services/machine_learning/ml_service.dart';
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
import "package:photos/services/memory_home_widget_service.dart";
import "package:photos/services/notification_service.dart";
import "package:photos/src/rust/api/simple.dart";
import "package:photos/src/rust/api/usearch_api.dart";
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
@@ -179,6 +190,380 @@ class _MLDebugSectionWidgetState extends State<MLDebugSectionWidget> {
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Do some basic usearch",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
// randomly generate some vectors and keys
final random = Random();
final tenEmbeddings = List.generate(
10,
(index) {
final randomList = List<double>.generate(
192,
(_) => random.nextDouble(), // Values between 0 and 1
);
final randomVector = Vector.fromList(randomList).normalize();
return Float32List.fromList(randomVector.toList());
},
);
final tenKeys = Uint64List.fromList(
List.generate(
tenEmbeddings.length,
(index) => BigInt.from(index + 1),
),
);
final embedDimensions = BigInt.from(tenEmbeddings.first.length);
final indexPath = (await getApplicationSupportDirectory()).path +
"/ml/test/vector_db_index.usearch";
final rustVectorDB = VectorDb(
filePath: indexPath,
dimensions: embedDimensions,
);
await rustVectorDB.resetIndex();
final stats = await rustVectorDB.getIndexStats();
logger.info("vector_db stats: $stats");
await rustVectorDB.bulkAddVectors(
keys: tenKeys,
vectors: tenEmbeddings,
);
final statsAgain = await rustVectorDB.getIndexStats();
logger.info("vector_db stats again: $statsAgain");
final size = statsAgain.$1;
final capacity = statsAgain.$2;
final dimensions = statsAgain.$3;
showShortToast(
context,
"Size: $size, Capacity: $capacity, Dimensions: $dimensions",
);
await rustVectorDB.deleteIndex();
} catch (e, s) {
logger.warning('Rust bridge failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Fill ClipVectorDB",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
final allClip = await MLDataDB.instance.getAllClipVectors();
final allClipSmall = allClip.sublist(0, 15000);
showShortToast(context, "Got all embeddings");
logger.info("Got all embeddings");
final clipVectorDB = ClipVectorDB.instance;
await clipVectorDB.deleteAllEmbeddings();
logger.info("Clean vector DB");
final stats = await clipVectorDB.getIndexStats();
logger.info(stats.toString());
showShortToast(context, stats.toString());
final fileIDs = allClipSmall.map((e) => e.fileID).toList();
final embeddings = allClipSmall
.map((e) => Float32List.fromList(e.vector.toList()))
.toList();
showShortToast(context, "Reshaped embeddings data");
logger.info("Reshaped embeddings data");
final now = DateTime.now();
await clipVectorDB.bulkInsertEmbeddings(
fileIDs: fileIDs,
embeddings: embeddings,
);
final duration = DateTime.now().difference(now);
logger.info(
"ClipVectorDB bulk insert took ${duration.inMilliseconds} ms for ${fileIDs.length} embeddings",
);
final statsAfter = await clipVectorDB.getIndexStats();
logger.info(statsAfter.toString());
showShortToast(context, statsAfter.toString());
} catch (e, s) {
logger.warning('ClipVectorDB migration failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Migrate to ClipVectorDB",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
await MLDataDB.instance.checkMigrateFillClipVectorDB();
showShortToast(context, "Migration done!");
} catch (e, s) {
logger.warning('ClipVectorDB migration failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Show ClipVectorDB stats",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
final clipVectorDB = ClipVectorDB.instance;
final stats = await clipVectorDB.getIndexStats();
logger.info(stats.toString());
showShortToast(context, stats.toString());
} catch (e, s) {
logger.warning('ClipVectorDB stats failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Delete/Empty ClipVectorDB",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
final clipVectorDB = ClipVectorDB.instance;
await clipVectorDB.deleteIndex();
} catch (e, s) {
logger.warning('ClipVectorDB cleanup failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Benchmark Vector DB Face",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
final w = (kDebugMode ? EnteWatch('MLDebugSectionWidget') : null)
?..start();
final persons = await PersonService.instance.getPersons();
w?.log('get all persons for ${persons.length} persons');
String laurensID = '';
for (final person in persons) {
if (person.data.name.toLowerCase().contains('laurens')) {
laurensID = person.remoteID;
}
}
if (laurensID.isEmpty) {
throw Exception('Laurens not found');
}
final laurensFaceIDs =
await MLDataDB.instance.getFaceIDsForPerson(laurensID);
w?.log(
'getting all face ids for laurens (${laurensFaceIDs.length} faces)',
);
final laurensFaceIdToEmbeddingData = await MLDataDB.instance
.getFaceEmbeddingMapForFaces(laurensFaceIDs);
// Fill the vector DB with all embeddings
final laurensFaceIdToFloat32 = laurensFaceIdToEmbeddingData.map(
(key, value) => MapEntry(
key,
Float32List.fromList(EVector.fromBuffer(value).values),
),
);
final keys = Uint64List.fromList(
List.generate(
laurensFaceIdToFloat32.length,
(index) => BigInt.from(index + 1),
),
);
final vectorDB = VectorDb(
filePath: (await getApplicationSupportDirectory()).path +
"/ml/test/vector_db_face_index.usearch",
dimensions: BigInt.from(
laurensFaceIdToFloat32.values.first.length,
),
);
await vectorDB.resetIndex();
await vectorDB.bulkAddVectors(
keys: keys,
vectors: laurensFaceIdToFloat32.values.toList(),
);
// Benchmarking the vector DB
final queries = laurensFaceIdToFloat32.values.toList();
final count = BigInt.from(10);
w?.reset();
final (vectorKeys, distances) = await vectorDB.bulkSearchVectors(
queries: queries,
count: count,
);
w?.log(
'Done with ${queries.length * queries.length} (${queries.length} x ${queries.length}}) embeddings comparisons in vector DB',
);
logger.info(
'vector db results: ${vectorKeys.length} results, first: ${vectorKeys.first}, hundredth: ${vectorKeys[99]}',
);
// Benchmarking our own vector comparisons
final laurensFaceIdToEmbeddingVectors =
laurensFaceIdToEmbeddingData.map(
(key, value) => MapEntry(
key,
Vector.fromList(EVector.fromBuffer(value).values),
),
);
final faceVectors = laurensFaceIdToEmbeddingVectors.values;
w?.reset();
for (final faceVector in faceVectors) {
for (final otherFaceVector in faceVectors) {
final _ = 1 - faceVector.dot(otherFaceVector);
}
}
w?.log(
'Done with ${faceVectors.length * faceVectors.length} (${faceVectors.length} x ${faceVectors.length}}) embeddings comparisons in own method',
);
await vectorDB.deleteIndex();
} catch (e, s) {
logger.warning('vector DB search failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Benchmark Vector DB CLIP",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
final w = (kDebugMode ? EnteWatch('MLDebugSectionWidget') : null)
?..start();
final clipEmbeddings = await mlDataDB.getAllClipVectors();
w?.log(
'getting all clip embeddings (${clipEmbeddings.length} embeddings)',
);
// Fill the vector DB with all embeddings
final clipFloat32 = clipEmbeddings
.map(
(value) => Float32List.fromList(value.vector.toList()),
)
.toList();
final keys = Uint64List.fromList(
List.generate(
clipFloat32.length,
(index) => BigInt.from(index + 1),
),
);
final vectorDB = VectorDb(
filePath: (await getApplicationSupportDirectory()).path +
"/ml/test/vector_db_clip_index.usearch",
dimensions: BigInt.from(
clipFloat32.first.length,
),
);
await vectorDB.resetIndex();
await vectorDB.bulkAddVectors(
keys: keys,
vectors: clipFloat32,
);
// Benchmarking the vector DB
final count = BigInt.from(10);
w?.reset();
final (vectorKeys, distances) = await vectorDB.bulkSearchVectors(
queries: clipFloat32,
count: count,
);
w?.log(
'Done with ${clipFloat32.length * clipFloat32.length} (${clipFloat32.length} x ${clipFloat32.length}}) embeddings comparisons in vector DB',
);
logger.info(
'vector db results: ${vectorKeys.length} results, first: ${vectorKeys.first} with distances ${distances.first}, hundredth: ${vectorKeys[99]} with distances ${distances[99]}',
);
// // Benchmarking our own vector comparisons
// final clipVectors = clipEmbeddings
// .map(
// (value) => value.vector,
// )
// .toList();
// w?.reset();
// int compared = 0;
// int ms = DateTime.now().millisecondsSinceEpoch;
// for (final faceVector in clipVectors) {
// for (final otherFaceVector in clipVectors) {
// final _ = 1 - faceVector.dot(otherFaceVector);
// }
// compared++;
// if (compared % 100 == 0) {
// final now = DateTime.now().millisecondsSinceEpoch;
// logger.info(
// 'Compared next 100 in ${now - ms} ms, progress: ($compared / ${clipVectors.length})',
// );
// ms = now;
// }
// }
// w?.log(
// 'Done with ${clipVectors.length * clipVectors.length} (${clipVectors.length} x ${clipVectors.length}}) embeddings comparisons in own method',
// );
await vectorDB.deleteIndex();
} catch (e, s) {
logger.warning('vector DB search failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Test rust bridge",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
final String greetings = greet(name: "Tom");
const String expected = "Hello, Tom!";
assert(greetings == expected);
debugPrint("String from rust: $greetings");
showShortToast(context, greetings);
} catch (e, s) {
logger.warning('Rust bridge failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: FutureBuilder<IndexStatus>(
future: getIndexStatus(),

View File

@@ -57,57 +57,69 @@ class _MLUserDeveloperOptionsState extends State<MLUserDeveloperOptions> {
),
),
const SizedBox(height: 48),
widget.mlIsEnabled ? ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Purge empty indices",
onTap: () async {
await deleteEmptyIndices(context);
},
) : const SizedBox(),
widget.mlIsEnabled ? const SizedBox(height: 24) : const SizedBox(),
widget.mlIsEnabled ? ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Reset all local ML",
onTap: () async {
await deleteAllLocalML(context);
},
) : const SizedBox(),
widget.mlIsEnabled ? const SizedBox(height: 24) : const SizedBox(),
widget.mlIsEnabled ? MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Remote fetch",
),
menuItemColor: colorScheme.fillFaint,
trailingWidget: ToggleSwitchWidget(
value: () => localSettings.remoteFetchEnabled,
onChanged: () async {
try {
await localSettings.toggleRemoteFetch();
_logger.info(
'Remote fetch is turned ${localSettings.remoteFetchEnabled ? 'on' : 'off'}',
);
if (mounted) {
setState(() {});
}
} catch (e, s) {
_logger.warning(
'Remote fetch toggle failed ',
e,
s,
);
await showGenericErrorDialog(
context: context,
error: e,
);
}
},
),
singleBorderRadius: 8,
alignCaptionedTextToLeft: true,
isBottomBorderRadiusRemoved: true,
isGestureDetectorDisabled: true,
) : const SizedBox(),
widget.mlIsEnabled ? const SizedBox(height: 24) : const SizedBox.shrink(),
widget.mlIsEnabled
? ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Purge empty indices",
onTap: () async {
await deleteEmptyIndices(context);
},
)
: const SizedBox(),
widget.mlIsEnabled
? const SizedBox(height: 24)
: const SizedBox(),
widget.mlIsEnabled
? ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Reset all local ML",
onTap: () async {
await deleteAllLocalML(context);
},
)
: const SizedBox(),
widget.mlIsEnabled
? const SizedBox(height: 24)
: const SizedBox(),
widget.mlIsEnabled
? MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Remote fetch",
),
menuItemColor: colorScheme.fillFaint,
trailingWidget: ToggleSwitchWidget(
value: () => localSettings.remoteFetchEnabled,
onChanged: () async {
try {
await localSettings.toggleRemoteFetch();
_logger.info(
'Remote fetch is turned ${localSettings.remoteFetchEnabled ? 'on' : 'off'}',
);
if (mounted) {
setState(() {});
}
} catch (e, s) {
_logger.warning(
'Remote fetch toggle failed ',
e,
s,
);
await showGenericErrorDialog(
context: context,
error: e,
);
}
},
),
singleBorderRadius: 8,
alignCaptionedTextToLeft: true,
isBottomBorderRadiusRemoved: true,
isGestureDetectorDisabled: true,
)
: const SizedBox(),
widget.mlIsEnabled
? const SizedBox(height: 24)
: const SizedBox.shrink(),
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Load face detection model",

View File

@@ -1,4 +1,3 @@
import "package:flutter/material.dart";
import 'package:photos/models/file/file.dart';
import "package:photos/ui/viewer/file/zoomable_image.dart";

View File

@@ -1,7 +1,6 @@
import "dart:async";
import 'package:fast_base58/fast_base58.dart';
import "package:flutter/cupertino.dart";
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import "package:local_auth/local_auth.dart";

View File

@@ -3,7 +3,8 @@ import "package:flutter/material.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/theme/ente_theme.dart";
Future<DateTime?> showDatePickerSheet(BuildContext context, {
Future<DateTime?> showDatePickerSheet(
BuildContext context, {
required DateTime initialDate,
DateTime? maxDate,
DateTime? minDate,

View File

@@ -1,4 +1,3 @@
import 'package:flutter/material.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/models/file/file.dart';

View File

@@ -12,6 +12,8 @@ class FileWidget extends StatelessWidget {
final Function(bool)? playbackCallback;
final BoxDecoration? backgroundDecoration;
final bool? autoPlay;
final bool? isFromMemories;
final Function(int)? onFinalFileLoad;
const FileWidget(
this.file, {
@@ -20,6 +22,8 @@ class FileWidget extends StatelessWidget {
this.playbackCallback,
required this.tagPrefix,
this.backgroundDecoration,
this.isFromMemories = false,
this.onFinalFileLoad,
super.key,
});
@@ -37,7 +41,9 @@ class FileWidget extends StatelessWidget {
shouldDisableScroll: shouldDisableScroll,
tagPrefix: tagPrefix,
backgroundDecoration: backgroundDecoration,
isFromMemories: isFromMemories ?? false,
key: key ?? ValueKey(fileKey),
onFinalFileLoad: onFinalFileLoad,
);
} else if (file.fileType == FileType.video) {
// use old video widget on iOS simulator as the new one crashes while
@@ -54,6 +60,8 @@ class FileWidget extends StatelessWidget {
file,
tagPrefix: tagPrefix,
playbackCallback: playbackCallback,
onFinalFileLoad: onFinalFileLoad,
isFromMemories: isFromMemories ?? false,
key: key ?? ValueKey(fileKey),
);
} else {

View File

@@ -23,10 +23,15 @@ class VideoWidget extends StatefulWidget {
final EnteFile file;
final String? tagPrefix;
final Function(bool)? playbackCallback;
final Function(int)? onFinalFileLoad;
final bool isFromMemories;
const VideoWidget(
this.file, {
this.tagPrefix,
this.playbackCallback,
this.onFinalFileLoad,
this.isFromMemories = false,
super.key,
});
@@ -149,6 +154,7 @@ class _VideoWidgetState extends State<VideoWidget> {
playbackCallback: widget.playbackCallback,
playlistData: playlistData,
selectedPreview: playPreview,
isFromMemories: widget.isFromMemories,
onStreamChange: () {
setState(() {
selectPreviewForPlay = !selectPreviewForPlay;
@@ -162,6 +168,7 @@ class _VideoWidgetState extends State<VideoWidget> {
);
});
},
onFinalFileLoad: widget.onFinalFileLoad,
);
}
return VideoWidgetMediaKitNew(
@@ -171,6 +178,7 @@ class _VideoWidgetState extends State<VideoWidget> {
playbackCallback: widget.playbackCallback,
preview: playlistData?.preview,
selectedPreview: playPreview,
isFromMemories: widget.isFromMemories,
onStreamChange: () {
setState(() {
selectPreviewForPlay = !selectPreviewForPlay;
@@ -184,6 +192,7 @@ class _VideoWidgetState extends State<VideoWidget> {
);
});
},
onFinalFileLoad: widget.onFinalFileLoad,
);
}
}

View File

@@ -100,6 +100,7 @@ class _VideoWidgetState extends State<VideoWidget> {
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (widget.isFromMemories) return;
showControlsNotifier.value = !showControlsNotifier.value;
if (widget.playbackCallback != null) {
widget.playbackCallback!(
@@ -107,48 +108,62 @@ class _VideoWidgetState extends State<VideoWidget> {
);
}
},
onLongPress: () {
if (widget.isFromMemories) {
widget.controller.player.stop();
}
},
onLongPressUp: () {
if (widget.isFromMemories) {
widget.controller.player.play();
}
},
child: Container(
constraints: const BoxConstraints.expand(),
),
),
IgnorePointer(
ignoring: !value,
child: PlayPauseButtonMediaKit(widget.controller),
),
Positioned(
bottom: verticalMargin,
right: 0,
left: 0,
child: IgnorePointer(
ignoring: !value,
child: SafeArea(
top: false,
left: false,
right: false,
child: Padding(
padding: EdgeInsets.only(
bottom: widget.isFromMemories ? 32 : 0,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
VideoStreamChangeWidget(
showControls: value,
file: widget.file,
isPreviewPlayer: widget.isPreviewPlayer,
onStreamChange: widget.onStreamChange,
widget.isFromMemories
? const SizedBox.shrink()
: IgnorePointer(
ignoring: !value,
child: PlayPauseButtonMediaKit(widget.controller),
),
widget.isFromMemories
? const SizedBox.shrink()
: Positioned(
bottom: verticalMargin,
right: 0,
left: 0,
child: IgnorePointer(
ignoring: !value,
child: SafeArea(
top: false,
left: false,
right: false,
child: Padding(
padding: EdgeInsets.only(
bottom: widget.isFromMemories ? 32 : 0,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
VideoStreamChangeWidget(
showControls: value,
file: widget.file,
isPreviewPlayer: widget.isPreviewPlayer,
onStreamChange: widget.onStreamChange,
),
SeekBarAndDuration(
controller: widget.controller,
isSeekingNotifier: _isSeekingNotifier,
file: widget.file,
),
],
),
),
SeekBarAndDuration(
controller: widget.controller,
isSeekingNotifier: _isSeekingNotifier,
file: widget.file,
),
],
),
),
),
),
),
),
],
),
);

View File

@@ -36,6 +36,7 @@ class VideoWidgetMediaKitNew extends StatefulWidget {
final void Function() onStreamChange;
final File? preview;
final bool selectedPreview;
final Function(int)? onFinalFileLoad;
const VideoWidgetMediaKitNew(
this.file, {
@@ -45,6 +46,7 @@ class VideoWidgetMediaKitNew extends StatefulWidget {
required this.onStreamChange,
this.preview,
required this.selectedPreview,
this.onFinalFileLoad,
super.key,
});
@@ -307,6 +309,8 @@ class _VideoWidgetMediaKitNewState extends State<VideoWidgetMediaKitNew>
}
player.open(Media(url), play: _isAppInFG);
});
final duration = controller!.player.state.duration.inSeconds;
widget.onFinalFileLoad?.call(duration);
}
}
}

View File

@@ -46,6 +46,7 @@ class VideoWidgetNative extends StatefulWidget {
final void Function()? onStreamChange;
final PlaylistData? playlistData;
final bool selectedPreview;
final Function(int)? onFinalFileLoad;
const VideoWidgetNative(
this.file, {
@@ -55,6 +56,7 @@ class VideoWidgetNative extends StatefulWidget {
required this.onStreamChange,
super.key,
this.playlistData,
this.onFinalFileLoad,
required this.selectedPreview,
});
@@ -302,12 +304,23 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (widget.isFromMemories) return;
_showControls.value = !_showControls.value;
if (widget.playbackCallback != null) {
widget.playbackCallback!(!_showControls.value);
}
_elTooltipController.hide();
},
onLongPress: () {
if (widget.isFromMemories) {
_controller?.pause();
}
},
onLongPressUp: () {
if (widget.isFromMemories) {
_controller?.play();
}
},
child: Container(
constraints: const BoxConstraints.expand(),
),
@@ -331,32 +344,38 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
),
)
: const SizedBox.shrink(),
Positioned.fill(
child: Center(
child: ValueListenableBuilder(
builder: (BuildContext context, bool value, _) {
return value
? ValueListenableBuilder(
builder: (context, bool value, _) {
return AnimatedOpacity(
duration:
const Duration(milliseconds: 200),
opacity: value ? 1 : 0,
curve: Curves.easeInOutQuad,
child: IgnorePointer(
ignoring: !value,
child: PlayPauseButton(_controller),
),
);
},
valueListenable: _showControls,
)
: const SizedBox();
},
valueListenable: _isPlaybackReady,
),
),
),
widget.isFromMemories
? const SizedBox.shrink()
: Positioned.fill(
child: Center(
child: ValueListenableBuilder(
builder:
(BuildContext context, bool value, _) {
return value
? ValueListenableBuilder(
builder: (context, bool value, _) {
return AnimatedOpacity(
duration: const Duration(
milliseconds: 200,
),
opacity: value ? 1 : 0,
curve: Curves.easeInOutQuad,
child: IgnorePointer(
ignoring: !value,
child: PlayPauseButton(
_controller,
),
),
);
},
valueListenable: _showControls,
)
: const SizedBox();
},
valueListenable: _isPlaybackReady,
),
),
),
Positioned(
bottom: verticalMargin,
right: 0,
@@ -394,7 +413,7 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
valueListenable: _isPlaybackReady,
builder:
(BuildContext context, bool value, _) {
return value
return value && !widget.isFromMemories
? _SeekBarAndDuration(
controller: _controller,
duration: duration,
@@ -525,6 +544,8 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
Future<void> _onPlaybackReady() async {
if (_isPlaybackReady.value) return;
await _controller!.play();
final durationInSeconds = durationToSeconds(duration) ?? 0;
widget.onFinalFileLoad?.call(durationInSeconds);
unawaited(_controller!.setVolume(1));
_isPlaybackReady.value = true;
}
@@ -739,11 +760,7 @@ class _SeekBarAndDuration extends StatelessWidget {
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: showControls,
builder: (
BuildContext context,
bool value,
_,
) {
builder: (BuildContext context, bool value, _) {
return AnimatedOpacity(
duration: const Duration(
milliseconds: 200,

View File

@@ -31,6 +31,7 @@ class ZoomableImage extends StatefulWidget {
final Decoration? backgroundDecoration;
final bool shouldCover;
final bool isGuestView;
final Function(int)? onFinalFileLoad;
const ZoomableImage(
this.photo, {
@@ -40,6 +41,7 @@ class ZoomableImage extends StatefulWidget {
this.backgroundDecoration,
this.shouldCover = false,
this.isGuestView = false,
this.onFinalFileLoad,
});
@override
@@ -426,6 +428,9 @@ class _ZoomableImageState extends State<ZoomableImage> {
_loadedFinalImage = true;
_logger.info("Final image loaded");
});
if (_imageProvider != null) {
widget.onFinalFileLoad?.call(5);
}
}
Future<void> _updatePhotoViewController({

View File

@@ -23,6 +23,8 @@ class ZoomableLiveImageNew extends StatefulWidget {
final Function(bool)? shouldDisableScroll;
final String? tagPrefix;
final Decoration? backgroundDecoration;
final bool isFromMemories;
final Function(int)? onFinalFileLoad;
const ZoomableLiveImageNew(
this.enteFile, {
@@ -30,6 +32,8 @@ class ZoomableLiveImageNew extends StatefulWidget {
this.shouldDisableScroll,
required this.tagPrefix,
this.backgroundDecoration,
this.isFromMemories = false,
this.onFinalFileLoad,
});
@override
@@ -94,13 +98,17 @@ class _ZoomableLiveImageNewState extends State<ZoomableLiveImageNew>
shouldDisableScroll: widget.shouldDisableScroll,
backgroundDecoration: widget.backgroundDecoration,
isGuestView: isGuestView,
onFinalFileLoad: widget.onFinalFileLoad,
);
}
return GestureDetector(
onLongPressStart: (_) => {_onLongPressEvent(true)},
onLongPressEnd: (_) => {_onLongPressEvent(false)},
child: content,
);
if (!widget.isFromMemories) {
return GestureDetector(
onLongPressStart: (_) => _onLongPressEvent(true),
onLongPressEnd: (_) => _onLongPressEvent(false),
child: content,
);
}
return content;
}
@override

View File

@@ -5,6 +5,7 @@ import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/components/info_item_widget.dart";
import "package:photos/ui/viewer/date/edit_date_sheet.dart";
import "package:photos/utils/standalone/date_time.dart";
class CreationTimeItem extends StatefulWidget {
final EnteFile file;
final int currentUserID;

View File

@@ -1,18 +1,16 @@
import "package:photos/core/configuration.dart";
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
Future<bool> isMeAssigned() async {
final personEntities = await PersonService.instance.getPersons();
final currentUserEmail = Configuration.instance.getEmail();
final personEntities = await PersonService.instance.getPersons();
final currentUserEmail = Configuration.instance.getEmail();
bool isAssigned = false;
for (final personEntity in personEntities) {
if (personEntity.data.email == currentUserEmail) {
isAssigned = true;
break;
}
bool isAssigned = false;
for (final personEntity in personEntities) {
if (personEntity.data.email == currentUserEmail) {
isAssigned = true;
break;
}
return isAssigned;
}
}
return isAssigned;
}

View File

@@ -23,7 +23,7 @@ import "package:photos/events/file_uploaded_event.dart";
import 'package:photos/events/files_updated_event.dart';
import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/events/subscription_purchased_event.dart';
import 'package:photos/main.dart';
import "package:photos/main.dart";
import "package:photos/models/api/metadata.dart";
import "package:photos/models/backup/backup_item.dart";
import "package:photos/models/backup/backup_item_status.dart";

View File

@@ -58,6 +58,8 @@ class FlagService {
bool get enableMobMultiPart => flags.enableMobMultiPart || internalUser;
bool get enableVectorDb => flags.internalUser;
String get castUrl => flags.castUrl;
Future<void> setMapEnabled(bool isEnabled) async {

View File

@@ -183,6 +183,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.1"
build_cli_annotations:
dependency: transitive
description:
name: build_cli_annotations
sha256: b59d2769769efd6c9ff6d4c4cede0be115a566afc591705c2040b707534b1172
url: "https://pub.dev"
source: hosted
version: "2.1.0"
build_config:
dependency: transitive
description:
@@ -1015,6 +1023,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.24"
flutter_rust_bridge:
dependency: "direct main"
description:
name: flutter_rust_bridge
sha256: "5a5c7a5deeef2cc2ffe6076a33b0429f4a20ceac22a397297aed2b1eb067e611"
url: "https://pub.dev"
source: hosted
version: "2.9.0"
flutter_secure_storage:
dependency: "direct main"
description:
@@ -2145,6 +2161,13 @@ packages:
url: "https://github.com/KasemJaffer/receive_sharing_intent.git"
source: git
version: "1.8.1"
rust_lib_photos:
dependency: "direct main"
description:
path: rust_builder
relative: true
source: path
version: "0.0.1"
rxdart:
dependency: transitive
description:

View File

@@ -12,11 +12,11 @@ description: ente photos application
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.1.0+1053
version: 1.1.31+1058
publish_to: none
environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=3.3.0 <4.0.0"
dependencies:
adaptive_theme: ^3.1.0
@@ -95,6 +95,7 @@ dependencies:
flutter_map: ^6.2.0
flutter_map_marker_cluster: ^1.3.6
flutter_password_strength: ^0.1.6
flutter_rust_bridge: 2.9.0
# Do not upgrade this package unless this issue is resolved:
# https://github.com/juliansteenbakker/flutter_secure_storage/issues/870
# On v9.2.4, keys related to lockscreen persist even after reintsall. For context see:
@@ -177,6 +178,8 @@ dependencies:
git:
url: https://github.com/KasemJaffer/receive_sharing_intent.git
ref: 2cea396
rust_lib_photos:
path: rust_builder
screenshot: ^3.0.0
scrollable_positioned_list: ^0.3.5
sentry: ^8.14.1

1
mobile/rust/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

843
mobile/rust/Cargo.lock generated Normal file
View File

@@ -0,0 +1,843 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "allo-isolate"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "449e356a4864c017286dbbec0e12767ea07efba29e3b7d984194c2a7ff3c4550"
dependencies = [
"anyhow",
"atomic",
"backtrace",
]
[[package]]
name = "android_log-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
[[package]]
name = "android_logger"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f"
dependencies = [
"android_log-sys",
"env_logger",
"log",
"once_cell",
]
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "atomic"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "build-target"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "832133bbabbbaa9fbdba793456a2827627a7d2b8fb96032fa1e7666d7895832b"
[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "bytemuck"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "cxx"
version = "1.0.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58ab30434ea0ff6aa640a08dda5284026a366d47565496fd40b6cbfbdd7e31a2"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b649d7dfae8268450d53d109388b337b9352c7cba1fc10db4a1bc23c3dc189fb"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42281b20eba5218c539295c667c18e2f50211bb11902419194c6ed1ae808e547"
[[package]]
name = "cxxbridge-macro"
version = "1.0.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b45506e3c66512b0a65d291a6b452128b7b1dd9841e20d1e151addbd2c00ea50"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dart-sys"
version = "4.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57967e4b200d767d091b961d6ab42cc7d0cc14fe9e052e75d0d3cf9eb732d895"
dependencies = [
"cc",
]
[[package]]
name = "dashmap"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
dependencies = [
"cfg-if",
"num_cpus",
]
[[package]]
name = "delegate-attr"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "env_logger"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
dependencies = [
"log",
"regex",
]
[[package]]
name = "flutter_rust_bridge"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f8c0dee6249225e815dcff3f3a39b98d9f66fdb3c392a432715b646bfa4da02"
dependencies = [
"allo-isolate",
"android_logger",
"anyhow",
"build-target",
"bytemuck",
"byteorder",
"console_error_panic_hook",
"dart-sys",
"delegate-attr",
"flutter_rust_bridge_macros",
"futures",
"js-sys",
"lazy_static",
"log",
"oslog",
"portable-atomic",
"threadpool",
"tokio",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "flutter_rust_bridge_macros"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e88d604908d9eccb4ca9c26640ce41033165cbef041460e704ae28bd5208bce"
dependencies = [
"hex",
"md-5",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
[[package]]
name = "futures-executor"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
[[package]]
name = "futures-macro"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
[[package]]
name = "futures-task"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
[[package]]
name = "futures-util"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "gimli"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "hermit-abi"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "link-cplusplus"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212"
dependencies = [
"cc",
]
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "oslog"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8343ce955f18e7e68c0207dd0ea776ec453035685395ababd2ea651c569728b3"
dependencies = [
"cc",
"dashmap",
"log",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "proc-macro2"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rust_lib_photos"
version = "0.1.0"
dependencies = [
"flutter_rust_bridge",
"usearch",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "scratch"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52"
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "syn"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]]
name = "tokio"
version = "1.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
dependencies = [
"backtrace",
"num_cpus",
"pin-project-lite",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "usearch"
version = "2.17.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "908331accde6ff6bfe83e1f2dfd4cc77343d107bfacecd2f19b7a14d87cdb2df"
dependencies = [
"cxx",
"cxx-build",
]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "web-sys"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

14
mobile/rust/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "rust_lib_photos"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "staticlib"]
[dependencies]
flutter_rust_bridge = "=2.9.0"
usearch = "2.17.11"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] }

View File

@@ -0,0 +1,2 @@
pub mod simple;
pub mod usearch_api;

View File

@@ -0,0 +1,10 @@
#[flutter_rust_bridge::frb(sync)] // Synchronous mode for simplicity of the demo
pub fn greet(name: String) -> String {
format!("Hello, {name}!")
}
#[flutter_rust_bridge::frb(init)]
pub fn init_app() {
// Default utilities - feel free to customize
flutter_rust_bridge::setup_default_user_utils();
}

View File

@@ -0,0 +1,188 @@
use flutter_rust_bridge::frb;
use usearch::{Index, IndexOptions, MetricKind, ScalarKind};
use std::path::PathBuf;
#[frb(opaque)]
pub struct VectorDB {
index: Index,
path: PathBuf,
}
impl VectorDB {
#[frb(sync)]
pub fn new(file_path: &str, dimensions: usize) -> Self {
let path = PathBuf::from(file_path);
let file_exists = path.try_exists().unwrap_or(false);
let mut options = IndexOptions::default();
options.dimensions = dimensions;
options.metric = MetricKind::IP;
options.quantization = ScalarKind::F32;
options.connectivity = 0; // auto
options.expansion_add = 0; // auto
options.expansion_search = 0; // auto
let index = Index::new(&options).expect("Failed to create index");
index
.reserve(1000)
.expect("Failed to reserve space in index");
let db = Self { index, path };
if file_exists {
println!("Loading index from disk.");
db.index.load(file_path).expect("Failed to load index");
} else {
println!("Creating new index.");
db.save_index();
}
db
}
fn save_index(&self) {
// Ensure directory exists
if let Some(parent) = self.path.parent() {
std::fs::create_dir_all(parent).expect("Failed to create directory");
}
self.index
.save(self.path.to_str().expect("Invalid path"))
.expect("Failed to save index");
}
fn ensure_capacity(&self, margin: usize) {
let current_size = self.index.size();
let capacity = self.index.capacity();
if current_size + margin + 1000 >= capacity {
self.index
.reserve(current_size + margin)
.expect("Failed to reserve space in index");
}
}
pub fn add_vector(&self, key: u64, vector: &Vec<f32>) {
if self.contains_vector(key) {
self.remove_vector(key);
} else {
self.ensure_capacity(1);
}
self.index.add(key, vector).expect("Failed to add vector");
self.save_index();
}
pub fn bulk_add_vectors(&self, keys: Vec<u64>, vectors: &Vec<Vec<f32>>) {
self.ensure_capacity(keys.len());
for (key, vector) in keys.iter().zip(vectors.iter()) {
if self.contains_vector(*key) {
self.remove_vector(*key);
}
self.index
.add(*key, vector)
.expect("Failed to (bulk) add vector");
}
self.save_index();
}
pub fn search_vectors(&self, query: &Vec<f32>, count: usize) -> (Vec<u64>, Vec<f32>) {
let matches = self
.index
.search(query, count)
.expect("Failed to search vectors");
(matches.keys, matches.distances)
}
pub fn bulk_search_vectors(
&self,
queries: &Vec<Vec<f32>>,
count: usize,
) -> (Vec<Vec<u64>>, Vec<Vec<f32>>) {
let mut keys = Vec::new();
let mut distances = Vec::new();
for query in queries {
let (keys_result, distances_result) = self.search_vectors(query, count);
keys.push(keys_result);
distances.push(distances_result);
}
(keys, distances)
}
/// Check if a vector with the given key exists in the index.
/// `true` if the index contains the vector with the given key, `false` otherwise.
pub fn contains_vector(&self, key: u64) -> bool {
self.index.contains(key)
}
pub fn get_vector(&self, key: u64) -> Vec<f32> {
let mut vector: Vec<f32> = vec![0.0; self.index.dimensions()];
self.index
.get(key, &mut vector)
.expect("Failed to get vector");
vector
}
pub fn bulk_get_vectors(&self, keys: Vec<u64>) -> Vec<Vec<f32>> {
let mut vectors = Vec::new();
for key in keys {
let vector = self.get_vector(key);
vectors.push(vector);
}
vectors
}
pub fn remove_vector(&self, key: u64) -> usize {
let removed_count = self.index.remove(key).expect("Failed to remove vector");
self.save_index();
removed_count
}
pub fn bulk_remove_vectors(&self, keys: Vec<u64>) -> usize {
let mut removed_count = 0;
for key in keys {
removed_count += self
.index
.remove(key)
.expect("Failed to (bulk) remove vector");
}
self.save_index();
removed_count
}
pub fn reset_index(&self) {
self.index.reset().expect("Failed to reset index");
self.index
.reserve(1000)
.expect("Failed to reserve space in index");
self.save_index();
}
pub fn delete_index(self) {
if self.path.exists() {
std::fs::remove_file(&self.path).expect("Failed to delete index file");
} else {
println!("Index file does not exist.");
}
}
pub fn get_index_stats(&self) -> (usize, usize, usize, usize, usize, usize, usize) {
let size = self.index.size();
let capacity = self.index.capacity();
let dimensions = self.index.dimensions();
let file_size = self.index.serialized_length();
let memory_usage = self.index.memory_usage();
let expansion_add = self.index.expansion_add();
let expansion_search = self.index.expansion_search();
(
size,
capacity,
dimensions,
file_size,
memory_usage,
expansion_add,
expansion_search,
)
}
}

File diff suppressed because it is too large Load Diff

2
mobile/rust/src/lib.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod api;
mod frb_generated;

29
mobile/rust_builder/.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
build/

View File

@@ -0,0 +1 @@
Please ignore this folder, which is just glue to build Rust with Flutter.

View File

@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

View File

@@ -0,0 +1,56 @@
// The Android Gradle Plugin builds the native code with the Android NDK.
group 'com.flutter_rust_bridge.rust_lib_photos'
version '1.0'
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
// The Android Gradle Plugin knows how to build native code with the NDK.
classpath 'com.android.tools.build:gradle:7.3.0'
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
android {
if (project.android.hasProperty("namespace")) {
namespace 'com.flutter_rust_bridge.rust_lib_photos'
}
// Bumping the plugin compileSdkVersion requires all clients of this plugin
// to bump the version in their app.
compileSdkVersion 33
// Use the NDK version
// declared in /android/app/build.gradle file of the Flutter project.
// Replace it with a version number if this plugin requires a specfic NDK version.
// (e.g. ndkVersion "23.1.7779620")
ndkVersion android.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 19
}
}
apply from: "../cargokit/gradle/plugin.gradle"
cargokit {
manifestDir = "../../rust"
libname = "rust_lib_photos"
}

View File

@@ -0,0 +1 @@
rootProject.name = 'rust_lib_photos'

View File

@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.flutter_rust_bridge.rust_lib_photos">
</manifest>

View File

@@ -0,0 +1,4 @@
target
.dart_tool
*.iml
!pubspec.lock

View File

@@ -0,0 +1,42 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
Copyright 2022 Matej Knopp
================================================================================
MIT LICENSE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================================================
APACHE LICENSE, VERSION 2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,11 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
Experimental repository to provide glue for seamlessly integrating cargo build
with flutter plugins and packages.
See https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/
for a tutorial on how to use Cargokit.
Example plugin available at https://github.com/irondash/hello_rust_ffi_plugin.

View File

@@ -0,0 +1,58 @@
#!/bin/sh
set -e
BASEDIR=$(dirname "$0")
# Workaround for https://github.com/dart-lang/pub/issues/4010
BASEDIR=$(cd "$BASEDIR" ; pwd -P)
# Remove XCode SDK from path. Otherwise this breaks tool compilation when building iOS project
NEW_PATH=`echo $PATH | tr ":" "\n" | grep -v "Contents/Developer/" | tr "\n" ":"`
export PATH=${NEW_PATH%?} # remove trailing :
env
# Platform name (macosx, iphoneos, iphonesimulator)
export CARGOKIT_DARWIN_PLATFORM_NAME=$PLATFORM_NAME
# Arctive architectures (arm64, armv7, x86_64), space separated.
export CARGOKIT_DARWIN_ARCHS=$ARCHS
# Current build configuration (Debug, Release)
export CARGOKIT_CONFIGURATION=$CONFIGURATION
# Path to directory containing Cargo.toml.
export CARGOKIT_MANIFEST_DIR=$PODS_TARGET_SRCROOT/$1
# Temporary directory for build artifacts.
export CARGOKIT_TARGET_TEMP_DIR=$TARGET_TEMP_DIR
# Output directory for final artifacts.
export CARGOKIT_OUTPUT_DIR=$PODS_CONFIGURATION_BUILD_DIR/$PRODUCT_NAME
# Directory to store built tool artifacts.
export CARGOKIT_TOOL_TEMP_DIR=$TARGET_TEMP_DIR/build_tool
# Directory inside root project. Not necessarily the top level directory of root project.
export CARGOKIT_ROOT_PROJECT_DIR=$SRCROOT
FLUTTER_EXPORT_BUILD_ENVIRONMENT=(
"$PODS_ROOT/../Flutter/ephemeral/flutter_export_environment.sh" # macOS
"$PODS_ROOT/../Flutter/flutter_export_environment.sh" # iOS
)
for path in "${FLUTTER_EXPORT_BUILD_ENVIRONMENT[@]}"
do
if [[ -f "$path" ]]; then
source "$path"
fi
done
sh "$BASEDIR/run_build_tool.sh" build-pod "$@"
# Make a symlink from built framework to phony file, which will be used as input to
# build script. This should force rebuild (podspec currently doesn't support alwaysOutOfDate
# attribute on custom build phase)
ln -fs "$OBJROOT/XCBuildData/build.db" "${BUILT_PRODUCTS_DIR}/cargokit_phony"
ln -fs "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" "${BUILT_PRODUCTS_DIR}/cargokit_phony_out"

View File

@@ -0,0 +1,5 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
A sample command-line application with an entrypoint in `bin/`, library code
in `lib/`, and example unit test in `test/`.

View File

@@ -0,0 +1,34 @@
# This is copied from Cargokit (which is the official way to use it currently)
# Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lints/recommended.yaml
# Uncomment the following section to specify additional rules.
linter:
rules:
- prefer_relative_imports
- directives_ordering
# analyzer:
# exclude:
# - path/to/excluded/files/**
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options

View File

@@ -0,0 +1,8 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'package:build_tool/build_tool.dart' as build_tool;
void main(List<String> arguments) {
build_tool.runMain(arguments);
}

View File

@@ -0,0 +1,8 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'src/build_tool.dart' as build_tool;
Future<void> runMain(List<String> args) async {
return build_tool.runMain(args);
}

View File

@@ -0,0 +1,195 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'dart:isolate';
import 'dart:math' as math;
import 'package:collection/collection.dart';
import 'package:path/path.dart' as path;
import 'package:version/version.dart';
import 'target.dart';
import 'util.dart';
class AndroidEnvironment {
AndroidEnvironment({
required this.sdkPath,
required this.ndkVersion,
required this.minSdkVersion,
required this.targetTempDir,
required this.target,
});
static void clangLinkerWrapper(List<String> args) {
final clang = Platform.environment['_CARGOKIT_NDK_LINK_CLANG'];
if (clang == null) {
throw Exception(
"cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_CLANG env var");
}
final target = Platform.environment['_CARGOKIT_NDK_LINK_TARGET'];
if (target == null) {
throw Exception(
"cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_TARGET env var");
}
runCommand(clang, [
target,
...args,
]);
}
/// Full path to Android SDK.
final String sdkPath;
/// Full version of Android NDK.
final String ndkVersion;
/// Minimum supported SDK version.
final int minSdkVersion;
/// Target directory for build artifacts.
final String targetTempDir;
/// Target being built.
final Target target;
bool ndkIsInstalled() {
final ndkPath = path.join(sdkPath, 'ndk', ndkVersion);
final ndkPackageXml = File(path.join(ndkPath, 'package.xml'));
return ndkPackageXml.existsSync();
}
void installNdk({
required String javaHome,
}) {
final sdkManagerExtension = Platform.isWindows ? '.bat' : '';
final sdkManager = path.join(
sdkPath,
'cmdline-tools',
'latest',
'bin',
'sdkmanager$sdkManagerExtension',
);
log.info('Installing NDK $ndkVersion');
runCommand(sdkManager, [
'--install',
'ndk;$ndkVersion',
], environment: {
'JAVA_HOME': javaHome,
});
}
Future<Map<String, String>> buildEnvironment() async {
final hostArch = Platform.isMacOS
? "darwin-x86_64"
: (Platform.isLinux ? "linux-x86_64" : "windows-x86_64");
final ndkPath = path.join(sdkPath, 'ndk', ndkVersion);
final toolchainPath = path.join(
ndkPath,
'toolchains',
'llvm',
'prebuilt',
hostArch,
'bin',
);
final minSdkVersion =
math.max(target.androidMinSdkVersion!, this.minSdkVersion);
final exe = Platform.isWindows ? '.exe' : '';
final arKey = 'AR_${target.rust}';
final arValue = ['${target.rust}-ar', 'llvm-ar', 'llvm-ar.exe']
.map((e) => path.join(toolchainPath, e))
.firstWhereOrNull((element) => File(element).existsSync());
if (arValue == null) {
throw Exception('Failed to find ar for $target in $toolchainPath');
}
final targetArg = '--target=${target.rust}$minSdkVersion';
final ccKey = 'CC_${target.rust}';
final ccValue = path.join(toolchainPath, 'clang$exe');
final cfFlagsKey = 'CFLAGS_${target.rust}';
final cFlagsValue = targetArg;
final cxxKey = 'CXX_${target.rust}';
final cxxValue = path.join(toolchainPath, 'clang++$exe');
final cxxFlagsKey = 'CXXFLAGS_${target.rust}';
final cxxFlagsValue = targetArg;
final linkerKey =
'cargo_target_${target.rust.replaceAll('-', '_')}_linker'.toUpperCase();
final ranlibKey = 'RANLIB_${target.rust}';
final ranlibValue = path.join(toolchainPath, 'llvm-ranlib$exe');
final ndkVersionParsed = Version.parse(ndkVersion);
final rustFlagsKey = 'CARGO_ENCODED_RUSTFLAGS';
final rustFlagsValue = _libGccWorkaround(targetTempDir, ndkVersionParsed);
final runRustTool =
Platform.isWindows ? 'run_build_tool.cmd' : 'run_build_tool.sh';
final packagePath = (await Isolate.resolvePackageUri(
Uri.parse('package:build_tool/buildtool.dart')))!
.toFilePath();
final selfPath = path.canonicalize(path.join(
packagePath,
'..',
'..',
'..',
runRustTool,
));
// Make sure that run_build_tool is working properly even initially launched directly
// through dart run.
final toolTempDir =
Platform.environment['CARGOKIT_TOOL_TEMP_DIR'] ?? targetTempDir;
return {
arKey: arValue,
ccKey: ccValue,
cfFlagsKey: cFlagsValue,
cxxKey: cxxValue,
cxxFlagsKey: cxxFlagsValue,
ranlibKey: ranlibValue,
rustFlagsKey: rustFlagsValue,
linkerKey: selfPath,
// Recognized by main() so we know when we're acting as a wrapper
'_CARGOKIT_NDK_LINK_TARGET': targetArg,
'_CARGOKIT_NDK_LINK_CLANG': ccValue,
'CARGOKIT_TOOL_TEMP_DIR': toolTempDir,
};
}
// Workaround for libgcc missing in NDK23, inspired by cargo-ndk
String _libGccWorkaround(String buildDir, Version ndkVersion) {
final workaroundDir = path.join(
buildDir,
'cargokit',
'libgcc_workaround',
'${ndkVersion.major}',
);
Directory(workaroundDir).createSync(recursive: true);
if (ndkVersion.major >= 23) {
File(path.join(workaroundDir, 'libgcc.a'))
.writeAsStringSync('INPUT(-lunwind)');
} else {
// Other way around, untested, forward libgcc.a from libunwind once Rust
// gets updated for NDK23+.
File(path.join(workaroundDir, 'libunwind.a'))
.writeAsStringSync('INPUT(-lgcc)');
}
var rustFlags = Platform.environment['CARGO_ENCODED_RUSTFLAGS'] ?? '';
if (rustFlags.isNotEmpty) {
rustFlags = '$rustFlags\x1f';
}
rustFlags = '$rustFlags-L\x1f$workaroundDir';
return rustFlags;
}
}

View File

@@ -0,0 +1,266 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:ed25519_edwards/ed25519_edwards.dart';
import 'package:http/http.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'builder.dart';
import 'crate_hash.dart';
import 'options.dart';
import 'precompile_binaries.dart';
import 'rustup.dart';
import 'target.dart';
class Artifact {
/// File system location of the artifact.
final String path;
/// Actual file name that the artifact should have in destination folder.
final String finalFileName;
AritifactType get type {
if (finalFileName.endsWith('.dll') ||
finalFileName.endsWith('.dll.lib') ||
finalFileName.endsWith('.pdb') ||
finalFileName.endsWith('.so') ||
finalFileName.endsWith('.dylib')) {
return AritifactType.dylib;
} else if (finalFileName.endsWith('.lib') || finalFileName.endsWith('.a')) {
return AritifactType.staticlib;
} else {
throw Exception('Unknown artifact type for $finalFileName');
}
}
Artifact({
required this.path,
required this.finalFileName,
});
}
final _log = Logger('artifacts_provider');
class ArtifactProvider {
ArtifactProvider({
required this.environment,
required this.userOptions,
});
final BuildEnvironment environment;
final CargokitUserOptions userOptions;
Future<Map<Target, List<Artifact>>> getArtifacts(List<Target> targets) async {
final result = await _getPrecompiledArtifacts(targets);
final pendingTargets = List.of(targets);
pendingTargets.removeWhere((element) => result.containsKey(element));
if (pendingTargets.isEmpty) {
return result;
}
final rustup = Rustup();
for (final target in targets) {
final builder = RustBuilder(target: target, environment: environment);
builder.prepare(rustup);
_log.info('Building ${environment.crateInfo.packageName} for $target');
final targetDir = await builder.build();
// For local build accept both static and dynamic libraries.
final artifactNames = <String>{
...getArtifactNames(
target: target,
libraryName: environment.crateInfo.packageName,
aritifactType: AritifactType.dylib,
remote: false,
),
...getArtifactNames(
target: target,
libraryName: environment.crateInfo.packageName,
aritifactType: AritifactType.staticlib,
remote: false,
)
};
final artifacts = artifactNames
.map((artifactName) => Artifact(
path: path.join(targetDir, artifactName),
finalFileName: artifactName,
))
.where((element) => File(element.path).existsSync())
.toList();
result[target] = artifacts;
}
return result;
}
Future<Map<Target, List<Artifact>>> _getPrecompiledArtifacts(
List<Target> targets) async {
if (userOptions.usePrecompiledBinaries == false) {
_log.info('Precompiled binaries are disabled');
return {};
}
if (environment.crateOptions.precompiledBinaries == null) {
_log.fine('Precompiled binaries not enabled for this crate');
return {};
}
final start = Stopwatch()..start();
final crateHash = CrateHash.compute(environment.manifestDir,
tempStorage: environment.targetTempDir);
_log.fine(
'Computed crate hash $crateHash in ${start.elapsedMilliseconds}ms');
final downloadedArtifactsDir =
path.join(environment.targetTempDir, 'precompiled', crateHash);
Directory(downloadedArtifactsDir).createSync(recursive: true);
final res = <Target, List<Artifact>>{};
for (final target in targets) {
final requiredArtifacts = getArtifactNames(
target: target,
libraryName: environment.crateInfo.packageName,
remote: true,
);
final artifactsForTarget = <Artifact>[];
for (final artifact in requiredArtifacts) {
final fileName = PrecompileBinaries.fileName(target, artifact);
final downloadedPath = path.join(downloadedArtifactsDir, fileName);
if (!File(downloadedPath).existsSync()) {
final signatureFileName =
PrecompileBinaries.signatureFileName(target, artifact);
await _tryDownloadArtifacts(
crateHash: crateHash,
fileName: fileName,
signatureFileName: signatureFileName,
finalPath: downloadedPath,
);
}
if (File(downloadedPath).existsSync()) {
artifactsForTarget.add(Artifact(
path: downloadedPath,
finalFileName: artifact,
));
} else {
break;
}
}
// Only provide complete set of artifacts.
if (artifactsForTarget.length == requiredArtifacts.length) {
_log.fine('Found precompiled artifacts for $target');
res[target] = artifactsForTarget;
}
}
return res;
}
static Future<Response> _get(Uri url, {Map<String, String>? headers}) async {
int attempt = 0;
const maxAttempts = 10;
while (true) {
try {
return await get(url, headers: headers);
} on SocketException catch (e) {
// Try to detect reset by peer error and retry.
if (attempt++ < maxAttempts &&
(e.osError?.errorCode == 54 || e.osError?.errorCode == 10054)) {
_log.severe(
'Failed to download $url: $e, attempt $attempt of $maxAttempts, will retry...');
await Future.delayed(Duration(seconds: 1));
continue;
} else {
rethrow;
}
}
}
}
Future<void> _tryDownloadArtifacts({
required String crateHash,
required String fileName,
required String signatureFileName,
required String finalPath,
}) async {
final precompiledBinaries = environment.crateOptions.precompiledBinaries!;
final prefix = precompiledBinaries.uriPrefix;
final url = Uri.parse('$prefix$crateHash/$fileName');
final signatureUrl = Uri.parse('$prefix$crateHash/$signatureFileName');
_log.fine('Downloading signature from $signatureUrl');
final signature = await _get(signatureUrl);
if (signature.statusCode == 404) {
_log.warning(
'Precompiled binaries not available for crate hash $crateHash ($fileName)');
return;
}
if (signature.statusCode != 200) {
_log.severe(
'Failed to download signature $signatureUrl: status ${signature.statusCode}');
return;
}
_log.fine('Downloading binary from $url');
final res = await _get(url);
if (res.statusCode != 200) {
_log.severe('Failed to download binary $url: status ${res.statusCode}');
return;
}
if (verify(
precompiledBinaries.publicKey, res.bodyBytes, signature.bodyBytes)) {
File(finalPath).writeAsBytesSync(res.bodyBytes);
} else {
_log.shout('Signature verification failed! Ignoring binary.');
}
}
}
enum AritifactType {
staticlib,
dylib,
}
AritifactType artifactTypeForTarget(Target target) {
if (target.darwinPlatform != null) {
return AritifactType.staticlib;
} else {
return AritifactType.dylib;
}
}
List<String> getArtifactNames({
required Target target,
required String libraryName,
required bool remote,
AritifactType? aritifactType,
}) {
aritifactType ??= artifactTypeForTarget(target);
if (target.darwinArch != null) {
if (aritifactType == AritifactType.staticlib) {
return ['lib$libraryName.a'];
} else {
return ['lib$libraryName.dylib'];
}
} else if (target.rust.contains('-windows-')) {
if (aritifactType == AritifactType.staticlib) {
return ['$libraryName.lib'];
} else {
return [
'$libraryName.dll',
'$libraryName.dll.lib',
if (!remote) '$libraryName.pdb'
];
}
} else if (target.rust.contains('-linux-')) {
if (aritifactType == AritifactType.staticlib) {
return ['lib$libraryName.a'];
} else {
return ['lib$libraryName.so'];
}
} else {
throw Exception("Unsupported target: ${target.rust}");
}
}

View File

@@ -0,0 +1,40 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:path/path.dart' as path;
import 'artifacts_provider.dart';
import 'builder.dart';
import 'environment.dart';
import 'options.dart';
import 'target.dart';
class BuildCMake {
final CargokitUserOptions userOptions;
BuildCMake({required this.userOptions});
Future<void> build() async {
final targetPlatform = Environment.targetPlatform;
final target = Target.forFlutterName(Environment.targetPlatform);
if (target == null) {
throw Exception("Unknown target platform: $targetPlatform");
}
final environment = BuildEnvironment.fromEnvironment(isAndroid: false);
final provider =
ArtifactProvider(environment: environment, userOptions: userOptions);
final artifacts = await provider.getArtifacts([target]);
final libs = artifacts[target]!;
for (final lib in libs) {
if (lib.type == AritifactType.dylib) {
File(lib.path)
.copySync(path.join(Environment.outputDir, lib.finalFileName));
}
}
}
}

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