From db95de88299eca2f35395fc4cdc0af91175a0ab2 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 16 Apr 2024 09:55:01 +0530 Subject: [PATCH 001/367] [mob][photos] Add cast pkg dependency --- mobile/pubspec.lock | 25 +++++++++++++++++++++++++ mobile/pubspec.yaml | 4 ++++ 2 files changed, 29 insertions(+) diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index bab6f37ca..3c9ff792c 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -209,6 +209,15 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + cast: + dependency: "direct main" + description: + path: "." + ref: multicast_version + resolved-ref: "1f39cd4d6efa9363e77b2439f0317bae0c92dda1" + url: "https://github.com/guyluz11/flutter_cast.git" + source: git + version: "2.0.9" characters: dependency: transitive description: @@ -1416,6 +1425,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + multicast_dns: + dependency: transitive + description: + name: multicast_dns + sha256: "316cc47a958d4bd3c67bd238fe8b44fdfb6133bad89cb191c0c3bd3edb14e296" + url: "https://pub.dev" + source: hosted + version: "0.3.2+6" nested: dependency: transitive description: @@ -1729,6 +1746,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.dev" + source: hosted + version: "3.1.0" provider: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index f1575bdf8..bc0d4ba2d 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -27,6 +27,10 @@ dependencies: battery_info: ^1.1.1 bip39: ^1.0.6 cached_network_image: ^3.0.0 + cast: + git: + url: https://github.com/guyluz11/flutter_cast.git + ref: multicast_version chewie: git: url: https://github.com/ente-io/chewie.git From f645fff31c53c8f5dab6ad52102c589c67882e45 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:38:41 +0530 Subject: [PATCH 002/367] [mob][photos] Add hook to show cast devices --- mobile/ios/Runner/Info.plist | 9 +++++ .../gallery/gallery_app_bar_widget.dart | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index 037996520..cdbc23774 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -105,5 +105,14 @@ UIApplicationSupportsIndirectInputEvents + NSBonjourServices + + _googlecast._tcp + F5BCEC64._googlecast._tcp + + + NSLocalNetworkUsageDescription + ${PRODUCT_NAME} uses the local network to discover Cast-enabled devices on your WiFi + network. diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 1026bd7fd..aa09e49b2 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'dart:io'; import 'dart:math' as math; +import "package:cast/device.dart"; +import "package:cast/discovery_service.dart"; import "package:flutter/cupertino.dart"; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; @@ -576,6 +578,9 @@ class _GalleryAppBarWidgetState extends State { ), ); } + if (widget.collection != null) { + actions.add(castWidget(context)); + } if (items.isNotEmpty) { actions.add( PopupMenuButton( @@ -642,6 +647,39 @@ class _GalleryAppBarWidgetState extends State { return actions; } + Widget castWidget(BuildContext context) { + return FutureBuilder>( + future: CastDiscoveryService().search(), + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Text( + 'Error: ${snapshot.error.toString()}', + ), + ); + } else if (!snapshot.hasData) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + if (snapshot.data!.isEmpty) { + return const Text('No device'); + } + + return Column( + children: snapshot.data!.map((device) { + return Text(device.name); + + }).toList(), + ); + }, + ); + } + + Future _connectToYourApp( + BuildContext contect, CastDevice device,) async {} + Future onCleanUncategorizedClick(BuildContext buildContext) async { final actionResult = await showChoiceActionSheet( context, From da1d778eeb1562080c238b5c62a7097be0caba64 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:47:49 +0530 Subject: [PATCH 003/367] [mob][photos] Add hook to connect to cast device --- .../gallery/gallery_app_bar_widget.dart | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index aa09e49b2..5431e15a2 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -4,6 +4,8 @@ import 'dart:math' as math; import "package:cast/device.dart"; import "package:cast/discovery_service.dart"; +import "package:cast/session.dart"; +import "package:cast/session_manager.dart"; import "package:flutter/cupertino.dart"; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; @@ -669,8 +671,16 @@ class _GalleryAppBarWidgetState extends State { return Column( children: snapshot.data!.map((device) { - return Text(device.name); - + return GestureDetector( + onTap: () async { + try { + await _connectToYourApp(context, device); + } catch (e) { + showGenericErrorDialog(context: context, error: e).ignore(); + } + }, + child: Text( device.name), + ); }).toList(), ); }, @@ -678,7 +688,37 @@ class _GalleryAppBarWidgetState extends State { } Future _connectToYourApp( - BuildContext contect, CastDevice device,) async {} + BuildContext context, + CastDevice object, + ) async { + final session = await CastSessionManager().startSession(object); + + session.stateStream.listen((state) { + if (state == CastSessionState.connected) { + const snackBar = SnackBar(content: Text('Connected')); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + + _sendPairingRequestToTV(session); + } + }); + + session.messageStream.listen((message) { + print('receive message: $message'); + }); + + session.sendMessage(CastSession.kNamespaceReceiver, { + 'type': 'LAUNCH', + 'appId': 'F5BCEC64', // set the appId of your app here + }); + + _sendPairingRequestToTV(session); + } + + void _sendPairingRequestToTV(CastSession session) { + print('_sendMessageToYourApp'); + + session.sendMessage('urn:x-cast:pair-request', {}); + } Future onCleanUncategorizedClick(BuildContext buildContext) async { final actionResult = await showChoiceActionSheet( From 89646ac4698820f86574a916840f979049a1267a Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:55:22 +0530 Subject: [PATCH 004/367] [mob][cast] Refactor + add multiple cast plugin to gracefully handle fdroid --- mobile/lib/service_locator.dart | 28 +- .../gallery/gallery_app_bar_widget.dart | 42 +-- mobile/plugins/ente_cast/.metadata | 10 + .../plugins/ente_cast/analysis_options.yaml | 1 + mobile/plugins/ente_cast/lib/ente_cast.dart | 1 + mobile/plugins/ente_cast/lib/src/service.dart | 7 + mobile/plugins/ente_cast/pubspec.yaml | 19 + mobile/plugins/ente_cast_none/.metadata | 10 + .../ente_cast_none/analysis_options.yaml | 1 + .../ente_cast_none/lib/ente_cast_none.dart | 1 + .../ente_cast_none/lib/src/service.dart | 17 + mobile/plugins/ente_cast_none/pubspec.yaml | 18 + mobile/plugins/ente_cast_normal/.metadata | 10 + .../ente_cast_normal/analysis_options.yaml | 1 + .../lib/ente_cast_normal.dart | 1 + .../ente_cast_normal/lib/src/service.dart | 37 ++ mobile/plugins/ente_cast_normal/pubspec.lock | 333 ++++++++++++++++++ mobile/plugins/ente_cast_normal/pubspec.yaml | 22 ++ mobile/pubspec.lock | 16 +- mobile/pubspec.yaml | 8 +- 20 files changed, 532 insertions(+), 51 deletions(-) create mode 100644 mobile/plugins/ente_cast/.metadata create mode 100644 mobile/plugins/ente_cast/analysis_options.yaml create mode 100644 mobile/plugins/ente_cast/lib/ente_cast.dart create mode 100644 mobile/plugins/ente_cast/lib/src/service.dart create mode 100644 mobile/plugins/ente_cast/pubspec.yaml create mode 100644 mobile/plugins/ente_cast_none/.metadata create mode 100644 mobile/plugins/ente_cast_none/analysis_options.yaml create mode 100644 mobile/plugins/ente_cast_none/lib/ente_cast_none.dart create mode 100644 mobile/plugins/ente_cast_none/lib/src/service.dart create mode 100644 mobile/plugins/ente_cast_none/pubspec.yaml create mode 100644 mobile/plugins/ente_cast_normal/.metadata create mode 100644 mobile/plugins/ente_cast_normal/analysis_options.yaml create mode 100644 mobile/plugins/ente_cast_normal/lib/ente_cast_normal.dart create mode 100644 mobile/plugins/ente_cast_normal/lib/src/service.dart create mode 100644 mobile/plugins/ente_cast_normal/pubspec.lock create mode 100644 mobile/plugins/ente_cast_normal/pubspec.yaml diff --git a/mobile/lib/service_locator.dart b/mobile/lib/service_locator.dart index 0fec75b46..eb79aab0f 100644 --- a/mobile/lib/service_locator.dart +++ b/mobile/lib/service_locator.dart @@ -1,7 +1,25 @@ import "package:dio/dio.dart"; +import "package:ente_cast/ente_cast.dart"; +import "package:ente_cast_normal/ente_cast_normal.dart"; import "package:ente_feature_flag/ente_feature_flag.dart"; import "package:shared_preferences/shared_preferences.dart"; +CastService? _castService; +CastService get castService { + _castService ??= CastServiceImpl(); + return _castService!; +} + +FlagService? _flagService; + +FlagService get flagService { + _flagService ??= FlagService( + ServiceLocator.instance.prefs, + ServiceLocator.instance.enteDio, + ); + return _flagService!; +} + class ServiceLocator { late final SharedPreferences prefs; late final Dio enteDio; @@ -16,13 +34,3 @@ class ServiceLocator { this.enteDio = enteDio; } } - -FlagService? _flagService; - -FlagService get flagService { - _flagService ??= FlagService( - ServiceLocator.instance.prefs, - ServiceLocator.instance.enteDio, - ); - return _flagService!; -} diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 6c6c1e2ad..f433b336c 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -2,10 +2,6 @@ import 'dart:async'; import 'dart:io'; import 'dart:math' as math; -import "package:cast/device.dart"; -import "package:cast/discovery_service.dart"; -import "package:cast/session.dart"; -import "package:cast/session_manager.dart"; import "package:flutter/cupertino.dart"; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; @@ -580,7 +576,7 @@ class _GalleryAppBarWidgetState extends State { ), ); } - if (widget.collection != null) { + if (widget.collection != null && castService.isSupported) { actions.add(castWidget(context)); } if (items.isNotEmpty) { @@ -650,8 +646,8 @@ class _GalleryAppBarWidgetState extends State { } Widget castWidget(BuildContext context) { - return FutureBuilder>( - future: CastDiscoveryService().search(), + return FutureBuilder>( + future: castService.searchDevices(), builder: (context, snapshot) { if (snapshot.hasError) { return Center( @@ -679,7 +675,7 @@ class _GalleryAppBarWidgetState extends State { showGenericErrorDialog(context: context, error: e).ignore(); } }, - child: Text( device.name), + child: Text(device.toString()), ); }).toList(), ); @@ -689,35 +685,9 @@ class _GalleryAppBarWidgetState extends State { Future _connectToYourApp( BuildContext context, - CastDevice object, + Object castDevice, ) async { - final session = await CastSessionManager().startSession(object); - - session.stateStream.listen((state) { - if (state == CastSessionState.connected) { - const snackBar = SnackBar(content: Text('Connected')); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - - _sendPairingRequestToTV(session); - } - }); - - session.messageStream.listen((message) { - print('receive message: $message'); - }); - - session.sendMessage(CastSession.kNamespaceReceiver, { - 'type': 'LAUNCH', - 'appId': 'F5BCEC64', // set the appId of your app here - }); - - _sendPairingRequestToTV(session); - } - - void _sendPairingRequestToTV(CastSession session) { - print('_sendMessageToYourApp'); - - session.sendMessage('urn:x-cast:pair-request', {}); + await castService.connectDevice(context, castDevice); } Future onCleanUncategorizedClick(BuildContext buildContext) async { diff --git a/mobile/plugins/ente_cast/.metadata b/mobile/plugins/ente_cast/.metadata new file mode 100644 index 000000000..9fc7ede54 --- /dev/null +++ b/mobile/plugins/ente_cast/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 0b8abb4724aa590dd0f429683339b1e045a1594d + channel: stable + +project_type: plugin diff --git a/mobile/plugins/ente_cast/analysis_options.yaml b/mobile/plugins/ente_cast/analysis_options.yaml new file mode 100644 index 000000000..fac60e247 --- /dev/null +++ b/mobile/plugins/ente_cast/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analysis_options.yaml \ No newline at end of file diff --git a/mobile/plugins/ente_cast/lib/ente_cast.dart b/mobile/plugins/ente_cast/lib/ente_cast.dart new file mode 100644 index 000000000..66a7132d8 --- /dev/null +++ b/mobile/plugins/ente_cast/lib/ente_cast.dart @@ -0,0 +1 @@ +export 'src/service.dart'; diff --git a/mobile/plugins/ente_cast/lib/src/service.dart b/mobile/plugins/ente_cast/lib/src/service.dart new file mode 100644 index 000000000..74834f79d --- /dev/null +++ b/mobile/plugins/ente_cast/lib/src/service.dart @@ -0,0 +1,7 @@ +import "package:flutter/widgets.dart"; + +abstract class CastService { + bool get isSupported; + Future> searchDevices(); + Future connectDevice(BuildContext context, Object device); +} diff --git a/mobile/plugins/ente_cast/pubspec.yaml b/mobile/plugins/ente_cast/pubspec.yaml new file mode 100644 index 000000000..8ed1e7412 --- /dev/null +++ b/mobile/plugins/ente_cast/pubspec.yaml @@ -0,0 +1,19 @@ +name: ente_cast +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=3.3.0 <4.0.0' + +dependencies: + collection: + dio: ^4.0.6 + flutter: + sdk: flutter + shared_preferences: ^2.0.5 + stack_trace: + +dev_dependencies: + flutter_lints: + +flutter: \ No newline at end of file diff --git a/mobile/plugins/ente_cast_none/.metadata b/mobile/plugins/ente_cast_none/.metadata new file mode 100644 index 000000000..9fc7ede54 --- /dev/null +++ b/mobile/plugins/ente_cast_none/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 0b8abb4724aa590dd0f429683339b1e045a1594d + channel: stable + +project_type: plugin diff --git a/mobile/plugins/ente_cast_none/analysis_options.yaml b/mobile/plugins/ente_cast_none/analysis_options.yaml new file mode 100644 index 000000000..fac60e247 --- /dev/null +++ b/mobile/plugins/ente_cast_none/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analysis_options.yaml \ No newline at end of file diff --git a/mobile/plugins/ente_cast_none/lib/ente_cast_none.dart b/mobile/plugins/ente_cast_none/lib/ente_cast_none.dart new file mode 100644 index 000000000..66a7132d8 --- /dev/null +++ b/mobile/plugins/ente_cast_none/lib/ente_cast_none.dart @@ -0,0 +1 @@ +export 'src/service.dart'; diff --git a/mobile/plugins/ente_cast_none/lib/src/service.dart b/mobile/plugins/ente_cast_none/lib/src/service.dart new file mode 100644 index 000000000..964f4e472 --- /dev/null +++ b/mobile/plugins/ente_cast_none/lib/src/service.dart @@ -0,0 +1,17 @@ +import "package:ente_cast/ente_cast.dart"; +import "package:flutter/widgets.dart"; + +class CastServiceImpl extends CastService { + @override + Future connectDevice(BuildContext context, Object device) { + throw UnimplementedError(); + } + + @override + Future> searchDevices() { + throw UnimplementedError(); + } + + @override + bool get isSupported => false; +} diff --git a/mobile/plugins/ente_cast_none/pubspec.yaml b/mobile/plugins/ente_cast_none/pubspec.yaml new file mode 100644 index 000000000..0484f000f --- /dev/null +++ b/mobile/plugins/ente_cast_none/pubspec.yaml @@ -0,0 +1,18 @@ +name: ente_cast_none +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=3.3.0 <4.0.0' + +dependencies: + ente_cast: + path: ../ente_cast + flutter: + sdk: flutter + stack_trace: + +dev_dependencies: + flutter_lints: + +flutter: \ No newline at end of file diff --git a/mobile/plugins/ente_cast_normal/.metadata b/mobile/plugins/ente_cast_normal/.metadata new file mode 100644 index 000000000..9fc7ede54 --- /dev/null +++ b/mobile/plugins/ente_cast_normal/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 0b8abb4724aa590dd0f429683339b1e045a1594d + channel: stable + +project_type: plugin diff --git a/mobile/plugins/ente_cast_normal/analysis_options.yaml b/mobile/plugins/ente_cast_normal/analysis_options.yaml new file mode 100644 index 000000000..fac60e247 --- /dev/null +++ b/mobile/plugins/ente_cast_normal/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analysis_options.yaml \ No newline at end of file diff --git a/mobile/plugins/ente_cast_normal/lib/ente_cast_normal.dart b/mobile/plugins/ente_cast_normal/lib/ente_cast_normal.dart new file mode 100644 index 000000000..66a7132d8 --- /dev/null +++ b/mobile/plugins/ente_cast_normal/lib/ente_cast_normal.dart @@ -0,0 +1 @@ +export 'src/service.dart'; diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/plugins/ente_cast_normal/lib/src/service.dart new file mode 100644 index 000000000..1664511b6 --- /dev/null +++ b/mobile/plugins/ente_cast_normal/lib/src/service.dart @@ -0,0 +1,37 @@ +import "package:cast/cast.dart"; +import "package:ente_cast/ente_cast.dart"; +import "package:flutter/material.dart"; + +class CastServiceImpl extends CastService { + @override + Future connectDevice(BuildContext context, Object device) async { + final CastDevice castDevice = device as CastDevice; + final session = await CastSessionManager().startSession(castDevice); + + session.stateStream.listen((state) { + if (state == CastSessionState.connected) { + const snackBar = SnackBar(content: Text('Connected')); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + session.sendMessage('urn:x-cast:pair-request', {}); + } + }); + session.messageStream.listen((message) { + print('receive message: $message'); + }); + + session.sendMessage(CastSession.kNamespaceReceiver, { + 'type': 'LAUNCH', + 'appId': 'F5BCEC64', // set the appId of your app here + }); + session.sendMessage('urn:x-cast:pair-request', {}); + } + + @override + Future> searchDevices() { + // TODO: implement searchDevices + throw UnimplementedError(); + } + + @override + bool get isSupported => true; +} diff --git a/mobile/plugins/ente_cast_normal/pubspec.lock b/mobile/plugins/ente_cast_normal/pubspec.lock new file mode 100644 index 000000000..86051800c --- /dev/null +++ b/mobile/plugins/ente_cast_normal/pubspec.lock @@ -0,0 +1,333 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + cast: + dependency: "direct main" + description: + path: "." + ref: multicast_version + resolved-ref: "1f39cd4d6efa9363e77b2439f0317bae0c92dda1" + url: "https://github.com/guyluz11/flutter_cast.git" + source: git + version: "2.0.9" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + dio: + dependency: transitive + description: + name: dio + sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + url: "https://pub.dev" + source: hosted + version: "4.0.6" + ente_cast: + dependency: "direct main" + description: + path: "../ente_cast" + relative: true + source: path + version: "0.0.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + multicast_dns: + dependency: transitive + description: + name: multicast_dns + sha256: "316cc47a958d4bd3c67bd238fe8b44fdfb6133bad89cb191c0c3bd3edb14e296" + url: "https://pub.dev" + source: hosted + version: "0.3.2+6" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 + url: "https://pub.dev" + source: hosted + version: "2.2.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + url: "https://pub.dev" + source: hosted + version: "2.3.5" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: "direct main" + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" + url: "https://pub.dev" + source: hosted + version: "5.4.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" +sdks: + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/mobile/plugins/ente_cast_normal/pubspec.yaml b/mobile/plugins/ente_cast_normal/pubspec.yaml new file mode 100644 index 000000000..17d172121 --- /dev/null +++ b/mobile/plugins/ente_cast_normal/pubspec.yaml @@ -0,0 +1,22 @@ +name: ente_cast_normal +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=3.3.0 <4.0.0' + +dependencies: + cast: + git: + url: https://github.com/guyluz11/flutter_cast.git + ref: multicast_version + ente_cast: + path: ../ente_cast + flutter: + sdk: flutter + stack_trace: + +dev_dependencies: + flutter_lints: + +flutter: \ No newline at end of file diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 882967ff0..f776c0909 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -210,7 +210,7 @@ packages: source: hosted version: "1.1.1" cast: - dependency: "direct main" + dependency: transitive description: path: "." ref: multicast_version @@ -443,6 +443,20 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.17" + ente_cast: + dependency: "direct main" + description: + path: "plugins/ente_cast" + relative: true + source: path + version: "0.0.1" + ente_cast_normal: + dependency: "direct main" + description: + path: "plugins/ente_cast_normal" + relative: true + source: path + version: "0.0.1" ente_feature_flag: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index a641211ea..da2f4e089 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -27,10 +27,6 @@ dependencies: battery_info: ^1.1.1 bip39: ^1.0.6 cached_network_image: ^3.0.0 - cast: - git: - url: https://github.com/guyluz11/flutter_cast.git - ref: multicast_version chewie: git: url: https://github.com/ente-io/chewie.git @@ -51,6 +47,10 @@ dependencies: dotted_border: ^2.1.0 dropdown_button2: ^2.0.0 email_validator: ^2.0.1 + ente_cast: + path: plugins/ente_cast + ente_cast_normal: + path: plugins/ente_cast_normal ente_feature_flag: path: plugins/ente_feature_flag equatable: ^2.0.5 From bd225ced041a9de92b8af6d951d36f08a6f0f156 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:08:16 +0530 Subject: [PATCH 005/367] [mob][cast] Return name and castDevice as record --- mobile/lib/service_locator.dart | 32 +++++++++---------- .../gallery/gallery_app_bar_widget.dart | 8 +++-- .../plugins/ente_cast/analysis_options.yaml | 2 +- mobile/plugins/ente_cast/lib/src/service.dart | 2 +- mobile/plugins/ente_cast/pubspec.yaml | 2 +- .../ente_cast_none/analysis_options.yaml | 2 +- .../ente_cast_none/lib/src/service.dart | 9 +++--- mobile/plugins/ente_cast_none/pubspec.yaml | 2 +- .../ente_cast_normal/analysis_options.yaml | 2 +- .../ente_cast_normal/lib/src/service.dart | 7 ++-- mobile/plugins/ente_cast_normal/pubspec.yaml | 2 +- 11 files changed, 37 insertions(+), 33 deletions(-) diff --git a/mobile/lib/service_locator.dart b/mobile/lib/service_locator.dart index eb79aab0f..397703761 100644 --- a/mobile/lib/service_locator.dart +++ b/mobile/lib/service_locator.dart @@ -4,22 +4,6 @@ import "package:ente_cast_normal/ente_cast_normal.dart"; import "package:ente_feature_flag/ente_feature_flag.dart"; import "package:shared_preferences/shared_preferences.dart"; -CastService? _castService; -CastService get castService { - _castService ??= CastServiceImpl(); - return _castService!; -} - -FlagService? _flagService; - -FlagService get flagService { - _flagService ??= FlagService( - ServiceLocator.instance.prefs, - ServiceLocator.instance.enteDio, - ); - return _flagService!; -} - class ServiceLocator { late final SharedPreferences prefs; late final Dio enteDio; @@ -34,3 +18,19 @@ class ServiceLocator { this.enteDio = enteDio; } } + +FlagService? _flagService; + +FlagService get flagService { + _flagService ??= FlagService( + ServiceLocator.instance.prefs, + ServiceLocator.instance.enteDio, + ); + return _flagService!; +} + +CastService? _castService; +CastService get castService { + _castService ??= CastServiceImpl(); + return _castService!; +} diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index f433b336c..a33fc9628 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -646,7 +646,7 @@ class _GalleryAppBarWidgetState extends State { } Widget castWidget(BuildContext context) { - return FutureBuilder>( + return FutureBuilder>( future: castService.searchDevices(), builder: (context, snapshot) { if (snapshot.hasError) { @@ -666,7 +666,9 @@ class _GalleryAppBarWidgetState extends State { } return Column( - children: snapshot.data!.map((device) { + children: snapshot.data!.map((result) { + final device = result.$2; + final name = result.$1; return GestureDetector( onTap: () async { try { @@ -675,7 +677,7 @@ class _GalleryAppBarWidgetState extends State { showGenericErrorDialog(context: context, error: e).ignore(); } }, - child: Text(device.toString()), + child: Text(name), ); }).toList(), ); diff --git a/mobile/plugins/ente_cast/analysis_options.yaml b/mobile/plugins/ente_cast/analysis_options.yaml index fac60e247..f04c6cf0f 100644 --- a/mobile/plugins/ente_cast/analysis_options.yaml +++ b/mobile/plugins/ente_cast/analysis_options.yaml @@ -1 +1 @@ -include: ../../analysis_options.yaml \ No newline at end of file +include: ../../analysis_options.yaml diff --git a/mobile/plugins/ente_cast/lib/src/service.dart b/mobile/plugins/ente_cast/lib/src/service.dart index 74834f79d..230269bb3 100644 --- a/mobile/plugins/ente_cast/lib/src/service.dart +++ b/mobile/plugins/ente_cast/lib/src/service.dart @@ -2,6 +2,6 @@ import "package:flutter/widgets.dart"; abstract class CastService { bool get isSupported; - Future> searchDevices(); + Future> searchDevices(); Future connectDevice(BuildContext context, Object device); } diff --git a/mobile/plugins/ente_cast/pubspec.yaml b/mobile/plugins/ente_cast/pubspec.yaml index 8ed1e7412..967e147e9 100644 --- a/mobile/plugins/ente_cast/pubspec.yaml +++ b/mobile/plugins/ente_cast/pubspec.yaml @@ -16,4 +16,4 @@ dependencies: dev_dependencies: flutter_lints: -flutter: \ No newline at end of file +flutter: diff --git a/mobile/plugins/ente_cast_none/analysis_options.yaml b/mobile/plugins/ente_cast_none/analysis_options.yaml index fac60e247..f04c6cf0f 100644 --- a/mobile/plugins/ente_cast_none/analysis_options.yaml +++ b/mobile/plugins/ente_cast_none/analysis_options.yaml @@ -1 +1 @@ -include: ../../analysis_options.yaml \ No newline at end of file +include: ../../analysis_options.yaml diff --git a/mobile/plugins/ente_cast_none/lib/src/service.dart b/mobile/plugins/ente_cast_none/lib/src/service.dart index 964f4e472..166108c52 100644 --- a/mobile/plugins/ente_cast_none/lib/src/service.dart +++ b/mobile/plugins/ente_cast_none/lib/src/service.dart @@ -8,10 +8,11 @@ class CastServiceImpl extends CastService { } @override - Future> searchDevices() { - throw UnimplementedError(); - } + bool get isSupported => false; @override - bool get isSupported => false; + Future> searchDevices() { + // TODO: implement searchDevices + throw UnimplementedError(); + } } diff --git a/mobile/plugins/ente_cast_none/pubspec.yaml b/mobile/plugins/ente_cast_none/pubspec.yaml index 0484f000f..a4559fac5 100644 --- a/mobile/plugins/ente_cast_none/pubspec.yaml +++ b/mobile/plugins/ente_cast_none/pubspec.yaml @@ -15,4 +15,4 @@ dependencies: dev_dependencies: flutter_lints: -flutter: \ No newline at end of file +flutter: diff --git a/mobile/plugins/ente_cast_normal/analysis_options.yaml b/mobile/plugins/ente_cast_normal/analysis_options.yaml index fac60e247..f04c6cf0f 100644 --- a/mobile/plugins/ente_cast_normal/analysis_options.yaml +++ b/mobile/plugins/ente_cast_normal/analysis_options.yaml @@ -1 +1 @@ -include: ../../analysis_options.yaml \ No newline at end of file +include: ../../analysis_options.yaml diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/plugins/ente_cast_normal/lib/src/service.dart index 1664511b6..eac98ae51 100644 --- a/mobile/plugins/ente_cast_normal/lib/src/service.dart +++ b/mobile/plugins/ente_cast_normal/lib/src/service.dart @@ -27,9 +27,10 @@ class CastServiceImpl extends CastService { } @override - Future> searchDevices() { - // TODO: implement searchDevices - throw UnimplementedError(); + Future> searchDevices() { + return CastDiscoveryService().search().then((devices) { + return devices.map((device) => (device.name, device)).toList(); + }); } @override diff --git a/mobile/plugins/ente_cast_normal/pubspec.yaml b/mobile/plugins/ente_cast_normal/pubspec.yaml index 17d172121..c97d70a84 100644 --- a/mobile/plugins/ente_cast_normal/pubspec.yaml +++ b/mobile/plugins/ente_cast_normal/pubspec.yaml @@ -19,4 +19,4 @@ dependencies: dev_dependencies: flutter_lints: -flutter: \ No newline at end of file +flutter: From 1251a014b052082c0a044aad5a25e4bddc130c58 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 24 Apr 2024 10:57:28 +0530 Subject: [PATCH 006/367] [mob][cast] Show choice to auto and manual pair --- mobile/lib/generated/intl/messages_en.dart | 2 + mobile/lib/generated/l10n.dart | 20 +++++++ mobile/lib/l10n/intl_en.arb | 2 + .../gallery/gallery_app_bar_widget.dart | 54 +++++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index eef309aa5..4de38ce12 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -357,6 +357,7 @@ class MessageLookup extends MessageLookupByLibrary { "Authentication failed, please try again"), "authenticationSuccessful": MessageLookupByLibrary.simpleMessage("Authentication successful!"), + "autoPair": MessageLookupByLibrary.simpleMessage("Auto pair"), "available": MessageLookupByLibrary.simpleMessage("Available"), "backedUpFolders": MessageLookupByLibrary.simpleMessage("Backed up folders"), @@ -982,6 +983,7 @@ class MessageLookup extends MessageLookupByLibrary { "orPickAnExistingOne": MessageLookupByLibrary.simpleMessage("Or pick an existing one"), "pair": MessageLookupByLibrary.simpleMessage("Pair"), + "pairWithPin": MessageLookupByLibrary.simpleMessage("Pair with PIN"), "passkey": MessageLookupByLibrary.simpleMessage("Passkey"), "passkeyAuthTitle": MessageLookupByLibrary.simpleMessage("Passkey verification"), diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index 3fa9c2209..7f5dc9614 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -8378,6 +8378,26 @@ class S { ); } + /// `Auto pair` + String get autoPair { + return Intl.message( + 'Auto pair', + name: 'autoPair', + desc: '', + args: [], + ); + } + + /// `Pair with PIN` + String get pairWithPin { + return Intl.message( + 'Pair with PIN', + name: 'pairWithPin', + desc: '', + args: [], + ); + } + /// `Device not found` String get deviceNotFound { return Intl.message( diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index 7115c6950..ee0049935 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1195,6 +1195,8 @@ "verifyPasskey": "Verify passkey", "playOnTv": "Play album on TV", "pair": "Pair", + "autoPair": "Auto pair", + "pairWithPin": "Pair with PIN", "deviceNotFound": "Device not found", "castInstruction": "Visit cast.ente.io on the device you want to pair.\n\nEnter the code below to play the album on your TV.", "deviceCodeHint": "Enter the code", diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index a33fc9628..35933272d 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -26,6 +26,7 @@ import 'package:photos/services/update_service.dart'; import 'package:photos/ui/actions/collection/collection_sharing_actions.dart'; import 'package:photos/ui/components/action_sheet_widget.dart'; import 'package:photos/ui/components/buttons/button_widget.dart'; +import "package:photos/ui/components/dialog_widget.dart"; import 'package:photos/ui/components/models/button_type.dart'; import "package:photos/ui/map/enable_map.dart"; import "package:photos/ui/map/map_screen.dart"; @@ -892,6 +893,59 @@ class _GalleryAppBarWidgetState extends State { final gw = CastGateway(NetworkClient.instance.enteDio); // stop any existing cast session gw.revokeAllTokens().ignore(); + final result = await showDialogWidget( + context: context, + title: S.of(context).playOnTv, + body: + "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.\n\nPair with PIN works for any large screen device you want to play your album on.", + buttons: [ + ButtonWidget( + labelText: S.of(context).autoPair, + icon: Icons.cast_outlined, + buttonType: ButtonType.trailingIconPrimary, + buttonSize: ButtonSize.large, + shouldStickToDarkTheme: true, + buttonAction: ButtonAction.first, + shouldSurfaceExecutionStates: true, + isInAlert: true, + onTap: () async { + showToast(context, "Coming soon"); + // await _castAlbum(gw); + }, + ), + ButtonWidget( + labelText: S.of(context).pairWithPin, + buttonType: ButtonType.trailingIconPrimary, + // icon for pairing with TV manually + icon: Icons.tv_outlined, + buttonSize: ButtonSize.large, + isInAlert: true, + shouldStickToDarkTheme: true, + buttonAction: ButtonAction.second, + shouldSurfaceExecutionStates: false, + ), + // cancel button + ], + ); + _logger.info("Cast result: $result"); + if (result == null) { + return; + } + if (result.action == ButtonAction.error) { + await showGenericErrorDialog( + context: context, + error: result.exception, + ); + } + if (result.action == ButtonAction.first) { + showToast(context, "Coming soon"); + } + if (result.action == ButtonAction.second) { + await _pairWithPin(gw); + } + } + + Future _pairWithPin(CastGateway gw) async { await showTextInputDialog( context, title: context.l10n.playOnTv, From bed14d8ee98459e5f6871dbf6036a08af0561e8b Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 24 Apr 2024 11:38:22 +0530 Subject: [PATCH 007/367] [mob][photos] Use cast Icon in appbar --- .../gallery/gallery_app_bar_widget.dart | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 35933272d..e29ca9d3b 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -578,7 +578,17 @@ class _GalleryAppBarWidgetState extends State { ); } if (widget.collection != null && castService.isSupported) { - actions.add(castWidget(context)); + actions.add( + Tooltip( + message: "Cast album", + child: IconButton( + icon: const Icon(Icons.cast_outlined), + onPressed: () async { + await _castChoiceDialog(); + }, + ), + ), + ); } if (items.isNotEmpty) { actions.add( @@ -607,7 +617,7 @@ class _GalleryAppBarWidgetState extends State { } else if (value == AlbumPopupAction.leave) { await _leaveAlbum(context); } else if (value == AlbumPopupAction.playOnTv) { - await castAlbum(); + await _castChoiceDialog(); } else if (value == AlbumPopupAction.freeUpSpace) { await _deleteBackedUpFiles(context); } else if (value == AlbumPopupAction.setCover) { @@ -889,7 +899,7 @@ class _GalleryAppBarWidgetState extends State { setState(() {}); } - Future castAlbum() async { + Future _castChoiceDialog() async { final gw = CastGateway(NetworkClient.instance.enteDio); // stop any existing cast session gw.revokeAllTokens().ignore(); @@ -908,10 +918,6 @@ class _GalleryAppBarWidgetState extends State { buttonAction: ButtonAction.first, shouldSurfaceExecutionStates: true, isInAlert: true, - onTap: () async { - showToast(context, "Coming soon"); - // await _castAlbum(gw); - }, ), ButtonWidget( labelText: S.of(context).pairWithPin, From 729e2adfd11c6d28f5dc38d228e27c94a5bcb589 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 24 Apr 2024 13:21:03 +0530 Subject: [PATCH 008/367] [mob] Use separate widget for auto-cast --- mobile/lib/service_locator.dart | 2 +- mobile/lib/ui/cast/auto.dart | 92 +++++++++++++++++++ .../gallery/gallery_app_bar_widget.dart | 56 ++--------- 3 files changed, 101 insertions(+), 49 deletions(-) create mode 100644 mobile/lib/ui/cast/auto.dart diff --git a/mobile/lib/service_locator.dart b/mobile/lib/service_locator.dart index 397703761..4d75d8e35 100644 --- a/mobile/lib/service_locator.dart +++ b/mobile/lib/service_locator.dart @@ -1,6 +1,6 @@ import "package:dio/dio.dart"; import "package:ente_cast/ente_cast.dart"; -import "package:ente_cast_normal/ente_cast_normal.dart"; +import "package:ente_cast_none/ente_cast_none.dart"; import "package:ente_feature_flag/ente_feature_flag.dart"; import "package:shared_preferences/shared_preferences.dart"; diff --git a/mobile/lib/ui/cast/auto.dart b/mobile/lib/ui/cast/auto.dart new file mode 100644 index 000000000..5d8b64175 --- /dev/null +++ b/mobile/lib/ui/cast/auto.dart @@ -0,0 +1,92 @@ +import "dart:io"; + +import "package:flutter/material.dart"; +import "package:photos/service_locator.dart"; +import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/common/loading_widget.dart"; +import "package:photos/utils/dialog_util.dart"; + +class AutoCastDialog extends StatefulWidget { + AutoCastDialog({ + Key? key, + }) : super(key: key) {} + + @override + State createState() => _AutoCastDialogState(); +} + +class _AutoCastDialogState extends State { + final bool doesUserExist = true; + + @override + Widget build(BuildContext context) { + final textStyle = getEnteTextTheme(context); + + final AlertDialog alert = AlertDialog( + title: Text( + "Connect to device", + style: textStyle.largeBold, + ), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "You'll see available Cast devices here.", + style: textStyle.bodyMuted, + ), + if (Platform.isIOS) + Text( + "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.", + style: textStyle.bodyMuted, + ), + const SizedBox(height: 16), + FutureBuilder>( + future: castService.searchDevices(), + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Text( + 'Error: ${snapshot.error.toString()}', + ), + ); + } else if (!snapshot.hasData) { + return const EnteLoadingWidget(); + } + + if (snapshot.data!.isEmpty) { + return const Center(child: Text('No device')); + } + + return Column( + children: snapshot.data!.map((result) { + final device = result.$2; + final name = result.$1; + return GestureDetector( + onTap: () async { + try { + await _connectToYourApp(context, device); + } catch (e) { + showGenericErrorDialog(context: context, error: e) + .ignore(); + } + }, + child: Text(name), + ); + }).toList(), + ); + }, + ), + ], + ), + ); + return alert; + } + + Future _connectToYourApp( + BuildContext context, + Object castDevice, + ) async { + await castService.connectDevice(context, castDevice); + } +} diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index e29ca9d3b..086343516 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -24,6 +24,7 @@ import 'package:photos/services/collections_service.dart'; import 'package:photos/services/sync_service.dart'; import 'package:photos/services/update_service.dart'; import 'package:photos/ui/actions/collection/collection_sharing_actions.dart'; +import "package:photos/ui/cast/auto.dart"; import 'package:photos/ui/components/action_sheet_widget.dart'; import 'package:photos/ui/components/buttons/button_widget.dart'; import "package:photos/ui/components/dialog_widget.dart"; @@ -656,53 +657,6 @@ class _GalleryAppBarWidgetState extends State { return actions; } - Widget castWidget(BuildContext context) { - return FutureBuilder>( - future: castService.searchDevices(), - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center( - child: Text( - 'Error: ${snapshot.error.toString()}', - ), - ); - } else if (!snapshot.hasData) { - return const Center( - child: CircularProgressIndicator(), - ); - } - - if (snapshot.data!.isEmpty) { - return const Text('No device'); - } - - return Column( - children: snapshot.data!.map((result) { - final device = result.$2; - final name = result.$1; - return GestureDetector( - onTap: () async { - try { - await _connectToYourApp(context, device); - } catch (e) { - showGenericErrorDialog(context: context, error: e).ignore(); - } - }, - child: Text(name), - ); - }).toList(), - ); - }, - ); - } - - Future _connectToYourApp( - BuildContext context, - Object castDevice, - ) async { - await castService.connectDevice(context, castDevice); - } - Future onCleanUncategorizedClick(BuildContext buildContext) async { final actionResult = await showChoiceActionSheet( context, @@ -944,7 +898,13 @@ class _GalleryAppBarWidgetState extends State { ); } if (result.action == ButtonAction.first) { - showToast(context, "Coming soon"); + await showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return AutoCastDialog(); + }, + ); } if (result.action == ButtonAction.second) { await _pairWithPin(gw); From aced4bb5cf2caea91c5ee97e0ac75fdb1b0d8aaf Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 24 Apr 2024 15:22:24 +0530 Subject: [PATCH 009/367] [mob][photos] Update cast selection dialog --- mobile/lib/service_locator.dart | 2 +- mobile/lib/ui/cast/choose.dart | 83 +++++++++++++++++++ .../gallery/gallery_app_bar_widget.dart | 47 +++-------- 3 files changed, 94 insertions(+), 38 deletions(-) create mode 100644 mobile/lib/ui/cast/choose.dart diff --git a/mobile/lib/service_locator.dart b/mobile/lib/service_locator.dart index 4d75d8e35..397703761 100644 --- a/mobile/lib/service_locator.dart +++ b/mobile/lib/service_locator.dart @@ -1,6 +1,6 @@ import "package:dio/dio.dart"; import "package:ente_cast/ente_cast.dart"; -import "package:ente_cast_none/ente_cast_none.dart"; +import "package:ente_cast_normal/ente_cast_normal.dart"; import "package:ente_feature_flag/ente_feature_flag.dart"; import "package:shared_preferences/shared_preferences.dart"; diff --git a/mobile/lib/ui/cast/choose.dart b/mobile/lib/ui/cast/choose.dart new file mode 100644 index 000000000..0b82ea299 --- /dev/null +++ b/mobile/lib/ui/cast/choose.dart @@ -0,0 +1,83 @@ +import "package:flutter/material.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/service_locator.dart"; +import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/components/buttons/button_widget.dart"; +import "package:photos/ui/components/models/button_type.dart"; + +class CastChooseDialog extends StatefulWidget { + CastChooseDialog({ + Key? key, + }) : super(key: key) {} + + @override + State createState() => _AutoCastDialogState(); +} + +class _AutoCastDialogState extends State { + final bool doesUserExist = true; + + @override + Widget build(BuildContext context) { + final textStyle = getEnteTextTheme(context); + final AlertDialog alert = AlertDialog( + title: Text( + "Play album on TV", + style: textStyle.largeBold, + ), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + Text( + "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.", + style: textStyle.bodyMuted, + ), + const SizedBox(height: 12), + ButtonWidget( + labelText: S.of(context).autoPair, + icon: Icons.cast_outlined, + buttonType: ButtonType.primary, + buttonSize: ButtonSize.large, + shouldStickToDarkTheme: true, + buttonAction: ButtonAction.first, + shouldSurfaceExecutionStates: false, + isInAlert: true, + onTap: () async { + Navigator.of(context).pop(ButtonAction.first); + }, + ), + const SizedBox(height: 36), + Text( + "Pair with PIN works for any large screen device you want to play your album on.", + style: textStyle.bodyMuted, + ), + const SizedBox(height: 12), + ButtonWidget( + labelText: S.of(context).pairWithPin, + buttonType: ButtonType.primary, + // icon for pairing with TV manually + icon: Icons.tv_outlined, + buttonSize: ButtonSize.large, + isInAlert: true, + onTap: () async { + Navigator.of(context).pop(ButtonAction.second); + }, + shouldStickToDarkTheme: true, + buttonAction: ButtonAction.second, + shouldSurfaceExecutionStates: false, + ), + ], + ), + ); + return alert; + } + + Future _connectToYourApp( + BuildContext context, + Object castDevice, + ) async { + await castService.connectDevice(context, castDevice); + } +} diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 086343516..7b45ae53f 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -25,9 +25,9 @@ import 'package:photos/services/sync_service.dart'; import 'package:photos/services/update_service.dart'; import 'package:photos/ui/actions/collection/collection_sharing_actions.dart'; import "package:photos/ui/cast/auto.dart"; +import "package:photos/ui/cast/choose.dart"; import 'package:photos/ui/components/action_sheet_widget.dart'; import 'package:photos/ui/components/buttons/button_widget.dart'; -import "package:photos/ui/components/dialog_widget.dart"; import 'package:photos/ui/components/models/button_type.dart'; import "package:photos/ui/map/enable_map.dart"; import "package:photos/ui/map/map_screen.dart"; @@ -857,47 +857,20 @@ class _GalleryAppBarWidgetState extends State { final gw = CastGateway(NetworkClient.instance.enteDio); // stop any existing cast session gw.revokeAllTokens().ignore(); - final result = await showDialogWidget( + final result = await showDialog( context: context, - title: S.of(context).playOnTv, - body: - "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.\n\nPair with PIN works for any large screen device you want to play your album on.", - buttons: [ - ButtonWidget( - labelText: S.of(context).autoPair, - icon: Icons.cast_outlined, - buttonType: ButtonType.trailingIconPrimary, - buttonSize: ButtonSize.large, - shouldStickToDarkTheme: true, - buttonAction: ButtonAction.first, - shouldSurfaceExecutionStates: true, - isInAlert: true, - ), - ButtonWidget( - labelText: S.of(context).pairWithPin, - buttonType: ButtonType.trailingIconPrimary, - // icon for pairing with TV manually - icon: Icons.tv_outlined, - buttonSize: ButtonSize.large, - isInAlert: true, - shouldStickToDarkTheme: true, - buttonAction: ButtonAction.second, - shouldSurfaceExecutionStates: false, - ), - // cancel button - ], + barrierDismissible: true, + builder: (BuildContext context) { + return CastChooseDialog(); + }, ); _logger.info("Cast result: $result"); if (result == null) { return; } - if (result.action == ButtonAction.error) { - await showGenericErrorDialog( - context: context, - error: result.exception, - ); - } - if (result.action == ButtonAction.first) { + // wait to allow the dialog to close + await Future.delayed(const Duration(milliseconds: 100)); + if (result == ButtonAction.first) { await showDialog( context: context, barrierDismissible: true, @@ -906,7 +879,7 @@ class _GalleryAppBarWidgetState extends State { }, ); } - if (result.action == ButtonAction.second) { + if (result == ButtonAction.second) { await _pairWithPin(gw); } } From e903fbf9bcc15b2fb10a6f27c144e37e119ddae7 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:50:02 +0530 Subject: [PATCH 010/367] [mob][photos] Continue showing pair dialog in case of error --- .../ui/viewer/gallery/gallery_app_bar_widget.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 7b45ae53f..9e53014ed 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -880,11 +880,11 @@ class _GalleryAppBarWidgetState extends State { ); } if (result == ButtonAction.second) { - await _pairWithPin(gw); + await _pairWithPin(gw, ''); } } - Future _pairWithPin(CastGateway gw) async { + Future _pairWithPin(CastGateway gw, String code) async { await showTextInputDialog( context, title: context.l10n.playOnTv, @@ -892,12 +892,17 @@ class _GalleryAppBarWidgetState extends State { submitButtonLabel: S.of(context).pair, textInputType: TextInputType.streetAddress, hintText: context.l10n.deviceCodeHint, + showOnlyLoadingState: true, + alwaysShowSuccessState: false, + initialValue: code, onSubmit: (String text) async { try { - final code = text.trim(); + code = text.trim(); final String? publicKey = await gw.getPublicKey(code); if (publicKey == null) { showToast(context, S.of(context).deviceNotFound); + // show _pairPin again + Future.delayed(Duration.zero, () => _pairWithPin(gw, code)); return; } final String castToken = const Uuid().v4().toString(); @@ -912,6 +917,7 @@ class _GalleryAppBarWidgetState extends State { } catch (e, s) { _logger.severe("Failed to cast album", e, s); await showGenericErrorDialog(context: context, error: e); + Future.delayed(Duration.zero, () => _pairWithPin(gw, code)); } }, ); From 36dbda895c3983114c8f527df6dc55cca86255e3 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:50:49 +0530 Subject: [PATCH 011/367] [mob][photos] Send pair req after getting receiver status --- .../ente_cast_normal/lib/src/service.dart | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/plugins/ente_cast_normal/lib/src/service.dart index eac98ae51..4c4624d8c 100644 --- a/mobile/plugins/ente_cast_normal/lib/src/service.dart +++ b/mobile/plugins/ente_cast_normal/lib/src/service.dart @@ -1,29 +1,49 @@ +import "dart:developer" as dev; + import "package:cast/cast.dart"; import "package:ente_cast/ente_cast.dart"; import "package:flutter/material.dart"; class CastServiceImpl extends CastService { + final String _appId = 'F5BCEC64'; + final String _pairRequestNamespace = 'urn:x-cast:pair-request'; + final Map sessionIDToDeviceID = {}; + @override Future connectDevice(BuildContext context, Object device) async { final CastDevice castDevice = device as CastDevice; final session = await CastSessionManager().startSession(castDevice); + session.messageStream.listen((message) { + if (message['type'] == "RECEIVER_STATUS") { + dev.log( + "got RECEIVER_STATUS, Send request to pair", + name: "CastServiceImpl", + ); + session.sendMessage(_pairRequestNamespace, {}); + } else { + print('receive message: $message'); + } + }); session.stateStream.listen((state) { if (state == CastSessionState.connected) { const snackBar = SnackBar(content: Text('Connected')); ScaffoldMessenger.of(context).showSnackBar(snackBar); - session.sendMessage('urn:x-cast:pair-request', {}); + sessionIDToDeviceID[session.sessionId] = castDevice; + debugPrint("Send request to pair"); + session.sendMessage(_pairRequestNamespace, {}); + } else if (state == CastSessionState.closed) { + dev.log('Session closed', name: 'CastServiceImpl'); + sessionIDToDeviceID.remove(session.sessionId); } }); - session.messageStream.listen((message) { - print('receive message: $message'); - }); + debugPrint("Send request to launch"); session.sendMessage(CastSession.kNamespaceReceiver, { 'type': 'LAUNCH', - 'appId': 'F5BCEC64', // set the appId of your app here + 'appId': _appId, // set the appId of your app here }); - session.sendMessage('urn:x-cast:pair-request', {}); + // session.sendMessage('urn:x-cast:pair-request', {}); } @override From f777bdba1b0952d176e102de5c0f425d34d84c93 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:44:46 +0530 Subject: [PATCH 012/367] [mob][photos] Extract strings --- mobile/lib/generated/intl/messages_en.dart | 4 ++++ mobile/lib/generated/l10n.dart | 20 ++++++++++++++++++++ mobile/lib/l10n/intl_en.arb | 4 +++- mobile/lib/ui/cast/choose.dart | 19 ++++++------------- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index 4de38ce12..5c5a9fd4c 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -358,6 +358,8 @@ class MessageLookup extends MessageLookupByLibrary { "authenticationSuccessful": MessageLookupByLibrary.simpleMessage("Authentication successful!"), "autoPair": MessageLookupByLibrary.simpleMessage("Auto pair"), + "autoPairGoogle": MessageLookupByLibrary.simpleMessage( + "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos."), "available": MessageLookupByLibrary.simpleMessage("Available"), "backedUpFolders": MessageLookupByLibrary.simpleMessage("Backed up folders"), @@ -903,6 +905,8 @@ class MessageLookup extends MessageLookupByLibrary { "manageParticipants": MessageLookupByLibrary.simpleMessage("Manage"), "manageSubscription": MessageLookupByLibrary.simpleMessage("Manage subscription"), + "manualPairDesc": MessageLookupByLibrary.simpleMessage( + "Pair with PIN works for any large screen device you want to play your album on."), "map": MessageLookupByLibrary.simpleMessage("Map"), "maps": MessageLookupByLibrary.simpleMessage("Maps"), "mastodon": MessageLookupByLibrary.simpleMessage("Mastodon"), diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index 7f5dc9614..7af032ce1 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -8583,6 +8583,26 @@ class S { args: [], ); } + + /// `Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.` + String get autoPairGoogle { + return Intl.message( + 'Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.', + name: 'autoPairGoogle', + desc: '', + args: [], + ); + } + + /// `Pair with PIN works for any large screen device you want to play your album on.` + String get manualPairDesc { + return Intl.message( + 'Pair with PIN works for any large screen device you want to play your album on.', + name: 'manualPairDesc', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index ee0049935..0404e33f0 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1214,5 +1214,7 @@ "endpointUpdatedMessage": "Endpoint updated successfully", "customEndpoint": "Connected to {endpoint}", "createCollaborativeLink": "Create collaborative link", - "search": "Search" + "search": "Search", + "autoPairGoogle": "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.", + "manualPairDesc": "Pair with PIN works for any large screen device you want to play your album on." } \ No newline at end of file diff --git a/mobile/lib/ui/cast/choose.dart b/mobile/lib/ui/cast/choose.dart index 0b82ea299..f3ab39a62 100644 --- a/mobile/lib/ui/cast/choose.dart +++ b/mobile/lib/ui/cast/choose.dart @@ -1,6 +1,6 @@ import "package:flutter/material.dart"; import "package:photos/generated/l10n.dart"; -import "package:photos/service_locator.dart"; +import "package:photos/l10n/l10n.dart"; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/components/buttons/button_widget.dart"; import "package:photos/ui/components/models/button_type.dart"; @@ -11,10 +11,10 @@ class CastChooseDialog extends StatefulWidget { }) : super(key: key) {} @override - State createState() => _AutoCastDialogState(); + State createState() => _CastChooseDialogState(); } -class _AutoCastDialogState extends State { +class _CastChooseDialogState extends State { final bool doesUserExist = true; @override @@ -22,7 +22,7 @@ class _AutoCastDialogState extends State { final textStyle = getEnteTextTheme(context); final AlertDialog alert = AlertDialog( title: Text( - "Play album on TV", + context.l10n.playOnTv, style: textStyle.largeBold, ), content: Column( @@ -31,7 +31,7 @@ class _AutoCastDialogState extends State { children: [ const SizedBox(height: 8), Text( - "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.", + S.of(context).autoPairGoogle, style: textStyle.bodyMuted, ), const SizedBox(height: 12), @@ -50,7 +50,7 @@ class _AutoCastDialogState extends State { ), const SizedBox(height: 36), Text( - "Pair with PIN works for any large screen device you want to play your album on.", + S.of(context).manualPairDesc, style: textStyle.bodyMuted, ), const SizedBox(height: 12), @@ -73,11 +73,4 @@ class _AutoCastDialogState extends State { ); return alert; } - - Future _connectToYourApp( - BuildContext context, - Object castDevice, - ) async { - await castService.connectDevice(context, castDevice); - } } From 864f5c1fd4c33abce43666dc25e766c2ee931054 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:25:04 +0530 Subject: [PATCH 013/367] [mob][photos] Extract strings --- mobile/lib/generated/intl/messages_en.dart | 8 +++++ mobile/lib/generated/l10n.dart | 40 ++++++++++++++++++++++ mobile/lib/l10n/intl_en.arb | 6 +++- mobile/lib/ui/cast/auto.dart | 9 ++--- 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index 5c5a9fd4c..23ea9c5e2 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -357,6 +357,10 @@ class MessageLookup extends MessageLookupByLibrary { "Authentication failed, please try again"), "authenticationSuccessful": MessageLookupByLibrary.simpleMessage("Authentication successful!"), + "autoCastDialogBody": MessageLookupByLibrary.simpleMessage( + "You\'ll see available Cast devices here."), + "autoCastiOSPermission": MessageLookupByLibrary.simpleMessage( + "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings."), "autoPair": MessageLookupByLibrary.simpleMessage("Auto pair"), "autoPairGoogle": MessageLookupByLibrary.simpleMessage( "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos."), @@ -463,6 +467,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Confirm recovery key"), "confirmYourRecoveryKey": MessageLookupByLibrary.simpleMessage("Confirm your recovery key"), + "connectToDevice": + MessageLookupByLibrary.simpleMessage("Connect to device"), "contactFamilyAdmin": m12, "contactSupport": MessageLookupByLibrary.simpleMessage("Contact support"), @@ -941,6 +947,8 @@ class MessageLookup extends MessageLookupByLibrary { "no": MessageLookupByLibrary.simpleMessage("No"), "noAlbumsSharedByYouYet": MessageLookupByLibrary.simpleMessage("No albums shared by you yet"), + "noDeviceFound": + MessageLookupByLibrary.simpleMessage("No device found"), "noDeviceLimit": MessageLookupByLibrary.simpleMessage("None"), "noDeviceThatCanBeDeleted": MessageLookupByLibrary.simpleMessage( "You\'ve no files on this device that can be deleted"), diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index 7af032ce1..d55ab1795 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -8603,6 +8603,46 @@ class S { args: [], ); } + + /// `Connect to device` + String get connectToDevice { + return Intl.message( + 'Connect to device', + name: 'connectToDevice', + desc: '', + args: [], + ); + } + + /// `You'll see available Cast devices here.` + String get autoCastDialogBody { + return Intl.message( + 'You\'ll see available Cast devices here.', + name: 'autoCastDialogBody', + desc: '', + args: [], + ); + } + + /// `Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.` + String get autoCastiOSPermission { + return Intl.message( + 'Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.', + name: 'autoCastiOSPermission', + desc: '', + args: [], + ); + } + + /// `No device found` + String get noDeviceFound { + return Intl.message( + 'No device found', + name: 'noDeviceFound', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index 0404e33f0..72afd2a4b 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1216,5 +1216,9 @@ "createCollaborativeLink": "Create collaborative link", "search": "Search", "autoPairGoogle": "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.", - "manualPairDesc": "Pair with PIN works for any large screen device you want to play your album on." + "manualPairDesc": "Pair with PIN works for any large screen device you want to play your album on.", + "connectToDevice": "Connect to device", + "autoCastDialogBody": "You'll see available Cast devices here.", + "autoCastiOSPermission": "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.", + "noDeviceFound": "No device found" } \ No newline at end of file diff --git a/mobile/lib/ui/cast/auto.dart b/mobile/lib/ui/cast/auto.dart index 5d8b64175..64bae7cf3 100644 --- a/mobile/lib/ui/cast/auto.dart +++ b/mobile/lib/ui/cast/auto.dart @@ -1,6 +1,7 @@ import "dart:io"; import "package:flutter/material.dart"; +import "package:photos/generated/l10n.dart"; import "package:photos/service_locator.dart"; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/common/loading_widget.dart"; @@ -24,7 +25,7 @@ class _AutoCastDialogState extends State { final AlertDialog alert = AlertDialog( title: Text( - "Connect to device", + S.of(context).connectToDevice, style: textStyle.largeBold, ), content: Column( @@ -32,12 +33,12 @@ class _AutoCastDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - "You'll see available Cast devices here.", + S.of(context).autoCastDialogBody, style: textStyle.bodyMuted, ), if (Platform.isIOS) Text( - "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.", + S.of(context).autoCastiOSPermission, style: textStyle.bodyMuted, ), const SizedBox(height: 16), @@ -55,7 +56,7 @@ class _AutoCastDialogState extends State { } if (snapshot.data!.isEmpty) { - return const Center(child: Text('No device')); + return const Center(child: Text(S.of(context).noDeviceFound)); } return Column( From 483cfd1f3913109bb5c63e02d5bae8110e6f8149 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:28:30 +0530 Subject: [PATCH 014/367] [mob][photos] Lint suggestions --- mobile/lib/ui/cast/auto.dart | 2 +- mobile/lib/ui/cast/choose.dart | 4 ++-- mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mobile/lib/ui/cast/auto.dart b/mobile/lib/ui/cast/auto.dart index 64bae7cf3..0b088a9d2 100644 --- a/mobile/lib/ui/cast/auto.dart +++ b/mobile/lib/ui/cast/auto.dart @@ -56,7 +56,7 @@ class _AutoCastDialogState extends State { } if (snapshot.data!.isEmpty) { - return const Center(child: Text(S.of(context).noDeviceFound)); + return Center(child: Text(S.of(context).noDeviceFound)); } return Column( diff --git a/mobile/lib/ui/cast/choose.dart b/mobile/lib/ui/cast/choose.dart index f3ab39a62..1cfd275c8 100644 --- a/mobile/lib/ui/cast/choose.dart +++ b/mobile/lib/ui/cast/choose.dart @@ -6,9 +6,9 @@ import "package:photos/ui/components/buttons/button_widget.dart"; import "package:photos/ui/components/models/button_type.dart"; class CastChooseDialog extends StatefulWidget { - CastChooseDialog({ + const CastChooseDialog({ Key? key, - }) : super(key: key) {} + }) : super(key: key); @override State createState() => _CastChooseDialogState(); diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 9e53014ed..d7b1b0190 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -861,7 +861,7 @@ class _GalleryAppBarWidgetState extends State { context: context, barrierDismissible: true, builder: (BuildContext context) { - return CastChooseDialog(); + return const CastChooseDialog(); }, ); _logger.info("Cast result: $result"); From dddbb959b53e1a1da80750507b01cb3fb01afb64 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:45:03 +0530 Subject: [PATCH 015/367] [mob][photos] Refactor --- .../gallery/gallery_app_bar_widget.dart | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index d7b1b0190..d73662e19 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -896,30 +896,37 @@ class _GalleryAppBarWidgetState extends State { alwaysShowSuccessState: false, initialValue: code, onSubmit: (String text) async { - try { - code = text.trim(); - final String? publicKey = await gw.getPublicKey(code); - if (publicKey == null) { - showToast(context, S.of(context).deviceNotFound); - // show _pairPin again - Future.delayed(Duration.zero, () => _pairWithPin(gw, code)); - return; - } - final String castToken = const Uuid().v4().toString(); - final castPayload = CollectionsService.instance - .getCastData(castToken, widget.collection!, publicKey); - await gw.publishCastPayload( - code, - castPayload, - widget.collection!.id, - castToken, - ); - } catch (e, s) { - _logger.severe("Failed to cast album", e, s); - await showGenericErrorDialog(context: context, error: e); + final bool paired = await _castPair(gw, text); + if (!paired) { Future.delayed(Duration.zero, () => _pairWithPin(gw, code)); } }, ); } + + Future _castPair(CastGateway gw, String code) async { + try { + final String? publicKey = await gw.getPublicKey(code); + if (publicKey == null) { + showToast(context, S.of(context).deviceNotFound); + + return false; + } + final String castToken = const Uuid().v4().toString(); + final castPayload = CollectionsService.instance + .getCastData(castToken, widget.collection!, publicKey); + await gw.publishCastPayload( + code, + castPayload, + widget.collection!.id, + castToken, + ); + showToast(context, "Pairing complete"); + return true; + } catch (e, s) { + _logger.severe("Failed to cast album", e, s); + await showGenericErrorDialog(context: context, error: e); + return false; + } + } } From 4ce6fa790fa6c86a5a4892c63fd545c487860bde Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 27 Apr 2024 12:16:48 +0530 Subject: [PATCH 016/367] [mob] Add method to close cast and keep track of active casts --- mobile/plugins/ente_cast/lib/ente_cast.dart | 1 + mobile/plugins/ente_cast/lib/src/model.dart | 5 +++ mobile/plugins/ente_cast/lib/src/service.dart | 13 +++++- .../ente_cast_none/lib/src/service.dart | 19 +++++++- .../ente_cast_normal/lib/src/service.dart | 43 +++++++++++++++++-- 5 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 mobile/plugins/ente_cast/lib/src/model.dart diff --git a/mobile/plugins/ente_cast/lib/ente_cast.dart b/mobile/plugins/ente_cast/lib/ente_cast.dart index 66a7132d8..f421a9297 100644 --- a/mobile/plugins/ente_cast/lib/ente_cast.dart +++ b/mobile/plugins/ente_cast/lib/ente_cast.dart @@ -1 +1,2 @@ +export 'src/model.dart'; export 'src/service.dart'; diff --git a/mobile/plugins/ente_cast/lib/src/model.dart b/mobile/plugins/ente_cast/lib/src/model.dart new file mode 100644 index 000000000..e86582f76 --- /dev/null +++ b/mobile/plugins/ente_cast/lib/src/model.dart @@ -0,0 +1,5 @@ +// create enum for type of message for cast +enum CastMessageType { + pairCode, + alreadyCasting, +} diff --git a/mobile/plugins/ente_cast/lib/src/service.dart b/mobile/plugins/ente_cast/lib/src/service.dart index 230269bb3..82d8c5978 100644 --- a/mobile/plugins/ente_cast/lib/src/service.dart +++ b/mobile/plugins/ente_cast/lib/src/service.dart @@ -1,7 +1,18 @@ +import "package:ente_cast/src/model.dart"; import "package:flutter/widgets.dart"; abstract class CastService { bool get isSupported; Future> searchDevices(); - Future connectDevice(BuildContext context, Object device); + Future connectDevice( + BuildContext context, + Object device, { + int? collectionID, + // callback that take a map of string, dynamic + void Function(Map>)? onMessage, + }); + // returns a map of sessionID to deviceNames + Future> getActiveSessions(); + + Future closeActiveCasts(); } diff --git a/mobile/plugins/ente_cast_none/lib/src/service.dart b/mobile/plugins/ente_cast_none/lib/src/service.dart index 166108c52..007a4daaa 100644 --- a/mobile/plugins/ente_cast_none/lib/src/service.dart +++ b/mobile/plugins/ente_cast_none/lib/src/service.dart @@ -3,7 +3,12 @@ import "package:flutter/widgets.dart"; class CastServiceImpl extends CastService { @override - Future connectDevice(BuildContext context, Object device) { + Future connectDevice( + BuildContext context, + Object device, { + int? collectionID, + void Function(Map>)? onMessage, + }) { throw UnimplementedError(); } @@ -15,4 +20,16 @@ class CastServiceImpl extends CastService { // TODO: implement searchDevices throw UnimplementedError(); } + + @override + Future closeActiveCasts() { + // TODO: implement closeActiveCasts + throw UnimplementedError(); + } + + @override + Future> getActiveSessions() { + // TODO: implement getActiveSessions + throw UnimplementedError(); + } } diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/plugins/ente_cast_normal/lib/src/service.dart index 4c4624d8c..314194b94 100644 --- a/mobile/plugins/ente_cast_normal/lib/src/service.dart +++ b/mobile/plugins/ente_cast_normal/lib/src/service.dart @@ -7,10 +7,15 @@ import "package:flutter/material.dart"; class CastServiceImpl extends CastService { final String _appId = 'F5BCEC64'; final String _pairRequestNamespace = 'urn:x-cast:pair-request'; - final Map sessionIDToDeviceID = {}; + final Map collectionIDToSessions = {}; @override - Future connectDevice(BuildContext context, Object device) async { + Future connectDevice( + BuildContext context, + Object device, { + int? collectionID, + void Function(Map>)? onMessage, + }) async { final CastDevice castDevice = device as CastDevice; final session = await CastSessionManager().startSession(castDevice); session.messageStream.listen((message) { @@ -21,6 +26,13 @@ class CastServiceImpl extends CastService { ); session.sendMessage(_pairRequestNamespace, {}); } else { + if (onMessage != null && message!.containsKey("code")) { + onMessage( + { + CastMessageType.pairCode: message, + }, + ); + } print('receive message: $message'); } }); @@ -29,12 +41,10 @@ class CastServiceImpl extends CastService { if (state == CastSessionState.connected) { const snackBar = SnackBar(content: Text('Connected')); ScaffoldMessenger.of(context).showSnackBar(snackBar); - sessionIDToDeviceID[session.sessionId] = castDevice; debugPrint("Send request to pair"); session.sendMessage(_pairRequestNamespace, {}); } else if (state == CastSessionState.closed) { dev.log('Session closed', name: 'CastServiceImpl'); - sessionIDToDeviceID.remove(session.sessionId); } }); @@ -55,4 +65,29 @@ class CastServiceImpl extends CastService { @override bool get isSupported => true; + + @override + Future closeActiveCasts() { + final sessions = CastSessionManager().sessions; + for (final session in sessions) { + session.sendMessage( + _pairRequestNamespace, + { + "type": "CLOSE", + }, + ); + session.close(); + } + return Future.value(); + } + + @override + Future> getActiveSessions() { + final sessions = CastSessionManager().sessions; + final Map result = {}; + for (final session in sessions) { + result[session.sessionId] = session.state.toString(); + } + return Future.value(result); + } } From 1418741229c6971647e3e40dd434af40339ce773 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Fri, 26 Apr 2024 11:29:36 +0530 Subject: [PATCH 017/367] fix: treat steam host same as totp with 5 digits --- auth/lib/models/code.dart | 12 +++++++++--- auth/lib/utils/totp_util.dart | 10 +++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/auth/lib/models/code.dart b/auth/lib/models/code.dart index 7853eb19d..ab50c2ddd 100644 --- a/auth/lib/models/code.dart +++ b/auth/lib/models/code.dart @@ -2,6 +2,7 @@ import 'package:ente_auth/utils/totp_util.dart'; class Code { static const defaultDigits = 6; + static const steamDigits = 5; static const defaultPeriod = 30; int? generatedID; @@ -67,10 +68,12 @@ class Code { String issuer, String secret, ) { + final digits = + issuer.toLowerCase() == "steam" ? steamDigits : defaultDigits; return Code( account, issuer, - defaultDigits, + digits, defaultPeriod, secret, Algorithm.sha1, @@ -82,10 +85,13 @@ class Code { static Code fromRawData(String rawData) { Uri uri = Uri.parse(rawData); + final issuer = _getIssuer(uri); + final digits = issuer.toLowerCase() == "stream" ? 5 : _getDigits(uri); + try { return Code( _getAccount(uri), - _getIssuer(uri), + issuer, _getDigits(uri), _getPeriod(uri), getSanitizedSecret(uri.queryParameters['secret']!), @@ -184,7 +190,7 @@ class Code { } static Type _getType(Uri uri) { - if (uri.host == "totp") { + if (uri.host == "totp" || uri.host == "steam") { return Type.totp; } else if (uri.host == "hotp") { return Type.hotp; diff --git a/auth/lib/utils/totp_util.dart b/auth/lib/utils/totp_util.dart index a49448524..eded63483 100644 --- a/auth/lib/utils/totp_util.dart +++ b/auth/lib/utils/totp_util.dart @@ -3,13 +3,13 @@ import 'package:flutter/foundation.dart'; import 'package:otp/otp.dart' as otp; String getOTP(Code code) { - if(code.type == Type.hotp) { + if (code.type == Type.hotp) { return _getHOTPCode(code); } return otp.OTP.generateTOTPCodeString( getSanitizedSecret(code.secret), DateTime.now().millisecondsSinceEpoch, - length: code.digits, + length: code.issuer.toLowerCase() == "steam" ? 5 : code.digits, interval: code.period, algorithm: _getAlgorithm(code), isGoogle: true, @@ -20,7 +20,7 @@ String _getHOTPCode(Code code) { return otp.OTP.generateHOTPCodeString( getSanitizedSecret(code.secret), code.counter, - length: code.digits, + length: code.issuer.toLowerCase() == "steam" ? 5 : code.digits, algorithm: _getAlgorithm(code), isGoogle: true, ); @@ -30,7 +30,7 @@ String getNextTotp(Code code) { return otp.OTP.generateTOTPCodeString( getSanitizedSecret(code.secret), DateTime.now().millisecondsSinceEpoch + code.period * 1000, - length: code.digits, + length: code.issuer.toLowerCase() == "stream" ? 5 : code.digits, interval: code.period, algorithm: _getAlgorithm(code), isGoogle: true, @@ -60,4 +60,4 @@ String safeDecode(String value) { debugPrint("Failed to decode $e"); return value; } -} \ No newline at end of file +} From 6cd624c90cc6055dc630ed97dfddf1bf8fe1b09d Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Fri, 26 Apr 2024 11:29:48 +0530 Subject: [PATCH 018/367] fix: don't run tray window code on mobile --- auth/lib/main.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/auth/lib/main.dart b/auth/lib/main.dart index 09b85d8b3..d8d22ca4f 100644 --- a/auth/lib/main.dart +++ b/auth/lib/main.dart @@ -37,6 +37,7 @@ import 'package:window_manager/window_manager.dart'; final _logger = Logger("main"); Future initSystemTray() async { + if (PlatformUtil.isMobile()) return; String path = Platform.isWindows ? 'assets/icons/auth-icon.ico' : 'assets/icons/auth-icon.png'; From e84b9da35e738f10f0cbe51af8de0c985af4f291 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Fri, 26 Apr 2024 11:33:30 +0530 Subject: [PATCH 019/367] fix: typo and lint --- auth/lib/models/code.dart | 4 ++-- auth/lib/utils/totp_util.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/auth/lib/models/code.dart b/auth/lib/models/code.dart index ab50c2ddd..6b7facf53 100644 --- a/auth/lib/models/code.dart +++ b/auth/lib/models/code.dart @@ -86,13 +86,13 @@ class Code { static Code fromRawData(String rawData) { Uri uri = Uri.parse(rawData); final issuer = _getIssuer(uri); - final digits = issuer.toLowerCase() == "stream" ? 5 : _getDigits(uri); + final digits = issuer.toLowerCase() == "steam" ? 5 : _getDigits(uri); try { return Code( _getAccount(uri), issuer, - _getDigits(uri), + digits, _getPeriod(uri), getSanitizedSecret(uri.queryParameters['secret']!), _getAlgorithm(uri), diff --git a/auth/lib/utils/totp_util.dart b/auth/lib/utils/totp_util.dart index eded63483..c01f4843e 100644 --- a/auth/lib/utils/totp_util.dart +++ b/auth/lib/utils/totp_util.dart @@ -30,7 +30,7 @@ String getNextTotp(Code code) { return otp.OTP.generateTOTPCodeString( getSanitizedSecret(code.secret), DateTime.now().millisecondsSinceEpoch + code.period * 1000, - length: code.issuer.toLowerCase() == "stream" ? 5 : code.digits, + length: code.issuer.toLowerCase() == "steam" ? 5 : code.digits, interval: code.period, algorithm: _getAlgorithm(code), isGoogle: true, From e4ea377ee03341429bd5316430d638e41c299961 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Sat, 27 Apr 2024 15:44:58 +0530 Subject: [PATCH 020/367] fix: use steam digits instead of hardcoding it everywhere --- auth/lib/utils/totp_util.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/auth/lib/utils/totp_util.dart b/auth/lib/utils/totp_util.dart index c01f4843e..3de7f5047 100644 --- a/auth/lib/utils/totp_util.dart +++ b/auth/lib/utils/totp_util.dart @@ -9,7 +9,8 @@ String getOTP(Code code) { return otp.OTP.generateTOTPCodeString( getSanitizedSecret(code.secret), DateTime.now().millisecondsSinceEpoch, - length: code.issuer.toLowerCase() == "steam" ? 5 : code.digits, + length: + code.issuer.toLowerCase() == "steam" ? Code.steamDigits : code.digits, interval: code.period, algorithm: _getAlgorithm(code), isGoogle: true, @@ -20,7 +21,8 @@ String _getHOTPCode(Code code) { return otp.OTP.generateHOTPCodeString( getSanitizedSecret(code.secret), code.counter, - length: code.issuer.toLowerCase() == "steam" ? 5 : code.digits, + length: + code.issuer.toLowerCase() == "steam" ? Code.steamDigits : code.digits, algorithm: _getAlgorithm(code), isGoogle: true, ); @@ -30,7 +32,8 @@ String getNextTotp(Code code) { return otp.OTP.generateTOTPCodeString( getSanitizedSecret(code.secret), DateTime.now().millisecondsSinceEpoch + code.period * 1000, - length: code.issuer.toLowerCase() == "steam" ? 5 : code.digits, + length: + code.issuer.toLowerCase() == "steam" ? Code.steamDigits : code.digits, interval: code.period, algorithm: _getAlgorithm(code), isGoogle: true, From 8a4229122e4bc935403f1db11450d1dcd9b4487f Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Sat, 27 Apr 2024 15:45:50 +0530 Subject: [PATCH 021/367] fix: use steam digits instead of hardcoding it --- auth/lib/models/code.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auth/lib/models/code.dart b/auth/lib/models/code.dart index 6b7facf53..09591a62f 100644 --- a/auth/lib/models/code.dart +++ b/auth/lib/models/code.dart @@ -86,7 +86,8 @@ class Code { static Code fromRawData(String rawData) { Uri uri = Uri.parse(rawData); final issuer = _getIssuer(uri); - final digits = issuer.toLowerCase() == "steam" ? 5 : _getDigits(uri); + final digits = + issuer.toLowerCase() == "steam" ? steamDigits : _getDigits(uri); try { return Code( From 4b97f832b2a1246823863e3aad0c1e25242ac4c0 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:23:19 +0530 Subject: [PATCH 022/367] [mob][photos] Finish auto-pair integration --- mobile/lib/ui/cast/auto.dart | 19 +++++++++++++++++-- .../gallery/gallery_app_bar_widget.dart | 6 +++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/mobile/lib/ui/cast/auto.dart b/mobile/lib/ui/cast/auto.dart index 0b088a9d2..8afacf7db 100644 --- a/mobile/lib/ui/cast/auto.dart +++ b/mobile/lib/ui/cast/auto.dart @@ -1,5 +1,6 @@ import "dart:io"; +import "package:ente_cast/ente_cast.dart"; import "package:flutter/material.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/service_locator.dart"; @@ -8,7 +9,11 @@ import "package:photos/ui/common/loading_widget.dart"; import "package:photos/utils/dialog_util.dart"; class AutoCastDialog extends StatefulWidget { - AutoCastDialog({ + // async method that takes string as input + // and returns void + final void Function(String) onConnect; + AutoCastDialog( + this.onConnect, { Key? key, }) : super(key: key) {} @@ -88,6 +93,16 @@ class _AutoCastDialogState extends State { BuildContext context, Object castDevice, ) async { - await castService.connectDevice(context, castDevice); + await castService.connectDevice( + context, + castDevice, + onMessage: (message) { + if (message.containsKey(CastMessageType.pairCode)) { + final code = message[CastMessageType.pairCode]!['code']; + widget.onConnect(code); + // Navigator.of(context).pop(); + } + }, + ); } } diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index d73662e19..4365b7813 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -875,7 +875,11 @@ class _GalleryAppBarWidgetState extends State { context: context, barrierDismissible: true, builder: (BuildContext context) { - return AutoCastDialog(); + return AutoCastDialog( + (device) async { + await _castPair(gw, device); + }, + ); }, ); } From 7411125194659af92704f5dcf4892ae24125d5e7 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:20:17 +0530 Subject: [PATCH 023/367] [mob][photos] Add support for closing session --- mobile/lib/ui/cast/auto.dart | 2 +- .../gallery/gallery_app_bar_widget.dart | 22 ++++++++++++++++++- mobile/plugins/ente_cast/lib/src/service.dart | 2 +- .../ente_cast_none/lib/src/service.dart | 2 +- .../ente_cast_normal/lib/src/service.dart | 21 ++++++++++-------- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/mobile/lib/ui/cast/auto.dart b/mobile/lib/ui/cast/auto.dart index 8afacf7db..4dc9d5bd0 100644 --- a/mobile/lib/ui/cast/auto.dart +++ b/mobile/lib/ui/cast/auto.dart @@ -100,7 +100,7 @@ class _AutoCastDialogState extends State { if (message.containsKey(CastMessageType.pairCode)) { final code = message[CastMessageType.pairCode]!['code']; widget.onConnect(code); - // Navigator.of(context).pop(); + Navigator.of(context).pop(); } }, ); diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 4365b7813..489d5478e 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -583,9 +583,14 @@ class _GalleryAppBarWidgetState extends State { Tooltip( message: "Cast album", child: IconButton( - icon: const Icon(Icons.cast_outlined), + icon: castService.getActiveSessions().isNotEmpty + ? const Icon(Icons.cast_connected_rounded) + : const Icon(Icons.cast_outlined), onPressed: () async { await _castChoiceDialog(); + if (mounted) { + setState(() {}); + } }, ), ), @@ -855,6 +860,21 @@ class _GalleryAppBarWidgetState extends State { Future _castChoiceDialog() async { final gw = CastGateway(NetworkClient.instance.enteDio); + if (castService.getActiveSessions().isNotEmpty) { + await showChoiceDialog( + context, + title: "Stop casting", + firstButtonLabel: "Yes", + secondButtonLabel: "No", + body: "Do you want to stop casting?", + firstButtonOnTap: () async { + gw.revokeAllTokens().ignore(); + await castService.closeActiveCasts(); + }, + ); + return; + } + // stop any existing cast session gw.revokeAllTokens().ignore(); final result = await showDialog( diff --git a/mobile/plugins/ente_cast/lib/src/service.dart b/mobile/plugins/ente_cast/lib/src/service.dart index 82d8c5978..2ab0961db 100644 --- a/mobile/plugins/ente_cast/lib/src/service.dart +++ b/mobile/plugins/ente_cast/lib/src/service.dart @@ -12,7 +12,7 @@ abstract class CastService { void Function(Map>)? onMessage, }); // returns a map of sessionID to deviceNames - Future> getActiveSessions(); + Map getActiveSessions(); Future closeActiveCasts(); } diff --git a/mobile/plugins/ente_cast_none/lib/src/service.dart b/mobile/plugins/ente_cast_none/lib/src/service.dart index 007a4daaa..c78188973 100644 --- a/mobile/plugins/ente_cast_none/lib/src/service.dart +++ b/mobile/plugins/ente_cast_none/lib/src/service.dart @@ -28,7 +28,7 @@ class CastServiceImpl extends CastService { } @override - Future> getActiveSessions() { + Map getActiveSessions() { // TODO: implement getActiveSessions throw UnimplementedError(); } diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/plugins/ente_cast_normal/lib/src/service.dart index 314194b94..a2c4206c8 100644 --- a/mobile/plugins/ente_cast_normal/lib/src/service.dart +++ b/mobile/plugins/ente_cast_normal/lib/src/service.dart @@ -70,24 +70,27 @@ class CastServiceImpl extends CastService { Future closeActiveCasts() { final sessions = CastSessionManager().sessions; for (final session in sessions) { - session.sendMessage( - _pairRequestNamespace, - { - "type": "CLOSE", - }, - ); + debugPrint("send close message for ${session.sessionId}"); + session.sendMessage(CastSession.kNamespaceConnection, { + 'type': 'CLOSE', + }); + debugPrint("close session ${session.sessionId}"); session.close(); } + CastSessionManager().sessions.clear(); + debugPrint("send close message"); return Future.value(); } @override - Future> getActiveSessions() { + Map getActiveSessions() { final sessions = CastSessionManager().sessions; final Map result = {}; for (final session in sessions) { - result[session.sessionId] = session.state.toString(); + if (session.state == CastSessionState.connected) { + result[session.sessionId] = session.state.toString(); + } } - return Future.value(result); + return result; } } From 195ad01f148d7109dbe68647cd05d87e63d3776a Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:26:13 +0530 Subject: [PATCH 024/367] [mob][photos] Add timeout for stop casting --- .../plugins/ente_cast_normal/lib/src/service.dart | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/plugins/ente_cast_normal/lib/src/service.dart index a2c4206c8..454af58ad 100644 --- a/mobile/plugins/ente_cast_normal/lib/src/service.dart +++ b/mobile/plugins/ente_cast_normal/lib/src/service.dart @@ -71,9 +71,16 @@ class CastServiceImpl extends CastService { final sessions = CastSessionManager().sessions; for (final session in sessions) { debugPrint("send close message for ${session.sessionId}"); - session.sendMessage(CastSession.kNamespaceConnection, { - 'type': 'CLOSE', - }); + Future(() { + session.sendMessage(CastSession.kNamespaceConnection, { + 'type': 'CLOSE', + }); + }).timeout( + const Duration(seconds: 5), + onTimeout: () { + print('sendMessage timed out after 5 seconds'); + }, + ); debugPrint("close session ${session.sessionId}"); session.close(); } From b12e6221d431a404029f617bef1f5d7be93258e9 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 29 Apr 2024 13:46:18 +0530 Subject: [PATCH 025/367] [mob][photos] Change button type to neutral --- mobile/lib/ui/cast/choose.dart | 4 ++-- mobile/plugins/ente_cast_normal/lib/src/service.dart | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mobile/lib/ui/cast/choose.dart b/mobile/lib/ui/cast/choose.dart index 1cfd275c8..7f0288733 100644 --- a/mobile/lib/ui/cast/choose.dart +++ b/mobile/lib/ui/cast/choose.dart @@ -38,7 +38,7 @@ class _CastChooseDialogState extends State { ButtonWidget( labelText: S.of(context).autoPair, icon: Icons.cast_outlined, - buttonType: ButtonType.primary, + buttonType: ButtonType.neutral, buttonSize: ButtonSize.large, shouldStickToDarkTheme: true, buttonAction: ButtonAction.first, @@ -56,7 +56,7 @@ class _CastChooseDialogState extends State { const SizedBox(height: 12), ButtonWidget( labelText: S.of(context).pairWithPin, - buttonType: ButtonType.primary, + buttonType: ButtonType.neutral, // icon for pairing with TV manually icon: Icons.tv_outlined, buttonSize: ButtonSize.large, diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/plugins/ente_cast_normal/lib/src/service.dart index 454af58ad..34787734b 100644 --- a/mobile/plugins/ente_cast_normal/lib/src/service.dart +++ b/mobile/plugins/ente_cast_normal/lib/src/service.dart @@ -78,14 +78,13 @@ class CastServiceImpl extends CastService { }).timeout( const Duration(seconds: 5), onTimeout: () { - print('sendMessage timed out after 5 seconds'); + debugPrint('sendMessage timed out after 5 seconds'); }, ); debugPrint("close session ${session.sessionId}"); session.close(); } CastSessionManager().sessions.clear(); - debugPrint("send close message"); return Future.value(); } From bd07759d8ee2341551a6a596f624172fed276576 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 29 Apr 2024 14:05:41 +0530 Subject: [PATCH 026/367] [mob][photos] Show loading indicator on device tap --- mobile/lib/ui/cast/auto.dart | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/mobile/lib/ui/cast/auto.dart b/mobile/lib/ui/cast/auto.dart index 4dc9d5bd0..ee5974f00 100644 --- a/mobile/lib/ui/cast/auto.dart +++ b/mobile/lib/ui/cast/auto.dart @@ -23,11 +23,11 @@ class AutoCastDialog extends StatefulWidget { class _AutoCastDialogState extends State { final bool doesUserExist = true; + final Set _isDeviceTapInProgress = {}; @override Widget build(BuildContext context) { final textStyle = getEnteTextTheme(context); - final AlertDialog alert = AlertDialog( title: Text( S.of(context).connectToDevice, @@ -65,19 +65,39 @@ class _AutoCastDialogState extends State { } return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: snapshot.data!.map((result) { final device = result.$2; final name = result.$1; return GestureDetector( onTap: () async { + if (_isDeviceTapInProgress.contains(device)) { + return; + } + setState(() { + _isDeviceTapInProgress.add(device); + }); try { await _connectToYourApp(context, device); } catch (e) { showGenericErrorDialog(context: context, error: e) .ignore(); + } finally { + setState(() { + _isDeviceTapInProgress.remove(device); + }); } }, - child: Text(name), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded(child: Text(name)), + if (_isDeviceTapInProgress.contains(device)) + const EnteLoadingWidget(), + ], + ), + ), ); }).toList(), ); @@ -93,6 +113,8 @@ class _AutoCastDialogState extends State { BuildContext context, Object castDevice, ) async { + // sleep for 10 seconds + await Future.delayed(const Duration(seconds: 10)); await castService.connectDevice( context, castDevice, From 16888c8aadee6a3c078ec300416104f1ca8214c2 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 29 Apr 2024 14:14:23 +0530 Subject: [PATCH 027/367] [mob][photos] Fix lint warning --- mobile/plugins/ente_cast_normal/lib/src/service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/plugins/ente_cast_normal/lib/src/service.dart index 34787734b..1f98fa746 100644 --- a/mobile/plugins/ente_cast_normal/lib/src/service.dart +++ b/mobile/plugins/ente_cast_normal/lib/src/service.dart @@ -26,7 +26,7 @@ class CastServiceImpl extends CastService { ); session.sendMessage(_pairRequestNamespace, {}); } else { - if (onMessage != null && message!.containsKey("code")) { + if (onMessage != null && message.containsKey("code")) { onMessage( { CastMessageType.pairCode: message, From bb6cd41ec8810c7cc275e0f925b0aa45ebd1b784 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Mon, 29 Apr 2024 16:44:07 +0530 Subject: [PATCH 028/367] Implement SQLite version of EmbeddingsDB --- mobile/lib/db/embeddings_sqlite_db.dart | 129 ++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 mobile/lib/db/embeddings_sqlite_db.dart diff --git a/mobile/lib/db/embeddings_sqlite_db.dart b/mobile/lib/db/embeddings_sqlite_db.dart new file mode 100644 index 000000000..cdbe64fb7 --- /dev/null +++ b/mobile/lib/db/embeddings_sqlite_db.dart @@ -0,0 +1,129 @@ +import "dart:io"; +import "dart:typed_data"; + +import "package:path/path.dart"; +import 'package:path_provider/path_provider.dart'; +import "package:photos/core/event_bus.dart"; +import "package:photos/events/embedding_updated_event.dart"; +import "package:photos/models/embedding.dart"; +import "package:sqlite_async/sqlite_async.dart"; + +class EmbeddingsDB { + EmbeddingsDB._privateConstructor(); + + static final EmbeddingsDB instance = EmbeddingsDB._privateConstructor(); + + static const databaseName = "ente.embeddings.db"; + static const tableName = "embeddings"; + static const columnFileID = "file_id"; + static const columnModel = "model"; + static const columnEmbedding = "embedding"; + static const columnUpdationTime = "updation_time"; + + static Future? _dbFuture; + + Future get _database async { + _dbFuture ??= _initDatabase(); + return _dbFuture!; + } + + Future _initDatabase() async { + final Directory documentsDirectory = + await getApplicationDocumentsDirectory(); + final String path = join(documentsDirectory.path, databaseName); + final migrations = SqliteMigrations() + ..add( + SqliteMigration( + 1, + (tx) async { + await tx.execute( + 'CREATE TABLE $tableName IF NOT EXISTS ($columnFileID INTEGER NOT NULL, $columnModel TEXT NOT NULL, $columnEmbedding BLOB NOT NULL, $columnUpdationTime INTEGER, UNIQUE ($columnFileID, $columnModel))', + ); + }, + ), + ); + final database = SqliteDatabase(path: path); + await migrations.migrate(database); + return database; + } + + Future clearTable() async { + final db = await _database; + await db.execute('DELETE * FROM $tableName'); + } + + Future> getAll(Model model) async { + final db = await _database; + final results = await db.getAll('SELECT * FROM $tableName'); + return _convertToEmbeddings(results); + } + + Future put(Embedding embedding) async { + final db = await _database; + await db.execute( + 'INSERT OR REPLACE INTO $tableName ($columnFileID, $columnModel, $columnEmbedding) VALUES (?, ?, ?, ?)', + _getRowFromEmbedding(embedding), + ); + Bus.instance.fire(EmbeddingUpdatedEvent()); + } + + Future putMany(List embeddings) async { + final db = await _database; + final inputs = embeddings.map((e) => _getRowFromEmbedding(e)).toList(); + await db.executeBatch( + 'INSERT OR REPLACE INTO $tableName ($columnFileID, $columnModel, $columnEmbedding) values(?, ?, ?, ?)', + inputs, + ); + Bus.instance.fire(EmbeddingUpdatedEvent()); + } + + Future> getUnsyncedEmbeddings() async { + final db = await _database; + final results = await db.getAll( + 'SELECT * FROM $tableName WHERE $columnUpdationTime IS NULL', + ); + return _convertToEmbeddings(results); + } + + Future deleteEmbeddings(List fileIDs) async { + final db = await _database; + await db.execute( + 'DELETE FROM $tableName WHERE $columnFileID IN (${fileIDs.join(", ")})', + ); + Bus.instance.fire(EmbeddingUpdatedEvent()); + } + + Future deleteAllForModel(Model model) async { + final db = await _database; + await db.execute( + 'DELETE FROM $tableName WHERE $columnModel = ?', + [serialize(model)], + ); + Bus.instance.fire(EmbeddingUpdatedEvent()); + } + + List _convertToEmbeddings(List> results) { + final List embeddings = []; + for (final result in results) { + embeddings.add(_getEmbeddingFromRow(result)); + } + return embeddings; + } + + Embedding _getEmbeddingFromRow(Map row) { + final fileID = row[columnFileID]; + final model = deserialize(row[columnModel]); + final bytes = row[columnEmbedding] as Uint8List; + final list = Float32List.view(bytes.buffer); + return Embedding(fileID: fileID, model: model, embedding: list); + } + + List _getRowFromEmbedding(Embedding embedding) { + return [ + embedding.fileID, + serialize(embedding.model), + Float32List.fromList(embedding.embedding).buffer.asUint8List(), + embedding.updationTime, + ]; + } +} From 7c9160478dee0cf5461bd052f9a96bd233c176fa Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:45:43 +0530 Subject: [PATCH 029/367] [mob][photos] Extract strings --- mobile/lib/generated/intl/messages_en.dart | 4 ++++ mobile/lib/generated/l10n.dart | 20 +++++++++++++++++++ mobile/lib/l10n/intl_en.arb | 4 +++- mobile/lib/ui/cast/auto.dart | 2 -- .../gallery/gallery_app_bar_widget.dart | 9 ++++----- .../ente_cast_normal/lib/src/service.dart | 2 -- 6 files changed, 31 insertions(+), 10 deletions(-) diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index 23ea9c5e2..265e4819b 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -1342,6 +1342,10 @@ class MessageLookup extends MessageLookupByLibrary { "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Success"), "startBackup": MessageLookupByLibrary.simpleMessage("Start backup"), "status": MessageLookupByLibrary.simpleMessage("Status"), + "stopCastingBody": MessageLookupByLibrary.simpleMessage( + "Do you want to stop casting?"), + "stopCastingTitle": + MessageLookupByLibrary.simpleMessage("Stop casting"), "storage": MessageLookupByLibrary.simpleMessage("Storage"), "storageBreakupFamily": MessageLookupByLibrary.simpleMessage("Family"), "storageBreakupYou": MessageLookupByLibrary.simpleMessage("You"), diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index d55ab1795..a142fb78e 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -8643,6 +8643,26 @@ class S { args: [], ); } + + /// `Stop casting` + String get stopCastingTitle { + return Intl.message( + 'Stop casting', + name: 'stopCastingTitle', + desc: '', + args: [], + ); + } + + /// `Do you want to stop casting?` + String get stopCastingBody { + return Intl.message( + 'Do you want to stop casting?', + name: 'stopCastingBody', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index 72afd2a4b..f365f50e8 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1220,5 +1220,7 @@ "connectToDevice": "Connect to device", "autoCastDialogBody": "You'll see available Cast devices here.", "autoCastiOSPermission": "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.", - "noDeviceFound": "No device found" + "noDeviceFound": "No device found", + "stopCastingTitle": "Stop casting", + "stopCastingBody": "Do you want to stop casting?" } \ No newline at end of file diff --git a/mobile/lib/ui/cast/auto.dart b/mobile/lib/ui/cast/auto.dart index ee5974f00..aed8ee0a5 100644 --- a/mobile/lib/ui/cast/auto.dart +++ b/mobile/lib/ui/cast/auto.dart @@ -113,8 +113,6 @@ class _AutoCastDialogState extends State { BuildContext context, Object castDevice, ) async { - // sleep for 10 seconds - await Future.delayed(const Duration(seconds: 10)); await castService.connectDevice( context, castDevice, diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 489d5478e..0fc20ab1e 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -863,10 +863,10 @@ class _GalleryAppBarWidgetState extends State { if (castService.getActiveSessions().isNotEmpty) { await showChoiceDialog( context, - title: "Stop casting", - firstButtonLabel: "Yes", - secondButtonLabel: "No", - body: "Do you want to stop casting?", + title: S.of(context).stopCastingTitle, + firstButtonLabel: S.of(context).yes, + secondButtonLabel: S.of(context).no, + body: S.of(context).stopCastingBody, firstButtonOnTap: () async { gw.revokeAllTokens().ignore(); await castService.closeActiveCasts(); @@ -884,7 +884,6 @@ class _GalleryAppBarWidgetState extends State { return const CastChooseDialog(); }, ); - _logger.info("Cast result: $result"); if (result == null) { return; } diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/plugins/ente_cast_normal/lib/src/service.dart index 1f98fa746..04c501666 100644 --- a/mobile/plugins/ente_cast_normal/lib/src/service.dart +++ b/mobile/plugins/ente_cast_normal/lib/src/service.dart @@ -39,8 +39,6 @@ class CastServiceImpl extends CastService { session.stateStream.listen((state) { if (state == CastSessionState.connected) { - const snackBar = SnackBar(content: Text('Connected')); - ScaffoldMessenger.of(context).showSnackBar(snackBar); debugPrint("Send request to pair"); session.sendMessage(_pairRequestNamespace, {}); } else if (state == CastSessionState.closed) { From 5c645d50f052e979aafd46b4b8ebfbb77a6fea43 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:56:53 +0530 Subject: [PATCH 030/367] [mob][photos] Show custom error on ip mismatch --- mobile/lib/gateways/cast_gw.dart | 16 +++++++++++---- mobile/lib/generated/intl/messages_en.dart | 4 ++++ mobile/lib/generated/l10n.dart | 20 +++++++++++++++++++ mobile/lib/l10n/intl_en.arb | 4 +++- .../gallery/gallery_app_bar_widget.dart | 10 +++++++++- 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/mobile/lib/gateways/cast_gw.dart b/mobile/lib/gateways/cast_gw.dart index fb342c1a9..63735d678 100644 --- a/mobile/lib/gateways/cast_gw.dart +++ b/mobile/lib/gateways/cast_gw.dart @@ -12,10 +12,14 @@ class CastGateway { ); return response.data["publicKey"]; } catch (e) { - if (e is DioError && - e.response != null && - e.response!.statusCode == 404) { - return null; + if (e is DioError && e.response != null) { + if (e.response!.statusCode == 404) { + return null; + } else if (e.response!.statusCode == 403) { + throw CastIPMismatchException(); + } else { + rethrow; + } } rethrow; } @@ -48,3 +52,7 @@ class CastGateway { } } } + +class CastIPMismatchException implements Exception { + CastIPMismatchException(); +} diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index 265e4819b..6c91e4c7c 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -394,6 +394,10 @@ class MessageLookup extends MessageLookupByLibrary { "cannotAddMorePhotosAfterBecomingViewer": m9, "cannotDeleteSharedFiles": MessageLookupByLibrary.simpleMessage("Cannot delete shared files"), + "castIPMismatchBody": MessageLookupByLibrary.simpleMessage( + "Please make sure you are on the same network as the TV."), + "castIPMismatchTitle": + MessageLookupByLibrary.simpleMessage("Failed to cast album"), "castInstruction": MessageLookupByLibrary.simpleMessage( "Visit cast.ente.io on the device you want to pair.\n\nEnter the code below to play the album on your TV."), "centerPoint": MessageLookupByLibrary.simpleMessage("Center point"), diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index a142fb78e..0acdf14df 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -8663,6 +8663,26 @@ class S { args: [], ); } + + /// `Failed to cast album` + String get castIPMismatchTitle { + return Intl.message( + 'Failed to cast album', + name: 'castIPMismatchTitle', + desc: '', + args: [], + ); + } + + /// `Please make sure you are on the same network as the TV.` + String get castIPMismatchBody { + return Intl.message( + 'Please make sure you are on the same network as the TV.', + name: 'castIPMismatchBody', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index f365f50e8..dbfa1448c 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1222,5 +1222,7 @@ "autoCastiOSPermission": "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.", "noDeviceFound": "No device found", "stopCastingTitle": "Stop casting", - "stopCastingBody": "Do you want to stop casting?" + "stopCastingBody": "Do you want to stop casting?", + "castIPMismatchTitle": "Failed to cast album", + "castIPMismatchBody": "Please make sure you are on the same network as the TV." } \ No newline at end of file diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 0fc20ab1e..c363ebd2e 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -948,7 +948,15 @@ class _GalleryAppBarWidgetState extends State { return true; } catch (e, s) { _logger.severe("Failed to cast album", e, s); - await showGenericErrorDialog(context: context, error: e); + if (e is CastIPMismatchException) { + await showErrorDialog( + context, + S.of(context).castIPMismatchTitle, + S.of(context).castIPMismatchBody, + ); + } else { + await showGenericErrorDialog(context: context, error: e); + } return false; } } From 9e7c82d5b9c57f95638aa76912e42a8c56d3a350 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:59:28 +0530 Subject: [PATCH 031/367] [mob][photos] Extract string --- mobile/lib/generated/intl/messages_en.dart | 2 ++ mobile/lib/generated/l10n.dart | 10 ++++++++++ mobile/lib/l10n/intl_en.arb | 3 ++- .../lib/ui/viewer/gallery/gallery_app_bar_widget.dart | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index 6c91e4c7c..efa91ffb7 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -1000,6 +1000,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Or pick an existing one"), "pair": MessageLookupByLibrary.simpleMessage("Pair"), "pairWithPin": MessageLookupByLibrary.simpleMessage("Pair with PIN"), + "pairingComplete": + MessageLookupByLibrary.simpleMessage("Pairing complete"), "passkey": MessageLookupByLibrary.simpleMessage("Passkey"), "passkeyAuthTitle": MessageLookupByLibrary.simpleMessage("Passkey verification"), diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index 0acdf14df..fcf926511 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -8683,6 +8683,16 @@ class S { args: [], ); } + + /// `Pairing complete` + String get pairingComplete { + return Intl.message( + 'Pairing complete', + name: 'pairingComplete', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index dbfa1448c..7139a7098 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1224,5 +1224,6 @@ "stopCastingTitle": "Stop casting", "stopCastingBody": "Do you want to stop casting?", "castIPMismatchTitle": "Failed to cast album", - "castIPMismatchBody": "Please make sure you are on the same network as the TV." + "castIPMismatchBody": "Please make sure you are on the same network as the TV.", + "pairingComplete": "Pairing complete" } \ No newline at end of file diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index c363ebd2e..fc925b4c7 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -944,7 +944,7 @@ class _GalleryAppBarWidgetState extends State { widget.collection!.id, castToken, ); - showToast(context, "Pairing complete"); + showToast(context, S.of(context).pairingComplete); return true; } catch (e, s) { _logger.severe("Failed to cast album", e, s); From 104a7a5f003dc2b519d0121102f3fd9e37edaf59 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Mon, 29 Apr 2024 17:10:53 +0530 Subject: [PATCH 032/367] Fix queries --- mobile/lib/db/embeddings_sqlite_db.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mobile/lib/db/embeddings_sqlite_db.dart b/mobile/lib/db/embeddings_sqlite_db.dart index cdbe64fb7..1a101d4eb 100644 --- a/mobile/lib/db/embeddings_sqlite_db.dart +++ b/mobile/lib/db/embeddings_sqlite_db.dart @@ -37,7 +37,7 @@ class EmbeddingsDB { 1, (tx) async { await tx.execute( - 'CREATE TABLE $tableName IF NOT EXISTS ($columnFileID INTEGER NOT NULL, $columnModel TEXT NOT NULL, $columnEmbedding BLOB NOT NULL, $columnUpdationTime INTEGER, UNIQUE ($columnFileID, $columnModel))', + 'CREATE TABLE $tableName ($columnFileID INTEGER NOT NULL, $columnModel TEXT NOT NULL, $columnEmbedding BLOB NOT NULL, $columnUpdationTime INTEGER, UNIQUE ($columnFileID, $columnModel))', ); }, ), @@ -61,7 +61,7 @@ class EmbeddingsDB { Future put(Embedding embedding) async { final db = await _database; await db.execute( - 'INSERT OR REPLACE INTO $tableName ($columnFileID, $columnModel, $columnEmbedding) VALUES (?, ?, ?, ?)', + 'INSERT OR REPLACE INTO $tableName ($columnFileID, $columnModel, $columnEmbedding, $columnUpdationTime) VALUES (?, ?, ?, ?)', _getRowFromEmbedding(embedding), ); Bus.instance.fire(EmbeddingUpdatedEvent()); @@ -71,7 +71,7 @@ class EmbeddingsDB { final db = await _database; final inputs = embeddings.map((e) => _getRowFromEmbedding(e)).toList(); await db.executeBatch( - 'INSERT OR REPLACE INTO $tableName ($columnFileID, $columnModel, $columnEmbedding) values(?, ?, ?, ?)', + 'INSERT OR REPLACE INTO $tableName ($columnFileID, $columnModel, $columnEmbedding, $columnUpdationTime) values(?, ?, ?, ?)', inputs, ); Bus.instance.fire(EmbeddingUpdatedEvent()); From 64f2be09e65ca795e56cb3f009f0975fb0c071dd Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Mon, 29 Apr 2024 17:30:56 +0530 Subject: [PATCH 033/367] Replace Isar with SQLite --- mobile/lib/core/configuration.dart | 2 +- .../machine_learning/semantic_search/embedding_store.dart | 4 ++-- .../semantic_search/semantic_search_service.dart | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mobile/lib/core/configuration.dart b/mobile/lib/core/configuration.dart index cde766b1e..8b7ecbad5 100644 --- a/mobile/lib/core/configuration.dart +++ b/mobile/lib/core/configuration.dart @@ -11,7 +11,7 @@ import 'package:photos/core/constants.dart'; import 'package:photos/core/error-reporting/super_logging.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/db/collections_db.dart'; -import "package:photos/db/embeddings_db.dart"; +import "package:photos/db/embeddings_sqlite_db.dart"; import 'package:photos/db/files_db.dart'; import 'package:photos/db/memories_db.dart'; import 'package:photos/db/trash_db.dart'; diff --git a/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart b/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart index f7d17f8b8..b8cd539e4 100644 --- a/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart +++ b/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart @@ -5,7 +5,7 @@ import "dart:typed_data"; import "package:computer/computer.dart"; import "package:logging/logging.dart"; import "package:photos/core/network/network.dart"; -import "package:photos/db/embeddings_db.dart"; +import "package:photos/db/embeddings_sqlite_db.dart"; import "package:photos/db/files_db.dart"; import "package:photos/models/embedding.dart"; import "package:photos/models/file/file.dart"; @@ -19,7 +19,7 @@ class EmbeddingStore { static final EmbeddingStore instance = EmbeddingStore._privateConstructor(); - static const kEmbeddingsSyncTimeKey = "sync_time_embeddings_v2"; + static const kEmbeddingsSyncTimeKey = "sync_time_embeddings_v3-test"; final _logger = Logger("EmbeddingStore"); final _dio = NetworkClient.instance.enteDio; diff --git a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart index d1074053a..0587ff522 100644 --- a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart +++ b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart @@ -7,7 +7,7 @@ import "package:logging/logging.dart"; import "package:photos/core/cache/lru_map.dart"; import "package:photos/core/configuration.dart"; import "package:photos/core/event_bus.dart"; -import "package:photos/db/embeddings_db.dart"; +import "package:photos/db/embeddings_sqlite_db.dart"; import "package:photos/db/files_db.dart"; import "package:photos/events/diff_sync_complete_event.dart"; import 'package:photos/events/embedding_updated_event.dart'; @@ -72,7 +72,6 @@ class SemanticSearchService { _mlFramework = _currentModel == Model.onnxClip ? ONNX(shouldDownloadOverMobileData) : GGML(shouldDownloadOverMobileData); - await EmbeddingsDB.instance.init(); await EmbeddingStore.instance.init(); await _loadEmbeddings(); Bus.instance.on().listen((event) { From 0f5007b8d23cfead2f8ea50f4b3240865147352f Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Mon, 29 Apr 2024 17:32:42 +0530 Subject: [PATCH 034/367] Update key --- .../machine_learning/semantic_search/embedding_store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart b/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart index b8cd539e4..b3795db4e 100644 --- a/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart +++ b/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart @@ -19,7 +19,7 @@ class EmbeddingStore { static final EmbeddingStore instance = EmbeddingStore._privateConstructor(); - static const kEmbeddingsSyncTimeKey = "sync_time_embeddings_v3-test"; + static const kEmbeddingsSyncTimeKey = "sync_time_embeddings_v3"; final _logger = Logger("EmbeddingStore"); final _dio = NetworkClient.instance.enteDio; From 8ecb7710de66bcfeae340d67b59928ecd3e88a01 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Mon, 29 Apr 2024 17:34:00 +0530 Subject: [PATCH 035/367] v0.8.85 --- mobile/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 9c96bc762..d47937404 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.84+604 +version: 0.8.85+605 publish_to: none environment: From f5754eb2e19f219599d3dd8eec459d0da28b94f4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 13:01:29 +0530 Subject: [PATCH 036/367] Remove uses of path --- .../photos/src/components/Upload/Uploader.tsx | 4 +- web/packages/shared/hooks/useFileInput.tsx | 41 ++++++------------- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx index fdc6ee932..90b5f94b4 100644 --- a/web/apps/photos/src/components/Upload/Uploader.tsx +++ b/web/apps/photos/src/components/Upload/Uploader.tsx @@ -324,8 +324,8 @@ export default function Uploader({ // Trigger an upload when any of the dependencies change. useEffect(() => { const allItemAndPaths = [ - /* TODO(MR): ElectronFile | use webkitRelativePath || name here */ - webFiles.map((f) => [f, f["path"] ?? f.name]), + // See: [Note: webkitRelativePath] + webFiles.map((f) => [f, f.webkitRelativePath ?? f.name]), desktopFiles.map((fp) => [fp, fp.path]), desktopFilePaths.map((p) => [p, p]), desktopZipItems.map((ze) => [ze, ze[1]]), diff --git a/web/packages/shared/hooks/useFileInput.tsx b/web/packages/shared/hooks/useFileInput.tsx index 4eb346d39..158a71b44 100644 --- a/web/packages/shared/hooks/useFileInput.tsx +++ b/web/packages/shared/hooks/useFileInput.tsx @@ -72,19 +72,27 @@ export default function useFileInput({ event, ) => { if (!!event.target && !!event.target.files) { - const files = [...event.target.files].map((file) => - toFileWithPath(file), - ); - setSelectedFiles(files); + setSelectedFiles([...event.target.files]); } }; + // [Note: webkitRelativePath] + // + // If the webkitdirectory attribute of an HTML element is set then + // the File objects that we get will have `webkitRelativePath` property + // containing the relative path to the selected directory. + // + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory + const directoryOpts = directory + ? { directory: "", webkitdirectory: "" } + : {}; + const getInputProps = useCallback( () => ({ type: "file", multiple: true, style: { display: "none" }, - ...(directory ? { directory: "", webkitdirectory: "" } : {}), + ...directoryOpts, ref: inputRef, onChange: handleChange, ...(accept ? { accept } : {}), @@ -98,26 +106,3 @@ export default function useFileInput({ selectedFiles: selectedFiles, }; } - -// https://github.com/react-dropzone/file-selector/blob/master/src/file.ts#L88 -export function toFileWithPath(file: File, path?: string): FileWithPath { - if (typeof (file as any).path !== "string") { - // on electron, path is already set to the absolute path - const { webkitRelativePath } = file; - Object.defineProperty(file, "path", { - value: - typeof path === "string" - ? path - : typeof webkitRelativePath === "string" && // If is set, - // the File will have a {webkitRelativePath} property - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory - webkitRelativePath.length > 0 - ? webkitRelativePath - : file.name, - writable: false, - configurable: false, - enumerable: true, - }); - } - return file; -} From f84937f8c1dc38a83e0e4b342bf932a624a17f47 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 13:26:55 +0530 Subject: [PATCH 037/367] Bye ElectronFile --- desktop/src/main/dialogs.ts | 72 ---------- desktop/src/main/ipc.ts | 19 +-- desktop/src/main/services/dialog.ts | 10 ++ desktop/src/main/services/fs.ts | 154 --------------------- desktop/src/main/services/image.ts | 2 +- desktop/src/main/services/upload.ts | 53 +------ desktop/src/main/services/watch.ts | 8 +- desktop/src/main/utils-path.ts | 8 ++ desktop/src/main/utils-temp.ts | 2 +- desktop/src/preload.ts | 14 +- web/packages/next/types/ipc.ts | 30 ++-- web/packages/shared/hooks/useFileInput.tsx | 23 --- 12 files changed, 44 insertions(+), 351 deletions(-) delete mode 100644 desktop/src/main/dialogs.ts create mode 100644 desktop/src/main/services/dialog.ts delete mode 100644 desktop/src/main/services/fs.ts create mode 100644 desktop/src/main/utils-path.ts diff --git a/desktop/src/main/dialogs.ts b/desktop/src/main/dialogs.ts deleted file mode 100644 index f119e3d13..000000000 --- a/desktop/src/main/dialogs.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { dialog } from "electron/main"; -import fs from "node:fs/promises"; -import path from "node:path"; -import type { ElectronFile } from "../types/ipc"; -import { getElectronFile } from "./services/fs"; -import { getElectronFilesFromGoogleZip } from "./services/upload"; - -export const selectDirectory = async () => { - const result = await dialog.showOpenDialog({ - properties: ["openDirectory"], - }); - if (result.filePaths && result.filePaths.length > 0) { - return result.filePaths[0]?.split(path.sep)?.join(path.posix.sep); - } -}; - -export const showUploadFilesDialog = async () => { - const selectedFiles = await dialog.showOpenDialog({ - properties: ["openFile", "multiSelections"], - }); - const filePaths = selectedFiles.filePaths; - return await Promise.all(filePaths.map(getElectronFile)); -}; - -export const showUploadDirsDialog = async () => { - const dir = await dialog.showOpenDialog({ - properties: ["openDirectory", "multiSelections"], - }); - - let filePaths: string[] = []; - for (const dirPath of dir.filePaths) { - filePaths = [...filePaths, ...(await getDirFilePaths(dirPath))]; - } - - return await Promise.all(filePaths.map(getElectronFile)); -}; - -// https://stackoverflow.com/a/63111390 -const getDirFilePaths = async (dirPath: string) => { - if (!(await fs.stat(dirPath)).isDirectory()) { - return [dirPath]; - } - - let files: string[] = []; - const filePaths = await fs.readdir(dirPath); - - for (const filePath of filePaths) { - const absolute = path.join(dirPath, filePath); - files = [...files, ...(await getDirFilePaths(absolute))]; - } - - return files; -}; - -export const showUploadZipDialog = async () => { - const selectedFiles = await dialog.showOpenDialog({ - properties: ["openFile", "multiSelections"], - filters: [{ name: "Zip File", extensions: ["zip"] }], - }); - const filePaths = selectedFiles.filePaths; - - let files: ElectronFile[] = []; - - for (const filePath of filePaths) { - files = [...files, ...(await getElectronFilesFromGoogleZip(filePath))]; - } - - return { - zipPaths: filePaths, - files, - }; -}; diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index df6ab7c8e..bb5daeaba 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -16,12 +16,6 @@ import type { PendingUploads, ZipItem, } from "../types/ipc"; -import { - selectDirectory, - showUploadDirsDialog, - showUploadFilesDialog, - showUploadZipDialog, -} from "./dialogs"; import { fsExists, fsIsDir, @@ -39,6 +33,7 @@ import { updateAndRestart, updateOnNextRestart, } from "./services/app-update"; +import { selectDirectory } from "./services/dialog"; import { ffmpegExec } from "./services/ffmpeg"; import { convertToJPEG, generateImageThumbnail } from "./services/image"; import { @@ -102,6 +97,8 @@ export const attachIPCHandlers = () => { // See [Note: Catching exception during .send/.on] ipcMain.on("logToDisk", (_, message) => logToDisk(message)); + ipcMain.handle("selectDirectory", () => selectDirectory()); + ipcMain.on("clearStores", () => clearStores()); ipcMain.handle("saveEncryptionKey", (_, encryptionKey) => @@ -193,16 +190,6 @@ export const attachIPCHandlers = () => { faceEmbedding(input), ); - // - File selection - - ipcMain.handle("selectDirectory", () => selectDirectory()); - - ipcMain.handle("showUploadFilesDialog", () => showUploadFilesDialog()); - - ipcMain.handle("showUploadDirsDialog", () => showUploadDirsDialog()); - - ipcMain.handle("showUploadZipDialog", () => showUploadZipDialog()); - // - Upload ipcMain.handle("listZipItems", (_, zipPath: string) => diff --git a/desktop/src/main/services/dialog.ts b/desktop/src/main/services/dialog.ts new file mode 100644 index 000000000..e98a6a9dd --- /dev/null +++ b/desktop/src/main/services/dialog.ts @@ -0,0 +1,10 @@ +import { dialog } from "electron/main"; +import { posixPath } from "../utils-path"; + +export const selectDirectory = async () => { + const result = await dialog.showOpenDialog({ + properties: ["openDirectory"], + }); + const dirPath = result.filePaths[0]; + return dirPath ? posixPath(dirPath) : undefined; +}; diff --git a/desktop/src/main/services/fs.ts b/desktop/src/main/services/fs.ts deleted file mode 100644 index 609fc82d7..000000000 --- a/desktop/src/main/services/fs.ts +++ /dev/null @@ -1,154 +0,0 @@ -import StreamZip from "node-stream-zip"; -import { existsSync } from "node:fs"; -import fs from "node:fs/promises"; -import path from "node:path"; -import { ElectronFile } from "../../types/ipc"; -import log from "../log"; - -const FILE_STREAM_CHUNK_SIZE: number = 4 * 1024 * 1024; - -const getFileStream = async (filePath: string) => { - const file = await fs.open(filePath, "r"); - let offset = 0; - const readableStream = new ReadableStream({ - async pull(controller) { - try { - const buff = new Uint8Array(FILE_STREAM_CHUNK_SIZE); - const bytesRead = (await file.read( - buff, - 0, - FILE_STREAM_CHUNK_SIZE, - offset, - )) as unknown as number; - offset += bytesRead; - if (bytesRead === 0) { - controller.close(); - await file.close(); - } else { - controller.enqueue(buff.slice(0, bytesRead)); - } - } catch (e) { - await file.close(); - } - }, - async cancel() { - await file.close(); - }, - }); - return readableStream; -}; - -export async function getElectronFile(filePath: string): Promise { - const fileStats = await fs.stat(filePath); - return { - path: filePath.split(path.sep).join(path.posix.sep), - name: path.basename(filePath), - size: fileStats.size, - lastModified: fileStats.mtime.valueOf(), - stream: async () => { - if (!existsSync(filePath)) { - throw new Error("electronFile does not exist"); - } - return await getFileStream(filePath); - }, - blob: async () => { - if (!existsSync(filePath)) { - throw new Error("electronFile does not exist"); - } - const blob = await fs.readFile(filePath); - return new Blob([new Uint8Array(blob)]); - }, - arrayBuffer: async () => { - if (!existsSync(filePath)) { - throw new Error("electronFile does not exist"); - } - const blob = await fs.readFile(filePath); - return new Uint8Array(blob); - }, - }; -} - -export const getZipFileStream = async ( - zip: StreamZip.StreamZipAsync, - filePath: string, -) => { - const stream = await zip.stream(filePath); - const done = { - current: false, - }; - const inProgress = { - current: false, - }; - // eslint-disable-next-line no-unused-vars - let resolveObj: (value?: any) => void = null; - // eslint-disable-next-line no-unused-vars - let rejectObj: (reason?: any) => void = null; - stream.on("readable", () => { - try { - if (resolveObj) { - inProgress.current = true; - const chunk = stream.read(FILE_STREAM_CHUNK_SIZE) as Buffer; - if (chunk) { - resolveObj(new Uint8Array(chunk)); - resolveObj = null; - } - inProgress.current = false; - } - } catch (e) { - rejectObj(e); - } - }); - stream.on("end", () => { - try { - done.current = true; - if (resolveObj && !inProgress.current) { - resolveObj(null); - resolveObj = null; - } - } catch (e) { - rejectObj(e); - } - }); - stream.on("error", (e) => { - try { - done.current = true; - if (rejectObj) { - rejectObj(e); - rejectObj = null; - } - } catch (e) { - rejectObj(e); - } - }); - - const readStreamData = async () => { - return new Promise((resolve, reject) => { - const chunk = stream.read(FILE_STREAM_CHUNK_SIZE) as Buffer; - - if (chunk || done.current) { - resolve(chunk); - } else { - resolveObj = resolve; - rejectObj = reject; - } - }); - }; - - const readableStream = new ReadableStream({ - async pull(controller) { - try { - const data = await readStreamData(); - - if (data) { - controller.enqueue(data); - } else { - controller.close(); - } - } catch (e) { - log.error("Failed to pull from readableStream", e); - controller.close(); - } - }, - }); - return readableStream; -}; diff --git a/desktop/src/main/services/image.ts b/desktop/src/main/services/image.ts index c48e87c5b..273607c4b 100644 --- a/desktop/src/main/services/image.ts +++ b/desktop/src/main/services/image.ts @@ -1,7 +1,7 @@ /** @file Image format conversions and thumbnail generation */ import fs from "node:fs/promises"; -import path from "path"; +import path from "node:path"; import { CustomErrorMessage, type ZipItem } from "../../types/ipc"; import log from "../log"; import { execAsync, isDev } from "../utils-electron"; diff --git a/desktop/src/main/services/upload.ts b/desktop/src/main/services/upload.ts index 9b24cc0ea..a1103a748 100644 --- a/desktop/src/main/services/upload.ts +++ b/desktop/src/main/services/upload.ts @@ -1,10 +1,9 @@ import StreamZip from "node-stream-zip"; import fs from "node:fs/promises"; +import path from "node:path"; import { existsSync } from "original-fs"; -import path from "path"; -import type { ElectronFile, PendingUploads, ZipItem } from "../../types/ipc"; +import type { PendingUploads, ZipItem } from "../../types/ipc"; import { uploadStatusStore } from "../stores/upload-status"; -import { getZipFileStream } from "./fs"; export const listZipItems = async (zipPath: string): Promise => { const zip = new StreamZip.async({ file: zipPath }); @@ -99,51 +98,3 @@ export const markUploadedZipItems = async ( }; export const clearPendingUploads = () => uploadStatusStore.clear(); - -export const getElectronFilesFromGoogleZip = async (filePath: string) => { - const zip = new StreamZip.async({ - file: filePath, - }); - const zipName = path.basename(filePath, ".zip"); - - const entries = await zip.entries(); - const files: ElectronFile[] = []; - - for (const entry of Object.values(entries)) { - const basename = path.basename(entry.name); - if (entry.isFile && basename.length > 0 && basename[0] !== ".") { - files.push(await getZipEntryAsElectronFile(zipName, zip, entry)); - } - } - - zip.close(); - - return files; -}; - -export async function getZipEntryAsElectronFile( - zipName: string, - zip: StreamZip.StreamZipAsync, - entry: StreamZip.ZipEntry, -): Promise { - return { - path: path - .join(zipName, entry.name) - .split(path.sep) - .join(path.posix.sep), - name: path.basename(entry.name), - size: entry.size, - lastModified: entry.time, - stream: async () => { - return await getZipFileStream(zip, entry.name); - }, - blob: async () => { - const buffer = await zip.entryData(entry.name); - return new Blob([new Uint8Array(buffer)]); - }, - arrayBuffer: async () => { - const buffer = await zip.entryData(entry.name); - return new Uint8Array(buffer); - }, - }; -} diff --git a/desktop/src/main/services/watch.ts b/desktop/src/main/services/watch.ts index 73a13c545..85463ae49 100644 --- a/desktop/src/main/services/watch.ts +++ b/desktop/src/main/services/watch.ts @@ -6,6 +6,7 @@ import { FolderWatch, type CollectionMapping } from "../../types/ipc"; import { fsIsDir } from "../fs"; import log from "../log"; import { watchStore } from "../stores/watch"; +import { posixPath } from "../utils-path"; /** * Create and return a new file system watcher. @@ -46,13 +47,6 @@ const eventData = (path: string): [string, FolderWatch] => { return [path, watch]; }; -/** - * Convert a file system {@link filePath} that uses the local system specific - * path separators into a path that uses POSIX file separators. - */ -const posixPath = (filePath: string) => - filePath.split(path.sep).join(path.posix.sep); - export const watchGet = (watcher: FSWatcher) => { const [valid, deleted] = folderWatches().reduce( ([valid, deleted], watch) => { diff --git a/desktop/src/main/utils-path.ts b/desktop/src/main/utils-path.ts new file mode 100644 index 000000000..b5e358e03 --- /dev/null +++ b/desktop/src/main/utils-path.ts @@ -0,0 +1,8 @@ +import path from "node:path"; + +/** + * Convert a file system {@link filePath} that uses the local system specific + * path separators into a path that uses POSIX file separators. + */ +export const posixPath = (filePath: string) => + filePath.split(path.sep).join(path.posix.sep); diff --git a/desktop/src/main/utils-temp.ts b/desktop/src/main/utils-temp.ts index 3f3a6081e..5928931f2 100644 --- a/desktop/src/main/utils-temp.ts +++ b/desktop/src/main/utils-temp.ts @@ -2,7 +2,7 @@ import { app } from "electron/main"; import StreamZip from "node-stream-zip"; import { existsSync } from "node:fs"; import fs from "node:fs/promises"; -import path from "path"; +import path from "node:path"; import type { ZipItem } from "../types/ipc"; /** diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index 61955b524..52fe068e4 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -63,6 +63,9 @@ const openDirectory = (dirPath: string): Promise => const openLogDirectory = (): Promise => ipcRenderer.invoke("openLogDirectory"); +const selectDirectory = (): Promise => + ipcRenderer.invoke("selectDirectory"); + const clearStores = () => ipcRenderer.send("clearStores"); const encryptionKey = (): Promise => @@ -174,9 +177,6 @@ const faceEmbedding = (input: Float32Array): Promise => // TODO: Deprecated - use dialogs on the renderer process itself -const selectDirectory = (): Promise => - ipcRenderer.invoke("selectDirectory"); - const showUploadFilesDialog = (): Promise => ipcRenderer.invoke("showUploadFilesDialog"); @@ -310,6 +310,7 @@ contextBridge.exposeInMainWorld("electron", { logToDisk, openDirectory, openLogDirectory, + selectDirectory, clearStores, encryptionKey, saveEncryptionKey, @@ -348,13 +349,6 @@ contextBridge.exposeInMainWorld("electron", { detectFaces, faceEmbedding, - // - File selection - - selectDirectory, - showUploadFilesDialog, - showUploadDirsDialog, - showUploadZipDialog, - // - Watch watch: { diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index 173b12b17..d97a7e564 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -3,8 +3,6 @@ // // See [Note: types.ts <-> preload.ts <-> ipc.ts] -import type { ElectronFile } from "./file"; - /** * Extra APIs provided by our Node.js layer when our code is running inside our * desktop (Electron) app. @@ -51,6 +49,18 @@ export interface Electron { */ openLogDirectory: () => Promise; + /** + * Ask the user to select a directory on their local file system, and return + * it path. + * + * We don't strictly need IPC for this, we can use a hidden element + * and trigger its click for the same behaviour (as we do for the + * `useFileInput` hook that we use for uploads). However, it's a bit + * cumbersome, and we anyways will need to IPC to get back its full path, so + * it is just convenient to expose this direct method. + */ + selectDirectory: () => Promise; + /** * Clear any stored data. * @@ -122,6 +132,8 @@ export interface Electron { */ skipAppUpdate: (version: string) => void; + // - FS + /** * A subset of file system access APIs. * @@ -332,20 +344,6 @@ export interface Electron { */ faceEmbedding: (input: Float32Array) => Promise; - // - File selection - // TODO: Deprecated - use dialogs on the renderer process itself - - selectDirectory: () => Promise; - - showUploadFilesDialog: () => Promise; - - showUploadDirsDialog: () => Promise; - - showUploadZipDialog: () => Promise<{ - zipPaths: string[]; - files: ElectronFile[]; - }>; - // - Watch /** diff --git a/web/packages/shared/hooks/useFileInput.tsx b/web/packages/shared/hooks/useFileInput.tsx index 158a71b44..ae1dfcab0 100644 --- a/web/packages/shared/hooks/useFileInput.tsx +++ b/web/packages/shared/hooks/useFileInput.tsx @@ -1,28 +1,5 @@ import { useCallback, useRef, useState } from "react"; -/** - * [Note: File paths when running under Electron] - * - * We have access to the absolute path of the web {@link File} object when we - * are running in the context of our desktop app. - * - * https://www.electronjs.org/docs/latest/api/file-object - * - * This is in contrast to the `webkitRelativePath` that we get when we're - * running in the browser, which is the relative path to the directory that the - * user selected (or just the name of the file if the user selected or - * drag/dropped a single one). - * - * Note that this is a deprecated approach. From Electron docs: - * - * > Warning: The path property that Electron adds to the File interface is - * > deprecated and will be removed in a future Electron release. We recommend - * > you use `webUtils.getPathForFile` instead. - */ -export interface FileWithPath extends File { - readonly path?: string; -} - interface UseFileInputParams { directory?: boolean; accept?: string; From d6aeef85d6892a3d9775cc0783c68636ca626695 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 13:33:18 +0530 Subject: [PATCH 038/367] Rearrange --- desktop/src/main.ts | 2 +- desktop/src/main/ipc.ts | 25 +++++----- desktop/src/main/log.ts | 2 +- desktop/src/main/menu.ts | 2 +- desktop/src/main/services/dialog.ts | 10 ---- desktop/src/main/services/dir.ts | 48 +++++++++++++++++++ desktop/src/main/services/ffmpeg.ts | 4 +- desktop/src/main/{ => services}/fs.ts | 0 desktop/src/main/services/image.ts | 4 +- desktop/src/main/services/ml-clip.ts | 2 +- desktop/src/main/services/watch.ts | 4 +- .../{utils-electron.ts => utils/electron.ts} | 40 +--------------- desktop/src/main/{utils.ts => utils/index.ts} | 0 .../src/main/{utils-path.ts => utils/path.ts} | 0 .../src/main/{utils-temp.ts => utils/temp.ts} | 2 +- 15 files changed, 74 insertions(+), 71 deletions(-) delete mode 100644 desktop/src/main/services/dialog.ts create mode 100644 desktop/src/main/services/dir.ts rename desktop/src/main/{ => services}/fs.ts (100%) rename desktop/src/main/{utils-electron.ts => utils/electron.ts} (53%) rename desktop/src/main/{utils.ts => utils/index.ts} (100%) rename desktop/src/main/{utils-path.ts => utils/path.ts} (100%) rename desktop/src/main/{utils-temp.ts => utils/temp.ts} (98%) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 2774ec730..4b6db7eac 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -26,7 +26,7 @@ import { createWatcher } from "./main/services/watch"; import { userPreferences } from "./main/stores/user-preferences"; import { migrateLegacyWatchStoreIfNeeded } from "./main/stores/watch"; import { registerStreamProtocol } from "./main/stream"; -import { isDev } from "./main/utils-electron"; +import { isDev } from "./main/utils/electron"; /** * The URL where the renderer HTML is being served from. diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index bb5daeaba..eb8b6cdda 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -16,6 +16,19 @@ import type { PendingUploads, ZipItem, } from "../types/ipc"; +import { logToDisk } from "./log"; +import { + appVersion, + skipAppUpdate, + updateAndRestart, + updateOnNextRestart, +} from "./services/app-update"; +import { + openDirectory, + openLogDirectory, + selectDirectory, +} from "./services/dir"; +import { ffmpegExec } from "./services/ffmpeg"; import { fsExists, fsIsDir, @@ -25,16 +38,7 @@ import { fsRm, fsRmdir, fsWriteFile, -} from "./fs"; -import { logToDisk } from "./log"; -import { - appVersion, - skipAppUpdate, - updateAndRestart, - updateOnNextRestart, -} from "./services/app-update"; -import { selectDirectory } from "./services/dialog"; -import { ffmpegExec } from "./services/ffmpeg"; +} from "./services/fs"; import { convertToJPEG, generateImageThumbnail } from "./services/image"; import { clipImageEmbedding, @@ -63,7 +67,6 @@ import { watchUpdateIgnoredFiles, watchUpdateSyncedFiles, } from "./services/watch"; -import { openDirectory, openLogDirectory } from "./utils-electron"; /** * Listen for IPC events sent/invoked by the renderer process, and route them to diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index 22ebb5300..7fc25a94b 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -1,6 +1,6 @@ import log from "electron-log"; import util from "node:util"; -import { isDev } from "./utils-electron"; +import { isDev } from "./utils/electron"; /** * Initialize logging in the main process. diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index 12b1ee17d..990dd40e5 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -9,7 +9,7 @@ import { allowWindowClose } from "../main"; import { forceCheckForAppUpdates } from "./services/app-update"; import autoLauncher from "./services/auto-launcher"; import { userPreferences } from "./stores/user-preferences"; -import { isDev, openLogDirectory } from "./utils-electron"; +import { isDev, openLogDirectory } from "./utils/electron"; /** Create and return the entries in the app's main menu bar */ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { diff --git a/desktop/src/main/services/dialog.ts b/desktop/src/main/services/dialog.ts deleted file mode 100644 index e98a6a9dd..000000000 --- a/desktop/src/main/services/dialog.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { dialog } from "electron/main"; -import { posixPath } from "../utils-path"; - -export const selectDirectory = async () => { - const result = await dialog.showOpenDialog({ - properties: ["openDirectory"], - }); - const dirPath = result.filePaths[0]; - return dirPath ? posixPath(dirPath) : undefined; -}; diff --git a/desktop/src/main/services/dir.ts b/desktop/src/main/services/dir.ts new file mode 100644 index 000000000..a6917fe27 --- /dev/null +++ b/desktop/src/main/services/dir.ts @@ -0,0 +1,48 @@ +import { shell } from "electron/common"; +import { app, dialog } from "electron/main"; +import path from "node:path"; +import { posixPath } from "../utils/path"; + +export const selectDirectory = async () => { + const result = await dialog.showOpenDialog({ + properties: ["openDirectory"], + }); + const dirPath = result.filePaths[0]; + return dirPath ? posixPath(dirPath) : undefined; +}; + +/** + * Open the given {@link dirPath} in the system's folder viewer. + * + * For example, on macOS this'll open {@link dirPath} in Finder. + */ +export const openDirectory = async (dirPath: string) => { + const res = await shell.openPath(path.normalize(dirPath)); + // shell.openPath resolves with a string containing the error message + // corresponding to the failure if a failure occurred, otherwise "". + if (res) throw new Error(`Failed to open directory ${dirPath}: res`); +}; + +/** + * Open the app's log directory in the system's folder viewer. + * + * @see {@link openDirectory} + */ +export const openLogDirectory = () => openDirectory(logDirectoryPath()); + +/** + * Return the path where the logs for the app are saved. + * + * [Note: Electron app paths] + * + * By default, these paths are at the following locations: + * + * - macOS: `~/Library/Application Support/ente` + * - Linux: `~/.config/ente` + * - Windows: `%APPDATA%`, e.g. `C:\Users\\AppData\Local\ente` + * - Windows: C:\Users\\AppData\Local\ + * + * https://www.electronjs.org/docs/latest/api/app + * + */ +const logDirectoryPath = () => app.getPath("logs"); diff --git a/desktop/src/main/services/ffmpeg.ts b/desktop/src/main/services/ffmpeg.ts index 35977409a..78b7a9e9a 100644 --- a/desktop/src/main/services/ffmpeg.ts +++ b/desktop/src/main/services/ffmpeg.ts @@ -3,12 +3,12 @@ import fs from "node:fs/promises"; import type { ZipItem } from "../../types/ipc"; import log from "../log"; import { withTimeout } from "../utils"; -import { execAsync } from "../utils-electron"; +import { execAsync } from "../utils/electron"; import { deleteTempFile, makeFileForDataOrPathOrZipItem, makeTempFilePath, -} from "../utils-temp"; +} from "../utils/temp"; /* Duplicated in the web app's code (used by the WASM FFmpeg implementation). */ const ffmpegPathPlaceholder = "FFMPEG"; diff --git a/desktop/src/main/fs.ts b/desktop/src/main/services/fs.ts similarity index 100% rename from desktop/src/main/fs.ts rename to desktop/src/main/services/fs.ts diff --git a/desktop/src/main/services/image.ts b/desktop/src/main/services/image.ts index 273607c4b..957fe8120 100644 --- a/desktop/src/main/services/image.ts +++ b/desktop/src/main/services/image.ts @@ -4,12 +4,12 @@ import fs from "node:fs/promises"; import path from "node:path"; import { CustomErrorMessage, type ZipItem } from "../../types/ipc"; import log from "../log"; -import { execAsync, isDev } from "../utils-electron"; +import { execAsync, isDev } from "../utils/electron"; import { deleteTempFile, makeFileForDataOrPathOrZipItem, makeTempFilePath, -} from "../utils-temp"; +} from "../utils/temp"; export const convertToJPEG = async (imageData: Uint8Array) => { const inputFilePath = await makeTempFilePath(); diff --git a/desktop/src/main/services/ml-clip.ts b/desktop/src/main/services/ml-clip.ts index cdd2baab7..99e512aa6 100644 --- a/desktop/src/main/services/ml-clip.ts +++ b/desktop/src/main/services/ml-clip.ts @@ -11,7 +11,7 @@ import * as ort from "onnxruntime-node"; import Tokenizer from "../../thirdparty/clip-bpe-ts/mod"; import log from "../log"; import { writeStream } from "../stream"; -import { deleteTempFile, makeTempFilePath } from "../utils-temp"; +import { deleteTempFile, makeTempFilePath } from "../utils/temp"; import { makeCachedInferenceSession } from "./ml"; const cachedCLIPImageSession = makeCachedInferenceSession( diff --git a/desktop/src/main/services/watch.ts b/desktop/src/main/services/watch.ts index 85463ae49..5e57df3e5 100644 --- a/desktop/src/main/services/watch.ts +++ b/desktop/src/main/services/watch.ts @@ -3,10 +3,10 @@ import { BrowserWindow } from "electron/main"; import fs from "node:fs/promises"; import path from "node:path"; import { FolderWatch, type CollectionMapping } from "../../types/ipc"; -import { fsIsDir } from "../fs"; import log from "../log"; import { watchStore } from "../stores/watch"; -import { posixPath } from "../utils-path"; +import { posixPath } from "../utils/path"; +import { fsIsDir } from "./fs"; /** * Create and return a new file system watcher. diff --git a/desktop/src/main/utils-electron.ts b/desktop/src/main/utils/electron.ts similarity index 53% rename from desktop/src/main/utils-electron.ts rename to desktop/src/main/utils/electron.ts index e8a98f1df..97d05ea6d 100644 --- a/desktop/src/main/utils-electron.ts +++ b/desktop/src/main/utils/electron.ts @@ -1,10 +1,8 @@ import shellescape from "any-shell-escape"; -import { shell } from "electron"; /* TODO(MR): Why is this not in /main? */ import { app } from "electron/main"; import { exec } from "node:child_process"; -import path from "node:path"; import { promisify } from "node:util"; -import log from "./log"; +import log from "../log"; /** `true` if the app is running in development mode. */ export const isDev = !app.isPackaged; @@ -41,39 +39,3 @@ export const execAsync = (command: string | string[]) => { }; const execAsync_ = promisify(exec); - -/** - * Open the given {@link dirPath} in the system's folder viewer. - * - * For example, on macOS this'll open {@link dirPath} in Finder. - */ -export const openDirectory = async (dirPath: string) => { - const res = await shell.openPath(path.normalize(dirPath)); - // shell.openPath resolves with a string containing the error message - // corresponding to the failure if a failure occurred, otherwise "". - if (res) throw new Error(`Failed to open directory ${dirPath}: res`); -}; - -/** - * Open the app's log directory in the system's folder viewer. - * - * @see {@link openDirectory} - */ -export const openLogDirectory = () => openDirectory(logDirectoryPath()); - -/** - * Return the path where the logs for the app are saved. - * - * [Note: Electron app paths] - * - * By default, these paths are at the following locations: - * - * - macOS: `~/Library/Application Support/ente` - * - Linux: `~/.config/ente` - * - Windows: `%APPDATA%`, e.g. `C:\Users\\AppData\Local\ente` - * - Windows: C:\Users\\AppData\Local\ - * - * https://www.electronjs.org/docs/latest/api/app - * - */ -const logDirectoryPath = () => app.getPath("logs"); diff --git a/desktop/src/main/utils.ts b/desktop/src/main/utils/index.ts similarity index 100% rename from desktop/src/main/utils.ts rename to desktop/src/main/utils/index.ts diff --git a/desktop/src/main/utils-path.ts b/desktop/src/main/utils/path.ts similarity index 100% rename from desktop/src/main/utils-path.ts rename to desktop/src/main/utils/path.ts diff --git a/desktop/src/main/utils-temp.ts b/desktop/src/main/utils/temp.ts similarity index 98% rename from desktop/src/main/utils-temp.ts rename to desktop/src/main/utils/temp.ts index 5928931f2..28aea245d 100644 --- a/desktop/src/main/utils-temp.ts +++ b/desktop/src/main/utils/temp.ts @@ -3,7 +3,7 @@ import StreamZip from "node-stream-zip"; import { existsSync } from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; -import type { ZipItem } from "../types/ipc"; +import type { ZipItem } from "../../types/ipc"; /** * Our very own directory within the system temp directory. Go crazy, but From 6c4adb112702e0fdbf6a0e33b85ea4a4fe0a9aa1 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 13:37:17 +0530 Subject: [PATCH 039/367] Housekeeping --- desktop/src/main.ts | 6 ------ desktop/src/main/init.ts | 21 --------------------- desktop/src/main/services/dir.ts | 2 +- desktop/src/main/services/watch.ts | 2 +- desktop/src/main/utils/electron.ts | 8 ++++++++ desktop/src/main/utils/index.ts | 2 +- desktop/src/main/utils/path.ts | 8 -------- 7 files changed, 11 insertions(+), 38 deletions(-) delete mode 100644 desktop/src/main/init.ts delete mode 100644 desktop/src/main/utils/path.ts diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 4b6db7eac..9fb970604 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -401,12 +401,6 @@ const main = () => { setDownloadPath(mainWindow.webContents); allowExternalLinks(mainWindow.webContents); - // TODO(MR): Remove or resurrect - // The commit that introduced this header override had the message - // "fix cors issue for uploads". Not sure what that means, so disabling - // it for now to see why exactly this is required. - // addAllowOriginHeader(mainWindow); - // Start loading the renderer. mainWindow.loadURL(rendererURL); diff --git a/desktop/src/main/init.ts b/desktop/src/main/init.ts deleted file mode 100644 index d0aee17f8..000000000 --- a/desktop/src/main/init.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { BrowserWindow } from "electron"; - -export function addAllowOriginHeader(mainWindow: BrowserWindow) { - mainWindow.webContents.session.webRequest.onHeadersReceived( - (details, callback) => { - details.responseHeaders = lowerCaseHeaders(details.responseHeaders); - details.responseHeaders["access-control-allow-origin"] = ["*"]; - callback({ - responseHeaders: details.responseHeaders, - }); - }, - ); -} - -function lowerCaseHeaders(responseHeaders: Record) { - const headers: Record = {}; - for (const key of Object.keys(responseHeaders)) { - headers[key.toLowerCase()] = responseHeaders[key]; - } - return headers; -} diff --git a/desktop/src/main/services/dir.ts b/desktop/src/main/services/dir.ts index a6917fe27..4e2a8c65e 100644 --- a/desktop/src/main/services/dir.ts +++ b/desktop/src/main/services/dir.ts @@ -1,7 +1,7 @@ import { shell } from "electron/common"; import { app, dialog } from "electron/main"; import path from "node:path"; -import { posixPath } from "../utils/path"; +import { posixPath } from "../utils/electron"; export const selectDirectory = async () => { const result = await dialog.showOpenDialog({ diff --git a/desktop/src/main/services/watch.ts b/desktop/src/main/services/watch.ts index 5e57df3e5..588279b70 100644 --- a/desktop/src/main/services/watch.ts +++ b/desktop/src/main/services/watch.ts @@ -5,7 +5,7 @@ import path from "node:path"; import { FolderWatch, type CollectionMapping } from "../../types/ipc"; import log from "../log"; import { watchStore } from "../stores/watch"; -import { posixPath } from "../utils/path"; +import { posixPath } from "../utils/electron"; import { fsIsDir } from "./fs"; /** diff --git a/desktop/src/main/utils/electron.ts b/desktop/src/main/utils/electron.ts index 97d05ea6d..d627ec5c4 100644 --- a/desktop/src/main/utils/electron.ts +++ b/desktop/src/main/utils/electron.ts @@ -1,12 +1,20 @@ import shellescape from "any-shell-escape"; import { app } from "electron/main"; import { exec } from "node:child_process"; +import path from "node:path"; import { promisify } from "node:util"; import log from "../log"; /** `true` if the app is running in development mode. */ export const isDev = !app.isPackaged; +/** + * Convert a file system {@link filePath} that uses the local system specific + * path separators into a path that uses POSIX file separators. + */ +export const posixPath = (filePath: string) => + filePath.split(path.sep).join(path.posix.sep); + /** * Run a shell command asynchronously. * diff --git a/desktop/src/main/utils/index.ts b/desktop/src/main/utils/index.ts index 132859a43..1ae35d55d 100644 --- a/desktop/src/main/utils/index.ts +++ b/desktop/src/main/utils/index.ts @@ -1,5 +1,5 @@ /** - * @file grab bag of utitity functions. + * @file grab bag of utility functions. * * Many of these are verbatim copies of functions from web code since there * isn't currently a common package that both of them share. diff --git a/desktop/src/main/utils/path.ts b/desktop/src/main/utils/path.ts deleted file mode 100644 index b5e358e03..000000000 --- a/desktop/src/main/utils/path.ts +++ /dev/null @@ -1,8 +0,0 @@ -import path from "node:path"; - -/** - * Convert a file system {@link filePath} that uses the local system specific - * path separators into a path that uses POSIX file separators. - */ -export const posixPath = (filePath: string) => - filePath.split(path.sep).join(path.posix.sep); From b52c9f558fd686ea7521f34e984d2e9e9cc20e4a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 13:39:58 +0530 Subject: [PATCH 040/367] Remove cache size overrides Need a bit more benchmarking or real world feedback to see if this is even something that is helping us. --- desktop/src/main.ts | 27 --------------------------- web/packages/next/blob-cache.ts | 2 -- 2 files changed, 29 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 9fb970604..6f8881dd6 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -141,30 +141,6 @@ const registerPrivilegedSchemes = () => { ]); }; -/** - * [Note: Increased disk cache for the desktop app] - * - * Set the "disk-cache-size" command line flag to ask the Chromium process to - * use a larger size for the caches that it keeps on disk. This allows us to use - * the web based caching mechanisms on both the web and the desktop app, just - * ask the embedded Chromium to be a bit more generous in disk usage when - * running as the desktop app. - * - * The size we provide is in bytes. - * https://www.electronjs.org/docs/latest/api/command-line-switches#--disk-cache-sizesize - * - * Note that increasing the disk cache size does not guarantee that Chromium - * will respect in verbatim, it uses its own heuristics atop this hint. - * https://superuser.com/questions/378991/what-is-chrome-default-cache-size-limit/1577693#1577693 - * - * See also: [Note: Caching files]. - */ -const increaseDiskCache = () => - app.commandLine.appendSwitch( - "disk-cache-size", - `${5 * 1024 * 1024 * 1024}`, // 5 GB - ); - /** * Create an return the {@link BrowserWindow} that will form our app's UI. * @@ -321,8 +297,6 @@ const setupTrayItem = (mainWindow: BrowserWindow) => { * Older versions of our app used to maintain a cache dir using the main * process. This has been deprecated in favor of using a normal web cache. * - * See [Note: Increased disk cache for the desktop app] - * * Delete the old cache dir if it exists. This code was added March 2024, and * can be removed after some time once most people have upgraded to newer * versions. @@ -375,7 +349,6 @@ const main = () => { // The order of the next two calls is important setupRendererServer(); registerPrivilegedSchemes(); - increaseDiskCache(); migrateLegacyWatchStoreIfNeeded(); app.on("second-instance", () => { diff --git a/web/packages/next/blob-cache.ts b/web/packages/next/blob-cache.ts index 0e092fed6..e6c3734df 100644 --- a/web/packages/next/blob-cache.ts +++ b/web/packages/next/blob-cache.ts @@ -50,8 +50,6 @@ export type BlobCacheNamespace = (typeof blobCacheNames)[number]; * ([the WebKit bug](https://bugs.webkit.org/show_bug.cgi?id=231706)), so it's * not trivial to use this as a full on replacement of the Web Cache in the * browser. So for now we go with this split implementation. - * - * See also: [Note: Increased disk cache for the desktop app]. */ export interface BlobCache { /** From 4feefb9b8d497fa4a9072b4eec40950ed9776ca7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 13:41:18 +0530 Subject: [PATCH 041/367] Fix comment --- desktop/src/main/log.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index 7fc25a94b..d2421da62 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -7,9 +7,9 @@ import { isDev } from "./utils/electron"; * * This will set our underlying logger up to log to a file named `ente.log`, * - * - on Linux at ~/.config/ente/logs/main.log - * - on macOS at ~/Library/Logs/ente/main.log - * - on Windows at %USERPROFILE%\AppData\Roaming\ente\logs\main.log + * - on Linux at ~/.config/ente/logs/ente.log + * - on macOS at ~/Library/Logs/ente/ente.log + * - on Windows at %USERPROFILE%\AppData\Roaming\ente\logs\ente.log * * On dev builds, it will also log to the console. */ From 8400620488f47e4826877030b3979e94bc66e233 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 13:42:41 +0530 Subject: [PATCH 042/367] Gone from desktop --- desktop/src/preload.ts | 16 ---------------- desktop/src/types/ipc.ts | 22 ---------------------- 2 files changed, 38 deletions(-) diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index 52fe068e4..ecc800db3 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -44,7 +44,6 @@ import { contextBridge, ipcRenderer, webUtils } from "electron/renderer"; import type { AppUpdate, CollectionMapping, - ElectronFile, FolderWatch, PendingUploads, ZipItem, @@ -173,21 +172,6 @@ const detectFaces = (input: Float32Array): Promise => const faceEmbedding = (input: Float32Array): Promise => ipcRenderer.invoke("faceEmbedding", input); -// - File selection - -// TODO: Deprecated - use dialogs on the renderer process itself - -const showUploadFilesDialog = (): Promise => - ipcRenderer.invoke("showUploadFilesDialog"); - -const showUploadDirsDialog = (): Promise => - ipcRenderer.invoke("showUploadDirsDialog"); - -const showUploadZipDialog = (): Promise<{ - zipPaths: string[]; - files: ElectronFile[]; -}> => ipcRenderer.invoke("showUploadZipDialog"); - // - Watch const watchGet = (): Promise => ipcRenderer.invoke("watchGet"); diff --git a/desktop/src/types/ipc.ts b/desktop/src/types/ipc.ts index 6e47b7a3a..c02ed1726 100644 --- a/desktop/src/types/ipc.ts +++ b/desktop/src/types/ipc.ts @@ -42,25 +42,3 @@ export interface PendingUploads { export const CustomErrorMessage = { NotAvailable: "This feature in not available on the current OS/arch", }; - -/** - * Deprecated - Use File + webUtils.getPathForFile instead - * - * Electron used to augment the standard web - * [File](https://developer.mozilla.org/en-US/docs/Web/API/File) object with an - * additional `path` property. This is now deprecated, and will be removed in a - * future release. - * https://www.electronjs.org/docs/latest/api/file-object - * - * The alternative to the `path` property is to use `webUtils.getPathForFile` - * https://www.electronjs.org/docs/latest/api/web-utils - */ -export interface ElectronFile { - name: string; - path: string; - size: number; - lastModified: number; - stream: () => Promise>; - blob: () => Promise; - arrayBuffer: () => Promise; -} From 14348351a97120c7a897639042e0060ce32e8b56 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 13:44:46 +0530 Subject: [PATCH 043/367] Fix call of undefined --- desktop/src/main/utils/temp.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/desktop/src/main/utils/temp.ts b/desktop/src/main/utils/temp.ts index 28aea245d..09071c157 100644 --- a/desktop/src/main/utils/temp.ts +++ b/desktop/src/main/utils/temp.ts @@ -76,15 +76,14 @@ interface FileForDataOrPathOrZipItem { */ isFileTemporary: boolean; /** - * If set, this'll be a function that can be called to actually write the - * contents of the source `Uint8Array | string | ZipItem` into the file at - * {@link path}. + * A function that can be called to actually write the contents of the + * source `Uint8Array | string | ZipItem` into the file at {@link path}. * - * It will be undefined if the source is already a path since nothing needs - * to be written in that case. In the other two cases this function will - * write the data or zip item into the file at {@link path}. + * It will do nothing in the case when the source is already a path. In the + * other two cases this function will write the data or zip item into the + * file at {@link path}. */ - writeToTemporaryFile?: () => Promise; + writeToTemporaryFile: () => Promise; } /** @@ -101,7 +100,7 @@ export const makeFileForDataOrPathOrZipItem = async ( ): Promise => { let path: string; let isFileTemporary: boolean; - let writeToTemporaryFile: () => Promise | undefined; + let writeToTemporaryFile = async () => {}; if (typeof dataOrPathOrZipItem == "string") { path = dataOrPathOrZipItem; From 333f9c58f25ec772eba6159666bdbe312454179a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 13:56:11 +0530 Subject: [PATCH 044/367] strict 1 --- desktop/src/main.ts | 2 +- desktop/src/main/log.ts | 2 +- desktop/src/main/menu.ts | 3 +- desktop/src/main/services/dir.ts | 2 +- desktop/src/main/services/ffmpeg.ts | 4 +- desktop/src/main/services/image.ts | 2 +- desktop/src/main/services/ml-clip.ts | 7 ++- desktop/src/main/services/watch.ts | 2 +- desktop/src/main/stream.ts | 5 +- desktop/src/main/utils/common.ts | 35 ++++++++++++++ desktop/src/main/utils/electron.ts | 49 ------------------- desktop/src/main/utils/index.ts | 70 +++++++++++++++++----------- desktop/tsconfig.json | 6 +-- 13 files changed, 96 insertions(+), 93 deletions(-) create mode 100644 desktop/src/main/utils/common.ts delete mode 100644 desktop/src/main/utils/electron.ts diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 6f8881dd6..42c5ab732 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -26,7 +26,7 @@ import { createWatcher } from "./main/services/watch"; import { userPreferences } from "./main/stores/user-preferences"; import { migrateLegacyWatchStoreIfNeeded } from "./main/stores/watch"; import { registerStreamProtocol } from "./main/stream"; -import { isDev } from "./main/utils/electron"; +import { isDev } from "./main/utils"; /** * The URL where the renderer HTML is being served from. diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index d2421da62..c1902d8eb 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -1,6 +1,6 @@ import log from "electron-log"; import util from "node:util"; -import { isDev } from "./utils/electron"; +import { isDev } from "./utils"; /** * Initialize logging in the main process. diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index 990dd40e5..0693c01dc 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -8,8 +8,9 @@ import { import { allowWindowClose } from "../main"; import { forceCheckForAppUpdates } from "./services/app-update"; import autoLauncher from "./services/auto-launcher"; +import { openLogDirectory } from "./services/dir"; import { userPreferences } from "./stores/user-preferences"; -import { isDev, openLogDirectory } from "./utils/electron"; +import { isDev } from "./utils"; /** Create and return the entries in the app's main menu bar */ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { diff --git a/desktop/src/main/services/dir.ts b/desktop/src/main/services/dir.ts index 4e2a8c65e..ef3adb013 100644 --- a/desktop/src/main/services/dir.ts +++ b/desktop/src/main/services/dir.ts @@ -1,7 +1,7 @@ import { shell } from "electron/common"; import { app, dialog } from "electron/main"; import path from "node:path"; -import { posixPath } from "../utils/electron"; +import { posixPath } from "../utils"; export const selectDirectory = async () => { const result = await dialog.showOpenDialog({ diff --git a/desktop/src/main/services/ffmpeg.ts b/desktop/src/main/services/ffmpeg.ts index 78b7a9e9a..dc417c595 100644 --- a/desktop/src/main/services/ffmpeg.ts +++ b/desktop/src/main/services/ffmpeg.ts @@ -2,8 +2,8 @@ import pathToFfmpeg from "ffmpeg-static"; import fs from "node:fs/promises"; import type { ZipItem } from "../../types/ipc"; import log from "../log"; -import { withTimeout } from "../utils"; -import { execAsync } from "../utils/electron"; +import { execAsync } from "../utils"; +import { withTimeout } from "../utils/common"; import { deleteTempFile, makeFileForDataOrPathOrZipItem, diff --git a/desktop/src/main/services/image.ts b/desktop/src/main/services/image.ts index 957fe8120..d607b0ead 100644 --- a/desktop/src/main/services/image.ts +++ b/desktop/src/main/services/image.ts @@ -4,7 +4,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { CustomErrorMessage, type ZipItem } from "../../types/ipc"; import log from "../log"; -import { execAsync, isDev } from "../utils/electron"; +import { execAsync, isDev } from "../utils"; import { deleteTempFile, makeFileForDataOrPathOrZipItem, diff --git a/desktop/src/main/services/ml-clip.ts b/desktop/src/main/services/ml-clip.ts index 99e512aa6..67c6d2db7 100644 --- a/desktop/src/main/services/ml-clip.ts +++ b/desktop/src/main/services/ml-clip.ts @@ -22,6 +22,7 @@ const cachedCLIPImageSession = makeCachedInferenceSession( export const clipImageEmbedding = async (jpegImageData: Uint8Array) => { const tempFilePath = await makeTempFilePath(); const imageStream = new Response(jpegImageData.buffer).body; + if (!imageStream) throw new Error("Missing body that we just fed data to"); await writeStream(tempFilePath, imageStream); try { return await clipImageEmbedding_(tempFilePath); @@ -134,11 +135,9 @@ const cachedCLIPTextSession = makeCachedInferenceSession( 64173509 /* 61.2 MB */, ); -let _tokenizer: Tokenizer = null; +let _tokenizer: Tokenizer | undefined; const getTokenizer = () => { - if (!_tokenizer) { - _tokenizer = new Tokenizer(); - } + if (!_tokenizer) _tokenizer = new Tokenizer(); return _tokenizer; }; diff --git a/desktop/src/main/services/watch.ts b/desktop/src/main/services/watch.ts index 588279b70..4d7b89e46 100644 --- a/desktop/src/main/services/watch.ts +++ b/desktop/src/main/services/watch.ts @@ -5,7 +5,7 @@ import path from "node:path"; import { FolderWatch, type CollectionMapping } from "../../types/ipc"; import log from "../log"; import { watchStore } from "../stores/watch"; -import { posixPath } from "../utils/electron"; +import { posixPath } from "../utils"; import { fsIsDir } from "./fs"; /** diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index b37970cfa..be84c022f 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -99,7 +99,10 @@ const handleReadZip = async (zipPath: string, entryName: string) => { try { const zip = new StreamZip.async({ file: zipPath }); const entry = await zip.entry(entryName); + if (!entry) return new Response("", { status: 404 }); + const stream = await zip.stream(entry); + // TODO(MR): when to call zip.close() return new Response(Readable.toWeb(new Readable(stream)), { @@ -122,7 +125,7 @@ const handleReadZip = async (zipPath: string, entryName: string) => { `Failed to read entry ${entryName} from zip file at ${zipPath}`, e, ); - return new Response(`Failed to read stream: ${e.message}`, { + return new Response(`Failed to read stream: ${String(e)}`, { status: 500, }); } diff --git a/desktop/src/main/utils/common.ts b/desktop/src/main/utils/common.ts new file mode 100644 index 000000000..100a8ad2d --- /dev/null +++ b/desktop/src/main/utils/common.ts @@ -0,0 +1,35 @@ +/** + * @file grab bag of utility functions. + * + * These are verbatim copies of functions from web code since there isn't + * currently a common package that both of them share. + */ + +/** + * Wait for {@link ms} milliseconds + * + * This function is a promisified `setTimeout`. It returns a promise that + * resolves after {@link ms} milliseconds. + */ +export const wait = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + +/** + * Await the given {@link promise} for {@link timeoutMS} milliseconds. If it + * does not resolve within {@link timeoutMS}, then reject with a timeout error. + */ +export const withTimeout = async (promise: Promise, ms: number) => { + let timeoutId: ReturnType; + const rejectOnTimeout = new Promise((_, reject) => { + timeoutId = setTimeout( + () => reject(new Error("Operation timed out")), + ms, + ); + }); + const promiseAndCancelTimeout = async () => { + const result = await promise; + clearTimeout(timeoutId); + return result; + }; + return Promise.race([promiseAndCancelTimeout(), rejectOnTimeout]); +}; diff --git a/desktop/src/main/utils/electron.ts b/desktop/src/main/utils/electron.ts deleted file mode 100644 index d627ec5c4..000000000 --- a/desktop/src/main/utils/electron.ts +++ /dev/null @@ -1,49 +0,0 @@ -import shellescape from "any-shell-escape"; -import { app } from "electron/main"; -import { exec } from "node:child_process"; -import path from "node:path"; -import { promisify } from "node:util"; -import log from "../log"; - -/** `true` if the app is running in development mode. */ -export const isDev = !app.isPackaged; - -/** - * Convert a file system {@link filePath} that uses the local system specific - * path separators into a path that uses POSIX file separators. - */ -export const posixPath = (filePath: string) => - filePath.split(path.sep).join(path.posix.sep); - -/** - * Run a shell command asynchronously. - * - * This is a convenience promisified version of child_process.exec. It runs the - * command asynchronously and returns its stdout and stderr if there were no - * errors. - * - * If the command is passed as a string, then it will be executed verbatim. - * - * If the command is passed as an array, then the first argument will be treated - * as the executable and the remaining (optional) items as the command line - * parameters. This function will shellescape and join the array to form the - * command that finally gets executed. - * - * > Note: This is not a 1-1 replacement of child_process.exec - if you're - * > trying to run a trivial shell command, say something that produces a lot of - * > output, this might not be the best option and it might be better to use the - * > underlying functions. - */ -export const execAsync = (command: string | string[]) => { - const escapedCommand = Array.isArray(command) - ? shellescape(command) - : command; - const startTime = Date.now(); - const result = execAsync_(escapedCommand); - log.debug( - () => `${escapedCommand} (${Math.round(Date.now() - startTime)} ms)`, - ); - return result; -}; - -const execAsync_ = promisify(exec); diff --git a/desktop/src/main/utils/index.ts b/desktop/src/main/utils/index.ts index 1ae35d55d..d627ec5c4 100644 --- a/desktop/src/main/utils/index.ts +++ b/desktop/src/main/utils/index.ts @@ -1,35 +1,49 @@ -/** - * @file grab bag of utility functions. - * - * Many of these are verbatim copies of functions from web code since there - * isn't currently a common package that both of them share. - */ +import shellescape from "any-shell-escape"; +import { app } from "electron/main"; +import { exec } from "node:child_process"; +import path from "node:path"; +import { promisify } from "node:util"; +import log from "../log"; + +/** `true` if the app is running in development mode. */ +export const isDev = !app.isPackaged; /** - * Wait for {@link ms} milliseconds - * - * This function is a promisified `setTimeout`. It returns a promise that - * resolves after {@link ms} milliseconds. + * Convert a file system {@link filePath} that uses the local system specific + * path separators into a path that uses POSIX file separators. */ -export const wait = (ms: number) => - new Promise((resolve) => setTimeout(resolve, ms)); +export const posixPath = (filePath: string) => + filePath.split(path.sep).join(path.posix.sep); /** - * Await the given {@link promise} for {@link timeoutMS} milliseconds. If it - * does not resolve within {@link timeoutMS}, then reject with a timeout error. + * Run a shell command asynchronously. + * + * This is a convenience promisified version of child_process.exec. It runs the + * command asynchronously and returns its stdout and stderr if there were no + * errors. + * + * If the command is passed as a string, then it will be executed verbatim. + * + * If the command is passed as an array, then the first argument will be treated + * as the executable and the remaining (optional) items as the command line + * parameters. This function will shellescape and join the array to form the + * command that finally gets executed. + * + * > Note: This is not a 1-1 replacement of child_process.exec - if you're + * > trying to run a trivial shell command, say something that produces a lot of + * > output, this might not be the best option and it might be better to use the + * > underlying functions. */ -export const withTimeout = async (promise: Promise, ms: number) => { - let timeoutId: ReturnType; - const rejectOnTimeout = new Promise((_, reject) => { - timeoutId = setTimeout( - () => reject(new Error("Operation timed out")), - ms, - ); - }); - const promiseAndCancelTimeout = async () => { - const result = await promise; - clearTimeout(timeoutId); - return result; - }; - return Promise.race([promiseAndCancelTimeout(), rejectOnTimeout]); +export const execAsync = (command: string | string[]) => { + const escapedCommand = Array.isArray(command) + ? shellescape(command) + : command; + const startTime = Date.now(); + const result = execAsync_(escapedCommand); + log.debug( + () => `${escapedCommand} (${Math.round(Date.now() - startTime)} ms)`, + ); + return result; }; + +const execAsync_ = promisify(exec); diff --git a/desktop/tsconfig.json b/desktop/tsconfig.json index 700ea3fa0..16946bf3f 100644 --- a/desktop/tsconfig.json +++ b/desktop/tsconfig.json @@ -50,12 +50,12 @@ "outDir": "app", /* Temporary overrides to get things to compile with the older config */ - "strict": false, - "noImplicitAny": true + // "strict": false, + "noImplicitAny": true, /* Below is the state we want */ /* Enable these one by one */ - // "strict": true, + "strict": true, /* Require the `type` modifier when importing types */ // "verbatimModuleSyntax": true From 72b9113d3045c74740d8310b84cf412ab28ca482 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 13:58:15 +0530 Subject: [PATCH 045/367] ensure --- desktop/src/main/services/ffmpeg.ts | 4 ++-- desktop/src/main/services/fs.ts | 1 + desktop/src/main/services/ml-clip.ts | 4 ++-- desktop/src/main/utils/common.ts | 9 +++++++++ web/packages/utils/ensure.ts | 5 +++-- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/desktop/src/main/services/ffmpeg.ts b/desktop/src/main/services/ffmpeg.ts index dc417c595..850b70d44 100644 --- a/desktop/src/main/services/ffmpeg.ts +++ b/desktop/src/main/services/ffmpeg.ts @@ -3,7 +3,7 @@ import fs from "node:fs/promises"; import type { ZipItem } from "../../types/ipc"; import log from "../log"; import { execAsync } from "../utils"; -import { withTimeout } from "../utils/common"; +import { ensure, withTimeout } from "../utils/common"; import { deleteTempFile, makeFileForDataOrPathOrZipItem, @@ -110,5 +110,5 @@ const ffmpegBinaryPath = () => { // This substitution of app.asar by app.asar.unpacked is suggested by the // ffmpeg-static library author themselves: // https://github.com/eugeneware/ffmpeg-static/issues/16 - return pathToFfmpeg.replace("app.asar", "app.asar.unpacked"); + return ensure(pathToFfmpeg).replace("app.asar", "app.asar.unpacked"); }; diff --git a/desktop/src/main/services/fs.ts b/desktop/src/main/services/fs.ts index 2428d3a80..4570a4a33 100644 --- a/desktop/src/main/services/fs.ts +++ b/desktop/src/main/services/fs.ts @@ -1,6 +1,7 @@ /** * @file file system related functions exposed over the context bridge. */ + import { existsSync } from "node:fs"; import fs from "node:fs/promises"; diff --git a/desktop/src/main/services/ml-clip.ts b/desktop/src/main/services/ml-clip.ts index 67c6d2db7..149f634b5 100644 --- a/desktop/src/main/services/ml-clip.ts +++ b/desktop/src/main/services/ml-clip.ts @@ -11,6 +11,7 @@ import * as ort from "onnxruntime-node"; import Tokenizer from "../../thirdparty/clip-bpe-ts/mod"; import log from "../log"; import { writeStream } from "../stream"; +import { ensure } from "../utils/common"; import { deleteTempFile, makeTempFilePath } from "../utils/temp"; import { makeCachedInferenceSession } from "./ml"; @@ -22,8 +23,7 @@ const cachedCLIPImageSession = makeCachedInferenceSession( export const clipImageEmbedding = async (jpegImageData: Uint8Array) => { const tempFilePath = await makeTempFilePath(); const imageStream = new Response(jpegImageData.buffer).body; - if (!imageStream) throw new Error("Missing body that we just fed data to"); - await writeStream(tempFilePath, imageStream); + await writeStream(tempFilePath, ensure(imageStream)); try { return await clipImageEmbedding_(tempFilePath); } finally { diff --git a/desktop/src/main/utils/common.ts b/desktop/src/main/utils/common.ts index 100a8ad2d..1f5016e61 100644 --- a/desktop/src/main/utils/common.ts +++ b/desktop/src/main/utils/common.ts @@ -5,6 +5,15 @@ * currently a common package that both of them share. */ +/** + * Throw an exception if the given value is `null` or `undefined`. + */ +export const ensure = (v: T | null | undefined): T => { + if (v === null) throw new Error("Required value was null"); + if (v === undefined) throw new Error("Required value was not found"); + return v; +}; + /** * Wait for {@link ms} milliseconds * diff --git a/web/packages/utils/ensure.ts b/web/packages/utils/ensure.ts index 761cedc99..93706bfb6 100644 --- a/web/packages/utils/ensure.ts +++ b/web/packages/utils/ensure.ts @@ -1,7 +1,8 @@ /** - * Throw an exception if the given value is undefined. + * Throw an exception if the given value is `null` or `undefined`. */ -export const ensure = (v: T | undefined): T => { +export const ensure = (v: T | null | undefined): T => { + if (v === null) throw new Error("Required value was null"); if (v === undefined) throw new Error("Required value was not found"); return v; }; From bee2cd533ce0fece2d221fa9cbdacb55634124f4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 14:01:28 +0530 Subject: [PATCH 046/367] strict 2 --- desktop/src/main/services/ml.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/desktop/src/main/services/ml.ts b/desktop/src/main/services/ml.ts index 8292596a2..6b38bc74d 100644 --- a/desktop/src/main/services/ml.ts +++ b/desktop/src/main/services/ml.ts @@ -34,6 +34,7 @@ import { writeStream } from "../stream"; * actively trigger a download until the returned function is called. * * @param modelName The name of the model to download. + * * @param modelByteSize The size in bytes that we expect the model to have. If * the size of the downloaded model does not match the expected size, then we * will redownload it. @@ -99,13 +100,15 @@ const downloadModel = async (saveLocation: string, name: string) => { // `mkdir -p` the directory where we want to save the model. const saveDir = path.dirname(saveLocation); await fs.mkdir(saveDir, { recursive: true }); - // Download + // Download. log.info(`Downloading ML model from ${name}`); const url = `https://models.ente.io/${name}`; const res = await net.fetch(url); if (!res.ok) throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`); - // Save - await writeStream(saveLocation, res.body); + const body = res.body; + if (!body) throw new Error(`Received an null response for ${url}`); + // Save. + await writeStream(saveLocation, body); log.info(`Downloaded CLIP model ${name}`); }; @@ -114,9 +117,9 @@ const downloadModel = async (saveLocation: string, name: string) => { */ const createInferenceSession = async (modelPath: string) => { return await ort.InferenceSession.create(modelPath, { - // Restrict the number of threads to 1 + // Restrict the number of threads to 1. intraOpNumThreads: 1, - // Be more conservative with RAM usage + // Be more conservative with RAM usage. enableCpuMemArena: false, }); }; From 0c312c0ea15d1cfc60f68b93008c619fdc32ba02 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 14:10:59 +0530 Subject: [PATCH 047/367] strict 3 --- desktop/src/main/services/upload.ts | 10 +++++++--- desktop/src/types/ipc.ts | 2 +- web/apps/photos/src/components/Upload/Uploader.tsx | 6 +----- web/packages/next/types/ipc.ts | 4 +++- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/desktop/src/main/services/upload.ts b/desktop/src/main/services/upload.ts index a1103a748..c3efbb9f4 100644 --- a/desktop/src/main/services/upload.ts +++ b/desktop/src/main/services/upload.ts @@ -35,6 +35,10 @@ export const pathOrZipItemSize = async ( const [zipPath, entryName] = pathOrZipItem; const zip = new StreamZip.async({ file: zipPath }); const entry = await zip.entry(entryName); + if (!entry) + throw new Error( + `An entry with name ${entryName} does not exist in the zip file at ${zipPath}`, + ); const size = entry.size; zip.close(); return size; @@ -60,7 +64,7 @@ export const pendingUploads = async (): Promise => { // file, but the dedup logic will kick in at that point so no harm will come // off it. if (allZipItems === undefined) { - const allZipPaths = uploadStatusStore.get("filePaths"); + const allZipPaths = uploadStatusStore.get("filePaths") ?? []; const zipPaths = allZipPaths.filter((f) => existsSync(f)); zipItems = []; for (const zip of zipPaths) @@ -83,7 +87,7 @@ export const setPendingUploads = async (pendingUploads: PendingUploads) => export const markUploadedFiles = async (paths: string[]) => { const existing = uploadStatusStore.get("filePaths"); - const updated = existing.filter((p) => !paths.includes(p)); + const updated = existing?.filter((p) => !paths.includes(p)); uploadStatusStore.set("filePaths", updated); }; @@ -91,7 +95,7 @@ export const markUploadedZipItems = async ( items: [zipPath: string, entryName: string][], ) => { const existing = uploadStatusStore.get("zipItems"); - const updated = existing.filter( + const updated = existing?.filter( (z) => !items.some((e) => z[0] == e[0] && z[1] == e[1]), ); uploadStatusStore.set("zipItems", updated); diff --git a/desktop/src/types/ipc.ts b/desktop/src/types/ipc.ts index c02ed1726..c8c7ef6b4 100644 --- a/desktop/src/types/ipc.ts +++ b/desktop/src/types/ipc.ts @@ -28,7 +28,7 @@ export interface FolderWatchSyncedFile { export type ZipItem = [zipPath: string, entryName: string]; export interface PendingUploads { - collectionName: string; + collectionName?: string; filePaths: string[]; zipItems: ZipItem[]; } diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx index 90b5f94b4..0d310d7f9 100644 --- a/web/apps/photos/src/components/Upload/Uploader.tsx +++ b/web/apps/photos/src/components/Upload/Uploader.tsx @@ -930,9 +930,5 @@ export const setPendingUploads = async ( } } - await electron.setPendingUploads({ - collectionName, - filePaths, - zipItems: zipItems, - }); + await electron.setPendingUploads({ collectionName, filePaths, zipItems }); }; diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index d97a7e564..895dfcf70 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -650,8 +650,10 @@ export interface PendingUploads { * This is name of the collection (when uploading to a singular collection) * or the root collection (when uploading to separate * albums) to which we * these uploads are meant to go to. See {@link CollectionMapping}. + * + * It will not be set if we're just uploading standalone files. */ - collectionName: string; + collectionName?: string; /** * Paths of regular files that need to be uploaded. */ From 612d8682b5151b95ab6e345691598f7bceca5773 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 14:19:25 +0530 Subject: [PATCH 048/367] strict --- desktop/src/main/services/watch.ts | 19 +++++++++---------- desktop/src/main/stream.ts | 7 ++++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/desktop/src/main/services/watch.ts b/desktop/src/main/services/watch.ts index 4d7b89e46..e8e380390 100644 --- a/desktop/src/main/services/watch.ts +++ b/desktop/src/main/services/watch.ts @@ -47,16 +47,15 @@ const eventData = (path: string): [string, FolderWatch] => { return [path, watch]; }; -export const watchGet = (watcher: FSWatcher) => { - const [valid, deleted] = folderWatches().reduce( - ([valid, deleted], watch) => { - (fsIsDir(watch.folderPath) ? valid : deleted).push(watch); - return [valid, deleted]; - }, - [[], []], - ); - if (deleted.length) { - for (const watch of deleted) watchRemove(watcher, watch.folderPath); +export const watchGet = async (watcher: FSWatcher): Promise => { + const valid: FolderWatch[] = []; + const deletedPaths: string[] = []; + for (const watch of folderWatches()) { + if (await fsIsDir(watch.folderPath)) valid.push(watch); + else deletedPaths.push(watch.folderPath); + } + if (deletedPaths.length) { + await Promise.all(deletedPaths.map((p) => watchRemove(watcher, p))); setFolderWatches(valid); } return valid; diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index be84c022f..26021fdf1 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -8,6 +8,7 @@ import fs from "node:fs/promises"; import { Readable } from "node:stream"; import { pathToFileURL } from "node:url"; import log from "./log"; +import { ensure } from "./utils/common"; /** * Register a protocol handler that we use for streaming large files between the @@ -89,7 +90,7 @@ const handleRead = async (path: string) => { return res; } catch (e) { log.error(`Failed to read stream at ${path}`, e); - return new Response(`Failed to read stream: ${e.message}`, { + return new Response(`Failed to read stream: ${String(e)}`, { status: 500, }); } @@ -133,11 +134,11 @@ const handleReadZip = async (zipPath: string, entryName: string) => { const handleWrite = async (path: string, request: Request) => { try { - await writeStream(path, request.body); + await writeStream(path, ensure(request.body)); return new Response("", { status: 200 }); } catch (e) { log.error(`Failed to write stream to ${path}`, e); - return new Response(`Failed to write stream: ${e.message}`, { + return new Response(`Failed to write stream: ${String(e)}`, { status: 500, }); } From 24fc486721081d4a47d15fe4291fa482df7b64a4 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 30 Apr 2024 14:29:12 +0530 Subject: [PATCH 049/367] Pull out the code to download a file to gallery --- mobile/lib/ui/viewer/file/file_app_bar.dart | 93 +----------------- mobile/lib/utils/file_download_util.dart | 100 ++++++++++++++++++++ 2 files changed, 102 insertions(+), 91 deletions(-) diff --git a/mobile/lib/ui/viewer/file/file_app_bar.dart b/mobile/lib/ui/viewer/file/file_app_bar.dart index e029aeb89..f0339f368 100644 --- a/mobile/lib/ui/viewer/file/file_app_bar.dart +++ b/mobile/lib/ui/viewer/file/file_app_bar.dart @@ -4,30 +4,23 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:media_extension/media_extension.dart'; -import 'package:path/path.dart' as file_path; -import 'package:photo_manager/photo_manager.dart'; -import 'package:photos/core/event_bus.dart'; -import 'package:photos/db/files_db.dart'; -import 'package:photos/events/local_photos_updated_event.dart'; import "package:photos/generated/l10n.dart"; import "package:photos/l10n/l10n.dart"; import "package:photos/models/file/extensions/file_props.dart"; import 'package:photos/models/file/file.dart'; import 'package:photos/models/file/file_type.dart'; import 'package:photos/models/file/trash_file.dart'; -import 'package:photos/models/ignored_file.dart'; import "package:photos/models/metadata/common_keys.dart"; import 'package:photos/models/selected_files.dart'; import "package:photos/service_locator.dart"; import 'package:photos/services/collections_service.dart'; import 'package:photos/services/hidden_service.dart'; -import 'package:photos/services/ignored_files_service.dart'; -import 'package:photos/services/local_sync_service.dart'; import 'package:photos/ui/collections/collection_action_sheet.dart'; import 'package:photos/ui/viewer/file/custom_app_bar.dart'; import "package:photos/ui/viewer/file_details/favorite_widget.dart"; import "package:photos/ui/viewer/file_details/upload_icon_widget.dart"; import 'package:photos/utils/dialog_util.dart'; +import "package:photos/utils/file_download_util.dart"; import 'package:photos/utils/file_util.dart'; import "package:photos/utils/magic_util.dart"; import 'package:photos/utils/toast_util.dart'; @@ -330,98 +323,16 @@ class FileAppBarState extends State { ); await dialog.show(); try { - final FileType type = file.fileType; - final bool downloadLivePhotoOnDroid = - type == FileType.livePhoto && Platform.isAndroid; - AssetEntity? savedAsset; - final File? fileToSave = await getFile(file); - //Disabling notifications for assets changing to insert the file into - //files db before triggering a sync. - await PhotoManager.stopChangeNotify(); - if (type == FileType.image) { - savedAsset = await PhotoManager.editor - .saveImageWithPath(fileToSave!.path, title: file.title!); - } else if (type == FileType.video) { - savedAsset = await PhotoManager.editor - .saveVideo(fileToSave!, title: file.title!); - } else if (type == FileType.livePhoto) { - final File? liveVideoFile = - await getFileFromServer(file, liveVideo: true); - if (liveVideoFile == null) { - throw AssertionError("Live video can not be null"); - } - if (downloadLivePhotoOnDroid) { - await _saveLivePhotoOnDroid(fileToSave!, liveVideoFile, file); - } else { - savedAsset = await PhotoManager.editor.darwin.saveLivePhoto( - imageFile: fileToSave!, - videoFile: liveVideoFile, - title: file.title!, - ); - } - } - - if (savedAsset != null) { - file.localID = savedAsset.id; - await FilesDB.instance.insert(file); - Bus.instance.fire( - LocalPhotosUpdatedEvent( - [file], - source: "download", - ), - ); - } else if (!downloadLivePhotoOnDroid && savedAsset == null) { - _logger.severe('Failed to save assert of type $type'); - } + await downloadToGallery(file); showToast(context, S.of(context).fileSavedToGallery); await dialog.hide(); } catch (e) { _logger.warning("Failed to save file", e); await dialog.hide(); await showGenericErrorDialog(context: context, error: e); - } finally { - await PhotoManager.startChangeNotify(); - LocalSyncService.instance.checkAndSync().ignore(); } } - Future _saveLivePhotoOnDroid( - File image, - File video, - EnteFile enteFile, - ) async { - debugPrint("Downloading LivePhoto on Droid"); - AssetEntity? savedAsset = await (PhotoManager.editor - .saveImageWithPath(image.path, title: enteFile.title!)); - if (savedAsset == null) { - throw Exception("Failed to save image of live photo"); - } - IgnoredFile ignoreVideoFile = IgnoredFile( - savedAsset.id, - savedAsset.title ?? '', - savedAsset.relativePath ?? 'remoteDownload', - "remoteDownload", - ); - await IgnoredFilesService.instance.cacheAndInsert([ignoreVideoFile]); - final videoTitle = file_path.basenameWithoutExtension(enteFile.title!) + - file_path.extension(video.path); - savedAsset = (await (PhotoManager.editor.saveVideo( - video, - title: videoTitle, - ))); - if (savedAsset == null) { - throw Exception("Failed to save video of live photo"); - } - - ignoreVideoFile = IgnoredFile( - savedAsset.id, - savedAsset.title ?? videoTitle, - savedAsset.relativePath ?? 'remoteDownload', - "remoteDownload", - ); - await IgnoredFilesService.instance.cacheAndInsert([ignoreVideoFile]); - } - Future _setAs(EnteFile file) async { final dialog = createProgressDialog(context, S.of(context).pleaseWait); await dialog.show(); diff --git a/mobile/lib/utils/file_download_util.dart b/mobile/lib/utils/file_download_util.dart index f99a43527..b0a1d20d5 100644 --- a/mobile/lib/utils/file_download_util.dart +++ b/mobile/lib/utils/file_download_util.dart @@ -4,14 +4,23 @@ import "package:computer/computer.dart"; import 'package:dio/dio.dart'; import "package:flutter/foundation.dart"; import 'package:logging/logging.dart'; +import 'package:path/path.dart' as file_path; +import "package:photo_manager/photo_manager.dart"; import 'package:photos/core/configuration.dart'; +import "package:photos/core/event_bus.dart"; import 'package:photos/core/network/network.dart'; +import "package:photos/db/files_db.dart"; +import "package:photos/events/local_photos_updated_event.dart"; import 'package:photos/models/file/file.dart'; import "package:photos/models/file/file_type.dart"; +import "package:photos/models/ignored_file.dart"; import 'package:photos/services/collections_service.dart'; +import "package:photos/services/ignored_files_service.dart"; +import "package:photos/services/local_sync_service.dart"; import 'package:photos/utils/crypto_util.dart'; import "package:photos/utils/data_util.dart"; import "package:photos/utils/fake_progress.dart"; +import "package:photos/utils/file_util.dart"; final _logger = Logger("file_download_util"); @@ -115,6 +124,97 @@ Future getFileKeyUsingBgWorker(EnteFile file) async { ); } +Future downloadToGallery(EnteFile file) async { + try { + final FileType type = file.fileType; + final bool downloadLivePhotoOnDroid = + type == FileType.livePhoto && Platform.isAndroid; + AssetEntity? savedAsset; + final File? fileToSave = await getFile(file); + //Disabling notifications for assets changing to insert the file into + //files db before triggering a sync. + await PhotoManager.stopChangeNotify(); + if (type == FileType.image) { + savedAsset = await PhotoManager.editor + .saveImageWithPath(fileToSave!.path, title: file.title!); + } else if (type == FileType.video) { + savedAsset = + await PhotoManager.editor.saveVideo(fileToSave!, title: file.title!); + } else if (type == FileType.livePhoto) { + final File? liveVideoFile = + await getFileFromServer(file, liveVideo: true); + if (liveVideoFile == null) { + throw AssertionError("Live video can not be null"); + } + if (downloadLivePhotoOnDroid) { + await _saveLivePhotoOnDroid(fileToSave!, liveVideoFile, file); + } else { + savedAsset = await PhotoManager.editor.darwin.saveLivePhoto( + imageFile: fileToSave!, + videoFile: liveVideoFile, + title: file.title!, + ); + } + } + + if (savedAsset != null) { + file.localID = savedAsset.id; + await FilesDB.instance.insert(file); + Bus.instance.fire( + LocalPhotosUpdatedEvent( + [file], + source: "download", + ), + ); + } else if (!downloadLivePhotoOnDroid && savedAsset == null) { + _logger.severe('Failed to save assert of type $type'); + } + } catch (e) { + _logger.warning("Failed to save file", e); + rethrow; + } finally { + await PhotoManager.startChangeNotify(); + LocalSyncService.instance.checkAndSync().ignore(); + } +} + +Future _saveLivePhotoOnDroid( + File image, + File video, + EnteFile enteFile, +) async { + debugPrint("Downloading LivePhoto on Droid"); + AssetEntity? savedAsset = await (PhotoManager.editor + .saveImageWithPath(image.path, title: enteFile.title!)); + if (savedAsset == null) { + throw Exception("Failed to save image of live photo"); + } + IgnoredFile ignoreVideoFile = IgnoredFile( + savedAsset.id, + savedAsset.title ?? '', + savedAsset.relativePath ?? 'remoteDownload', + "remoteDownload", + ); + await IgnoredFilesService.instance.cacheAndInsert([ignoreVideoFile]); + final videoTitle = file_path.basenameWithoutExtension(enteFile.title!) + + file_path.extension(video.path); + savedAsset = (await (PhotoManager.editor.saveVideo( + video, + title: videoTitle, + ))); + if (savedAsset == null) { + throw Exception("Failed to save video of live photo"); + } + + ignoreVideoFile = IgnoredFile( + savedAsset.id, + savedAsset.title ?? videoTitle, + savedAsset.relativePath ?? 'remoteDownload', + "remoteDownload", + ); + await IgnoredFilesService.instance.cacheAndInsert([ignoreVideoFile]); +} + Uint8List _decryptFileKey(Map args) { final encryptedKey = CryptoUtil.base642bin(args["encryptedKey"]); final nonce = CryptoUtil.base642bin(args["keyDecryptionNonce"]); From 5681f1496765c46a484e72899ca90b21c6d6a984 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 14:31:06 +0530 Subject: [PATCH 050/367] Clarify that entry names are guaranteed to be posixy --- web/packages/next/types/ipc.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index 895dfcf70..d5007ea80 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -632,6 +632,19 @@ export interface FolderWatchSyncedFile { * The name of the entry is not just the file name, but rather is the full path * of the file within the zip. That is, each entry name uniquely identifies a * particular file within the given zip. + * + * When `entryName` is a path within a nested directory, it is guaranteed to use + * the POSIX path separator ("/") since that is the path separator required by + * the ZIP format itself + * + * > 4.4.17.1 The name of the file, with optional relative path. + * > + * > The path stored MUST NOT contain a drive or device letter, or a leading + * > slash. All slashes MUST be forward slashes '/' as opposed to backwards + * > slashes '\' for compatibility with Amiga and UNIX file systems etc. If + * > input came from standard input, there is no file name field. + * > + * > https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT */ export type ZipItem = [zipPath: string, entryName: string]; From ae057da331e938248c49c9eb00b06cb5fc8eae4d Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 30 Apr 2024 14:37:15 +0530 Subject: [PATCH 051/367] Remove redundant code --- .../file_selection_actions_widget.dart | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart b/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart index e2e29e021..9de4c27e6 100644 --- a/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart +++ b/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart @@ -379,41 +379,36 @@ class _FileSelectionActionsWidgetState ), ); - if (items.isNotEmpty) { - final scrollController = ScrollController(); - // h4ck: https://github.com/flutter/flutter/issues/57920#issuecomment-893970066 - return MediaQuery( - data: MediaQuery.of(context).removePadding(removeBottom: true), - child: SafeArea( - child: Scrollbar( - radius: const Radius.circular(1), - thickness: 2, - controller: scrollController, - thumbVisibility: true, - child: SingleChildScrollView( - physics: const BouncingScrollPhysics( - decelerationRate: ScrollDecelerationRate.fast, - ), - scrollDirection: Axis.horizontal, - child: Container( - padding: const EdgeInsets.only(bottom: 24), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(width: 4), - ...items, - const SizedBox(width: 4), - ], - ), + final scrollController = ScrollController(); + // h4ck: https://github.com/flutter/flutter/issues/57920#issuecomment-893970066 + return MediaQuery( + data: MediaQuery.of(context).removePadding(removeBottom: true), + child: SafeArea( + child: Scrollbar( + radius: const Radius.circular(1), + thickness: 2, + controller: scrollController, + thumbVisibility: true, + child: SingleChildScrollView( + physics: const BouncingScrollPhysics( + decelerationRate: ScrollDecelerationRate.fast, + ), + scrollDirection: Axis.horizontal, + child: Container( + padding: const EdgeInsets.only(bottom: 24), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(width: 4), + ...items, + const SizedBox(width: 4), + ], ), ), ), ), - ); - } else { - // TODO: Return "Select All" here - return const SizedBox.shrink(); - } + ), + ); } Future _moveFiles() async { From 9cc730e6a9e1a6bdb5e6dee77e0cde939e2e1705 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 14:56:13 +0530 Subject: [PATCH 052/367] more posix --- web/apps/photos/src/components/Upload/Uploader.tsx | 4 +++- web/packages/next/types/ipc.ts | 2 +- web/packages/shared/hooks/useFileInput.tsx | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx index 0d310d7f9..cfd674e3f 100644 --- a/web/apps/photos/src/components/Upload/Uploader.tsx +++ b/web/apps/photos/src/components/Upload/Uploader.tsx @@ -324,10 +324,12 @@ export default function Uploader({ // Trigger an upload when any of the dependencies change. useEffect(() => { const allItemAndPaths = [ - // See: [Note: webkitRelativePath] + // See: [Note: webkitRelativePath]. In particular, they use POSIX + // separators. webFiles.map((f) => [f, f.webkitRelativePath ?? f.name]), desktopFiles.map((fp) => [fp, fp.path]), desktopFilePaths.map((p) => [p, p]), + // ze[1], the entry name, uses POSIX separators. desktopZipItems.map((ze) => [ze, ze[1]]), ].flat() as [UploadItem, string][]; diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index d5007ea80..c85106241 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -641,7 +641,7 @@ export interface FolderWatchSyncedFile { * > * > The path stored MUST NOT contain a drive or device letter, or a leading * > slash. All slashes MUST be forward slashes '/' as opposed to backwards - * > slashes '\' for compatibility with Amiga and UNIX file systems etc. If + * > slashes '\' for compatibility with Amiga and UNIX file systems etc. If * > input came from standard input, there is no file name field. * > * > https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT diff --git a/web/packages/shared/hooks/useFileInput.tsx b/web/packages/shared/hooks/useFileInput.tsx index ae1dfcab0..71f027cef 100644 --- a/web/packages/shared/hooks/useFileInput.tsx +++ b/web/packages/shared/hooks/useFileInput.tsx @@ -60,6 +60,10 @@ export default function useFileInput({ // containing the relative path to the selected directory. // // https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory + // + // These paths use the POSIX path separator ("/"). + // https://stackoverflow.com/questions/62806233/when-using-webkitrelativepath-is-the-path-separator-operating-system-specific + // const directoryOpts = directory ? { directory: "", webkitdirectory: "" } : {}; From 824e73f150b0b02e419e737f499fa41abc859c9a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 15:30:57 +0530 Subject: [PATCH 053/367] strict --- desktop/src/main/stream.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index 26021fdf1..7b773baed 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -102,11 +102,23 @@ const handleReadZip = async (zipPath: string, entryName: string) => { const entry = await zip.entry(entryName); if (!entry) return new Response("", { status: 404 }); + // This returns an "old style" NodeJS.ReadableStream. const stream = await zip.stream(entry); + // Convert it into a new style NodeJS.Readable. + const nodeReadable = new Readable().wrap(stream); + // Then convert it into a Web stream. + const webReadableStreamAny = Readable.toWeb(nodeReadable); + // However, we get a ReadableStream now. This doesn't go into the + // `BodyInit` expected by the Response constructor, which wants a + // ReadableStream. Force a cast. + const webReadableStream = + webReadableStreamAny as ReadableStream; - // TODO(MR): when to call zip.close() + // Close the zip handle when the underlying stream closes. + // TODO(MR): Verify + stream.on("end", () => zip.close()); - return new Response(Readable.toWeb(new Readable(stream)), { + return new Response(webReadableStream, { headers: { // We don't know the exact type, but it doesn't really matter, // just set it to a generic binary content-type so that the From 2f3a2421f796c481e6bfe9f260963c09f26f5585 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 15:44:16 +0530 Subject: [PATCH 054/367] Strict --- desktop/src/main/services/ml-clip.ts | 70 +++++++++++------------ desktop/src/main/services/ml-face.ts | 3 +- desktop/src/main/stores/upload-status.ts | 8 +-- desktop/src/thirdparty/clip-bpe-ts/mod.ts | 3 + desktop/src/types/ipc.ts | 2 +- desktop/tsconfig.json | 31 +++++----- 6 files changed, 58 insertions(+), 59 deletions(-) diff --git a/desktop/src/main/services/ml-clip.ts b/desktop/src/main/services/ml-clip.ts index 149f634b5..451cdcb09 100644 --- a/desktop/src/main/services/ml-clip.ts +++ b/desktop/src/main/services/ml-clip.ts @@ -45,7 +45,7 @@ const clipImageEmbedding_ = async (jpegFilePath: string) => { `onnx/clip image embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`, ); /* Need these model specific casts to type the result */ - const imageEmbedding = results["output"].data as Float32Array; + const imageEmbedding = ensure(results["output"]).data as Float32Array; return normalizeEmbedding(imageEmbedding); }; @@ -56,19 +56,19 @@ const getRGBData = async (jpegFilePath: string) => { formatAsRGBA: false, }); - const nx: number = rawImageData.width; - const ny: number = rawImageData.height; - const inputImage: Uint8Array = rawImageData.data; + const nx = rawImageData.width; + const ny = rawImageData.height; + const inputImage = rawImageData.data; - const nx2: number = 224; - const ny2: number = 224; - const totalSize: number = 3 * nx2 * ny2; + const nx2 = 224; + const ny2 = 224; + const totalSize = 3 * nx2 * ny2; const result: number[] = Array(totalSize).fill(0); - const scale: number = Math.max(nx, ny) / 224; + const scale = Math.max(nx, ny) / 224; - const nx3: number = Math.round(nx / scale); - const ny3: number = Math.round(ny / scale); + const nx3 = Math.round(nx / scale); + const ny3 = Math.round(ny / scale); const mean: number[] = [0.48145466, 0.4578275, 0.40821073]; const std: number[] = [0.26862954, 0.26130258, 0.27577711]; @@ -77,40 +77,40 @@ const getRGBData = async (jpegFilePath: string) => { for (let x = 0; x < nx3; x++) { for (let c = 0; c < 3; c++) { // Linear interpolation - const sx: number = (x + 0.5) * scale - 0.5; - const sy: number = (y + 0.5) * scale - 0.5; + const sx = (x + 0.5) * scale - 0.5; + const sy = (y + 0.5) * scale - 0.5; - const x0: number = Math.max(0, Math.floor(sx)); - const y0: number = Math.max(0, Math.floor(sy)); + const x0 = Math.max(0, Math.floor(sx)); + const y0 = Math.max(0, Math.floor(sy)); - const x1: number = Math.min(x0 + 1, nx - 1); - const y1: number = Math.min(y0 + 1, ny - 1); + const x1 = Math.min(x0 + 1, nx - 1); + const y1 = Math.min(y0 + 1, ny - 1); - const dx: number = sx - x0; - const dy: number = sy - y0; + const dx = sx - x0; + const dy = sy - y0; - const j00: number = 3 * (y0 * nx + x0) + c; - const j01: number = 3 * (y0 * nx + x1) + c; - const j10: number = 3 * (y1 * nx + x0) + c; - const j11: number = 3 * (y1 * nx + x1) + c; + const j00 = 3 * (y0 * nx + x0) + c; + const j01 = 3 * (y0 * nx + x1) + c; + const j10 = 3 * (y1 * nx + x0) + c; + const j11 = 3 * (y1 * nx + x1) + c; - const v00: number = inputImage[j00]; - const v01: number = inputImage[j01]; - const v10: number = inputImage[j10]; - const v11: number = inputImage[j11]; + const v00 = inputImage[j00] ?? 0; + const v01 = inputImage[j01] ?? 0; + const v10 = inputImage[j10] ?? 0; + const v11 = inputImage[j11] ?? 0; - const v0: number = v00 * (1 - dx) + v01 * dx; - const v1: number = v10 * (1 - dx) + v11 * dx; + const v0 = v00 * (1 - dx) + v01 * dx; + const v1 = v10 * (1 - dx) + v11 * dx; - const v: number = v0 * (1 - dy) + v1 * dy; + const v = v0 * (1 - dy) + v1 * dy; - const v2: number = Math.min(Math.max(Math.round(v), 0), 255); + const v2 = Math.min(Math.max(Math.round(v), 0), 255); // createTensorWithDataList is dumb compared to reshape and // hence has to be given with one channel after another - const i: number = y * nx3 + x + (c % 3) * 224 * 224; + const i = y * nx3 + x + (c % 3) * 224 * 224; - result[i] = (v2 / 255 - mean[c]) / std[c]; + result[i] = (v2 / 255 - (mean[c] ?? 0)) / (std[c] ?? 1); } } } @@ -121,11 +121,11 @@ const getRGBData = async (jpegFilePath: string) => { const normalizeEmbedding = (embedding: Float32Array) => { let normalization = 0; for (let index = 0; index < embedding.length; index++) { - normalization += embedding[index] * embedding[index]; + normalization += ensure(embedding[index]) * ensure(embedding[index]); } const sqrtNormalization = Math.sqrt(normalization); for (let index = 0; index < embedding.length; index++) { - embedding[index] = embedding[index] / sqrtNormalization; + embedding[index] = ensure(embedding[index]) / sqrtNormalization; } return embedding; }; @@ -168,6 +168,6 @@ export const clipTextEmbeddingIfAvailable = async (text: string) => { () => `onnx/clip text embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`, ); - const textEmbedding = results["output"].data as Float32Array; + const textEmbedding = ensure(results["output"]).data as Float32Array; return normalizeEmbedding(textEmbedding); }; diff --git a/desktop/src/main/services/ml-face.ts b/desktop/src/main/services/ml-face.ts index 2309d193c..e4e43a4b0 100644 --- a/desktop/src/main/services/ml-face.ts +++ b/desktop/src/main/services/ml-face.ts @@ -8,6 +8,7 @@ */ import * as ort from "onnxruntime-node"; import log from "../log"; +import { ensure } from "../utils/common"; import { makeCachedInferenceSession } from "./ml"; const cachedFaceDetectionSession = makeCachedInferenceSession( @@ -23,7 +24,7 @@ export const detectFaces = async (input: Float32Array) => { }; const results = await session.run(feeds); log.debug(() => `onnx/yolo face detection took ${Date.now() - t} ms`); - return results["output"].data; + return ensure(results["output"]).data; }; const cachedFaceEmbeddingSession = makeCachedInferenceSession( diff --git a/desktop/src/main/stores/upload-status.ts b/desktop/src/main/stores/upload-status.ts index 472f38a7f..f098e9fc5 100644 --- a/desktop/src/main/stores/upload-status.ts +++ b/desktop/src/main/stores/upload-status.ts @@ -6,24 +6,24 @@ export interface UploadStatusStore { * * Not all pending uploads will have an associated collection. */ - collectionName?: string; + collectionName: string | undefined; /** * Paths to regular files that are pending upload. * * This should generally be present, albeit empty, but it is marked optional * in sympathy with its siblings. */ - filePaths?: string[]; + filePaths: string[] | undefined; /** * Each item is the path to a zip file and the name of an entry within it. * * This is marked optional since legacy stores will not have it. */ - zipItems?: [zipPath: string, entryName: string][]; + zipItems: [zipPath: string, entryName: string][] | undefined; /** * @deprecated Legacy paths to zip files, now subsumed into zipItems. */ - zipPaths?: string[]; + zipPaths: string[] | undefined; } const uploadStatusSchema: Schema = { diff --git a/desktop/src/thirdparty/clip-bpe-ts/mod.ts b/desktop/src/thirdparty/clip-bpe-ts/mod.ts index 6cdf246f7..b59b762e7 100644 --- a/desktop/src/thirdparty/clip-bpe-ts/mod.ts +++ b/desktop/src/thirdparty/clip-bpe-ts/mod.ts @@ -410,6 +410,7 @@ export default class { newWord.push(first + second); i += 2; } else { + // @ts-expect-error "Array indexing can return undefined but not modifying thirdparty code" newWord.push(word[i]); i += 1; } @@ -434,6 +435,7 @@ export default class { .map((b) => this.byteEncoder[b.charCodeAt(0) as number]) .join(""); bpeTokens.push( + // @ts-expect-error "Array indexing can return undefined but not modifying thirdparty code" ...this.bpe(token) .split(" ") .map((bpeToken: string) => this.encoder[bpeToken]), @@ -458,6 +460,7 @@ export default class { .join(""); text = [...text] .map((c) => this.byteDecoder[c]) + // @ts-expect-error "Array indexing can return undefined but not modifying thirdparty code" .map((v) => String.fromCharCode(v)) .join("") .replace(/<\/w>/g, " "); diff --git a/desktop/src/types/ipc.ts b/desktop/src/types/ipc.ts index c8c7ef6b4..f4985bfc7 100644 --- a/desktop/src/types/ipc.ts +++ b/desktop/src/types/ipc.ts @@ -28,7 +28,7 @@ export interface FolderWatchSyncedFile { export type ZipItem = [zipPath: string, entryName: string]; export interface PendingUploads { - collectionName?: string; + collectionName: string | undefined; filePaths: string[]; zipItems: ZipItem[]; } diff --git a/desktop/tsconfig.json b/desktop/tsconfig.json index 16946bf3f..654158a50 100644 --- a/desktop/tsconfig.json +++ b/desktop/tsconfig.json @@ -41,33 +41,28 @@ "target": "es2022", "module": "node16", + /* Emit the generated JS into `app/` */ + "outDir": "app", + /* Enable various workarounds to play better with CJS libraries */ "esModuleInterop": true, /* Speed things up by not type checking `node_modules` */ "skipLibCheck": true, - /* Emit the generated JS into `app/` */ - "outDir": "app", - - /* Temporary overrides to get things to compile with the older config */ - // "strict": false, - "noImplicitAny": true, - - /* Below is the state we want */ - /* Enable these one by one */ - "strict": true, - /* Require the `type` modifier when importing types */ - // "verbatimModuleSyntax": true + /* We want this, but it causes "ESM syntax is not allowed in a CommonJS + module when 'verbatimModuleSyntax' is enabled" currently */ + /* "verbatimModuleSyntax": true, */ + "strict": true, /* Stricter than strict */ - // "noImplicitReturns": true, - // "noUnusedParameters": true, - // "noUnusedLocals": true, - // "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noUnusedParameters": true, + "noUnusedLocals": true, + "noFallthroughCasesInSwitch": true, /* e.g. makes array indexing returns undefined */ - // "noUncheckedIndexedAccess": true, - // "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true }, /* Transpile all `.ts` files in `src/` */ "include": ["src/**/*.ts"] From 51ffaa4a904551412bd2a82063c722246fe89d80 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 15:59:10 +0530 Subject: [PATCH 055/367] Preempt --- desktop/docs/dependencies.md | 3 +++ desktop/package.json | 1 + desktop/src/main/stream.ts | 1 + desktop/tsconfig.json | 50 +++++++----------------------------- desktop/yarn.lock | 5 ++++ 5 files changed, 19 insertions(+), 41 deletions(-) diff --git a/desktop/docs/dependencies.md b/desktop/docs/dependencies.md index 5c6b222b0..605235703 100644 --- a/desktop/docs/dependencies.md +++ b/desktop/docs/dependencies.md @@ -90,6 +90,9 @@ Some extra ones specific to the code here are: Unix commands in our `package.json` scripts. This allows us to use the same commands (like `ln`) across different platforms like Linux and Windows. +- [@tsconfig/recommended](https://github.com/tsconfig/bases) gives us a base + tsconfig for the Node.js version that our current Electron version uses. + ## Functionality ### Format conversion diff --git a/desktop/package.json b/desktop/package.json index 69d54f75b..509ee8583 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -34,6 +34,7 @@ "onnxruntime-node": "^1.17" }, "devDependencies": { + "@tsconfig/node20": "^20.1.4", "@types/auto-launch": "^5.0", "@types/ffmpeg-static": "^3.0", "@typescript-eslint/eslint-plugin": "^7", diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index 7b773baed..15715c550 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -6,6 +6,7 @@ import StreamZip from "node-stream-zip"; import { createWriteStream, existsSync } from "node:fs"; import fs from "node:fs/promises"; import { Readable } from "node:stream"; +import { ReadableStream } from "node:stream/web"; import { pathToFileURL } from "node:url"; import log from "./log"; import { ensure } from "./utils/common"; diff --git a/desktop/tsconfig.json b/desktop/tsconfig.json index 654158a50..7806cd93a 100644 --- a/desktop/tsconfig.json +++ b/desktop/tsconfig.json @@ -3,52 +3,20 @@ into JavaScript that'll then be loaded and run by the main (node) process of our Electron app. */ + /* + * Recommended target, lib and other settings for code running in the + * version of Node.js bundled with Electron. + * + * Currently, with Electron 30, this is Node.js 20.11.1. + * https://www.electronjs.org/blog/electron-30-0 + */ + "extends": "@tsconfig/node20/tsconfig.json", + /* TSConfig docs: https://aka.ms/tsconfig.json */ - "compilerOptions": { - /* Recommended target, lib and other settings for code running in the - version of Node.js bundled with Electron. - - Currently, with Electron 29, this is Node.js 20.9 - https://www.electronjs.org/blog/electron-29-0 - - Note that we cannot do - - "extends": "@tsconfig/node20/tsconfig.json", - - because that sets "lib": ["es2023"]. However (and I don't fully - understand what's going on here), that breaks our compilation since - tsc can then not find type definitions of things like ReadableStream. - - Adding "dom" to "lib" (e.g. `"lib": ["es2023", "dom"]`) fixes the - issue, but that doesn't sound correct - the main Electron process - isn't running in a browser context. - - It is possible that we're using some of the types incorrectly. For - now, we just omit the "lib" definition and rely on the defaults for - the "target" we've chosen. This is also what the current - electron-forge starter does: - - yarn create electron-app electron-forge-starter -- --template=webpack-typescript - - Enhancement: Can revisit this later. - - Refs: - - https://github.com/electron/electron/issues/27092 - - https://github.com/electron/electron/issues/16146 - */ - - "target": "es2022", - "module": "node16", - /* Emit the generated JS into `app/` */ "outDir": "app", - /* Enable various workarounds to play better with CJS libraries */ - "esModuleInterop": true, - /* Speed things up by not type checking `node_modules` */ - "skipLibCheck": true, - /* Require the `type` modifier when importing types */ /* We want this, but it causes "ESM syntax is not allowed in a CommonJS module when 'verbatimModuleSyntax' is enabled" currently */ diff --git a/desktop/yarn.lock b/desktop/yarn.lock index a5b86f1eb..bf9057d47 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -246,6 +246,11 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@tsconfig/node20@^20.1.4": + version "20.1.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node20/-/node20-20.1.4.tgz#3457d42eddf12d3bde3976186ab0cd22b85df928" + integrity sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg== + "@types/auto-launch@^5.0": version "5.0.5" resolved "https://registry.yarnpkg.com/@types/auto-launch/-/auto-launch-5.0.5.tgz#439ed36aaaea501e2e2cfbddd8a20c366c34863b" From 9b996ff353d34e8bde539993abfb8d54511a827f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:03:52 +0530 Subject: [PATCH 056/367] Lint+ --- desktop/.eslintrc.js | 2 +- desktop/src/thirdparty/clip-bpe-ts/mod.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/desktop/.eslintrc.js b/desktop/.eslintrc.js index 977071a27..d9c13a442 100644 --- a/desktop/.eslintrc.js +++ b/desktop/.eslintrc.js @@ -4,7 +4,7 @@ module.exports = { "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", /* What we really want eventually */ - // "plugin:@typescript-eslint/strict-type-checked", + "plugin:@typescript-eslint/strict-type-checked", // "plugin:@typescript-eslint/stylistic-type-checked", ], plugins: ["@typescript-eslint"], diff --git a/desktop/src/thirdparty/clip-bpe-ts/mod.ts b/desktop/src/thirdparty/clip-bpe-ts/mod.ts index b59b762e7..4d00eef0e 100644 --- a/desktop/src/thirdparty/clip-bpe-ts/mod.ts +++ b/desktop/src/thirdparty/clip-bpe-ts/mod.ts @@ -1,3 +1,5 @@ +/* eslint-disable */ + import * as htmlEntities from "html-entities"; import bpeVocabData from "./bpe_simple_vocab_16e6"; // import ftfy from "https://deno.land/x/ftfy_pyodide@v0.1.1/mod.js"; From 872245cf0e4743fb2f1658e98d00707c669d53ca Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 30 Apr 2024 16:04:32 +0530 Subject: [PATCH 057/367] Update package for icons --- mobile/pubspec.lock | 4 ++-- mobile/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 0610e4588..4dd40c9a0 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -342,10 +342,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" dart_style: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 9c96bc762..3cd8b6e66 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: connectivity_plus: ^6.0.2 cross_file: ^0.3.3 crypto: ^3.0.2 - cupertino_icons: ^1.0.0 + cupertino_icons: ^1.0.8 defer_pointer: ^0.0.2 device_info_plus: ^9.0.3 dio: ^4.0.6 From 6f338867e4ae9f3bcf6c3efd6d4f3952771fcb9c Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 30 Apr 2024 16:04:50 +0530 Subject: [PATCH 058/367] Add log --- mobile/lib/db/files_db.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/lib/db/files_db.dart b/mobile/lib/db/files_db.dart index fce650086..7022100b7 100644 --- a/mobile/lib/db/files_db.dart +++ b/mobile/lib/db/files_db.dart @@ -455,6 +455,7 @@ class FilesDB { } Future insert(EnteFile file) async { + _logger.info("Inserting $file"); final db = await instance.database; return db.insert( filesTable, From db47f8eaf5b819cf50773ecf5cac5ecb0657d243 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 30 Apr 2024 16:05:00 +0530 Subject: [PATCH 059/367] Add copy --- mobile/lib/generated/intl/messages_en.dart | 2 ++ mobile/lib/generated/l10n.dart | 10 ++++++++++ mobile/lib/l10n/intl_en.arb | 1 + 3 files changed, 13 insertions(+) diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index eef309aa5..51e895c9e 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -721,6 +721,8 @@ class MessageLookup extends MessageLookupByLibrary { "filesBackedUpFromDevice": m22, "filesBackedUpInAlbum": m23, "filesDeleted": MessageLookupByLibrary.simpleMessage("Files deleted"), + "filesSavedToGallery": + MessageLookupByLibrary.simpleMessage("Files saved to gallery"), "flip": MessageLookupByLibrary.simpleMessage("Flip"), "forYourMemories": MessageLookupByLibrary.simpleMessage("for your memories"), diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index 3fa9c2209..68f9fff40 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -5945,6 +5945,16 @@ class S { ); } + /// `Files saved to gallery` + String get filesSavedToGallery { + return Intl.message( + 'Files saved to gallery', + name: 'filesSavedToGallery', + desc: '', + args: [], + ); + } + /// `Failed to save file to gallery` String get fileFailedToSaveToGallery { return Intl.message( diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index 7115c6950..97691a938 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -835,6 +835,7 @@ "close": "Close", "setAs": "Set as", "fileSavedToGallery": "File saved to gallery", + "filesSavedToGallery": "Files saved to gallery", "fileFailedToSaveToGallery": "Failed to save file to gallery", "download": "Download", "pressAndHoldToPlayVideo": "Press and hold to play video", From d7bef6cd4d8cc50554675d59137b9945548f212d Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 30 Apr 2024 16:05:06 +0530 Subject: [PATCH 060/367] Log filetype --- mobile/lib/models/file/file.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/models/file/file.dart b/mobile/lib/models/file/file.dart index 2aa5a4558..d96a81e1c 100644 --- a/mobile/lib/models/file/file.dart +++ b/mobile/lib/models/file/file.dart @@ -308,7 +308,7 @@ class EnteFile { @override String toString() { return '''File(generatedID: $generatedID, localID: $localID, title: $title, - uploadedFileId: $uploadedFileID, modificationTime: $modificationTime, + type: $fileType, uploadedFileId: $uploadedFileID, modificationTime: $modificationTime, ownerID: $ownerID, collectionID: $collectionID, updationTime: $updationTime)'''; } From cdddbc4602eeba6993767bbb692a3829e7a5d42b Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 30 Apr 2024 16:05:44 +0530 Subject: [PATCH 061/367] Increase severity of error log --- mobile/lib/utils/file_download_util.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/utils/file_download_util.dart b/mobile/lib/utils/file_download_util.dart index b0a1d20d5..a8847e3fd 100644 --- a/mobile/lib/utils/file_download_util.dart +++ b/mobile/lib/utils/file_download_util.dart @@ -170,7 +170,7 @@ Future downloadToGallery(EnteFile file) async { _logger.severe('Failed to save assert of type $type'); } } catch (e) { - _logger.warning("Failed to save file", e); + _logger.severe("Failed to save file", e); rethrow; } finally { await PhotoManager.startChangeNotify(); From fe5e6c18e8542d1ab7acec756071c619269fdf7f Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 30 Apr 2024 16:06:03 +0530 Subject: [PATCH 062/367] Provide option to download multiple items --- .../file_selection_actions_widget.dart | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart b/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart index 9de4c27e6..a630e3354 100644 --- a/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart +++ b/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart @@ -3,6 +3,7 @@ import "dart:async"; import 'package:fast_base58/fast_base58.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import "package:logging/logging.dart"; import "package:modal_bottom_sheet/modal_bottom_sheet.dart"; import 'package:photos/core/configuration.dart'; import "package:photos/generated/l10n.dart"; @@ -30,6 +31,8 @@ import 'package:photos/ui/sharing/manage_links_widget.dart'; import "package:photos/ui/tools/collage/collage_creator_page.dart"; import "package:photos/ui/viewer/location/update_location_data_widget.dart"; import 'package:photos/utils/delete_file_util.dart'; +import "package:photos/utils/dialog_util.dart"; +import "package:photos/utils/file_download_util.dart"; import 'package:photos/utils/magic_util.dart'; import 'package:photos/utils/navigation_util.dart'; import "package:photos/utils/share_util.dart"; @@ -56,6 +59,7 @@ class FileSelectionActionsWidget extends StatefulWidget { class _FileSelectionActionsWidgetState extends State { + static final _logger = Logger("FileSelectionActionsWidget"); late int currentUserID; late FilesSplit split; late CollectionActions collectionActions; @@ -115,6 +119,8 @@ class _FileSelectionActionsWidgetState !widget.selectedFiles.files.any( (element) => element.fileType == FileType.video, ); + final showDownloadOption = + widget.selectedFiles.files.any((element) => element.localID == null); //To animate adding and removing of [SelectedActionButton], add all items //and set [shouldShow] to false for items that should not be shown and true @@ -367,6 +373,16 @@ class _FileSelectionActionsWidgetState ); } + if (showDownloadOption) { + items.add( + SelectionActionButton( + labelText: S.of(context).download, + icon: Icons.cloud_download_outlined, + onTap: () => _download(widget.selectedFiles.files.toList()), + ), + ); + } + items.add( SelectionActionButton( labelText: S.of(context).share, @@ -642,4 +658,29 @@ class _FileSelectionActionsWidgetState widget.selectedFiles.clearAll(); } } + + Future _download(List files) async { + final dialog = createProgressDialog( + context, + S.of(context).downloading, + isDismissible: true, + ); + await dialog.show(); + try { + final futures = []; + for (final file in files) { + if (file.localID == null) { + futures.add(downloadToGallery(file)); + } + } + await Future.wait(futures); + await dialog.hide(); + widget.selectedFiles.clearAll(); + showToast(context, S.of(context).filesSavedToGallery); + } catch (e) { + _logger.warning("Failed to save files", e); + await dialog.hide(); + await showGenericErrorDialog(context: context, error: e); + } + } } From a9671481d8d06139a5118b0ee1f48dfe6ba6b7a4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:10:56 +0530 Subject: [PATCH 063/367] Allow numbers to be used in template literals --- desktop/.eslintrc.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/desktop/.eslintrc.js b/desktop/.eslintrc.js index d9c13a442..8074990af 100644 --- a/desktop/.eslintrc.js +++ b/desktop/.eslintrc.js @@ -1,5 +1,6 @@ /* eslint-env node */ module.exports = { + root: true, extends: [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", @@ -12,10 +13,17 @@ module.exports = { parserOptions: { project: true, }, - root: true, ignorePatterns: [".eslintrc.js", "app", "out", "dist"], env: { es2022: true, node: true, }, + rules: { + "@typescript-eslint/restrict-template-expressions": [ + "error", + { + allowNumber: true, + }, + ], + }, }; From 755ee4a0c24ccd8447820b28d58753e3c0d85673 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:13:16 +0530 Subject: [PATCH 064/367] hopefully --- desktop/.eslintrc.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/desktop/.eslintrc.js b/desktop/.eslintrc.js index 8074990af..541ba33a9 100644 --- a/desktop/.eslintrc.js +++ b/desktop/.eslintrc.js @@ -25,5 +25,10 @@ module.exports = { allowNumber: true, }, ], + /* Temporary (RIP) */ + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-confusing-void-expression": "off", + "@typescript-eslint/no-misused-promises": "off", + "@typescript-eslint/no-floating-promises": "off", }, }; From 99e72a119f4f1c49d960ae0f8cbdd6d5f98c99a7 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 30 Apr 2024 16:13:48 +0530 Subject: [PATCH 065/367] Update download icon --- mobile/lib/ui/viewer/file/file_app_bar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/ui/viewer/file/file_app_bar.dart b/mobile/lib/ui/viewer/file/file_app_bar.dart index f0339f368..2918924db 100644 --- a/mobile/lib/ui/viewer/file/file_app_bar.dart +++ b/mobile/lib/ui/viewer/file/file_app_bar.dart @@ -158,7 +158,7 @@ class FileAppBarState extends State { Icon( Platform.isAndroid ? Icons.download - : CupertinoIcons.cloud_download, + : Icons.cloud_download_outlined, color: Theme.of(context).iconTheme.color, ), const Padding( From 994ca4b6a3781ca28524aec4128b2c984b9fe2e4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:25:35 +0530 Subject: [PATCH 066/367] That's why cache fails --- desktop/src/main.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 42c5ab732..4cd25881f 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -174,7 +174,7 @@ const createMainWindow = async () => { if (isDev) window.webContents.openDevTools(); window.webContents.on("render-process-gone", (_, details) => { - log.error(`render-process-gone: ${details}`); + log.error(`render-process-gone: ${details.reason}`); window.webContents.reload(); }); @@ -302,17 +302,19 @@ const setupTrayItem = (mainWindow: BrowserWindow) => { * versions. */ const deleteLegacyDiskCacheDirIfExists = async () => { - // The existing code was passing "cache" as a parameter to getPath. This is - // incorrect if we go by the types - "cache" is not a valid value for the - // parameter to `app.getPath`. + // The existing code was passing "cache" as a parameter to getPath. // - // It might be an issue in the types, since at runtime it seems to work. For - // example, on macOS I get `~/Library/Caches`. + // However, "cache" is not a valid parameter to getPath. It works! (for + // example, on macOS I get `~/Library/Caches`), but it is intentionally not + // documented as part of the public API: + // + // - docs: remove "cache" from app.getPath + // https://github.com/electron/electron/pull/33509 // // Irrespective, we replicate the original behaviour so that we get back the - // same path that the old got was getting. + // same path that the old code was getting. // - // @ts-expect-error + // @ts-expect-error "cache" works but is not part of the public API. const cacheDir = path.join(app.getPath("cache"), "ente"); if (existsSync(cacheDir)) { log.info(`Removing legacy disk cache from ${cacheDir}`); From 9771db6ade1a6230ffe3290dfa49bf9260edae5c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:28:48 +0530 Subject: [PATCH 067/367] Use the built in transformer --- desktop/src/main/stream.ts | 39 ++++++-------------------------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index 15715c550..b97900659 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -163,39 +163,12 @@ const handleWrite = async (path: string, request: Request) => { * The returned promise resolves when the write completes. * * @param filePath The local filesystem path where the file should be written. - * @param readableStream A [web - * ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) + * + * @param readableStream A web + * [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream). */ export const writeStream = (filePath: string, readableStream: ReadableStream) => - writeNodeStream(filePath, convertWebReadableStreamToNode(readableStream)); - -/** - * Convert a Web ReadableStream into a Node.js ReadableStream - * - * This can be used to, for example, write a ReadableStream obtained via - * `net.fetch` into a file using the Node.js `fs` APIs - */ -const convertWebReadableStreamToNode = (readableStream: ReadableStream) => { - const reader = readableStream.getReader(); - const rs = new Readable(); - - rs._read = async () => { - try { - const result = await reader.read(); - - if (!result.done) { - rs.push(Buffer.from(result.value)); - } else { - rs.push(null); - return; - } - } catch (e) { - rs.emit("error", e); - } - }; - - return rs; -}; + writeNodeStream(filePath, Readable.fromWeb(readableStream)); const writeNodeStream = async (filePath: string, fileStream: Readable) => { const writeable = createWriteStream(filePath); @@ -208,11 +181,11 @@ const writeNodeStream = async (filePath: string, fileStream: Readable) => { await new Promise((resolve, reject) => { writeable.on("finish", resolve); - writeable.on("error", async (e: unknown) => { + writeable.on("error", async (err: Error) => { if (existsSync(filePath)) { await fs.unlink(filePath); } - reject(e); + reject(err); }); }); }; From 01c77c39495a06e8c8c29349aad9717dc1afb8d3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:29:24 +0530 Subject: [PATCH 068/367] unk --- desktop/src/main/log.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index c1902d8eb..c4d2f3cbb 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -65,7 +65,7 @@ const logError_ = (message: string) => { if (isDev) console.error(`[error] ${message}`); }; -const logInfo = (...params: any[]) => { +const logInfo = (...params: unknown[]) => { const message = params .map((p) => (typeof p == "string" ? p : util.inspect(p))) .join(" "); @@ -73,7 +73,7 @@ const logInfo = (...params: any[]) => { if (isDev) console.log(`[info] ${message}`); }; -const logDebug = (param: () => any) => { +const logDebug = (param: () => unknown) => { if (isDev) { const p = param(); console.log(`[debug] ${typeof p == "string" ? p : util.inspect(p)}`); From 9e279da6b3f9a530af8f56fc7e85ebc16c67bf00 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:30:19 +0530 Subject: [PATCH 069/367] annotations --- desktop/src/main/ipc.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index eb8b6cdda..66cfddabd 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -93,18 +93,20 @@ export const attachIPCHandlers = () => { ipcMain.handle("appVersion", () => appVersion()); - ipcMain.handle("openDirectory", (_, dirPath) => openDirectory(dirPath)); + ipcMain.handle("openDirectory", (_, dirPath: string) => + openDirectory(dirPath), + ); ipcMain.handle("openLogDirectory", () => openLogDirectory()); // See [Note: Catching exception during .send/.on] - ipcMain.on("logToDisk", (_, message) => logToDisk(message)); + ipcMain.on("logToDisk", (_, message: string) => logToDisk(message)); ipcMain.handle("selectDirectory", () => selectDirectory()); ipcMain.on("clearStores", () => clearStores()); - ipcMain.handle("saveEncryptionKey", (_, encryptionKey) => + ipcMain.handle("saveEncryptionKey", (_, encryptionKey: string) => saveEncryptionKey(encryptionKey), ); @@ -114,21 +116,23 @@ export const attachIPCHandlers = () => { ipcMain.on("updateAndRestart", () => updateAndRestart()); - ipcMain.on("updateOnNextRestart", (_, version) => + ipcMain.on("updateOnNextRestart", (_, version: string) => updateOnNextRestart(version), ); - ipcMain.on("skipAppUpdate", (_, version) => skipAppUpdate(version)); + ipcMain.on("skipAppUpdate", (_, version: string) => skipAppUpdate(version)); // - FS - ipcMain.handle("fsExists", (_, path) => fsExists(path)); + ipcMain.handle("fsExists", (_, path: string) => fsExists(path)); ipcMain.handle("fsRename", (_, oldPath: string, newPath: string) => fsRename(oldPath, newPath), ); - ipcMain.handle("fsMkdirIfNeeded", (_, dirPath) => fsMkdirIfNeeded(dirPath)); + ipcMain.handle("fsMkdirIfNeeded", (_, dirPath: string) => + fsMkdirIfNeeded(dirPath), + ); ipcMain.handle("fsRmdir", (_, path: string) => fsRmdir(path)); From 7fb912c9dfd55109c7669d3d90dceb8fb2fb966d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:34:48 +0530 Subject: [PATCH 070/367] ensure --- desktop/src/main/utils/temp.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/desktop/src/main/utils/temp.ts b/desktop/src/main/utils/temp.ts index 09071c157..b336a902f 100644 --- a/desktop/src/main/utils/temp.ts +++ b/desktop/src/main/utils/temp.ts @@ -4,6 +4,7 @@ import { existsSync } from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; import type { ZipItem } from "../../types/ipc"; +import { ensure } from "./common"; /** * Our very own directory within the system temp directory. Go crazy, but @@ -17,13 +18,10 @@ const enteTempDirPath = async () => { /** Generate a random string suitable for being used as a file name prefix */ const randomPrefix = () => { - const alphabet = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const randomChar = () => ensure(ch[Math.floor(Math.random() * ch.length)]); - let result = ""; - for (let i = 0; i < 10; i++) - result += alphabet[Math.floor(Math.random() * alphabet.length)]; - return result; + return Array(10).fill("").map(randomChar).join(""); }; /** From 1076471d51fd27dc07e60e5072d32d37abe3ecc5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:42:52 +0530 Subject: [PATCH 071/367] Turn one off --- desktop/src/main/stores/watch.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/desktop/src/main/stores/watch.ts b/desktop/src/main/stores/watch.ts index 7ee383038..6006c28ac 100644 --- a/desktop/src/main/stores/watch.ts +++ b/desktop/src/main/stores/watch.ts @@ -54,8 +54,11 @@ export const watchStore = new Store({ */ export const migrateLegacyWatchStoreIfNeeded = () => { let needsUpdate = false; - const watches = watchStore.get("mappings")?.map((watch) => { + const watches = watchStore.get("mappings").map((watch) => { let collectionMapping = watch.collectionMapping; + // The required type defines the latest schema, but before migration + // this'll be undefined, so tell ESLint to calm down. + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!collectionMapping) { collectionMapping = watch.uploadStrategy == 1 ? "parent" : "root"; needsUpdate = true; From 46d67f0c49dd77a17a63824e89562998a8f05fb5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:49:56 +0530 Subject: [PATCH 072/367] Disentagle map from modifications --- desktop/src/main/services/watch.ts | 4 ++-- desktop/src/main/stores/watch.ts | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/desktop/src/main/services/watch.ts b/desktop/src/main/services/watch.ts index e8e380390..ca550a787 100644 --- a/desktop/src/main/services/watch.ts +++ b/desktop/src/main/services/watch.ts @@ -73,7 +73,7 @@ export const watchAdd = async ( ) => { const watches = folderWatches(); - if (!fsIsDir(folderPath)) + if (!(await fsIsDir(folderPath))) throw new Error( `Attempting to add a folder watch for a folder path ${folderPath} that is not an existing directory`, ); @@ -97,7 +97,7 @@ export const watchAdd = async ( return watches; }; -export const watchRemove = async (watcher: FSWatcher, folderPath: string) => { +export const watchRemove = (watcher: FSWatcher, folderPath: string) => { const watches = folderWatches(); const filtered = watches.filter((watch) => watch.folderPath != folderPath); if (watches.length == filtered.length) diff --git a/desktop/src/main/stores/watch.ts b/desktop/src/main/stores/watch.ts index 6006c28ac..59032c9ac 100644 --- a/desktop/src/main/stores/watch.ts +++ b/desktop/src/main/stores/watch.ts @@ -3,7 +3,7 @@ import { type FolderWatch } from "../../types/ipc"; import log from "../log"; interface WatchStore { - mappings: FolderWatchWithLegacyFields[]; + mappings?: FolderWatchWithLegacyFields[]; } type FolderWatchWithLegacyFields = FolderWatch & { @@ -54,7 +54,8 @@ export const watchStore = new Store({ */ export const migrateLegacyWatchStoreIfNeeded = () => { let needsUpdate = false; - const watches = watchStore.get("mappings").map((watch) => { + const updatedWatches = []; + for (const watch of watchStore.get("mappings") ?? []) { let collectionMapping = watch.collectionMapping; // The required type defines the latest schema, but before migration // this'll be undefined, so tell ESLint to calm down. @@ -67,10 +68,10 @@ export const migrateLegacyWatchStoreIfNeeded = () => { delete watch.rootFolderName; needsUpdate = true; } - return { ...watch, collectionMapping }; - }); + updatedWatches.push({ ...watch, collectionMapping }); + } if (needsUpdate) { - watchStore.set("mappings", watches); + watchStore.set("mappings", updatedWatches); log.info("Migrated legacy watch store data to new schema"); } }; From 9cce8b379ca468641d876d384e588ece0b41aed3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:51:19 +0530 Subject: [PATCH 073/367] Remove unnecessary asyncs --- desktop/src/main/services/upload.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/desktop/src/main/services/upload.ts b/desktop/src/main/services/upload.ts index c3efbb9f4..cf9639919 100644 --- a/desktop/src/main/services/upload.ts +++ b/desktop/src/main/services/upload.ts @@ -82,16 +82,16 @@ export const pendingUploads = async (): Promise => { }; }; -export const setPendingUploads = async (pendingUploads: PendingUploads) => +export const setPendingUploads = (pendingUploads: PendingUploads) => uploadStatusStore.set(pendingUploads); -export const markUploadedFiles = async (paths: string[]) => { +export const markUploadedFiles = (paths: string[]) => { const existing = uploadStatusStore.get("filePaths"); const updated = existing?.filter((p) => !paths.includes(p)); uploadStatusStore.set("filePaths", updated); }; -export const markUploadedZipItems = async ( +export const markUploadedZipItems = ( items: [zipPath: string, entryName: string][], ) => { const existing = uploadStatusStore.get("zipItems"); From f4660baeb8eff59a6feb8336d07e96656e937a06 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 16:58:58 +0530 Subject: [PATCH 074/367] Remove unnecessary awaits --- desktop/src/main/services/store.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/desktop/src/main/services/store.ts b/desktop/src/main/services/store.ts index 9ec65c8c3..1884efbc5 100644 --- a/desktop/src/main/services/store.ts +++ b/desktop/src/main/services/store.ts @@ -14,15 +14,15 @@ export const clearStores = () => { watchStore.clear(); }; -export const saveEncryptionKey = async (encryptionKey: string) => { - const encryptedKey: Buffer = await safeStorage.encryptString(encryptionKey); +export const saveEncryptionKey = (encryptionKey: string) => { + const encryptedKey = safeStorage.encryptString(encryptionKey); const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64"); safeStorageStore.set("encryptionKey", b64EncryptedKey); }; -export const encryptionKey = async (): Promise => { +export const encryptionKey = (): string | undefined => { const b64EncryptedKey = safeStorageStore.get("encryptionKey"); if (!b64EncryptedKey) return undefined; const keyBuffer = Buffer.from(b64EncryptedKey, "base64"); - return await safeStorage.decryptString(keyBuffer); + return safeStorage.decryptString(keyBuffer); }; From 20459afc7bcd400f2d15168fc1800cf0d709f78b Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 30 Apr 2024 17:33:25 +0530 Subject: [PATCH 075/367] [cast] Add logs --- web/apps/cast/src/pages/index.tsx | 26 +++++----- web/apps/cast/src/pages/slideshow.tsx | 73 +++++++++++++++------------ 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 12c859e9c..fa4d9e485 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -41,6 +41,19 @@ export default function PairingMode() { init(); }, []); + const init = async () => { + try { + const data = generateSecureData(6); + setDigits(convertDataToDecimalString(data).split("")); + const keypair = await generateKeyPair(); + setPublicKeyB64(await toB64(keypair.publicKey)); + setPrivateKeyB64(await toB64(keypair.privateKey)); + } catch (e) { + log.error("failed to generate keypair", e); + throw e; + } + }; + useEffect(() => { if (!cast) { return; @@ -102,19 +115,6 @@ export default function PairingMode() { } }; - const init = async () => { - try { - const data = generateSecureData(6); - setDigits(convertDataToDecimalString(data).split("")); - const keypair = await generateKeyPair(); - setPublicKeyB64(await toB64(keypair.publicKey)); - setPrivateKeyB64(await toB64(keypair.privateKey)); - } catch (e) { - log.error("failed to generate keypair", e); - throw e; - } - }; - const generateKeyPair = async () => { await _sodium.ready; diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index 99b2209de..b860523ab 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -30,6 +30,7 @@ export default function Slideshow() { const syncCastFiles = async (token: string) => { try { + console.log("syncCastFiles"); const castToken = window.localStorage.getItem("castToken"); const requestedCollectionKey = window.localStorage.getItem("collectionKey"); @@ -50,6 +51,7 @@ export default function Slideshow() { } } catch (e) { log.error("error during sync", e); + // go back to preview page router.push("/"); } }; @@ -100,45 +102,54 @@ export default function Slideshow() { }, [collectionFiles]); const showNextSlide = async () => { - const currentIndex = collectionFiles.findIndex( - (file) => file.id === currentFileId, - ); + try { + const currentIndex = collectionFiles.findIndex( + (file) => file.id === currentFileId, + ); - const nextIndex = (currentIndex + 1) % collectionFiles.length; - const nextNextIndex = (nextIndex + 1) % collectionFiles.length; + const nextIndex = (currentIndex + 1) % collectionFiles.length; + const nextNextIndex = (nextIndex + 1) % collectionFiles.length; - const nextFile = collectionFiles[nextIndex]; - const nextNextFile = collectionFiles[nextNextIndex]; + const nextFile = collectionFiles[nextIndex]; + const nextNextFile = collectionFiles[nextNextIndex]; - let nextURL = renderableFileURLCache.get(nextFile.id); - let nextNextURL = renderableFileURLCache.get(nextNextFile.id); + let nextURL = renderableFileURLCache.get(nextFile.id); + let nextNextURL = renderableFileURLCache.get(nextNextFile.id); - if (!nextURL) { - try { - const blob = await getPreviewableImage(nextFile, castToken); - const url = URL.createObjectURL(blob); - renderableFileURLCache.set(nextFile.id, url); - nextURL = url; - } catch (e) { - return; + if (!nextURL) { + try { + const blob = await getPreviewableImage(nextFile, castToken); + const url = URL.createObjectURL(blob); + renderableFileURLCache.set(nextFile.id, url); + nextURL = url; + } catch (e) { + console.log("error in nextUrl", e); + return; + } } - } - if (!nextNextURL) { - try { - const blob = await getPreviewableImage(nextNextFile, castToken); - const url = URL.createObjectURL(blob); - renderableFileURLCache.set(nextNextFile.id, url); - nextNextURL = url; - } catch (e) { - return; + if (!nextNextURL) { + try { + const blob = await getPreviewableImage( + nextNextFile, + castToken, + ); + const url = URL.createObjectURL(blob); + renderableFileURLCache.set(nextNextFile.id, url); + nextNextURL = url; + } catch (e) { + console.log("error in nextNextURL", e); + return; + } } - } - setLoading(false); - setCurrentFileId(nextFile.id); - setCurrentFileURL(nextURL); - setNextFileURL(nextNextURL); + setLoading(false); + setCurrentFileId(nextFile.id); + setCurrentFileURL(nextURL); + setNextFileURL(nextNextURL); + } catch (e) { + console.log("error in showNextSlide", e); + } }; if (loading) return ; From d308d334f8c9d53abb7fa6cbdb33e9cc3d77eb8c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 17:01:24 +0530 Subject: [PATCH 076/367] tt --- desktop/src/main.ts | 6 +++--- desktop/src/main/services/auto-launcher.ts | 2 +- desktop/src/main/services/ml-clip.ts | 4 ++-- desktop/src/main/services/ml-face.ts | 4 +++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 4cd25881f..b0c51fb93 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -146,7 +146,7 @@ const registerPrivilegedSchemes = () => { * * This window will show the HTML served from {@link rendererURL}. */ -const createMainWindow = async () => { +const createMainWindow = () => { // Create the main window. This'll show our web content. const window = new BrowserWindow({ webPreferences: { @@ -160,7 +160,7 @@ const createMainWindow = async () => { show: false, }); - const wasAutoLaunched = await autoLauncher.wasAutoLaunched(); + const wasAutoLaunched = autoLauncher.wasAutoLaunched(); if (wasAutoLaunched) { // Don't automatically show the app's window if we were auto-launched. // On macOS, also hide the dock icon on macOS. @@ -367,7 +367,7 @@ const main = () => { // Note that some Electron APIs can only be used after this event occurs. app.on("ready", async () => { // Create window and prepare for the renderer. - mainWindow = await createMainWindow(); + mainWindow = createMainWindow(); attachIPCHandlers(); attachFSWatchIPCHandlers(createWatcher(mainWindow)); registerStreamProtocol(); diff --git a/desktop/src/main/services/auto-launcher.ts b/desktop/src/main/services/auto-launcher.ts index c704f7399..4e97a0225 100644 --- a/desktop/src/main/services/auto-launcher.ts +++ b/desktop/src/main/services/auto-launcher.ts @@ -38,7 +38,7 @@ class AutoLauncher { } } - async wasAutoLaunched() { + wasAutoLaunched() { if (this.autoLaunch) { return app.commandLine.hasSwitch("hidden"); } else { diff --git a/desktop/src/main/services/ml-clip.ts b/desktop/src/main/services/ml-clip.ts index 451cdcb09..ddbfb0881 100644 --- a/desktop/src/main/services/ml-clip.ts +++ b/desktop/src/main/services/ml-clip.ts @@ -49,7 +49,7 @@ const clipImageEmbedding_ = async (jpegFilePath: string) => { return normalizeEmbedding(imageEmbedding); }; -const getRGBData = async (jpegFilePath: string) => { +const getRGBData = async (jpegFilePath: string): Promise => { const jpegData = await fs.readFile(jpegFilePath); const rawImageData = jpeg.decode(jpegData, { useTArray: true, @@ -64,7 +64,7 @@ const getRGBData = async (jpegFilePath: string) => { const ny2 = 224; const totalSize = 3 * nx2 * ny2; - const result: number[] = Array(totalSize).fill(0); + const result = Array(totalSize).fill(0); const scale = Math.max(nx, ny) / 224; const nx3 = Math.round(nx / scale); diff --git a/desktop/src/main/services/ml-face.ts b/desktop/src/main/services/ml-face.ts index e4e43a4b0..e72f043e0 100644 --- a/desktop/src/main/services/ml-face.ts +++ b/desktop/src/main/services/ml-face.ts @@ -47,5 +47,7 @@ export const faceEmbedding = async (input: Float32Array) => { const results = await session.run(feeds); log.debug(() => `onnx/yolo face embedding took ${Date.now() - t} ms`); /* Need these model specific casts to extract and type the result */ - return (results.embeddings as unknown as any)["cpuData"] as Float32Array; + return (results.embeddings as unknown as Record)[ + "cpuData" + ] as Float32Array; }; From 82316ff290f41db23ba380b8c3d67f72a8aa9d74 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 17:43:30 +0530 Subject: [PATCH 077/367] Unawaited promises --- desktop/.eslintrc.js | 2 +- desktop/src/main/menu.ts | 2 +- desktop/src/main/services/upload.ts | 4 ++-- desktop/src/main/utils/temp.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/desktop/.eslintrc.js b/desktop/.eslintrc.js index 541ba33a9..daf2c2838 100644 --- a/desktop/.eslintrc.js +++ b/desktop/.eslintrc.js @@ -29,6 +29,6 @@ module.exports = { "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-confusing-void-expression": "off", "@typescript-eslint/no-misused-promises": "off", - "@typescript-eslint/no-floating-promises": "off", + // "@typescript-eslint/no-floating-promises": "off", }, }; diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index 0693c01dc..5dd2b335e 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -35,7 +35,7 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { ); const toggleAutoLaunch = () => { - autoLauncher.toggleAutoLaunch(); + void autoLauncher.toggleAutoLaunch(); isAutoLaunchEnabled = !isAutoLaunchEnabled; }; diff --git a/desktop/src/main/services/upload.ts b/desktop/src/main/services/upload.ts index cf9639919..96835bfb0 100644 --- a/desktop/src/main/services/upload.ts +++ b/desktop/src/main/services/upload.ts @@ -20,7 +20,7 @@ export const listZipItems = async (zipPath: string): Promise => { } } - zip.close(); + await zip.close(); return entryNames.map((entryName) => [zipPath, entryName]); }; @@ -40,7 +40,7 @@ export const pathOrZipItemSize = async ( `An entry with name ${entryName} does not exist in the zip file at ${zipPath}`, ); const size = entry.size; - zip.close(); + await zip.close(); return size; } }; diff --git a/desktop/src/main/utils/temp.ts b/desktop/src/main/utils/temp.ts index b336a902f..582f0a2b3 100644 --- a/desktop/src/main/utils/temp.ts +++ b/desktop/src/main/utils/temp.ts @@ -114,7 +114,7 @@ export const makeFileForDataOrPathOrZipItem = async ( const [zipPath, entryName] = dataOrPathOrZipItem; const zip = new StreamZip.async({ file: zipPath }); await zip.extract(entryName, path); - zip.close(); + await zip.close(); }; } } From ecfb7d944dd468770e140b5f6a71d8305ac20acd Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 30 Apr 2024 17:45:35 +0530 Subject: [PATCH 078/367] [web][cast] Add slide logs --- web/apps/cast/src/pages/slideshow.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index b860523ab..11b34baf7 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -103,13 +103,26 @@ export default function Slideshow() { const showNextSlide = async () => { try { + console.log("showNextSlide"); const currentIndex = collectionFiles.findIndex( (file) => file.id === currentFileId, ); + console.log( + "showNextSlide-index", + currentIndex, + collectionFiles.length, + ); + const nextIndex = (currentIndex + 1) % collectionFiles.length; const nextNextIndex = (nextIndex + 1) % collectionFiles.length; + console.log( + "showNextSlide-nextIndex and nextNextIndex", + nextIndex, + nextNextIndex, + ); + const nextFile = collectionFiles[nextIndex]; const nextNextFile = collectionFiles[nextNextIndex]; From 13d5a9f71af2a5f996f3ef7a9561c400b35f67d5 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 30 Apr 2024 18:09:35 +0530 Subject: [PATCH 079/367] [web][cast] Add logs --- web/apps/cast/src/components/PhotoAuditorium.tsx | 4 +++- web/apps/cast/src/pages/slideshow.tsx | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/web/apps/cast/src/components/PhotoAuditorium.tsx b/web/apps/cast/src/components/PhotoAuditorium.tsx index 6aa2c3990..c77c9e6ca 100644 --- a/web/apps/cast/src/components/PhotoAuditorium.tsx +++ b/web/apps/cast/src/components/PhotoAuditorium.tsx @@ -11,14 +11,16 @@ export const PhotoAuditorium: React.FC = ({ showNextSlide, }) => { useEffect(() => { + console.log("showing slide"); const timeoutId = window.setTimeout(() => { + console.log("showing next slide timer"); showNextSlide(); }, 10000); return () => { if (timeoutId) clearTimeout(timeoutId); }; - }, [showNextSlide]); + }, []); return (
Date: Tue, 30 Apr 2024 18:20:01 +0530 Subject: [PATCH 080/367] [web][cast] More logs --- web/apps/cast/src/pages/index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index fa4d9e485..7ad310fe1 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -174,12 +174,18 @@ export default function PairingMode() { const router = useRouter(); useEffect(() => { + console.log("useEffect for pairing called"); if (digits.length < 1 || !publicKeyB64 || !privateKeyB64) return; const interval = setInterval(async () => { + console.log("polling for cast data"); const data = await pollForCastData(); - if (!data) return; + if (!data) { + console.log("no data"); + return; + } storeCastData(data); + console.log("pushing slideshow"); await router.push("/slideshow"); }, 1000); From bda52267967427e7fb73c7614d16a59811293af7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 18:32:34 +0530 Subject: [PATCH 081/367] More unawaited --- desktop/src/main.ts | 12 ++++++------ desktop/src/main/services/app-update.ts | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index b0c51fb93..f5ad89158 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -202,8 +202,8 @@ const createMainWindow = () => { app.dock.hide(); }); - window.on("show", () => { - if (process.platform == "darwin") app.dock.show(); + window.on("show", async () => { + if (process.platform == "darwin") await app.dock.show(); }); // Let ipcRenderer know when mainWindow is in the foreground so that it can @@ -257,7 +257,7 @@ export const allowExternalLinks = (webContents: WebContents) => { // Returning `action` "deny" accomplishes this. webContents.setWindowOpenHandler(({ url }) => { if (!url.startsWith(rendererURL)) { - shell.openExternal(url); + void shell.openExternal(url); return { action: "deny" }; } else { return { action: "allow" }; @@ -377,7 +377,7 @@ const main = () => { allowExternalLinks(mainWindow.webContents); // Start loading the renderer. - mainWindow.loadURL(rendererURL); + void mainWindow.loadURL(rendererURL); // Continue on with the rest of the startup sequence. Menu.setApplicationMenu(await createApplicationMenu(mainWindow)); @@ -385,8 +385,8 @@ const main = () => { if (!isDev) setupAutoUpdater(mainWindow); try { - deleteLegacyDiskCacheDirIfExists(); - deleteLegacyKeysStoreIfExists(); + await deleteLegacyDiskCacheDirIfExists(); + await deleteLegacyKeysStoreIfExists(); } catch (e) { // Log but otherwise ignore errors during non-critical startup // actions. diff --git a/desktop/src/main/services/app-update.ts b/desktop/src/main/services/app-update.ts index e20d42fb7..a9e310934 100644 --- a/desktop/src/main/services/app-update.ts +++ b/desktop/src/main/services/app-update.ts @@ -13,7 +13,7 @@ export const setupAutoUpdater = (mainWindow: BrowserWindow) => { const oneDay = 1 * 24 * 60 * 60 * 1000; setInterval(() => checkForUpdatesAndNotify(mainWindow), oneDay); - checkForUpdatesAndNotify(mainWindow); + void checkForUpdatesAndNotify(mainWindow); }; /** @@ -22,7 +22,7 @@ export const setupAutoUpdater = (mainWindow: BrowserWindow) => { export const forceCheckForAppUpdates = (mainWindow: BrowserWindow) => { userPreferences.delete("skipAppVersion"); userPreferences.delete("muteUpdateNotificationVersion"); - checkForUpdatesAndNotify(mainWindow); + void checkForUpdatesAndNotify(mainWindow); }; const checkForUpdatesAndNotify = async (mainWindow: BrowserWindow) => { @@ -56,7 +56,7 @@ const checkForUpdatesAndNotify = async (mainWindow: BrowserWindow) => { mainWindow.webContents.send("appUpdateAvailable", update); log.debug(() => "Attempting auto update"); - autoUpdater.downloadUpdate(); + await autoUpdater.downloadUpdate(); let timeoutId: ReturnType; const fiveMinutes = 5 * 60 * 1000; From 9a281725652ebf96120e71d0426ec938d6deb613 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 18:37:50 +0530 Subject: [PATCH 082/367] iife wrapper --- desktop/.eslintrc.js | 2 +- desktop/src/main.ts | 52 +++++++++++++++++++++++--------------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/desktop/.eslintrc.js b/desktop/.eslintrc.js index daf2c2838..8c1867fc7 100644 --- a/desktop/.eslintrc.js +++ b/desktop/.eslintrc.js @@ -28,7 +28,7 @@ module.exports = { /* Temporary (RIP) */ "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-confusing-void-expression": "off", - "@typescript-eslint/no-misused-promises": "off", + // "@typescript-eslint/no-misused-promises": "off", // "@typescript-eslint/no-floating-promises": "off", }, }; diff --git a/desktop/src/main.ts b/desktop/src/main.ts index f5ad89158..c849c755f 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -202,8 +202,8 @@ const createMainWindow = () => { app.dock.hide(); }); - window.on("show", async () => { - if (process.platform == "darwin") await app.dock.show(); + window.on("show", () => { + if (process.platform == "darwin") void app.dock.show(); }); // Let ipcRenderer know when mainWindow is in the foreground so that it can @@ -365,33 +365,35 @@ const main = () => { // Emitted once, when Electron has finished initializing. // // Note that some Electron APIs can only be used after this event occurs. - app.on("ready", async () => { - // Create window and prepare for the renderer. - mainWindow = createMainWindow(); - attachIPCHandlers(); - attachFSWatchIPCHandlers(createWatcher(mainWindow)); - registerStreamProtocol(); + app.on("ready", () => { + void (async () => { + // Create window and prepare for the renderer. + mainWindow = createMainWindow(); + attachIPCHandlers(); + attachFSWatchIPCHandlers(createWatcher(mainWindow)); + registerStreamProtocol(); - // Configure the renderer's environment. - setDownloadPath(mainWindow.webContents); - allowExternalLinks(mainWindow.webContents); + // Configure the renderer's environment. + setDownloadPath(mainWindow.webContents); + allowExternalLinks(mainWindow.webContents); - // Start loading the renderer. - void mainWindow.loadURL(rendererURL); + // Start loading the renderer. + void mainWindow.loadURL(rendererURL); - // Continue on with the rest of the startup sequence. - Menu.setApplicationMenu(await createApplicationMenu(mainWindow)); - setupTrayItem(mainWindow); - if (!isDev) setupAutoUpdater(mainWindow); + // Continue on with the rest of the startup sequence. + Menu.setApplicationMenu(await createApplicationMenu(mainWindow)); + setupTrayItem(mainWindow); + if (!isDev) setupAutoUpdater(mainWindow); - try { - await deleteLegacyDiskCacheDirIfExists(); - await deleteLegacyKeysStoreIfExists(); - } catch (e) { - // Log but otherwise ignore errors during non-critical startup - // actions. - log.error("Ignoring startup error", e); - } + try { + await deleteLegacyDiskCacheDirIfExists(); + await deleteLegacyKeysStoreIfExists(); + } catch (e) { + // Log but otherwise ignore errors during non-critical startup + // actions. + log.error("Ignoring startup error", e); + } + })(); }); // This is a macOS only event. Show our window when the user activates the From 7b16fa9f38cc4dcfe64d3c88769d2d3058c57498 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 18:39:18 +0530 Subject: [PATCH 083/367] void --- desktop/src/main/menu.ts | 12 +++++++----- desktop/src/main/services/app-update.ts | 2 +- desktop/src/main/stream.ts | 10 +++++----- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index 5dd2b335e..1019c7a8f 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -30,7 +30,7 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { const handleCheckForUpdates = () => forceCheckForAppUpdates(mainWindow); const handleViewChangelog = () => - shell.openExternal( + void shell.openExternal( "https://github.com/ente-io/ente/blob/main/desktop/CHANGELOG.md", ); @@ -46,13 +46,15 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { shouldHideDockIcon = !shouldHideDockIcon; }; - const handleHelp = () => shell.openExternal("https://help.ente.io/photos/"); + const handleHelp = () => + void shell.openExternal("https://help.ente.io/photos/"); - const handleSupport = () => shell.openExternal("mailto:support@ente.io"); + const handleSupport = () => + void shell.openExternal("mailto:support@ente.io"); - const handleBlog = () => shell.openExternal("https://ente.io/blog/"); + const handleBlog = () => void shell.openExternal("https://ente.io/blog/"); - const handleViewLogs = openLogDirectory; + const handleViewLogs = () => void openLogDirectory(); return Menu.buildFromTemplate([ { diff --git a/desktop/src/main/services/app-update.ts b/desktop/src/main/services/app-update.ts index a9e310934..bc4bd38d6 100644 --- a/desktop/src/main/services/app-update.ts +++ b/desktop/src/main/services/app-update.ts @@ -12,7 +12,7 @@ export const setupAutoUpdater = (mainWindow: BrowserWindow) => { autoUpdater.autoDownload = false; const oneDay = 1 * 24 * 60 * 60 * 1000; - setInterval(() => checkForUpdatesAndNotify(mainWindow), oneDay); + setInterval(() => void checkForUpdatesAndNotify(mainWindow), oneDay); void checkForUpdatesAndNotify(mainWindow); }; diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index b97900659..3e27de12b 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -117,7 +117,7 @@ const handleReadZip = async (zipPath: string, entryName: string) => { // Close the zip handle when the underlying stream closes. // TODO(MR): Verify - stream.on("end", () => zip.close()); + stream.on("end", () => void zip.close()); return new Response(webReadableStream, { headers: { @@ -173,17 +173,17 @@ export const writeStream = (filePath: string, readableStream: ReadableStream) => const writeNodeStream = async (filePath: string, fileStream: Readable) => { const writeable = createWriteStream(filePath); - fileStream.on("error", (error) => { - writeable.destroy(error); // Close the writable stream with an error + fileStream.on("error", (err) => { + writeable.destroy(err); // Close the writable stream with an error }); fileStream.pipe(writeable); await new Promise((resolve, reject) => { writeable.on("finish", resolve); - writeable.on("error", async (err: Error) => { + writeable.on("error", (err) => { if (existsSync(filePath)) { - await fs.unlink(filePath); + void fs.unlink(filePath); } reject(err); }); From 7e2ee61a97dc612d88a89b4e993d5500b9de6de9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 18:46:13 +0530 Subject: [PATCH 084/367] void expressions are fine --- desktop/.eslintrc.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/desktop/.eslintrc.js b/desktop/.eslintrc.js index 8c1867fc7..33c458b14 100644 --- a/desktop/.eslintrc.js +++ b/desktop/.eslintrc.js @@ -4,8 +4,8 @@ module.exports = { extends: [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", - /* What we really want eventually */ "plugin:@typescript-eslint/strict-type-checked", + /* What we really want eventually */ // "plugin:@typescript-eslint/stylistic-type-checked", ], plugins: ["@typescript-eslint"], @@ -19,16 +19,21 @@ module.exports = { node: true, }, rules: { + /* Allow numbers to be used in template literals */ "@typescript-eslint/restrict-template-expressions": [ "error", { allowNumber: true, }, ], + /* Allow void expressions as the entire body of an arrow function */ + "@typescript-eslint/no-confusing-void-expression": [ + "error", + { + ignoreArrowShorthand: true, + }, + ], /* Temporary (RIP) */ "@typescript-eslint/no-unsafe-return": "off", - "@typescript-eslint/no-confusing-void-expression": "off", - // "@typescript-eslint/no-misused-promises": "off", - // "@typescript-eslint/no-floating-promises": "off", }, }; From 76c98bdf326865e66c411415b7a78ddb465f6ac6 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 18:50:15 +0530 Subject: [PATCH 085/367] handle unsafe returns --- desktop/.eslintrc.js | 2 - desktop/src/main/services/ml-clip.ts | 2 +- desktop/src/preload.ts | 86 +++++++++++----------------- 3 files changed, 36 insertions(+), 54 deletions(-) diff --git a/desktop/.eslintrc.js b/desktop/.eslintrc.js index 33c458b14..ed16db526 100644 --- a/desktop/.eslintrc.js +++ b/desktop/.eslintrc.js @@ -33,7 +33,5 @@ module.exports = { ignoreArrowShorthand: true, }, ], - /* Temporary (RIP) */ - "@typescript-eslint/no-unsafe-return": "off", }, }; diff --git a/desktop/src/main/services/ml-clip.ts b/desktop/src/main/services/ml-clip.ts index ddbfb0881..c9378edf2 100644 --- a/desktop/src/main/services/ml-clip.ts +++ b/desktop/src/main/services/ml-clip.ts @@ -64,7 +64,7 @@ const getRGBData = async (jpegFilePath: string): Promise => { const ny2 = 224; const totalSize = 3 * nx2 * ny2; - const result = Array(totalSize).fill(0); + const result = Array(totalSize).fill(0); const scale = Math.max(nx, ny) / 224; const nx3 = Math.round(nx / scale); diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index ecc800db3..2b5eb8fcc 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -51,26 +51,23 @@ import type { // - General -const appVersion = (): Promise => ipcRenderer.invoke("appVersion"); +const appVersion = () => ipcRenderer.invoke("appVersion"); const logToDisk = (message: string): void => ipcRenderer.send("logToDisk", message); -const openDirectory = (dirPath: string): Promise => +const openDirectory = (dirPath: string) => ipcRenderer.invoke("openDirectory", dirPath); -const openLogDirectory = (): Promise => - ipcRenderer.invoke("openLogDirectory"); +const openLogDirectory = () => ipcRenderer.invoke("openLogDirectory"); -const selectDirectory = (): Promise => - ipcRenderer.invoke("selectDirectory"); +const selectDirectory = () => ipcRenderer.invoke("selectDirectory"); const clearStores = () => ipcRenderer.send("clearStores"); -const encryptionKey = (): Promise => - ipcRenderer.invoke("encryptionKey"); +const encryptionKey = () => ipcRenderer.invoke("encryptionKey"); -const saveEncryptionKey = (encryptionKey: string): Promise => +const saveEncryptionKey = (encryptionKey: string) => ipcRenderer.invoke("saveEncryptionKey", encryptionKey); const onMainWindowFocus = (cb?: () => void) => { @@ -102,39 +99,36 @@ const skipAppUpdate = (version: string) => { // - FS -const fsExists = (path: string): Promise => - ipcRenderer.invoke("fsExists", path); +const fsExists = (path: string) => ipcRenderer.invoke("fsExists", path); -const fsMkdirIfNeeded = (dirPath: string): Promise => +const fsMkdirIfNeeded = (dirPath: string) => ipcRenderer.invoke("fsMkdirIfNeeded", dirPath); -const fsRename = (oldPath: string, newPath: string): Promise => +const fsRename = (oldPath: string, newPath: string) => ipcRenderer.invoke("fsRename", oldPath, newPath); -const fsRmdir = (path: string): Promise => - ipcRenderer.invoke("fsRmdir", path); +const fsRmdir = (path: string) => ipcRenderer.invoke("fsRmdir", path); -const fsRm = (path: string): Promise => ipcRenderer.invoke("fsRm", path); +const fsRm = (path: string) => ipcRenderer.invoke("fsRm", path); -const fsReadTextFile = (path: string): Promise => +const fsReadTextFile = (path: string) => ipcRenderer.invoke("fsReadTextFile", path); -const fsWriteFile = (path: string, contents: string): Promise => +const fsWriteFile = (path: string, contents: string) => ipcRenderer.invoke("fsWriteFile", path, contents); -const fsIsDir = (dirPath: string): Promise => - ipcRenderer.invoke("fsIsDir", dirPath); +const fsIsDir = (dirPath: string) => ipcRenderer.invoke("fsIsDir", dirPath); // - Conversion -const convertToJPEG = (imageData: Uint8Array): Promise => +const convertToJPEG = (imageData: Uint8Array) => ipcRenderer.invoke("convertToJPEG", imageData); const generateImageThumbnail = ( dataOrPathOrZipItem: Uint8Array | string | ZipItem, maxDimension: number, maxSize: number, -): Promise => +) => ipcRenderer.invoke( "generateImageThumbnail", dataOrPathOrZipItem, @@ -147,7 +141,7 @@ const ffmpegExec = ( dataOrPathOrZipItem: Uint8Array | string | ZipItem, outputFileExtension: string, timeoutMS: number, -): Promise => +) => ipcRenderer.invoke( "ffmpegExec", command, @@ -158,44 +152,37 @@ const ffmpegExec = ( // - ML -const clipImageEmbedding = (jpegImageData: Uint8Array): Promise => +const clipImageEmbedding = (jpegImageData: Uint8Array) => ipcRenderer.invoke("clipImageEmbedding", jpegImageData); -const clipTextEmbeddingIfAvailable = ( - text: string, -): Promise => +const clipTextEmbeddingIfAvailable = (text: string) => ipcRenderer.invoke("clipTextEmbeddingIfAvailable", text); -const detectFaces = (input: Float32Array): Promise => +const detectFaces = (input: Float32Array) => ipcRenderer.invoke("detectFaces", input); -const faceEmbedding = (input: Float32Array): Promise => +const faceEmbedding = (input: Float32Array) => ipcRenderer.invoke("faceEmbedding", input); // - Watch -const watchGet = (): Promise => ipcRenderer.invoke("watchGet"); +const watchGet = () => ipcRenderer.invoke("watchGet"); -const watchAdd = ( - folderPath: string, - collectionMapping: CollectionMapping, -): Promise => +const watchAdd = (folderPath: string, collectionMapping: CollectionMapping) => ipcRenderer.invoke("watchAdd", folderPath, collectionMapping); -const watchRemove = (folderPath: string): Promise => +const watchRemove = (folderPath: string) => ipcRenderer.invoke("watchRemove", folderPath); const watchUpdateSyncedFiles = ( syncedFiles: FolderWatch["syncedFiles"], folderPath: string, -): Promise => - ipcRenderer.invoke("watchUpdateSyncedFiles", syncedFiles, folderPath); +) => ipcRenderer.invoke("watchUpdateSyncedFiles", syncedFiles, folderPath); const watchUpdateIgnoredFiles = ( ignoredFiles: FolderWatch["ignoredFiles"], folderPath: string, -): Promise => - ipcRenderer.invoke("watchUpdateIgnoredFiles", ignoredFiles, folderPath); +) => ipcRenderer.invoke("watchUpdateIgnoredFiles", ignoredFiles, folderPath); const watchOnAddFile = (f: (path: string, watch: FolderWatch) => void) => { ipcRenderer.removeAllListeners("watchAddFile"); @@ -218,34 +205,31 @@ const watchOnRemoveDir = (f: (path: string, watch: FolderWatch) => void) => { ); }; -const watchFindFiles = (folderPath: string): Promise => +const watchFindFiles = (folderPath: string) => ipcRenderer.invoke("watchFindFiles", folderPath); // - Upload const pathForFile = (file: File) => webUtils.getPathForFile(file); -const listZipItems = (zipPath: string): Promise => +const listZipItems = (zipPath: string) => ipcRenderer.invoke("listZipItems", zipPath); -const pathOrZipItemSize = (pathOrZipItem: string | ZipItem): Promise => +const pathOrZipItemSize = (pathOrZipItem: string | ZipItem) => ipcRenderer.invoke("pathOrZipItemSize", pathOrZipItem); -const pendingUploads = (): Promise => - ipcRenderer.invoke("pendingUploads"); +const pendingUploads = () => ipcRenderer.invoke("pendingUploads"); -const setPendingUploads = (pendingUploads: PendingUploads): Promise => +const setPendingUploads = (pendingUploads: PendingUploads) => ipcRenderer.invoke("setPendingUploads", pendingUploads); -const markUploadedFiles = (paths: PendingUploads["filePaths"]): Promise => +const markUploadedFiles = (paths: PendingUploads["filePaths"]) => ipcRenderer.invoke("markUploadedFiles", paths); -const markUploadedZipItems = ( - items: PendingUploads["zipItems"], -): Promise => ipcRenderer.invoke("markUploadedZipItems", items); +const markUploadedZipItems = (items: PendingUploads["zipItems"]) => + ipcRenderer.invoke("markUploadedZipItems", items); -const clearPendingUploads = (): Promise => - ipcRenderer.invoke("clearPendingUploads"); +const clearPendingUploads = () => ipcRenderer.invoke("clearPendingUploads"); /** * These objects exposed here will become available to the JS code in our From 50a14470202512df6d72eea2104a19da7f69fa0f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 18:53:17 +0530 Subject: [PATCH 086/367] Stylistic --- desktop/.eslintrc.js | 3 +-- desktop/src/main/services/ml-clip.ts | 13 ++++++------- desktop/src/main/services/ml-face.ts | 7 +++---- desktop/src/main/services/upload.ts | 2 +- desktop/src/main/utils/temp.ts | 4 +++- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/desktop/.eslintrc.js b/desktop/.eslintrc.js index ed16db526..44d03ef0c 100644 --- a/desktop/.eslintrc.js +++ b/desktop/.eslintrc.js @@ -5,8 +5,7 @@ module.exports = { "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/strict-type-checked", - /* What we really want eventually */ - // "plugin:@typescript-eslint/stylistic-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", ], plugins: ["@typescript-eslint"], parser: "@typescript-eslint/parser", diff --git a/desktop/src/main/services/ml-clip.ts b/desktop/src/main/services/ml-clip.ts index c9378edf2..e3dd99204 100644 --- a/desktop/src/main/services/ml-clip.ts +++ b/desktop/src/main/services/ml-clip.ts @@ -45,7 +45,7 @@ const clipImageEmbedding_ = async (jpegFilePath: string) => { `onnx/clip image embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`, ); /* Need these model specific casts to type the result */ - const imageEmbedding = ensure(results["output"]).data as Float32Array; + const imageEmbedding = ensure(results.output).data as Float32Array; return normalizeEmbedding(imageEmbedding); }; @@ -120,13 +120,12 @@ const getRGBData = async (jpegFilePath: string): Promise => { const normalizeEmbedding = (embedding: Float32Array) => { let normalization = 0; - for (let index = 0; index < embedding.length; index++) { - normalization += ensure(embedding[index]) * ensure(embedding[index]); - } + for (const v of embedding) normalization += v * v; + const sqrtNormalization = Math.sqrt(normalization); - for (let index = 0; index < embedding.length; index++) { + for (let index = 0; index < embedding.length; index++) embedding[index] = ensure(embedding[index]) / sqrtNormalization; - } + return embedding; }; @@ -168,6 +167,6 @@ export const clipTextEmbeddingIfAvailable = async (text: string) => { () => `onnx/clip text embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`, ); - const textEmbedding = ensure(results["output"]).data as Float32Array; + const textEmbedding = ensure(results.output).data as Float32Array; return normalizeEmbedding(textEmbedding); }; diff --git a/desktop/src/main/services/ml-face.ts b/desktop/src/main/services/ml-face.ts index e72f043e0..976525255 100644 --- a/desktop/src/main/services/ml-face.ts +++ b/desktop/src/main/services/ml-face.ts @@ -24,7 +24,7 @@ export const detectFaces = async (input: Float32Array) => { }; const results = await session.run(feeds); log.debug(() => `onnx/yolo face detection took ${Date.now() - t} ms`); - return ensure(results["output"]).data; + return ensure(results.output).data; }; const cachedFaceEmbeddingSession = makeCachedInferenceSession( @@ -47,7 +47,6 @@ export const faceEmbedding = async (input: Float32Array) => { const results = await session.run(feeds); log.debug(() => `onnx/yolo face embedding took ${Date.now() - t} ms`); /* Need these model specific casts to extract and type the result */ - return (results.embeddings as unknown as Record)[ - "cpuData" - ] as Float32Array; + return (results.embeddings as unknown as Record) + .cpuData as Float32Array; }; diff --git a/desktop/src/main/services/upload.ts b/desktop/src/main/services/upload.ts index 96835bfb0..7871b56fd 100644 --- a/desktop/src/main/services/upload.ts +++ b/desktop/src/main/services/upload.ts @@ -14,7 +14,7 @@ export const listZipItems = async (zipPath: string): Promise => { for (const entry of Object.values(entries)) { const basename = path.basename(entry.name); // Ignore "hidden" files (files whose names begins with a dot). - if (entry.isFile && basename.length > 0 && basename[0] != ".") { + if (entry.isFile && basename.startsWith(".")) { // `entry.name` is the path within the zip. entryNames.push(entry.name); } diff --git a/desktop/src/main/utils/temp.ts b/desktop/src/main/utils/temp.ts index 582f0a2b3..11f7a5d84 100644 --- a/desktop/src/main/utils/temp.ts +++ b/desktop/src/main/utils/temp.ts @@ -98,7 +98,9 @@ export const makeFileForDataOrPathOrZipItem = async ( ): Promise => { let path: string; let isFileTemporary: boolean; - let writeToTemporaryFile = async () => {}; + let writeToTemporaryFile = async () => { + /* no-op */ + }; if (typeof dataOrPathOrZipItem == "string") { path = dataOrPathOrZipItem; From 1eff04fe92bd670708aaa7b70ada9e81458718d6 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 18:57:19 +0530 Subject: [PATCH 087/367] Enable lints --- .github/workflows/desktop-lint.yml | 30 ++++++++++++++++++++++++++++++ desktop/package.json | 4 ++-- 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/desktop-lint.yml diff --git a/.github/workflows/desktop-lint.yml b/.github/workflows/desktop-lint.yml new file mode 100644 index 000000000..0b8263f3d --- /dev/null +++ b/.github/workflows/desktop-lint.yml @@ -0,0 +1,30 @@ +name: "Lint (desktop)" + +on: + # Run on every push to a branch other than main that changes desktop/ + push: + branches-ignore: [main, "deploy/**"] + paths: + - "desktop/**" + - ".github/workflows/desktop-lint.yml" + +jobs: + lint: + runs-on: ubuntu-latest + defaults: + run: + working-directory: desktop + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup node and enable yarn caching + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "yarn" + cache-dependency-path: "desktop/yarn.lock" + + - run: yarn install + + - run: yarn lint diff --git a/desktop/package.json b/desktop/package.json index 509ee8583..d4c571cad 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -15,8 +15,8 @@ "dev-main": "tsc && electron app/main.js", "dev-renderer": "cd ../web && yarn install && yarn dev:photos", "postinstall": "electron-builder install-app-deps", - "lint": "yarn prettier --check . && eslint --ext .ts src", - "lint-fix": "yarn prettier --write . && eslint --fix --ext .ts src" + "lint": "yarn prettier --check . && eslint --ext .ts src && yarn tsc", + "lint-fix": "yarn prettier --write . && eslint --fix --ext .ts src && yarn tsc" }, "dependencies": { "any-shell-escape": "^0.1", From 348b78467478ad8cf2cfbdea86495248295da4e3 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 30 Apr 2024 19:52:30 +0530 Subject: [PATCH 088/367] Remove Isar DB for Embeddings --- mobile/lib/db/embeddings_db.dart | 79 -- mobile/lib/db/embeddings_sqlite_db.dart | 16 + mobile/lib/models/embedding.dart | 10 - mobile/lib/models/embedding.g.dart | 1059 ----------------- .../semantic_search_service.dart | 1 + 5 files changed, 17 insertions(+), 1148 deletions(-) delete mode 100644 mobile/lib/db/embeddings_db.dart delete mode 100644 mobile/lib/models/embedding.g.dart diff --git a/mobile/lib/db/embeddings_db.dart b/mobile/lib/db/embeddings_db.dart deleted file mode 100644 index a339d4d0d..000000000 --- a/mobile/lib/db/embeddings_db.dart +++ /dev/null @@ -1,79 +0,0 @@ -import "dart:io"; - -import "package:isar/isar.dart"; -import 'package:path_provider/path_provider.dart'; -import "package:photos/core/event_bus.dart"; -import "package:photos/events/embedding_updated_event.dart"; -import "package:photos/models/embedding.dart"; - -class EmbeddingsDB { - late final Isar _isar; - - EmbeddingsDB._privateConstructor(); - - static final EmbeddingsDB instance = EmbeddingsDB._privateConstructor(); - - Future init() async { - final dir = await getApplicationDocumentsDirectory(); - _isar = await Isar.open( - [EmbeddingSchema], - directory: dir.path, - ); - await _clearDeprecatedStore(dir); - } - - Future clearTable() async { - await _isar.writeTxn(() => _isar.clear()); - } - - Future> getAll(Model model) async { - return _isar.embeddings.filter().modelEqualTo(model).findAll(); - } - - Future put(Embedding embedding) { - return _isar.writeTxn(() async { - await _isar.embeddings.putByIndex(Embedding.index, embedding); - Bus.instance.fire(EmbeddingUpdatedEvent()); - }); - } - - Future putMany(List embeddings) { - return _isar.writeTxn(() async { - await _isar.embeddings.putAllByIndex(Embedding.index, embeddings); - Bus.instance.fire(EmbeddingUpdatedEvent()); - }); - } - - Future> getUnsyncedEmbeddings() async { - return await _isar.embeddings.filter().updationTimeEqualTo(null).findAll(); - } - - Future deleteEmbeddings(List fileIDs) async { - await _isar.writeTxn(() async { - final embeddings = []; - for (final fileID in fileIDs) { - embeddings.addAll( - await _isar.embeddings.filter().fileIDEqualTo(fileID).findAll(), - ); - } - await _isar.embeddings.deleteAll(embeddings.map((e) => e.id).toList()); - Bus.instance.fire(EmbeddingUpdatedEvent()); - }); - } - - Future deleteAllForModel(Model model) async { - await _isar.writeTxn(() async { - final embeddings = - await _isar.embeddings.filter().modelEqualTo(model).findAll(); - await _isar.embeddings.deleteAll(embeddings.map((e) => e.id).toList()); - Bus.instance.fire(EmbeddingUpdatedEvent()); - }); - } - - Future _clearDeprecatedStore(Directory dir) async { - final deprecatedStore = Directory(dir.path + "/object-box-store"); - if (await deprecatedStore.exists()) { - await deprecatedStore.delete(recursive: true); - } - } -} diff --git a/mobile/lib/db/embeddings_sqlite_db.dart b/mobile/lib/db/embeddings_sqlite_db.dart index 1a101d4eb..2c77af281 100644 --- a/mobile/lib/db/embeddings_sqlite_db.dart +++ b/mobile/lib/db/embeddings_sqlite_db.dart @@ -27,6 +27,11 @@ class EmbeddingsDB { return _dbFuture!; } + Future init() async { + final dir = await getApplicationDocumentsDirectory(); + await _clearDeprecatedStores(dir); + } + Future _initDatabase() async { final Directory documentsDirectory = await getApplicationDocumentsDirectory(); @@ -126,4 +131,15 @@ class EmbeddingsDB { embedding.updationTime, ]; } + + Future _clearDeprecatedStores(Directory dir) async { + final deprecatedStore = Directory(dir.path + "/object-box-store"); + if (await deprecatedStore.exists()) { + await deprecatedStore.delete(recursive: true); + } + final deprecatedDB = File(dir.path + "/default.isar"); + if (await deprecatedDB.exists()) { + await deprecatedDB.delete(); + } + } } diff --git a/mobile/lib/models/embedding.dart b/mobile/lib/models/embedding.dart index 1f78687b9..c8f742caa 100644 --- a/mobile/lib/models/embedding.dart +++ b/mobile/lib/models/embedding.dart @@ -1,17 +1,7 @@ import "dart:convert"; -import "package:isar/isar.dart"; - -part 'embedding.g.dart'; - -@collection class Embedding { - static const index = 'unique_file_model_embedding'; - - Id id = Isar.autoIncrement; final int fileID; - @enumerated - @Index(name: index, composite: [CompositeIndex('fileID')], unique: true, replace: true) final Model model; final List embedding; int? updationTime; diff --git a/mobile/lib/models/embedding.g.dart b/mobile/lib/models/embedding.g.dart deleted file mode 100644 index ca041a0d0..000000000 --- a/mobile/lib/models/embedding.g.dart +++ /dev/null @@ -1,1059 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'embedding.dart'; - -// ************************************************************************** -// IsarCollectionGenerator -// ************************************************************************** - -// coverage:ignore-file -// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types - -extension GetEmbeddingCollection on Isar { - IsarCollection get embeddings => this.collection(); -} - -const EmbeddingSchema = CollectionSchema( - name: r'Embedding', - id: -8064100183150254587, - properties: { - r'embedding': PropertySchema( - id: 0, - name: r'embedding', - type: IsarType.doubleList, - ), - r'fileID': PropertySchema( - id: 1, - name: r'fileID', - type: IsarType.long, - ), - r'model': PropertySchema( - id: 2, - name: r'model', - type: IsarType.byte, - enumMap: _EmbeddingmodelEnumValueMap, - ), - r'updationTime': PropertySchema( - id: 3, - name: r'updationTime', - type: IsarType.long, - ) - }, - estimateSize: _embeddingEstimateSize, - serialize: _embeddingSerialize, - deserialize: _embeddingDeserialize, - deserializeProp: _embeddingDeserializeProp, - idName: r'id', - indexes: { - r'unique_file_model_embedding': IndexSchema( - id: 6248303800853228628, - name: r'unique_file_model_embedding', - unique: true, - replace: true, - properties: [ - IndexPropertySchema( - name: r'model', - type: IndexType.value, - caseSensitive: false, - ), - IndexPropertySchema( - name: r'fileID', - type: IndexType.value, - caseSensitive: false, - ) - ], - ) - }, - links: {}, - embeddedSchemas: {}, - getId: _embeddingGetId, - getLinks: _embeddingGetLinks, - attach: _embeddingAttach, - version: '3.1.0+1', -); - -int _embeddingEstimateSize( - Embedding object, - List offsets, - Map> allOffsets, -) { - var bytesCount = offsets.last; - bytesCount += 3 + object.embedding.length * 8; - return bytesCount; -} - -void _embeddingSerialize( - Embedding object, - IsarWriter writer, - List offsets, - Map> allOffsets, -) { - writer.writeDoubleList(offsets[0], object.embedding); - writer.writeLong(offsets[1], object.fileID); - writer.writeByte(offsets[2], object.model.index); - writer.writeLong(offsets[3], object.updationTime); -} - -Embedding _embeddingDeserialize( - Id id, - IsarReader reader, - List offsets, - Map> allOffsets, -) { - final object = Embedding( - embedding: reader.readDoubleList(offsets[0]) ?? [], - fileID: reader.readLong(offsets[1]), - model: _EmbeddingmodelValueEnumMap[reader.readByteOrNull(offsets[2])] ?? - Model.onnxClip, - updationTime: reader.readLongOrNull(offsets[3]), - ); - object.id = id; - return object; -} - -P _embeddingDeserializeProp

( - IsarReader reader, - int propertyId, - int offset, - Map> allOffsets, -) { - switch (propertyId) { - case 0: - return (reader.readDoubleList(offset) ?? []) as P; - case 1: - return (reader.readLong(offset)) as P; - case 2: - return (_EmbeddingmodelValueEnumMap[reader.readByteOrNull(offset)] ?? - Model.onnxClip) as P; - case 3: - return (reader.readLongOrNull(offset)) as P; - default: - throw IsarError('Unknown property with id $propertyId'); - } -} - -const _EmbeddingmodelEnumValueMap = { - 'onnxClip': 0, - 'ggmlClip': 1, -}; -const _EmbeddingmodelValueEnumMap = { - 0: Model.onnxClip, - 1: Model.ggmlClip, -}; - -Id _embeddingGetId(Embedding object) { - return object.id; -} - -List> _embeddingGetLinks(Embedding object) { - return []; -} - -void _embeddingAttach(IsarCollection col, Id id, Embedding object) { - object.id = id; -} - -extension EmbeddingByIndex on IsarCollection { - Future getByModelFileID(Model model, int fileID) { - return getByIndex(r'unique_file_model_embedding', [model, fileID]); - } - - Embedding? getByModelFileIDSync(Model model, int fileID) { - return getByIndexSync(r'unique_file_model_embedding', [model, fileID]); - } - - Future deleteByModelFileID(Model model, int fileID) { - return deleteByIndex(r'unique_file_model_embedding', [model, fileID]); - } - - bool deleteByModelFileIDSync(Model model, int fileID) { - return deleteByIndexSync(r'unique_file_model_embedding', [model, fileID]); - } - - Future> getAllByModelFileID( - List modelValues, List fileIDValues) { - final len = modelValues.length; - assert(fileIDValues.length == len, - 'All index values must have the same length'); - final values = >[]; - for (var i = 0; i < len; i++) { - values.add([modelValues[i], fileIDValues[i]]); - } - - return getAllByIndex(r'unique_file_model_embedding', values); - } - - List getAllByModelFileIDSync( - List modelValues, List fileIDValues) { - final len = modelValues.length; - assert(fileIDValues.length == len, - 'All index values must have the same length'); - final values = >[]; - for (var i = 0; i < len; i++) { - values.add([modelValues[i], fileIDValues[i]]); - } - - return getAllByIndexSync(r'unique_file_model_embedding', values); - } - - Future deleteAllByModelFileID( - List modelValues, List fileIDValues) { - final len = modelValues.length; - assert(fileIDValues.length == len, - 'All index values must have the same length'); - final values = >[]; - for (var i = 0; i < len; i++) { - values.add([modelValues[i], fileIDValues[i]]); - } - - return deleteAllByIndex(r'unique_file_model_embedding', values); - } - - int deleteAllByModelFileIDSync( - List modelValues, List fileIDValues) { - final len = modelValues.length; - assert(fileIDValues.length == len, - 'All index values must have the same length'); - final values = >[]; - for (var i = 0; i < len; i++) { - values.add([modelValues[i], fileIDValues[i]]); - } - - return deleteAllByIndexSync(r'unique_file_model_embedding', values); - } - - Future putByModelFileID(Embedding object) { - return putByIndex(r'unique_file_model_embedding', object); - } - - Id putByModelFileIDSync(Embedding object, {bool saveLinks = true}) { - return putByIndexSync(r'unique_file_model_embedding', object, - saveLinks: saveLinks); - } - - Future> putAllByModelFileID(List objects) { - return putAllByIndex(r'unique_file_model_embedding', objects); - } - - List putAllByModelFileIDSync(List objects, - {bool saveLinks = true}) { - return putAllByIndexSync(r'unique_file_model_embedding', objects, - saveLinks: saveLinks); - } -} - -extension EmbeddingQueryWhereSort - on QueryBuilder { - QueryBuilder anyId() { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(const IdWhereClause.any()); - }); - } - - QueryBuilder anyModelFileID() { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause( - const IndexWhereClause.any(indexName: r'unique_file_model_embedding'), - ); - }); - } -} - -extension EmbeddingQueryWhere - on QueryBuilder { - QueryBuilder idEqualTo(Id id) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IdWhereClause.between( - lower: id, - upper: id, - )); - }); - } - - QueryBuilder idNotEqualTo(Id id) { - return QueryBuilder.apply(this, (query) { - if (query.whereSort == Sort.asc) { - return query - .addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: false), - ) - .addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: false), - ); - } else { - return query - .addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: false), - ) - .addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: false), - ); - } - }); - } - - QueryBuilder idGreaterThan(Id id, - {bool include = false}) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: include), - ); - }); - } - - QueryBuilder idLessThan(Id id, - {bool include = false}) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: include), - ); - }); - } - - QueryBuilder idBetween( - Id lowerId, - Id upperId, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IdWhereClause.between( - lower: lowerId, - includeLower: includeLower, - upper: upperId, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder modelEqualToAnyFileID( - Model model) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.equalTo( - indexName: r'unique_file_model_embedding', - value: [model], - )); - }); - } - - QueryBuilder - modelNotEqualToAnyFileID(Model model) { - return QueryBuilder.apply(this, (query) { - if (query.whereSort == Sort.asc) { - return query - .addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [], - upper: [model], - includeUpper: false, - )) - .addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [model], - includeLower: false, - upper: [], - )); - } else { - return query - .addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [model], - includeLower: false, - upper: [], - )) - .addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [], - upper: [model], - includeUpper: false, - )); - } - }); - } - - QueryBuilder - modelGreaterThanAnyFileID( - Model model, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [model], - includeLower: include, - upper: [], - )); - }); - } - - QueryBuilder modelLessThanAnyFileID( - Model model, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [], - upper: [model], - includeUpper: include, - )); - }); - } - - QueryBuilder modelBetweenAnyFileID( - Model lowerModel, - Model upperModel, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [lowerModel], - includeLower: includeLower, - upper: [upperModel], - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder modelFileIDEqualTo( - Model model, int fileID) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.equalTo( - indexName: r'unique_file_model_embedding', - value: [model, fileID], - )); - }); - } - - QueryBuilder - modelEqualToFileIDNotEqualTo(Model model, int fileID) { - return QueryBuilder.apply(this, (query) { - if (query.whereSort == Sort.asc) { - return query - .addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [model], - upper: [model, fileID], - includeUpper: false, - )) - .addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [model, fileID], - includeLower: false, - upper: [model], - )); - } else { - return query - .addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [model, fileID], - includeLower: false, - upper: [model], - )) - .addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [model], - upper: [model, fileID], - includeUpper: false, - )); - } - }); - } - - QueryBuilder - modelEqualToFileIDGreaterThan( - Model model, - int fileID, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [model, fileID], - includeLower: include, - upper: [model], - )); - }); - } - - QueryBuilder - modelEqualToFileIDLessThan( - Model model, - int fileID, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [model], - upper: [model, fileID], - includeUpper: include, - )); - }); - } - - QueryBuilder - modelEqualToFileIDBetween( - Model model, - int lowerFileID, - int upperFileID, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.between( - indexName: r'unique_file_model_embedding', - lower: [model, lowerFileID], - includeLower: includeLower, - upper: [model, upperFileID], - includeUpper: includeUpper, - )); - }); - } -} - -extension EmbeddingQueryFilter - on QueryBuilder { - QueryBuilder - embeddingElementEqualTo( - double value, { - double epsilon = Query.epsilon, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'embedding', - value: value, - epsilon: epsilon, - )); - }); - } - - QueryBuilder - embeddingElementGreaterThan( - double value, { - bool include = false, - double epsilon = Query.epsilon, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'embedding', - value: value, - epsilon: epsilon, - )); - }); - } - - QueryBuilder - embeddingElementLessThan( - double value, { - bool include = false, - double epsilon = Query.epsilon, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'embedding', - value: value, - epsilon: epsilon, - )); - }); - } - - QueryBuilder - embeddingElementBetween( - double lower, - double upper, { - bool includeLower = true, - bool includeUpper = true, - double epsilon = Query.epsilon, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'embedding', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - epsilon: epsilon, - )); - }); - } - - QueryBuilder - embeddingLengthEqualTo(int length) { - return QueryBuilder.apply(this, (query) { - return query.listLength( - r'embedding', - length, - true, - length, - true, - ); - }); - } - - QueryBuilder embeddingIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.listLength( - r'embedding', - 0, - true, - 0, - true, - ); - }); - } - - QueryBuilder - embeddingIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.listLength( - r'embedding', - 0, - false, - 999999, - true, - ); - }); - } - - QueryBuilder - embeddingLengthLessThan( - int length, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.listLength( - r'embedding', - 0, - true, - length, - include, - ); - }); - } - - QueryBuilder - embeddingLengthGreaterThan( - int length, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.listLength( - r'embedding', - length, - include, - 999999, - true, - ); - }); - } - - QueryBuilder - embeddingLengthBetween( - int lower, - int upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.listLength( - r'embedding', - lower, - includeLower, - upper, - includeUpper, - ); - }); - } - - QueryBuilder fileIDEqualTo( - int value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'fileID', - value: value, - )); - }); - } - - QueryBuilder fileIDGreaterThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'fileID', - value: value, - )); - }); - } - - QueryBuilder fileIDLessThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'fileID', - value: value, - )); - }); - } - - QueryBuilder fileIDBetween( - int lower, - int upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'fileID', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder idEqualTo( - Id value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'id', - value: value, - )); - }); - } - - QueryBuilder idGreaterThan( - Id value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'id', - value: value, - )); - }); - } - - QueryBuilder idLessThan( - Id value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'id', - value: value, - )); - }); - } - - QueryBuilder idBetween( - Id lower, - Id upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'id', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder modelEqualTo( - Model value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'model', - value: value, - )); - }); - } - - QueryBuilder modelGreaterThan( - Model value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'model', - value: value, - )); - }); - } - - QueryBuilder modelLessThan( - Model value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'model', - value: value, - )); - }); - } - - QueryBuilder modelBetween( - Model lower, - Model upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'model', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - updationTimeIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'updationTime', - )); - }); - } - - QueryBuilder - updationTimeIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'updationTime', - )); - }); - } - - QueryBuilder updationTimeEqualTo( - int? value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'updationTime', - value: value, - )); - }); - } - - QueryBuilder - updationTimeGreaterThan( - int? value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'updationTime', - value: value, - )); - }); - } - - QueryBuilder - updationTimeLessThan( - int? value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'updationTime', - value: value, - )); - }); - } - - QueryBuilder updationTimeBetween( - int? lower, - int? upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'updationTime', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } -} - -extension EmbeddingQueryObject - on QueryBuilder {} - -extension EmbeddingQueryLinks - on QueryBuilder {} - -extension EmbeddingQuerySortBy on QueryBuilder { - QueryBuilder sortByFileID() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'fileID', Sort.asc); - }); - } - - QueryBuilder sortByFileIDDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'fileID', Sort.desc); - }); - } - - QueryBuilder sortByModel() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'model', Sort.asc); - }); - } - - QueryBuilder sortByModelDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'model', Sort.desc); - }); - } - - QueryBuilder sortByUpdationTime() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updationTime', Sort.asc); - }); - } - - QueryBuilder sortByUpdationTimeDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updationTime', Sort.desc); - }); - } -} - -extension EmbeddingQuerySortThenBy - on QueryBuilder { - QueryBuilder thenByFileID() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'fileID', Sort.asc); - }); - } - - QueryBuilder thenByFileIDDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'fileID', Sort.desc); - }); - } - - QueryBuilder thenById() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'id', Sort.asc); - }); - } - - QueryBuilder thenByIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'id', Sort.desc); - }); - } - - QueryBuilder thenByModel() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'model', Sort.asc); - }); - } - - QueryBuilder thenByModelDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'model', Sort.desc); - }); - } - - QueryBuilder thenByUpdationTime() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updationTime', Sort.asc); - }); - } - - QueryBuilder thenByUpdationTimeDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updationTime', Sort.desc); - }); - } -} - -extension EmbeddingQueryWhereDistinct - on QueryBuilder { - QueryBuilder distinctByEmbedding() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'embedding'); - }); - } - - QueryBuilder distinctByFileID() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'fileID'); - }); - } - - QueryBuilder distinctByModel() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'model'); - }); - } - - QueryBuilder distinctByUpdationTime() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'updationTime'); - }); - } -} - -extension EmbeddingQueryProperty - on QueryBuilder { - QueryBuilder idProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'id'); - }); - } - - QueryBuilder, QQueryOperations> embeddingProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'embedding'); - }); - } - - QueryBuilder fileIDProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'fileID'); - }); - } - - QueryBuilder modelProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'model'); - }); - } - - QueryBuilder updationTimeProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'updationTime'); - }); - } -} diff --git a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart index 0587ff522..eb5bfeb91 100644 --- a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart +++ b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart @@ -73,6 +73,7 @@ class SemanticSearchService { ? ONNX(shouldDownloadOverMobileData) : GGML(shouldDownloadOverMobileData); await EmbeddingStore.instance.init(); + await EmbeddingsDB.instance.init(); await _loadEmbeddings(); Bus.instance.on().listen((event) { _embeddingLoaderDebouncer.run(() async { From 9d6059a1762fcab38893f72b20b21e8db8d6ea25 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 30 Apr 2024 20:06:40 +0530 Subject: [PATCH 089/367] v0.8.86 --- mobile/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 0ad736a1a..ea01ba086 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.85+605 +version: 0.8.86+606 publish_to: none environment: From 54e8d64b9e0ad4ac6d80bd925e012da9686a0ecf Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 22:46:04 +0530 Subject: [PATCH 090/367] Fix the loading of utils/index Naming it index doesn't cause isDev to be loaded. --- desktop/src/main.ts | 2 +- desktop/src/main/log.ts | 2 +- desktop/src/main/menu.ts | 2 +- desktop/src/main/services/dir.ts | 2 +- desktop/src/main/services/ffmpeg.ts | 2 +- desktop/src/main/services/image.ts | 2 +- desktop/src/main/services/watch.ts | 2 +- desktop/src/main/utils/{index.ts => electron.ts} | 0 8 files changed, 7 insertions(+), 7 deletions(-) rename desktop/src/main/utils/{index.ts => electron.ts} (100%) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index c849c755f..8beb45881 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -26,7 +26,7 @@ import { createWatcher } from "./main/services/watch"; import { userPreferences } from "./main/stores/user-preferences"; import { migrateLegacyWatchStoreIfNeeded } from "./main/stores/watch"; import { registerStreamProtocol } from "./main/stream"; -import { isDev } from "./main/utils"; +import { isDev } from "./main/utils/electron"; /** * The URL where the renderer HTML is being served from. diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index c4d2f3cbb..cf1404a90 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -1,6 +1,6 @@ import log from "electron-log"; import util from "node:util"; -import { isDev } from "./utils"; +import { isDev } from "./utils/electron"; /** * Initialize logging in the main process. diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index 1019c7a8f..024a226f1 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -10,7 +10,7 @@ import { forceCheckForAppUpdates } from "./services/app-update"; import autoLauncher from "./services/auto-launcher"; import { openLogDirectory } from "./services/dir"; import { userPreferences } from "./stores/user-preferences"; -import { isDev } from "./utils"; +import { isDev } from "./utils/electron"; /** Create and return the entries in the app's main menu bar */ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { diff --git a/desktop/src/main/services/dir.ts b/desktop/src/main/services/dir.ts index ef3adb013..4e2a8c65e 100644 --- a/desktop/src/main/services/dir.ts +++ b/desktop/src/main/services/dir.ts @@ -1,7 +1,7 @@ import { shell } from "electron/common"; import { app, dialog } from "electron/main"; import path from "node:path"; -import { posixPath } from "../utils"; +import { posixPath } from "../utils/electron"; export const selectDirectory = async () => { const result = await dialog.showOpenDialog({ diff --git a/desktop/src/main/services/ffmpeg.ts b/desktop/src/main/services/ffmpeg.ts index 850b70d44..0a5c4eed2 100644 --- a/desktop/src/main/services/ffmpeg.ts +++ b/desktop/src/main/services/ffmpeg.ts @@ -2,8 +2,8 @@ import pathToFfmpeg from "ffmpeg-static"; import fs from "node:fs/promises"; import type { ZipItem } from "../../types/ipc"; import log from "../log"; -import { execAsync } from "../utils"; import { ensure, withTimeout } from "../utils/common"; +import { execAsync } from "../utils/electron"; import { deleteTempFile, makeFileForDataOrPathOrZipItem, diff --git a/desktop/src/main/services/image.ts b/desktop/src/main/services/image.ts index d607b0ead..957fe8120 100644 --- a/desktop/src/main/services/image.ts +++ b/desktop/src/main/services/image.ts @@ -4,7 +4,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { CustomErrorMessage, type ZipItem } from "../../types/ipc"; import log from "../log"; -import { execAsync, isDev } from "../utils"; +import { execAsync, isDev } from "../utils/electron"; import { deleteTempFile, makeFileForDataOrPathOrZipItem, diff --git a/desktop/src/main/services/watch.ts b/desktop/src/main/services/watch.ts index ca550a787..a56c0cf6c 100644 --- a/desktop/src/main/services/watch.ts +++ b/desktop/src/main/services/watch.ts @@ -5,7 +5,7 @@ import path from "node:path"; import { FolderWatch, type CollectionMapping } from "../../types/ipc"; import log from "../log"; import { watchStore } from "../stores/watch"; -import { posixPath } from "../utils"; +import { posixPath } from "../utils/electron"; import { fsIsDir } from "./fs"; /** diff --git a/desktop/src/main/utils/index.ts b/desktop/src/main/utils/electron.ts similarity index 100% rename from desktop/src/main/utils/index.ts rename to desktop/src/main/utils/electron.ts From a0d44b58e218f5a83adbd9e9138a4d4054f4e391 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 30 Apr 2024 22:48:10 +0530 Subject: [PATCH 091/367] Fix load Using .on("ready" was not causing the window to start loading the renderer unless createWindow was made async. --- desktop/src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 8beb45881..eb1114cc4 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -365,7 +365,7 @@ const main = () => { // Emitted once, when Electron has finished initializing. // // Note that some Electron APIs can only be used after this event occurs. - app.on("ready", () => { + void app.whenReady().then(() => { void (async () => { // Create window and prepare for the renderer. mainWindow = createMainWindow(); From ac9f5095d1c584303d3331fa797b34ea5050cf8b Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Wed, 1 May 2024 00:57:54 +0530 Subject: [PATCH 092/367] fix: rpm deps and webview package --- auth/linux/packaging/rpm/make_config.yaml | 2 +- auth/pubspec.lock | 6 +++--- auth/pubspec.yaml | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/auth/linux/packaging/rpm/make_config.yaml b/auth/linux/packaging/rpm/make_config.yaml index 5d5f3aab5..e82dd63bf 100644 --- a/auth/linux/packaging/rpm/make_config.yaml +++ b/auth/linux/packaging/rpm/make_config.yaml @@ -11,7 +11,7 @@ display_name: Auth requires: - libsqlite3x - - webkit2gtk-4.0 + - webkit2gtk4.0 - libsodium - libsecret - libappindicator diff --git a/auth/pubspec.lock b/auth/pubspec.lock index 2d61b77c3..772416042 100644 --- a/auth/pubspec.lock +++ b/auth/pubspec.lock @@ -293,9 +293,9 @@ packages: dependency: "direct main" description: path: "packages/desktop_webview_window" - ref: HEAD - resolved-ref: "8cbbf9cd6efcfee5e0f420a36f7f8e7e64b667a1" - url: "https://github.com/MixinNetwork/flutter-plugins" + ref: fix-webkit-version + resolved-ref: fe2223e4edfecdbb3a97bb9e3ced73db4ae9d979 + url: "https://github.com/ente-io/flutter-desktopwebview-fork" source: git version: "0.2.4" device_info_plus: diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index 2ef543aa6..cdc39383d 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -20,7 +20,8 @@ dependencies: convert: ^3.1.1 desktop_webview_window: git: - url: https://github.com/MixinNetwork/flutter-plugins + url: https://github.com/ente-io/flutter-desktopwebview-fork + ref: fix-webkit-version path: packages/desktop_webview_window device_info_plus: ^9.1.1 dio: ^5.4.0 From d0d5ead80be86fc52770887e711e89a9efca7fdc Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Wed, 1 May 2024 01:07:13 +0530 Subject: [PATCH 093/367] fix: install patchelf for dynamic linking --- .github/workflows/auth-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auth-release.yml b/.github/workflows/auth-release.yml index cc3e598e3..707bae895 100644 --- a/.github/workflows/auth-release.yml +++ b/.github/workflows/auth-release.yml @@ -85,7 +85,7 @@ jobs: - name: Install dependencies for desktop build run: | sudo apt-get update -y - sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate appindicator3-0.1 libappindicator3-dev libffi-dev libtiff5 + sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm patchelf libsqlite3-dev locate appindicator3-0.1 libappindicator3-dev libffi-dev libtiff5 sudo updatedb --localpaths='/usr/lib/x86_64-linux-gnu' - name: Install appimagetool From b39b470b959095f0c7180326daadfa9bd77ba182 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Wed, 1 May 2024 01:48:32 +0530 Subject: [PATCH 094/367] chore: bump version --- auth/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index cdc39383d..388f76869 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -1,6 +1,6 @@ name: ente_auth description: ente two-factor authenticator -version: 2.0.55+255 +version: 2.0.56+256 publish_to: none environment: From 4e8f2e65f032aeb211e6022d6506d9205a5101d4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 08:15:43 +0530 Subject: [PATCH 095/367] Handle undefined better --- desktop/src/main/menu.ts | 2 +- desktop/src/main/services/app-update.ts | 7 ++- desktop/src/main/services/store.ts | 2 +- desktop/src/main/services/upload.ts | 61 ++++++++++++++++++--- desktop/src/main/stores/safe-storage.ts | 2 +- desktop/src/main/stores/upload-status.ts | 8 +-- desktop/src/main/stores/user-preferences.ts | 2 +- 7 files changed, 66 insertions(+), 18 deletions(-) diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index 024a226f1..b6fa7acfe 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -19,7 +19,7 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { // Whenever the menu is redrawn the current value of these variables is used // to set the checked state for the various settings checkboxes. let isAutoLaunchEnabled = await autoLauncher.isEnabled(); - let shouldHideDockIcon = userPreferences.get("hideDockIcon"); + let shouldHideDockIcon = !!userPreferences.get("hideDockIcon"); const macOSOnly = (options: MenuItemConstructorOptions[]) => process.platform == "darwin" ? options : []; diff --git a/desktop/src/main/services/app-update.ts b/desktop/src/main/services/app-update.ts index bc4bd38d6..8d66cb8c3 100644 --- a/desktop/src/main/services/app-update.ts +++ b/desktop/src/main/services/app-update.ts @@ -36,18 +36,21 @@ const checkForUpdatesAndNotify = async (mainWindow: BrowserWindow) => { log.debug(() => `Update check found version ${version}`); + if (!version) + throw new Error("Unexpected empty version obtained from auto-updater"); + if (compareVersions(version, app.getVersion()) <= 0) { log.debug(() => "Skipping update, already at latest version"); return; } - if (version === userPreferences.get("skipAppVersion")) { + if (version == userPreferences.get("skipAppVersion")) { log.info(`User chose to skip version ${version}`); return; } const mutedVersion = userPreferences.get("muteUpdateNotificationVersion"); - if (version === mutedVersion) { + if (version == mutedVersion) { log.info(`User has muted update notifications for version ${version}`); return; } diff --git a/desktop/src/main/services/store.ts b/desktop/src/main/services/store.ts index 1884efbc5..20cc91ea4 100644 --- a/desktop/src/main/services/store.ts +++ b/desktop/src/main/services/store.ts @@ -9,8 +9,8 @@ import { watchStore } from "../stores/watch"; * This is useful to reset state when the user logs out. */ export const clearStores = () => { - uploadStatusStore.clear(); safeStorageStore.clear(); + uploadStatusStore.clear(); watchStore.clear(); }; diff --git a/desktop/src/main/services/upload.ts b/desktop/src/main/services/upload.ts index 7871b56fd..795ce48af 100644 --- a/desktop/src/main/services/upload.ts +++ b/desktop/src/main/services/upload.ts @@ -46,7 +46,7 @@ export const pathOrZipItemSize = async ( }; export const pendingUploads = async (): Promise => { - const collectionName = uploadStatusStore.get("collectionName"); + const collectionName = uploadStatusStore.get("collectionName") ?? undefined; const allFilePaths = uploadStatusStore.get("filePaths") ?? []; const filePaths = allFilePaths.filter((f) => existsSync(f)); @@ -62,7 +62,7 @@ export const pendingUploads = async (): Promise => { // // This potentially can be cause us to try reuploading an already uploaded // file, but the dedup logic will kick in at that point so no harm will come - // off it. + // of it. if (allZipItems === undefined) { const allZipPaths = uploadStatusStore.get("filePaths") ?? []; const zipPaths = allZipPaths.filter((f) => existsSync(f)); @@ -82,20 +82,65 @@ export const pendingUploads = async (): Promise => { }; }; -export const setPendingUploads = (pendingUploads: PendingUploads) => - uploadStatusStore.set(pendingUploads); +/** + * [Note: Missing values in electron-store] + * + * Suppose we were to create a store like this: + * + * const store = new Store({ + * schema: { + * foo: { type: "string" }, + * bars: { type: "array", items: { type: "string" } }, + * }, + * }); + * + * If we fetch `store.get("foo")` or `store.get("bars")`, we get `undefined`. + * But if we try to set these back to `undefined`, say `store.set("foo", + * someUndefValue)`, we get asked to + * + * TypeError: Use `delete()` to clear values + * + * This happens even if we do bulk object updates, e.g. with a JS object that + * has undefined keys: + * + * > TypeError: Setting a value of type `undefined` for key `collectionName` is + * > not allowed as it's not supported by JSON + * + * So what should the TypeScript type for "foo" be? + * + * If it is were to not include the possibility of `undefined`, then the type + * would lie because `store.get("foo")` can indeed be `undefined. But if we were + * to include the possibility of `undefined`, then trying to `store.set("foo", + * someUndefValue)` will throw. + * + * The approach we take is to rely on false-y values (empty strings and empty + * arrays) to indicate missing values, and then converting those to `undefined` + * when reading from the store, and converting `undefined` to the corresponding + * false-y value when writing. + */ +export const setPendingUploads = ({ + collectionName, + filePaths, + zipItems, +}: PendingUploads) => { + uploadStatusStore.set({ + collectionName: collectionName ?? "", + filePaths: filePaths, + zipItems: zipItems, + }); +}; export const markUploadedFiles = (paths: string[]) => { - const existing = uploadStatusStore.get("filePaths"); - const updated = existing?.filter((p) => !paths.includes(p)); + const existing = uploadStatusStore.get("filePaths") ?? []; + const updated = existing.filter((p) => !paths.includes(p)); uploadStatusStore.set("filePaths", updated); }; export const markUploadedZipItems = ( items: [zipPath: string, entryName: string][], ) => { - const existing = uploadStatusStore.get("zipItems"); - const updated = existing?.filter( + const existing = uploadStatusStore.get("zipItems") ?? []; + const updated = existing.filter( (z) => !items.some((e) => z[0] == e[0] && z[1] == e[1]), ); uploadStatusStore.set("zipItems", updated); diff --git a/desktop/src/main/stores/safe-storage.ts b/desktop/src/main/stores/safe-storage.ts index 1e1369db8..040af1f3e 100644 --- a/desktop/src/main/stores/safe-storage.ts +++ b/desktop/src/main/stores/safe-storage.ts @@ -1,7 +1,7 @@ import Store, { Schema } from "electron-store"; interface SafeStorageStore { - encryptionKey: string; + encryptionKey?: string; } const safeStorageSchema: Schema = { diff --git a/desktop/src/main/stores/upload-status.ts b/desktop/src/main/stores/upload-status.ts index f098e9fc5..472f38a7f 100644 --- a/desktop/src/main/stores/upload-status.ts +++ b/desktop/src/main/stores/upload-status.ts @@ -6,24 +6,24 @@ export interface UploadStatusStore { * * Not all pending uploads will have an associated collection. */ - collectionName: string | undefined; + collectionName?: string; /** * Paths to regular files that are pending upload. * * This should generally be present, albeit empty, but it is marked optional * in sympathy with its siblings. */ - filePaths: string[] | undefined; + filePaths?: string[]; /** * Each item is the path to a zip file and the name of an entry within it. * * This is marked optional since legacy stores will not have it. */ - zipItems: [zipPath: string, entryName: string][] | undefined; + zipItems?: [zipPath: string, entryName: string][]; /** * @deprecated Legacy paths to zip files, now subsumed into zipItems. */ - zipPaths: string[] | undefined; + zipPaths?: string[]; } const uploadStatusSchema: Schema = { diff --git a/desktop/src/main/stores/user-preferences.ts b/desktop/src/main/stores/user-preferences.ts index b4a02bc5b..f3b192989 100644 --- a/desktop/src/main/stores/user-preferences.ts +++ b/desktop/src/main/stores/user-preferences.ts @@ -1,7 +1,7 @@ import Store, { Schema } from "electron-store"; interface UserPreferences { - hideDockIcon: boolean; + hideDockIcon?: boolean; skipAppVersion?: string; muteUpdateNotificationVersion?: string; } From 6c716ad8925ba77a882c0daef9186db573200d2a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 08:30:35 +0530 Subject: [PATCH 096/367] Tell prettier not to print the names of the files it processes --- desktop/package.json | 4 ++-- web/package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/desktop/package.json b/desktop/package.json index d4c571cad..8cbe1b755 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -15,8 +15,8 @@ "dev-main": "tsc && electron app/main.js", "dev-renderer": "cd ../web && yarn install && yarn dev:photos", "postinstall": "electron-builder install-app-deps", - "lint": "yarn prettier --check . && eslint --ext .ts src && yarn tsc", - "lint-fix": "yarn prettier --write . && eslint --fix --ext .ts src && yarn tsc" + "lint": "yarn prettier --check --log-level warn . && eslint --ext .ts src && yarn tsc", + "lint-fix": "yarn prettier --write --log-level warn . && eslint --fix --ext .ts src && yarn tsc" }, "dependencies": { "any-shell-escape": "^0.1", diff --git a/web/package.json b/web/package.json index 2d5919eb1..647ee3ba3 100644 --- a/web/package.json +++ b/web/package.json @@ -27,8 +27,8 @@ "dev:payments": "yarn workspace payments dev", "dev:photos": "yarn workspace photos next dev", "dev:staff": "yarn workspace staff dev", - "lint": "yarn prettier --check . && yarn workspaces run eslint --report-unused-disable-directives .", - "lint-fix": "yarn prettier --write . && yarn workspaces run eslint --fix .", + "lint": "yarn prettier --check --log-level warn . && yarn workspaces run eslint --report-unused-disable-directives .", + "lint-fix": "yarn prettier --write --log-level warn . && yarn workspaces run eslint --fix .", "preview": "yarn preview:photos", "preview:accounts": "yarn build:accounts && python3 -m http.server -d apps/accounts/out 3001", "preview:auth": "yarn build:auth && python3 -m http.server -d apps/auth/out 3000", From 9cbf69f9b3871b5529b84669834e4dfcc2b30193 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 08:46:43 +0530 Subject: [PATCH 097/367] upgrade-interactive --- desktop/yarn.lock | 197 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 138 insertions(+), 59 deletions(-) diff --git a/desktop/yarn.lock b/desktop/yarn.lock index bf9057d47..ddfe37183 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -181,6 +181,13 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + "@malept/cross-spawn-promise@^1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz#504af200af6b98e198bce768bc1730c6936ae01d" @@ -357,15 +364,15 @@ "@types/node" "*" "@typescript-eslint/eslint-plugin@^7": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz#1f5df5cda490a0bcb6fbdd3382e19f1241024242" - integrity sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A== + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz#c78e309fe967cb4de05b85cdc876fb95f8e01b6f" + integrity sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "7.6.0" - "@typescript-eslint/type-utils" "7.6.0" - "@typescript-eslint/utils" "7.6.0" - "@typescript-eslint/visitor-keys" "7.6.0" + "@typescript-eslint/scope-manager" "7.8.0" + "@typescript-eslint/type-utils" "7.8.0" + "@typescript-eslint/utils" "7.8.0" + "@typescript-eslint/visitor-keys" "7.8.0" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.3.1" @@ -374,46 +381,46 @@ ts-api-utils "^1.3.0" "@typescript-eslint/parser@^7": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.6.0.tgz#0aca5de3045d68b36e88903d15addaf13d040a95" - integrity sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg== + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.8.0.tgz#1e1db30c8ab832caffee5f37e677dbcb9357ddc8" + integrity sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ== dependencies: - "@typescript-eslint/scope-manager" "7.6.0" - "@typescript-eslint/types" "7.6.0" - "@typescript-eslint/typescript-estree" "7.6.0" - "@typescript-eslint/visitor-keys" "7.6.0" + "@typescript-eslint/scope-manager" "7.8.0" + "@typescript-eslint/types" "7.8.0" + "@typescript-eslint/typescript-estree" "7.8.0" + "@typescript-eslint/visitor-keys" "7.8.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz#1e9972f654210bd7500b31feadb61a233f5b5e9d" - integrity sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w== +"@typescript-eslint/scope-manager@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz#bb19096d11ec6b87fb6640d921df19b813e02047" + integrity sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g== dependencies: - "@typescript-eslint/types" "7.6.0" - "@typescript-eslint/visitor-keys" "7.6.0" + "@typescript-eslint/types" "7.8.0" + "@typescript-eslint/visitor-keys" "7.8.0" -"@typescript-eslint/type-utils@7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz#644f75075f379827d25fe0713e252ccd4e4a428c" - integrity sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw== +"@typescript-eslint/type-utils@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz#9de166f182a6e4d1c5da76e94880e91831e3e26f" + integrity sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A== dependencies: - "@typescript-eslint/typescript-estree" "7.6.0" - "@typescript-eslint/utils" "7.6.0" + "@typescript-eslint/typescript-estree" "7.8.0" + "@typescript-eslint/utils" "7.8.0" debug "^4.3.4" ts-api-utils "^1.3.0" -"@typescript-eslint/types@7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.6.0.tgz#53dba7c30c87e5f10a731054266dd905f1fbae38" - integrity sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ== +"@typescript-eslint/types@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.8.0.tgz#1fd2577b3ad883b769546e2d1ef379f929a7091d" + integrity sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw== -"@typescript-eslint/typescript-estree@7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz#112a3775563799fd3f011890ac8322f80830ac17" - integrity sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw== +"@typescript-eslint/typescript-estree@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz#b028a9226860b66e623c1ee55cc2464b95d2987c" + integrity sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg== dependencies: - "@typescript-eslint/types" "7.6.0" - "@typescript-eslint/visitor-keys" "7.6.0" + "@typescript-eslint/types" "7.8.0" + "@typescript-eslint/visitor-keys" "7.8.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -421,25 +428,25 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.6.0.tgz#e400d782280b6f724c8a1204269d984c79202282" - integrity sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA== +"@typescript-eslint/utils@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.8.0.tgz#57a79f9c0c0740ead2f622e444cfaeeb9fd047cd" + integrity sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.15" "@types/semver" "^7.5.8" - "@typescript-eslint/scope-manager" "7.6.0" - "@typescript-eslint/types" "7.6.0" - "@typescript-eslint/typescript-estree" "7.6.0" + "@typescript-eslint/scope-manager" "7.8.0" + "@typescript-eslint/types" "7.8.0" + "@typescript-eslint/typescript-estree" "7.8.0" semver "^7.6.0" -"@typescript-eslint/visitor-keys@7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz#d1ce13145844379021e1f9bd102c1d78946f4e76" - integrity sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw== +"@typescript-eslint/visitor-keys@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz#7285aab991da8bee411a42edbd5db760d22fdd91" + integrity sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA== dependencies: - "@typescript-eslint/types" "7.6.0" + "@typescript-eslint/types" "7.8.0" eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0": @@ -829,6 +836,11 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + chromium-pickle-js@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205" @@ -1637,6 +1649,17 @@ glob@^10.3.10: minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-scurry "^1.10.1" +glob@^10.3.7: + version "10.3.12" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.12.tgz#3a65c363c2e9998d220338e88a5f6ac97302960b" + integrity sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.6" + minimatch "^9.0.1" + minipass "^7.0.4" + path-scurry "^1.10.2" + glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -1954,7 +1977,7 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -jackspeak@^2.3.5: +jackspeak@^2.3.5, jackspeak@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== @@ -2126,6 +2149,11 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +lru-cache@^10.2.0: + version "10.2.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" + integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -2240,7 +2268,7 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== @@ -2253,6 +2281,14 @@ minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" +minizlib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" + integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== + dependencies: + minipass "^7.0.4" + rimraf "^5.0.5" + mkdirp@^0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -2265,6 +2301,11 @@ mkdirp@^1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -2329,17 +2370,18 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -onnxruntime-common@1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/onnxruntime-common/-/onnxruntime-common-1.17.0.tgz#b2534ce021b1c1b19182bec39aaea8d547d2013e" - integrity sha512-Vq1remJbCPITjDMJ04DA7AklUTnbYUp4vbnm6iL7ukSt+7VErH0NGYfekRSTjxxurEtX7w41PFfnQlE6msjPJw== +onnxruntime-common@1.17.3: + version "1.17.3" + resolved "https://registry.yarnpkg.com/onnxruntime-common/-/onnxruntime-common-1.17.3.tgz#aadc456477873a540ee3d611ae9cd4f3de7c43e5" + integrity sha512-IkbaDelNVX8cBfHFgsNADRIq2TlXMFWW+nG55mwWvQT4i0NZb32Jf35Pf6h9yjrnK78RjcnlNYaI37w394ovMw== onnxruntime-node@^1.17: - version "1.17.0" - resolved "https://registry.yarnpkg.com/onnxruntime-node/-/onnxruntime-node-1.17.0.tgz#38af0ba527cb44c1afb639bdcb4e549edba029a1" - integrity sha512-pRxdqSP3a6wtiFVkVX1V3/gsEMwBRUA9D2oYmcN3cjF+j+ILS+SIY2L7KxdWapsG6z64i5rUn8ijFZdIvbojBg== + version "1.17.3" + resolved "https://registry.yarnpkg.com/onnxruntime-node/-/onnxruntime-node-1.17.3.tgz#53b8b7ef68bf3834bba9d7be592e4c2d718d2018" + integrity sha512-NtbN1pfApTSEjVq46LrJ396aPP2Gjhy+oYZi5Bu1leDXAEvVap/BQ8CZELiLs7z0UnXy3xjJW23HiB4P3//FIw== dependencies: - onnxruntime-common "1.17.0" + onnxruntime-common "1.17.3" + tar "^7.0.1" optionator@^0.9.3: version "0.9.3" @@ -2453,6 +2495,14 @@ path-scurry@^1.10.1: lru-cache "^9.1.1 || ^10.0.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-scurry@^1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.2.tgz#8f6357eb1239d5fa1da8b9f70e9c080675458ba7" + integrity sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -2660,6 +2710,13 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.5.tgz#9be65d2d6e683447d2e9013da2bf451139a61ccf" + integrity sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A== + dependencies: + glob "^10.3.7" + roarr@^2.15.3: version "2.15.4" resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" @@ -2982,6 +3039,18 @@ tar@^6.1.12: mkdirp "^1.0.3" yallist "^4.0.0" +tar@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.0.1.tgz#8f6ccebcd91b69e9767a6fc4892799e8b0e606d5" + integrity sha512-IjMhdQMZFpKsHEQT3woZVxBtCQY+0wk3CVxdRkGXEgyGa0dNS/ehPvOMr2nmfC7x5Zj2N+l6yZUpmICjLGS35w== + dependencies: + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^5.0.0" + minizlib "^3.0.1" + mkdirp "^3.0.1" + yallist "^5.0.0" + temp-file@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/temp-file/-/temp-file-3.4.0.tgz#766ea28911c683996c248ef1a20eea04d51652c7" @@ -3083,7 +3152,12 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@^5, typescript@^5.3.3: +typescript@^5: + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== + +typescript@^5.3.3: version "5.4.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== @@ -3192,6 +3266,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" From e18731e625c57ba77a3f2b5320d290f0e822618c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 08:51:47 +0530 Subject: [PATCH 098/367] yarn upgrade-interactive typescript and lints --- web/yarn.lock | 157 ++++++++++++++++++++++++++------------------------ 1 file changed, 82 insertions(+), 75 deletions(-) diff --git a/web/yarn.lock b/web/yarn.lock index 6886647d7..af3a5f210 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -528,7 +528,7 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": version "4.10.0" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== @@ -1018,7 +1018,7 @@ "@types/react" "*" hoist-non-react-statics "^3.3.0" -"@types/json-schema@^7.0.12": +"@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -1134,10 +1134,10 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff" integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A== -"@types/semver@^7.5.0": - version "7.5.7" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.7.tgz#326f5fdda70d13580777bcaa1bc6fa772a5aef0e" - integrity sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg== +"@types/semver@^7.5.8": + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== "@types/uuid@^9.0.2": version "9.0.8" @@ -1150,21 +1150,21 @@ integrity sha512-Tuk4q7q0DnpzyJDI4aMeghGuFu2iS1QAdKpabn8JfbtfGmVDUgvZv1I7mEjP61Bvnp3ljKCC8BE6YYSTNxmvRQ== "@typescript-eslint/eslint-plugin@^7": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.2.tgz#c13a34057be425167cc4a765158c46fdf2fd981d" - integrity sha512-/XtVZJtbaphtdrWjr+CJclaCVGPtOdBpFEnvtNf/jRV0IiEemRrL0qABex/nEt8isYcnFacm3nPHYQwL+Wb7qg== + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz#c78e309fe967cb4de05b85cdc876fb95f8e01b6f" + integrity sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg== dependencies: - "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "7.0.2" - "@typescript-eslint/type-utils" "7.0.2" - "@typescript-eslint/utils" "7.0.2" - "@typescript-eslint/visitor-keys" "7.0.2" + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.8.0" + "@typescript-eslint/type-utils" "7.8.0" + "@typescript-eslint/utils" "7.8.0" + "@typescript-eslint/visitor-keys" "7.8.0" debug "^4.3.4" graphemer "^1.4.0" - ignore "^5.2.4" + ignore "^5.3.1" natural-compare "^1.4.0" - semver "^7.5.4" - ts-api-utils "^1.0.1" + semver "^7.6.0" + ts-api-utils "^1.3.0" "@typescript-eslint/parser@^5.4.2 || ^6.0.0": version "6.21.0" @@ -1178,14 +1178,14 @@ debug "^4.3.4" "@typescript-eslint/parser@^7": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.0.2.tgz#95c31233d343db1ca1df8df7811b5b87ca7b1a68" - integrity sha512-GdwfDglCxSmU+QTS9vhz2Sop46ebNCXpPPvsByK7hu0rFGRHL+AusKQJ7SoN+LbLh6APFpQwHKmDSwN35Z700Q== + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.8.0.tgz#1e1db30c8ab832caffee5f37e677dbcb9357ddc8" + integrity sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ== dependencies: - "@typescript-eslint/scope-manager" "7.0.2" - "@typescript-eslint/types" "7.0.2" - "@typescript-eslint/typescript-estree" "7.0.2" - "@typescript-eslint/visitor-keys" "7.0.2" + "@typescript-eslint/scope-manager" "7.8.0" + "@typescript-eslint/types" "7.8.0" + "@typescript-eslint/typescript-estree" "7.8.0" + "@typescript-eslint/visitor-keys" "7.8.0" debug "^4.3.4" "@typescript-eslint/scope-manager@6.21.0": @@ -1196,33 +1196,33 @@ "@typescript-eslint/types" "6.21.0" "@typescript-eslint/visitor-keys" "6.21.0" -"@typescript-eslint/scope-manager@7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.0.2.tgz#6ec4cc03752758ddd1fdaae6fbd0ed9a2ca4fe63" - integrity sha512-l6sa2jF3h+qgN2qUMjVR3uCNGjWw4ahGfzIYsCtFrQJCjhbrDPdiihYT8FnnqFwsWX+20hK592yX9I2rxKTP4g== +"@typescript-eslint/scope-manager@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz#bb19096d11ec6b87fb6640d921df19b813e02047" + integrity sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g== dependencies: - "@typescript-eslint/types" "7.0.2" - "@typescript-eslint/visitor-keys" "7.0.2" + "@typescript-eslint/types" "7.8.0" + "@typescript-eslint/visitor-keys" "7.8.0" -"@typescript-eslint/type-utils@7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.0.2.tgz#a7fc0adff0c202562721357e7478207d380a757b" - integrity sha512-IKKDcFsKAYlk8Rs4wiFfEwJTQlHcdn8CLwLaxwd6zb8HNiMcQIFX9sWax2k4Cjj7l7mGS5N1zl7RCHOVwHq2VQ== +"@typescript-eslint/type-utils@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz#9de166f182a6e4d1c5da76e94880e91831e3e26f" + integrity sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A== dependencies: - "@typescript-eslint/typescript-estree" "7.0.2" - "@typescript-eslint/utils" "7.0.2" + "@typescript-eslint/typescript-estree" "7.8.0" + "@typescript-eslint/utils" "7.8.0" debug "^4.3.4" - ts-api-utils "^1.0.1" + ts-api-utils "^1.3.0" "@typescript-eslint/types@6.21.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== -"@typescript-eslint/types@7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.0.2.tgz#b6edd108648028194eb213887d8d43ab5750351c" - integrity sha512-ZzcCQHj4JaXFjdOql6adYV4B/oFOFjPOC9XYwCaZFRvqN8Llfvv4gSxrkQkd2u4Ci62i2c6W6gkDwQJDaRc4nA== +"@typescript-eslint/types@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.8.0.tgz#1fd2577b3ad883b769546e2d1ef379f929a7091d" + integrity sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw== "@typescript-eslint/typescript-estree@6.21.0": version "6.21.0" @@ -1238,32 +1238,32 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/typescript-estree@7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.2.tgz#3c6dc8a3b9799f4ef7eca0d224ded01974e4cb39" - integrity sha512-3AMc8khTcELFWcKcPc0xiLviEvvfzATpdPj/DXuOGIdQIIFybf4DMT1vKRbuAEOFMwhWt7NFLXRkbjsvKZQyvw== +"@typescript-eslint/typescript-estree@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz#b028a9226860b66e623c1ee55cc2464b95d2987c" + integrity sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg== dependencies: - "@typescript-eslint/types" "7.0.2" - "@typescript-eslint/visitor-keys" "7.0.2" + "@typescript-eslint/types" "7.8.0" + "@typescript-eslint/visitor-keys" "7.8.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" - minimatch "9.0.3" - semver "^7.5.4" - ts-api-utils "^1.0.1" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" -"@typescript-eslint/utils@7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.0.2.tgz#8756123054cd934c8ba7db6a6cffbc654b10b5c4" - integrity sha512-PZPIONBIB/X684bhT1XlrkjNZJIEevwkKDsdwfiu1WeqBxYEEdIgVDgm8/bbKHVu+6YOpeRqcfImTdImx/4Bsw== +"@typescript-eslint/utils@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.8.0.tgz#57a79f9c0c0740ead2f622e444cfaeeb9fd047cd" + integrity sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@types/json-schema" "^7.0.12" - "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "7.0.2" - "@typescript-eslint/types" "7.0.2" - "@typescript-eslint/typescript-estree" "7.0.2" - semver "^7.5.4" + "@types/json-schema" "^7.0.15" + "@types/semver" "^7.5.8" + "@typescript-eslint/scope-manager" "7.8.0" + "@typescript-eslint/types" "7.8.0" + "@typescript-eslint/typescript-estree" "7.8.0" + semver "^7.6.0" "@typescript-eslint/visitor-keys@6.21.0": version "6.21.0" @@ -1273,13 +1273,13 @@ "@typescript-eslint/types" "6.21.0" eslint-visitor-keys "^3.4.1" -"@typescript-eslint/visitor-keys@7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.2.tgz#2899b716053ad7094962beb895d11396fc12afc7" - integrity sha512-8Y+YiBmqPighbm5xA2k4wKTxRzx9EkBu7Rlw+WHqMvRJ3RPz/BMBO9b2ru0LUNmXg120PHUXD5+SWFy2R8DqlQ== +"@typescript-eslint/visitor-keys@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz#7285aab991da8bee411a42edbd5db760d22fdd91" + integrity sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA== dependencies: - "@typescript-eslint/types" "7.0.2" - eslint-visitor-keys "^3.4.1" + "@typescript-eslint/types" "7.8.0" + eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0": version "1.2.0" @@ -2893,7 +2893,7 @@ ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.2.0, ignore@^5.2.4: +ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== @@ -3449,6 +3449,13 @@ minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -4173,7 +4180,7 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.4: +semver@^7.5.4, semver@^7.6.0: version "7.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== @@ -4565,10 +4572,10 @@ truncate-utf8-bytes@^1.0.0: dependencies: utf8-byte-length "^1.0.1" -ts-api-utils@^1.0.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.2.1.tgz#f716c7e027494629485b21c0df6180f4d08f5e8b" - integrity sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA== +ts-api-utils@^1.0.1, ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== tsconfig-paths@^3.15.0: version "3.15.0" @@ -4659,9 +4666,9 @@ typed-array-length@^1.0.6: possible-typed-array-names "^1.0.0" typescript@^5: - version "5.3.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" - integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== unbox-primitive@^1.0.2: version "1.0.2" From 42b214a4ba45b490253317be8b0799c5a68c3276 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 09:05:12 +0530 Subject: [PATCH 099/367] Remove duplicate state and reset store on successful completion Clears out the collection name --- .../src/services/upload/uploadManager.ts | 37 +++---------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/web/apps/photos/src/services/upload/uploadManager.ts b/web/apps/photos/src/services/upload/uploadManager.ts index 99fe6ced3..3d53adbea 100644 --- a/web/apps/photos/src/services/upload/uploadManager.ts +++ b/web/apps/photos/src/services/upload/uploadManager.ts @@ -321,7 +321,6 @@ class UploadManager { >(maxConcurrentUploads); private parsedMetadataJSONMap: Map; private itemsToBeUploaded: ClusteredUploadItem[]; - private remainingItems: ClusteredUploadItem[] = []; private failedItems: ClusteredUploadItem[]; private existingFiles: EnteFile[]; private setFiles: SetFiles; @@ -360,7 +359,6 @@ class UploadManager { private resetState() { this.itemsToBeUploaded = []; - this.remainingItems = []; this.failedItems = []; this.parsedMetadataJSONMap = new Map(); @@ -440,17 +438,13 @@ class UploadManager { await this.uploadMediaItems(clusteredMediaItems); } } catch (e) { - if (e.message === CustomError.UPLOAD_CANCELLED) { - if (isElectron()) { - this.remainingItems = []; - await cancelRemainingUploads(); - } - } else { - log.error("Uploading failed", e); + if (e.message != CustomError.UPLOAD_CANCELLED) { + log.error("Upload failed", e); throw e; } } finally { this.uiService.setUploadStage(UPLOAD_STAGES.FINISH); + void globalThis.electron?.clearPendingUploads(); for (let i = 0; i < maxConcurrentUploads; i++) { this.cryptoWorkers[i]?.terminate(); } @@ -503,15 +497,8 @@ class UploadManager { private async uploadMediaItems(mediaItems: ClusteredUploadItem[]) { this.itemsToBeUploaded = [...this.itemsToBeUploaded, ...mediaItems]; - - if (isElectron()) { - this.remainingItems = [...this.remainingItems, ...mediaItems]; - } - this.uiService.reset(mediaItems.length); - await UploadService.setFileCount(mediaItems.length); - this.uiService.setUploadStage(UPLOAD_STAGES.UPLOADING); const uploadProcesses = []; @@ -584,8 +571,10 @@ class UploadManager { `Uploaded ${uploadableItem.fileName} with result ${uploadResult}`, ); try { + const electron = globalThis.electron; + if (electron) await markUploaded(electron, uploadableItem); + let decryptedFile: EnteFile; - await this.removeFromPendingUploads(uploadableItem); switch (uploadResult) { case UPLOAD_RESULT.FAILED: case UPLOAD_RESULT.BLOCKED: @@ -688,18 +677,6 @@ class UploadManager { this.setFiles((files) => sortFiles([...files, decryptedFile])); } - private async removeFromPendingUploads( - clusteredUploadItem: ClusteredUploadItem, - ) { - const electron = globalThis.electron; - if (electron) { - this.remainingItems = this.remainingItems.filter( - (f) => f.localID != clusteredUploadItem.localID, - ); - await markUploaded(electron, clusteredUploadItem); - } - } - public shouldAllowNewUpload = () => { return !this.uploadInProgress || watcher.isUploadRunning(); }; @@ -847,8 +824,6 @@ const markUploaded = async (electron: Electron, item: ClusteredUploadItem) => { } }; -const cancelRemainingUploads = () => ensureElectron().clearPendingUploads(); - /** * Go through the given files, combining any sibling image + video assets into a * single live photo when appropriate. From abee517f8c989739f6a718d64564a9366b14a48a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 09:13:20 +0530 Subject: [PATCH 100/367] Fix remote build by recreating yarn.lock --- desktop/yarn.lock | 151 +++++++++++++++++----------------------------- 1 file changed, 56 insertions(+), 95 deletions(-) diff --git a/desktop/yarn.lock b/desktop/yarn.lock index ddfe37183..436ae8438 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -7,11 +7,6 @@ resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.2.0.tgz#7a03314684dd6572b7dfa89e68ce31d60286854d" integrity sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A== -"@aashutoshrathi/word-wrap@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" - integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== - "@babel/code-frame@^7.0.0": version "7.24.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" @@ -20,25 +15,25 @@ "@babel/highlight" "^7.24.2" picocolors "^1.0.0" -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== +"@babel/helper-validator-identifier@^7.24.5": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" + integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== "@babel/highlight@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" - integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.5.tgz#bc0613f98e1dd0720e99b2a9ee3760194a704b6e" + integrity sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw== dependencies: - "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-validator-identifier" "^7.24.5" chalk "^2.4.2" js-tokens "^4.0.0" picocolors "^1.0.0" "@babel/runtime@^7.21.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" - integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" + integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== dependencies: regenerator-runtime "^0.14.0" @@ -165,9 +160,9 @@ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/object-schema@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" - integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== "@isaacs/cliui@^8.0.2": version "8.0.2" @@ -499,14 +494,14 @@ ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.4: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.6.3: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + version "8.13.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91" + integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA== dependencies: - fast-deep-equal "^3.1.1" + fast-deep-equal "^3.1.3" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" - uri-js "^4.2.2" + uri-js "^4.4.1" ansi-regex@^5.0.1: version "5.0.1" @@ -1043,7 +1038,7 @@ define-data-property@^1.0.1: es-errors "^1.3.0" gopd "^1.0.1" -define-properties@^1.1.3: +define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -1150,9 +1145,9 @@ eastasianwidth@^0.2.0: integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== ejs@^3.1.8: - version "3.1.9" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" - integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== dependencies: jake "^10.8.5" @@ -1232,9 +1227,9 @@ electron-updater@^6.1: tiny-typed-emitter "^2.1.0" electron@^29: - version "29.3.0" - resolved "https://registry.yarnpkg.com/electron/-/electron-29.3.0.tgz#8e65cb08e9c0952c66d3196e1b5c811c43b8c5b0" - integrity sha512-ZxFKm0/v48GSoBuO3DdnMlCYXefEUKUHLMsKxyXY4nZGgzbBKpF/X8haZa2paNj23CLfsCKBOtfc2vsEQiOOsA== + version "29.3.1" + resolved "https://registry.yarnpkg.com/electron/-/electron-29.3.1.tgz#87c82b2cd2c326f78f036499377a5448bea5d4bb" + integrity sha512-auge1/6RVqgUd6TgIq88wKdUCJi2cjESi3jy7d+6X4JzvBGprKBqMJ8JSSFpu/Px1YJrFUKAxfy6SC+TQf1uLw== dependencies: "@electron/get" "^2.0.0" "@types/node" "^20.9.0" @@ -1512,17 +1507,18 @@ find-up@^5.0.0: path-exists "^4.0.0" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== dependencies: - flatted "^3.1.0" + flatted "^3.2.9" + keyv "^4.5.3" rimraf "^3.0.2" -flatted@^3.1.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2" - integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ== +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== foreground-child@^3.1.0: version "3.1.1" @@ -1638,18 +1634,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^10.3.10: - version "10.3.10" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" - integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== - dependencies: - foreground-child "^3.1.0" - jackspeak "^2.3.5" - minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry "^1.10.1" - -glob@^10.3.7: +glob@^10.3.10, glob@^10.3.7: version "10.3.12" resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.12.tgz#3a65c363c2e9998d220338e88a5f6ac97302960b" integrity sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg== @@ -1692,11 +1677,12 @@ globals@^13.19.0: type-fest "^0.20.2" globalthis@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" - integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== dependencies: - define-properties "^1.1.3" + define-properties "^1.2.1" + gopd "^1.0.1" globby@^11.1.0: version "11.1.0" @@ -1977,7 +1963,7 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -jackspeak@^2.3.5, jackspeak@^2.3.6: +jackspeak@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== @@ -2077,7 +2063,7 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -keyv@^4.0.0: +keyv@^4.0.0, keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -2161,11 +2147,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -"lru-cache@^9.1.1 || ^10.0.0": - version "10.2.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" - integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== - matcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" @@ -2237,14 +2218,7 @@ minimatch@^5.0.1, minimatch@^5.1.1: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.1: - version "9.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.4: +minimatch@^9.0.1, minimatch@^9.0.4: version "9.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== @@ -2384,16 +2358,16 @@ onnxruntime-node@^1.17: tar "^7.0.1" optionator@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" - integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== dependencies: - "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" + word-wrap "^1.2.5" p-cancelable@^2.0.0: version "2.1.1" @@ -2487,14 +2461,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.10.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" - integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== - dependencies: - lru-cache "^9.1.1 || ^10.0.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.2.tgz#8f6357eb1239d5fa1da8b9f70e9c080675458ba7" @@ -3105,12 +3071,7 @@ ts-api-utils@^1.3.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== -tslib@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== - -tslib@^2.6.2: +tslib@^2.1.0, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -3152,16 +3113,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@^5: +typescript@^5, typescript@^5.3.3: version "5.4.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== -typescript@^5.3.3: - version "5.4.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" - integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== - undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" @@ -3182,7 +3138,7 @@ untildify@^3.0.2: resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9" integrity sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA== -uri-js@^4.2.2: +uri-js@^4.2.2, uri-js@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== @@ -3228,6 +3184,11 @@ winreg@1.2.4: resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b" integrity sha512-IHpzORub7kYlb8A43Iig3reOvlcBJGX9gZ0WycHhghHtA65X0LYnMRuJs+aH1abVnMJztQkvQNlltnbPi5aGIA== +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" From 7056e04c020e3eea853e092618d5da3b7fd24370 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 09:13:41 +0530 Subject: [PATCH 101/367] Fix lint for web Copy overrides from desktop --- web/packages/build-config/eslintrc-base.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/web/packages/build-config/eslintrc-base.js b/web/packages/build-config/eslintrc-base.js index b302be36d..3e65638c1 100644 --- a/web/packages/build-config/eslintrc-base.js +++ b/web/packages/build-config/eslintrc-base.js @@ -10,4 +10,20 @@ module.exports = { parserOptions: { project: true }, parser: "@typescript-eslint/parser", ignorePatterns: [".eslintrc.js"], + rules: { + /* Allow numbers to be used in template literals */ + "@typescript-eslint/restrict-template-expressions": [ + "error", + { + allowNumber: true, + }, + ], + /* Allow void expressions as the entire body of an arrow function */ + "@typescript-eslint/no-confusing-void-expression": [ + "error", + { + ignoreArrowShorthand: true, + }, + ], + }, }; From 735213b474d68e8561e23c03d2dfefd1c9810d32 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 09:15:09 +0530 Subject: [PATCH 102/367] linter linter on the wall who's the fairest of them all --- web/apps/staff/src/App.tsx | 2 +- web/packages/next/log.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/apps/staff/src/App.tsx b/web/apps/staff/src/App.tsx index f8984fecb..01d79b18c 100644 --- a/web/apps/staff/src/App.tsx +++ b/web/apps/staff/src/App.tsx @@ -9,7 +9,7 @@ export const App: React.FC = () => { .then((userDetails) => { console.log("Fetched user details", userDetails); }) - .catch((e) => { + .catch((e: unknown) => { console.error("Failed to fetch user details", e); }); }; diff --git a/web/packages/next/log.ts b/web/packages/next/log.ts index a04520ed3..f9ef7e549 100644 --- a/web/packages/next/log.ts +++ b/web/packages/next/log.ts @@ -17,7 +17,7 @@ export const logToDisk = (message: string) => { }; const workerLogToDisk = (message: string) => { - workerBridge.logToDisk(message).catch((e) => { + workerBridge.logToDisk(message).catch((e: unknown) => { console.error( "Failed to log a message from worker", e, From eb1d8a8210a2420a772fdba8f419575b82959fc2 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 09:26:19 +0530 Subject: [PATCH 103/367] Add a workaround for broken yarn classic dependency resolution ...that causes `yarn install` to fail. Ref: - https://github.com/isaacs/jackspeak/issues/5 - https://github.com/yargs/cliui/issues/159 --- desktop/package.json | 3 ++ desktop/yarn.lock | 73 +++++--------------------------------------- 2 files changed, 11 insertions(+), 65 deletions(-) diff --git a/desktop/package.json b/desktop/package.json index 8cbe1b755..5ec8b45be 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -18,6 +18,9 @@ "lint": "yarn prettier --check --log-level warn . && eslint --ext .ts src && yarn tsc", "lint-fix": "yarn prettier --write --log-level warn . && eslint --fix --ext .ts src && yarn tsc" }, + "resolutions": { + "jackspeak": "2.1.1" + }, "dependencies": { "any-shell-escape": "^0.1", "auto-launch": "^5.0", diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 436ae8438..2210d4745 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -164,18 +164,6 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - "@isaacs/fs-minipass@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" @@ -508,11 +496,6 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -527,11 +510,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - any-shell-escape@^0.1: version "0.1.1" resolved "https://registry.yarnpkg.com/any-shell-escape/-/any-shell-escape-0.1.1.tgz#d55ab972244c71a9a5e1ab0879f30bf110806959" @@ -1139,11 +1117,6 @@ dotenv@^9.0.2: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-9.0.2.tgz#dacc20160935a37dea6364aa1bef819fb9b6ab05" integrity sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg== -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - ejs@^3.1.8: version "3.1.10" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" @@ -1240,11 +1213,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -1963,12 +1931,12 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -jackspeak@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" - integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== +jackspeak@2.1.1, jackspeak@^2.3.6: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.1.1.tgz#2a42db4cfbb7e55433c28b6f75d8b796af9669cd" + integrity sha512-juf9stUEwUaILepraGOWIJTLwg48bUnBmRqd2ln2Os1sW987zeoj/hzhbvRB95oMuS2ZTpjULmdwHNX4rzZIZw== dependencies: - "@isaacs/cliui" "^8.0.2" + cliui "^8.0.1" optionalDependencies: "@pkgjs/parseargs" "^0.11.0" @@ -2908,7 +2876,7 @@ stat-mode@^1.0.0: resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-1.0.0.tgz#68b55cb61ea639ff57136f36b216a291800d1465" integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2917,15 +2885,6 @@ stat-mode@^1.0.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -2933,20 +2892,13 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -3189,7 +3141,7 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -3198,15 +3150,6 @@ word-wrap@^1.2.5: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From baf943550f72b019d7349e2817dc2c2373077e54 Mon Sep 17 00:00:00 2001 From: Vishnu Mohandas Date: Wed, 1 May 2024 10:01:41 +0530 Subject: [PATCH 104/367] Don't sent internal build for review --- .github/workflows/mobile-internal-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/mobile-internal-release.yml b/.github/workflows/mobile-internal-release.yml index 9779a5d7a..4ee736742 100644 --- a/.github/workflows/mobile-internal-release.yml +++ b/.github/workflows/mobile-internal-release.yml @@ -54,3 +54,4 @@ jobs: packageName: io.ente.photos releaseFiles: mobile/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab track: internal + changesNotSentForReview: true From bd2969daffb2b3079789ce9d3cf08fdc26c5e999 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 10:03:03 +0530 Subject: [PATCH 105/367] Fix inverted condition --- desktop/src/main/services/upload.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/main/services/upload.ts b/desktop/src/main/services/upload.ts index 795ce48af..f7d0436c0 100644 --- a/desktop/src/main/services/upload.ts +++ b/desktop/src/main/services/upload.ts @@ -14,7 +14,7 @@ export const listZipItems = async (zipPath: string): Promise => { for (const entry of Object.values(entries)) { const basename = path.basename(entry.name); // Ignore "hidden" files (files whose names begins with a dot). - if (entry.isFile && basename.startsWith(".")) { + if (entry.isFile && !basename.startsWith(".")) { // `entry.name` is the path within the zip. entryNames.push(entry.name); } From 74f93efe1691a86a5f77283dfba8f041981f642e Mon Sep 17 00:00:00 2001 From: Vishnu Mohandas Date: Wed, 1 May 2024 10:03:26 +0530 Subject: [PATCH 106/367] Don't send changes for review in internal build --- .github/workflows/mobile-internal-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/mobile-internal-release.yml b/.github/workflows/mobile-internal-release.yml index 9779a5d7a..4ee736742 100644 --- a/.github/workflows/mobile-internal-release.yml +++ b/.github/workflows/mobile-internal-release.yml @@ -54,3 +54,4 @@ jobs: packageName: io.ente.photos releaseFiles: mobile/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab track: internal + changesNotSentForReview: true From 38969d6f45b2558d9517b4c180756d90c59b2f5e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 10:17:51 +0530 Subject: [PATCH 107/367] Fix zip reader --- desktop/src/main/stream.ts | 3 ++- web/apps/photos/src/utils/native-stream.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index 3e27de12b..30aebaa9d 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -48,7 +48,8 @@ export const registerStreamProtocol = () => { const { host, pathname, hash } = new URL(url); // Convert e.g. "%20" to spaces. const path = decodeURIComponent(pathname); - const hashPath = decodeURIComponent(hash); + // `hash` begins with a "#", slice that off. + const hashPath = decodeURIComponent(hash.slice(1)); switch (host) { case "read": return handleRead(path); diff --git a/web/apps/photos/src/utils/native-stream.ts b/web/apps/photos/src/utils/native-stream.ts index 8ada6070c..8d93becfe 100644 --- a/web/apps/photos/src/utils/native-stream.ts +++ b/web/apps/photos/src/utils/native-stream.ts @@ -42,7 +42,7 @@ export const readStream = async ( url = new URL(`stream://read${pathOrZipItem}`); } else { const [zipPath, entryName] = pathOrZipItem; - url = new URL(`stream://read${zipPath}`); + url = new URL(`stream://read-zip${zipPath}`); url.hash = entryName; } From 15b013ea36c45833f6d1d8efd833a43eeb14f9bd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 10:19:21 +0530 Subject: [PATCH 108/367] Verified with a console log --- desktop/src/main/stream.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index 30aebaa9d..74f6d428c 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -117,7 +117,6 @@ const handleReadZip = async (zipPath: string, entryName: string) => { webReadableStreamAny as ReadableStream; // Close the zip handle when the underlying stream closes. - // TODO(MR): Verify stream.on("end", () => void zip.close()); return new Response(webReadableStream, { From 55c603d34504113dea40c23e00f5506ca862f603 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 10:37:58 +0530 Subject: [PATCH 109/367] Tweak logs --- web/apps/photos/src/components/PhotoFrame.tsx | 2 +- web/apps/photos/src/utils/file/index.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/web/apps/photos/src/components/PhotoFrame.tsx b/web/apps/photos/src/components/PhotoFrame.tsx index f7db350da..89f1ce887 100644 --- a/web/apps/photos/src/components/PhotoFrame.tsx +++ b/web/apps/photos/src/components/PhotoFrame.tsx @@ -308,7 +308,7 @@ const PhotoFrame = ({ item: EnteFile, ) => { log.info( - `[${item.id}] getSlideData called for thumbnail: ${!!item.msrc} sourceLoaded: ${item.isSourceLoaded} fetching:${fetching[item.id]}`, + `[${item.id}] getSlideData called for thumbnail: ${!!item.msrc} sourceLoaded: ${!!item.isSourceLoaded} fetching: ${!!fetching[item.id]}`, ); if (!item.msrc) { diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index abbc8b0fa..2b98ba318 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -301,7 +301,8 @@ export const getRenderableImage = async (fileName: string, imageBlob: Blob) => { const tempFile = new File([imageBlob], fileName); const fileTypeInfo = await detectFileTypeInfo(tempFile); log.debug( - () => `Need renderable image for ${JSON.stringify(fileTypeInfo)}`, + () => + `Need renderable image for ${JSON.stringify({ fileName, ...fileTypeInfo })}`, ); const { extension } = fileTypeInfo; From 81feeef79252de2d24113979edc258e655523c17 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 1 May 2024 10:41:15 +0530 Subject: [PATCH 110/367] Remove dependency on Isar --- mobile/ios/Podfile.lock | 6 --- mobile/ios/Runner.xcodeproj/project.pbxproj | 2 - mobile/pubspec.lock | 48 --------------------- mobile/pubspec.yaml | 3 -- mobile/scripts/build_isar.sh | 17 -------- 5 files changed, 76 deletions(-) delete mode 100755 mobile/scripts/build_isar.sh diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 88bc70bf7..731514957 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -108,8 +108,6 @@ PODS: - FlutterMacOS - integration_test (0.0.1): - Flutter - - isar_flutter_libs (1.0.0): - - Flutter - libwebp (1.3.2): - libwebp/demux (= 1.3.2) - libwebp/mux (= 1.3.2) @@ -246,7 +244,6 @@ DEPENDENCIES: - image_editor_common (from `.symlinks/plugins/image_editor_common/ios`) - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - media_extension (from `.symlinks/plugins/media_extension/ios`) @@ -341,8 +338,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/in_app_purchase_storekit/darwin" integration_test: :path: ".symlinks/plugins/integration_test/ios" - isar_flutter_libs: - :path: ".symlinks/plugins/isar_flutter_libs/ios" local_auth_darwin: :path: ".symlinks/plugins/local_auth_darwin/darwin" local_auth_ios: @@ -427,7 +422,6 @@ SPEC CHECKSUMS: image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43 in_app_purchase_storekit: 0e4b3c2e43ba1e1281f4f46dd71b0593ce529892 integration_test: 13825b8a9334a850581300559b8839134b124670 - isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98 local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9 diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 89c492629..c88f9da38 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -308,7 +308,6 @@ "${BUILT_PRODUCTS_DIR}/image_editor_common/image_editor_common.framework", "${BUILT_PRODUCTS_DIR}/in_app_purchase_storekit/in_app_purchase_storekit.framework", "${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework", - "${BUILT_PRODUCTS_DIR}/isar_flutter_libs/isar_flutter_libs.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", @@ -390,7 +389,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_editor_common.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase_storekit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/isar_flutter_libs.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", diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 19553bdcb..ae74068eb 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -363,14 +363,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" - dartx: - dependency: transitive - description: - name: dartx - sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" - url: "https://pub.dev" - source: hosted - version: "1.2.0" dbus: dependency: transitive description: @@ -1116,30 +1108,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" - isar: - dependency: "direct main" - description: - name: isar - sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea" - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" - isar_flutter_libs: - dependency: "direct main" - description: - name: isar_flutter_libs - sha256: bc6768cc4b9c61aabff77152e7f33b4b17d2fc93134f7af1c3dd51500fe8d5e8 - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" - isar_generator: - dependency: "direct dev" - description: - name: isar_generator - sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133" - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" js: dependency: transitive description: @@ -2220,14 +2188,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.9" - time: - dependency: transitive - description: - name: time - sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 - url: "https://pub.dev" - source: hosted - version: "2.1.4" timezone: dependency: transitive description: @@ -2597,14 +2557,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" - xxh3: - dependency: transitive - description: - name: xxh3 - sha256: a92b30944a9aeb4e3d4f3c3d4ddb3c7816ca73475cd603682c4f8149690f56d7 - url: "https://pub.dev" - source: hosted - version: "1.0.1" yaml: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index ea01ba086..a839ce674 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -99,8 +99,6 @@ dependencies: image_editor: ^1.3.0 in_app_purchase: ^3.0.7 intl: ^0.18.0 - isar: ^3.1.0+1 - isar_flutter_libs: ^3.1.0+1 json_annotation: ^4.8.0 latlong2: ^0.9.0 like_button: ^2.0.5 @@ -196,7 +194,6 @@ dev_dependencies: freezed: ^2.5.2 integration_test: sdk: flutter - isar_generator: ^3.1.0+1 json_serializable: ^6.6.1 test: ^1.22.0 diff --git a/mobile/scripts/build_isar.sh b/mobile/scripts/build_isar.sh deleted file mode 100755 index 1bb1d38f6..000000000 --- a/mobile/scripts/build_isar.sh +++ /dev/null @@ -1,17 +0,0 @@ -# TODO: add `rustup@1.25.2` to `srclibs` -# TODO: verify if `gcc-multilib` or `libc-dev` is needed -$$rustup$$/rustup-init.sh -y -source $HOME/.cargo/env -cd thirdparty/isar/ -bash tool/build_android.sh x86 -bash tool/build_android.sh x64 -bash tool/build_android.sh armv7 -bash tool/build_android.sh arm64 -mv libisar_android_arm64.so libisar.so -mv libisar.so $PUB_CACHE/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/arm64-v8a/ -mv libisar_android_armv7.so libisar.so -mv libisar.so $PUB_CACHE/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/armeabi-v7a/ -mv libisar_android_x64.so libisar.so -mv libisar.so $PUB_CACHE/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/x86_64/ -mv libisar_android_x86.so libisar.so -mv libisar.so $PUB_CACHE/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/x86/ From de92a9dd649ccc341c85ab51143f1dcd92613f25 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 1 May 2024 10:41:36 +0530 Subject: [PATCH 111/367] v0.8.87 --- mobile/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index a839ce674..cfef39e56 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.86+606 +version: 0.8.87+607 publish_to: none environment: From ff5a167f48af1395954a4526d478cde570c5eff8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 11:33:42 +0530 Subject: [PATCH 112/367] Might not be JSONStringify-able --- web/apps/photos/src/services/exif.ts | 43 ++++++++-------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/web/apps/photos/src/services/exif.ts b/web/apps/photos/src/services/exif.ts index 584d79f88..073a695f7 100644 --- a/web/apps/photos/src/services/exif.ts +++ b/web/apps/photos/src/services/exif.ts @@ -167,14 +167,7 @@ function parseExifData(exifData: RawEXIFData): ParsedEXIFData { parsedExif.imageWidth = ImageWidth; parsedExif.imageHeight = ImageHeight; } else { - log.error( - `Image dimension parsing failed - ImageWidth or ImageHeight is not a number ${JSON.stringify( - { - ImageWidth, - ImageHeight, - }, - )}`, - ); + log.warn("EXIF: Ignoring non-numeric ImageWidth or ImageHeight"); } } else if (ExifImageWidth && ExifImageHeight) { if ( @@ -184,13 +177,8 @@ function parseExifData(exifData: RawEXIFData): ParsedEXIFData { parsedExif.imageWidth = ExifImageWidth; parsedExif.imageHeight = ExifImageHeight; } else { - log.error( - `Image dimension parsing failed - ExifImageWidth or ExifImageHeight is not a number ${JSON.stringify( - { - ExifImageWidth, - ExifImageHeight, - }, - )}`, + log.warn( + "EXIF: Ignoring non-numeric ExifImageWidth or ExifImageHeight", ); } } else if (PixelXDimension && PixelYDimension) { @@ -201,13 +189,8 @@ function parseExifData(exifData: RawEXIFData): ParsedEXIFData { parsedExif.imageWidth = PixelXDimension; parsedExif.imageHeight = PixelYDimension; } else { - log.error( - `Image dimension parsing failed - PixelXDimension or PixelYDimension is not a number ${JSON.stringify( - { - PixelXDimension, - PixelYDimension, - }, - )}`, + log.warn( + "EXIF: Ignoring non-numeric PixelXDimension or PixelYDimension", ); } } @@ -302,15 +285,13 @@ export function parseEXIFLocation( ); return { latitude, longitude }; } catch (e) { - log.error( - `Failed to parseEXIFLocation ${JSON.stringify({ - gpsLatitude, - gpsLatitudeRef, - gpsLongitude, - gpsLongitudeRef, - })}`, - e, - ); + const p = { + gpsLatitude, + gpsLatitudeRef, + gpsLongitude, + gpsLongitudeRef, + }; + log.error(`Failed to parse EXIF location ${JSON.stringify(p)}`, e); return { ...NULL_LOCATION }; } } From 6086d436358404ef68acf6e9bcf74e6fc29e6918 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 12:29:11 +0530 Subject: [PATCH 113/367] Don't log a potentially huge list --- web/apps/photos/src/components/Upload/Uploader.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx index cfd674e3f..e56d7583e 100644 --- a/web/apps/photos/src/components/Upload/Uploader.tsx +++ b/web/apps/photos/src/components/Upload/Uploader.tsx @@ -261,7 +261,9 @@ export default function Uploader({ const { collectionName, filePaths, zipItems } = pending; - log.info("Resuming pending upload", pending); + log.info( + `Resuming pending of upload of ${filePaths.length + zipItems.length} items${collectionName ? " to collection " + collectionName : ""}`, + ); isPendingDesktopUpload.current = true; pendingDesktopUploadCollectionName.current = collectionName; setDesktopFilePaths(filePaths); From f91dddda93cf1e82bfa4706a63405dc5be46a009 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 13:35:42 +0530 Subject: [PATCH 114/367] Fix paths --- web/apps/photos/src/services/export/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/photos/src/services/export/index.ts b/web/apps/photos/src/services/export/index.ts index 82dfdbf8b..b387d4fd3 100644 --- a/web/apps/photos/src/services/export/index.ts +++ b/web/apps/photos/src/services/export/index.ts @@ -1409,6 +1409,6 @@ const moveToTrash = async ( metadataFileName, fs.exists, ); - await fs.rename(filePath, metadataTrashFilePath); + await fs.rename(metadataFilePath, metadataTrashFilePath); } }; From f2a764aac2ff897470df09c29e316af30bbc22aa Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 13:42:09 +0530 Subject: [PATCH 115/367] Fix export trash movement --- web/apps/photos/src/services/export/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/apps/photos/src/services/export/index.ts b/web/apps/photos/src/services/export/index.ts index b387d4fd3..c46e1d8fc 100644 --- a/web/apps/photos/src/services/export/index.ts +++ b/web/apps/photos/src/services/export/index.ts @@ -1398,17 +1398,19 @@ const moveToTrash = async ( if (await fs.exists(filePath)) { await fs.mkdirIfNeeded(trashDir); - const trashFilePath = await safeFileName(trashDir, fileName, fs.exists); + const trashFileName = await safeFileName(trashDir, fileName, fs.exists); + const trashFilePath = `${trashDir}/${trashFileName}`; await fs.rename(filePath, trashFilePath); } if (await fs.exists(metadataFilePath)) { await fs.mkdirIfNeeded(metadataTrashDir); - const metadataTrashFilePath = await safeFileName( + const metadataTrashFileName = await safeFileName( metadataTrashDir, metadataFileName, fs.exists, ); + const metadataTrashFilePath = `${metadataTrashDir}/${metadataTrashFileName}`; await fs.rename(metadataFilePath, metadataTrashFilePath); } }; From 5ffc2f20fd28e2fe269404f0656890468a9e0b36 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 13:57:33 +0530 Subject: [PATCH 116/367] Replicate previous behaviour --- web/apps/photos/src/services/export/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/apps/photos/src/services/export/index.ts b/web/apps/photos/src/services/export/index.ts index c46e1d8fc..b02e05a42 100644 --- a/web/apps/photos/src/services/export/index.ts +++ b/web/apps/photos/src/services/export/index.ts @@ -547,6 +547,9 @@ class ExportService { isCanceled: CancellationStatus, ) { const fs = ensureElectron().fs; + const rmdirIfExists = async (dirPath: string) => { + if (await fs.exists(dirPath)) await fs.rmdir(dirPath); + }; try { const exportRecord = await this.getExportRecord(exportFolder); const collectionIDPathMap = @@ -581,11 +584,11 @@ class ExportService { ); try { // delete the collection metadata folder - await fs.rmdir( + await rmdirIfExists( getMetadataFolderExportPath(collectionExportPath), ); // delete the collection folder - await fs.rmdir(collectionExportPath); + await rmdirIfExists(collectionExportPath); } catch (e) { await this.addCollectionExportedRecord( exportFolder, From 52909f6f2118d497871fe455adadff9f08463e36 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 14:01:05 +0530 Subject: [PATCH 117/367] The handler adds its own error message prefix Error occurred in handler for 'generateImageThumbnail': Error: This feature in not available on the current OS/arch --- web/apps/photos/src/services/upload/uploadService.ts | 2 +- web/apps/photos/src/utils/file/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index 7d3303884..52f495785 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -1021,7 +1021,7 @@ const withThumbnail = async ( fileTypeInfo, ); } catch (e) { - if (e.message == CustomErrorMessage.NotAvailable) { + if (e.message.endsWith(CustomErrorMessage.NotAvailable)) { moduleState.isNativeImageThumbnailGenerationNotAvailable = true; } else { log.error("Native thumbnail generation failed", e); diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 2b98ba318..212b2efd3 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -319,7 +319,7 @@ export const getRenderableImage = async (fileName: string, imageBlob: Blob) => { try { return await nativeConvertToJPEG(imageBlob); } catch (e) { - if (e.message == CustomErrorMessage.NotAvailable) { + if (e.message.endsWith(CustomErrorMessage.NotAvailable)) { moduleState.isNativeJPEGConversionNotAvailable = true; } else { log.error("Native conversion to JPEG failed", e); From cb73bc143dde713d4bd4b15925fc0f57810deb45 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 14:35:03 +0530 Subject: [PATCH 118/367] Research --- desktop/src/main/utils/electron.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/desktop/src/main/utils/electron.ts b/desktop/src/main/utils/electron.ts index d627ec5c4..43471cd1e 100644 --- a/desktop/src/main/utils/electron.ts +++ b/desktop/src/main/utils/electron.ts @@ -11,6 +11,19 @@ export const isDev = !app.isPackaged; /** * Convert a file system {@link filePath} that uses the local system specific * path separators into a path that uses POSIX file separators. + * + * For all paths that we persist or pass over the IPC boundary, we always use + * POSIX paths, even on Windows. + * + * Windows recognizes both forward and backslashes. This also works with drive + * names. c:\foo\bar and c:/foo/bar are both valid. + * + * > Almost all paths passed to Windows APIs are normalized. During + * > normalization, Windows performs the following steps: ... All forward + * > slashes (/) are converted into the standard Windows separator, the back + * > slash (\). + * > + * > https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats */ export const posixPath = (filePath: string) => filePath.split(path.sep).join(path.posix.sep); From 36984012a8883f1cbdddf6a59663b1014cf278a2 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 15:00:18 +0530 Subject: [PATCH 119/367] They're all optionals now --- desktop/src/main/stores/upload-status.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/desktop/src/main/stores/upload-status.ts b/desktop/src/main/stores/upload-status.ts index 472f38a7f..8cb2410df 100644 --- a/desktop/src/main/stores/upload-status.ts +++ b/desktop/src/main/stores/upload-status.ts @@ -9,15 +9,10 @@ export interface UploadStatusStore { collectionName?: string; /** * Paths to regular files that are pending upload. - * - * This should generally be present, albeit empty, but it is marked optional - * in sympathy with its siblings. */ filePaths?: string[]; /** * Each item is the path to a zip file and the name of an entry within it. - * - * This is marked optional since legacy stores will not have it. */ zipItems?: [zipPath: string, entryName: string][]; /** From 4c28e83dbb4bebe2985f03bb9a92fd09970c2dcc Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 15:07:58 +0530 Subject: [PATCH 120/367] Optimize --- desktop/src/main/services/watch.ts | 4 ++-- desktop/src/main/utils/electron.ts | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/desktop/src/main/services/watch.ts b/desktop/src/main/services/watch.ts index a56c0cf6c..975d8a7c3 100644 --- a/desktop/src/main/services/watch.ts +++ b/desktop/src/main/services/watch.ts @@ -35,8 +35,8 @@ export const createWatcher = (mainWindow: BrowserWindow) => { return watcher; }; -const eventData = (path: string): [string, FolderWatch] => { - path = posixPath(path); +const eventData = (platformPath: string): [string, FolderWatch] => { + const path = posixPath(platformPath); const watch = folderWatches().find((watch) => path.startsWith(watch.folderPath + "/"), diff --git a/desktop/src/main/utils/electron.ts b/desktop/src/main/utils/electron.ts index 43471cd1e..93e8565ef 100644 --- a/desktop/src/main/utils/electron.ts +++ b/desktop/src/main/utils/electron.ts @@ -9,8 +9,8 @@ import log from "../log"; export const isDev = !app.isPackaged; /** - * Convert a file system {@link filePath} that uses the local system specific - * path separators into a path that uses POSIX file separators. + * Convert a file system {@link platformPath} that uses the local system + * specific path separators into a path that uses POSIX file separators. * * For all paths that we persist or pass over the IPC boundary, we always use * POSIX paths, even on Windows. @@ -25,8 +25,10 @@ export const isDev = !app.isPackaged; * > * > https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats */ -export const posixPath = (filePath: string) => - filePath.split(path.sep).join(path.posix.sep); +export const posixPath = (platformPath: string) => + path.sep == path.posix.sep + ? platformPath + : platformPath.split(path.sep).join(path.posix.sep); /** * Run a shell command asynchronously. From 5f131693f4a4482a5477441cb1cd9ccfa9b90288 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 1 May 2024 15:12:48 +0530 Subject: [PATCH 121/367] Use Int instead of String to represent an enum --- mobile/lib/db/embeddings_sqlite_db.dart | 30 +++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/mobile/lib/db/embeddings_sqlite_db.dart b/mobile/lib/db/embeddings_sqlite_db.dart index 2c77af281..a993c3275 100644 --- a/mobile/lib/db/embeddings_sqlite_db.dart +++ b/mobile/lib/db/embeddings_sqlite_db.dart @@ -42,7 +42,7 @@ class EmbeddingsDB { 1, (tx) async { await tx.execute( - 'CREATE TABLE $tableName ($columnFileID INTEGER NOT NULL, $columnModel TEXT NOT NULL, $columnEmbedding BLOB NOT NULL, $columnUpdationTime INTEGER, UNIQUE ($columnFileID, $columnModel))', + 'CREATE TABLE $tableName ($columnFileID INTEGER NOT NULL, $columnModel INTEGER NOT NULL, $columnEmbedding BLOB NOT NULL, $columnUpdationTime INTEGER, UNIQUE ($columnFileID, $columnModel))', ); }, ), @@ -102,7 +102,7 @@ class EmbeddingsDB { final db = await _database; await db.execute( 'DELETE FROM $tableName WHERE $columnModel = ?', - [serialize(model)], + [modelToInt(model)!], ); Bus.instance.fire(EmbeddingUpdatedEvent()); } @@ -117,7 +117,7 @@ class EmbeddingsDB { Embedding _getEmbeddingFromRow(Map row) { final fileID = row[columnFileID]; - final model = deserialize(row[columnModel]); + final model = intToModel(row[columnModel])!; final bytes = row[columnEmbedding] as Uint8List; final list = Float32List.view(bytes.buffer); return Embedding(fileID: fileID, model: model, embedding: list); @@ -126,7 +126,7 @@ class EmbeddingsDB { List _getRowFromEmbedding(Embedding embedding) { return [ embedding.fileID, - serialize(embedding.model), + modelToInt(embedding.model)!, Float32List.fromList(embedding.embedding).buffer.asUint8List(), embedding.updationTime, ]; @@ -142,4 +142,26 @@ class EmbeddingsDB { await deprecatedDB.delete(); } } + + int? modelToInt(Model model) { + switch (model) { + case Model.onnxClip: + return 1; + case Model.ggmlClip: + return 2; + default: + return null; + } + } + + Model? intToModel(int model) { + switch (model) { + case 1: + return Model.onnxClip; + case 2: + return Model.ggmlClip; + default: + return null; + } + } } From 10f2c3db6f84e013ce2cef80c6184d9a24d8ba74 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 15:12:50 +0530 Subject: [PATCH 122/367] Mention why we're normalizing --- desktop/src/main/services/dir.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/desktop/src/main/services/dir.ts b/desktop/src/main/services/dir.ts index 4e2a8c65e..d375648f6 100644 --- a/desktop/src/main/services/dir.ts +++ b/desktop/src/main/services/dir.ts @@ -17,8 +17,11 @@ export const selectDirectory = async () => { * For example, on macOS this'll open {@link dirPath} in Finder. */ export const openDirectory = async (dirPath: string) => { + // We need to use `path.normalize` because `shell.openPath; does not support + // POSIX path, it needs to be a platform specific path: + // https://github.com/electron/electron/issues/28831#issuecomment-826370589 const res = await shell.openPath(path.normalize(dirPath)); - // shell.openPath resolves with a string containing the error message + // `shell.openPath` resolves with a string containing the error message // corresponding to the failure if a failure occurred, otherwise "". if (res) throw new Error(`Failed to open directory ${dirPath}: res`); }; From 1cd9fb1021b611a5bd7fbd0aa618e1ceb01ba212 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 1 May 2024 15:12:57 +0530 Subject: [PATCH 123/367] v0.8.88 --- mobile/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index cfef39e56..2adf29321 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.87+607 +version: 0.8.88+608 publish_to: none environment: From bc32c89d7772347ac68592fd04cff2cbdcff887c Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 1 May 2024 15:14:44 +0530 Subject: [PATCH 124/367] Rename DB --- mobile/lib/core/configuration.dart | 2 +- mobile/lib/db/{embeddings_sqlite_db.dart => embeddings_db.dart} | 0 .../machine_learning/semantic_search/embedding_store.dart | 2 +- .../semantic_search/semantic_search_service.dart | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename mobile/lib/db/{embeddings_sqlite_db.dart => embeddings_db.dart} (100%) diff --git a/mobile/lib/core/configuration.dart b/mobile/lib/core/configuration.dart index 8b7ecbad5..cde766b1e 100644 --- a/mobile/lib/core/configuration.dart +++ b/mobile/lib/core/configuration.dart @@ -11,7 +11,7 @@ import 'package:photos/core/constants.dart'; import 'package:photos/core/error-reporting/super_logging.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/db/collections_db.dart'; -import "package:photos/db/embeddings_sqlite_db.dart"; +import "package:photos/db/embeddings_db.dart"; import 'package:photos/db/files_db.dart'; import 'package:photos/db/memories_db.dart'; import 'package:photos/db/trash_db.dart'; diff --git a/mobile/lib/db/embeddings_sqlite_db.dart b/mobile/lib/db/embeddings_db.dart similarity index 100% rename from mobile/lib/db/embeddings_sqlite_db.dart rename to mobile/lib/db/embeddings_db.dart diff --git a/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart b/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart index b3795db4e..420b8c97f 100644 --- a/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart +++ b/mobile/lib/services/machine_learning/semantic_search/embedding_store.dart @@ -5,7 +5,7 @@ import "dart:typed_data"; import "package:computer/computer.dart"; import "package:logging/logging.dart"; import "package:photos/core/network/network.dart"; -import "package:photos/db/embeddings_sqlite_db.dart"; +import "package:photos/db/embeddings_db.dart"; import "package:photos/db/files_db.dart"; import "package:photos/models/embedding.dart"; import "package:photos/models/file/file.dart"; diff --git a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart index eb5bfeb91..337ca913f 100644 --- a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart +++ b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart @@ -7,7 +7,7 @@ import "package:logging/logging.dart"; import "package:photos/core/cache/lru_map.dart"; import "package:photos/core/configuration.dart"; import "package:photos/core/event_bus.dart"; -import "package:photos/db/embeddings_sqlite_db.dart"; +import "package:photos/db/embeddings_db.dart"; import "package:photos/db/files_db.dart"; import "package:photos/events/diff_sync_complete_event.dart"; import 'package:photos/events/embedding_updated_event.dart'; From 9dd865ff6e69647062ad3efeba3c8d3ebb5d9276 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 15:25:46 +0530 Subject: [PATCH 125/367] Comment --- .../photos/src/components/Upload/Uploader.tsx | 20 ++++++++++++++----- web/packages/next/types/ipc.ts | 2 ++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx index e56d7583e..53ad1fb32 100644 --- a/web/apps/photos/src/components/Upload/Uploader.tsx +++ b/web/apps/photos/src/components/Upload/Uploader.tsx @@ -325,13 +325,26 @@ export default function Uploader({ // Trigger an upload when any of the dependencies change. useEffect(() => { + // Re the paths: + // + // - These are not necessarily the full paths. In particular, when + // running on the browser they'll be the relative paths (at best) or + // just the file-name otherwise. + // + // - All the paths use POSIX separators. See inline comments. const allItemAndPaths = [ // See: [Note: webkitRelativePath]. In particular, they use POSIX // separators. webFiles.map((f) => [f, f.webkitRelativePath ?? f.name]), + // The paths we get from the desktop app all eventually come either + // from electron.selectDirectory or electron.pathForFile, both of + // which return POSIX paths. desktopFiles.map((fp) => [fp, fp.path]), desktopFilePaths.map((p) => [p, p]), - // ze[1], the entry name, uses POSIX separators. + // The first path, that of the zip file itself, is POSIX like the + // other paths we get over the IPC boundary. And the second path, + // ze[1], the entry name, uses POSIX separators because that is what + // the ZIP format uses. desktopZipItems.map((ze) => [ze, ze[1]]), ].flat() as [UploadItem, string][]; @@ -794,10 +807,7 @@ async function waitAndRun( await task(); } -const desktopFilesAndZipItems = async ( - electron: Electron, - files: File[], -): Promise<{ fileAndPaths: FileAndPath[]; zipItems: ZipItem[] }> => { +const desktopFilesAndZipItems = async (electron: Electron, files: File[]) => { const fileAndPaths: FileAndPath[] = []; let zipItems: ZipItem[] = []; diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index c85106241..fb72bcf5c 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -53,6 +53,8 @@ export interface Electron { * Ask the user to select a directory on their local file system, and return * it path. * + * The returned path is guaranteed to use POSIX separators ('/'). + * * We don't strictly need IPC for this, we can use a hidden element * and trigger its click for the same behaviour (as we do for the * `useFileInput` hook that we use for uploads). However, it's a bit From 0a93ba67a1fc6c1b2627c1d2e47dcd68239021ff Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 17:00:44 +0530 Subject: [PATCH 126/367] Fix warnings Refs: https://stackoverflow.com/questions/69730364/what-is-the-purpose-of-shouldforwardprop-option-in-styled --- .../photos/src/components/PhotoList/index.tsx | 19 ++++++++++--------- .../Search/SearchBar/styledComponents.tsx | 4 +++- .../shared/components/Navbar/base.tsx | 5 ++++- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/web/apps/photos/src/components/PhotoList/index.tsx b/web/apps/photos/src/components/PhotoList/index.tsx index 91f712df1..4803995d4 100644 --- a/web/apps/photos/src/components/PhotoList/index.tsx +++ b/web/apps/photos/src/components/PhotoList/index.tsx @@ -111,14 +111,13 @@ function getShrinkRatio(width: number, columns: number) { ); } -const ListContainer = styled(Box)<{ - columns: number; - shrinkRatio: number; - groups?: number[]; +const ListContainer = styled(Box, { + shouldForwardProp: (propName) => propName != "gridTemplateColumns", +})<{ + gridTemplateColumns: string; }>` display: grid; - grid-template-columns: ${({ columns, shrinkRatio, groups }) => - getTemplateColumns(columns, shrinkRatio, groups)}; + grid-template-columns: ${(props) => props.gridTemplateColumns}; grid-column-gap: ${GAP_BTW_TILES}px; width: 100%; color: #fff; @@ -235,9 +234,11 @@ const PhotoListRow = React.memo( return ( {renderListItem(timeStampList[index], isScrolling)} diff --git a/web/apps/photos/src/components/Search/SearchBar/styledComponents.tsx b/web/apps/photos/src/components/Search/SearchBar/styledComponents.tsx index 41d4a0971..d33c7c949 100644 --- a/web/apps/photos/src/components/Search/SearchBar/styledComponents.tsx +++ b/web/apps/photos/src/components/Search/SearchBar/styledComponents.tsx @@ -23,7 +23,9 @@ export const SearchMobileBox = styled(FluidContainer)` } `; -export const SearchInputWrapper = styled(CenteredFlex)<{ isOpen: boolean }>` +export const SearchInputWrapper = styled(CenteredFlex, { + shouldForwardProp: (propName) => propName != "isOpen", +})<{ isOpen: boolean }>` background: ${({ theme }) => theme.colors.background.base}; max-width: 484px; margin: auto; diff --git a/web/packages/shared/components/Navbar/base.tsx b/web/packages/shared/components/Navbar/base.tsx index 101506cfd..403dc808c 100644 --- a/web/packages/shared/components/Navbar/base.tsx +++ b/web/packages/shared/components/Navbar/base.tsx @@ -1,6 +1,9 @@ import { styled } from "@mui/material"; import { FlexWrapper } from "../../components/Container"; -const NavbarBase = styled(FlexWrapper)<{ isMobile: boolean }>` + +const NavbarBase = styled(FlexWrapper, { + shouldForwardProp: (propName) => propName != "isMobile", +})<{ isMobile: boolean }>` min-height: 64px; position: sticky; top: 0; From b967d4bbea3349b86cfe6a070d443e62b57d4cf4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 17:39:28 +0530 Subject: [PATCH 127/367] URL encode better e.g. fixes the reading of a file with a hash in the name --- desktop/src/main/stream.ts | 3 +++ web/apps/photos/src/utils/native-stream.ts | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index 74f6d428c..f1000c6bd 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -50,6 +50,9 @@ export const registerStreamProtocol = () => { const path = decodeURIComponent(pathname); // `hash` begins with a "#", slice that off. const hashPath = decodeURIComponent(hash.slice(1)); + log.debug( + () => `[stream] ${host} ${path}${hashPath ? "::" + hashPath : ""}`, + ); switch (host) { case "read": return handleRead(path); diff --git a/web/apps/photos/src/utils/native-stream.ts b/web/apps/photos/src/utils/native-stream.ts index 8d93becfe..a910d3b22 100644 --- a/web/apps/photos/src/utils/native-stream.ts +++ b/web/apps/photos/src/utils/native-stream.ts @@ -39,10 +39,12 @@ export const readStream = async ( ): Promise<{ response: Response; size: number; lastModifiedMs: number }> => { let url: URL; if (typeof pathOrZipItem == "string") { - url = new URL(`stream://read${pathOrZipItem}`); + url = new URL("stream://read"); + url.pathname = pathOrZipItem } else { const [zipPath, entryName] = pathOrZipItem; - url = new URL(`stream://read-zip${zipPath}`); + url = new URL("stream://read-zip"); + url.pathname = zipPath; url.hash = entryName; } From c988884ab1b567f71b75300b81ea2aaf1f0e1891 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 17:56:46 +0530 Subject: [PATCH 128/367] Setting pathname had no effect --- web/apps/photos/src/utils/native-stream.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/web/apps/photos/src/utils/native-stream.ts b/web/apps/photos/src/utils/native-stream.ts index a910d3b22..b20cb9290 100644 --- a/web/apps/photos/src/utils/native-stream.ts +++ b/web/apps/photos/src/utils/native-stream.ts @@ -39,12 +39,10 @@ export const readStream = async ( ): Promise<{ response: Response; size: number; lastModifiedMs: number }> => { let url: URL; if (typeof pathOrZipItem == "string") { - url = new URL("stream://read"); - url.pathname = pathOrZipItem + url = new URL(`stream://read${encodeURIComponent(pathOrZipItem)}`); } else { const [zipPath, entryName] = pathOrZipItem; - url = new URL("stream://read-zip"); - url.pathname = zipPath; + url = new URL(`stream://read-zip${encodeURIComponent(zipPath)}`); url.hash = entryName; } From de4aa3a6cac19b90829ae31fb67734ccc2d0a0cb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 18:15:52 +0530 Subject: [PATCH 129/367] URL encode --- desktop/src/main/stream.ts | 14 +++++--------- web/apps/photos/src/utils/native-stream.ts | 7 ++++--- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index f1000c6bd..06c6e5fe9 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -45,19 +45,15 @@ export const registerStreamProtocol = () => { // stream://write/path/to/file#/path/to/another/file // host[pathname----] [pathname-2---------] // - const { host, pathname, hash } = new URL(url); - // Convert e.g. "%20" to spaces. - const path = decodeURIComponent(pathname); - // `hash` begins with a "#", slice that off. - const hashPath = decodeURIComponent(hash.slice(1)); - log.debug( - () => `[stream] ${host} ${path}${hashPath ? "::" + hashPath : ""}`, - ); + const { host, searchParams } = new URL(url); + const path = ensure(searchParams.get("path")); + const path2 = searchParams.get("path2") ?? undefined; + log.debug(() => `[stream] ${host} ${path}${path2 ? "::" + path2 : ""}`); switch (host) { case "read": return handleRead(path); case "read-zip": - return handleReadZip(path, hashPath); + return handleReadZip(path, ensure(path2)); case "write": return handleWrite(path, request); default: diff --git a/web/apps/photos/src/utils/native-stream.ts b/web/apps/photos/src/utils/native-stream.ts index b20cb9290..73c3522ed 100644 --- a/web/apps/photos/src/utils/native-stream.ts +++ b/web/apps/photos/src/utils/native-stream.ts @@ -39,11 +39,12 @@ export const readStream = async ( ): Promise<{ response: Response; size: number; lastModifiedMs: number }> => { let url: URL; if (typeof pathOrZipItem == "string") { - url = new URL(`stream://read${encodeURIComponent(pathOrZipItem)}`); + const params = new URLSearchParams({ path: pathOrZipItem }); + url = new URL(`stream://read?${params.toString()}`); } else { const [zipPath, entryName] = pathOrZipItem; - url = new URL(`stream://read-zip${encodeURIComponent(zipPath)}`); - url.hash = entryName; + const params = new URLSearchParams({ path: zipPath, path2: entryName }); + url = new URL(`stream://read-zip?${params.toString()}`); } const req = new Request(url, { method: "GET" }); From 4eb51061cb63f5d1443e6d8612d515b76ddbb161 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 18:21:45 +0530 Subject: [PATCH 130/367] For real --- desktop/src/main/stream.ts | 21 +++++++-------------- web/apps/photos/src/utils/native-stream.ts | 7 +++++-- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index 06c6e5fe9..bae13aa12 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -37,25 +37,18 @@ export const registerStreamProtocol = () => { protocol.handle("stream", async (request: Request) => { const url = request.url; // The request URL contains the command to run as the host, and the - // pathname of the file as the path. An additional path can be specified - // as the URL hash. - // - // For example, - // - // stream://write/path/to/file#/path/to/another/file - // host[pathname----] [pathname-2---------] - // + // pathname of the file(s) as the search params. const { host, searchParams } = new URL(url); - const path = ensure(searchParams.get("path")); - const path2 = searchParams.get("path2") ?? undefined; - log.debug(() => `[stream] ${host} ${path}${path2 ? "::" + path2 : ""}`); switch (host) { case "read": - return handleRead(path); + return handleRead(ensure(searchParams.get("path"))); case "read-zip": - return handleReadZip(path, ensure(path2)); + return handleReadZip( + ensure(searchParams.get("zipPath")), + ensure(searchParams.get("entryName")), + ); case "write": - return handleWrite(path, request); + return handleWrite(ensure(searchParams.get("path")), request); default: return new Response("", { status: 404 }); } diff --git a/web/apps/photos/src/utils/native-stream.ts b/web/apps/photos/src/utils/native-stream.ts index 73c3522ed..941c5a988 100644 --- a/web/apps/photos/src/utils/native-stream.ts +++ b/web/apps/photos/src/utils/native-stream.ts @@ -43,7 +43,7 @@ export const readStream = async ( url = new URL(`stream://read?${params.toString()}`); } else { const [zipPath, entryName] = pathOrZipItem; - const params = new URLSearchParams({ path: zipPath, path2: entryName }); + const params = new URLSearchParams({ zipPath, entryName }); url = new URL(`stream://read-zip?${params.toString()}`); } @@ -90,6 +90,9 @@ export const writeStream = async ( path: string, stream: ReadableStream, ) => { + const params = new URLSearchParams({ path }); + const url = new URL(`stream://write?${params.toString()}`); + // TODO(MR): This doesn't currently work. // // Not sure what I'm doing wrong here; I've opened an issue upstream @@ -120,7 +123,7 @@ export const writeStream = async ( }); */ - const req = new Request(`stream://write${path}`, { + const req = new Request(url, { method: "POST", body: await new Response(stream).blob(), }); From 932f26684de0c779938f7addc901ddc13e5a984a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 18:48:25 +0530 Subject: [PATCH 131/367] Electron logout --- desktop/src/main/ipc.ts | 3 +++ desktop/src/main/services/watch.ts | 4 ++++ desktop/src/preload.ts | 12 ++++++++++-- web/packages/accounts/services/user.ts | 16 ++++++++++++---- web/packages/next/types/ipc.ts | 11 +++++++++++ 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index 66cfddabd..f59969202 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -64,6 +64,7 @@ import { watchFindFiles, watchGet, watchRemove, + watchReset, watchUpdateIgnoredFiles, watchUpdateSyncedFiles, } from "./services/watch"; @@ -263,4 +264,6 @@ export const attachFSWatchIPCHandlers = (watcher: FSWatcher) => { ipcMain.handle("watchFindFiles", (_, folderPath: string) => watchFindFiles(folderPath), ); + + ipcMain.handle("watchReset", () => watchReset(watcher)); }; diff --git a/desktop/src/main/services/watch.ts b/desktop/src/main/services/watch.ts index 975d8a7c3..e115239d3 100644 --- a/desktop/src/main/services/watch.ts +++ b/desktop/src/main/services/watch.ts @@ -150,3 +150,7 @@ export const watchFindFiles = async (dirPath: string) => { } return paths; }; + +export const watchReset = async (watcher: FSWatcher) => { + await watcher.close(); +}; diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index 2b5eb8fcc..2acd8fbff 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -208,6 +208,13 @@ const watchOnRemoveDir = (f: (path: string, watch: FolderWatch) => void) => { const watchFindFiles = (folderPath: string) => ipcRenderer.invoke("watchFindFiles", folderPath); +const watchReset = () => { + ipcRenderer.removeAllListeners("watchAddFile"); + ipcRenderer.removeAllListeners("watchRemoveFile"); + ipcRenderer.removeAllListeners("watchRemoveDir"); + return ipcRenderer.invoke("watchReset"); +}; + // - Upload const pathForFile = (file: File) => webUtils.getPathForFile(file); @@ -323,12 +330,13 @@ contextBridge.exposeInMainWorld("electron", { get: watchGet, add: watchAdd, remove: watchRemove, + updateSyncedFiles: watchUpdateSyncedFiles, + updateIgnoredFiles: watchUpdateIgnoredFiles, onAddFile: watchOnAddFile, onRemoveFile: watchOnRemoveFile, onRemoveDir: watchOnRemoveDir, findFiles: watchFindFiles, - updateSyncedFiles: watchUpdateSyncedFiles, - updateIgnoredFiles: watchUpdateIgnoredFiles, + reset: watchReset, }, // - Upload diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index fb0e1c929..8f6d6609a 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -40,10 +40,18 @@ export const logoutUser = async () => { } catch (e) { log.error("Ignoring error when clearing files", e); } - try { - globalThis.electron?.clearStores(); - } catch (e) { - log.error("Ignoring error when clearing electron stores", e); + const electron = globalThis.electron; + if (electron) { + try { + await electron.watch.reset(); + } catch (e) { + log.error("Ignoring error when resetting native folder watches", e); + } + try { + await electron.clearStores(); + } catch (e) { + log.error("Ignoring error when clearing native stores", e); + } } try { eventBus.emit(Events.LOGOUT); diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index fb72bcf5c..4b05838fa 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -462,6 +462,17 @@ export interface Electron { * The returned paths are guaranteed to use POSIX separators ('/'). */ findFiles: (folderPath: string) => Promise; + + /** + * Stop watching all existing folder watches and remove any callbacks. + * + * This function is meant to be called when the user logs out. It stops + * all existing folder watches and forgets about any "on*" callback + * functions that have been registered. + * + * The persisted state itself gets cleared via {@link clearStores}. + */ + reset: () => Promise; }; // - Upload From 90b5054fcb6c68b22bd4ab10e2f089b2d923d8ed Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 19:02:43 +0530 Subject: [PATCH 132/367] chokidar seemed to have gotten stuck after a close Not sure if something else was off, but after a close new watches in the same session (after logging in) stopped reacting. --- desktop/src/main/services/watch.ts | 4 ++-- desktop/src/preload.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/desktop/src/main/services/watch.ts b/desktop/src/main/services/watch.ts index e115239d3..de66dcca1 100644 --- a/desktop/src/main/services/watch.ts +++ b/desktop/src/main/services/watch.ts @@ -151,6 +151,6 @@ export const watchFindFiles = async (dirPath: string) => { return paths; }; -export const watchReset = async (watcher: FSWatcher) => { - await watcher.close(); +export const watchReset = (watcher: FSWatcher) => { + watcher.unwatch(folderWatches().map((watch) => watch.folderPath)); }; diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index 2acd8fbff..589b17fab 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -208,11 +208,11 @@ const watchOnRemoveDir = (f: (path: string, watch: FolderWatch) => void) => { const watchFindFiles = (folderPath: string) => ipcRenderer.invoke("watchFindFiles", folderPath); -const watchReset = () => { +const watchReset = async () => { ipcRenderer.removeAllListeners("watchAddFile"); ipcRenderer.removeAllListeners("watchRemoveFile"); ipcRenderer.removeAllListeners("watchRemoveDir"); - return ipcRenderer.invoke("watchReset"); + await ipcRenderer.invoke("watchReset"); }; // - Upload From 11d6cdd7c114468871715844aa46b98f7512d5c5 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 1 May 2024 19:36:09 +0530 Subject: [PATCH 133/367] Rename variables --- mobile/lib/db/embeddings_db.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mobile/lib/db/embeddings_db.dart b/mobile/lib/db/embeddings_db.dart index a993c3275..0eb1d3f6d 100644 --- a/mobile/lib/db/embeddings_db.dart +++ b/mobile/lib/db/embeddings_db.dart @@ -133,13 +133,13 @@ class EmbeddingsDB { } Future _clearDeprecatedStores(Directory dir) async { - final deprecatedStore = Directory(dir.path + "/object-box-store"); - if (await deprecatedStore.exists()) { - await deprecatedStore.delete(recursive: true); + final deprecatedObjectBox = Directory(dir.path + "/object-box-store"); + if (await deprecatedObjectBox.exists()) { + await deprecatedObjectBox.delete(recursive: true); } - final deprecatedDB = File(dir.path + "/default.isar"); - if (await deprecatedDB.exists()) { - await deprecatedDB.delete(); + final deprecatedIsar = File(dir.path + "/default.isar"); + if (await deprecatedIsar.exists()) { + await deprecatedIsar.delete(); } } From 2690b874ee5763bbadd97363edf9d026e5c71620 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 19:49:09 +0530 Subject: [PATCH 134/367] Make the migration a no-op --- web/apps/photos/src/utils/storage/mlIDbStorage.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web/apps/photos/src/utils/storage/mlIDbStorage.ts b/web/apps/photos/src/utils/storage/mlIDbStorage.ts index 40e6dad66..766c3ac9a 100644 --- a/web/apps/photos/src/utils/storage/mlIDbStorage.ts +++ b/web/apps/photos/src/utils/storage/mlIDbStorage.ts @@ -144,7 +144,13 @@ class MLIDbStorage { .objectStore("configs") .add(DEFAULT_ML_SEARCH_CONFIG, ML_SEARCH_CONFIG_NAME); } + /* + This'll go in version 5. Note that version 4 was never released, + but it was in main for a while, so we'll just skip it to avoid + breaking the upgrade path for people who ran the mainline. + */ if (oldVersion < 4) { + /* try { await tx .objectStore("configs") @@ -163,8 +169,8 @@ class MLIDbStorage { // the shipped implementation should have a more // deterministic migration. } + */ } - log.info( `ML DB upgraded from version ${oldVersion} to version ${newVersion}`, ); From 0226a99fa3fdebb52d8a449bfc4175de167b7a6b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 20:09:36 +0530 Subject: [PATCH 135/367] Disable enabling ML search --- .../src/components/ml/MLSearchSettings.tsx | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/web/apps/photos/src/components/ml/MLSearchSettings.tsx b/web/apps/photos/src/components/ml/MLSearchSettings.tsx index 583b79529..9b50c2d6a 100644 --- a/web/apps/photos/src/components/ml/MLSearchSettings.tsx +++ b/web/apps/photos/src/components/ml/MLSearchSettings.tsx @@ -22,7 +22,7 @@ import { getFaceSearchEnabledStatus, updateFaceSearchEnabledStatus, } from "services/userService"; -import { openLink } from "utils/common"; +import { isInternalUser } from "utils/user"; export const MLSearchSettings = ({ open, onClose, onRootClose }) => { const { @@ -255,8 +255,8 @@ function EnableFaceSearch({ open, onClose, enableFaceSearch, onRootClose }) { } function EnableMLSearch({ onClose, enableMlSearch, onRootClose }) { - const showDetails = () => - openLink("https://ente.io/blog/desktop-ml-beta", true); + // const showDetails = () => + // openLink("https://ente.io/blog/desktop-ml-beta", true); return ( @@ -269,25 +269,37 @@ function EnableMLSearch({ onClose, enableMlSearch, onRootClose }) { {" "} - + {/* */} +

+ We're putting finishing touches, coming back soon! +

+

+ + Existing indexed faces will continue to show. + +

- - - + {/* + - + > + {t("ML_MORE_DETAILS")} + + */} + + )} ); From 5ba2e35af6e71d5f4e4823fc5e03655f48494e46 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 20:13:08 +0530 Subject: [PATCH 136/367] Force disable it for non internal users --- .../photos/src/utils/machineLearning/config.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/web/apps/photos/src/utils/machineLearning/config.ts b/web/apps/photos/src/utils/machineLearning/config.ts index 4d2030ca3..30a65b8f1 100644 --- a/web/apps/photos/src/utils/machineLearning/config.ts +++ b/web/apps/photos/src/utils/machineLearning/config.ts @@ -10,6 +10,7 @@ import mlIDbStorage, { ML_SYNC_CONFIG_NAME, ML_SYNC_JOB_CONFIG_NAME, } from "utils/storage/mlIDbStorage"; +import { isInternalUser } from "utils/user"; export async function getMLSyncJobConfig() { return mlIDbStorage.getConfig( @@ -23,10 +24,15 @@ export async function getMLSyncConfig() { } export async function getMLSearchConfig() { - return mlIDbStorage.getConfig( - ML_SEARCH_CONFIG_NAME, - DEFAULT_ML_SEARCH_CONFIG, - ); + if (isInternalUser()) { + return mlIDbStorage.getConfig( + ML_SEARCH_CONFIG_NAME, + DEFAULT_ML_SEARCH_CONFIG, + ); + } + // Force disabled for everyone else while we finalize it to avoid redundant + // reindexing for users. + return DEFAULT_ML_SEARCH_CONFIG; } export async function updateMLSyncJobConfig(newConfig: JobConfig) { From 30f22e333abc8cf84578aa1aa12dc2b515db3eff Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 21:04:41 +0530 Subject: [PATCH 137/367] Pass file when we have it --- .../src/services/upload/uploadManager.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/web/apps/photos/src/services/upload/uploadManager.ts b/web/apps/photos/src/services/upload/uploadManager.ts index 3d53adbea..38fd7037b 100644 --- a/web/apps/photos/src/services/upload/uploadManager.ts +++ b/web/apps/photos/src/services/upload/uploadManager.ts @@ -609,11 +609,25 @@ class UploadManager { ].includes(uploadResult) ) { try { + let file: File | undefined; + const uploadItem = + uploadableItem.uploadItem ?? + uploadableItem.livePhotoAssets.image; + if (uploadItem) { + if (uploadItem instanceof File) { + file = uploadItem; + } else if ( + typeof uploadItem == "string" || + Array.isArray(uploadItem) + ) { + // path from desktop, no file object + } else { + file = uploadItem.file; + } + } eventBus.emit(Events.FILE_UPLOADED, { enteFile: decryptedFile, - localFile: - uploadableItem.uploadItem ?? - uploadableItem.livePhotoAssets.image, + localFile: file, }); } catch (e) { log.warn("Ignoring error in fileUploaded handlers", e); From cd5c1e35fa32c5c3474de66b512246a1eafb9764 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 1 May 2024 21:05:29 +0530 Subject: [PATCH 138/367] Disable live clip, rely on the thumbnailed version --- web/apps/photos/src/pages/gallery/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/photos/src/pages/gallery/index.tsx b/web/apps/photos/src/pages/gallery/index.tsx index 70b48c3cc..20d95ce00 100644 --- a/web/apps/photos/src/pages/gallery/index.tsx +++ b/web/apps/photos/src/pages/gallery/index.tsx @@ -370,7 +370,7 @@ export default function Gallery() { syncWithRemote(false, true); }, SYNC_INTERVAL_IN_MICROSECONDS); if (electron) { - void clipService.setupOnFileUploadListener(); + // void clipService.setupOnFileUploadListener(); electron.onMainWindowFocus(() => syncWithRemote(false, true)); } }; From 8327c2b8816ff6763d03e03cc4235c929f38f38c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 2 May 2024 10:23:31 +0530 Subject: [PATCH 139/367] Remove unused ElectronFile --- .../photos/src/components/Upload/Uploader.tsx | 3 +- web/apps/photos/src/services/upload/types.ts | 12 ++++++- web/packages/next/types/file.ts | 36 ------------------- 3 files changed, 12 insertions(+), 39 deletions(-) delete mode 100644 web/packages/next/types/file.ts diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx index 53ad1fb32..717430655 100644 --- a/web/apps/photos/src/components/Upload/Uploader.tsx +++ b/web/apps/photos/src/components/Upload/Uploader.tsx @@ -1,6 +1,5 @@ import { basename } from "@/next/file"; import log from "@/next/log"; -import { type FileAndPath } from "@/next/types/file"; import type { CollectionMapping, Electron, ZipItem } from "@/next/types/ipc"; import { CustomError } from "@ente/shared/error"; import { isPromise } from "@ente/shared/utils"; @@ -20,7 +19,7 @@ import { getPublicCollectionUploaderName, savePublicCollectionUploaderName, } from "services/publicCollectionService"; -import type { UploadItem } from "services/upload/types"; +import type { FileAndPath, UploadItem } from "services/upload/types"; import type { InProgressUpload, SegregatedFinishedUploads, diff --git a/web/apps/photos/src/services/upload/types.ts b/web/apps/photos/src/services/upload/types.ts index 05ad332d4..25e2ab408 100644 --- a/web/apps/photos/src/services/upload/types.ts +++ b/web/apps/photos/src/services/upload/types.ts @@ -1,4 +1,3 @@ -import type { FileAndPath } from "@/next/types/file"; import type { ZipItem } from "@/next/types/ipc"; /** @@ -30,6 +29,17 @@ import type { ZipItem } from "@/next/types/ipc"; */ export type UploadItem = File | FileAndPath | string | ZipItem; +/** + * When we are running in the context of our desktop app, we have access to the + * absolute path of {@link File} objects. This convenience type clubs these two + * bits of information, saving us the need to query the path again and again + * using the {@link getPathForFile} method of {@link Electron}. + */ +export interface FileAndPath { + file: File; + path: string; +} + /** * The of cases of {@link UploadItem} that apply when we're running in the * context of our desktop app. diff --git a/web/packages/next/types/file.ts b/web/packages/next/types/file.ts deleted file mode 100644 index 6dd1032cd..000000000 --- a/web/packages/next/types/file.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * ElectronFile is a custom interface that is used to represent - * any file on disk as a File-like object in the Electron desktop app. - * - * This was added to support the auto-resuming of failed uploads - * which needed absolute paths to the files which the - * normal File interface does not provide. - */ -export interface ElectronFile { - name: string; - path: string; - size: number; - lastModified: number; - stream: () => Promise>; - blob: () => Promise; - arrayBuffer: () => Promise; -} - -/** - * When we are running in the context of our desktop app, we have access to the - * absolute path of {@link File} objects. This convenience type clubs these two - * bits of information, saving us the need to query the path again and again - * using the {@link getPathForFile} method of {@link Electron}. - */ -export interface FileAndPath { - file: File; - path: string; -} - -export interface EventQueueItem { - type: "upload" | "trash"; - folderPath: string; - collectionName?: string; - paths?: string[]; - files?: ElectronFile[]; -} From 68721b8168b7292d32a7319d21f67e620aa8e3aa Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 2 May 2024 11:10:26 +0530 Subject: [PATCH 140/367] Pick from the correct table --- web/apps/photos/src/services/embeddingService.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/web/apps/photos/src/services/embeddingService.ts b/web/apps/photos/src/services/embeddingService.ts index a4309e314..36af84842 100644 --- a/web/apps/photos/src/services/embeddingService.ts +++ b/web/apps/photos/src/services/embeddingService.ts @@ -86,7 +86,11 @@ export const syncEmbeddings = async () => { allLocalFiles.forEach((file) => { fileIdToKeyMap.set(file.id, file.key); }); - await cleanupDeletedEmbeddings(allLocalFiles, allEmbeddings); + await cleanupDeletedEmbeddings( + allLocalFiles, + allEmbeddings, + EMBEDDINGS_TABLE, + ); log.info(`Syncing embeddings localCount: ${allEmbeddings.length}`); for (const model of models) { let modelLastSinceTime = await getModelEmbeddingSyncTime(model); @@ -168,7 +172,11 @@ export const syncFileEmbeddings = async () => { allLocalFiles.forEach((file) => { fileIdToKeyMap.set(file.id, file.key); }); - await cleanupDeletedEmbeddings(allLocalFiles, allEmbeddings); + await cleanupDeletedEmbeddings( + allLocalFiles, + allEmbeddings, + FILE_EMBEDING_TABLE, + ); log.info(`Syncing embeddings localCount: ${allEmbeddings.length}`); for (const model of models) { let modelLastSinceTime = await getModelEmbeddingSyncTime(model); @@ -289,6 +297,7 @@ export const putEmbedding = async ( export const cleanupDeletedEmbeddings = async ( allLocalFiles: EnteFile[], allLocalEmbeddings: Embedding[] | FileML[], + tableName: string, ) => { const activeFileIds = new Set(); allLocalFiles.forEach((file) => { @@ -302,6 +311,6 @@ export const cleanupDeletedEmbeddings = async ( log.info( `cleanupDeletedEmbeddings embeddingsCount: ${allLocalEmbeddings.length} remainingEmbeddingsCount: ${remainingEmbeddings.length}`, ); - await localForage.setItem(EMBEDDINGS_TABLE, remainingEmbeddings); + await localForage.setItem(tableName, remainingEmbeddings); } }; From fa182b951dbb81e8fd0b8485d4b8aa4aecf16a74 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 2 May 2024 12:52:05 +0530 Subject: [PATCH 141/367] [desktop] Resurrect build Untested --- .github/workflows/desktop-release.yml | 72 +++++++++++++++++++++++++ desktop/.github/workflows/build.yml | 55 ------------------- desktop/README.md | 6 --- desktop/docs/release.md | 78 ++++++++++----------------- 4 files changed, 99 insertions(+), 112 deletions(-) create mode 100644 .github/workflows/desktop-release.yml delete mode 100644 desktop/.github/workflows/build.yml diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml new file mode 100644 index 000000000..44c63e5b2 --- /dev/null +++ b/.github/workflows/desktop-release.yml @@ -0,0 +1,72 @@ +name: "Release (photos desktop)" + +# This will create a new draft release with public artifacts. +# +# Note that a release will only get created if there is an associated tag +# (GitHub releases need a corresponding tag). + +on: + workflow_dispatch: # Allow manually running the action + push: + # Run when a tag matching the pattern "photosd-v*"" is pushed + # See: [Note: Testing release workflows that are triggered by tags] + tags: + - "photosd-v*" + +jobs: + release: + runs-on: ${{ matrix.os }} + + defaults: + run: + working-directory: desktop + + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: yarn install + + - name: Prepare for app notarization + if: startsWith(matrix.os, 'macos') + # Import Apple API key for app notarization on macOS + run: | + mkdir -p ~/private_keys/ + echo '${{ secrets.APPLE_API_KEY }}' > ~/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8 + + - name: Install libarchive-tools for pacman build + if: startsWith(matrix.os, 'ubuntu') + # See: + # https://github.com/electron-userland/electron-builder/issues/4181 + run: sudo apt-get install libarchive-tools + + - name: Build + uses: ente-io/action-electron-builder@v1.0.0 + with: + # GitHub token, automatically provided to the action + # (No need to define this secret in the repo settings) + github_token: ${{ secrets.GITHUB_TOKEN }} + + # If the commit is tagged with a version (e.g. "v1.0.0"), + # release the app after building + release: ${{ startsWith(github.ref, 'refs/tags/v') }} + + mac_certs: ${{ secrets.MAC_CERTS }} + mac_certs_password: ${{ secrets.MAC_CERTS_PASSWORD }} + env: + # macOS notarization API key details + API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + API_KEY_ISSUER_ID: ${{ secrets.APPLE_API_KEY_ISSUER_ID }} + USE_HARD_LINKS: false diff --git a/desktop/.github/workflows/build.yml b/desktop/.github/workflows/build.yml deleted file mode 100644 index acd744c05..000000000 --- a/desktop/.github/workflows/build.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Build/release - -on: - push: - tags: - - v* - -jobs: - release: - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [macos-latest, ubuntu-latest, windows-latest] - - steps: - - name: Check out Git repository - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Install Node.js, NPM and Yarn - uses: actions/setup-node@v3 - with: - node-version: 20 - - - name: Prepare for app notarization - if: startsWith(matrix.os, 'macos') - # Import Apple API key for app notarization on macOS - run: | - mkdir -p ~/private_keys/ - echo '${{ secrets.api_key }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8 - - - name: Install libarchive-tools for pacman build # Related https://github.com/electron-userland/electron-builder/issues/4181 - if: startsWith(matrix.os, 'ubuntu') - run: sudo apt-get install libarchive-tools - - - name: Ente Electron Builder Action - uses: ente-io/action-electron-builder@v1.0.0 - with: - # GitHub token, automatically provided to the action - # (No need to define this secret in the repo settings) - github_token: ${{ secrets.github_token }} - - # If the commit is tagged with a version (e.g. "v1.0.0"), - # release the app after building - release: ${{ startsWith(github.ref, 'refs/tags/v') }} - - mac_certs: ${{ secrets.mac_certs }} - mac_certs_password: ${{ secrets.mac_certs_password }} - env: - # macOS notarization API key - API_KEY_ID: ${{ secrets.api_key_id }} - API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id}} - USE_HARD_LINKS: false diff --git a/desktop/README.md b/desktop/README.md index 05149f5d0..39b7663fa 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -10,12 +10,6 @@ To know more about Ente, see [our main README](../README.md) or visit ## Building from source -> [!CAUTION] -> -> We're improving the security of the desktop app further by migrating to -> Electron's sandboxing and contextIsolation. These updates are still WIP and -> meanwhile the instructions below might not fully work on the main branch. - Fetch submodules ```sh diff --git a/desktop/docs/release.md b/desktop/docs/release.md index 7254e26fc..0a5c2970f 100644 --- a/desktop/docs/release.md +++ b/desktop/docs/release.md @@ -1,43 +1,33 @@ ## Releases -> [!NOTE] -> -> TODO(MR): This document needs to be audited and changed as we do the first -> release from this new monorepo. - The Github Action that builds the desktop binaries is triggered by pushing a tag -matching the pattern `photos-desktop-v1.2.3`. This value should match the -version in `package.json`. +matching the pattern `photosd-v1.2.3`. This value should match the version in +`package.json`. -So the process for doing a release would be. +To make a new release -1. Create a new branch (can be named anything). On this branch, include your - changes. +1. Create a new branch (can be named anything). On this branch, change the + `version` in `package.json` to `1.x.x` and finalize `CHANGELOG.md`. -2. Mention the changes in `CHANGELOG.md`. - -3. Changing the `version` in `package.json` to `1.x.x`. - -4. Commit and push to remote +2. Commit, tag and push to remote. Note that the tag should have a `photosd-` + prefix: ```sh - git add package.json && git commit -m 'Release v1.x.x' - git tag v1.x.x - git push && git push --tags + git add CHANGELOG.md package.json + git commit -m 'Release v1.x.x' + git tag photosd-v1.x.x + git push origin photosd-v1.x.x ``` -This by itself will already trigger a new release. The GitHub action will create -a new draft release that can then be used as descibed below. + This will trigger the GitHub action that will create a new draft release. -To wrap up, we also need to merge back these changes into main. So for that, +3. To wrap up, increase the version number in `package.json` the next release + train. That is, suppose we just released `v4.0.1`. Then we'll change the + version number in main to `v4.0.2-beta.0`. Each pre-release will modify the + `beta.0` part. Finally, at the time of the next release, this'll become + `v4.0.2`. -5. Open a PR for the branch that we're working on (where the above tag was - pushed from) to get it merged into main. - -6. In this PR, also increase the version number for the next release train. That - is, supposed we just released `v4.0.1`. Then we'll change the version number - in main to `v4.0.2-next.0`. Each pre-release will modify the `next.0` part. - Finally, at the time of the next release, this'll become `v4.0.2`. +4. Open a PR for the branch to get it merged into main. The GitHub Action runs on Windows, Linux and macOS. It produces the artifacts defined in the `build` value in `package.json`. @@ -49,26 +39,14 @@ defined in the `build` value in `package.json`. Additionally, the GitHub action notarizes the macOS DMG. For this it needs credentials provided via GitHub secrets. -During the build the Sentry webpack plugin checks to see if SENTRY_AUTH_TOKEN is -defined. If so, it uploads the sourcemaps for the renderer process to Sentry -(For our GitHub action, the SENTRY_AUTH_TOKEN is defined as a GitHub secret). +To rollout the build, we need to publish the draft release. This needs to be +done in the old photos-desktop repository since that the Electron Updater +mechanism doesn't work well with monorepos. So we need to create a new tag with +changelog updates on +[photos-desktop](https://github.com/ente-io/photos-desktop/), use that to create +a new release, copying over all the artifacts. -The sourcemaps for the main (node) process are currently not sent to Sentry -(this works fine in practice since the node process files are not minified, we -only run `tsc`). - -Once the build is done, a draft release with all these artifacts attached is -created. The build is idempotent, so if something goes wrong and we need to -re-run the GitHub action, just delete the draft release (if it got created) and -start a new run by pushing a new tag (if some code changes are required). - -If no code changes are required, say the build failed for some transient network -or sentry issue, we can even be re-run by the build by going to Github Action -age and rerun from there. This will re-trigger for the same tag. - -If everything goes well, we'll have a release on GitHub, and the corresponding -source maps for the renderer process uploaded to Sentry. There isn't anything -else to do: +Thereafter, everything is automated: - The website automatically redirects to the latest release on GitHub when people try to download. @@ -76,7 +54,7 @@ else to do: - The file formats with support auto update (Windows `exe`, the Linux AppImage and the macOS DMG) also check the latest GitHub release automatically to download and apply the update (the rest of the formats don't support auto - updates). + updates yet). - We're not putting the desktop app in other stores currently. It is available as a `brew cask`, but we only had to open a PR to add the initial formula, @@ -87,6 +65,4 @@ else to do: We can also publish the draft releases by checking the "pre-release" option. Such releases don't cause any of the channels (our website, or the desktop app auto updater, or brew) to be notified, instead these are useful for giving links -to pre-release builds to customers. Generally, in the version number for these -we'll add a label to the version, e.g. the "beta.x" in `1.x.x-beta.x`. This -should be done both in `package.json`, and what we tag the commit with. +to pre-release builds to customers. From aed781b0ffe8b54cc857f232c4b01ebe31190c11 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 2 May 2024 19:05:52 +0530 Subject: [PATCH 142/367] Use same credentials as the auth app While we won't actually be using the monorepo for releases, get the action to a known state: - MAC_OS_CERTIFICATE and MAC_OS_CERTIFICATE_PASSWORD is the same GitHub secret that the auth app already uses - Need to add APPLE_API_KEY, APPLE_API_KEY_ID, APPLE_API_KEY_ISSUER_ID. --- .github/workflows/desktop-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index 44c63e5b2..60f012b65 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -63,8 +63,8 @@ jobs: # release the app after building release: ${{ startsWith(github.ref, 'refs/tags/v') }} - mac_certs: ${{ secrets.MAC_CERTS }} - mac_certs_password: ${{ secrets.MAC_CERTS_PASSWORD }} + mac_certs: ${{ secrets.MAC_OS_CERTIFICATE }} + mac_certs_password: ${{ secrets.MAC_OS_CERTIFICATE_PASSWORD }} env: # macOS notarization API key details API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} From 3b3d24e9e05bc3ddccf55e64559b45dd9c843cb4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 2 May 2024 19:34:58 +0530 Subject: [PATCH 143/367] It'll need to live in the releases repo, reword accordingly --- .../.github}/workflows/desktop-release.yml | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) rename {.github => desktop/.github}/workflows/desktop-release.yml (61%) diff --git a/.github/workflows/desktop-release.yml b/desktop/.github/workflows/desktop-release.yml similarity index 61% rename from .github/workflows/desktop-release.yml rename to desktop/.github/workflows/desktop-release.yml index 60f012b65..cb895fd4d 100644 --- a/.github/workflows/desktop-release.yml +++ b/desktop/.github/workflows/desktop-release.yml @@ -1,17 +1,26 @@ -name: "Release (photos desktop)" +name: "Release" # This will create a new draft release with public artifacts. # # Note that a release will only get created if there is an associated tag # (GitHub releases need a corresponding tag). +# +# The canonical source for this action is in the repository where we keep the +# source code for the Ente Photos desktop app: https://github.com/ente-io/ente +# +# However, it actually lives and runs in the repository that we use for making +# releases: https://github.com/ente-io/photos-desktop +# +# We need two repositories because Electron updater currently doesn't work well +# with monorepos. For more details, see `docs/release.md`. on: - workflow_dispatch: # Allow manually running the action push: - # Run when a tag matching the pattern "photosd-v*"" is pushed - # See: [Note: Testing release workflows that are triggered by tags] + # Run when a tag matching the pattern "v*"" is pushed. + # + # See: [Note: Testing release workflows that are triggered by tags]. tags: - - "photosd-v*" + - "v*" jobs: release: @@ -29,6 +38,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: + # Checkout the tag photosd-v1.x.x from the source code + # repository when we're invoked for tag v1.x.x on the releases + # repository. + repository: ente-io/ente + ref: photosd-${{ github.ref }} submodules: recursive - name: Setup node @@ -44,7 +58,7 @@ jobs: # Import Apple API key for app notarization on macOS run: | mkdir -p ~/private_keys/ - echo '${{ secrets.APPLE_API_KEY }}' > ~/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8 + echo '${{ secrets.API_KEY }}' > ~/private_keys/AuthKey_${{ secrets.API_KEY_ID }}.p8 - name: Install libarchive-tools for pacman build if: startsWith(matrix.os, 'ubuntu') @@ -60,13 +74,13 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} # If the commit is tagged with a version (e.g. "v1.0.0"), - # release the app after building + # release the app after building. release: ${{ startsWith(github.ref, 'refs/tags/v') }} - mac_certs: ${{ secrets.MAC_OS_CERTIFICATE }} - mac_certs_password: ${{ secrets.MAC_OS_CERTIFICATE_PASSWORD }} + mac_certs: ${{ secrets.MAC_CERTS }} + mac_certs_password: ${{ secrets.MAC_CERTS_PASSWORD }} env: # macOS notarization API key details - API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - API_KEY_ISSUER_ID: ${{ secrets.APPLE_API_KEY_ISSUER_ID }} + API_KEY_ID: ${{ secrets.API_KEY_ID }} + API_KEY_ISSUER_ID: ${{ secrets.API_KEY_ISSUER_ID }} USE_HARD_LINKS: false From fecfb4a8b7d12c4c0540d363bdd2ddad7f60753f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 2 May 2024 19:52:39 +0530 Subject: [PATCH 144/367] Hopes and dreams --- desktop/docs/release.md | 74 ++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/desktop/docs/release.md b/desktop/docs/release.md index 0a5c2970f..59b2be10e 100644 --- a/desktop/docs/release.md +++ b/desktop/docs/release.md @@ -1,33 +1,59 @@ ## Releases -The Github Action that builds the desktop binaries is triggered by pushing a tag -matching the pattern `photosd-v1.2.3`. This value should match the version in -`package.json`. +Conceptually, the release is straightforward: We push a tag, a GitHub workflow +gets triggered that creates a draft release with artifacts built from that tag. +We then publish that release. The download links on our website, and existing +apps already know how to check for the latest GitHub release and update +accordingly. -To make a new release +The complication comes by the fact that Electron Updater (the mechanism that we +use for auto updates) doesn't work well with monorepos. So we need to keep a +separate (non-mono) repository just for doing releases. -1. Create a new branch (can be named anything). On this branch, change the - `version` in `package.json` to `1.x.x` and finalize `CHANGELOG.md`. +- Source code lives here, in [ente-io/ente](https://github.com/ente-io/ente). -2. Commit, tag and push to remote. Note that the tag should have a `photosd-` - prefix: +- Releases are done from + [ente-io/photos-desktop](https://github.com/ente-io/photos-desktop). + +## Workflow + +The workflow is: + +1. Finalize the changes in the source repo. + + - Update the CHANGELOG. + - Update the version in `package.json` + - `git commit -m 'Release v1.x.x'` + - Open PR, merge into main. + + +2. Tag this commit with a tag matching the pattern `photosd-v1.2.3`, where + `1.2.3` is the version in `package.json` ```sh - git add CHANGELOG.md package.json - git commit -m 'Release v1.x.x' git tag photosd-v1.x.x git push origin photosd-v1.x.x ``` - This will trigger the GitHub action that will create a new draft release. +3. Head over to the releases repository, copy all relevant changes from the + source repository, commit and push the changes. -3. To wrap up, increase the version number in `package.json` the next release - train. That is, suppose we just released `v4.0.1`. Then we'll change the - version number in main to `v4.0.2-beta.0`. Each pre-release will modify the - `beta.0` part. Finally, at the time of the next release, this'll become - `v4.0.2`. + ```sh + cp ../ente/desktop/CHANGELOG.md CHANGELOG.md + git add CHANGELOG.md + git commit -m 'Release v1.x.x' + git push origin main + ``` -4. Open a PR for the branch to get it merged into main. +4. Tag this commit, but this time _don't_ use the `photosd-` prefix. Push the + tag to trigger the GitHub action. + + ```sh + git tag v1.x.x + git push origin v1.x.x + ``` + +## Post build The GitHub Action runs on Windows, Linux and macOS. It produces the artifacts defined in the `build` value in `package.json`. @@ -36,17 +62,11 @@ defined in the `build` value in `package.json`. - Linux - An AppImage, and 3 other packages (`.rpm`, `.deb`, `.pacman`) - macOS - A universal DMG -Additionally, the GitHub action notarizes the macOS DMG. For this it needs -credentials provided via GitHub secrets. +Additionally, the GitHub action notarizes and signs the macOS DMG (For this it +uses credentials provided via GitHub secrets). -To rollout the build, we need to publish the draft release. This needs to be -done in the old photos-desktop repository since that the Electron Updater -mechanism doesn't work well with monorepos. So we need to create a new tag with -changelog updates on -[photos-desktop](https://github.com/ente-io/photos-desktop/), use that to create -a new release, copying over all the artifacts. - -Thereafter, everything is automated: +To rollout the build, we need to publish the draft release. Thereafter, +everything is automated: - The website automatically redirects to the latest release on GitHub when people try to download. From 2f2d15c9f2127602e448cbedeb697d777933d134 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 2 May 2024 20:22:40 +0530 Subject: [PATCH 145/367] lint --- desktop/docs/release.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/desktop/docs/release.md b/desktop/docs/release.md index 59b2be10e..0d1b11bc6 100644 --- a/desktop/docs/release.md +++ b/desktop/docs/release.md @@ -21,11 +21,10 @@ The workflow is: 1. Finalize the changes in the source repo. - - Update the CHANGELOG. - - Update the version in `package.json` - - `git commit -m 'Release v1.x.x'` - - Open PR, merge into main. - + - Update the CHANGELOG. + - Update the version in `package.json` + - `git commit -m 'Release v1.x.x'` + - Open PR, merge into main. 2. Tag this commit with a tag matching the pattern `photosd-v1.2.3`, where `1.2.3` is the version in `package.json` From 67eed1aa89c18a320c5473b74dea6bf68b470332 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 2 May 2024 21:20:26 +0530 Subject: [PATCH 146/367] Upgrade to Electron 30 This picks up the stream fix we need > Fixed data corruption when protocol.handle() processed incoming data asynchronously. #41933 (Also in 31) > > https://github.com/electron/electron/releases/tag/v30.0.2 --- desktop/package.json | 2 +- desktop/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/desktop/package.json b/desktop/package.json index 5ec8b45be..d9aaf133e 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -43,7 +43,7 @@ "@typescript-eslint/eslint-plugin": "^7", "@typescript-eslint/parser": "^7", "concurrently": "^8", - "electron": "^29", + "electron": "^30", "electron-builder": "^24", "electron-builder-notarize": "^1.5", "eslint": "^8", diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 2210d4745..d4338312b 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -1199,10 +1199,10 @@ electron-updater@^6.1: semver "^7.3.8" tiny-typed-emitter "^2.1.0" -electron@^29: - version "29.3.1" - resolved "https://registry.yarnpkg.com/electron/-/electron-29.3.1.tgz#87c82b2cd2c326f78f036499377a5448bea5d4bb" - integrity sha512-auge1/6RVqgUd6TgIq88wKdUCJi2cjESi3jy7d+6X4JzvBGprKBqMJ8JSSFpu/Px1YJrFUKAxfy6SC+TQf1uLw== +electron@^30: + version "30.0.2" + resolved "https://registry.yarnpkg.com/electron/-/electron-30.0.2.tgz#95ba019216bf8be9f3097580123e33ea37497733" + integrity sha512-zv7T+GG89J/hyWVkQsLH4Y/rVEfqJG5M/wOBIGNaDdqd8UV9/YZPdS7CuFeaIj0H9LhCt95xkIQNpYB/3svOkQ== dependencies: "@electron/get" "^2.0.0" "@types/node" "^20.9.0" From 6a990020649965c377bd843a4bbb400e3faaa0fb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 2 May 2024 21:22:58 +0530 Subject: [PATCH 147/367] Start using it --- desktop/src/main.ts | 8 -------- web/apps/photos/src/utils/native-stream.ts | 23 ++-------------------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index eb1114cc4..49b316206 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -127,15 +127,7 @@ const registerPrivilegedSchemes = () => { { scheme: "stream", privileges: { - // TODO(MR): Remove the commented bits if we don't end up - // needing them by the time the IPC refactoring is done. - - // Prevent the insecure origin issues when fetching this - // secure: true, - // Allow the web fetch API in the renderer to use this scheme. supportFetchAPI: true, - // Allow it to be used with video tags. - // stream: true, }, }, ]); diff --git a/web/apps/photos/src/utils/native-stream.ts b/web/apps/photos/src/utils/native-stream.ts index 941c5a988..4ed9da753 100644 --- a/web/apps/photos/src/utils/native-stream.ts +++ b/web/apps/photos/src/utils/native-stream.ts @@ -93,40 +93,21 @@ export const writeStream = async ( const params = new URLSearchParams({ path }); const url = new URL(`stream://write?${params.toString()}`); - // TODO(MR): This doesn't currently work. - // - // Not sure what I'm doing wrong here; I've opened an issue upstream - // https://github.com/electron/electron/issues/41872 - // - // A gist with a minimal reproduction - // https://gist.github.com/mnvr/e08d9f4876fb8400b7615347b4d268eb - // - // Meanwhile, write the complete body in one go (this'll eventually run into - // memory failures with large files - just a temporary stopgap to get the - // code to work). - - /* // The duplex parameter needs to be set to 'half' when streaming requests. // // Currently browsers, and specifically in our case, since this code runs // only within our desktop (Electron) app, Chromium, don't support 'full' // duplex mode (i.e. streaming both the request and the response). // https://developer.chrome.com/docs/capabilities/web-apis/fetch-streaming-requests - const req = new Request(`stream://write${path}`, { + const req = new Request(url, { // GET can't have a body method: "POST", body: stream, - // --@ts-expect-error TypeScript's libdom.d.ts does not include the + // @ts-expect-error TypeScript's libdom.d.ts does not include the // "duplex" parameter, e.g. see // https://github.com/node-fetch/node-fetch/issues/1769. duplex: "half", }); - */ - - const req = new Request(url, { - method: "POST", - body: await new Response(stream).blob(), - }); const res = await fetch(req); if (!res.ok) From 0c4da8c86aea2aa00ea47df64a70f367a7410697 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 2 May 2024 21:38:02 +0530 Subject: [PATCH 148/367] POSIX paths --- desktop/src/preload.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index 589b17fab..407e541ff 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -217,7 +217,25 @@ const watchReset = async () => { // - Upload -const pathForFile = (file: File) => webUtils.getPathForFile(file); +const pathForFile = (file: File) => { + const path = webUtils.getPathForFile(file); + // The path that we get back from `webUtils.getPathForFile` on Windows uses + // "/" as the path separator. Convert them to POSIX separators. + // + // Note that we do not have access to the path or the os module in the + // preload script, thus this hand rolled transformation. + + // However that makes TypeScript fidgety since we it cannot find navigator, + // as we haven't included "lib": ["dom"] in our tsconfig to avoid making DOM + // APIs available to our main Node.js code. We could create a separate + // tsconfig just for the preload script, but for now let's go with a cast. + // + // @ts-expect-error navigator is not defined. + const platform = (navigator as { platform: string }).platform; + return platform.toLowerCase().includes("win") + ? path.split("\\").join("/") + : path; +}; const listZipItems = (zipPath: string) => ipcRenderer.invoke("listZipItems", zipPath); From d08c2b4fa0830962d0cdf0b2cb7483d60710c995 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Fri, 3 May 2024 01:40:38 +0000 Subject: [PATCH 149/367] New Crowdin translations by GitHub Action --- .../next/locales/pt-BR/translation.json | 18 +++++++++--------- .../next/locales/zh-CN/translation.json | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/web/packages/next/locales/pt-BR/translation.json b/web/packages/next/locales/pt-BR/translation.json index dfe0030c5..9fc00517c 100644 --- a/web/packages/next/locales/pt-BR/translation.json +++ b/web/packages/next/locales/pt-BR/translation.json @@ -239,7 +239,7 @@ "ENABLE_MAPS": "Habilitar mapa?", "ENABLE_MAP": "Habilitar mapa", "DISABLE_MAPS": "Desativar Mapas?", - "ENABLE_MAP_DESCRIPTION": "Isto mostrará suas fotos em um mapa do mundo.

Este mapa é hospedado pelo OpenStreetMap , e os exatos locais de suas fotos nunca são compartilhados.

Você pode desativar esse recurso a qualquer momento nas Configurações.

", + "ENABLE_MAP_DESCRIPTION": "

Isto mostrará suas fotos em um mapa do mundo.

Este mapa é hospedado pelo OpenStreetMap, e os exatos locais de suas fotos nunca são compartilhados.

Você pode desativar esse recurso a qualquer momento nas Configurações.

", "DISABLE_MAP_DESCRIPTION": "

Isto irá desativar a exibição de suas fotos em um mapa mundial.

Você pode ativar este recurso a qualquer momento nas Configurações.

", "DISABLE_MAP": "Desabilitar mapa", "DETAILS": "Detalhes", @@ -380,14 +380,14 @@ "LINK_EXPIRED_MESSAGE": "Este link expirou ou foi desativado!", "MANAGE_LINK": "Gerenciar link", "LINK_TOO_MANY_REQUESTS": "Desculpe, este álbum foi visualizado em muitos dispositivos!", - "FILE_DOWNLOAD": "Permitir transferências", + "FILE_DOWNLOAD": "Permitir downloads", "LINK_PASSWORD_LOCK": "Bloqueio de senha", "PUBLIC_COLLECT": "Permitir adicionar fotos", "LINK_DEVICE_LIMIT": "Limite de dispositivos", "NO_DEVICE_LIMIT": "Nenhum", "LINK_EXPIRY": "Expiração do link", "NEVER": "Nunca", - "DISABLE_FILE_DOWNLOAD": "Desabilitar transferência", + "DISABLE_FILE_DOWNLOAD": "Desabilitar download", "DISABLE_FILE_DOWNLOAD_MESSAGE": "

Tem certeza de que deseja desativar o botão de download para arquivos?

Os visualizadores ainda podem capturar imagens da tela ou salvar uma cópia de suas fotos usando ferramentas externas.

", "SHARED_USING": "Compartilhar usando ", "SHARING_REFERRAL_CODE": "Use o código {{referralCode}} para obter 10 GB de graça", @@ -408,8 +408,8 @@ "STOP_ALL_UPLOADS_MESSAGE": "Tem certeza que deseja parar todos os envios em andamento?", "STOP_UPLOADS_HEADER": "Parar envios?", "YES_STOP_UPLOADS": "Sim, parar envios", - "STOP_DOWNLOADS_HEADER": "Parar transferências?", - "YES_STOP_DOWNLOADS": "Sim, parar transferências", + "STOP_DOWNLOADS_HEADER": "Parar downloads?", + "YES_STOP_DOWNLOADS": "Sim, parar downloads", "STOP_ALL_DOWNLOADS_MESSAGE": "Tem certeza que deseja parar todos as transferências em andamento?", "albums_one": "1 Álbum", "albums_other": "{{count, number}} Álbuns", @@ -556,8 +556,8 @@ "SELECT_COLLECTION": "Selecionar álbum", "PIN_ALBUM": "Fixar álbum", "UNPIN_ALBUM": "Desafixar álbum", - "DOWNLOAD_COMPLETE": "Transferência concluída", - "DOWNLOADING_COLLECTION": "Transferindo {{name}}", + "DOWNLOAD_COMPLETE": "Download concluído", + "DOWNLOADING_COLLECTION": "Fazendo download de {{name}}", "DOWNLOAD_FAILED": "Falha ao baixar", "DOWNLOAD_PROGRESS": "{{progress.current}} / {{progress.total}} arquivos", "CHRISTMAS": "Natal", @@ -622,6 +622,6 @@ "TRY_AGAIN": "Tente novamente", "PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER": "Siga os passos do seu navegador para continuar acessando.", "LOGIN_WITH_PASSKEY": "Entrar com a chave de acesso", - "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_first_album_name": "Meu Primeiro Álbum", + "autogenerated_default_album_name": "Novo Álbum" } diff --git a/web/packages/next/locales/zh-CN/translation.json b/web/packages/next/locales/zh-CN/translation.json index d2345f1ae..c67018aaa 100644 --- a/web/packages/next/locales/zh-CN/translation.json +++ b/web/packages/next/locales/zh-CN/translation.json @@ -622,6 +622,6 @@ "TRY_AGAIN": "重试", "PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER": "按照浏览器中提示的步骤继续登录。", "LOGIN_WITH_PASSKEY": "使用通行密钥来登录", - "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_first_album_name": "我的第一个相册", + "autogenerated_default_album_name": "新建相册" } From 3eda263d26211418c95a3b2b4a0d1f4b10e06139 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 09:38:58 +0530 Subject: [PATCH 150/367] Clarify cwd --- docs/docs/self-hosting/guides/custom-server/index.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/docs/self-hosting/guides/custom-server/index.md b/docs/docs/self-hosting/guides/custom-server/index.md index bf695af30..a5ce76cc2 100644 --- a/docs/docs/self-hosting/guides/custom-server/index.md +++ b/docs/docs/self-hosting/guides/custom-server/index.md @@ -25,10 +25,13 @@ configure the endpoint the app should be connecting to. > You can download the CLI from > [here](https://github.com/ente-io/ente/releases?q=tag%3Acli-v0) -Define a config.yaml and put it either in the same directory as CLI or path -defined in env variable `ENTE_CLI_CONFIG_PATH` +Define a config.yaml and put it either in the same directory as where you run +the CLI from ("current working directory"), or in the path defined in env +variable `ENTE_CLI_CONFIG_PATH`: ```yaml endpoint: api: "http://localhost:8080" ``` + +(Another [example](https://github.com/ente-io/ente/blob/main/cli/config.yaml.example)) From 977d212be6151b54b94946ba3acbc97204a6380d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 09:42:17 +0530 Subject: [PATCH 151/367] Add a notice about ente account add --- cli/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cli/README.md b/cli/README.md index 8fc9aa694..40858da0f 100644 --- a/cli/README.md +++ b/cli/README.md @@ -36,7 +36,8 @@ ente --help ### Accounts -If you wish, you can add multiple accounts (your own and that of your family members) and export all data using this tool. +If you wish, you can add multiple accounts (your own and that of your family +members) and export all data using this tool. #### Add an account @@ -44,6 +45,12 @@ If you wish, you can add multiple accounts (your own and that of your family mem ente account add ``` +> [!NOTE] +> +> `ente account add` does not create new accounts, it just adds pre-existing +> accounts to the list of accounts that the CLI knows about so that you can use +> them for other actions. + #### List accounts ```shell From 024f160ca0735f67e3a649058f4bf1fb2290af62 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 10:14:27 +0530 Subject: [PATCH 152/367] [mob] Improve log --- mobile/lib/utils/file_uploader.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mobile/lib/utils/file_uploader.dart b/mobile/lib/utils/file_uploader.dart index d77bc95d7..f81f9d34b 100644 --- a/mobile/lib/utils/file_uploader.dart +++ b/mobile/lib/utils/file_uploader.dart @@ -357,10 +357,16 @@ class FileUploader { final List connections = await (Connectivity().checkConnectivity()); bool canUploadUnderCurrentNetworkConditions = true; - if (connections.any((element) => element == ConnectivityResult.mobile)) { - canUploadUnderCurrentNetworkConditions = - Configuration.instance.shouldBackupOverMobileData(); + if (!Configuration.instance.shouldBackupOverMobileData()) { + if (connections.any((element) => element == ConnectivityResult.mobile)) { + canUploadUnderCurrentNetworkConditions = false; + } else { + _logger.info( + "mobileBackupDisabled, backing up with connections: ${connections.map((e) => e.name).toString()}", + ); + } } + if (!canUploadUnderCurrentNetworkConditions) { throw WiFiUnavailableError(); } From ddad863b313b3f9a56d8c2d6a7d00088f0ce47ce Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 10:50:21 +0530 Subject: [PATCH 153/367] Prepare for release --- desktop/CHANGELOG.md | 8 ++++++++ desktop/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/desktop/CHANGELOG.md b/desktop/CHANGELOG.md index 83d2123d8..eb118a424 100644 --- a/desktop/CHANGELOG.md +++ b/desktop/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## v1.7.0 (Unreleased) + +v1.7 is a major rewrite to improve the security of our app. We have enabled +sandboxing and disabled node integration for the renderer process. All this +required restructuring our IPC mechanisms, which resulted in a lot of under the +hood changes. The outcome is a more secure app that also uses the latest and +greatest Electron recommendations. + ## v1.6.63 ### New diff --git a/desktop/package.json b/desktop/package.json index d9aaf133e..a57219aa3 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,6 +1,6 @@ { "name": "ente", - "version": "1.6.63", + "version": "1.7.0-beta+0", "private": true, "description": "Desktop client for Ente Photos", "author": "Ente ", From 647cc0d80348b1b7944bd903c316d21b173d3a94 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 11:00:55 +0530 Subject: [PATCH 154/367] [desktop] Fix ref ref in action ref_name is the (from my understanding) the shorthand we need for prefixing. Untested, will do a test build. --- desktop/.github/workflows/desktop-release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/desktop/.github/workflows/desktop-release.yml b/desktop/.github/workflows/desktop-release.yml index cb895fd4d..ef198ca48 100644 --- a/desktop/.github/workflows/desktop-release.yml +++ b/desktop/.github/workflows/desktop-release.yml @@ -32,7 +32,9 @@ jobs: strategy: matrix: - os: [macos-latest, ubuntu-latest, windows-latest] + os: [macos-latest] + # Commented for testing + # os: [macos-latest, ubuntu-latest, windows-latest] steps: - name: Checkout code @@ -42,7 +44,7 @@ jobs: # repository when we're invoked for tag v1.x.x on the releases # repository. repository: ente-io/ente - ref: photosd-${{ github.ref }} + ref: photosd-${{ github.ref_name }} submodules: recursive - name: Setup node From 5d0e62cf5fb6d3802d9acc0720e9b9c95587a7df Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 11:08:06 +0530 Subject: [PATCH 155/367] Use same convention as other preexisting tags in our repo --- .github/workflows/auth-release.yml | 4 ++-- desktop/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/auth-release.yml b/.github/workflows/auth-release.yml index 707bae895..174b6c1d3 100644 --- a/.github/workflows/auth-release.yml +++ b/.github/workflows/auth-release.yml @@ -17,8 +17,8 @@ name: "Release (auth)" # We use a suffix like `-test` to indicate that these are test tags, and that # they belong to a pre-release. # -# If you need to do multiple tests, add a +x at the end of the tag. e.g. -# `auth-v1.2.3-test+1`. +# If you need to do multiple tests, add a .x at the end of the tag. e.g. +# `auth-v1.2.3-test.1`. # # Once the testing is done, also delete the tag(s) please. diff --git a/desktop/package.json b/desktop/package.json index a57219aa3..dc5ed9dba 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,6 +1,6 @@ { "name": "ente", - "version": "1.7.0-beta+0", + "version": "1.7.0-beta.0", "private": true, "description": "Desktop client for Ente Photos", "author": "Ente ", From e9feec37d506772d5a9e2ce8654232cf42e34013 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 11:28:06 +0530 Subject: [PATCH 156/367] Run the electron builder in the correct path --- desktop/.github/workflows/desktop-release.yml | 2 ++ desktop/docs/release.md | 17 +++-------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/desktop/.github/workflows/desktop-release.yml b/desktop/.github/workflows/desktop-release.yml index ef198ca48..7013d3e57 100644 --- a/desktop/.github/workflows/desktop-release.yml +++ b/desktop/.github/workflows/desktop-release.yml @@ -71,6 +71,8 @@ jobs: - name: Build uses: ente-io/action-electron-builder@v1.0.0 with: + package_root: desktop + # GitHub token, automatically provided to the action # (No need to define this secret in the repo settings) github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/desktop/docs/release.md b/desktop/docs/release.md index 0d1b11bc6..da807b572 100644 --- a/desktop/docs/release.md +++ b/desktop/docs/release.md @@ -34,22 +34,11 @@ The workflow is: git push origin photosd-v1.x.x ``` -3. Head over to the releases repository, copy all relevant changes from the - source repository, commit and push the changes. +3. Head over to the releases repository and run the trigger script, passing it + the tag _without_ the `photosd-` prefix. ```sh - cp ../ente/desktop/CHANGELOG.md CHANGELOG.md - git add CHANGELOG.md - git commit -m 'Release v1.x.x' - git push origin main - ``` - -4. Tag this commit, but this time _don't_ use the `photosd-` prefix. Push the - tag to trigger the GitHub action. - - ```sh - git tag v1.x.x - git push origin v1.x.x + ./.github/trigger-release.sh v1.x.x ``` ## Post build From b52133fe57a26fa8739828667de46ee4e8565a61 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Fri, 3 May 2024 12:00:29 +0530 Subject: [PATCH 157/367] fix: revert totp changes --- auth/lib/utils/totp_util.dart | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/auth/lib/utils/totp_util.dart b/auth/lib/utils/totp_util.dart index 3de7f5047..0d6a8bd68 100644 --- a/auth/lib/utils/totp_util.dart +++ b/auth/lib/utils/totp_util.dart @@ -9,8 +9,7 @@ String getOTP(Code code) { return otp.OTP.generateTOTPCodeString( getSanitizedSecret(code.secret), DateTime.now().millisecondsSinceEpoch, - length: - code.issuer.toLowerCase() == "steam" ? Code.steamDigits : code.digits, + length: code.digits, interval: code.period, algorithm: _getAlgorithm(code), isGoogle: true, @@ -21,8 +20,7 @@ String _getHOTPCode(Code code) { return otp.OTP.generateHOTPCodeString( getSanitizedSecret(code.secret), code.counter, - length: - code.issuer.toLowerCase() == "steam" ? Code.steamDigits : code.digits, + length: code.digits, algorithm: _getAlgorithm(code), isGoogle: true, ); @@ -32,8 +30,7 @@ String getNextTotp(Code code) { return otp.OTP.generateTOTPCodeString( getSanitizedSecret(code.secret), DateTime.now().millisecondsSinceEpoch + code.period * 1000, - length: - code.issuer.toLowerCase() == "steam" ? Code.steamDigits : code.digits, + length: code.digits, interval: code.period, algorithm: _getAlgorithm(code), isGoogle: true, From 0f3555468d39fcf540255a807994f0b90e2eeced Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 12:09:03 +0530 Subject: [PATCH 158/367] Provide repository Fixes: Cannot detect repository by .git/config. Please specify "repository" in the package.json (https://docs.npmjs.com/files/package.json#repository). Please see https://electron.build/configuration/publish failedTask=build stackTrace=Error: Cannot detect repository by .git/config. Please specify "repository" in the package.json (https://docs.npmjs.com/files/package.json#repository). --- desktop/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/desktop/package.json b/desktop/package.json index dc5ed9dba..db5041ead 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -3,6 +3,7 @@ "version": "1.7.0-beta.0", "private": true, "description": "Desktop client for Ente Photos", + "repository": "github:ente-io/photos-desktop", "author": "Ente ", "main": "app/main.js", "scripts": { From 48f24d48b547c8aafe447de1d06eebb063036182 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 12:11:35 +0530 Subject: [PATCH 159/367] [server] Move generateAlphaNumString to util --- .../pkg/controller/storagebonus/referral.go | 31 ++----------------- server/pkg/utils/random/generate.go | 27 ++++++++++++++++ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/server/pkg/controller/storagebonus/referral.go b/server/pkg/controller/storagebonus/referral.go index b452484f4..5bdd951f8 100644 --- a/server/pkg/controller/storagebonus/referral.go +++ b/server/pkg/controller/storagebonus/referral.go @@ -3,7 +3,7 @@ package storagebonus import ( "database/sql" "errors" - "fmt" + "github.com/ente-io/museum/pkg/utils/random" "github.com/ente-io/museum/ente" entity "github.com/ente-io/museum/ente/storagebonus" @@ -119,7 +119,7 @@ func (c *Controller) GetOrCreateReferralCode(ctx *gin.Context, userID int64) (*s if !errors.Is(err, sql.ErrNoRows) { return nil, stacktrace.Propagate(err, "failed to get storagebonus code") } - code, err := generateAlphaNumString(codeLength) + code, err := random.GenerateAlphaNumString(codeLength) if err != nil { return nil, stacktrace.Propagate(err, "") } @@ -131,30 +131,3 @@ func (c *Controller) GetOrCreateReferralCode(ctx *gin.Context, userID int64) (*s } return referralCode, nil } - -// generateAlphaNumString returns AlphaNumeric code of given length -// which exclude number 0 and letter O. The code always starts with an -// alphabet -func generateAlphaNumString(length int) (string, error) { - // Define the alphabet and numbers to be used in the string. - alphabet := "ABCDEFGHIJKLMNPQRSTUVWXYZ" - // Define the alphabet and numbers to be used in the string. - alphaNum := fmt.Sprintf("%s123456789", alphabet) - // Allocate a byte slice with the desired length. - result := make([]byte, length) - // Generate the first letter as an alphabet. - r0, err := auth.GenerateRandomInt(int64(len(alphabet))) - if err != nil { - return "", stacktrace.Propagate(err, "") - } - result[0] = alphabet[r0] - // Generate the remaining characters as alphanumeric. - for i := 1; i < length; i++ { - ri, err := auth.GenerateRandomInt(int64(len(alphaNum))) - if err != nil { - return "", stacktrace.Propagate(err, "") - } - result[i] = alphaNum[ri] - } - return string(result), nil -} diff --git a/server/pkg/utils/random/generate.go b/server/pkg/utils/random/generate.go index 47932b660..75a811c8e 100644 --- a/server/pkg/utils/random/generate.go +++ b/server/pkg/utils/random/generate.go @@ -13,3 +13,30 @@ func GenerateSixDigitOtp() (string, error) { } return fmt.Sprintf("%06d", n), nil } + +// GenerateAlphaNumString returns AlphaNumeric code of given length +// which exclude number 0 and letter O. The code always starts with an +// alphabet +func GenerateAlphaNumString(length int) (string, error) { + // Define the alphabet and numbers to be used in the string. + alphabet := "ABCDEFGHIJKLMNPQRSTUVWXYZ" + // Define the alphabet and numbers to be used in the string. + alphaNum := fmt.Sprintf("%s123456789", alphabet) + // Allocate a byte slice with the desired length. + result := make([]byte, length) + // Generate the first letter as an alphabet. + r0, err := auth.GenerateRandomInt(int64(len(alphabet))) + if err != nil { + return "", stacktrace.Propagate(err, "") + } + result[0] = alphabet[r0] + // Generate the remaining characters as alphanumeric. + for i := 1; i < length; i++ { + ri, err := auth.GenerateRandomInt(int64(len(alphaNum))) + if err != nil { + return "", stacktrace.Propagate(err, "") + } + result[i] = alphaNum[ri] + } + return string(result), nil +} From b9b928797c7cb141a70358384600a6f7068411c2 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 12:24:52 +0530 Subject: [PATCH 160/367] [web][cast] Use server to generate deviceCode --- web/apps/cast/src/pages/index.tsx | 41 ++++++----------------------- web/packages/shared/network/cast.ts | 13 +++++---- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 7ad310fe1..bbba9a1ad 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -9,27 +9,8 @@ import { useEffect, useState } from "react"; import { storeCastData } from "services/cast/castService"; import { useCastReceiver } from "../utils/useCastReceiver"; -// Function to generate cryptographically secure digits -const generateSecureData = (length: number): Uint8Array => { - const array = new Uint8Array(length); - window.crypto.getRandomValues(array); - // Modulo operation to ensure each byte is a single digit - for (let i = 0; i < length; i++) { - array[i] = array[i] % 10; - } - return array; -}; - -const convertDataToDecimalString = (data: Uint8Array): string => { - let decimalString = ""; - for (let i = 0; i < data.length; i++) { - decimalString += data[i].toString(); // No need to pad, as each value is a single digit - } - return decimalString; -}; - export default function PairingMode() { - const [digits, setDigits] = useState([]); + const [deviceCode, setDeviceCode] = useState([]); const [publicKeyB64, setPublicKeyB64] = useState(""); const [privateKeyB64, setPrivateKeyB64] = useState(""); const [codePending, setCodePending] = useState(true); @@ -43,8 +24,6 @@ export default function PairingMode() { const init = async () => { try { - const data = generateSecureData(6); - setDigits(convertDataToDecimalString(data).split("")); const keypair = await generateKeyPair(); setPublicKeyB64(await toB64(keypair.publicKey)); setPrivateKeyB64(await toB64(keypair.privateKey)); @@ -107,7 +86,7 @@ export default function PairingMode() { "urn:x-cast:pair-request", message.senderId, { - code: digits.join(""), + code: deviceCode.join(""), }, ); } catch (e) { @@ -117,9 +96,7 @@ export default function PairingMode() { const generateKeyPair = async () => { await _sodium.ready; - const keypair = _sodium.crypto_box_keypair(); - return keypair; }; @@ -133,7 +110,7 @@ export default function PairingMode() { let devicePayload = ""; try { const encDastData = await castGateway.getCastData( - `${digits.join("")}`, + `${deviceCode.join("")}`, ); if (!encDastData) return; devicePayload = encDastData; @@ -157,10 +134,8 @@ export default function PairingMode() { const advertisePublicKey = async (publicKeyB64: string) => { // hey client, we exist! try { - await castGateway.registerDevice( - `${digits.join("")}`, - publicKeyB64, - ); + const codeValue = await castGateway.registerDevice(publicKeyB64); + setDeviceCode(codeValue.split("")); setCodePending(false); } catch (e) { // schedule re-try after 5 seconds @@ -175,7 +150,7 @@ export default function PairingMode() { useEffect(() => { console.log("useEffect for pairing called"); - if (digits.length < 1 || !publicKeyB64 || !privateKeyB64) return; + if (deviceCode.length < 1 || !publicKeyB64 || !privateKeyB64) return; const interval = setInterval(async () => { console.log("polling for cast data"); @@ -192,7 +167,7 @@ export default function PairingMode() { return () => { clearInterval(interval); }; - }, [digits, publicKeyB64, privateKeyB64, codePending]); + }, [deviceCode, publicKeyB64, privateKeyB64, codePending]); useEffect(() => { if (!publicKeyB64) return; @@ -235,7 +210,7 @@ export default function PairingMode() { ) : ( <> - + )}
diff --git a/web/packages/shared/network/cast.ts b/web/packages/shared/network/cast.ts index b240eab32..a18767baa 100644 --- a/web/packages/shared/network/cast.ts +++ b/web/packages/shared/network/cast.ts @@ -58,11 +58,14 @@ class CastGateway { return resp.data.publicKey; } - public async registerDevice(code: string, publicKey: string) { - await HTTPService.post(getEndpoint() + "/cast/device-info/", { - deviceCode: `${code}`, - publicKey: publicKey, - }); + public async registerDevice(publicKey: string): Promise { + const resp = await HTTPService.post( + getEndpoint() + "/cast/device-info/", + { + publicKey: publicKey, + }, + ); + return resp.data.deviceCode; } public async publishCastPayload( From 8a859325129d5d4a66f488bb4f1eca151c327952 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 12:27:48 +0530 Subject: [PATCH 161/367] refactor --- web/apps/cast/src/pages/index.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index bbba9a1ad..b12bf1e76 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -10,7 +10,7 @@ import { storeCastData } from "services/cast/castService"; import { useCastReceiver } from "../utils/useCastReceiver"; export default function PairingMode() { - const [deviceCode, setDeviceCode] = useState([]); + const [deviceCode, setDeviceCode] = useState(""); const [publicKeyB64, setPublicKeyB64] = useState(""); const [privateKeyB64, setPrivateKeyB64] = useState(""); const [codePending, setCodePending] = useState(true); @@ -86,7 +86,7 @@ export default function PairingMode() { "urn:x-cast:pair-request", message.senderId, { - code: deviceCode.join(""), + code: deviceCode, }, ); } catch (e) { @@ -109,9 +109,7 @@ export default function PairingMode() { // then, we can decrypt this and store all the necessary info locally so we can play the collection slideshow. let devicePayload = ""; try { - const encDastData = await castGateway.getCastData( - `${deviceCode.join("")}`, - ); + const encDastData = await castGateway.getCastData(`${deviceCode}`); if (!encDastData) return; devicePayload = encDastData; } catch (e) { @@ -135,7 +133,7 @@ export default function PairingMode() { // hey client, we exist! try { const codeValue = await castGateway.registerDevice(publicKeyB64); - setDeviceCode(codeValue.split("")); + setDeviceCode(codeValue); setCodePending(false); } catch (e) { // schedule re-try after 5 seconds @@ -210,7 +208,7 @@ export default function PairingMode() { ) : ( <> - + )} From 99b13d18b0e84541bd682d739c3a5e26efc521ec Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 12:29:32 +0530 Subject: [PATCH 162/367] [server][cast] Generate alphaNumeric deviceCode --- server/ente/cast/entity.go | 3 +-- server/pkg/controller/cast/controller.go | 2 +- server/pkg/repo/cast/repo.go | 14 ++------------ 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/server/ente/cast/entity.go b/server/ente/cast/entity.go index deffa90b9..a54d109fc 100644 --- a/server/ente/cast/entity.go +++ b/server/ente/cast/entity.go @@ -9,8 +9,7 @@ type CastRequest struct { } type RegisterDeviceRequest struct { - DeviceCode *string `json:"deviceCode"` - PublicKey string `json:"publicKey" binding:"required"` + PublicKey string `json:"publicKey" binding:"required"` } type AuthContext struct { diff --git a/server/pkg/controller/cast/controller.go b/server/pkg/controller/cast/controller.go index 4432e149f..e2d41101a 100644 --- a/server/pkg/controller/cast/controller.go +++ b/server/pkg/controller/cast/controller.go @@ -28,7 +28,7 @@ func NewController(castRepo *castRepo.Repository, } func (c *Controller) RegisterDevice(ctx *gin.Context, request *cast.RegisterDeviceRequest) (string, error) { - return c.CastRepo.AddCode(ctx, request.DeviceCode, request.PublicKey, network.GetClientIP(ctx)) + return c.CastRepo.AddCode(ctx, request.PublicKey, network.GetClientIP(ctx)) } func (c *Controller) GetPublicKey(ctx *gin.Context, deviceCode string) (string, error) { diff --git a/server/pkg/repo/cast/repo.go b/server/pkg/repo/cast/repo.go index 89ebc4083..ee51f6f03 100644 --- a/server/pkg/repo/cast/repo.go +++ b/server/pkg/repo/cast/repo.go @@ -8,24 +8,14 @@ import ( "github.com/ente-io/stacktrace" "github.com/google/uuid" log "github.com/sirupsen/logrus" - "strings" ) type Repository struct { DB *sql.DB } -func (r *Repository) AddCode(ctx context.Context, code *string, pubKey string, ip string) (string, error) { - var codeValue string - var err error - if code == nil || *code == "" { - codeValue, err = random.GenerateSixDigitOtp() - if err != nil { - return "", stacktrace.Propagate(err, "") - } - } else { - codeValue = strings.TrimSpace(*code) - } +func (r *Repository) AddCode(ctx context.Context, pubKey string, ip string) (string, error) { + codeValue, err := random.GenerateAlphaNumString(6) _, err = r.DB.ExecContext(ctx, "INSERT INTO casting (code, public_key, id, ip) VALUES ($1, $2, $3, $4)", codeValue, pubKey, uuid.New(), ip) if err != nil { return "", err From ad5cfdc6db3d49eafc52fa66f3545f3b6ec9ebb7 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 12:31:26 +0530 Subject: [PATCH 163/367] [server][cast] convert deviceCode input to upperCase --- server/pkg/api/cast.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/pkg/api/cast.go b/server/pkg/api/cast.go index 62d5c9478..9012624d3 100644 --- a/server/pkg/api/cast.go +++ b/server/pkg/api/cast.go @@ -1,16 +1,16 @@ package api import ( - entity "github.com/ente-io/museum/ente/cast" - "github.com/ente-io/museum/pkg/controller/cast" - "net/http" - "strconv" - "github.com/ente-io/museum/ente" + entity "github.com/ente-io/museum/ente/cast" "github.com/ente-io/museum/pkg/controller" + "github.com/ente-io/museum/pkg/controller/cast" "github.com/ente-io/museum/pkg/utils/handler" "github.com/ente-io/stacktrace" "github.com/gin-gonic/gin" + "net/http" + "strconv" + "strings" ) // CastHandler exposes request handlers for publicly accessible collections @@ -126,7 +126,7 @@ func (h *CastHandler) GetDiff(c *gin.Context) { } func getDeviceCode(c *gin.Context) string { - return c.Param("deviceCode") + return strings.ToUpper(c.Param("deviceCode")) } func (h *CastHandler) getFileForType(c *gin.Context, objectType ente.ObjectType) { From 627eab472c4b0cd5f41d2a0d87988abebe894497 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 12:32:16 +0530 Subject: [PATCH 164/367] [server][cast] Only log ip mismatch instances --- server/pkg/controller/cast/controller.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/pkg/controller/cast/controller.go b/server/pkg/controller/cast/controller.go index e2d41101a..2bb002f81 100644 --- a/server/pkg/controller/cast/controller.go +++ b/server/pkg/controller/cast/controller.go @@ -2,7 +2,6 @@ package cast import ( "context" - "github.com/ente-io/museum/ente" "github.com/ente-io/museum/ente/cast" "github.com/ente-io/museum/pkg/controller/access" castRepo "github.com/ente-io/museum/pkg/repo/cast" @@ -42,7 +41,6 @@ func (c *Controller) GetPublicKey(ctx *gin.Context, deviceCode string) (string, "ip": ip, "clientIP": network.GetClientIP(ctx), }).Warn("GetPublicKey: IP mismatch") - return "", &ente.ErrCastIPMismatch } return pubKey, nil } From a62edad446ddd35c986beb0a9dba22a64f6eb96c Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 12:36:02 +0530 Subject: [PATCH 165/367] [server][cast] Fix err handling --- server/pkg/repo/cast/repo.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/pkg/repo/cast/repo.go b/server/pkg/repo/cast/repo.go index ee51f6f03..2f4446c9d 100644 --- a/server/pkg/repo/cast/repo.go +++ b/server/pkg/repo/cast/repo.go @@ -16,6 +16,9 @@ type Repository struct { func (r *Repository) AddCode(ctx context.Context, pubKey string, ip string) (string, error) { codeValue, err := random.GenerateAlphaNumString(6) + if err != nil { + return "", err + } _, err = r.DB.ExecContext(ctx, "INSERT INTO casting (code, public_key, id, ip) VALUES ($1, $2, $3, $4)", codeValue, pubKey, uuid.New(), ip) if err != nil { return "", err From b24d80a2675d478004faecb2237cf35c08af707c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 12:59:59 +0530 Subject: [PATCH 166/367] Move to new notarization mechanism Refs: - https://www.electron.build/configuration/mac.html#NotarizeLegacyOptions - https://github.com/electron-userland/electron-builder/blob/master/packages/app-builder-lib/src/macPackager.ts - https://github.com/samuelmeuli/action-electron-builder/issues/101 --- desktop/.github/workflows/desktop-release.yml | 14 ++++---------- desktop/electron-builder.yml | 1 + 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/desktop/.github/workflows/desktop-release.yml b/desktop/.github/workflows/desktop-release.yml index 7013d3e57..fc1464cd9 100644 --- a/desktop/.github/workflows/desktop-release.yml +++ b/desktop/.github/workflows/desktop-release.yml @@ -55,13 +55,6 @@ jobs: - name: Install dependencies run: yarn install - - name: Prepare for app notarization - if: startsWith(matrix.os, 'macos') - # Import Apple API key for app notarization on macOS - run: | - mkdir -p ~/private_keys/ - echo '${{ secrets.API_KEY }}' > ~/private_keys/AuthKey_${{ secrets.API_KEY_ID }}.p8 - - name: Install libarchive-tools for pacman build if: startsWith(matrix.os, 'ubuntu') # See: @@ -84,7 +77,8 @@ jobs: mac_certs: ${{ secrets.MAC_CERTS }} mac_certs_password: ${{ secrets.MAC_CERTS_PASSWORD }} env: - # macOS notarization API key details - API_KEY_ID: ${{ secrets.API_KEY_ID }} - API_KEY_ISSUER_ID: ${{ secrets.API_KEY_ISSUER_ID }} + # macOS notarization credentials key details + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} USE_HARD_LINKS: false diff --git a/desktop/electron-builder.yml b/desktop/electron-builder.yml index 298b1c5f3..f62033fb9 100644 --- a/desktop/electron-builder.yml +++ b/desktop/electron-builder.yml @@ -29,4 +29,5 @@ mac: arch: [universal] category: public.app-category.photography hardenedRuntime: true + notarize: true afterSign: electron-builder-notarize From ec93a0267463cf06015d067c3be5127784c06578 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Fri, 3 May 2024 13:05:02 +0530 Subject: [PATCH 167/367] [mob][photos] fix: BG process not getting killed when app is brought to foreground --- mobile/lib/main.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 0bdc097ff..a981b5e4a 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import "dart:isolate"; import "package:adaptive_theme/adaptive_theme.dart"; import 'package:background_fetch/background_fetch.dart'; @@ -276,6 +277,7 @@ Future _scheduleHeartBeat( isBackground ? kLastBGTaskHeartBeatTime : kLastFGTaskHeartBeatTime, DateTime.now().microsecondsSinceEpoch, ); + _logger.info("Heartbeat scheduled for ${isBackground ? 'BG' : 'FG'} task"); Future.delayed(kHeartBeatFrequency, () async { // ignore: unawaited_futures _scheduleHeartBeat(prefs, isBackground); @@ -303,11 +305,16 @@ Future _scheduleFGSync(String caller) async { } void _scheduleBGTaskKill(String taskId) async { + _logger.info("debugBGTask : Checking if BG task should be killed"); if (await _isRunningInForeground()) { _logger.info("Found app in FG, committing seppuku. $taskId"); await _killBGTask(taskId); return; } + _logger.info( + "is running in foreground: ${await _isRunningInForeground()} *************", + ); + _logger.info("debugBGTask : isNotRunningInForeground *************"); Future.delayed(kHeartBeatFrequency, () async { _scheduleBGTaskKill(taskId); }); @@ -325,15 +332,22 @@ Future _isRunningInForeground() async { } Future _killBGTask([String? taskId]) async { + _logger.info("KillingBGTask taskId: $taskId ***************"); await UploadLocksDB.instance.releaseLocksAcquiredByOwnerBefore( ProcessType.background.toString(), DateTime.now().microsecondsSinceEpoch, ); final prefs = await SharedPreferences.getInstance(); + await prefs.remove(kLastBGTaskHeartBeatTime); if (taskId != null) { BackgroundFetch.finish(taskId); } + + ///Band aid for background process not getting killed. Should migrate to using + ///workmanager instead of background_fetch. + Isolate.current.kill(); + _logger.info('Kill BG task done *************'); } Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { From 075d8d9769c5710344b35ee777f732d3070bb0d3 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Fri, 3 May 2024 13:07:29 +0530 Subject: [PATCH 168/367] [mob][photos] bump up version --- mobile/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 2adf29321..a5f5a07ff 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.88+608 +version: 0.8.89+609 publish_to: none environment: From 3fafc5fd998142c075d2f11b623a2e12c97716a5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 13:17:58 +0530 Subject: [PATCH 169/367] Use an Electron Builder version that supports notarytool We need the following patch (from the release notes of v25.0.0-alpha.6) https://github.com/electron-userland/electron-builder/releases > feat: Make notarization with Apple ID more usable by > https://github.com/electron-userland/electron-builder/pull/8159 --- desktop/package.json | 2 +- desktop/yarn.lock | 676 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 581 insertions(+), 97 deletions(-) diff --git a/desktop/package.json b/desktop/package.json index db5041ead..462857a8b 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -45,7 +45,7 @@ "@typescript-eslint/parser": "^7", "concurrently": "^8", "electron": "^30", - "electron-builder": "^24", + "electron-builder": "25.0.0-alpha.6", "electron-builder-notarize": "^1.5", "eslint": "^8", "prettier": "^3", diff --git a/desktop/yarn.lock b/desktop/yarn.lock index d4338312b..833b623a7 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -55,10 +55,10 @@ ajv "^6.12.0" ajv-keywords "^3.4.1" -"@electron/asar@^3.2.1": - version "3.2.9" - resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.2.9.tgz#7b3a1fd677b485629f334dd80ced8c85353ba7e7" - integrity sha512-Vu2P3X2gcZ3MY9W7yH72X9+AMXwUQZEJBrsPIbX0JsdllLtoh62/Q8Wg370/DawIEVKOyfD6KtTLo645ezqxUA== +"@electron/asar@^3.2.7": + version "3.2.10" + resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.2.10.tgz#615cf346b734b23cafa4e0603551010bd0e50aa8" + integrity sha512-mvBSwIBUeiRscrCeJE1LwctAriBj65eUDm0Pc11iE5gRwzkmsdbS7FnZ1XUWjpSeQWL1L5g12Fc/SchPM9DUOw== dependencies: commander "^5.0.0" glob "^7.1.6" @@ -79,10 +79,10 @@ optionalDependencies: global-agent "^3.0.0" -"@electron/notarize@2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@electron/notarize/-/notarize-2.2.1.tgz#d0aa6bc43cba830c41bfd840b85dbe0e273f59fe" - integrity sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg== +"@electron/notarize@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@electron/notarize/-/notarize-2.3.0.tgz#9659cf6c92563dd69411afce229f52f9f7196227" + integrity sha512-EiTBU0BwE7HZZjAG1fFWQaiQpCuPrVGn7jPss1kUjD6eTTdXXd29RiZqEqkgN7xqt/Pgn4g3I7Saqovanrfj3w== dependencies: debug "^4.1.1" fs-extra "^9.0.1" @@ -100,18 +100,38 @@ minimist "^1.2.6" plist "^3.0.5" -"@electron/universal@1.5.1": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.5.1.tgz#f338bc5bcefef88573cf0ab1d5920fac10d06ee5" - integrity sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw== +"@electron/rebuild@3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@electron/rebuild/-/rebuild-3.6.0.tgz#60211375a5f8541a71eb07dd2f97354ad0b2b96f" + integrity sha512-zF4x3QupRU3uNGaP5X1wjpmcjfw1H87kyqZ00Tc3HvriV+4gmOGuvQjGNkrJuXdsApssdNyVwLsy+TaeTGGcVw== dependencies: - "@electron/asar" "^3.2.1" - "@malept/cross-spawn-promise" "^1.1.0" + "@malept/cross-spawn-promise" "^2.0.0" + chalk "^4.0.0" + debug "^4.1.1" + detect-libc "^2.0.1" + fs-extra "^10.0.0" + got "^11.7.0" + node-abi "^3.45.0" + node-api-version "^0.2.0" + node-gyp "^9.0.0" + ora "^5.1.0" + read-binary-file-arch "^1.0.6" + semver "^7.3.5" + tar "^6.0.5" + yargs "^17.0.1" + +"@electron/universal@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-2.0.1.tgz#7b070ab355e02957388f3dbd68e2c3cd08c448ae" + integrity sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA== + dependencies: + "@electron/asar" "^3.2.7" + "@malept/cross-spawn-promise" "^2.0.0" debug "^4.3.1" - dir-compare "^3.0.0" - fs-extra "^9.0.1" - minimatch "^3.0.4" - plist "^3.0.4" + dir-compare "^4.2.0" + fs-extra "^11.1.1" + minimatch "^9.0.3" + plist "^3.1.0" "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" @@ -145,6 +165,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@gar/promisify@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -171,10 +196,10 @@ dependencies: minipass "^7.0.4" -"@malept/cross-spawn-promise@^1.1.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz#504af200af6b98e198bce768bc1730c6936ae01d" - integrity sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ== +"@malept/cross-spawn-promise@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz#d0772de1aa680a0bfb9ba2f32b4c828c7857cb9d" + integrity sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg== dependencies: cross-spawn "^7.0.1" @@ -209,6 +234,22 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@npmcli/fs@^2.1.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" + integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== + dependencies: + "@gar/promisify" "^1.1.3" + semver "^7.3.5" + +"@npmcli/move-file@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" + integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -442,6 +483,11 @@ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99" integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw== +abbrev@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -452,13 +498,28 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== -agent-base@6: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" +agentkeepalive@^4.2.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" + integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== + dependencies: + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -528,25 +589,26 @@ app-builder-bin@4.0.0: resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-4.0.0.tgz#1df8e654bd1395e4a319d82545c98667d7eed2f0" integrity sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA== -app-builder-lib@24.13.3: - version "24.13.3" - resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-24.13.3.tgz#36e47b65fecb8780bb73bff0fee4e0480c28274b" - integrity sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig== +app-builder-lib@25.0.0-alpha.6: + version "25.0.0-alpha.6" + resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-25.0.0-alpha.6.tgz#3edb49843b249a1dd52b32a80f9787677bc5a32b" + integrity sha512-kXveR7MFTJXBwb2xB2geKWeWP+YGcJ3IzWRgTEV96zwyo4IxzE5xRXcndSQQglmlzw/VoM5Mx322E9ErYbMCVg== dependencies: "@develar/schema-utils" "~2.6.5" - "@electron/notarize" "2.2.1" + "@electron/notarize" "2.3.0" "@electron/osx-sign" "1.0.5" - "@electron/universal" "1.5.1" + "@electron/rebuild" "3.6.0" + "@electron/universal" "2.0.1" "@malept/flatpak-bundler" "^0.4.0" "@types/fs-extra" "9.0.13" async-exit-hook "^2.0.1" bluebird-lst "^1.0.9" - builder-util "24.13.1" - builder-util-runtime "9.2.4" + builder-util "25.0.0-alpha.6" + builder-util-runtime "9.2.5-alpha.2" chromium-pickle-js "^0.2.0" debug "^4.3.4" ejs "^3.1.8" - electron-publish "24.13.1" + electron-publish "25.0.0-alpha.6" form-data "^4.0.0" fs-extra "^10.1.0" hosted-git-info "^4.1.0" @@ -566,6 +628,19 @@ applescript@^1.0.0: resolved "https://registry.yarnpkg.com/applescript/-/applescript-1.0.0.tgz#bb87af568cad034a4e48c4bdaf6067a3a2701317" integrity sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ== +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -644,6 +719,15 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + bluebird-lst@^1.0.9: version "1.0.9" resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.9.tgz#a64a0e4365658b9ab5fe875eb9dfb694189bb41c" @@ -688,17 +772,12 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== -buffer-equal@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.1.tgz#2f7651be5b1b3f057fcd6e7ee16cf34767077d90" - integrity sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg== - buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^5.1.0: +buffer@^5.1.0, buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -714,24 +793,24 @@ builder-util-runtime@9.2.3: debug "^4.3.4" sax "^1.2.4" -builder-util-runtime@9.2.4: - version "9.2.4" - resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz#13cd1763da621e53458739a1e63f7fcba673c42a" - integrity sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA== +builder-util-runtime@9.2.5-alpha.2: + version "9.2.5-alpha.2" + resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.5-alpha.2.tgz#b0a1737996717d7ae0b71e5efdf0bfbd1dd2c21e" + integrity sha512-/Ln2ddejGj2HNMJ+X66mKHRcOvmRzUO/dSi8t4hSV64J7IA+DE+mqDb+zogIE2gin7p7YwcGiOkKny4nwPPPXg== dependencies: debug "^4.3.4" sax "^1.2.4" -builder-util@24.13.1: - version "24.13.1" - resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-24.13.1.tgz#4a4c4f9466b016b85c6990a0ea15aa14edec6816" - integrity sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA== +builder-util@25.0.0-alpha.6: + version "25.0.0-alpha.6" + resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-25.0.0-alpha.6.tgz#4ac5e13d9e6c750987efc9cd9c1eace58622a30b" + integrity sha512-ghT1XcP6JI926AArlBcPHRRKYCsVWbT/ywnXPwW5X1ani2jmnddPpnwm92xRvCPWGBmeXd2diF69FV5rBJxhRQ== dependencies: "7zip-bin" "~5.2.0" "@types/debug" "^4.1.6" app-builder-bin "4.0.0" bluebird-lst "^1.0.9" - builder-util-runtime "9.2.4" + builder-util-runtime "9.2.5-alpha.2" chalk "^4.1.2" cross-spawn "^7.0.3" debug "^4.3.4" @@ -744,6 +823,30 @@ builder-util@24.13.1: stat-mode "^1.0.0" temp-file "^3.4.0" +cacache@^16.1.0: + version "16.1.3" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" + integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== + dependencies: + "@npmcli/fs" "^2.1.0" + "@npmcli/move-file" "^2.0.0" + chownr "^2.0.0" + fs-minipass "^2.1.0" + glob "^8.0.1" + infer-owner "^1.0.4" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^9.0.0" + tar "^6.1.11" + unique-filename "^2.0.0" + cacheable-lookup@^5.0.3: version "5.0.4" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" @@ -781,7 +884,7 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -824,6 +927,23 @@ ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.5.0: + version "2.9.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + cli-truncate@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" @@ -848,6 +968,11 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -872,6 +997,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -948,6 +1078,11 @@ config-file-ts@^0.2.4: glob "^10.3.10" typescript "^5.3.3" +console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -983,7 +1118,7 @@ debounce-fn@^4.0.0: dependencies: mimic-fn "^3.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -1002,6 +1137,13 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + defer-to-connect@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" @@ -1030,11 +1172,21 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + detect-indent@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-7.0.1.tgz#cbb060a12842b9c4d333f1cac4aa4da1bb66bc25" integrity sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g== +detect-libc@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + detect-newline@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-4.0.1.tgz#fcefdb5713e1fb8cb2839b8b6ee22e6716ab8f23" @@ -1045,13 +1197,13 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -dir-compare@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/dir-compare/-/dir-compare-3.3.0.tgz#2c749f973b5c4b5d087f11edaae730db31788416" - integrity sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg== +dir-compare@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dir-compare/-/dir-compare-4.2.0.tgz#d1d4999c14fbf55281071fdae4293b3b9ce86f19" + integrity sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ== dependencies: - buffer-equal "^1.0.0" - minimatch "^3.0.4" + minimatch "^3.0.5" + p-limit "^3.1.0 " dir-glob@^3.0.1: version "3.0.1" @@ -1060,14 +1212,14 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dmg-builder@24.13.3: - version "24.13.3" - resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-24.13.3.tgz#95d5b99c587c592f90d168a616d7ec55907c7e55" - integrity sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ== +dmg-builder@25.0.0-alpha.6: + version "25.0.0-alpha.6" + resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-25.0.0-alpha.6.tgz#1a13008de0543c3080595534ab294cde2a5e57e8" + integrity sha512-GStVExwsuumGN6rPGJksA5bLN5n5QEQd5iQrGKyBSxuwR1+LWidFkM+anroXnANIyTwbppk2S7+808vHjT/Eyw== dependencies: - app-builder-lib "24.13.3" - builder-util "24.13.1" - builder-util-runtime "9.2.4" + app-builder-lib "25.0.0-alpha.6" + builder-util "25.0.0-alpha.6" + builder-util-runtime "9.2.5-alpha.2" fs-extra "^10.1.0" iconv-lite "^0.6.2" js-yaml "^4.1.0" @@ -1134,16 +1286,16 @@ electron-builder-notarize@^1.5: js-yaml "^3.14.0" read-pkg-up "^7.0.0" -electron-builder@^24: - version "24.13.3" - resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-24.13.3.tgz#c506dfebd36d9a50a83ee8aa32d803d83dbe4616" - integrity sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg== +electron-builder@25.0.0-alpha.6: + version "25.0.0-alpha.6" + resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-25.0.0-alpha.6.tgz#a72f96f7029539ac28f92ce5c83f872ba3b6e7c1" + integrity sha512-qXzzdID2W9hhx3TXddwXv1C5HsqjF6bKnftUtywAB/gtDwu+neifPZvnXDNHI4ZamRrZpJJH59esfkqkc2KNSQ== dependencies: - app-builder-lib "24.13.3" - builder-util "24.13.1" - builder-util-runtime "9.2.4" + app-builder-lib "25.0.0-alpha.6" + builder-util "25.0.0-alpha.6" + builder-util-runtime "9.2.5-alpha.2" chalk "^4.1.2" - dmg-builder "24.13.3" + dmg-builder "25.0.0-alpha.6" fs-extra "^10.1.0" is-ci "^3.0.0" lazy-val "^1.0.5" @@ -1164,14 +1316,14 @@ electron-notarize@^1.1.1: debug "^4.1.1" fs-extra "^9.0.1" -electron-publish@24.13.1: - version "24.13.1" - resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-24.13.1.tgz#57289b2f7af18737dc2ad134668cdd4a1b574a0c" - integrity sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A== +electron-publish@25.0.0-alpha.6: + version "25.0.0-alpha.6" + resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-25.0.0-alpha.6.tgz#8af3cb6e2435c00b8c71de43c330483808df5924" + integrity sha512-Hin+6j+jiXBc5g6Wlv9JB5Xu7MADBHxZAndt4WE7luCw7b3+OJdQeDvD/uYiCLpiG8cc2NLxu4MyBSVu86MrJA== dependencies: "@types/fs-extra" "^9.0.11" - builder-util "24.13.1" - builder-util-runtime "9.2.4" + builder-util "25.0.0-alpha.6" + builder-util-runtime "9.2.5-alpha.2" chalk "^4.1.2" fs-extra "^10.1.0" lazy-val "^1.0.5" @@ -1213,6 +1365,13 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +encoding@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -1364,6 +1523,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +exponential-backoff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" + integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== + extract-zip@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" @@ -1514,6 +1678,15 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^11.1.1: + version "11.2.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -1533,7 +1706,7 @@ fs-extra@^9.0.0, fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^2.0.0" -fs-minipass@^2.0.0: +fs-minipass@^2.0.0, fs-minipass@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== @@ -1555,6 +1728,20 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -1613,7 +1800,7 @@ glob@^10.3.10, glob@^10.3.7: minipass "^7.0.4" path-scurry "^1.10.2" -glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: +glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -1625,6 +1812,17 @@ glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + global-agent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" @@ -1682,7 +1880,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -got@^11.8.5: +got@^11.7.0, got@^11.8.5: version "11.8.6" resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== @@ -1699,7 +1897,7 @@ got@^11.8.5: p-cancelable "^2.0.0" responselike "^2.0.0" -graceful-fs@^4.1.6, graceful-fs@^4.2.0: +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -1736,6 +1934,11 @@ has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + hasown@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" @@ -1760,7 +1963,7 @@ html-entities@^2.5: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== -http-cache-semantics@^4.0.0: +http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== @@ -1797,6 +2000,13 @@ https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: agent-base "6" debug "4" +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + iconv-corefoundation@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz#31065e6ab2c9272154c8b0821151e2c88f1b002a" @@ -1835,6 +2045,16 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -1843,7 +2063,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3: +inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1853,6 +2073,14 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -1896,6 +2124,16 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -1916,6 +2154,11 @@ is-plain-obj@^4.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + isbinaryfile@^4.0.8: version "4.0.10" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" @@ -1975,6 +2218,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -2098,6 +2346,14 @@ lodash@^4.17.15, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + lowercase-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" @@ -2115,6 +2371,33 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-cache@^7.7.1: + version "7.18.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + +make-fetch-happen@^10.0.3: + version "10.2.1" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" + integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== + dependencies: + agentkeepalive "^4.2.1" + cacache "^16.1.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-fetch "^2.0.3" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + promise-retry "^2.0.1" + socks-proxy-agent "^7.0.0" + ssri "^9.0.0" + matcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" @@ -2186,7 +2469,7 @@ minimatch@^5.0.1, minimatch@^5.1.1: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.1, minimatch@^9.0.4: +minimatch@^9.0.1, minimatch@^9.0.3, minimatch@^9.0.4: version "9.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== @@ -2198,7 +2481,46 @@ minimist@^1.2.3, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -minipass@^3.0.0: +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" + integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== + dependencies: + minipass "^3.1.6" + minipass-sized "^1.0.3" + minizlib "^2.1.2" + optionalDependencies: + encoding "^0.1.13" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: version "3.3.6" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== @@ -2215,7 +2537,7 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== -minizlib@^2.1.1: +minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -2238,7 +2560,7 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.6" -mkdirp@^1.0.3: +mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -2253,26 +2575,74 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +negotiator@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + next-electron-server@^1: version "1.0.0" resolved "https://registry.yarnpkg.com/next-electron-server/-/next-electron-server-1.0.0.tgz#03e133ed64a5ef671b6c6409f908c4901b1828cb" integrity sha512-fTUaHwT0Jry2fbdUSIkAiIqgDAInI5BJFF4/j90/okvZCYlyx6yxpXB30KpzmOG6TN/ESwyvsFJVvS2WHT8PAA== +node-abi@^3.45.0: + version "3.62.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.62.0.tgz#017958ed120f89a3a14a7253da810f5d724e3f36" + integrity sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g== + dependencies: + semver "^7.3.5" + node-addon-api@^1.6.3: version "1.7.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== +node-api-version@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/node-api-version/-/node-api-version-0.2.0.tgz#5177441da2b1046a4d4547ab9e0972eed7b1ac1d" + integrity sha512-fthTTsi8CxaBXMaBAD7ST2uylwvsnYxh2PfaScwpMhos6KlSFajXQPcM4ogNE1q2s3Lbz9GCGqeIHC+C6OZnKg== + dependencies: + semver "^7.3.5" + +node-gyp@^9.0.0: + version "9.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.1.tgz#8a1023e0d6766ecb52764cc3a734b36ff275e185" + integrity sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ== + dependencies: + env-paths "^2.2.0" + exponential-backoff "^3.1.1" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^10.0.3" + nopt "^6.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + node-stream-zip@^1.15: version "1.15.0" resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea" integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw== +nopt@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" + integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g== + dependencies: + abbrev "^1.0.0" + normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -2293,6 +2663,16 @@ normalize-url@^6.0.1: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== +npmlog@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -2305,7 +2685,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.2: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -2337,6 +2717,21 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +ora@^5.1.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + p-cancelable@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" @@ -2349,7 +2744,7 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2: +p-limit@^3.0.2, "p-limit@^3.1.0 ": version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -2377,6 +2772,13 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -2464,7 +2866,7 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -plist@^3.0.4, plist@^3.0.5: +plist@^3.0.4, plist@^3.0.5, plist@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" integrity sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ== @@ -2501,6 +2903,11 @@ progress@^2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + promise-retry@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" @@ -2532,6 +2939,13 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== +read-binary-file-arch@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz#959c4637daa932280a9b911b1a6766a7e44288fc" + integrity sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg== + dependencies: + debug "^4.3.4" + read-config-file@6.3.2: version "6.3.2" resolved "https://registry.yarnpkg.com/read-config-file/-/read-config-file-6.3.2.tgz#556891aa6ffabced916ed57457cb192e61880411" @@ -2563,7 +2977,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@^3.0.2: +readable-stream@^3.0.2, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -2627,6 +3041,14 @@ responselike@^2.0.0: dependencies: lowercase-keys "^2.0.0" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -2728,6 +3150,11 @@ serialize-error@^7.0.1: dependencies: type-fest "^0.13.1" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -2762,6 +3189,11 @@ shx@^0.3: minimist "^1.2.3" shelljs "^0.8.5" +signal-exit@^3.0.2, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + signal-exit@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" @@ -2793,11 +3225,28 @@ slice-ansi@^3.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -smart-buffer@^4.0.2: +smart-buffer@^4.0.2, smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +socks-proxy-agent@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" + integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" + integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + sort-object-keys@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/sort-object-keys/-/sort-object-keys-1.1.3.tgz#bff833fe85cab147b34742e45863453c1e190b45" @@ -2861,7 +3310,7 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== -sprintf-js@^1.1.2: +sprintf-js@^1.1.2, sprintf-js@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== @@ -2871,12 +3320,19 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +ssri@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" + integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== + dependencies: + minipass "^3.1.1" + stat-mode@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-1.0.0.tgz#68b55cb61ea639ff57136f36b216a291800d1465" integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg== -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2945,7 +3401,7 @@ synckit@0.9.0: "@pkgr/core" "^0.1.0" tslib "^2.6.2" -tar@^6.1.12: +tar@^6.0.5, tar@^6.1.11, tar@^6.1.12, tar@^6.1.2: version "6.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== @@ -3075,6 +3531,20 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +unique-filename@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" + integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== + dependencies: + unique-slug "^3.0.0" + +unique-slug@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" + integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== + dependencies: + imurmurhash "^0.1.4" + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -3124,13 +3594,27 @@ verror@^1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -which@^2.0.1: +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + +which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" +wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + winreg@1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b" @@ -3180,7 +3664,7 @@ yargs-parser@^21.1.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.6.2, yargs@^17.7.2: +yargs@^17.0.1, yargs@^17.6.2, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From b374dc0dd00f1e55e9aebb3dffe4697ddb278ef4 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Fri, 3 May 2024 13:56:59 +0530 Subject: [PATCH 170/367] fix: add digits to setup page and create stream type --- auth/lib/l10n/arb/app_en.arb | 1 + auth/lib/models/code.dart | 26 ++++++++----- .../view/setup_enter_secret_key_page.dart | 39 +++++++++++++++++++ auth/lib/ui/code_widget.dart | 10 ++--- .../data/import/bitwarden_import.dart | 2 + 5 files changed, 63 insertions(+), 15 deletions(-) diff --git a/auth/lib/l10n/arb/app_en.arb b/auth/lib/l10n/arb/app_en.arb index e16a39c79..0c42d78f7 100644 --- a/auth/lib/l10n/arb/app_en.arb +++ b/auth/lib/l10n/arb/app_en.arb @@ -20,6 +20,7 @@ "codeIssuerHint": "Issuer", "codeSecretKeyHint": "Secret Key", "codeAccountHint": "Account (you@domain.com)", + "digitsAccountHint": "Digits (default=6)", "accountKeyType": "Type of key", "sessionExpired": "Session expired", "@sessionExpired": { diff --git a/auth/lib/models/code.dart b/auth/lib/models/code.dart index 09591a62f..524e896ad 100644 --- a/auth/lib/models/code.dart +++ b/auth/lib/models/code.dart @@ -58,18 +58,20 @@ class Code { updatedAlgo, updatedType, updatedCounter, - "otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}", + "otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}" + "${updatedType == Type.steam ? "" : "&digits=$updatedDigits"}&issuer=$updateIssuer" + "&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}", generatedID: generatedID, ); } static Code fromAccountAndSecret( + Type type, String account, String issuer, String secret, + int digits, ) { - final digits = - issuer.toLowerCase() == "steam" ? steamDigits : defaultDigits; return Code( account, issuer, @@ -77,23 +79,21 @@ class Code { defaultPeriod, secret, Algorithm.sha1, - Type.totp, + type, 0, - "otpauth://totp/$issuer:$account?algorithm=SHA1&digits=6&issuer=$issuer&period=30&secret=$secret", + "otpauth://${type.name}/$issuer:$account?algorithm=SHA1${type == Type.steam ? "" : "&digits=$digits"}&issuer=$issuer&period=30&secret=$secret", ); } static Code fromRawData(String rawData) { Uri uri = Uri.parse(rawData); final issuer = _getIssuer(uri); - final digits = - issuer.toLowerCase() == "steam" ? steamDigits : _getDigits(uri); try { return Code( _getAccount(uri), issuer, - digits, + _getDigits(uri, issuer), _getPeriod(uri), getSanitizedSecret(uri.queryParameters['secret']!), _getAlgorithm(uri), @@ -147,8 +147,9 @@ class Code { } } - static int _getDigits(Uri uri) { + static int _getDigits(Uri uri, String issuer) { try { + if (issuer.toLowerCase() == "steam") return steamDigits; return int.parse(uri.queryParameters['digits']!); } catch (e) { return defaultDigits; @@ -191,8 +192,10 @@ class Code { } static Type _getType(Uri uri) { - if (uri.host == "totp" || uri.host == "steam") { + if (uri.host == "totp") { return Type.totp; + } else if (uri.host == "steam") { + return Type.steam; } else if (uri.host == "hotp") { return Type.hotp; } @@ -230,6 +233,9 @@ class Code { enum Type { totp, hotp, + steam; + + bool get isTOTPCompatible => this == totp || this == steam; } enum Algorithm { diff --git a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart index 3937142d6..64837f8f5 100644 --- a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart +++ b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart @@ -20,6 +20,7 @@ class _SetupEnterSecretKeyPageState extends State { late TextEditingController _issuerController; late TextEditingController _accountController; late TextEditingController _secretController; + late TextEditingController _digitsController; late bool _secretKeyObscured; @override @@ -34,6 +35,9 @@ class _SetupEnterSecretKeyPageState extends State { _secretController = TextEditingController( text: widget.code?.secret, ); + _digitsController = TextEditingController( + text: widget.code?.digits.toString(), + ); _secretKeyObscured = widget.code != null; super.initState(); } @@ -61,6 +65,8 @@ class _SetupEnterSecretKeyPageState extends State { }, decoration: InputDecoration( hintText: l10n.codeIssuerHint, + floatingLabelBehavior: FloatingLabelBehavior.auto, + labelText: l10n.codeIssuerHint, ), controller: _issuerController, autofocus: true, @@ -78,6 +84,8 @@ class _SetupEnterSecretKeyPageState extends State { }, decoration: InputDecoration( hintText: l10n.codeSecretKeyHint, + floatingLabelBehavior: FloatingLabelBehavior.auto, + labelText: l10n.codeSecretKeyHint, suffixIcon: IconButton( onPressed: () { setState(() { @@ -105,9 +113,33 @@ class _SetupEnterSecretKeyPageState extends State { }, decoration: InputDecoration( hintText: l10n.codeAccountHint, + floatingLabelBehavior: FloatingLabelBehavior.auto, + labelText: l10n.codeAccountHint, ), controller: _accountController, ), + const SizedBox( + height: 20, + ), + TextFormField( + validator: (value) { + if (value == null || value.isEmpty) { + return "Please enter some number"; + } + if (int.tryParse(value) == null) { + return "Please enter a valid number"; + } + return null; + }, + readOnly: widget.code?.type == Type.steam, + keyboardType: TextInputType.number, + decoration: InputDecoration( + hintText: l10n.digitsAccountHint, + labelText: l10n.digitsAccountHint, + floatingLabelBehavior: FloatingLabelBehavior.auto, + ), + controller: _digitsController, + ), const SizedBox( height: 40, ), @@ -152,6 +184,10 @@ class _SetupEnterSecretKeyPageState extends State { final account = _accountController.text.trim(); final issuer = _issuerController.text.trim(); final secret = _secretController.text.trim().replaceAll(' ', ''); + final digits = int.tryParse(_digitsController.text.trim()) ?? + (widget.code?.type == Type.steam + ? Code.steamDigits + : Code.defaultDigits); if (widget.code != null && widget.code!.secret != secret) { ButtonResult? result = await showChoiceActionSheet( context, @@ -168,14 +204,17 @@ class _SetupEnterSecretKeyPageState extends State { } final Code newCode = widget.code == null ? Code.fromAccountAndSecret( + Type.totp, account, issuer, secret, + digits, ) : widget.code!.copyWith( account: account, issuer: issuer, secret: secret, + digits: digits, ); // Verify the validity of the code getOTP(newCode); diff --git a/auth/lib/ui/code_widget.dart b/auth/lib/ui/code_widget.dart index f97e865ec..d989edf18 100644 --- a/auth/lib/ui/code_widget.dart +++ b/auth/lib/ui/code_widget.dart @@ -53,7 +53,7 @@ class _CodeWidgetState extends State { String newCode = _getCurrentOTP(); if (newCode != _currentCode.value) { _currentCode.value = newCode; - if (widget.code.type == Type.totp) { + if (widget.code.type.isTOTPCompatible) { _nextCode.value = _getNextTotp(); } } @@ -78,7 +78,7 @@ class _CodeWidgetState extends State { _shouldShowLargeIcon = PreferenceService.instance.shouldShowLargeIcons(); if (!_isInitialized) { _currentCode.value = _getCurrentOTP(); - if (widget.code.type == Type.totp) { + if (widget.code.type.isTOTPCompatible) { _nextCode.value = _getNextTotp(); } _isInitialized = true; @@ -213,7 +213,7 @@ class _CodeWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - if (widget.code.type == Type.totp) + if (widget.code.type.isTOTPCompatible) CodeTimerProgress( period: widget.code.period, ), @@ -263,7 +263,7 @@ class _CodeWidgetState extends State { }, ), ), - widget.code.type == Type.totp + widget.code.type.isTOTPCompatible ? GestureDetector( onTap: () { _copyNextToClipboard(); @@ -481,7 +481,7 @@ class _CodeWidgetState extends State { String _getNextTotp() { try { - assert(widget.code.type == Type.totp); + assert(widget.code.type.isTOTPCompatible); return getNextTotp(widget.code); } catch (e) { return context.l10n.error; diff --git a/auth/lib/ui/settings/data/import/bitwarden_import.dart b/auth/lib/ui/settings/data/import/bitwarden_import.dart index 90e527dde..7a562d82b 100644 --- a/auth/lib/ui/settings/data/import/bitwarden_import.dart +++ b/auth/lib/ui/settings/data/import/bitwarden_import.dart @@ -92,9 +92,11 @@ Future _processBitwardenExportFile( var account = item['login']['username']; code = Code.fromAccountAndSecret( + Type.totp, account, issuer, totp, + Code.defaultDigits, ); } From 8bf9a2544c553cf6f33549af207a87934c38718e Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Fri, 3 May 2024 13:57:09 +0530 Subject: [PATCH 171/367] chore: bump version --- auth/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index 2ef543aa6..0487eb128 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -1,6 +1,6 @@ name: ente_auth description: ente two-factor authenticator -version: 2.0.55+255 +version: 2.0.57+257 publish_to: none environment: From da45ea317386e39d6f5cba32d88dcd9c0951117b Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 14:09:36 +0530 Subject: [PATCH 172/367] [auth] Improve _getDigits --- auth/lib/models/code.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/auth/lib/models/code.dart b/auth/lib/models/code.dart index 524e896ad..1be4ef37e 100644 --- a/auth/lib/models/code.dart +++ b/auth/lib/models/code.dart @@ -81,7 +81,7 @@ class Code { Algorithm.sha1, type, 0, - "otpauth://${type.name}/$issuer:$account?algorithm=SHA1${type == Type.steam ? "" : "&digits=$digits"}&issuer=$issuer&period=30&secret=$secret", + "otpauth://${type.name}/$issuer:$account?algorithm=SHA1&digits=$digits&issuer=$issuer&period=30&secret=$secret", ); } @@ -149,9 +149,11 @@ class Code { static int _getDigits(Uri uri, String issuer) { try { - if (issuer.toLowerCase() == "steam") return steamDigits; return int.parse(uri.queryParameters['digits']!); } catch (e) { + if (issuer.toLowerCase() == "steam") { + return steamDigits; + } return defaultDigits; } } From 536de8a3fa19d649d0042bf5e2d6b4dd5b07dc30 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 14:17:23 +0530 Subject: [PATCH 173/367] Minor refactor --- .../view/setup_enter_secret_key_page.dart | 46 +++++-------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart index 64837f8f5..f9b87c7d3 100644 --- a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart +++ b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart @@ -20,7 +20,6 @@ class _SetupEnterSecretKeyPageState extends State { late TextEditingController _issuerController; late TextEditingController _accountController; late TextEditingController _secretController; - late TextEditingController _digitsController; late bool _secretKeyObscured; @override @@ -35,9 +34,6 @@ class _SetupEnterSecretKeyPageState extends State { _secretController = TextEditingController( text: widget.code?.secret, ); - _digitsController = TextEditingController( - text: widget.code?.digits.toString(), - ); _secretKeyObscured = widget.code != null; super.initState(); } @@ -118,31 +114,7 @@ class _SetupEnterSecretKeyPageState extends State { ), controller: _accountController, ), - const SizedBox( - height: 20, - ), - TextFormField( - validator: (value) { - if (value == null || value.isEmpty) { - return "Please enter some number"; - } - if (int.tryParse(value) == null) { - return "Please enter a valid number"; - } - return null; - }, - readOnly: widget.code?.type == Type.steam, - keyboardType: TextInputType.number, - decoration: InputDecoration( - hintText: l10n.digitsAccountHint, - labelText: l10n.digitsAccountHint, - floatingLabelBehavior: FloatingLabelBehavior.auto, - ), - controller: _digitsController, - ), - const SizedBox( - height: 40, - ), + const SizedBox(height: 40), SizedBox( width: 400, child: OutlinedButton( @@ -184,10 +156,15 @@ class _SetupEnterSecretKeyPageState extends State { final account = _accountController.text.trim(); final issuer = _issuerController.text.trim(); final secret = _secretController.text.trim().replaceAll(' ', ''); - final digits = int.tryParse(_digitsController.text.trim()) ?? - (widget.code?.type == Type.steam - ? Code.steamDigits - : Code.defaultDigits); + final isStreamCode = issuer.toLowerCase() == "steam"; + late int digits; + if (widget.code != null) { + digits = widget.code!.digits; + } else if (isStreamCode) { + digits = Code.steamDigits; + } else { + digits = Code.defaultDigits; + } if (widget.code != null && widget.code!.secret != secret) { ButtonResult? result = await showChoiceActionSheet( context, @@ -204,7 +181,7 @@ class _SetupEnterSecretKeyPageState extends State { } final Code newCode = widget.code == null ? Code.fromAccountAndSecret( - Type.totp, + isStreamCode ? Type.steam : Type.totp, account, issuer, secret, @@ -214,7 +191,6 @@ class _SetupEnterSecretKeyPageState extends State { account: account, issuer: issuer, secret: secret, - digits: digits, ); // Verify the validity of the code getOTP(newCode); From 679922bdfee749c34887f7e1fcb1a360dfa862e8 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 14:19:12 +0530 Subject: [PATCH 174/367] Refactor --- auth/lib/l10n/arb/app_en.arb | 2 -- .../onboarding/view/setup_enter_secret_key_page.dart | 10 +--------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/auth/lib/l10n/arb/app_en.arb b/auth/lib/l10n/arb/app_en.arb index 0c42d78f7..c22bac930 100644 --- a/auth/lib/l10n/arb/app_en.arb +++ b/auth/lib/l10n/arb/app_en.arb @@ -20,8 +20,6 @@ "codeIssuerHint": "Issuer", "codeSecretKeyHint": "Secret Key", "codeAccountHint": "Account (you@domain.com)", - "digitsAccountHint": "Digits (default=6)", - "accountKeyType": "Type of key", "sessionExpired": "Session expired", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart index f9b87c7d3..57edcc2e1 100644 --- a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart +++ b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart @@ -157,14 +157,6 @@ class _SetupEnterSecretKeyPageState extends State { final issuer = _issuerController.text.trim(); final secret = _secretController.text.trim().replaceAll(' ', ''); final isStreamCode = issuer.toLowerCase() == "steam"; - late int digits; - if (widget.code != null) { - digits = widget.code!.digits; - } else if (isStreamCode) { - digits = Code.steamDigits; - } else { - digits = Code.defaultDigits; - } if (widget.code != null && widget.code!.secret != secret) { ButtonResult? result = await showChoiceActionSheet( context, @@ -185,7 +177,7 @@ class _SetupEnterSecretKeyPageState extends State { account, issuer, secret, - digits, + isStreamCode ? Code.steamDigits : Code.defaultDigits, ) : widget.code!.copyWith( account: account, From 88315601519e762d44b9db96ea13a9c2817bfa87 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 3 May 2024 14:21:59 +0530 Subject: [PATCH 175/367] Minor fix --- auth/lib/models/code.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/lib/models/code.dart b/auth/lib/models/code.dart index 1be4ef37e..bd6077326 100644 --- a/auth/lib/models/code.dart +++ b/auth/lib/models/code.dart @@ -59,7 +59,7 @@ class Code { updatedType, updatedCounter, "otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}" - "${updatedType == Type.steam ? "" : "&digits=$updatedDigits"}&issuer=$updateIssuer" + "&digits=$updatedDigits&issuer=$updateIssuer" "&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}", generatedID: generatedID, ); From ea812561c6340016e3535d24a2247af046b7b9cb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 14:38:56 +0530 Subject: [PATCH 176/367] Run everywhere --- desktop/.github/workflows/desktop-release.yml | 4 +--- desktop/docs/release.md | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/desktop/.github/workflows/desktop-release.yml b/desktop/.github/workflows/desktop-release.yml index fc1464cd9..7fd96c7d9 100644 --- a/desktop/.github/workflows/desktop-release.yml +++ b/desktop/.github/workflows/desktop-release.yml @@ -32,9 +32,7 @@ jobs: strategy: matrix: - os: [macos-latest] - # Commented for testing - # os: [macos-latest, ubuntu-latest, windows-latest] + os: [macos-latest, ubuntu-latest, windows-latest] steps: - name: Checkout code diff --git a/desktop/docs/release.md b/desktop/docs/release.md index da807b572..b55c96326 100644 --- a/desktop/docs/release.md +++ b/desktop/docs/release.md @@ -23,10 +23,10 @@ The workflow is: - Update the CHANGELOG. - Update the version in `package.json` - - `git commit -m 'Release v1.x.x'` + - `git commit -m "[photosd] Release v1.2.3"` - Open PR, merge into main. -2. Tag this commit with a tag matching the pattern `photosd-v1.2.3`, where +2. Tag the merge commit with a tag matching the pattern `photosd-v1.2.3`, where `1.2.3` is the version in `package.json` ```sh From 2b9ee7824d44349376913905a624141407baf28d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 14:44:24 +0530 Subject: [PATCH 177/367] lint --- desktop/.github/workflows/desktop-release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/desktop/.github/workflows/desktop-release.yml b/desktop/.github/workflows/desktop-release.yml index 7fd96c7d9..2fa382376 100644 --- a/desktop/.github/workflows/desktop-release.yml +++ b/desktop/.github/workflows/desktop-release.yml @@ -77,6 +77,7 @@ jobs: env: # macOS notarization credentials key details APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + APPLE_APP_SPECIFIC_PASSWORD: + ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} USE_HARD_LINKS: false From 03e9aef848473f30ed50835ab1a4683bb06a225d Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 3 May 2024 14:54:31 +0530 Subject: [PATCH 178/367] Update copies for Cast --- mobile/lib/generated/intl/messages_en.dart | 6 +++--- mobile/lib/generated/l10n.dart | 12 ++++++------ mobile/lib/l10n/intl_en.arb | 4 ++-- mobile/lib/ui/cast/choose.dart | 2 +- .../CollectionOptions/AlbumCastDialog.tsx | 4 ++-- web/packages/next/locales/bg-BG/translation.json | 4 ++-- web/packages/next/locales/de-DE/translation.json | 4 ++-- web/packages/next/locales/en-US/translation.json | 8 ++++---- web/packages/next/locales/es-ES/translation.json | 4 ++-- web/packages/next/locales/fa-IR/translation.json | 4 ++-- web/packages/next/locales/fi-FI/translation.json | 4 ++-- web/packages/next/locales/fr-FR/translation.json | 4 ++-- web/packages/next/locales/it-IT/translation.json | 4 ++-- web/packages/next/locales/ko-KR/translation.json | 4 ++-- web/packages/next/locales/nl-NL/translation.json | 4 ++-- web/packages/next/locales/pt-BR/translation.json | 4 ++-- web/packages/next/locales/pt-PT/translation.json | 4 ++-- web/packages/next/locales/ru-RU/translation.json | 4 ++-- web/packages/next/locales/sv-SE/translation.json | 4 ++-- web/packages/next/locales/th-TH/translation.json | 4 ++-- web/packages/next/locales/tr-TR/translation.json | 4 ++-- web/packages/next/locales/zh-CN/translation.json | 4 ++-- 22 files changed, 50 insertions(+), 50 deletions(-) diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index 9f7792f34..43b39c82e 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -362,8 +362,8 @@ class MessageLookup extends MessageLookupByLibrary { "autoCastiOSPermission": MessageLookupByLibrary.simpleMessage( "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings."), "autoPair": MessageLookupByLibrary.simpleMessage("Auto pair"), - "autoPairGoogle": MessageLookupByLibrary.simpleMessage( - "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos."), + "autoPairDesc": MessageLookupByLibrary.simpleMessage( + "Auto pair works only with devices that support Chromecast."), "available": MessageLookupByLibrary.simpleMessage("Available"), "backedUpFolders": MessageLookupByLibrary.simpleMessage("Backed up folders"), @@ -918,7 +918,7 @@ class MessageLookup extends MessageLookupByLibrary { "manageSubscription": MessageLookupByLibrary.simpleMessage("Manage subscription"), "manualPairDesc": MessageLookupByLibrary.simpleMessage( - "Pair with PIN works for any large screen device you want to play your album on."), + "Pair with PIN works with any screen you wish to view your album on."), "map": MessageLookupByLibrary.simpleMessage("Map"), "maps": MessageLookupByLibrary.simpleMessage("Maps"), "mastodon": MessageLookupByLibrary.simpleMessage("Mastodon"), diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index b564a6d3d..dbedbadf6 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -8594,20 +8594,20 @@ class S { ); } - /// `Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.` - String get autoPairGoogle { + /// `Auto pair works only with devices that support Chromecast.` + String get autoPairDesc { return Intl.message( - 'Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.', - name: 'autoPairGoogle', + 'Auto pair works only with devices that support Chromecast.', + name: 'autoPairDesc', desc: '', args: [], ); } - /// `Pair with PIN works for any large screen device you want to play your album on.` + /// `Pair with PIN works with any screen you wish to view your album on.` String get manualPairDesc { return Intl.message( - 'Pair with PIN works for any large screen device you want to play your album on.', + 'Pair with PIN works with any screen you wish to view your album on.', name: 'manualPairDesc', desc: '', args: [], diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index 2f13dd1ba..e59163e6b 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1216,8 +1216,8 @@ "customEndpoint": "Connected to {endpoint}", "createCollaborativeLink": "Create collaborative link", "search": "Search", - "autoPairGoogle": "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.", - "manualPairDesc": "Pair with PIN works for any large screen device you want to play your album on.", + "autoPairDesc": "Auto pair works only with devices that support Chromecast.", + "manualPairDesc": "Pair with PIN works with any screen you wish to view your album on.", "connectToDevice": "Connect to device", "autoCastDialogBody": "You'll see available Cast devices here.", "autoCastiOSPermission": "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.", diff --git a/mobile/lib/ui/cast/choose.dart b/mobile/lib/ui/cast/choose.dart index 7f0288733..bd4c9876d 100644 --- a/mobile/lib/ui/cast/choose.dart +++ b/mobile/lib/ui/cast/choose.dart @@ -31,7 +31,7 @@ class _CastChooseDialogState extends State { children: [ const SizedBox(height: 8), Text( - S.of(context).autoPairGoogle, + S.of(context).autoPairDesc, style: textStyle.bodyMuted, ), const SizedBox(height: 12), diff --git a/web/apps/photos/src/components/Collections/CollectionOptions/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/CollectionOptions/AlbumCastDialog.tsx index fdabffe84..4b48b62ef 100644 --- a/web/apps/photos/src/components/Collections/CollectionOptions/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/CollectionOptions/AlbumCastDialog.tsx @@ -162,7 +162,7 @@ export default function AlbumCastDialog(props: Props) { <> {t( - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE", + "AUTO_CAST_PAIR_DESC", )} @@ -179,7 +179,7 @@ export default function AlbumCastDialog(props: Props) { )} - {t("PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE")} + {t("PAIR_WITH_PIN_DESC")} {{url}} on the device you want to pair.", - "CAST_AUTO_PAIR_FAILED": "Chromecast Auto Pair failed. Please try again.", + "CAST_AUTO_PAIR_FAILED": "Chromecast auto pair failed. Please try again.", "FREEHAND": "Freehand", "APPLY_CROP": "Apply Crop", "PHOTO_EDIT_REQUIRED_TO_SAVE": "At least one transformation or color adjustment must be performed before saving.", diff --git a/web/packages/next/locales/es-ES/translation.json b/web/packages/next/locales/es-ES/translation.json index a01d322b7..2916b5d5c 100644 --- a/web/packages/next/locales/es-ES/translation.json +++ b/web/packages/next/locales/es-ES/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "", "TV_NOT_FOUND": "", "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "", "CHOOSE_DEVICE_FROM_BROWSER": "", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "", "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "", diff --git a/web/packages/next/locales/fa-IR/translation.json b/web/packages/next/locales/fa-IR/translation.json index 0c3749d13..f06066116 100644 --- a/web/packages/next/locales/fa-IR/translation.json +++ b/web/packages/next/locales/fa-IR/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "", "TV_NOT_FOUND": "", "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "", "CHOOSE_DEVICE_FROM_BROWSER": "", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "", "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "", diff --git a/web/packages/next/locales/fi-FI/translation.json b/web/packages/next/locales/fi-FI/translation.json index d945fcde3..38455b3e2 100644 --- a/web/packages/next/locales/fi-FI/translation.json +++ b/web/packages/next/locales/fi-FI/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "", "TV_NOT_FOUND": "", "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "", "CHOOSE_DEVICE_FROM_BROWSER": "", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "", "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "", diff --git a/web/packages/next/locales/fr-FR/translation.json b/web/packages/next/locales/fr-FR/translation.json index f3113202f..d7911130e 100644 --- a/web/packages/next/locales/fr-FR/translation.json +++ b/web/packages/next/locales/fr-FR/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "Associer les appareils", "TV_NOT_FOUND": "TV introuvable. Avez-vous entré le code PIN correctement ?", "AUTO_CAST_PAIR": "Paire automatique", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "La paire automatique nécessite la connexion aux serveurs Google et ne fonctionne qu'avec les appareils pris en charge par Chromecast. Google ne recevra pas de données sensibles, telles que vos photos.", + "AUTO_CAST_PAIR_DESC": "La paire automatique nécessite la connexion aux serveurs Google et ne fonctionne qu'avec les appareils pris en charge par Chromecast. Google ne recevra pas de données sensibles, telles que vos photos.", "PAIR_WITH_PIN": "Associer avec le code PIN", "CHOOSE_DEVICE_FROM_BROWSER": "Choisissez un périphérique compatible avec la caste à partir de la fenêtre pop-up du navigateur.", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "L'association avec le code PIN fonctionne pour tout appareil grand écran sur lequel vous voulez lire votre album.", + "PAIR_WITH_PIN_DESC": "L'association avec le code PIN fonctionne pour tout appareil grand écran sur lequel vous voulez lire votre album.", "VISIT_CAST_ENTE_IO": "Visitez {{url}} sur l'appareil que vous voulez associer.", "CAST_AUTO_PAIR_FAILED": "La paire automatique de Chromecast a échoué. Veuillez réessayer.", "FREEHAND": "Main levée", diff --git a/web/packages/next/locales/it-IT/translation.json b/web/packages/next/locales/it-IT/translation.json index bf555911c..eb7a68d45 100644 --- a/web/packages/next/locales/it-IT/translation.json +++ b/web/packages/next/locales/it-IT/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "", "TV_NOT_FOUND": "", "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "", "CHOOSE_DEVICE_FROM_BROWSER": "", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "", "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "", diff --git a/web/packages/next/locales/ko-KR/translation.json b/web/packages/next/locales/ko-KR/translation.json index aee2c6cd5..35aeff339 100644 --- a/web/packages/next/locales/ko-KR/translation.json +++ b/web/packages/next/locales/ko-KR/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "", "TV_NOT_FOUND": "", "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "", "CHOOSE_DEVICE_FROM_BROWSER": "", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "", "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "", diff --git a/web/packages/next/locales/nl-NL/translation.json b/web/packages/next/locales/nl-NL/translation.json index 62b846b14..0a2debcb3 100644 --- a/web/packages/next/locales/nl-NL/translation.json +++ b/web/packages/next/locales/nl-NL/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "Koppel apparaten", "TV_NOT_FOUND": "TV niet gevonden. Heeft u de pincode correct ingevoerd?", "AUTO_CAST_PAIR": "Automatisch koppelen", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "Automatisch koppelen vereist verbinding met Google-servers en werkt alleen met apparaten die door Chromecast worden ondersteund. Google zal geen gevoelige gegevens ontvangen, zoals uw foto's.", + "AUTO_CAST_PAIR_DESC": "Automatisch koppelen vereist verbinding met Google-servers en werkt alleen met apparaten die door Chromecast worden ondersteund. Google zal geen gevoelige gegevens ontvangen, zoals uw foto's.", "PAIR_WITH_PIN": "Koppelen met PIN", "CHOOSE_DEVICE_FROM_BROWSER": "Kies een compatibel apparaat uit de browser popup.", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "Koppelen met PIN werkt op elk groot schermapparaat waarop u uw album wilt afspelen.", + "PAIR_WITH_PIN_DESC": "Koppelen met PIN werkt op elk groot schermapparaat waarop u uw album wilt afspelen.", "VISIT_CAST_ENTE_IO": "Bezoek {{url}} op het apparaat dat je wilt koppelen.", "CAST_AUTO_PAIR_FAILED": "Auto koppelen van Chromecast is mislukt. Probeer het opnieuw.", "FREEHAND": "Losse hand", diff --git a/web/packages/next/locales/pt-BR/translation.json b/web/packages/next/locales/pt-BR/translation.json index 9fc00517c..dd95ef884 100644 --- a/web/packages/next/locales/pt-BR/translation.json +++ b/web/packages/next/locales/pt-BR/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "Parear dispositivos", "TV_NOT_FOUND": "TV não encontrada. Você inseriu o PIN correto?", "AUTO_CAST_PAIR": "Pareamento automático", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "O Auto Pair requer a conexão com servidores do Google e só funciona com dispositivos Chromecast. O Google não receberá dados confidenciais, como suas fotos.", + "AUTO_CAST_PAIR_DESC": "O Auto Pair requer a conexão com servidores do Google e só funciona com dispositivos Chromecast. O Google não receberá dados confidenciais, como suas fotos.", "PAIR_WITH_PIN": "Parear com PIN", "CHOOSE_DEVICE_FROM_BROWSER": "Escolha um dispositivo compatível com casts no navegador popup.", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "Parear com o PIN funciona para qualquer dispositivo de tela grande onde você deseja reproduzir seu álbum.", + "PAIR_WITH_PIN_DESC": "Parear com o PIN funciona para qualquer dispositivo de tela grande onde você deseja reproduzir seu álbum.", "VISIT_CAST_ENTE_IO": "Acesse {{url}} no dispositivo que você deseja parear.", "CAST_AUTO_PAIR_FAILED": "Chromecast Auto Pair falhou. Por favor, tente novamente.", "FREEHAND": "Mão livre", diff --git a/web/packages/next/locales/pt-PT/translation.json b/web/packages/next/locales/pt-PT/translation.json index f6980b56e..29b36622c 100644 --- a/web/packages/next/locales/pt-PT/translation.json +++ b/web/packages/next/locales/pt-PT/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "", "TV_NOT_FOUND": "", "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "", "CHOOSE_DEVICE_FROM_BROWSER": "", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "", "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "", diff --git a/web/packages/next/locales/ru-RU/translation.json b/web/packages/next/locales/ru-RU/translation.json index 5d036c6c8..734a93ffb 100644 --- a/web/packages/next/locales/ru-RU/translation.json +++ b/web/packages/next/locales/ru-RU/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "Сопряжение устройств", "TV_NOT_FOUND": "Телевизор не найден. Вы правильно ввели PIN-код?", "AUTO_CAST_PAIR": "Автоматическое сопряжение", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "Автоматическое сопряжение требует подключения к серверам Google и работает только с устройствами, поддерживающими Chromecast. Google не будет получать конфиденциальные данные, такие как ваши фотографии.", + "AUTO_CAST_PAIR_DESC": "Автоматическое сопряжение требует подключения к серверам Google и работает только с устройствами, поддерживающими Chromecast. Google не будет получать конфиденциальные данные, такие как ваши фотографии.", "PAIR_WITH_PIN": "Соединение с помощью булавки", "CHOOSE_DEVICE_FROM_BROWSER": "Выберите устройство, совместимое с cast, во всплывающем окне браузера.", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "Сопряжение с помощью PIN-кода работает на любом устройстве с большим экраном, на котором вы хотите воспроизвести свой альбом.", + "PAIR_WITH_PIN_DESC": "Сопряжение с помощью PIN-кода работает на любом устройстве с большим экраном, на котором вы хотите воспроизвести свой альбом.", "VISIT_CAST_ENTE_IO": "Перейдите на страницу {{url}} на устройстве, которое вы хотите подключить.", "CAST_AUTO_PAIR_FAILED": "Не удалось выполнить автоматическое сопряжение Chromecast. Пожалуйста, попробуйте снова.", "FREEHAND": "От руки", diff --git a/web/packages/next/locales/sv-SE/translation.json b/web/packages/next/locales/sv-SE/translation.json index ba6ecee09..78e7116e0 100644 --- a/web/packages/next/locales/sv-SE/translation.json +++ b/web/packages/next/locales/sv-SE/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "", "TV_NOT_FOUND": "", "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "", "CHOOSE_DEVICE_FROM_BROWSER": "", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "", "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "", diff --git a/web/packages/next/locales/th-TH/translation.json b/web/packages/next/locales/th-TH/translation.json index d945fcde3..38455b3e2 100644 --- a/web/packages/next/locales/th-TH/translation.json +++ b/web/packages/next/locales/th-TH/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "", "TV_NOT_FOUND": "", "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "", "CHOOSE_DEVICE_FROM_BROWSER": "", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "", "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "", diff --git a/web/packages/next/locales/tr-TR/translation.json b/web/packages/next/locales/tr-TR/translation.json index d945fcde3..38455b3e2 100644 --- a/web/packages/next/locales/tr-TR/translation.json +++ b/web/packages/next/locales/tr-TR/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "", "TV_NOT_FOUND": "", "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "", "CHOOSE_DEVICE_FROM_BROWSER": "", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "", "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "", diff --git a/web/packages/next/locales/zh-CN/translation.json b/web/packages/next/locales/zh-CN/translation.json index c67018aaa..b37c1e603 100644 --- a/web/packages/next/locales/zh-CN/translation.json +++ b/web/packages/next/locales/zh-CN/translation.json @@ -599,10 +599,10 @@ "PAIR_DEVICE_TO_TV": "配对设备", "TV_NOT_FOUND": "未找到电视。您输入的 PIN 码正确吗?", "AUTO_CAST_PAIR": "自动配对", - "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "自动配对需要连接到 Google 服务器,且仅适用于支持 Chromecast 的设备。Google 不会接收敏感数据,例如您的照片。", + "AUTO_CAST_PAIR_DESC": "自动配对需要连接到 Google 服务器,且仅适用于支持 Chromecast 的设备。Google 不会接收敏感数据,例如您的照片。", "PAIR_WITH_PIN": "用 PIN 配对", "CHOOSE_DEVICE_FROM_BROWSER": "从浏览器弹出窗口中选择兼容 Cast 的设备。", - "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "用 PIN 配对适用于任何大屏幕设备,您可以在这些设备上播放您的相册。", + "PAIR_WITH_PIN_DESC": "用 PIN 配对适用于任何大屏幕设备,您可以在这些设备上播放您的相册。", "VISIT_CAST_ENTE_IO": "在您要配对的设备上访问 {{url}} 。", "CAST_AUTO_PAIR_FAILED": "Chromecast 自动配对失败。请再试一次。", "FREEHAND": "手画", From 8dc6e1ab6a1f6bb95ea4b09c35189e65bfd060e2 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 3 May 2024 14:59:42 +0530 Subject: [PATCH 179/367] Fix lint error --- .../Collections/CollectionOptions/AlbumCastDialog.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/apps/photos/src/components/Collections/CollectionOptions/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/CollectionOptions/AlbumCastDialog.tsx index 4b48b62ef..3d9d06166 100644 --- a/web/apps/photos/src/components/Collections/CollectionOptions/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/CollectionOptions/AlbumCastDialog.tsx @@ -161,9 +161,7 @@ export default function AlbumCastDialog(props: Props) { {browserCanCast && ( <> - {t( - "AUTO_CAST_PAIR_DESC", - )} + {t("AUTO_CAST_PAIR_DESC")} Date: Fri, 3 May 2024 09:31:18 +0000 Subject: [PATCH 180/367] New Crowdin translations by GitHub Action --- web/packages/next/locales/fr-FR/translation.json | 8 ++++---- web/packages/next/locales/nl-NL/translation.json | 8 ++++---- web/packages/next/locales/pt-BR/translation.json | 8 ++++---- web/packages/next/locales/ru-RU/translation.json | 8 ++++---- web/packages/next/locales/zh-CN/translation.json | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/web/packages/next/locales/fr-FR/translation.json b/web/packages/next/locales/fr-FR/translation.json index d7911130e..89a054c22 100644 --- a/web/packages/next/locales/fr-FR/translation.json +++ b/web/packages/next/locales/fr-FR/translation.json @@ -598,13 +598,13 @@ "ENTER_CAST_PIN_CODE": "Entrez le code que vous voyez sur la TV ci-dessous pour appairer cet appareil.", "PAIR_DEVICE_TO_TV": "Associer les appareils", "TV_NOT_FOUND": "TV introuvable. Avez-vous entré le code PIN correctement ?", - "AUTO_CAST_PAIR": "Paire automatique", - "AUTO_CAST_PAIR_DESC": "La paire automatique nécessite la connexion aux serveurs Google et ne fonctionne qu'avec les appareils pris en charge par Chromecast. Google ne recevra pas de données sensibles, telles que vos photos.", + "AUTO_CAST_PAIR": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "Associer avec le code PIN", "CHOOSE_DEVICE_FROM_BROWSER": "Choisissez un périphérique compatible avec la caste à partir de la fenêtre pop-up du navigateur.", - "PAIR_WITH_PIN_DESC": "L'association avec le code PIN fonctionne pour tout appareil grand écran sur lequel vous voulez lire votre album.", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "Visitez {{url}} sur l'appareil que vous voulez associer.", - "CAST_AUTO_PAIR_FAILED": "La paire automatique de Chromecast a échoué. Veuillez réessayer.", + "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "Main levée", "APPLY_CROP": "Appliquer le recadrage", "PHOTO_EDIT_REQUIRED_TO_SAVE": "Au moins une transformation ou un ajustement de couleur doit être effectué avant de sauvegarder.", diff --git a/web/packages/next/locales/nl-NL/translation.json b/web/packages/next/locales/nl-NL/translation.json index 0a2debcb3..f75bd5e47 100644 --- a/web/packages/next/locales/nl-NL/translation.json +++ b/web/packages/next/locales/nl-NL/translation.json @@ -598,13 +598,13 @@ "ENTER_CAST_PIN_CODE": "Voer de code in die u op de TV ziet om dit apparaat te koppelen.", "PAIR_DEVICE_TO_TV": "Koppel apparaten", "TV_NOT_FOUND": "TV niet gevonden. Heeft u de pincode correct ingevoerd?", - "AUTO_CAST_PAIR": "Automatisch koppelen", - "AUTO_CAST_PAIR_DESC": "Automatisch koppelen vereist verbinding met Google-servers en werkt alleen met apparaten die door Chromecast worden ondersteund. Google zal geen gevoelige gegevens ontvangen, zoals uw foto's.", + "AUTO_CAST_PAIR": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "Koppelen met PIN", "CHOOSE_DEVICE_FROM_BROWSER": "Kies een compatibel apparaat uit de browser popup.", - "PAIR_WITH_PIN_DESC": "Koppelen met PIN werkt op elk groot schermapparaat waarop u uw album wilt afspelen.", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "Bezoek {{url}} op het apparaat dat je wilt koppelen.", - "CAST_AUTO_PAIR_FAILED": "Auto koppelen van Chromecast is mislukt. Probeer het opnieuw.", + "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "Losse hand", "APPLY_CROP": "Bijsnijden toepassen", "PHOTO_EDIT_REQUIRED_TO_SAVE": "Tenminste één transformatie of kleuraanpassing moet worden uitgevoerd voordat u opslaat.", diff --git a/web/packages/next/locales/pt-BR/translation.json b/web/packages/next/locales/pt-BR/translation.json index dd95ef884..630c8cf65 100644 --- a/web/packages/next/locales/pt-BR/translation.json +++ b/web/packages/next/locales/pt-BR/translation.json @@ -598,13 +598,13 @@ "ENTER_CAST_PIN_CODE": "Digite o código que você vê na TV abaixo para parear este dispositivo.", "PAIR_DEVICE_TO_TV": "Parear dispositivos", "TV_NOT_FOUND": "TV não encontrada. Você inseriu o PIN correto?", - "AUTO_CAST_PAIR": "Pareamento automático", - "AUTO_CAST_PAIR_DESC": "O Auto Pair requer a conexão com servidores do Google e só funciona com dispositivos Chromecast. O Google não receberá dados confidenciais, como suas fotos.", + "AUTO_CAST_PAIR": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "Parear com PIN", "CHOOSE_DEVICE_FROM_BROWSER": "Escolha um dispositivo compatível com casts no navegador popup.", - "PAIR_WITH_PIN_DESC": "Parear com o PIN funciona para qualquer dispositivo de tela grande onde você deseja reproduzir seu álbum.", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "Acesse {{url}} no dispositivo que você deseja parear.", - "CAST_AUTO_PAIR_FAILED": "Chromecast Auto Pair falhou. Por favor, tente novamente.", + "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "Mão livre", "APPLY_CROP": "Aplicar Recorte", "PHOTO_EDIT_REQUIRED_TO_SAVE": "Pelo menos uma transformação ou ajuste de cor deve ser feito antes de salvar.", diff --git a/web/packages/next/locales/ru-RU/translation.json b/web/packages/next/locales/ru-RU/translation.json index 734a93ffb..910c9253f 100644 --- a/web/packages/next/locales/ru-RU/translation.json +++ b/web/packages/next/locales/ru-RU/translation.json @@ -598,13 +598,13 @@ "ENTER_CAST_PIN_CODE": "Введите код, который вы видите на экране телевизора ниже, чтобы выполнить сопряжение с этим устройством.", "PAIR_DEVICE_TO_TV": "Сопряжение устройств", "TV_NOT_FOUND": "Телевизор не найден. Вы правильно ввели PIN-код?", - "AUTO_CAST_PAIR": "Автоматическое сопряжение", - "AUTO_CAST_PAIR_DESC": "Автоматическое сопряжение требует подключения к серверам Google и работает только с устройствами, поддерживающими Chromecast. Google не будет получать конфиденциальные данные, такие как ваши фотографии.", + "AUTO_CAST_PAIR": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "Соединение с помощью булавки", "CHOOSE_DEVICE_FROM_BROWSER": "Выберите устройство, совместимое с cast, во всплывающем окне браузера.", - "PAIR_WITH_PIN_DESC": "Сопряжение с помощью PIN-кода работает на любом устройстве с большим экраном, на котором вы хотите воспроизвести свой альбом.", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "Перейдите на страницу {{url}} на устройстве, которое вы хотите подключить.", - "CAST_AUTO_PAIR_FAILED": "Не удалось выполнить автоматическое сопряжение Chromecast. Пожалуйста, попробуйте снова.", + "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "От руки", "APPLY_CROP": "Применить обрезку", "PHOTO_EDIT_REQUIRED_TO_SAVE": "Перед сохранением необходимо выполнить по крайней мере одно преобразование или корректировку цвета.", diff --git a/web/packages/next/locales/zh-CN/translation.json b/web/packages/next/locales/zh-CN/translation.json index b37c1e603..04d0428fb 100644 --- a/web/packages/next/locales/zh-CN/translation.json +++ b/web/packages/next/locales/zh-CN/translation.json @@ -598,13 +598,13 @@ "ENTER_CAST_PIN_CODE": "输入您在下面的电视上看到的代码来配对此设备。", "PAIR_DEVICE_TO_TV": "配对设备", "TV_NOT_FOUND": "未找到电视。您输入的 PIN 码正确吗?", - "AUTO_CAST_PAIR": "自动配对", - "AUTO_CAST_PAIR_DESC": "自动配对需要连接到 Google 服务器,且仅适用于支持 Chromecast 的设备。Google 不会接收敏感数据,例如您的照片。", + "AUTO_CAST_PAIR": "", + "AUTO_CAST_PAIR_DESC": "", "PAIR_WITH_PIN": "用 PIN 配对", "CHOOSE_DEVICE_FROM_BROWSER": "从浏览器弹出窗口中选择兼容 Cast 的设备。", - "PAIR_WITH_PIN_DESC": "用 PIN 配对适用于任何大屏幕设备,您可以在这些设备上播放您的相册。", + "PAIR_WITH_PIN_DESC": "", "VISIT_CAST_ENTE_IO": "在您要配对的设备上访问 {{url}} 。", - "CAST_AUTO_PAIR_FAILED": "Chromecast 自动配对失败。请再试一次。", + "CAST_AUTO_PAIR_FAILED": "", "FREEHAND": "手画", "APPLY_CROP": "应用裁剪", "PHOTO_EDIT_REQUIRED_TO_SAVE": "保存之前必须至少执行一项转换或颜色调整。", From 377a8b11288aa465ac2af1be8fd9c0c0cd1de0f6 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Fri, 3 May 2024 15:08:04 +0530 Subject: [PATCH 181/367] [mob][photos] Request for ACCESS_MEDIA_LOCATION permission if not granted --- mobile/lib/utils/file_uploader.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mobile/lib/utils/file_uploader.dart b/mobile/lib/utils/file_uploader.dart index f81f9d34b..bcd5bb121 100644 --- a/mobile/lib/utils/file_uploader.dart +++ b/mobile/lib/utils/file_uploader.dart @@ -376,7 +376,13 @@ class FileUploader { if (Platform.isAndroid) { final bool hasPermission = await Permission.accessMediaLocation.isGranted; if (!hasPermission) { - throw NoMediaLocationAccessError(); + final permissionStatus = await Permission.accessMediaLocation.request(); + if (!permissionStatus.isGranted) { + _logger.severe( + "Media location access denied with permission status: ${permissionStatus.name}", + ); + throw NoMediaLocationAccessError(); + } } } } From ff45d1138490e9a0baefcc1c6221c86e904071e2 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 3 May 2024 15:12:53 +0530 Subject: [PATCH 182/367] Changelog --- mobile/lib/services/update_service.dart | 2 +- .../notification/update/change_log_page.dart | 53 +++++++++---------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/mobile/lib/services/update_service.dart b/mobile/lib/services/update_service.dart index e18d8548c..da01de828 100644 --- a/mobile/lib/services/update_service.dart +++ b/mobile/lib/services/update_service.dart @@ -16,7 +16,7 @@ class UpdateService { static final UpdateService instance = UpdateService._privateConstructor(); static const kUpdateAvailableShownTimeKey = "update_available_shown_time_key"; static const changeLogVersionKey = "update_change_log_key"; - static const currentChangeLogVersion = 18; + static const currentChangeLogVersion = 19; LatestVersionInfo? _latestVersion; final _logger = Logger("UpdateService"); diff --git a/mobile/lib/ui/notification/update/change_log_page.dart b/mobile/lib/ui/notification/update/change_log_page.dart index 289d84590..61494af93 100644 --- a/mobile/lib/ui/notification/update/change_log_page.dart +++ b/mobile/lib/ui/notification/update/change_log_page.dart @@ -1,5 +1,3 @@ -import "dart:async"; - import 'package:flutter/material.dart'; import "package:photos/generated/l10n.dart"; import 'package:photos/services/update_service.dart'; @@ -9,7 +7,6 @@ import 'package:photos/ui/components/divider_widget.dart'; import 'package:photos/ui/components/models/button_type.dart'; import 'package:photos/ui/components/title_bar_title_widget.dart'; import 'package:photos/ui/notification/update/change_log_entry.dart'; -import "package:url_launcher/url_launcher_string.dart"; class ChangeLogPage extends StatefulWidget { const ChangeLogPage({ @@ -81,31 +78,31 @@ class _ChangeLogPageState extends State { const SizedBox( height: 8, ), - ButtonWidget( - buttonType: ButtonType.trailingIconSecondary, - buttonSize: ButtonSize.large, - labelText: S.of(context).joinDiscord, - icon: Icons.discord_outlined, - iconColor: enteColorScheme.primary500, - onTap: () async { - unawaited( - launchUrlString( - "https://discord.com/invite/z2YVKkycX3", - mode: LaunchMode.externalApplication, - ), - ); - }, - ), // ButtonWidget( // buttonType: ButtonType.trailingIconSecondary, // buttonSize: ButtonSize.large, - // labelText: S.of(context).rateTheApp, - // icon: Icons.favorite_rounded, + // labelText: S.of(context).joinDiscord, + // icon: Icons.discord_outlined, // iconColor: enteColorScheme.primary500, // onTap: () async { - // await UpdateService.instance.launchReviewUrl(); + // unawaited( + // launchUrlString( + // "https://discord.com/invite/z2YVKkycX3", + // mode: LaunchMode.externalApplication, + // ), + // ); // }, // ), + ButtonWidget( + buttonType: ButtonType.trailingIconSecondary, + buttonSize: ButtonSize.large, + labelText: S.of(context).rateTheApp, + icon: Icons.favorite_rounded, + iconColor: enteColorScheme.primary500, + onTap: () async { + await UpdateService.instance.launchReviewUrl(); + }, + ), const SizedBox(height: 8), ], ), @@ -122,18 +119,16 @@ class _ChangeLogPageState extends State { final List items = []; items.addAll([ ChangeLogEntry( - "Improved Performance for Large Galleries ✨", - 'We\'ve made significant improvements to how quickly galleries load and' - ' with less stutter, especially for those with a lot of photos and videos.', + "Cast albums to TV ✨", + "View a slideshow of your albums on any big screen! Open an album and click on the Cast button to get started.", ), ChangeLogEntry( - "Enhanced Functionality for Video Backups", - 'Even if video backups are disabled, you can now manually upload individual videos.', + "Own shared photos", + "You can now add shared items to your favorites to any of your personal albums. Ente will create a copy that is fully owned by you and can be organized to your liking.", ), ChangeLogEntry( - "Bug Fixes", - 'Many a bugs were squashed in this release.\n' - '\nIf you run into any, please write to team@ente.io, or let us know on Discord! 🙏', + "Performance improvements", + "This release also brings in major changes that should improve responsiveness. If you discover room for improvement, please let us know!", ), ]); From 21faf766112d68bb7da4d65b2cdfe11efc14e52a Mon Sep 17 00:00:00 2001 From: ashilkn Date: Fri, 3 May 2024 15:34:19 +0530 Subject: [PATCH 183/367] [mob][photos] Bump up to v0.8.90 --- mobile/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 2adf29321..385deb769 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.88+608 +version: 0.8.90+610 publish_to: none environment: From 34c664ac33ff1d2bbf5daa3fe8797fabbc5bb07e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 18:37:57 +0530 Subject: [PATCH 184/367] Move to @/media --- .../src/components/PhotoViewer/index.tsx | 5 ++-- web/apps/photos/src/utils/file/index.ts | 21 +++------------ web/packages/media/formats.ts | 26 +++++++++++++++++++ 3 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 web/packages/media/formats.ts diff --git a/web/apps/photos/src/components/PhotoViewer/index.tsx b/web/apps/photos/src/components/PhotoViewer/index.tsx index 8e6debf68..c7383efb1 100644 --- a/web/apps/photos/src/components/PhotoViewer/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/index.tsx @@ -11,11 +11,11 @@ import { copyFileToClipboard, downloadSingleFile, getFileFromURL, - isRawFile, isSupportedRawFormat, } from "utils/file"; import { FILE_TYPE } from "@/media/file-type"; +import { isNonWebImageFileExtension } from "@/media/formats"; import { lowercaseExtension } from "@/next/file"; import { FlexWrapper } from "@ente/shared/components/Container"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; @@ -350,7 +350,8 @@ function PhotoViewer(props: Iprops) { function updateShowEditButton(file: EnteFile) { const extension = lowercaseExtension(file.metadata.title); const isSupported = - !isRawFile(extension) || isSupportedRawFormat(extension); + !isNonWebImageFileExtension(extension) || + isSupportedRawFormat(extension); setShowEditButton( file.metadata.fileType === FILE_TYPE.IMAGE && isSupported, ); diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 212b2efd3..862f7b7f2 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -1,4 +1,5 @@ import { FILE_TYPE } from "@/media/file-type"; +import { isNonWebImageFileExtension } from "@/media/formats"; import { decodeLivePhoto } from "@/media/live-photo"; import { lowercaseExtension } from "@/next/file"; import log from "@/next/log"; @@ -40,20 +41,6 @@ import { isArchivedFile, updateMagicMetadata } from "utils/magicMetadata"; import { safeFileName } from "utils/native-fs"; import { writeStream } from "utils/native-stream"; -const RAW_FORMATS = [ - "heic", - "rw2", - "tiff", - "arw", - "cr3", - "cr2", - "raf", - "nef", - "psd", - "dng", - "tif", -]; - const SUPPORTED_RAW_FORMATS = [ "heic", "rw2", @@ -306,9 +293,9 @@ export const getRenderableImage = async (fileName: string, imageBlob: Blob) => { ); const { extension } = fileTypeInfo; - if (!isRawFile(extension)) { - // Either it is not something we know how to handle yet, or - // something that the browser already knows how to render. + if (!isNonWebImageFileExtension(extension)) { + // Either it is something that the browser already knows how to + // render, or something we don't even about yet. return imageBlob; } diff --git a/web/packages/media/formats.ts b/web/packages/media/formats.ts new file mode 100644 index 000000000..24d2c7c87 --- /dev/null +++ b/web/packages/media/formats.ts @@ -0,0 +1,26 @@ +/** + * Image file extensions that we know the browser is unlikely to have native + * support for. + */ +const nonWebImageFileExtensions = [ + "heic", + "rw2", + "tiff", + "arw", + "cr3", + "cr2", + "raf", + "nef", + "psd", + "dng", + "tif", +]; + +/** + * Return `true` if {@link extension} is from amongst a known set of image file + * extensions that we know that the browser is unlikely to have native support + * for. If we want to display such files in the browser, we'll need to convert + * them to some other format first. + */ +export const isNonWebImageFileExtension = (extension: string) => + nonWebImageFileExtensions.includes(extension.toLowerCase()); From 08f23efecac2fbcc69a069befe507ca43fbe7fbd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 18:40:48 +0530 Subject: [PATCH 185/367] Use in cast --- web/apps/cast/src/constants/upload.ts | 13 ------------- web/apps/cast/src/pages/slideshow.tsx | 8 ++++++-- web/apps/cast/src/utils/file.ts | 10 ---------- 3 files changed, 6 insertions(+), 25 deletions(-) delete mode 100644 web/apps/cast/src/constants/upload.ts diff --git a/web/apps/cast/src/constants/upload.ts b/web/apps/cast/src/constants/upload.ts deleted file mode 100644 index 2ae1c4383..000000000 --- a/web/apps/cast/src/constants/upload.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const RAW_FORMATS = [ - "heic", - "rw2", - "tiff", - "arw", - "cr3", - "cr2", - "raf", - "nef", - "psd", - "dng", - "tif", -]; diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index 8554524b2..4ffc4d335 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -1,4 +1,6 @@ import { FILE_TYPE } from "@/media/file-type"; +import { isNonWebImageFileExtension } from "@/media/formats"; +import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; import PairedSuccessfullyOverlay from "components/PairedSuccessfullyOverlay"; import { PhotoAuditorium } from "components/PhotoAuditorium"; @@ -11,7 +13,7 @@ import { } from "services/cast/castService"; import { Collection } from "types/collection"; import { EnteFile } from "types/file"; -import { getPreviewableImage, isRawFileFromFileName } from "utils/file"; +import { getPreviewableImage } from "utils/file"; const renderableFileURLCache = new Map(); @@ -74,7 +76,9 @@ export default function Slideshow() { if (file.info.fileSize > 100 * 1024 * 1024) return false; - if (isRawFileFromFileName(file.metadata.title)) return false; + const [, extension] = nameAndExtension(file.metadata.title); + + if (isNonWebImageFileExtension(extension)) return false; return true; }; diff --git a/web/apps/cast/src/utils/file.ts b/web/apps/cast/src/utils/file.ts index 91961b7be..02c6b8134 100644 --- a/web/apps/cast/src/utils/file.ts +++ b/web/apps/cast/src/utils/file.ts @@ -2,7 +2,6 @@ import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; import log from "@/next/log"; import ComlinkCryptoWorker from "@ente/shared/crypto"; -import { RAW_FORMATS } from "constants/upload"; import CastDownloadManager from "services/castDownloadManager"; import { detectMediaMIMEType } from "services/detect-type"; import { @@ -95,15 +94,6 @@ export function generateStreamFromArrayBuffer(data: Uint8Array) { }); } -export function isRawFileFromFileName(fileName: string) { - for (const rawFormat of RAW_FORMATS) { - if (fileName.toLowerCase().endsWith(rawFormat)) { - return true; - } - } - return false; -} - export function mergeMetadata(files: EnteFile[]): EnteFile[] { return files.map((file) => { if (file.pubMagicMetadata?.data.editedTime) { From a0c5dd84c7f2a03cd5df95af6594b99fd3a71722 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 18:58:25 +0530 Subject: [PATCH 186/367] Add typings --- web/apps/cast/package.json | 3 +++ web/apps/cast/src/utils/useCastReceiver.tsx | 3 +-- web/yarn.lock | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/web/apps/cast/package.json b/web/apps/cast/package.json index 012148969..4f774662a 100644 --- a/web/apps/cast/package.json +++ b/web/apps/cast/package.json @@ -8,5 +8,8 @@ "@ente/accounts": "*", "@ente/eslint-config": "*", "@ente/shared": "*" + }, + "devDependencies": { + "@types/chromecast-caf-receiver": "^6.0.14" } } diff --git a/web/apps/cast/src/utils/useCastReceiver.tsx b/web/apps/cast/src/utils/useCastReceiver.tsx index ff17b0910..336a1e5f2 100644 --- a/web/apps/cast/src/utils/useCastReceiver.tsx +++ b/web/apps/cast/src/utils/useCastReceiver.tsx @@ -1,5 +1,4 @@ -declare const cast: any; - +import { cast } from "chromecast-caf-receiver"; import { useEffect, useState } from "react"; type Receiver = { diff --git a/web/yarn.lock b/web/yarn.lock index af3a5f210..972b14df1 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1000,6 +1000,11 @@ "@types/node" "*" base-x "^3.0.6" +"@types/chromecast-caf-receiver@^6.0.14": + version "6.0.14" + resolved "https://registry.yarnpkg.com/@types/chromecast-caf-receiver/-/chromecast-caf-receiver-6.0.14.tgz#e1e781c62c84ee85899fd20d658e258f8f45f5be" + integrity sha512-qvN4uE4MlYCEtniTtjxG4D+KeEXfs/Sgqex9sSZdPVh5rffdifINYzKH3z3QRl+0mk41vD6vYZ8s8ZfW/8iFoQ== + "@types/estree@1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" From c750329fb1003ec31ccee945fe621c0c92672dd5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 19:02:04 +0530 Subject: [PATCH 187/367] Fix type only import Ref: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/28190#issuecomment-444339277 --- web/apps/cast/src/utils/useCastReceiver.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/cast/src/utils/useCastReceiver.tsx b/web/apps/cast/src/utils/useCastReceiver.tsx index 336a1e5f2..662c46968 100644 --- a/web/apps/cast/src/utils/useCastReceiver.tsx +++ b/web/apps/cast/src/utils/useCastReceiver.tsx @@ -1,4 +1,4 @@ -import { cast } from "chromecast-caf-receiver"; +/// import { useEffect, useState } from "react"; type Receiver = { From 1d8be29bd62f5cce91159b661401d8b826adb458 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 19:07:25 +0530 Subject: [PATCH 188/367] Add missing dependency array --- web/apps/cast/src/utils/useCastReceiver.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/cast/src/utils/useCastReceiver.tsx b/web/apps/cast/src/utils/useCastReceiver.tsx index 662c46968..26bc4a833 100644 --- a/web/apps/cast/src/utils/useCastReceiver.tsx +++ b/web/apps/cast/src/utils/useCastReceiver.tsx @@ -36,7 +36,7 @@ export const useCastReceiver = () => { load().then((receiver) => { setReceiver(receiver); }); - }); + }, []); return receiver; }; From d91147903122baa6e7e091d75eb11d2b093c1a1b Mon Sep 17 00:00:00 2001 From: ashilkn Date: Fri, 3 May 2024 19:17:48 +0530 Subject: [PATCH 189/367] [mob][photos] Remove logs added for debugging --- mobile/lib/main.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index a981b5e4a..67bcdfc21 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -277,7 +277,6 @@ Future _scheduleHeartBeat( isBackground ? kLastBGTaskHeartBeatTime : kLastFGTaskHeartBeatTime, DateTime.now().microsecondsSinceEpoch, ); - _logger.info("Heartbeat scheduled for ${isBackground ? 'BG' : 'FG'} task"); Future.delayed(kHeartBeatFrequency, () async { // ignore: unawaited_futures _scheduleHeartBeat(prefs, isBackground); @@ -305,16 +304,11 @@ Future _scheduleFGSync(String caller) async { } void _scheduleBGTaskKill(String taskId) async { - _logger.info("debugBGTask : Checking if BG task should be killed"); if (await _isRunningInForeground()) { _logger.info("Found app in FG, committing seppuku. $taskId"); await _killBGTask(taskId); return; } - _logger.info( - "is running in foreground: ${await _isRunningInForeground()} *************", - ); - _logger.info("debugBGTask : isNotRunningInForeground *************"); Future.delayed(kHeartBeatFrequency, () async { _scheduleBGTaskKill(taskId); }); From 30464772e2a81d1ba76488d805e115da9252fd78 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 19:19:18 +0530 Subject: [PATCH 190/367] Inline --- web/apps/cast/src/utils/useCastReceiver.tsx | 33 ++++++--------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/web/apps/cast/src/utils/useCastReceiver.tsx b/web/apps/cast/src/utils/useCastReceiver.tsx index 26bc4a833..857c27386 100644 --- a/web/apps/cast/src/utils/useCastReceiver.tsx +++ b/web/apps/cast/src/utils/useCastReceiver.tsx @@ -5,37 +5,22 @@ type Receiver = { cast: typeof cast; }; -const load = (() => { - let promise: Promise | null = null; - - return () => { - if (promise === null) { - promise = new Promise((resolve) => { - const script = document.createElement("script"); - script.src = - "https://www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"; - - script.addEventListener("load", () => { - resolve({ - cast, - }); - }); - document.body.appendChild(script); - }); - } - return promise; - }; -})(); - export const useCastReceiver = () => { const [receiver, setReceiver] = useState({ cast: null, }); useEffect(() => { - load().then((receiver) => { - setReceiver(receiver); + const script = document.createElement("script"); + script.src = + "https://www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"; + + script.addEventListener("load", () => { + setReceiver({ + cast, + }); }); + document.body.appendChild(script); }, []); return receiver; From 8154dc4a7cf608aefe2547fcd2395f4b6ca8cdce Mon Sep 17 00:00:00 2001 From: ashilkn Date: Fri, 3 May 2024 19:21:56 +0530 Subject: [PATCH 191/367] [mob][photos] Remove logs added for debugging --- mobile/lib/main.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 67bcdfc21..52c9c715a 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -326,7 +326,6 @@ Future _isRunningInForeground() async { } Future _killBGTask([String? taskId]) async { - _logger.info("KillingBGTask taskId: $taskId ***************"); await UploadLocksDB.instance.releaseLocksAcquiredByOwnerBefore( ProcessType.background.toString(), DateTime.now().microsecondsSinceEpoch, @@ -341,7 +340,6 @@ Future _killBGTask([String? taskId]) async { ///Band aid for background process not getting killed. Should migrate to using ///workmanager instead of background_fetch. Isolate.current.kill(); - _logger.info('Kill BG task done *************'); } Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { From b7842765e883735f5b8c14eb2e86b741081946b3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 19:23:54 +0530 Subject: [PATCH 192/367] Document --- web/apps/cast/src/utils/useCastReceiver.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/web/apps/cast/src/utils/useCastReceiver.tsx b/web/apps/cast/src/utils/useCastReceiver.tsx index 857c27386..d8015befc 100644 --- a/web/apps/cast/src/utils/useCastReceiver.tsx +++ b/web/apps/cast/src/utils/useCastReceiver.tsx @@ -5,21 +5,19 @@ type Receiver = { cast: typeof cast; }; +/** + * Load the Chromecast Web Receiver SDK and return a reference to the `cast` + * global object that the SDK attaches to the window. + */ export const useCastReceiver = () => { - const [receiver, setReceiver] = useState({ - cast: null, - }); + const [receiver, setReceiver] = useState(); useEffect(() => { const script = document.createElement("script"); script.src = "https://www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"; - script.addEventListener("load", () => { - setReceiver({ - cast, - }); - }); + script.addEventListener("load", () => setReceiver({ cast })); document.body.appendChild(script); }, []); From 021ff4611c82870fee9eab8867bc81bb0d2459bd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 19:25:52 +0530 Subject: [PATCH 193/367] Remove indirection --- web/apps/cast/src/pages/index.tsx | 2 +- web/apps/cast/src/utils/useCastReceiver.tsx | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index b12bf1e76..59df251db 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -16,7 +16,7 @@ export default function PairingMode() { const [codePending, setCodePending] = useState(true); const [isCastReady, setIsCastReady] = useState(false); - const { cast } = useCastReceiver(); + const cast = useCastReceiver(); useEffect(() => { init(); diff --git a/web/apps/cast/src/utils/useCastReceiver.tsx b/web/apps/cast/src/utils/useCastReceiver.tsx index d8015befc..a2313fdd0 100644 --- a/web/apps/cast/src/utils/useCastReceiver.tsx +++ b/web/apps/cast/src/utils/useCastReceiver.tsx @@ -1,23 +1,21 @@ /// import { useEffect, useState } from "react"; -type Receiver = { - cast: typeof cast; -}; - /** * Load the Chromecast Web Receiver SDK and return a reference to the `cast` * global object that the SDK attaches to the window. + * + * https://developers.google.com/cast/docs/web_receiver/basic */ export const useCastReceiver = () => { - const [receiver, setReceiver] = useState(); + const [receiver, setReceiver] = useState(); useEffect(() => { const script = document.createElement("script"); script.src = "https://www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"; - script.addEventListener("load", () => setReceiver({ cast })); + script.addEventListener("load", () => setReceiver(cast)); document.body.appendChild(script); }, []); From b45262c75b7f846d1d96c6004296fd407f5462e8 Mon Sep 17 00:00:00 2001 From: Ashil <77285023+ashilkn@users.noreply.github.com> Date: Fri, 3 May 2024 19:57:01 +0530 Subject: [PATCH 194/367] [mob][photos] band aid fix for word gettng cut to next line in SelectionActionButton (#1607) --- .../bottom_action_bar/selection_action_button_widget.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mobile/lib/ui/components/bottom_action_bar/selection_action_button_widget.dart b/mobile/lib/ui/components/bottom_action_bar/selection_action_button_widget.dart index 60db98cf4..5ca6a25dc 100644 --- a/mobile/lib/ui/components/bottom_action_bar/selection_action_button_widget.dart +++ b/mobile/lib/ui/components/bottom_action_bar/selection_action_button_widget.dart @@ -132,14 +132,15 @@ class __BodyState extends State<_Body> { return maxWidth; } +//Todo: this doesn't give the correct width of the word, make it right double computeWidthOfWord(String text, TextStyle style) { final textPainter = TextPainter( text: TextSpan(text: text, style: style), maxLines: 1, textDirection: TextDirection.ltr, - textScaleFactor: MediaQuery.of(context).textScaleFactor, + textScaler: MediaQuery.textScalerOf(context), )..layout(); - - return textPainter.size.width; +//buffer of 8 added as width is shorter than actual text width + return textPainter.size.width + 8; } } From 791506b5102ee87db502c73ed06374068b991669 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta Date: Fri, 3 May 2024 20:14:21 +0530 Subject: [PATCH 195/367] [mobile][cast] Fix minor UI issues (#1588) ## Description ## Tests --- mobile/lib/ui/cast/auto.dart | 22 ++++++++++++------- .../gallery/gallery_app_bar_widget.dart | 22 ++++++++++++++----- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/mobile/lib/ui/cast/auto.dart b/mobile/lib/ui/cast/auto.dart index aed8ee0a5..7b310855e 100644 --- a/mobile/lib/ui/cast/auto.dart +++ b/mobile/lib/ui/cast/auto.dart @@ -49,7 +49,7 @@ class _AutoCastDialogState extends State { const SizedBox(height: 16), FutureBuilder>( future: castService.searchDevices(), - builder: (context, snapshot) { + builder: (_, snapshot) { if (snapshot.hasError) { return Center( child: Text( @@ -79,13 +79,20 @@ class _AutoCastDialogState extends State { }); try { await _connectToYourApp(context, device); + if (mounted) { + setState(() { + _isDeviceTapInProgress.remove(device); + }); + Navigator.of(context).pop(); + } } catch (e) { - showGenericErrorDialog(context: context, error: e) - .ignore(); - } finally { - setState(() { - _isDeviceTapInProgress.remove(device); - }); + if (mounted) { + setState(() { + _isDeviceTapInProgress.remove(device); + }); + showGenericErrorDialog(context: context, error: e) + .ignore(); + } } }, child: Padding( @@ -120,7 +127,6 @@ class _AutoCastDialogState extends State { if (message.containsKey(CastMessageType.pairCode)) { final code = message[CastMessageType.pairCode]!['code']; widget.onConnect(code); - Navigator.of(context).pop(); } }, ); diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index d7c3957b2..4a3d9450a 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -749,10 +749,10 @@ class _GalleryAppBarWidgetState extends State { await showDialog( context: context, barrierDismissible: true, - builder: (BuildContext context) { + builder: (BuildContext bContext) { return AutoCastDialog( (device) async { - await _castPair(gw, device); + await _castPair(bContext, gw, device); }, ); }, @@ -775,7 +775,7 @@ class _GalleryAppBarWidgetState extends State { alwaysShowSuccessState: false, initialValue: code, onSubmit: (String text) async { - final bool paired = await _castPair(gw, text); + final bool paired = await _castPair(context, gw, text); if (!paired) { Future.delayed(Duration.zero, () => _pairWithPin(gw, code)); } @@ -783,8 +783,15 @@ class _GalleryAppBarWidgetState extends State { ); } - Future _castPair(CastGateway gw, String code) async { + String lastCode = ''; + Future _castPair( + BuildContext bContext, CastGateway gw, String code) async { try { + if (lastCode == code) { + return false; + } + lastCode = code; + _logger.info("Casting album to device with code $code"); final String? publicKey = await gw.getPublicKey(code); if (publicKey == null) { showToast(context, S.of(context).deviceNotFound); @@ -794,15 +801,18 @@ class _GalleryAppBarWidgetState extends State { final String castToken = const Uuid().v4().toString(); final castPayload = CollectionsService.instance .getCastData(castToken, widget.collection!, publicKey); + _logger.info("Casting album with token $castToken"); await gw.publishCastPayload( code, castPayload, widget.collection!.id, castToken, ); - showToast(context, S.of(context).pairingComplete); + _logger.info("Casted album with token $castToken"); + // showToast(bContext, S.of(context).pairingComplete); return true; } catch (e, s) { + lastCode = ''; _logger.severe("Failed to cast album", e, s); if (e is CastIPMismatchException) { await showErrorDialog( @@ -811,7 +821,7 @@ class _GalleryAppBarWidgetState extends State { S.of(context).castIPMismatchBody, ); } else { - await showGenericErrorDialog(context: context, error: e); + await showGenericErrorDialog(context: bContext, error: e); } return false; } From ae2f939a4dc0e61814bd0f0405ed9c7c716b218d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 20:16:02 +0530 Subject: [PATCH 196/367] [mobile] New translations (#1538) New translations from [Crowdin](https://crowdin.com/project/ente-photos-app) Co-authored-by: Crowdin Bot --- mobile/lib/l10n/intl_pt.arb | 30 ++++++++++++++++++++++-------- mobile/lib/l10n/intl_zh.arb | 16 +++++++++++++++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/mobile/lib/l10n/intl_pt.arb b/mobile/lib/l10n/intl_pt.arb index 4185ea901..16a0ea753 100644 --- a/mobile/lib/l10n/intl_pt.arb +++ b/mobile/lib/l10n/intl_pt.arb @@ -47,7 +47,7 @@ "noRecoveryKey": "Nenhuma chave de recuperação?", "sorry": "Desculpe", "noRecoveryKeyNoDecryption": "Devido à natureza do nosso protocolo de criptografia de ponta a ponta, seus dados não podem ser descriptografados sem sua senha ou chave de recuperação", - "verifyEmail": "Verificar email", + "verifyEmail": "Verificar e-mail", "toResetVerifyEmail": "Para redefinir a sua senha, por favor verifique o seu email primeiro.", "checkInboxAndSpamFolder": "Verifique sua caixa de entrada (e ‘spam’) para concluir a verificação", "tapToEnterCode": "Toque para inserir código", @@ -156,7 +156,7 @@ "addANewEmail": "Adicionar um novo email", "orPickAnExistingOne": "Ou escolha um existente", "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": "Os colaboradores podem adicionar fotos e vídeos ao álbum compartilhado.", - "enterEmail": "Digite o email", + "enterEmail": "Insira o e-mail", "albumOwner": "Proprietário", "@albumOwner": { "description": "Role of the album owner" @@ -186,7 +186,7 @@ "passwordLock": "Bloqueio de senha", "disableDownloadWarningTitle": "Observe", "disableDownloadWarningBody": "Os espectadores ainda podem tirar screenshots ou salvar uma cópia de suas fotos usando ferramentas externas", - "allowDownloads": "Permitir transferências", + "allowDownloads": "Permitir downloads", "linkDeviceLimit": "Limite do dispositivo", "noDeviceLimit": "Nenhum", "@noDeviceLimit": { @@ -334,12 +334,12 @@ "removeParticipantBody": "{userEmail} será removido deste álbum compartilhado\n\nQuaisquer fotos adicionadas por eles também serão removidas do álbum", "keepPhotos": "Manter fotos", "deletePhotos": "Excluir fotos", - "inviteToEnte": "Convidar para o ente", + "inviteToEnte": "Convidar para o Ente", "removePublicLink": "Remover link público", "disableLinkMessage": "Isso removerá o link público para acessar \"{albumName}\".", "sharing": "Compartilhando...", "youCannotShareWithYourself": "Você não pode compartilhar consigo mesmo", - "archive": "Arquivado", + "archive": "Arquivar", "createAlbumActionHint": "Pressione e segure para selecionar fotos e clique em + para criar um álbum", "importing": "Importando....", "failedToLoadAlbums": "Falha ao carregar álbuns", @@ -353,7 +353,7 @@ "singleFileInBothLocalAndRemote": "Este {fileType} está tanto no Ente quanto no seu dispositivo.", "singleFileInRemoteOnly": "Este {fileType} será excluído do Ente.", "singleFileDeleteFromDevice": "Este {fileType} será excluído do seu dispositivo.", - "deleteFromEnte": "Excluir do ente", + "deleteFromEnte": "Excluir do Ente", "yesDelete": "Sim, excluir", "movedToTrash": "Movido para a lixeira", "deleteFromDevice": "Excluir do dispositivo", @@ -473,7 +473,7 @@ "ignoreUpdate": "Ignorar", "downloading": "Baixando...", "cannotDeleteSharedFiles": "Não é possível excluir arquivos compartilhados", - "theDownloadCouldNotBeCompleted": "Não foi possível concluir a transferência", + "theDownloadCouldNotBeCompleted": "Não foi possível concluir o download", "retry": "Tentar novamente", "backedUpFolders": "Backup de pastas concluído", "backup": "Backup", @@ -835,6 +835,7 @@ "close": "Fechar", "setAs": "Definir como", "fileSavedToGallery": "Vídeo salvo na galeria", + "filesSavedToGallery": "Arquivos salvos na galeria", "fileFailedToSaveToGallery": "Falha ao salvar o arquivo na galeria", "download": "Baixar", "pressAndHoldToPlayVideo": "Pressione e segure para reproduzir o vídeo", @@ -1195,6 +1196,8 @@ "verifyPasskey": "Verificar chave de acesso", "playOnTv": "Reproduzir álbum na TV", "pair": "Parear", + "autoPair": "Pareamento automático", + "pairWithPin": "Parear com PIN", "deviceNotFound": "Dispositivo não encontrado", "castInstruction": "Visite cast.ente.io no dispositivo que você deseja parear.\n\ndigite o código abaixo para reproduzir o álbum em sua TV.", "deviceCodeHint": "Insira o código", @@ -1212,5 +1215,16 @@ "endpointUpdatedMessage": "Endpoint atualizado com sucesso", "customEndpoint": "Conectado a {endpoint}", "createCollaborativeLink": "Criar link colaborativo", - "search": "Pesquisar" + "search": "Pesquisar", + "autoPairGoogle": "O Pareamento Automático requer a conexão com servidores do Google e só funciona com dispositivos Chromecast. O Google não receberá dados confidenciais, como suas fotos.", + "manualPairDesc": "Parear com o PIN funciona para qualquer dispositivo de tela grande onde você deseja reproduzir seu álbum.", + "connectToDevice": "Conectar ao dispositivo", + "autoCastDialogBody": "Você verá dispositivos disponíveis para transmitir aqui.", + "autoCastiOSPermission": "Certifique-se de que as permissões de Rede local estão ativadas para o aplicativo de Fotos Ente, em Configurações.", + "noDeviceFound": "Nenhum dispositivo encontrado", + "stopCastingTitle": "Parar transmissão", + "stopCastingBody": "Você quer parar a transmissão?", + "castIPMismatchTitle": "Falha ao transmitir álbum", + "castIPMismatchBody": "Certifique-se de estar na mesma rede que a TV.", + "pairingComplete": "Pareamento concluído" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_zh.arb b/mobile/lib/l10n/intl_zh.arb index 54fed47df..370bb6a3c 100644 --- a/mobile/lib/l10n/intl_zh.arb +++ b/mobile/lib/l10n/intl_zh.arb @@ -835,6 +835,7 @@ "close": "关闭", "setAs": "设置为", "fileSavedToGallery": "文件已保存到相册", + "filesSavedToGallery": "多个文件已保存到相册", "fileFailedToSaveToGallery": "无法将文件保存到相册", "download": "下载", "pressAndHoldToPlayVideo": "按住以播放视频", @@ -1195,6 +1196,8 @@ "verifyPasskey": "验证通行密钥", "playOnTv": "在电视上播放相册", "pair": "配对", + "autoPair": "自动配对", + "pairWithPin": "用 PIN 配对", "deviceNotFound": "未发现设备", "castInstruction": "在您要配对的设备上访问 cast.ente.io。\n输入下面的代码即可在电视上播放相册。", "deviceCodeHint": "输入代码", @@ -1212,5 +1215,16 @@ "endpointUpdatedMessage": "端点更新成功", "customEndpoint": "已连接至 {endpoint}", "createCollaborativeLink": "创建协作链接", - "search": "搜索" + "search": "搜索", + "autoPairGoogle": "自动配对需要连接到 Google 服务器,且仅适用于支持 Chromecast 的设备。Google 不会接收敏感数据,例如您的照片。", + "manualPairDesc": "用 PIN 配对适用于任何大屏幕设备,您可以在这些设备上播放您的相册。", + "connectToDevice": "连接到设备", + "autoCastDialogBody": "您将在此处看到可用的 Cast 设备。", + "autoCastiOSPermission": "请确保已在“设置”中为 Ente Photos 应用打开本地网络权限。", + "noDeviceFound": "未发现设备", + "stopCastingTitle": "停止投放", + "stopCastingBody": "您想停止投放吗?", + "castIPMismatchTitle": "投放相册失败", + "castIPMismatchBody": "请确保您的设备与电视处于同一网络。", + "pairingComplete": "配对完成" } \ No newline at end of file From 90a87a8e4f85fd8526b31ce276a545fbb365d5d3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 21:42:33 +0530 Subject: [PATCH 197/367] Pair 1 --- web/apps/cast/src/services/pair.ts | 103 ++++++++++++++++++++ web/apps/cast/src/utils/useCastReceiver.tsx | 4 +- 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 web/apps/cast/src/services/pair.ts diff --git a/web/apps/cast/src/services/pair.ts b/web/apps/cast/src/services/pair.ts new file mode 100644 index 000000000..02c1da6d6 --- /dev/null +++ b/web/apps/cast/src/services/pair.ts @@ -0,0 +1,103 @@ +import log from "@/next/log"; +import { toB64 } from "@ente/shared/crypto/internal/libsodium"; +import castGateway from "@ente/shared/network/cast"; +import { wait } from "@ente/shared/utils"; +import _sodium from "libsodium-wrappers"; +import { type Cast } from "../utils/useCastReceiver"; + +/** + * Listen for pairing requests using the given {@link cast} instance. + * + * [Note: Cast protocol] + * + * The Chromecast Framework (represented here by our handle to the Chromecast + * Web SDK, {@link cast}) itself is used for only the initial handshake, none of + * the data, even encrypted passes over it thereafter. + * + * The entire protocol is quite simple. + * + * 1. We (the receiver) generate a public/private keypair. and register the + * public part of it with museum. + * + * 2. Museum gives us a pairing "code" in lieu. + * + * 3. Listen for incoming messages over the Chromecast connection. + * + * 4. The client (our Web or mobile app) will connect using the "sender" + * Chromecast SDK. This will result in a bi-directional channel between us + * ("receiver") and the Ente client app ("sender"). + * + * 5. Thereafter, if at any time the sender disconnects, close the Chromecast + * context. This effectively shuts us down, causing the entire page to get + * reloaded. + * + * 6. After connecting, the sender sends an (empty) message. We reply by sending + * them a message containing the pairing code. This exchange is the only data + * that traverses over the Chromecast connection. + * + * 5. If at anytime the + * + * + * + * in our custom // "urn:x-cast:pair-request" namespace. over Chromecast + protocol is minimal: + * + * 1. Client (Web or mobile) sends an (empty) message in our custom // + "urn:x-cast:pair-request" namespace. + // + // 2. We reply with the device code. +*/ +export const listenForPairingRequest = async (cast: Cast) => { + // Generate keypair + const keypair = await generateKeyPair(); + const publicKeyB64 = await toB64(keypair.publicKey); + const privateKeyB64 = await toB64(keypair.privateKey); + + // Register keypair with museum to get a pairing code. + let code: string; + do { + try { + code = await castGateway.registerDevice(publicKeyB64); + } catch (e) { + log.error("Failed to register public key with server", e); + // Schedule retry after 10 seconds. + await wait(10000); + } + } while (code === undefined); + + // Listen for incoming messages sent via the Chromecast SDK + const context = cast.framework.CastReceiverContext.getInstance(); + const namespace = "urn:x-cast:pair-request"; + + const options = new cast.framework.CastReceiverOptions(); + // TODO(MR): Are any of these options required? + options.maxInactivity = 3600; + options.customNamespaces = Object.assign({}); + options.customNamespaces[namespace] = + cast.framework.system.MessageType.JSON; + options.disableIdleTimeout = true; + + // Reply with the code that we have if anyone asks over chromecast. + const incomingMessageListener = ({ senderId }: { senderId: string }) => + context.sendCustomMessage(namespace, senderId, { code }); + + context.addCustomMessageListener( + namespace, + // We need to cast, the `senderId` is present in the message we get but + // not present in the TypeScript type. + incomingMessageListener as unknown as SystemEventHandler, + ); + + // Shutdown ourselves if the "sender" disconnects. + context.addEventListener( + cast.framework.system.EventType.SENDER_DISCONNECTED, + () => context.stop(), + ); + + context.start(options); +}; + +const generateKeyPair = async () => { + await _sodium.ready; + return _sodium.crypto_box_keypair(); +}; diff --git a/web/apps/cast/src/utils/useCastReceiver.tsx b/web/apps/cast/src/utils/useCastReceiver.tsx index a2313fdd0..8cd958ec4 100644 --- a/web/apps/cast/src/utils/useCastReceiver.tsx +++ b/web/apps/cast/src/utils/useCastReceiver.tsx @@ -1,6 +1,8 @@ /// import { useEffect, useState } from "react"; +export type Cast = typeof cast; + /** * Load the Chromecast Web Receiver SDK and return a reference to the `cast` * global object that the SDK attaches to the window. @@ -8,7 +10,7 @@ import { useEffect, useState } from "react"; * https://developers.google.com/cast/docs/web_receiver/basic */ export const useCastReceiver = () => { - const [receiver, setReceiver] = useState(); + const [receiver, setReceiver] = useState(); useEffect(() => { const script = document.createElement("script"); From 5388112b4899674fe6bef1995ae6e4ccf69dea31 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 3 May 2024 21:57:30 +0530 Subject: [PATCH 198/367] Pair 2 --- web/apps/cast/src/services/pair.ts | 58 ++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/web/apps/cast/src/services/pair.ts b/web/apps/cast/src/services/pair.ts index 02c1da6d6..28c439d81 100644 --- a/web/apps/cast/src/services/pair.ts +++ b/web/apps/cast/src/services/pair.ts @@ -6,9 +6,11 @@ import _sodium from "libsodium-wrappers"; import { type Cast } from "../utils/useCastReceiver"; /** - * Listen for pairing requests using the given {@link cast} instance. + * Listen for pairing requests using the given {@link cast} instance. On + * successful pairing, return the payload (JSON) sent by the sender who + * connected to us. * - * [Note: Cast protocol] + * [Note: Pairing protocol] * * The Chromecast Framework (represented here by our handle to the Chromecast * Web SDK, {@link cast}) itself is used for only the initial handshake, none of @@ -35,19 +37,14 @@ import { type Cast } from "../utils/useCastReceiver"; * them a message containing the pairing code. This exchange is the only data * that traverses over the Chromecast connection. * - * 5. If at anytime the + * 5. In parallel, start polling museum to ask it if anyone has claimed that + * code we vended out and used that to send us an payload encrypted using our + * public key. This payload is a JSON object that contains the data we need + * to initiate a slideshow for a particular Ente collection. * - * - * - * in our custom // "urn:x-cast:pair-request" namespace. over Chromecast - protocol is minimal: - * - * 1. Client (Web or mobile) sends an (empty) message in our custom // - "urn:x-cast:pair-request" namespace. - // - // 2. We reply with the device code. -*/ -export const listenForPairingRequest = async (cast: Cast) => { + * 6. When that happens, decrypt that data with our private key, and return it. + */ +export const pair = async (cast: Cast) => { // Generate keypair const keypair = await generateKeyPair(); const publicKeyB64 = await toB64(keypair.publicKey); @@ -65,7 +62,7 @@ export const listenForPairingRequest = async (cast: Cast) => { } } while (code === undefined); - // Listen for incoming messages sent via the Chromecast SDK + // Prepare the Chromecast "context". const context = cast.framework.CastReceiverContext.getInstance(); const namespace = "urn:x-cast:pair-request"; @@ -94,7 +91,38 @@ export const listenForPairingRequest = async (cast: Cast) => { () => context.stop(), ); + // Start listening for Chromecast connections. context.start(options); + + // Start polling museum + let decryptedJSON: unknown | undefined + do { + // The client will send us the encrypted payload using our public key + // that we registered with museum. Then, we can decrypt this using the + // private key of the pair and return the plaintext payload, which'll be + // a JSON object containing all the data we need to play the collection + // slideshow. + let devicePayload = ""; + try { + const encDastData = await castGateway.getCastData(`${deviceCode}`); + if (!encDastData) return; + devicePayload = encDastData; + } catch (e) { + setCodePending(true); + init(); + return; + } + + const decryptedPayload = await boxSealOpen( + devicePayload, + publicKeyB64, + privateKeyB64, + ); + + const decryptedPayloadObj = JSON.parse(atob(decryptedPayload)); + + return decryptedPayloadObj; + }; }; const generateKeyPair = async () => { From a86a818924dd4beca057477eb503c5aabd98117f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 06:34:52 +0530 Subject: [PATCH 199/367] pp --- web/apps/cast/src/services/pair.ts | 38 +++++++++++++----------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/web/apps/cast/src/services/pair.ts b/web/apps/cast/src/services/pair.ts index 28c439d81..51bf9ba75 100644 --- a/web/apps/cast/src/services/pair.ts +++ b/web/apps/cast/src/services/pair.ts @@ -1,5 +1,5 @@ import log from "@/next/log"; -import { toB64 } from "@ente/shared/crypto/internal/libsodium"; +import { boxSealOpen, toB64 } from "@ente/shared/crypto/internal/libsodium"; import castGateway from "@ente/shared/network/cast"; import { wait } from "@ente/shared/utils"; import _sodium from "libsodium-wrappers"; @@ -45,7 +45,7 @@ import { type Cast } from "../utils/useCastReceiver"; * 6. When that happens, decrypt that data with our private key, and return it. */ export const pair = async (cast: Cast) => { - // Generate keypair + // Generate keypair. const keypair = await generateKeyPair(); const publicKeyB64 = await toB64(keypair.publicKey); const privateKeyB64 = await toB64(keypair.privateKey); @@ -86,6 +86,7 @@ export const pair = async (cast: Cast) => { ); // Shutdown ourselves if the "sender" disconnects. + // TODO(MR): Does it? context.addEventListener( cast.framework.system.EventType.SENDER_DISCONNECTED, () => context.stop(), @@ -95,34 +96,29 @@ export const pair = async (cast: Cast) => { context.start(options); // Start polling museum - let decryptedJSON: unknown | undefined + let encryptedCastData: string | undefined; do { // The client will send us the encrypted payload using our public key // that we registered with museum. Then, we can decrypt this using the // private key of the pair and return the plaintext payload, which'll be - // a JSON object containing all the data we need to play the collection - // slideshow. - let devicePayload = ""; + // a JSON object containing the data we need to start a slideshow for + // some collection. try { - const encDastData = await castGateway.getCastData(`${deviceCode}`); - if (!encDastData) return; - devicePayload = encDastData; + encryptedCastData = await castGateway.getCastData(code); } catch (e) { - setCodePending(true); - init(); - return; + log.error("Failed to get cast data from server", e); + // Schedule retry after 10 seconds. + await wait(5000); } + } while (encryptedCastData === undefined); - const decryptedPayload = await boxSealOpen( - devicePayload, - publicKeyB64, - privateKeyB64, - ); + const decryptedCastData = await boxSealOpen( + encryptedCastData, + publicKeyB64, + privateKeyB64, + ); - const decryptedPayloadObj = JSON.parse(atob(decryptedPayload)); - - return decryptedPayloadObj; - }; + return JSON.parse(atob(decryptedCastData)); }; const generateKeyPair = async () => { From afa8303d9167f929349ff07f56b615a06cbadd02 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 06:59:06 +0530 Subject: [PATCH 200/367] Split phases --- web/apps/cast/src/pages/index.tsx | 278 +++++++++--------- .../cast/src/services/{pair.ts => cast.ts} | 46 ++- 2 files changed, 169 insertions(+), 155 deletions(-) rename web/apps/cast/src/services/{pair.ts => cast.ts} (74%) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 59df251db..6a5afc2e1 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -1,176 +1,164 @@ -import log from "@/next/log"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; -import { boxSealOpen, toB64 } from "@ente/shared/crypto/internal/libsodium"; -import castGateway from "@ente/shared/network/cast"; import LargeType from "components/LargeType"; -import _sodium from "libsodium-wrappers"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; +import { pair, register, type Registration } from "services/cast"; import { storeCastData } from "services/cast/castService"; import { useCastReceiver } from "../utils/useCastReceiver"; export default function PairingMode() { + const [registration, setRegistration] = useState< + Registration | undefined + >(); const [deviceCode, setDeviceCode] = useState(""); - const [publicKeyB64, setPublicKeyB64] = useState(""); - const [privateKeyB64, setPrivateKeyB64] = useState(""); - const [codePending, setCodePending] = useState(true); - const [isCastReady, setIsCastReady] = useState(false); const cast = useCastReceiver(); - useEffect(() => { - init(); - }, []); + // useEffect(() => { + // init(); + // }, []); - const init = async () => { - try { - const keypair = await generateKeyPair(); - setPublicKeyB64(await toB64(keypair.publicKey)); - setPrivateKeyB64(await toB64(keypair.privateKey)); - } catch (e) { - log.error("failed to generate keypair", e); - throw e; - } - }; + // const init = async () => { + // try { + // const keypair = await generateKeyPair(); + // setPublicKeyB64(await toB64(keypair.publicKey)); + // setPrivateKeyB64(await toB64(keypair.privateKey)); + // } catch (e) { + // log.error("failed to generate keypair", e); + // throw e; + // } + // }; - useEffect(() => { - if (!cast) { - return; - } - if (isCastReady) { - return; - } - const context = cast.framework.CastReceiverContext.getInstance(); + // useEffect(() => { + // if (!cast) { + // return; + // } + // if (isCastReady) { + // return; + // } + // const context = cast.framework.CastReceiverContext.getInstance(); - try { - const options = new cast.framework.CastReceiverOptions(); - options.maxInactivity = 3600; - options.customNamespaces = Object.assign({}); - options.customNamespaces["urn:x-cast:pair-request"] = - cast.framework.system.MessageType.JSON; + // try { + // const options = new cast.framework.CastReceiverOptions(); + // options.maxInactivity = 3600; + // options.customNamespaces = Object.assign({}); + // options.customNamespaces["urn:x-cast:pair-request"] = + // cast.framework.system.MessageType.JSON; - options.disableIdleTimeout = true; - context.set; + // options.disableIdleTimeout = true; + // context.set; - context.addCustomMessageListener( - "urn:x-cast:pair-request", - messageReceiveHandler, - ); + // context.addCustomMessageListener( + // "urn:x-cast:pair-request", + // messageReceiveHandler, + // ); - // listen to close request and stop the context - context.addEventListener( - cast.framework.system.EventType.SENDER_DISCONNECTED, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - (_) => { - context.stop(); - }, - ); - context.start(options); - setIsCastReady(true); - } catch (e) { - log.error("failed to create cast context", e); - } + // // listen to close request and stop the context + // context.addEventListener( + // cast.framework.system.EventType.SENDER_DISCONNECTED, + // // eslint-disable-next-line @typescript-eslint/no-unused-vars + // (_) => { + // context.stop(); + // }, + // ); + // context.start(options); + // setIsCastReady(true); + // } catch (e) { + // log.error("failed to create cast context", e); + // } - return () => { - // context.stop(); - }; - }, [cast]); + // return () => { + // // context.stop(); + // }; + // }, [cast]); - const messageReceiveHandler = (message: { - type: string; - senderId: string; - data: any; - }) => { - try { - cast.framework.CastReceiverContext.getInstance().sendCustomMessage( - "urn:x-cast:pair-request", - message.senderId, - { - code: deviceCode, - }, - ); - } catch (e) { - log.error("failed to send message", e); - } - }; + // const messageReceiveHandler = (message: { + // type: string; + // senderId: string; + // data: any; + // }) => { + // try { + // cast.framework.CastReceiverContext.getInstance().sendCustomMessage( + // "urn:x-cast:pair-request", + // message.senderId, + // { + // code: deviceCode, + // }, + // ); + // } catch (e) { + // log.error("failed to send message", e); + // } + // }; - const generateKeyPair = async () => { - await _sodium.ready; - const keypair = _sodium.crypto_box_keypair(); - return keypair; - }; + // const generateKeyPair = async () => { + // await _sodium.ready; + // const keypair = _sodium.crypto_box_keypair(); + // return keypair; + // }; - const pollForCastData = async () => { - if (codePending) { - return; - } - // see if we were acknowledged on the client. - // the client will send us the encrypted payload using our public key that we advertised. - // then, we can decrypt this and store all the necessary info locally so we can play the collection slideshow. - let devicePayload = ""; - try { - const encDastData = await castGateway.getCastData(`${deviceCode}`); - if (!encDastData) return; - devicePayload = encDastData; - } catch (e) { - setCodePending(true); - init(); - return; - } + // const pollForCastData = async () => { + // if (codePending) { + // return; + // } + // // see if we were acknowledged on the client. + // // the client will send us the encrypted payload using our public key that we advertised. + // // then, we can decrypt this and store all the necessary info locally so we can play the collection slideshow. + // let devicePayload = ""; + // try { + // const encDastData = await castGateway.getCastData(`${deviceCode}`); + // if (!encDastData) return; + // devicePayload = encDastData; + // } catch (e) { + // setCodePending(true); + // init(); + // return; + // } - const decryptedPayload = await boxSealOpen( - devicePayload, - publicKeyB64, - privateKeyB64, - ); + // const decryptedPayload = await boxSealOpen( + // devicePayload, + // publicKeyB64, + // privateKeyB64, + // ); - const decryptedPayloadObj = JSON.parse(atob(decryptedPayload)); + // const decryptedPayloadObj = JSON.parse(atob(decryptedPayload)); - return decryptedPayloadObj; - }; + // return decryptedPayloadObj; + // }; - const advertisePublicKey = async (publicKeyB64: string) => { - // hey client, we exist! - try { - const codeValue = await castGateway.registerDevice(publicKeyB64); - setDeviceCode(codeValue); - setCodePending(false); - } catch (e) { - // schedule re-try after 5 seconds - setTimeout(() => { - init(); - }, 5000); - return; - } - }; + // const advertisePublicKey = async (publicKeyB64: string) => { + // // hey client, we exist! + // try { + // const codeValue = await castGateway.registerDevice(publicKeyB64); + // setDeviceCode(codeValue); + // setCodePending(false); + // } catch (e) { + // // schedule re-try after 5 seconds + // setTimeout(() => { + // init(); + // }, 5000); + // return; + // } + // }; const router = useRouter(); useEffect(() => { - console.log("useEffect for pairing called"); - if (deviceCode.length < 1 || !publicKeyB64 || !privateKeyB64) return; - - const interval = setInterval(async () => { - console.log("polling for cast data"); - const data = await pollForCastData(); - if (!data) { - console.log("no data"); - return; - } - storeCastData(data); - console.log("pushing slideshow"); - await router.push("/slideshow"); - }, 1000); - - return () => { - clearInterval(interval); - }; - }, [deviceCode, publicKeyB64, privateKeyB64, codePending]); + register().then((r) => setRegistration(r)); + }, []); useEffect(() => { - if (!publicKeyB64) return; - advertisePublicKey(publicKeyB64); - }, [publicKeyB64]); + if (!cast || !registration) return; + + pair(cast, registration).then((data) => { + storeCastData(data); + router.push("/slideshow"); + }); + }, [cast, registration]); + + // useEffect(() => { + // if (!publicKeyB64) return; + // advertisePublicKey(publicKeyB64); + // }, [publicKeyB64]); return ( <> @@ -204,12 +192,10 @@ export default function PairingMode() { overflow: "hidden", }} > - {codePending ? ( - + {deviceCode ? ( + ) : ( - <> - - + )}

{ +export const register = async (): Promise => { // Generate keypair. const keypair = await generateKeyPair(); const publicKeyB64 = await toB64(keypair.publicKey); const privateKeyB64 = await toB64(keypair.privateKey); // Register keypair with museum to get a pairing code. - let code: string; + let pairingCode: string; do { try { - code = await castGateway.registerDevice(publicKeyB64); + pairingCode = await castGateway.registerDevice(publicKeyB64); } catch (e) { log.error("Failed to register public key with server", e); // Schedule retry after 10 seconds. await wait(10000); } - } while (code === undefined); + } while (pairingCode === undefined); + + return { pairingCode, publicKeyB64, privateKeyB64 }; +}; + +/** + * Listen for pairing requests using the given {@link cast} instance for + * connections for {@link registration}. Phase 2 of the pairing protocol. + * + * On successful pairing, return the payload (JSON) sent by the sender who + * connected to us. See: [Note: Pairing protocol] + */ +export const pair = async (cast: Cast, registration: Registration) => { + const { pairingCode, publicKeyB64, privateKeyB64 } = registration; // Prepare the Chromecast "context". const context = cast.framework.CastReceiverContext.getInstance(); @@ -76,7 +104,7 @@ export const pair = async (cast: Cast) => { // Reply with the code that we have if anyone asks over chromecast. const incomingMessageListener = ({ senderId }: { senderId: string }) => - context.sendCustomMessage(namespace, senderId, { code }); + context.sendCustomMessage(namespace, senderId, { code: pairingCode }); context.addCustomMessageListener( namespace, @@ -104,7 +132,7 @@ export const pair = async (cast: Cast) => { // a JSON object containing the data we need to start a slideshow for // some collection. try { - encryptedCastData = await castGateway.getCastData(code); + encryptedCastData = await castGateway.getCastData(pairingCode); } catch (e) { log.error("Failed to get cast data from server", e); // Schedule retry after 10 seconds. From 5dc43521963ee61c7659dce0e3f06a65dc5160fd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 07:13:32 +0530 Subject: [PATCH 201/367] Ignore nulls --- web/apps/cast/src/pages/index.tsx | 1 + web/apps/cast/src/services/cast.ts | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 6a5afc2e1..76b0116b3 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -155,6 +155,7 @@ export default function PairingMode() { }); }, [cast, registration]); + console.log([cast, registration]); // useEffect(() => { // if (!publicKeyB64) return; // advertisePublicKey(publicKeyB64); diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index fe3d6059e..8c91bab3c 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -1,3 +1,7 @@ +/* eslint has already fixed this warning, we don't have the latest version yet + https://github.com/eslint/eslint/pull/18286 */ +/* eslint-disable no-constant-condition */ + import log from "@/next/log"; import { boxSealOpen, toB64 } from "@ente/shared/crypto/internal/libsodium"; import castGateway from "@ente/shared/network/cast"; @@ -67,15 +71,16 @@ export const register = async (): Promise => { // Register keypair with museum to get a pairing code. let pairingCode: string; - do { + while (true) { try { pairingCode = await castGateway.registerDevice(publicKeyB64); } catch (e) { log.error("Failed to register public key with server", e); - // Schedule retry after 10 seconds. - await wait(10000); } - } while (pairingCode === undefined); + if (pairingCode) break; + // Schedule retry after 10 seconds. + await wait(10000); + } return { pairingCode, publicKeyB64, privateKeyB64 }; }; @@ -124,8 +129,8 @@ export const pair = async (cast: Cast, registration: Registration) => { context.start(options); // Start polling museum - let encryptedCastData: string | undefined; - do { + let encryptedCastData: string | undefined | null; + while (true) { // The client will send us the encrypted payload using our public key // that we registered with museum. Then, we can decrypt this using the // private key of the pair and return the plaintext payload, which'll be @@ -135,10 +140,11 @@ export const pair = async (cast: Cast, registration: Registration) => { encryptedCastData = await castGateway.getCastData(pairingCode); } catch (e) { log.error("Failed to get cast data from server", e); - // Schedule retry after 10 seconds. - await wait(5000); } - } while (encryptedCastData === undefined); + if (encryptedCastData) break; + // Schedule retry after 10 seconds. + await wait(5000); + } const decryptedCastData = await boxSealOpen( encryptedCastData, From 159d207d1fe171ec97651ec245cf27520186962a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 07:21:50 +0530 Subject: [PATCH 202/367] Phases --- web/apps/cast/src/pages/index.tsx | 13 +++++---- web/apps/cast/src/services/cast.ts | 43 ++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 76b0116b3..595aed40f 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -10,9 +10,8 @@ export default function PairingMode() { const [registration, setRegistration] = useState< Registration | undefined >(); - const [deviceCode, setDeviceCode] = useState(""); - const cast = useCastReceiver(); + const router = useRouter(); // useEffect(() => { // init(); @@ -140,8 +139,6 @@ export default function PairingMode() { // } // }; - const router = useRouter(); - useEffect(() => { register().then((r) => setRegistration(r)); }, []); @@ -155,12 +152,14 @@ export default function PairingMode() { }); }, [cast, registration]); - console.log([cast, registration]); + // console.log([cast, registration]); // useEffect(() => { // if (!publicKeyB64) return; // advertisePublicKey(publicKeyB64); // }, [publicKeyB64]); + const { pairingCode } = registration ?? {}; + return ( <>

- {deviceCode ? ( - + {pairingCode ? ( + ) : ( )} diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 8c91bab3c..6ee9347e2 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -28,12 +28,22 @@ export interface Registration { * Web SDK, {@link cast}) itself is used for only the initial handshake, none of * the data, even encrypted passes over it thereafter. * - * The entire protocol is quite simple. + * The pairing happens in two phases: + * + * Phase 1 * * 1. We (the receiver) generate a public/private keypair. and register the * public part of it with museum. * - * 2. Museum gives us a pairing "code" in lieu. + * 2. Museum gives us a pairing "code" in lieu. Show this on the screen. + * + * Phase 2 + * + * There are two ways the client can connect - either by sending us a blank + * message over the Chromecast protocol (to which we'll reply with the pairing + * code), or by the user manually entering the pairing code on their screen. + * + * So there are two parallel processes. * * 3. Listen for incoming messages over the Chromecast connection. * @@ -49,19 +59,23 @@ export interface Registration { * them a message containing the pairing code. This exchange is the only data * that traverses over the Chromecast connection. * - * 5. In parallel, start polling museum to ask it if anyone has claimed that - * code we vended out and used that to send us an payload encrypted using our - * public key. This payload is a JSON object that contains the data we need - * to initiate a slideshow for a particular Ente collection. + * Once the client gets the pairing code (via Chromecast or manual entry), + * they'll let museum know. So * - * 6. When that happens, decrypt that data with our private key, and return it. + * 7. In parallel, keep polling museum to ask it if anyone has claimed that code + * we vended out and used that to send us an payload encrypted using our + * public key. * - * Steps 1 and 2 are done by the {@link register} function, which returns a - * {@link Registration}. + * 8. When that happens, decrypt that data with our private key, and return this + * payload. It is a JSON object that contains the data we need to initiate a + * slideshow for a particular Ente collection. + * + * Phase 1 (Steps 1 and 2) are done by the {@link register} function, which + * returns a {@link Registration}. * * At this time we start showing the pairing code on the UI, and proceed with - * the remaining steps using the {@link pair} function that returns the data we - * need to start the slideshow. + * the remaining steps (Phase 2) using the {@link pair} function that returns + * the data we need to start the slideshow. */ export const register = async (): Promise => { // Generate keypair. @@ -128,7 +142,7 @@ export const pair = async (cast: Cast, registration: Registration) => { // Start listening for Chromecast connections. context.start(options); - // Start polling museum + // Start polling museum. let encryptedCastData: string | undefined | null; while (true) { // The client will send us the encrypted payload using our public key @@ -142,8 +156,9 @@ export const pair = async (cast: Cast, registration: Registration) => { log.error("Failed to get cast data from server", e); } if (encryptedCastData) break; - // Schedule retry after 10 seconds. - await wait(5000); + // Nobody's claimed the code yet (or there was some error). Poll again + // after 2 seconds. + await wait(2000); } const decryptedCastData = await boxSealOpen( From 54bb32d5e77cda22287b223f030a6621767cb514 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 08:44:58 +0530 Subject: [PATCH 203/367] 3 phase --- web/apps/cast/src/pages/index.tsx | 183 +++++++---------------------- web/apps/cast/src/services/cast.ts | 92 ++++++++------- 2 files changed, 93 insertions(+), 182 deletions(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 595aed40f..88b7b5582 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -1,164 +1,65 @@ +import log from "@/next/log"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; import LargeType from "components/LargeType"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import { pair, register, type Registration } from "services/cast"; +import { advertiseCode, getCastData, register } from "services/cast"; import { storeCastData } from "services/cast/castService"; import { useCastReceiver } from "../utils/useCastReceiver"; export default function PairingMode() { - const [registration, setRegistration] = useState< - Registration | undefined - >(); + const [publicKeyB64, setPublicKeyB64] = useState(); + const [privateKeyB64, setPrivateKeyB64] = useState(); + const [pairingCode, setPairingCode] = useState(); + + // The returned cast object is a reference to a global instance and can be + // used in a useEffect dependency list. const cast = useCastReceiver(); + const router = useRouter(); - // useEffect(() => { - // init(); - // }, []); - - // const init = async () => { - // try { - // const keypair = await generateKeyPair(); - // setPublicKeyB64(await toB64(keypair.publicKey)); - // setPrivateKeyB64(await toB64(keypair.privateKey)); - // } catch (e) { - // log.error("failed to generate keypair", e); - // throw e; - // } - // }; - - // useEffect(() => { - // if (!cast) { - // return; - // } - // if (isCastReady) { - // return; - // } - // const context = cast.framework.CastReceiverContext.getInstance(); - - // try { - // const options = new cast.framework.CastReceiverOptions(); - // options.maxInactivity = 3600; - // options.customNamespaces = Object.assign({}); - // options.customNamespaces["urn:x-cast:pair-request"] = - // cast.framework.system.MessageType.JSON; - - // options.disableIdleTimeout = true; - // context.set; - - // context.addCustomMessageListener( - // "urn:x-cast:pair-request", - // messageReceiveHandler, - // ); - - // // listen to close request and stop the context - // context.addEventListener( - // cast.framework.system.EventType.SENDER_DISCONNECTED, - // // eslint-disable-next-line @typescript-eslint/no-unused-vars - // (_) => { - // context.stop(); - // }, - // ); - // context.start(options); - // setIsCastReady(true); - // } catch (e) { - // log.error("failed to create cast context", e); - // } - - // return () => { - // // context.stop(); - // }; - // }, [cast]); - - // const messageReceiveHandler = (message: { - // type: string; - // senderId: string; - // data: any; - // }) => { - // try { - // cast.framework.CastReceiverContext.getInstance().sendCustomMessage( - // "urn:x-cast:pair-request", - // message.senderId, - // { - // code: deviceCode, - // }, - // ); - // } catch (e) { - // log.error("failed to send message", e); - // } - // }; - - // const generateKeyPair = async () => { - // await _sodium.ready; - // const keypair = _sodium.crypto_box_keypair(); - // return keypair; - // }; - - // const pollForCastData = async () => { - // if (codePending) { - // return; - // } - // // see if we were acknowledged on the client. - // // the client will send us the encrypted payload using our public key that we advertised. - // // then, we can decrypt this and store all the necessary info locally so we can play the collection slideshow. - // let devicePayload = ""; - // try { - // const encDastData = await castGateway.getCastData(`${deviceCode}`); - // if (!encDastData) return; - // devicePayload = encDastData; - // } catch (e) { - // setCodePending(true); - // init(); - // return; - // } - - // const decryptedPayload = await boxSealOpen( - // devicePayload, - // publicKeyB64, - // privateKeyB64, - // ); - - // const decryptedPayloadObj = JSON.parse(atob(decryptedPayload)); - - // return decryptedPayloadObj; - // }; - - // const advertisePublicKey = async (publicKeyB64: string) => { - // // hey client, we exist! - // try { - // const codeValue = await castGateway.registerDevice(publicKeyB64); - // setDeviceCode(codeValue); - // setCodePending(false); - // } catch (e) { - // // schedule re-try after 5 seconds - // setTimeout(() => { - // init(); - // }, 5000); - // return; - // } - // }; + const init = () => { + register().then((r) => { + setPublicKeyB64(r.publicKeyB64); + setPrivateKeyB64(r.privateKeyB64); + setPairingCode(r.pairingCode); + }); + }; useEffect(() => { - register().then((r) => setRegistration(r)); + init(); }, []); useEffect(() => { - if (!cast || !registration) return; + if (cast) advertiseCode(cast, () => pairingCode); + }, [cast]); - pair(cast, registration).then((data) => { + const pollTick = async () => { + const registration = { publicKeyB64, privateKeyB64, pairingCode }; + try { + const data = await getCastData(registration); + if (!data) { + // No one has connected yet + return; + } + + log.info("Pairing complete"); storeCastData(data); - router.push("/slideshow"); - }); - }, [cast, registration]); + await router.push("/slideshow"); + } catch (e) { + console.log("Failed to get cast data", e); + // Start again from the beginning + setPairingCode(undefined); + init(); + } + }; - // console.log([cast, registration]); - // useEffect(() => { - // if (!publicKeyB64) return; - // advertisePublicKey(publicKeyB64); - // }, [publicKeyB64]); + useEffect(() => { + if (!publicKeyB64 || !privateKeyB64 || !pairingCode) return; - const { pairingCode } = registration ?? {}; + const interval = setInterval(pollTick, 2000); + return () => clearInterval(interval); + }, [publicKeyB64, privateKeyB64, pairingCode]); return ( <> diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 6ee9347e2..db41e707e 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -1,5 +1,5 @@ -/* eslint has already fixed this warning, we don't have the latest version yet - https://github.com/eslint/eslint/pull/18286 */ +// eslint has already fixed this warning, we don't have the latest version yet +// https://github.com/eslint/eslint/pull/18286 /* eslint-disable no-constant-condition */ import log from "@/next/log"; @@ -30,21 +30,19 @@ export interface Registration { * * The pairing happens in two phases: * - * Phase 1 + * Phase 1 - {@link register} * * 1. We (the receiver) generate a public/private keypair. and register the * public part of it with museum. * * 2. Museum gives us a pairing "code" in lieu. Show this on the screen. * - * Phase 2 + * Phase 2 - {@link advertiseCode} * * There are two ways the client can connect - either by sending us a blank * message over the Chromecast protocol (to which we'll reply with the pairing * code), or by the user manually entering the pairing code on their screen. * - * So there are two parallel processes. - * * 3. Listen for incoming messages over the Chromecast connection. * * 4. The client (our Web or mobile app) will connect using the "sender" @@ -60,11 +58,12 @@ export interface Registration { * that traverses over the Chromecast connection. * * Once the client gets the pairing code (via Chromecast or manual entry), - * they'll let museum know. So + * they'll let museum know. So in parallel with Phase 2, we perform Phase 3. * - * 7. In parallel, keep polling museum to ask it if anyone has claimed that code - * we vended out and used that to send us an payload encrypted using our - * public key. + * Phase 3 - {@link getCastData} in a setInterval. + * + * 7. Keep polling museum to ask it if anyone has claimed that code we vended + * out and used that to send us an payload encrypted using our public key. * * 8. When that happens, decrypt that data with our private key, and return this * payload. It is a JSON object that contains the data we need to initiate a @@ -73,9 +72,11 @@ export interface Registration { * Phase 1 (Steps 1 and 2) are done by the {@link register} function, which * returns a {@link Registration}. * - * At this time we start showing the pairing code on the UI, and proceed with - * the remaining steps (Phase 2) using the {@link pair} function that returns - * the data we need to start the slideshow. + * At this time we start showing the pairing code on the UI, and start phase 2, + * {@link advertiseCode} to vend out the pairing code to Chromecast connections. + * + * In parallel, we start Phase 3, calling {@link getCastData} in a loop. Once we + * get a response, we decrypt it to get the data we need to start the slideshow. */ export const register = async (): Promise => { // Generate keypair. @@ -100,15 +101,16 @@ export const register = async (): Promise => { }; /** - * Listen for pairing requests using the given {@link cast} instance for - * connections for {@link registration}. Phase 2 of the pairing protocol. + * Listen for incoming messages on the given {@link cast} receiver, replying to + * each of them with a pairing code obtained using the given {@link pairingCode} + * callback. Phase 2 of the pairing protocol. * - * On successful pairing, return the payload (JSON) sent by the sender who - * connected to us. See: [Note: Pairing protocol] + * See: [Note: Pairing protocol]. */ -export const pair = async (cast: Cast, registration: Registration) => { - const { pairingCode, publicKeyB64, privateKeyB64 } = registration; - +export const advertiseCode = ( + cast: Cast, + pairingCode: () => string | undefined, +) => { // Prepare the Chromecast "context". const context = cast.framework.CastReceiverContext.getInstance(); const namespace = "urn:x-cast:pair-request"; @@ -121,9 +123,18 @@ export const pair = async (cast: Cast, registration: Registration) => { cast.framework.system.MessageType.JSON; options.disableIdleTimeout = true; - // Reply with the code that we have if anyone asks over chromecast. - const incomingMessageListener = ({ senderId }: { senderId: string }) => - context.sendCustomMessage(namespace, senderId, { code: pairingCode }); + // Reply with the code that we have if anyone asks over Chromecast. + const incomingMessageListener = ({ senderId }: { senderId: string }) => { + const code = pairingCode(); + if (!code) { + log.warn( + "Ignoring incoming Chromecast message because we do not yet have a pairing code", + ); + return; + } + + context.sendCustomMessage(namespace, senderId, { code }); + }; context.addCustomMessageListener( namespace, @@ -141,26 +152,25 @@ export const pair = async (cast: Cast, registration: Registration) => { // Start listening for Chromecast connections. context.start(options); +}; - // Start polling museum. - let encryptedCastData: string | undefined | null; - while (true) { - // The client will send us the encrypted payload using our public key - // that we registered with museum. Then, we can decrypt this using the - // private key of the pair and return the plaintext payload, which'll be - // a JSON object containing the data we need to start a slideshow for - // some collection. - try { - encryptedCastData = await castGateway.getCastData(pairingCode); - } catch (e) { - log.error("Failed to get cast data from server", e); - } - if (encryptedCastData) break; - // Nobody's claimed the code yet (or there was some error). Poll again - // after 2 seconds. - await wait(2000); - } +/** + * Ask museum if anyone has sent a (encrypted) payload corresponding to the + * given pairing code. If so, decrypt it using our private key and return the + * JSON payload. Phase 3 of the pairing protocol. + * + * See: [Note: Pairing protocol]. + */ +export const getCastData = async (registration: Registration) => { + const { pairingCode, publicKeyB64, privateKeyB64 } = registration; + // The client will send us the encrypted payload using our public key that + // we registered with museum. + const encryptedCastData = await castGateway.getCastData(pairingCode); + + // Decrypt it using the private key of the pair and return the plaintext + // payload, which'll be a JSON object containing the data we need to start a + // slideshow for some collection. const decryptedCastData = await boxSealOpen( encryptedCastData, publicKeyB64, From 8a4f5e29343eed4f7e88d7d6b53c78e4b864f9f1 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 08:55:54 +0530 Subject: [PATCH 204/367] Fix --- web/apps/cast/src/services/cast.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index db41e707e..32e24a934 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -159,6 +159,8 @@ export const advertiseCode = ( * given pairing code. If so, decrypt it using our private key and return the * JSON payload. Phase 3 of the pairing protocol. * + * Returns `undefined` if there hasn't been any data obtained yet. + * * See: [Note: Pairing protocol]. */ export const getCastData = async (registration: Registration) => { @@ -167,6 +169,7 @@ export const getCastData = async (registration: Registration) => { // The client will send us the encrypted payload using our public key that // we registered with museum. const encryptedCastData = await castGateway.getCastData(pairingCode); + if (!encryptedCastData) return; // Decrypt it using the private key of the pair and return the plaintext // payload, which'll be a JSON object containing the data we need to start a From 0f64a506e5d10f45541636f6b01f2d2bfdc904dd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 08:59:49 +0530 Subject: [PATCH 205/367] Comments --- web/apps/cast/src/pages/index.tsx | 4 ++-- web/apps/cast/src/services/cast.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 88b7b5582..666cc1f0f 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -39,7 +39,7 @@ export default function PairingMode() { try { const data = await getCastData(registration); if (!data) { - // No one has connected yet + // No one has connected yet. return; } @@ -48,7 +48,7 @@ export default function PairingMode() { await router.push("/slideshow"); } catch (e) { console.log("Failed to get cast data", e); - // Start again from the beginning + // Start again from the beginning. setPairingCode(undefined); init(); } diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 32e24a934..f0bff7787 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -1,7 +1,3 @@ -// eslint has already fixed this warning, we don't have the latest version yet -// https://github.com/eslint/eslint/pull/18286 -/* eslint-disable no-constant-condition */ - import log from "@/next/log"; import { boxSealOpen, toB64 } from "@ente/shared/crypto/internal/libsodium"; import castGateway from "@ente/shared/network/cast"; @@ -86,6 +82,10 @@ export const register = async (): Promise => { // Register keypair with museum to get a pairing code. let pairingCode: string; + // eslint has fixed this spurious warning, but we're not on the latest + // version yet, so add a disable. + // https://github.com/eslint/eslint/pull/18286 + /* eslint-disable no-constant-condition */ while (true) { try { pairingCode = await castGateway.registerDevice(publicKeyB64); @@ -143,7 +143,7 @@ export const advertiseCode = ( incomingMessageListener as unknown as SystemEventHandler, ); - // Shutdown ourselves if the "sender" disconnects. + // Shutdown ourselves if the sender disconnects. // TODO(MR): Does it? context.addEventListener( cast.framework.system.EventType.SENDER_DISCONNECTED, From 6175c2617c99f5447e544c199e17e09a65d45f6c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 09:08:48 +0530 Subject: [PATCH 206/367] Rearrange --- .../{PhotoAuditorium.tsx => Slide.tsx} | 34 +++++++------------ web/apps/cast/src/pages/slideshow.tsx | 24 ++++++++----- 2 files changed, 28 insertions(+), 30 deletions(-) rename web/apps/cast/src/components/{PhotoAuditorium.tsx => Slide.tsx} (67%) diff --git a/web/apps/cast/src/components/PhotoAuditorium.tsx b/web/apps/cast/src/components/Slide.tsx similarity index 67% rename from web/apps/cast/src/components/PhotoAuditorium.tsx rename to web/apps/cast/src/components/Slide.tsx index c77c9e6ca..8309f8bc2 100644 --- a/web/apps/cast/src/components/PhotoAuditorium.tsx +++ b/web/apps/cast/src/components/Slide.tsx @@ -1,27 +1,17 @@ -import { useEffect } from "react"; - -interface PhotoAuditoriumProps { +interface SlideViewProps { + /** The URL of the image to show. */ url: string; - nextSlideUrl: string; - showNextSlide: () => void; + /** The URL of the next image that we will transition to. */ + nextURL: string; } -export const PhotoAuditorium: React.FC = ({ - url, - nextSlideUrl, - showNextSlide, -}) => { - useEffect(() => { - console.log("showing slide"); - const timeoutId = window.setTimeout(() => { - console.log("showing next slide timer"); - showNextSlide(); - }, 10000); - - return () => { - if (timeoutId) clearTimeout(timeoutId); - }; - }, []); +/** + * Show the image at {@link url} in a full screen view. + * + * Also show {@link nextURL} in an hidden image view to prepare the browser for + * an imminent transition to it. + */ +export const SlideView: React.FC = ({ url, nextURL }) => { return (
= ({ }} > { + if (loading) return; + + console.log("showing slide"); + const timeoutId = window.setTimeout(() => { + console.log("showing next slide timer"); + showNextSlide(); + }, 10000); + + return () => { + if (timeoutId) clearTimeout(timeoutId); + }; + }, [loading]); + if (loading) return ; - return ( - - ); + return ; } From 8bcbdeb6e2853c5eca160950423459ff1cfe0164 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 09:56:24 +0530 Subject: [PATCH 207/367] Rename --- web/apps/cast/src/pages/index.tsx | 2 +- web/apps/cast/src/services/{cast.ts => pair.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename web/apps/cast/src/services/{cast.ts => pair.ts} (100%) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 666cc1f0f..2e2297a75 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -3,7 +3,7 @@ import EnteSpinner from "@ente/shared/components/EnteSpinner"; import LargeType from "components/LargeType"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import { advertiseCode, getCastData, register } from "services/cast"; +import { advertiseCode, getCastData, register } from "services/pair"; import { storeCastData } from "services/cast/castService"; import { useCastReceiver } from "../utils/useCastReceiver"; diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/pair.ts similarity index 100% rename from web/apps/cast/src/services/cast.ts rename to web/apps/cast/src/services/pair.ts From 949dd22f81ffbc99af71c6288320dee658ae67cb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 10:01:55 +0530 Subject: [PATCH 208/367] Inline --- web/apps/cast/src/pages/index.tsx | 2 +- web/apps/cast/src/pages/slideshow.tsx | 4 +- .../services/{cast/castService.ts => cast.ts} | 228 +++++++++++++++++- .../cast/src/services/castDownloadManager.ts | 103 -------- web/apps/cast/src/utils/file.ts | 134 ---------- 5 files changed, 228 insertions(+), 243 deletions(-) rename web/apps/cast/src/services/{cast/castService.ts => cast.ts} (56%) delete mode 100644 web/apps/cast/src/services/castDownloadManager.ts delete mode 100644 web/apps/cast/src/utils/file.ts diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 2e2297a75..a9d581206 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -3,8 +3,8 @@ import EnteSpinner from "@ente/shared/components/EnteSpinner"; import LargeType from "components/LargeType"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; +import { storeCastData } from "services/cast"; import { advertiseCode, getCastData, register } from "services/pair"; -import { storeCastData } from "services/cast/castService"; import { useCastReceiver } from "../utils/useCastReceiver"; export default function PairingMode() { diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index e5e271a74..e00024d3f 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -9,11 +9,11 @@ import { useEffect, useState } from "react"; import { getCastCollection, getLocalFiles, + getPreviewableImage, syncPublicFiles, -} from "services/cast/castService"; +} from "services/cast"; import { Collection } from "types/collection"; import { EnteFile } from "types/file"; -import { getPreviewableImage } from "utils/file"; const renderableFileURLCache = new Map(); diff --git a/web/apps/cast/src/services/cast/castService.ts b/web/apps/cast/src/services/cast.ts similarity index 56% rename from web/apps/cast/src/services/cast/castService.ts rename to web/apps/cast/src/services/cast.ts index 84636d3a1..52821a1d1 100644 --- a/web/apps/cast/src/services/cast/castService.ts +++ b/web/apps/cast/src/services/cast.ts @@ -1,17 +1,25 @@ +import { FILE_TYPE } from "@/media/file-type"; +import { decodeLivePhoto } from "@/media/live-photo"; import log from "@/next/log"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint } from "@ente/shared/network/api"; +import { getCastFileURL, getEndpoint } from "@ente/shared/network/api"; import localForage from "@ente/shared/storage/localForage"; +import { detectMediaMIMEType } from "services/detect-type"; import { Collection, CollectionPublicMagicMetadata } from "types/collection"; -import { EncryptedEnteFile, EnteFile } from "types/file"; -import { decryptFile, mergeMetadata, sortFiles } from "utils/file"; +import { + EncryptedEnteFile, + EnteFile, + FileMagicMetadata, + FilePublicMagicMetadata, +} from "types/file"; export interface SavedCollectionFiles { collectionLocalID: string; files: EnteFile[]; } + const ENDPOINT = getEndpoint(); const COLLECTION_FILES_TABLE = "collection-files"; const COLLECTIONS_TABLE = "collections"; @@ -302,3 +310,217 @@ export const storeCastData = (payloadObj: Object) => { window.localStorage.setItem(key, payloadObj[key]); } }; + +export function sortFiles(files: EnteFile[], sortAsc = false) { + // sort based on the time of creation time of the file, + // for files with same creation time, sort based on the time of last modification + const factor = sortAsc ? -1 : 1; + return files.sort((a, b) => { + if (a.metadata.creationTime === b.metadata.creationTime) { + return ( + factor * + (b.metadata.modificationTime - a.metadata.modificationTime) + ); + } + return factor * (b.metadata.creationTime - a.metadata.creationTime); + }); +} + +export async function decryptFile( + file: EncryptedEnteFile, + collectionKey: string, +): Promise { + try { + const worker = await ComlinkCryptoWorker.getInstance(); + const { + encryptedKey, + keyDecryptionNonce, + metadata, + magicMetadata, + pubMagicMetadata, + ...restFileProps + } = file; + const fileKey = await worker.decryptB64( + encryptedKey, + keyDecryptionNonce, + collectionKey, + ); + const fileMetadata = await worker.decryptMetadata( + metadata.encryptedData, + metadata.decryptionHeader, + fileKey, + ); + let fileMagicMetadata: FileMagicMetadata; + let filePubMagicMetadata: FilePublicMagicMetadata; + if (magicMetadata?.data) { + fileMagicMetadata = { + ...file.magicMetadata, + data: await worker.decryptMetadata( + magicMetadata.data, + magicMetadata.header, + fileKey, + ), + }; + } + if (pubMagicMetadata?.data) { + filePubMagicMetadata = { + ...pubMagicMetadata, + data: await worker.decryptMetadata( + pubMagicMetadata.data, + pubMagicMetadata.header, + fileKey, + ), + }; + } + return { + ...restFileProps, + key: fileKey, + metadata: fileMetadata, + magicMetadata: fileMagicMetadata, + pubMagicMetadata: filePubMagicMetadata, + }; + } catch (e) { + log.error("file decryption failed", e); + throw e; + } +} + +export function generateStreamFromArrayBuffer(data: Uint8Array) { + return new ReadableStream({ + async start(controller: ReadableStreamDefaultController) { + controller.enqueue(data); + controller.close(); + }, + }); +} + +export function mergeMetadata(files: EnteFile[]): EnteFile[] { + return files.map((file) => { + if (file.pubMagicMetadata?.data.editedTime) { + file.metadata.creationTime = file.pubMagicMetadata.data.editedTime; + } + if (file.pubMagicMetadata?.data.editedName) { + file.metadata.title = file.pubMagicMetadata.data.editedName; + } + + return file; + }); +} + +export const getPreviewableImage = async ( + file: EnteFile, + castToken: string, +): Promise => { + try { + let fileBlob = await new Response( + await downloadFile(castToken, file), + ).blob(); + if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) { + const { imageData } = await decodeLivePhoto( + file.metadata.title, + fileBlob, + ); + fileBlob = new Blob([imageData]); + } + const mimeType = await detectMediaMIMEType( + new File([fileBlob], file.metadata.title), + ); + if (!mimeType) return undefined; + fileBlob = new Blob([fileBlob], { type: mimeType }); + return fileBlob; + } catch (e) { + log.error("failed to download file", e); + } +}; + +const downloadFile = async (castToken: string, file: EnteFile) => { + const cryptoWorker = await ComlinkCryptoWorker.getInstance(); + + if ( + file.metadata.fileType === FILE_TYPE.IMAGE || + file.metadata.fileType === FILE_TYPE.LIVE_PHOTO + ) { + const resp = await HTTPService.get( + getCastFileURL(file.id), + null, + { + "X-Cast-Access-Token": castToken, + }, + { responseType: "arraybuffer" }, + ); + if (typeof resp.data === "undefined") { + throw Error(CustomError.REQUEST_FAILED); + } + const decrypted = await cryptoWorker.decryptFile( + new Uint8Array(resp.data), + await cryptoWorker.fromB64(file.file.decryptionHeader), + file.key, + ); + return generateStreamFromArrayBuffer(decrypted); + } + const resp = await fetch(getCastFileURL(file.id), { + headers: { + "X-Cast-Access-Token": castToken, + }, + }); + const reader = resp.body.getReader(); + + const stream = new ReadableStream({ + async start(controller) { + const decryptionHeader = await cryptoWorker.fromB64( + file.file.decryptionHeader, + ); + const fileKey = await cryptoWorker.fromB64(file.key); + const { pullState, decryptionChunkSize } = + await cryptoWorker.initChunkDecryption( + decryptionHeader, + fileKey, + ); + let data = new Uint8Array(); + // The following function handles each data chunk + function push() { + // "done" is a Boolean and value a "Uint8Array" + reader.read().then(async ({ done, value }) => { + // Is there more data to read? + if (!done) { + const buffer = new Uint8Array( + data.byteLength + value.byteLength, + ); + buffer.set(new Uint8Array(data), 0); + buffer.set(new Uint8Array(value), data.byteLength); + if (buffer.length > decryptionChunkSize) { + const fileData = buffer.slice( + 0, + decryptionChunkSize, + ); + const { decryptedData } = + await cryptoWorker.decryptFileChunk( + fileData, + pullState, + ); + controller.enqueue(decryptedData); + data = buffer.slice(decryptionChunkSize); + } else { + data = buffer; + } + push(); + } else { + if (data) { + const { decryptedData } = + await cryptoWorker.decryptFileChunk( + data, + pullState, + ); + controller.enqueue(decryptedData); + data = null; + } + controller.close(); + } + }); + } + + push(); + }, + }); + return stream; +}; diff --git a/web/apps/cast/src/services/castDownloadManager.ts b/web/apps/cast/src/services/castDownloadManager.ts deleted file mode 100644 index 2314ed54e..000000000 --- a/web/apps/cast/src/services/castDownloadManager.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { FILE_TYPE } from "@/media/file-type"; -import ComlinkCryptoWorker from "@ente/shared/crypto"; -import { CustomError } from "@ente/shared/error"; -import HTTPService from "@ente/shared/network/HTTPService"; -import { getCastFileURL } from "@ente/shared/network/api"; -import { EnteFile } from "types/file"; -import { generateStreamFromArrayBuffer } from "utils/file"; - -class CastDownloadManager { - async downloadFile(castToken: string, file: EnteFile) { - const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - - if ( - file.metadata.fileType === FILE_TYPE.IMAGE || - file.metadata.fileType === FILE_TYPE.LIVE_PHOTO - ) { - const resp = await HTTPService.get( - getCastFileURL(file.id), - null, - { - "X-Cast-Access-Token": castToken, - }, - { responseType: "arraybuffer" }, - ); - if (typeof resp.data === "undefined") { - throw Error(CustomError.REQUEST_FAILED); - } - const decrypted = await cryptoWorker.decryptFile( - new Uint8Array(resp.data), - await cryptoWorker.fromB64(file.file.decryptionHeader), - file.key, - ); - return generateStreamFromArrayBuffer(decrypted); - } - const resp = await fetch(getCastFileURL(file.id), { - headers: { - "X-Cast-Access-Token": castToken, - }, - }); - const reader = resp.body.getReader(); - - const stream = new ReadableStream({ - async start(controller) { - const decryptionHeader = await cryptoWorker.fromB64( - file.file.decryptionHeader, - ); - const fileKey = await cryptoWorker.fromB64(file.key); - const { pullState, decryptionChunkSize } = - await cryptoWorker.initChunkDecryption( - decryptionHeader, - fileKey, - ); - let data = new Uint8Array(); - // The following function handles each data chunk - function push() { - // "done" is a Boolean and value a "Uint8Array" - reader.read().then(async ({ done, value }) => { - // Is there more data to read? - if (!done) { - const buffer = new Uint8Array( - data.byteLength + value.byteLength, - ); - buffer.set(new Uint8Array(data), 0); - buffer.set(new Uint8Array(value), data.byteLength); - if (buffer.length > decryptionChunkSize) { - const fileData = buffer.slice( - 0, - decryptionChunkSize, - ); - const { decryptedData } = - await cryptoWorker.decryptFileChunk( - fileData, - pullState, - ); - controller.enqueue(decryptedData); - data = buffer.slice(decryptionChunkSize); - } else { - data = buffer; - } - push(); - } else { - if (data) { - const { decryptedData } = - await cryptoWorker.decryptFileChunk( - data, - pullState, - ); - controller.enqueue(decryptedData); - data = null; - } - controller.close(); - } - }); - } - - push(); - }, - }); - return stream; - } -} - -export default new CastDownloadManager(); diff --git a/web/apps/cast/src/utils/file.ts b/web/apps/cast/src/utils/file.ts deleted file mode 100644 index 02c6b8134..000000000 --- a/web/apps/cast/src/utils/file.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { FILE_TYPE } from "@/media/file-type"; -import { decodeLivePhoto } from "@/media/live-photo"; -import log from "@/next/log"; -import ComlinkCryptoWorker from "@ente/shared/crypto"; -import CastDownloadManager from "services/castDownloadManager"; -import { detectMediaMIMEType } from "services/detect-type"; -import { - EncryptedEnteFile, - EnteFile, - FileMagicMetadata, - FilePublicMagicMetadata, -} from "types/file"; - -export function sortFiles(files: EnteFile[], sortAsc = false) { - // sort based on the time of creation time of the file, - // for files with same creation time, sort based on the time of last modification - const factor = sortAsc ? -1 : 1; - return files.sort((a, b) => { - if (a.metadata.creationTime === b.metadata.creationTime) { - return ( - factor * - (b.metadata.modificationTime - a.metadata.modificationTime) - ); - } - return factor * (b.metadata.creationTime - a.metadata.creationTime); - }); -} - -export async function decryptFile( - file: EncryptedEnteFile, - collectionKey: string, -): Promise { - try { - const worker = await ComlinkCryptoWorker.getInstance(); - const { - encryptedKey, - keyDecryptionNonce, - metadata, - magicMetadata, - pubMagicMetadata, - ...restFileProps - } = file; - const fileKey = await worker.decryptB64( - encryptedKey, - keyDecryptionNonce, - collectionKey, - ); - const fileMetadata = await worker.decryptMetadata( - metadata.encryptedData, - metadata.decryptionHeader, - fileKey, - ); - let fileMagicMetadata: FileMagicMetadata; - let filePubMagicMetadata: FilePublicMagicMetadata; - if (magicMetadata?.data) { - fileMagicMetadata = { - ...file.magicMetadata, - data: await worker.decryptMetadata( - magicMetadata.data, - magicMetadata.header, - fileKey, - ), - }; - } - if (pubMagicMetadata?.data) { - filePubMagicMetadata = { - ...pubMagicMetadata, - data: await worker.decryptMetadata( - pubMagicMetadata.data, - pubMagicMetadata.header, - fileKey, - ), - }; - } - return { - ...restFileProps, - key: fileKey, - metadata: fileMetadata, - magicMetadata: fileMagicMetadata, - pubMagicMetadata: filePubMagicMetadata, - }; - } catch (e) { - log.error("file decryption failed", e); - throw e; - } -} - -export function generateStreamFromArrayBuffer(data: Uint8Array) { - return new ReadableStream({ - async start(controller: ReadableStreamDefaultController) { - controller.enqueue(data); - controller.close(); - }, - }); -} - -export function mergeMetadata(files: EnteFile[]): EnteFile[] { - return files.map((file) => { - if (file.pubMagicMetadata?.data.editedTime) { - file.metadata.creationTime = file.pubMagicMetadata.data.editedTime; - } - if (file.pubMagicMetadata?.data.editedName) { - file.metadata.title = file.pubMagicMetadata.data.editedName; - } - - return file; - }); -} - -export const getPreviewableImage = async ( - file: EnteFile, - castToken: string, -): Promise => { - try { - let fileBlob = await new Response( - await CastDownloadManager.downloadFile(castToken, file), - ).blob(); - if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) { - const { imageData } = await decodeLivePhoto( - file.metadata.title, - fileBlob, - ); - fileBlob = new Blob([imageData]); - } - const mimeType = await detectMediaMIMEType( - new File([fileBlob], file.metadata.title), - ); - if (!mimeType) return undefined; - fileBlob = new Blob([fileBlob], { type: mimeType }); - return fileBlob; - } catch (e) { - log.error("failed to download file", e); - } -}; From 75a9abab0fe80a0d54fb4e9d2755663c425876dd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 10:11:59 +0530 Subject: [PATCH 209/367] Inline --- web/apps/cast/src/pages/slideshow.tsx | 18 +--- web/apps/cast/src/services/cast.ts | 123 ++++++++------------------ 2 files changed, 37 insertions(+), 104 deletions(-) diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index e00024d3f..c3bd59a73 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -1,6 +1,3 @@ -import { FILE_TYPE } from "@/media/file-type"; -import { isNonWebImageFileExtension } from "@/media/formats"; -import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; import PairedSuccessfullyOverlay from "components/PairedSuccessfullyOverlay"; import { SlideView } from "components/Slide"; @@ -10,6 +7,7 @@ import { getCastCollection, getLocalFiles, getPreviewableImage, + isFileEligibleForCast, syncPublicFiles, } from "services/cast"; import { Collection } from "types/collection"; @@ -69,20 +67,6 @@ export default function Slideshow() { } }, [castToken]); - const isFileEligibleForCast = (file: EnteFile) => { - const fileType = file.metadata.fileType; - if (fileType !== FILE_TYPE.IMAGE && fileType !== FILE_TYPE.LIVE_PHOTO) - return false; - - if (file.info.fileSize > 100 * 1024 * 1024) return false; - - const [, extension] = nameAndExtension(file.metadata.title); - - if (isNonWebImageFileExtension(extension)) return false; - - return true; - }; - useEffect(() => { try { const castToken = window.localStorage.getItem("castToken"); diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 52821a1d1..a705cea9b 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -1,5 +1,7 @@ import { FILE_TYPE } from "@/media/file-type"; +import { isNonWebImageFileExtension } from "@/media/formats"; import { decodeLivePhoto } from "@/media/live-photo"; +import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; @@ -433,94 +435,41 @@ export const getPreviewableImage = async ( } }; -const downloadFile = async (castToken: string, file: EnteFile) => { - const cryptoWorker = await ComlinkCryptoWorker.getInstance(); +export const isFileEligibleForCast = (file: EnteFile) => { + if (!isImageOrLivePhoto(file)) return false; + if (file.info.fileSize > 100 * 1024 * 1024) return false; - if ( - file.metadata.fileType === FILE_TYPE.IMAGE || - file.metadata.fileType === FILE_TYPE.LIVE_PHOTO - ) { - const resp = await HTTPService.get( - getCastFileURL(file.id), - null, - { - "X-Cast-Access-Token": castToken, - }, - { responseType: "arraybuffer" }, - ); - if (typeof resp.data === "undefined") { - throw Error(CustomError.REQUEST_FAILED); - } - const decrypted = await cryptoWorker.decryptFile( - new Uint8Array(resp.data), - await cryptoWorker.fromB64(file.file.decryptionHeader), - file.key, - ); - return generateStreamFromArrayBuffer(decrypted); - } - const resp = await fetch(getCastFileURL(file.id), { - headers: { + const [, extension] = nameAndExtension(file.metadata.title); + if (isNonWebImageFileExtension(extension)) return false; + + return true; +}; + +const isImageOrLivePhoto = (file: EnteFile) => { + const fileType = file.metadata.fileType; + return fileType == FILE_TYPE.IMAGE || fileType == FILE_TYPE.LIVE_PHOTO; +}; + +const downloadFile = async (castToken: string, file: EnteFile) => { + if (!isImageOrLivePhoto(file)) + throw new Error("Can only cast images and live photos"); + + const cryptoWorker = await ComlinkCryptoWorker.getInstance(); + const resp = await HTTPService.get( + getCastFileURL(file.id), + null, + { "X-Cast-Access-Token": castToken, }, - }); - const reader = resp.body.getReader(); - - const stream = new ReadableStream({ - async start(controller) { - const decryptionHeader = await cryptoWorker.fromB64( - file.file.decryptionHeader, - ); - const fileKey = await cryptoWorker.fromB64(file.key); - const { pullState, decryptionChunkSize } = - await cryptoWorker.initChunkDecryption( - decryptionHeader, - fileKey, - ); - let data = new Uint8Array(); - // The following function handles each data chunk - function push() { - // "done" is a Boolean and value a "Uint8Array" - reader.read().then(async ({ done, value }) => { - // Is there more data to read? - if (!done) { - const buffer = new Uint8Array( - data.byteLength + value.byteLength, - ); - buffer.set(new Uint8Array(data), 0); - buffer.set(new Uint8Array(value), data.byteLength); - if (buffer.length > decryptionChunkSize) { - const fileData = buffer.slice( - 0, - decryptionChunkSize, - ); - const { decryptedData } = - await cryptoWorker.decryptFileChunk( - fileData, - pullState, - ); - controller.enqueue(decryptedData); - data = buffer.slice(decryptionChunkSize); - } else { - data = buffer; - } - push(); - } else { - if (data) { - const { decryptedData } = - await cryptoWorker.decryptFileChunk( - data, - pullState, - ); - controller.enqueue(decryptedData); - data = null; - } - controller.close(); - } - }); - } - - push(); - }, - }); - return stream; + { responseType: "arraybuffer" }, + ); + if (typeof resp.data === "undefined") { + throw Error(CustomError.REQUEST_FAILED); + } + const decrypted = await cryptoWorker.decryptFile( + new Uint8Array(resp.data), + await cryptoWorker.fromB64(file.file.decryptionHeader), + file.key, + ); + return generateStreamFromArrayBuffer(decrypted); }; From 4b674c22c128dfb84e9164ed750f6097995af110 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 10:15:31 +0530 Subject: [PATCH 210/367] Inline --- web/apps/cast/src/services/cast.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index a705cea9b..85bdb67fd 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -387,15 +387,6 @@ export async function decryptFile( } } -export function generateStreamFromArrayBuffer(data: Uint8Array) { - return new ReadableStream({ - async start(controller: ReadableStreamDefaultController) { - controller.enqueue(data); - controller.close(); - }, - }); -} - export function mergeMetadata(files: EnteFile[]): EnteFile[] { return files.map((file) => { if (file.pubMagicMetadata?.data.editedTime) { @@ -454,18 +445,18 @@ const downloadFile = async (castToken: string, file: EnteFile) => { if (!isImageOrLivePhoto(file)) throw new Error("Can only cast images and live photos"); - const cryptoWorker = await ComlinkCryptoWorker.getInstance(); + const url = getCastFileURL(file.id); const resp = await HTTPService.get( - getCastFileURL(file.id), + url, null, { "X-Cast-Access-Token": castToken, }, { responseType: "arraybuffer" }, ); - if (typeof resp.data === "undefined") { - throw Error(CustomError.REQUEST_FAILED); - } + if (resp.data === undefined) throw new Error(`Failed to get ${url}`); + + const cryptoWorker = await ComlinkCryptoWorker.getInstance(); const decrypted = await cryptoWorker.decryptFile( new Uint8Array(resp.data), await cryptoWorker.fromB64(file.file.decryptionHeader), @@ -473,3 +464,12 @@ const downloadFile = async (castToken: string, file: EnteFile) => { ); return generateStreamFromArrayBuffer(decrypted); }; + +function generateStreamFromArrayBuffer(data: Uint8Array) { + return new ReadableStream({ + async start(controller: ReadableStreamDefaultController) { + controller.enqueue(data); + controller.close(); + }, + }); +} From 3ca1bd7729abc606d687a1141892bac2b1b3881d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 10:17:09 +0530 Subject: [PATCH 211/367] Fuse --- web/apps/cast/src/services/cast.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 85bdb67fd..1a7f32dbc 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -405,9 +405,7 @@ export const getPreviewableImage = async ( castToken: string, ): Promise => { try { - let fileBlob = await new Response( - await downloadFile(castToken, file), - ).blob(); + let fileBlob = await downloadFile(castToken, file); if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) { const { imageData } = await decodeLivePhoto( file.metadata.title, @@ -462,14 +460,5 @@ const downloadFile = async (castToken: string, file: EnteFile) => { await cryptoWorker.fromB64(file.file.decryptionHeader), file.key, ); - return generateStreamFromArrayBuffer(decrypted); + return new Response(decrypted).blob(); }; - -function generateStreamFromArrayBuffer(data: Uint8Array) { - return new ReadableStream({ - async start(controller: ReadableStreamDefaultController) { - controller.enqueue(data); - controller.close(); - }, - }); -} From a058897c488607fa88591f5cbbde44f469f27805 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 10:24:37 +0530 Subject: [PATCH 212/367] Simplify --- web/apps/cast/src/pages/slideshow.tsx | 57 ++++++++------------------- web/apps/cast/src/services/cast.ts | 10 +++++ 2 files changed, 26 insertions(+), 41 deletions(-) diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index c3bd59a73..fb7f95200 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -4,17 +4,15 @@ import { SlideView } from "components/Slide"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { + createRenderableURL, getCastCollection, getLocalFiles, - getPreviewableImage, isFileEligibleForCast, syncPublicFiles, } from "services/cast"; import { Collection } from "types/collection"; import { EnteFile } from "types/file"; -const renderableFileURLCache = new Map(); - export default function Slideshow() { const [loading, setLoading] = useState(true); const [castToken, setCastToken] = useState(""); @@ -114,46 +112,23 @@ export default function Slideshow() { const nextFile = collectionFiles[nextIndex]; const nextNextFile = collectionFiles[nextNextIndex]; - let nextURL = renderableFileURLCache.get(nextFile.id); - let nextNextURL = renderableFileURLCache.get(nextNextFile.id); - - if (!nextURL) { - try { - console.log("nextURL doesn't exist yet"); - const blob = await getPreviewableImage(nextFile, castToken); - console.log("nextURL blobread"); - const url = URL.createObjectURL(blob); - console.log("nextURL", url); - renderableFileURLCache.set(nextFile.id, url); - console.log("nextUrlCache set"); - nextURL = url; - } catch (e) { - console.log("error in nextUrl", e); - return; - } - } else { - console.log("nextURL already exists"); + let nextURL: string + try { + nextURL = await createRenderableURL(nextFile, castToken); + } catch (e) { + console.log("error in nextUrl", e); + return; } - if (!nextNextURL) { - try { - console.log("nextNextURL doesn't exist yet"); - const blob = await getPreviewableImage( - nextNextFile, - castToken, - ); - console.log("nextNextURL blobread"); - const url = URL.createObjectURL(blob); - console.log("nextNextURL", url); - renderableFileURLCache.set(nextNextFile.id, url); - console.log("nextNextURCacheL set"); - nextNextURL = url; - } catch (e) { - console.log("error in nextNextURL", e); - return; - } - } else { - console.log("nextNextURL already exists"); + let nextNextURL: string + try { + nextNextURL = await createRenderableURL( + nextNextFile, + castToken, + ); + } catch (e) { + console.log("error in nextNextURL", e); + return; } setLoading(false); diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 1a7f32dbc..59b92eba8 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -400,6 +400,16 @@ export function mergeMetadata(files: EnteFile[]): EnteFile[] { }); } +/** + * Create and return a new data URL that can be used to show the given + * {@link file} in our slideshow image viewer. + * + * Once we're done showing the file, the URL should be revoked using + * {@link URL.revokeObjectURL} to free up browser resources. + */ +export const createRenderableURL = async (file: EnteFile, castToken: string) => + URL.createObjectURL(await getPreviewableImage(file, castToken)); + export const getPreviewableImage = async ( file: EnteFile, castToken: string, From 2ca88ef0a37ee6d33bdb414b84f1822f21be3826 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 10:26:52 +0530 Subject: [PATCH 213/367] Placeholder --- web/apps/cast/src/pages/slideshow.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index fb7f95200..60e07b725 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -112,7 +112,7 @@ export default function Slideshow() { const nextFile = collectionFiles[nextIndex]; const nextNextFile = collectionFiles[nextNextIndex]; - let nextURL: string + let nextURL: string; try { nextURL = await createRenderableURL(nextFile, castToken); } catch (e) { @@ -120,7 +120,7 @@ export default function Slideshow() { return; } - let nextNextURL: string + let nextNextURL: string; try { nextNextURL = await createRenderableURL( nextNextFile, @@ -133,7 +133,11 @@ export default function Slideshow() { setLoading(false); setCurrentFileId(nextFile.id); + // TODO: These might be the same in case the album has < 3 files + // so commenting this out for now. + // if (currentFileURL) URL.revokeObjectURL(currentFileURL); setCurrentFileURL(nextURL); + // if (nextFileURL) URL.revokeObjectURL(nextFileURL); setNextFileURL(nextNextURL); } catch (e) { console.log("error in showNextSlide", e); From 1dafec15c7716bacc8b56fadba954a6e0d08d4b3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 10:31:59 +0530 Subject: [PATCH 214/367] Unused --- web/apps/photos/src/utils/file/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 862f7b7f2..bb212ff21 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -340,10 +340,6 @@ const nativeConvertToJPEG = async (imageBlob: Blob) => { return new Blob([jpegData]); }; -export function isRawFile(exactType: string) { - return RAW_FORMATS.includes(exactType.toLowerCase()); -} - export function isSupportedRawFormat(exactType: string) { return SUPPORTED_RAW_FORMATS.includes(exactType.toLowerCase()); } From 3c3955017e065005cec9ca9d6d422ba064b58d65 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 10:49:33 +0530 Subject: [PATCH 215/367] Start chromecast SDK only once --- web/apps/cast/src/pages/index.tsx | 12 ++++---- web/apps/cast/src/services/pair.ts | 2 +- web/apps/cast/src/utils/cast-receiver.tsx | 32 +++++++++++++++++++++ web/apps/cast/src/utils/useCastReceiver.tsx | 25 ---------------- 4 files changed, 38 insertions(+), 33 deletions(-) create mode 100644 web/apps/cast/src/utils/cast-receiver.tsx delete mode 100644 web/apps/cast/src/utils/useCastReceiver.tsx diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index a9d581206..87ce756e0 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -5,17 +5,13 @@ import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { storeCastData } from "services/cast"; import { advertiseCode, getCastData, register } from "services/pair"; -import { useCastReceiver } from "../utils/useCastReceiver"; +import { castReceiverLoadingIfNeeded } from "../utils/cast-receiver"; export default function PairingMode() { const [publicKeyB64, setPublicKeyB64] = useState(); const [privateKeyB64, setPrivateKeyB64] = useState(); const [pairingCode, setPairingCode] = useState(); - // The returned cast object is a reference to a global instance and can be - // used in a useEffect dependency list. - const cast = useCastReceiver(); - const router = useRouter(); const init = () => { @@ -31,8 +27,10 @@ export default function PairingMode() { }, []); useEffect(() => { - if (cast) advertiseCode(cast, () => pairingCode); - }, [cast]); + castReceiverLoadingIfNeeded().then((cast) => + advertiseCode(cast, () => pairingCode), + ); + }, []); const pollTick = async () => { const registration = { publicKeyB64, privateKeyB64, pairingCode }; diff --git a/web/apps/cast/src/services/pair.ts b/web/apps/cast/src/services/pair.ts index f0bff7787..31419927d 100644 --- a/web/apps/cast/src/services/pair.ts +++ b/web/apps/cast/src/services/pair.ts @@ -3,7 +3,7 @@ import { boxSealOpen, toB64 } from "@ente/shared/crypto/internal/libsodium"; import castGateway from "@ente/shared/network/cast"; import { wait } from "@ente/shared/utils"; import _sodium from "libsodium-wrappers"; -import { type Cast } from "../utils/useCastReceiver"; +import { type Cast } from "../utils/cast-receiver"; export interface Registration { /** A pairing code shown on the screen. A client can use this to connect. */ diff --git a/web/apps/cast/src/utils/cast-receiver.tsx b/web/apps/cast/src/utils/cast-receiver.tsx new file mode 100644 index 000000000..666a085ed --- /dev/null +++ b/web/apps/cast/src/utils/cast-receiver.tsx @@ -0,0 +1,32 @@ +/// + +export type Cast = typeof cast; + +let _cast: Cast | undefined; +let _loader: Promise | undefined; + +/** + * Load the Chromecast Web Receiver SDK and return a reference to the `cast` + * global object that the SDK attaches to the window. + * + * Calling this function multiple times is fine, once the Chromecast SDK is + * loaded it'll thereafter return the reference to the same object always. + * + * https://developers.google.com/cast/docs/web_receiver/basic + */ +export const castReceiverLoadingIfNeeded = async (): Promise => { + if (_cast) return _cast; + if (_loader) return await _loader; + + _loader = new Promise((resolve) => { + const script = document.createElement("script"); + script.src = + "https://www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"; + + script.addEventListener("load", () => resolve(cast)); + document.body.appendChild(script); + }); + const c = await _loader; + _cast = c; + return c; +}; diff --git a/web/apps/cast/src/utils/useCastReceiver.tsx b/web/apps/cast/src/utils/useCastReceiver.tsx deleted file mode 100644 index 8cd958ec4..000000000 --- a/web/apps/cast/src/utils/useCastReceiver.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/// -import { useEffect, useState } from "react"; - -export type Cast = typeof cast; - -/** - * Load the Chromecast Web Receiver SDK and return a reference to the `cast` - * global object that the SDK attaches to the window. - * - * https://developers.google.com/cast/docs/web_receiver/basic - */ -export const useCastReceiver = () => { - const [receiver, setReceiver] = useState(); - - useEffect(() => { - const script = document.createElement("script"); - script.src = - "https://www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"; - - script.addEventListener("load", () => setReceiver(cast)); - document.body.appendChild(script); - }, []); - - return receiver; -}; From 3d8d6e9fac288a1dc8130c8633ced8aaf13b702c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 10:52:12 +0530 Subject: [PATCH 216/367] Remove unused remove code --- web/apps/cast/src/services/cast.ts | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 59b92eba8..fb19d2bfd 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -278,34 +278,6 @@ export const getCastCollection = async ( } }; -export const removeCollection = async ( - collectionUID: string, - collectionKey: string, -) => { - const collections = - (await localForage.getItem(COLLECTIONS_TABLE)) || []; - await localForage.setItem( - COLLECTIONS_TABLE, - collections.filter((collection) => collection.key !== collectionKey), - ); - await removeCollectionFiles(collectionUID); -}; - -export const removeCollectionFiles = async (collectionUID: string) => { - await localForage.removeItem(getLastSyncKey(collectionUID)); - const collectionFiles = - (await localForage.getItem( - COLLECTION_FILES_TABLE, - )) ?? []; - await localForage.setItem( - COLLECTION_FILES_TABLE, - collectionFiles.filter( - (collectionFiles) => - collectionFiles.collectionLocalID !== collectionUID, - ), - ); -}; - export const storeCastData = (payloadObj: Object) => { // iterate through all the keys in the payload object and set them in localStorage. for (const key in payloadObj) { From c2a237051015304673519b310e191bc8000b4c0f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 11:11:49 +0530 Subject: [PATCH 217/367] Mirror --- web/apps/cast/src/pages/slideshow.tsx | 8 ++--- web/apps/cast/src/services/cast.ts | 45 ++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index 60e07b725..85cf7ad23 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -8,6 +8,7 @@ import { getCastCollection, getLocalFiles, isFileEligibleForCast, + readCastData, syncPublicFiles, } from "services/cast"; import { Collection } from "types/collection"; @@ -28,13 +29,10 @@ export default function Slideshow() { const syncCastFiles = async (token: string) => { try { - console.log("syncCastFiles"); - const castToken = window.localStorage.getItem("castToken"); - const requestedCollectionKey = - window.localStorage.getItem("collectionKey"); + const { castToken, collectionKey } = readCastData(); const collection = await getCastCollection( + collectionKey, castToken, - requestedCollectionKey, ); if ( castCollection === undefined || diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index fb19d2bfd..6e0282f79 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -3,6 +3,7 @@ import { isNonWebImageFileExtension } from "@/media/formats"; import { decodeLivePhoto } from "@/media/live-photo"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; +import { ensure } from "@/utils/ensure"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -26,6 +27,41 @@ const ENDPOINT = getEndpoint(); const COLLECTION_FILES_TABLE = "collection-files"; const COLLECTIONS_TABLE = "collections"; +/** + * Save the data received after pairing with a sender into local storage. + * + * We will read in back when we start the slideshow. + */ +export const storeCastData = (payload: unknown) => { + if (!payload || typeof payload != "object") + throw new Error("Unexpected cast data"); + + // Iterate through all the keys of the payload object and save them to + // localStorage. We don't validate here, we'll validate when we read these + // values back in `readCastData`. + for (const key in payload) { + window.localStorage.setItem(key, payload[key]); + } +}; + +interface CastData { + /** A key to decrypt the collection we are casting. */ + collectionKey: string; + /** A credential to use for fetching media files for this cast session. */ + castToken: string; +} + +/** + * Read back the cast data we got after pairing. + * + * Sibling of {@link storeCastData} + */ +export const readCastData = (): CastData => { + const collectionKey = ensure(window.localStorage.getItem("collectionKey")); + const castToken = ensure(window.localStorage.getItem("castToken")); + return { collectionKey, castToken }; +}; + const getLastSyncKey = (collectionUID: string) => `${collectionUID}-time`; export const getLocalFiles = async ( @@ -232,8 +268,8 @@ const fetchFiles = async ( }; export const getCastCollection = async ( - castToken: string, collectionKey: string, + castToken: string, ): Promise => { try { const resp = await HTTPService.get(`${ENDPOINT}/cast/info`, null, { @@ -278,13 +314,6 @@ export const getCastCollection = async ( } }; -export const storeCastData = (payloadObj: Object) => { - // iterate through all the keys in the payload object and set them in localStorage. - for (const key in payloadObj) { - window.localStorage.setItem(key, payloadObj[key]); - } -}; - export function sortFiles(files: EnteFile[], sortAsc = false) { // sort based on the time of creation time of the file, // for files with same creation time, sort based on the time of last modification From fdeb0f9493acbcc6221d8f1b444d9f0139a582bb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 11:12:43 +0530 Subject: [PATCH 218/367] Match --- web/apps/cast/src/pages/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 87ce756e0..204ff2d04 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -7,7 +7,7 @@ import { storeCastData } from "services/cast"; import { advertiseCode, getCastData, register } from "services/pair"; import { castReceiverLoadingIfNeeded } from "../utils/cast-receiver"; -export default function PairingMode() { +export default function Index() { const [publicKeyB64, setPublicKeyB64] = useState(); const [privateKeyB64, setPrivateKeyB64] = useState(); const [pairingCode, setPairingCode] = useState(); From efdf9805587505765bacf5c7d1b9a58a428ce400 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 11:15:06 +0530 Subject: [PATCH 219/367] busywork --- web/apps/cast/src/components/FilledCircleCheck.tsx | 4 +--- web/apps/cast/src/components/LargeType.tsx | 4 ++-- web/apps/cast/src/components/PairedSuccessfullyOverlay.tsx | 6 +++--- web/apps/cast/src/pages/index.tsx | 2 +- web/apps/cast/src/pages/slideshow.tsx | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/web/apps/cast/src/components/FilledCircleCheck.tsx b/web/apps/cast/src/components/FilledCircleCheck.tsx index c0635f138..ba2292922 100644 --- a/web/apps/cast/src/components/FilledCircleCheck.tsx +++ b/web/apps/cast/src/components/FilledCircleCheck.tsx @@ -1,6 +1,6 @@ import { styled } from "@mui/material"; -const FilledCircleCheck = () => { +export const FilledCircleCheck: React.FC = () => { return ( @@ -11,8 +11,6 @@ const FilledCircleCheck = () => { ); }; -export default FilledCircleCheck; - const Container = styled("div")` width: 100px; height: 100px; diff --git a/web/apps/cast/src/components/LargeType.tsx b/web/apps/cast/src/components/LargeType.tsx index ecf7a201b..42ccb65e9 100644 --- a/web/apps/cast/src/components/LargeType.tsx +++ b/web/apps/cast/src/components/LargeType.tsx @@ -23,7 +23,7 @@ const colourPool = [ "#808000", // Light Olive ]; -export default function LargeType({ chars }: { chars: string[] }) { +export const LargeType = ({ chars }: { chars: string[] }) => { return ( {chars.map((char, i) => ( @@ -41,7 +41,7 @@ export default function LargeType({ chars }: { chars: string[] }) { ))} ); -} +}; const Container = styled("div")` font-size: 4rem; diff --git a/web/apps/cast/src/components/PairedSuccessfullyOverlay.tsx b/web/apps/cast/src/components/PairedSuccessfullyOverlay.tsx index 845416fed..88f4d7c1f 100644 --- a/web/apps/cast/src/components/PairedSuccessfullyOverlay.tsx +++ b/web/apps/cast/src/components/PairedSuccessfullyOverlay.tsx @@ -1,6 +1,6 @@ -import FilledCircleCheck from "./FilledCircleCheck"; +import { FilledCircleCheck } from "./FilledCircleCheck"; -export default function PairedSuccessfullyOverlay() { +export const PairedSuccessfullyOverlay: React.FC = () => { return (
); -} +}; diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 204ff2d04..f1ec1a746 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -1,6 +1,6 @@ import log from "@/next/log"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; -import LargeType from "components/LargeType"; +import { LargeType } from "components/LargeType"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { storeCastData } from "services/cast"; diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index 85cf7ad23..f14a3f40e 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -1,5 +1,5 @@ import log from "@/next/log"; -import PairedSuccessfullyOverlay from "components/PairedSuccessfullyOverlay"; +import { PairedSuccessfullyOverlay } from "components/PairedSuccessfullyOverlay"; import { SlideView } from "components/Slide"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; From 882af50507025c87292e12f2913174c35b337405 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 11:18:23 +0530 Subject: [PATCH 220/367] Reorder --- web/apps/cast/src/pages/index.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index f1ec1a746..dd0389949 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -14,6 +14,10 @@ export default function Index() { const router = useRouter(); + useEffect(() => { + init(); + }, []); + const init = () => { register().then((r) => { setPublicKeyB64(r.publicKeyB64); @@ -22,16 +26,19 @@ export default function Index() { }); }; - useEffect(() => { - init(); - }, []); - useEffect(() => { castReceiverLoadingIfNeeded().then((cast) => advertiseCode(cast, () => pairingCode), ); }, []); + useEffect(() => { + if (!publicKeyB64 || !privateKeyB64 || !pairingCode) return; + + const interval = setInterval(pollTick, 2000); + return () => clearInterval(interval); + }, [publicKeyB64, privateKeyB64, pairingCode]); + const pollTick = async () => { const registration = { publicKeyB64, privateKeyB64, pairingCode }; try { @@ -52,13 +59,6 @@ export default function Index() { } }; - useEffect(() => { - if (!publicKeyB64 || !privateKeyB64 || !pairingCode) return; - - const interval = setInterval(pollTick, 2000); - return () => clearInterval(interval); - }, [publicKeyB64, privateKeyB64, pairingCode]); - return ( <>
Date: Sat, 4 May 2024 12:15:22 +0530 Subject: [PATCH 221/367] Update changelog --- mobile/lib/ui/notification/update/change_log_page.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mobile/lib/ui/notification/update/change_log_page.dart b/mobile/lib/ui/notification/update/change_log_page.dart index 61494af93..1216b3219 100644 --- a/mobile/lib/ui/notification/update/change_log_page.dart +++ b/mobile/lib/ui/notification/update/change_log_page.dart @@ -123,9 +123,13 @@ class _ChangeLogPageState extends State { "View a slideshow of your albums on any big screen! Open an album and click on the Cast button to get started.", ), ChangeLogEntry( - "Own shared photos", + "Organize shared photos", "You can now add shared items to your favorites to any of your personal albums. Ente will create a copy that is fully owned by you and can be organized to your liking.", ), + ChangeLogEntry( + "Download multiple items", + "You can now download multiple items to your gallery at once. Select the items you want to download and click on the download button.", + ), ChangeLogEntry( "Performance improvements", "This release also brings in major changes that should improve responsiveness. If you discover room for improvement, please let us know!", From b520996af50a6ee4564057acb093cf46b1be2041 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 12:19:18 +0530 Subject: [PATCH 222/367] Sketch --- web/apps/cast/src/pages/slideshow.tsx | 58 +++++++++++++++------- web/apps/cast/src/services/cast.ts | 69 ++++++++++++++++++++++----- 2 files changed, 99 insertions(+), 28 deletions(-) diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index f14a3f40e..c3e950783 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -3,31 +3,53 @@ import { PairedSuccessfullyOverlay } from "components/PairedSuccessfullyOverlay" import { SlideView } from "components/Slide"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import { - createRenderableURL, - getCastCollection, - getLocalFiles, - isFileEligibleForCast, - readCastData, - syncPublicFiles, -} from "services/cast"; -import { Collection } from "types/collection"; -import { EnteFile } from "types/file"; +import { readCastData, renderableURLs } from "services/cast"; export default function Slideshow() { const [loading, setLoading] = useState(true); const [castToken, setCastToken] = useState(""); - const [castCollection, setCastCollection] = useState< - Collection | undefined - >(); - const [collectionFiles, setCollectionFiles] = useState([]); - const [currentFileId, setCurrentFileId] = useState(); + // const [castCollection, setCastCollection] = useState< + // Collection | undefined + // >(); + // const [collectionFiles, setCollectionFiles] = useState([]); + // const [currentFileId, setCurrentFileId] = useState(); const [currentFileURL, setCurrentFileURL] = useState(); const [nextFileURL, setNextFileURL] = useState(); + const [urlGenerator, setURLGenerator] = useState< + AsyncGenerator | undefined + >(); + // const [canCast, setCanCast] = useState(false); const router = useRouter(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const syncCastFiles = async (token: string) => { + try { + setURLGenerator(renderableURLs(readCastData())); + // setCanCast(true); + setLoading(false); + } catch (e) { + log.error("Failed to prepare URL generator", e); + // Go back to pairing page + router.push("/"); + } + }; + + const advance = async () => { + if (!urlGenerator) throw new Error("Unexpected state"); + const urls = await urlGenerator.next(); + if (!urls) { + log.warn("Empty collection"); + // Go back to pairing page + router.push("/"); + return; + } + setCurrentFileURL(urls[0]); + setNextFileURL(urls[1]); + }; + + /* + const syncCastFiles0 = async (token: string) => { try { const { castToken, collectionKey } = readCastData(); const collection = await getCastCollection( @@ -51,6 +73,7 @@ export default function Slideshow() { router.push("/"); } }; + */ useEffect(() => { if (castToken) { @@ -80,6 +103,7 @@ export default function Slideshow() { } }, []); + /* useEffect(() => { if (collectionFiles.length < 1) return; showNextSlide(); @@ -141,6 +165,7 @@ export default function Slideshow() { console.log("error in showNextSlide", e); } }; + */ useEffect(() => { if (loading) return; @@ -148,7 +173,8 @@ export default function Slideshow() { console.log("showing slide"); const timeoutId = window.setTimeout(() => { console.log("showing next slide timer"); - showNextSlide(); + // showNextSlide(); + advance(); }, 10000); return () => { diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 6e0282f79..4312178c3 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -3,7 +3,7 @@ import { isNonWebImageFileExtension } from "@/media/formats"; import { decodeLivePhoto } from "@/media/live-photo"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; -import { ensure } from "@/utils/ensure"; +import { ensureString } from "@/utils/ensure"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -54,14 +54,59 @@ interface CastData { /** * Read back the cast data we got after pairing. * - * Sibling of {@link storeCastData} + * Sibling of {@link storeCastData}. It throws an error if the expected data is + * not present in localStorage. */ export const readCastData = (): CastData => { - const collectionKey = ensure(window.localStorage.getItem("collectionKey")); - const castToken = ensure(window.localStorage.getItem("castToken")); + const collectionKey = ensureString(localStorage.getItem("collectionKey")); + const castToken = ensureString(localStorage.getItem("castToken")); return { collectionKey, castToken }; }; +/** + * An async generator function that loops through all the files in the + * collection, returning renderable URLs to each that can be displayed in a + * slideshow. + * + * Each time it resolves with a pair of URLs, one for the current slideshow + * image, and one for the image to be displayed next. + * + * Once it reaches the end of the collection, it starts from the beginning + * again. + * + * It ignores errors in the fetching and decoding of individual images in the + * collection, and just moves onward to the next one. It will however throw if + * there are errors in getting the collection itself. + * + * If there are no renderable image in the collection, it resolves with + * `undefined`. + * + * @param castData The collection to show and credentials to fetch the files + * within it. + */ +export const renderableURLs = async function* (castData: CastData) { + const { collectionKey, castToken } = castData; + let previousURL: string | undefined + while (true) { + const collection = await getCollection(castToken, collectionKey); + await syncPublicFiles(castToken, collection, () => {}); + const allFiles = await getLocalFiles(String(collection.id)); + const files = allFiles.filter((file) => isFileEligibleForCast(file)); + + for (const file of files) { + if (!previousURL) { + previousURL = await createRenderableURL(castToken, file); + continue; + } + + const url = await createRenderableURL(castToken, file); + const urls = [previousURL, url]; + previousURL = url; + yield urls; + } + } +}; + const getLastSyncKey = (collectionUID: string) => `${collectionUID}-time`; export const getLocalFiles = async ( @@ -147,8 +192,8 @@ async function getSyncTime(collectionUID: string): Promise { const updateSyncTime = async (collectionUID: string, time: number) => await localForage.setItem(getLastSyncKey(collectionUID), time); -export const syncPublicFiles = async ( - token: string, +const syncPublicFiles = async ( + castToken: string, collection: Collection, setPublicFiles: (files: EnteFile[]) => void, ) => { @@ -164,7 +209,7 @@ export const syncPublicFiles = async ( return sortFiles(files, sortAsc); } const fetchedFiles = await fetchFiles( - token, + castToken, collection, lastSyncTime, files, @@ -267,9 +312,9 @@ const fetchFiles = async ( } }; -export const getCastCollection = async ( - collectionKey: string, +const getCollection = async ( castToken: string, + collectionKey: string, ): Promise => { try { const resp = await HTTPService.get(`${ENDPOINT}/cast/info`, null, { @@ -408,12 +453,12 @@ export function mergeMetadata(files: EnteFile[]): EnteFile[] { * Once we're done showing the file, the URL should be revoked using * {@link URL.revokeObjectURL} to free up browser resources. */ -export const createRenderableURL = async (file: EnteFile, castToken: string) => - URL.createObjectURL(await getPreviewableImage(file, castToken)); +export const createRenderableURL = async (castToken: string, file: EnteFile) => + URL.createObjectURL(await getPreviewableImage(castToken, file)); export const getPreviewableImage = async ( - file: EnteFile, castToken: string, + file: EnteFile, ): Promise => { try { let fileBlob = await downloadFile(castToken, file); From 8b75393ea08f494f0eccc67c444df0d113557831 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 12:39:55 +0530 Subject: [PATCH 223/367] gen --- web/apps/cast/src/pages/slideshow.tsx | 10 +++++++--- web/apps/cast/src/services/cast.ts | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index c3e950783..6d1eee605 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -36,9 +36,10 @@ export default function Slideshow() { }; const advance = async () => { + console.log("in advance"); if (!urlGenerator) throw new Error("Unexpected state"); - const urls = await urlGenerator.next(); - if (!urls) { + const { value: urls, done } = await urlGenerator.next(); + if (done) { log.warn("Empty collection"); // Go back to pairing page router.push("/"); @@ -182,7 +183,10 @@ export default function Slideshow() { }; }, [loading]); - if (loading) return ; + console.log({ a: "render", loading, currentFileURL, nextFileURL }); + + if (loading || !currentFileURL || !nextFileURL) + return ; return ; } diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 4312178c3..af069dfcf 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -78,15 +78,15 @@ export const readCastData = (): CastData => { * collection, and just moves onward to the next one. It will however throw if * there are errors in getting the collection itself. * - * If there are no renderable image in the collection, it resolves with - * `undefined`. + * If there are no renderable image in the collection, the sequence ends by + * yielding `done: true`. * * @param castData The collection to show and credentials to fetch the files * within it. */ export const renderableURLs = async function* (castData: CastData) { const { collectionKey, castToken } = castData; - let previousURL: string | undefined + let previousURL: string | undefined; while (true) { const collection = await getCollection(castToken, collectionKey); await syncPublicFiles(castToken, collection, () => {}); @@ -94,6 +94,7 @@ export const renderableURLs = async function* (castData: CastData) { const files = allFiles.filter((file) => isFileEligibleForCast(file)); for (const file of files) { + console.log("in generator", previousURL); if (!previousURL) { previousURL = await createRenderableURL(castToken, file); continue; From bf66697bcb02c480898ae062734096461e537540 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 13:24:58 +0530 Subject: [PATCH 224/367] t2 --- web/apps/cast/src/pages/slideshow.tsx | 152 +++++++------------------- web/apps/cast/src/services/cast.ts | 24 ++-- 2 files changed, 53 insertions(+), 123 deletions(-) diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index 6d1eee605..24f1bb4c5 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -3,113 +3,57 @@ import { PairedSuccessfullyOverlay } from "components/PairedSuccessfullyOverlay" import { SlideView } from "components/Slide"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import { readCastData, renderableURLs } from "services/cast"; +import { readCastData, renderableImageURLs } from "services/cast"; export default function Slideshow() { const [loading, setLoading] = useState(true); - const [castToken, setCastToken] = useState(""); - // const [castCollection, setCastCollection] = useState< - // Collection | undefined - // >(); - // const [collectionFiles, setCollectionFiles] = useState([]); - // const [currentFileId, setCurrentFileId] = useState(); - const [currentFileURL, setCurrentFileURL] = useState(); - const [nextFileURL, setNextFileURL] = useState(); - const [urlGenerator, setURLGenerator] = useState< - AsyncGenerator | undefined - >(); - // const [canCast, setCanCast] = useState(false); + const [imageURL, setImageURL] = useState(); + const [nextImageURL, setNextImageURL] = useState(); const router = useRouter(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const syncCastFiles = async (token: string) => { - try { - setURLGenerator(renderableURLs(readCastData())); - // setCanCast(true); - setLoading(false); - } catch (e) { - log.error("Failed to prepare URL generator", e); - // Go back to pairing page - router.push("/"); - } - }; - - const advance = async () => { - console.log("in advance"); - if (!urlGenerator) throw new Error("Unexpected state"); - const { value: urls, done } = await urlGenerator.next(); - if (done) { - log.warn("Empty collection"); - // Go back to pairing page - router.push("/"); - return; - } - setCurrentFileURL(urls[0]); - setNextFileURL(urls[1]); - }; - - /* - const syncCastFiles0 = async (token: string) => { - try { - const { castToken, collectionKey } = readCastData(); - const collection = await getCastCollection( - collectionKey, - castToken, - ); - if ( - castCollection === undefined || - castCollection.updationTime !== collection.updationTime - ) { - setCastCollection(collection); - await syncPublicFiles(token, collection, () => {}); - const files = await getLocalFiles(String(collection.id)); - setCollectionFiles( - files.filter((file) => isFileEligibleForCast(file)), - ); - } - } catch (e) { - log.error("error during sync", e); - // go back to preview page - router.push("/"); - } - }; - */ - - useEffect(() => { - if (castToken) { - const intervalId = setInterval(() => { - syncCastFiles(castToken); - }, 10000); - syncCastFiles(castToken); - - return () => clearInterval(intervalId); - } - }, [castToken]); + /** Go back to pairing page */ + const pair = () => router.push("/"); useEffect(() => { + let urlGenerator: AsyncGenerator<[string, string], void>; try { - const castToken = window.localStorage.getItem("castToken"); - // Wait 2 seconds to ensure the green tick and the confirmation - // message remains visible for at least 2 seconds before we start - // the slideshow. - const timeoutId = setTimeout(() => { - setCastToken(castToken); - }, 2000); - - return () => clearTimeout(timeoutId); + urlGenerator = renderableImageURLs(readCastData()); } catch (e) { - log.error("error during sync", e); - router.push("/"); + log.error("Failed to prepare generator", e); + pair(); } + + advance(urlGenerator); + + const interval = window.setInterval(() => { + advance(urlGenerator); + }, 10000); + + return () => clearInterval(interval); }, []); - /* - useEffect(() => { - if (collectionFiles.length < 1) return; - showNextSlide(); - }, [collectionFiles]); + const advance = async ( + urlGenerator: AsyncGenerator<[string, string], void>, + ) => { + try { + const { value: urls, done } = await urlGenerator.next(); + if (done) { + log.warn("Empty collection"); + pair(); + return; + } + setImageURL(urls[0]); + setNextImageURL(urls[1]); + setLoading(false); + } catch (e) { + log.error("Failed to generate image URL", e); + pair(); + } + }; + + /* const showNextSlide = async () => { try { console.log("showNextSlide"); @@ -168,25 +112,7 @@ export default function Slideshow() { }; */ - useEffect(() => { - if (loading) return; + if (loading) return ; - console.log("showing slide"); - const timeoutId = window.setTimeout(() => { - console.log("showing next slide timer"); - // showNextSlide(); - advance(); - }, 10000); - - return () => { - if (timeoutId) clearTimeout(timeoutId); - }; - }, [loading]); - - console.log({ a: "render", loading, currentFileURL, nextFileURL }); - - if (loading || !currentFileURL || !nextFileURL) - return ; - - return ; + return ; } diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index af069dfcf..a11c67e92 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -63,28 +63,31 @@ export const readCastData = (): CastData => { return { collectionKey, castToken }; }; +type RenderableImageURLPair = [url: string, nextURL: string]; + /** * An async generator function that loops through all the files in the * collection, returning renderable URLs to each that can be displayed in a * slideshow. * - * Each time it resolves with a pair of URLs, one for the current slideshow - * image, and one for the image to be displayed next. + * Each time it resolves with a pair of URLs (a {@link RenderableImageURLPair}), + * one for the current slideshow image, and one for the slideshow image that + * will be displayed next. + * + * If there are no renderable image in the collection, the sequence ends by + * yielding `{done: true}`. * * Once it reaches the end of the collection, it starts from the beginning - * again. + * again. So the sequence will continue indefinitely for non-empty collections. * * It ignores errors in the fetching and decoding of individual images in the * collection, and just moves onward to the next one. It will however throw if - * there are errors in getting the collection itself. - * - * If there are no renderable image in the collection, the sequence ends by - * yielding `done: true`. + * there are errors when getting the collection itself. * * @param castData The collection to show and credentials to fetch the files * within it. */ -export const renderableURLs = async function* (castData: CastData) { +export const renderableImageURLs = async function* (castData: CastData) { const { collectionKey, castToken } = castData; let previousURL: string | undefined; while (true) { @@ -93,15 +96,16 @@ export const renderableURLs = async function* (castData: CastData) { const allFiles = await getLocalFiles(String(collection.id)); const files = allFiles.filter((file) => isFileEligibleForCast(file)); + if (!files.length) return; + for (const file of files) { - console.log("in generator", previousURL); if (!previousURL) { previousURL = await createRenderableURL(castToken, file); continue; } const url = await createRenderableURL(castToken, file); - const urls = [previousURL, url]; + const urls: RenderableImageURLPair = [previousURL, url]; previousURL = url; yield urls; } From 80301b14f436f276ee2214b3cefc9002cd519446 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 14:18:44 +0530 Subject: [PATCH 225/367] Rearrange --- web/apps/cast/src/services/cast.ts | 106 +++++++++++++++-------------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index a11c67e92..4c8362ab8 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -91,14 +91,17 @@ export const renderableImageURLs = async function* (castData: CastData) { const { collectionKey, castToken } = castData; let previousURL: string | undefined; while (true) { - const collection = await getCollection(castToken, collectionKey); + const collection = await getCastCollection(castToken, collectionKey); await syncPublicFiles(castToken, collection, () => {}); - const allFiles = await getLocalFiles(String(collection.id)); - const files = allFiles.filter((file) => isFileEligibleForCast(file)); + const files = await getLocalFiles(String(collection.id)); - if (!files.length) return; + let haveEligibleFiles = false; for (const file of files) { + if (!isFileEligibleForCast(file)) continue; + + haveEligibleFiles = true; + if (!previousURL) { previousURL = await createRenderableURL(castToken, file); continue; @@ -109,9 +112,57 @@ export const renderableImageURLs = async function* (castData: CastData) { previousURL = url; yield urls; } + + // This collection does not have any files that we can show. + if (!haveEligibleFiles) return; } }; +const getCastCollection = async ( + castToken: string, + collectionKey: string, +): Promise => { + const resp = await HTTPService.get(`${ENDPOINT}/cast/info`, null, { + "Cache-Control": "no-cache", + "X-Cast-Access-Token": castToken, + }); + + let collection = resp.data.collection; + + const cryptoWorker = await ComlinkCryptoWorker.getInstance(); + + const collectionName = + collection.name || + (await cryptoWorker.decryptToUTF8( + collection.encryptedName, + collection.nameDecryptionNonce, + collectionKey, + )); + + let collectionPublicMagicMetadata: CollectionPublicMagicMetadata; + if (collection.pubMagicMetadata?.data) { + collectionPublicMagicMetadata = { + ...collection.pubMagicMetadata, + data: await cryptoWorker.decryptMetadata( + collection.pubMagicMetadata.data, + collection.pubMagicMetadata.header, + collectionKey, + ), + }; + } + + collection = { + ...collection, + name: collectionName, + key: collectionKey, + pubMagicMetadata: collectionPublicMagicMetadata, + }; + + await saveCollection(collection); + + return collection; +}; + const getLastSyncKey = (collectionUID: string) => `${collectionUID}-time`; export const getLocalFiles = async ( @@ -317,53 +368,6 @@ const fetchFiles = async ( } }; -const getCollection = async ( - castToken: string, - collectionKey: string, -): Promise => { - try { - const resp = await HTTPService.get(`${ENDPOINT}/cast/info`, null, { - "Cache-Control": "no-cache", - "X-Cast-Access-Token": castToken, - }); - const fetchedCollection = resp.data.collection; - - const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - - const collectionName = (fetchedCollection.name = - fetchedCollection.name || - (await cryptoWorker.decryptToUTF8( - fetchedCollection.encryptedName, - fetchedCollection.nameDecryptionNonce, - collectionKey, - ))); - - let collectionPublicMagicMetadata: CollectionPublicMagicMetadata; - if (fetchedCollection.pubMagicMetadata?.data) { - collectionPublicMagicMetadata = { - ...fetchedCollection.pubMagicMetadata, - data: await cryptoWorker.decryptMetadata( - fetchedCollection.pubMagicMetadata.data, - fetchedCollection.pubMagicMetadata.header, - collectionKey, - ), - }; - } - - const collection = { - ...fetchedCollection, - name: collectionName, - key: collectionKey, - pubMagicMetadata: collectionPublicMagicMetadata, - }; - await saveCollection(collection); - return collection; - } catch (e) { - log.error("failed to get cast collection", e); - throw e; - } -}; - export function sortFiles(files: EnteFile[], sortAsc = false) { // sort based on the time of creation time of the file, // for files with same creation time, sort based on the time of last modification From 1d11c906efc750f20fcec2c1b87c56e7b453bfcb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 14:21:44 +0530 Subject: [PATCH 226/367] Reorder --- web/apps/cast/src/services/cast.ts | 144 +++++++++++++---------------- 1 file changed, 65 insertions(+), 79 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 4c8362ab8..e9a8e8282 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -163,6 +163,71 @@ const getCastCollection = async ( return collection; }; +const saveCollection = async (collection: Collection) => { + const collections = + (await localForage.getItem(COLLECTIONS_TABLE)) ?? []; + await localForage.setItem( + COLLECTIONS_TABLE, + dedupeCollections([collection, ...collections]), + ); +}; + +const syncPublicFiles = async ( + castToken: string, + collection: Collection, + setPublicFiles: (files: EnteFile[]) => void, +) => { + let files: EnteFile[] = []; + const sortAsc = collection?.pubMagicMetadata?.data.asc ?? false; + const collectionUID = String(collection.id); + const localFiles = await getLocalFiles(collectionUID); + files = [...files, ...localFiles]; + try { + const lastSyncTime = await getSyncTime(collectionUID); + if (collection.updationTime === lastSyncTime) { + return sortFiles(files, sortAsc); + } + const fetchedFiles = await fetchFiles( + castToken, + collection, + lastSyncTime, + files, + setPublicFiles, + ); + + files = [...files, ...fetchedFiles]; + const latestVersionFiles = new Map(); + files.forEach((file) => { + const uid = `${file.collectionID}-${file.id}`; + if ( + !latestVersionFiles.has(uid) || + latestVersionFiles.get(uid).updationTime < file.updationTime + ) { + latestVersionFiles.set(uid, file); + } + }); + files = []; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + for (const [_, file] of latestVersionFiles) { + if (file.isDeleted) { + continue; + } + files.push(file); + } + await savecollectionFiles(collectionUID, files); + await updateSyncTime(collectionUID, collection.updationTime); + setPublicFiles([...sortFiles(mergeMetadata(files), sortAsc)]); + } catch (e) { + const parsedError = parseSharingErrorCodes(e); + log.error("failed to sync shared collection files", e); + if (parsedError.message === CustomError.TOKEN_EXPIRED) { + throw e; + } + } + return [...sortFiles(mergeMetadata(files), sortAsc)]; +}; + + const getLastSyncKey = (collectionUID: string) => `${collectionUID}-time`; export const getLocalFiles = async ( @@ -195,26 +260,6 @@ const savecollectionFiles = async ( ); }; -export const getLocalCollections = async (collectionKey: string) => { - const localCollections = - (await localForage.getItem(COLLECTIONS_TABLE)) || []; - const collection = - localCollections.find( - (localSavedPublicCollection) => - localSavedPublicCollection.key === collectionKey, - ) || null; - return collection; -}; - -const saveCollection = async (collection: Collection) => { - const collections = - (await localForage.getItem(COLLECTIONS_TABLE)) ?? []; - await localForage.setItem( - COLLECTIONS_TABLE, - dedupeCollections([collection, ...collections]), - ); -}; - const dedupeCollections = (collections: Collection[]) => { const keySet = new Set([]); return collections.filter((collection) => { @@ -248,65 +293,6 @@ async function getSyncTime(collectionUID: string): Promise { const updateSyncTime = async (collectionUID: string, time: number) => await localForage.setItem(getLastSyncKey(collectionUID), time); -const syncPublicFiles = async ( - castToken: string, - collection: Collection, - setPublicFiles: (files: EnteFile[]) => void, -) => { - try { - let files: EnteFile[] = []; - const sortAsc = collection?.pubMagicMetadata?.data.asc ?? false; - const collectionUID = String(collection.id); - const localFiles = await getLocalFiles(collectionUID); - files = [...files, ...localFiles]; - try { - const lastSyncTime = await getSyncTime(collectionUID); - if (collection.updationTime === lastSyncTime) { - return sortFiles(files, sortAsc); - } - const fetchedFiles = await fetchFiles( - castToken, - collection, - lastSyncTime, - files, - setPublicFiles, - ); - - files = [...files, ...fetchedFiles]; - const latestVersionFiles = new Map(); - files.forEach((file) => { - const uid = `${file.collectionID}-${file.id}`; - if ( - !latestVersionFiles.has(uid) || - latestVersionFiles.get(uid).updationTime < file.updationTime - ) { - latestVersionFiles.set(uid, file); - } - }); - files = []; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const [_, file] of latestVersionFiles) { - if (file.isDeleted) { - continue; - } - files.push(file); - } - await savecollectionFiles(collectionUID, files); - await updateSyncTime(collectionUID, collection.updationTime); - setPublicFiles([...sortFiles(mergeMetadata(files), sortAsc)]); - } catch (e) { - const parsedError = parseSharingErrorCodes(e); - log.error("failed to sync shared collection files", e); - if (parsedError.message === CustomError.TOKEN_EXPIRED) { - throw e; - } - } - return [...sortFiles(mergeMetadata(files), sortAsc)]; - } catch (e) { - log.error("failed to get local or sync shared collection files", e); - throw e; - } -}; const fetchFiles = async ( castToken: string, From b42326bd9b8a6a59bd005f5cdf8435286fb3074a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 14:23:56 +0530 Subject: [PATCH 227/367] Remove unused saved data --- web/apps/cast/src/services/cast.ts | 32 ++---------------------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index e9a8e8282..6cdc5e7b8 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -25,7 +25,6 @@ export interface SavedCollectionFiles { const ENDPOINT = getEndpoint(); const COLLECTION_FILES_TABLE = "collection-files"; -const COLLECTIONS_TABLE = "collections"; /** * Save the data received after pairing with a sender into local storage. @@ -127,7 +126,7 @@ const getCastCollection = async ( "X-Cast-Access-Token": castToken, }); - let collection = resp.data.collection; + const collection = resp.data.collection; const cryptoWorker = await ComlinkCryptoWorker.getInstance(); @@ -151,25 +150,12 @@ const getCastCollection = async ( }; } - collection = { + return { ...collection, name: collectionName, key: collectionKey, pubMagicMetadata: collectionPublicMagicMetadata, }; - - await saveCollection(collection); - - return collection; -}; - -const saveCollection = async (collection: Collection) => { - const collections = - (await localForage.getItem(COLLECTIONS_TABLE)) ?? []; - await localForage.setItem( - COLLECTIONS_TABLE, - dedupeCollections([collection, ...collections]), - ); }; const syncPublicFiles = async ( @@ -227,7 +213,6 @@ const syncPublicFiles = async ( return [...sortFiles(mergeMetadata(files), sortAsc)]; }; - const getLastSyncKey = (collectionUID: string) => `${collectionUID}-time`; export const getLocalFiles = async ( @@ -260,18 +245,6 @@ const savecollectionFiles = async ( ); }; -const dedupeCollections = (collections: Collection[]) => { - const keySet = new Set([]); - return collections.filter((collection) => { - if (!keySet.has(collection.key)) { - keySet.add(collection.key); - return true; - } else { - return false; - } - }); -}; - const dedupeCollectionFiles = (collectionFiles: SavedCollectionFiles[]) => { const keySet = new Set([]); return collectionFiles.filter(({ collectionLocalID: collectionUID }) => { @@ -293,7 +266,6 @@ async function getSyncTime(collectionUID: string): Promise { const updateSyncTime = async (collectionUID: string, time: number) => await localForage.setItem(getLastSyncKey(collectionUID), time); - const fetchFiles = async ( castToken: string, collection: Collection, From 77036ee29a79248db5c443a4d76b9317e6f00c31 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 14:26:18 +0530 Subject: [PATCH 228/367] Rearrange --- web/apps/cast/src/services/cast.ts | 91 ++++++++++++++---------------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 6cdc5e7b8..1be6e80eb 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -23,7 +23,6 @@ export interface SavedCollectionFiles { files: EnteFile[]; } -const ENDPOINT = getEndpoint(); const COLLECTION_FILES_TABLE = "collection-files"; /** @@ -121,7 +120,7 @@ const getCastCollection = async ( castToken: string, collectionKey: string, ): Promise => { - const resp = await HTTPService.get(`${ENDPOINT}/cast/info`, null, { + const resp = await HTTPService.get(`${getEndpoint()}/cast/info`, null, { "Cache-Control": "no-cache", "X-Cast-Access-Token": castToken, }); @@ -273,57 +272,49 @@ const fetchFiles = async ( files: EnteFile[], setPublicFiles: (files: EnteFile[]) => void, ): Promise => { - try { - let decryptedFiles: EnteFile[] = []; - let time = sinceTime; - let resp; - const sortAsc = collection?.pubMagicMetadata?.data.asc ?? false; - do { - if (!castToken) { - break; - } - resp = await HTTPService.get( - `${ENDPOINT}/cast/diff`, - { - sinceTime: time, - }, - { - "Cache-Control": "no-cache", - "X-Cast-Access-Token": castToken, - }, - ); - decryptedFiles = [ - ...decryptedFiles, - ...(await Promise.all( - resp.data.diff.map(async (file: EncryptedEnteFile) => { - if (!file.isDeleted) { - return await decryptFile(file, collection.key); - } else { - return file; - } - }) as Promise[], - )), - ]; + const sortAsc = collection?.pubMagicMetadata?.data.asc ?? false; + let decryptedFiles: EnteFile[] = []; + let time = sinceTime; + let resp; + do { + resp = await HTTPService.get( + `${getEndpoint()}/cast/diff`, + { + sinceTime: time, + }, + { + "Cache-Control": "no-cache", + "X-Cast-Access-Token": castToken, + }, + ); + decryptedFiles = [ + ...decryptedFiles, + ...(await Promise.all( + resp.data.diff.map(async (file: EncryptedEnteFile) => { + if (!file.isDeleted) { + return await decryptFile(file, collection.key); + } else { + return file; + } + }) as Promise[], + )), + ]; - if (resp.data.diff.length) { - time = resp.data.diff.slice(-1)[0].updationTime; - } - setPublicFiles( - sortFiles( - mergeMetadata( - [...(files || []), ...decryptedFiles].filter( - (item) => !item.isDeleted, - ), + if (resp.data.diff.length) { + time = resp.data.diff.slice(-1)[0].updationTime; + } + setPublicFiles( + sortFiles( + mergeMetadata( + [...(files || []), ...decryptedFiles].filter( + (item) => !item.isDeleted, ), - sortAsc, ), - ); - } while (resp.data.hasMore); - return decryptedFiles; - } catch (e) { - log.error("Get cast files failed", e); - throw e; - } + sortAsc, + ), + ); + } while (resp.data.hasMore); + return decryptedFiles; }; export function sortFiles(files: EnteFile[], sortAsc = false) { From b809523ec9e1c9966bd626dc5f6840659a5bc3b7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 14:41:06 +0530 Subject: [PATCH 229/367] More --- web/apps/cast/src/services/cast.ts | 79 +++++++++++++++--------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 1be6e80eb..3ec256863 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -98,17 +98,29 @@ export const renderableImageURLs = async function* (castData: CastData) { for (const file of files) { if (!isFileEligibleForCast(file)) continue; - haveEligibleFiles = true; - if (!previousURL) { - previousURL = await createRenderableURL(castToken, file); + try { + previousURL = await createRenderableURL(castToken, file); + } catch (e) { + log.error("Skipping unrenderable file", e); + } continue; } - const url = await createRenderableURL(castToken, file); + let url: string; + try { + url = await createRenderableURL(castToken, file); + } catch (e) { + log.error("Skipping unrenderable file", e); + continue; + } + + haveEligibleFiles = true; + const urls: RenderableImageURLPair = [previousURL, url]; previousURL = url; yield urls; + // TODO: URL.revokeObjectURL where } // This collection does not have any files that we can show. @@ -404,41 +416,7 @@ export function mergeMetadata(files: EnteFile[]): EnteFile[] { }); } -/** - * Create and return a new data URL that can be used to show the given - * {@link file} in our slideshow image viewer. - * - * Once we're done showing the file, the URL should be revoked using - * {@link URL.revokeObjectURL} to free up browser resources. - */ -export const createRenderableURL = async (castToken: string, file: EnteFile) => - URL.createObjectURL(await getPreviewableImage(castToken, file)); - -export const getPreviewableImage = async ( - castToken: string, - file: EnteFile, -): Promise => { - try { - let fileBlob = await downloadFile(castToken, file); - if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) { - const { imageData } = await decodeLivePhoto( - file.metadata.title, - fileBlob, - ); - fileBlob = new Blob([imageData]); - } - const mimeType = await detectMediaMIMEType( - new File([fileBlob], file.metadata.title), - ); - if (!mimeType) return undefined; - fileBlob = new Blob([fileBlob], { type: mimeType }); - return fileBlob; - } catch (e) { - log.error("failed to download file", e); - } -}; - -export const isFileEligibleForCast = (file: EnteFile) => { +const isFileEligibleForCast = (file: EnteFile) => { if (!isImageOrLivePhoto(file)) return false; if (file.info.fileSize > 100 * 1024 * 1024) return false; @@ -453,6 +431,29 @@ const isImageOrLivePhoto = (file: EnteFile) => { return fileType == FILE_TYPE.IMAGE || fileType == FILE_TYPE.LIVE_PHOTO; }; +/** + * Create and return a new data URL that can be used to show the given + * {@link file} in our slideshow image viewer. + * + * Once we're done showing the file, the URL should be revoked using + * {@link URL.revokeObjectURL} to free up browser resources. + */ +const createRenderableURL = async (castToken: string, file: EnteFile) => + URL.createObjectURL(await renderableImageBlob(castToken, file)); + +const renderableImageBlob = async (castToken: string, file: EnteFile) => { + const fileName = file.metadata.title; + let blob = await downloadFile(castToken, file); + if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) { + const { imageData } = await decodeLivePhoto(fileName, blob); + blob = new Blob([imageData]); + } + const mimeType = await detectMediaMIMEType(new File([blob], fileName)); + if (!mimeType) + throw new Error(`Could not detect MIME type for file ${fileName}`); + return new Blob([blob], { type: mimeType }); +}; + const downloadFile = async (castToken: string, file: EnteFile) => { if (!isImageOrLivePhoto(file)) throw new Error("Can only cast images and live photos"); From b6add9650aed05924af1655d169949b5a9199174 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 15:26:46 +0530 Subject: [PATCH 230/367] Revoke --- web/apps/cast/src/services/cast.ts | 77 ++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 3ec256863..02619a3a3 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -3,7 +3,7 @@ import { isNonWebImageFileExtension } from "@/media/formats"; import { decodeLivePhoto } from "@/media/live-photo"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; -import { ensureString } from "@/utils/ensure"; +import { ensure, ensureString } from "@/utils/ensure"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -69,25 +69,54 @@ type RenderableImageURLPair = [url: string, nextURL: string]; * slideshow. * * Each time it resolves with a pair of URLs (a {@link RenderableImageURLPair}), - * one for the current slideshow image, and one for the slideshow image that - * will be displayed next. + * one for the next slideshow image, and one for the slideshow image that will + * be displayed after that. It also pre-fetches the next to next URL each time. * * If there are no renderable image in the collection, the sequence ends by * yielding `{done: true}`. * - * Once it reaches the end of the collection, it starts from the beginning - * again. So the sequence will continue indefinitely for non-empty collections. + * Otherwise when the generator reaches the end of the collection, it starts + * from the beginning again. So the sequence will continue indefinitely for + * non-empty collections. * - * It ignores errors in the fetching and decoding of individual images in the - * collection, and just moves onward to the next one. It will however throw if - * there are errors when getting the collection itself. + * The generator ignores errors in the fetching and decoding of individual + * images in the collection, skipping the erroneous ones and moving onward to + * the next one. It will however throw if there are errors when getting the + * collection itself. This can happen both the first time, or when we are about + * to loop around to the start of the collection. * * @param castData The collection to show and credentials to fetch the files * within it. */ export const renderableImageURLs = async function* (castData: CastData) { const { collectionKey, castToken } = castData; - let previousURL: string | undefined; + + /** The URL that we gave out second most recently. */ + let oldURL: string | undefined; + /** + * The URL that we gave out most recently. This is the URL that is currently + * being shown in the slideshow. + */ + let url: string | undefined; + /** + * The two URLs we will give out when we next yield, plus an extra one that + * we have pre-fetched. + */ + const nextURLs: string[] = []; + + /** + * We can revoke an object URL when it no longer the one being displayed and + * it is not one of those that we're planning to give out next. + * + * We have a sliding window of five URLs: + * + * [oldURL, url, nextURLs[0], nextURLs[1], nextURLs[2]] + * + * Note that for small albums of only a few renderable items, these URLs + * might possibly be the same as each other. + */ + const canRevokeURL = (u: string) => ![url, ...nextURLs].includes(u); + while (true) { const collection = await getCastCollection(castToken, collectionKey); await syncPublicFiles(castToken, collection, () => {}); @@ -95,32 +124,30 @@ export const renderableImageURLs = async function* (castData: CastData) { let haveEligibleFiles = false; - for (const file of files) { + outer: for (const file of files) { if (!isFileEligibleForCast(file)) continue; - if (!previousURL) { + while (nextURLs.length < 3) { try { - previousURL = await createRenderableURL(castToken, file); + nextURLs.push(await createRenderableURL(castToken, file)); } catch (e) { log.error("Skipping unrenderable file", e); + continue outer; } - continue; - } - - let url: string; - try { - url = await createRenderableURL(castToken, file); - } catch (e) { - log.error("Skipping unrenderable file", e); - continue; } haveEligibleFiles = true; - const urls: RenderableImageURLPair = [previousURL, url]; - previousURL = url; - yield urls; - // TODO: URL.revokeObjectURL where + const urlPair: RenderableImageURLPair = [ + ensure(nextURLs[0]), + ensure(nextURLs[1]), + ]; + // Revoke the oldest one. + if (oldURL && canRevokeURL(oldURL)) URL.revokeObjectURL(oldURL); + // Slide the window to the right. + oldURL = url; + url = nextURLs.shift(); + yield urlPair; } // This collection does not have any files that we can show. From 438b6430e06eba36dd2e90bc21e32c7e6b6da7fa Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 16:59:10 +0530 Subject: [PATCH 231/367] Simplify --- web/apps/cast/src/services/cast.ts | 47 ++++++++++-------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 02619a3a3..2ed6f1d21 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -91,31 +91,20 @@ type RenderableImageURLPair = [url: string, nextURL: string]; export const renderableImageURLs = async function* (castData: CastData) { const { collectionKey, castToken } = castData; - /** The URL that we gave out second most recently. */ - let oldURL: string | undefined; /** - * The URL that we gave out most recently. This is the URL that is currently - * being shown in the slideshow. - */ - let url: string | undefined; - /** - * The two URLs we will give out when we next yield, plus an extra one that - * we have pre-fetched. - */ - const nextURLs: string[] = []; - - /** - * We can revoke an object URL when it no longer the one being displayed and - * it is not one of those that we're planning to give out next. + * We have a sliding window of four URLs, with the `urls[1]` being the one + * that is the one currently being shown in the slideshow. * - * We have a sliding window of five URLs: + * At each step, we shift the window towards the right by shifting out the + * leftmost (oldest) `urls[0]`, and adding a new one at the end. * - * [oldURL, url, nextURLs[0], nextURLs[1], nextURLs[2]] + * We can revoke url[0] when we shift it out because we know it is not being + * used anymore. * - * Note that for small albums of only a few renderable items, these URLs - * might possibly be the same as each other. + * To start off, we put two dummy entries, empty strings, in the array to + * avoid needing to special case the first render too much. */ - const canRevokeURL = (u: string) => ![url, ...nextURLs].includes(u); + const urls: string[] = ["", ""]; while (true) { const collection = await getCastCollection(castToken, collectionKey); @@ -127,9 +116,9 @@ export const renderableImageURLs = async function* (castData: CastData) { outer: for (const file of files) { if (!isFileEligibleForCast(file)) continue; - while (nextURLs.length < 3) { + while (urls.length < 4) { try { - nextURLs.push(await createRenderableURL(castToken, file)); + urls.push(await createRenderableURL(castToken, file)); } catch (e) { log.error("Skipping unrenderable file", e); continue outer; @@ -138,16 +127,10 @@ export const renderableImageURLs = async function* (castData: CastData) { haveEligibleFiles = true; - const urlPair: RenderableImageURLPair = [ - ensure(nextURLs[0]), - ensure(nextURLs[1]), - ]; - // Revoke the oldest one. - if (oldURL && canRevokeURL(oldURL)) URL.revokeObjectURL(oldURL); - // Slide the window to the right. - oldURL = url; - url = nextURLs.shift(); - yield urlPair; + const oldestURL = urls.shift(); + if (oldestURL) URL.revokeObjectURL(oldestURL); + + yield [ensure(urls[0]), ensure(urls[1])]; } // This collection does not have any files that we can show. From 2ae2b73c597575a92538ad5087613d706e28c3df Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 17:15:38 +0530 Subject: [PATCH 232/367] Simplify revoke --- web/apps/cast/src/services/cast.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 2ed6f1d21..83e67b335 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -101,10 +101,12 @@ export const renderableImageURLs = async function* (castData: CastData) { * We can revoke url[0] when we shift it out because we know it is not being * used anymore. * - * To start off, we put two dummy entries, empty strings, in the array to - * avoid needing to special case the first render too much. + * We need to special case the first two renders to avoid revoking the + * initial URLs that are displayed the first two times. This results in a + * memory leak of the very first objectURL that we display. */ - const urls: string[] = ["", ""]; + const urls: string[] = [""]; + let i = 0; while (true) { const collection = await getCastCollection(castToken, collectionKey); @@ -113,24 +115,28 @@ export const renderableImageURLs = async function* (castData: CastData) { let haveEligibleFiles = false; - outer: for (const file of files) { + for (const file of files) { if (!isFileEligibleForCast(file)) continue; - while (urls.length < 4) { + if (urls.length < 4) { try { urls.push(await createRenderableURL(castToken, file)); + haveEligibleFiles = true; } catch (e) { log.error("Skipping unrenderable file", e); - continue outer; } + continue; } - haveEligibleFiles = true; - const oldestURL = urls.shift(); - if (oldestURL) URL.revokeObjectURL(oldestURL); + if (oldestURL && i !== 1) URL.revokeObjectURL(oldestURL); + i += 1; - yield [ensure(urls[0]), ensure(urls[1])]; + const urlPair: RenderableImageURLPair = [ + ensure(urls[0]), + ensure(urls[1]), + ]; + yield urlPair; } // This collection does not have any files that we can show. From 42089403fd8a735c90b3d485e38093573dda2bae Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 17:32:02 +0530 Subject: [PATCH 233/367] Account for processing in the timings --- web/apps/cast/src/pages/slideshow.tsx | 112 ++++++-------------------- web/apps/cast/src/services/cast.ts | 13 +++ 2 files changed, 36 insertions(+), 89 deletions(-) diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index 24f1bb4c5..bd3339b42 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -16,101 +16,35 @@ export default function Slideshow() { const pair = () => router.push("/"); useEffect(() => { - let urlGenerator: AsyncGenerator<[string, string], void>; - try { - urlGenerator = renderableImageURLs(readCastData()); - } catch (e) { - log.error("Failed to prepare generator", e); - pair(); - } + let stop = false; - advance(urlGenerator); + const loop = async () => { + try { + const urlGenerator = renderableImageURLs(readCastData()); + while (!stop) { + const { value: urls, done } = await urlGenerator.next(); + if (done) { + log.warn("Empty collection"); + pair(); + return; + } - const interval = window.setInterval(() => { - advance(urlGenerator); - }, 10000); - - return () => clearInterval(interval); - }, []); - - const advance = async ( - urlGenerator: AsyncGenerator<[string, string], void>, - ) => { - try { - const { value: urls, done } = await urlGenerator.next(); - if (done) { - log.warn("Empty collection"); + setImageURL(urls[0]); + setNextImageURL(urls[1]); + setLoading(false); + } + } catch (e) { + log.error("Failed to prepare generator", e); pair(); - return; } + }; - setImageURL(urls[0]); - setNextImageURL(urls[1]); - setLoading(false); - } catch (e) { - log.error("Failed to generate image URL", e); - pair(); - } - }; + void loop(); - /* - const showNextSlide = async () => { - try { - console.log("showNextSlide"); - const currentIndex = collectionFiles.findIndex( - (file) => file.id === currentFileId, - ); - - console.log( - "showNextSlide-index", - currentIndex, - collectionFiles.length, - ); - - const nextIndex = (currentIndex + 1) % collectionFiles.length; - const nextNextIndex = (nextIndex + 1) % collectionFiles.length; - - console.log( - "showNextSlide-nextIndex and nextNextIndex", - nextIndex, - nextNextIndex, - ); - - const nextFile = collectionFiles[nextIndex]; - const nextNextFile = collectionFiles[nextNextIndex]; - - let nextURL: string; - try { - nextURL = await createRenderableURL(nextFile, castToken); - } catch (e) { - console.log("error in nextUrl", e); - return; - } - - let nextNextURL: string; - try { - nextNextURL = await createRenderableURL( - nextNextFile, - castToken, - ); - } catch (e) { - console.log("error in nextNextURL", e); - return; - } - - setLoading(false); - setCurrentFileId(nextFile.id); - // TODO: These might be the same in case the album has < 3 files - // so commenting this out for now. - // if (currentFileURL) URL.revokeObjectURL(currentFileURL); - setCurrentFileURL(nextURL); - // if (nextFileURL) URL.revokeObjectURL(nextFileURL); - setNextFileURL(nextNextURL); - } catch (e) { - console.log("error in showNextSlide", e); - } - }; - */ + return () => { + stop = true; + }; + }, []); if (loading) return ; diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 83e67b335..4d01d0f2f 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -9,6 +9,7 @@ import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; import { getCastFileURL, getEndpoint } from "@ente/shared/network/api"; import localForage from "@ente/shared/storage/localForage"; +import { wait } from "@ente/shared/utils"; import { detectMediaMIMEType } from "services/detect-type"; import { Collection, CollectionPublicMagicMetadata } from "types/collection"; import { @@ -107,6 +108,13 @@ export const renderableImageURLs = async function* (castData: CastData) { */ const urls: string[] = [""]; let i = 0; + /** + * Time when we last yielded. + * + * We use this to keep an roughly periodic spacing between yields that + * accounts for the time we spend fetching and processing the images. + */ + let lastYieldTime = Date.now(); while (true) { const collection = await getCastCollection(castToken, collectionKey); @@ -136,6 +144,11 @@ export const renderableImageURLs = async function* (castData: CastData) { ensure(urls[0]), ensure(urls[1]), ]; + + const elapsedTime = Date.now() - lastYieldTime; + if (elapsedTime < 10000) await wait(10000 - elapsedTime); + + lastYieldTime = Date.now(); yield urlPair; } From 3c000b5c625f691885d16311eff238dfdd0c3538 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 17:48:37 +0530 Subject: [PATCH 234/367] First time --- web/apps/cast/src/services/cast.ts | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 4d01d0f2f..fcb96447a 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -108,6 +108,11 @@ export const renderableImageURLs = async function* (castData: CastData) { */ const urls: string[] = [""]; let i = 0; + + /** + * Number of milliseconds to keep the slide on the screen. + */ + const slideDuration = 10000; /* 10 s */ /** * Time when we last yielded. * @@ -116,6 +121,11 @@ export const renderableImageURLs = async function* (castData: CastData) { */ let lastYieldTime = Date.now(); + // The first time around advance the lastYieldTime into the future so that + // we don't wait around too long for the first slide (we do want to wait a + // bit, for the user to see the checkmark animation as reassurance). + lastYieldTime += 7500; /* 7.5 s */ + while (true) { const collection = await getCastCollection(castToken, collectionKey); await syncPublicFiles(castToken, collection, () => {}); @@ -126,16 +136,16 @@ export const renderableImageURLs = async function* (castData: CastData) { for (const file of files) { if (!isFileEligibleForCast(file)) continue; - if (urls.length < 4) { - try { - urls.push(await createRenderableURL(castToken, file)); - haveEligibleFiles = true; - } catch (e) { - log.error("Skipping unrenderable file", e); - } + try { + urls.push(await createRenderableURL(castToken, file)); + haveEligibleFiles = true; + } catch (e) { + log.error("Skipping unrenderable file", e); continue; } + if (urls.length < 4) continue; + const oldestURL = urls.shift(); if (oldestURL && i !== 1) URL.revokeObjectURL(oldestURL); i += 1; @@ -146,7 +156,8 @@ export const renderableImageURLs = async function* (castData: CastData) { ]; const elapsedTime = Date.now() - lastYieldTime; - if (elapsedTime < 10000) await wait(10000 - elapsedTime); + if (elapsedTime > 0 && elapsedTime < slideDuration) + await wait(slideDuration - elapsedTime); lastYieldTime = Date.now(); yield urlPair; From 094f1daef3a8d90fbd2000c43f9ee6c7db5feb91 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 18:22:36 +0530 Subject: [PATCH 235/367] Get all files --- web/apps/cast/src/services/cast.ts | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index fcb96447a..3f2439af3 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -209,6 +209,37 @@ const getCastCollection = async ( }; }; +/** + * Fetch the list of non-deleted files in the given collection. + * + * The returned files are not decrypted yet, so their metadata will not be + * readable. + */ +const getEncryptedCollectionFiles = async ( + castToken: string, +): Promise => { + let files: EncryptedEnteFile[] = []; + let sinceTime = 0; + let resp; + do { + resp = await HTTPService.get( + `${getEndpoint()}/cast/diff`, + { sinceTime }, + { + "Cache-Control": "no-cache", + "X-Cast-Access-Token": castToken, + }, + ); + const diff = resp.data.diff; + files = files.concat(diff.filter((file) => !file.isDeleted)); + sinceTime = diff.reduce( + (max, file) => Math.max(max, file.updationTime), + sinceTime, + ); + } while (resp.data.hasMore); + return files; +}; + const syncPublicFiles = async ( castToken: string, collection: Collection, From ee503b3bce55a5fb38e3edcbb9f5153f8f00916d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 18:37:39 +0530 Subject: [PATCH 236/367] Shuffle --- web/packages/utils/array.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 web/packages/utils/array.ts diff --git a/web/packages/utils/array.ts b/web/packages/utils/array.ts new file mode 100644 index 000000000..d7c927d30 --- /dev/null +++ b/web/packages/utils/array.ts @@ -0,0 +1,15 @@ +/** + * Shuffle. + * + * Return a new array containing the shuffled elements of the given array. + * + * The algorithm used is not the most efficient, but is effectively a one-liner + * whilst being reasonably efficient. To each element we assign a random key, + * then we sort by this key. Since the key is random, the sorted array will have + * the original elements in a random order. + */ +export const shuffle = (xs: T[]) => + xs + .map((x) => [Math.random(), x]) + .sort() + .map(([, x]) => x); From 575e4809d82ec9e586e4953bf5c022a72a643bca Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 18:48:43 +0530 Subject: [PATCH 237/367] Random order --- web/apps/cast/src/services/cast.ts | 73 +++++++++++++++++++++++++++++- web/packages/utils/array.ts | 4 +- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 3f2439af3..535de7d0d 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -3,6 +3,7 @@ import { isNonWebImageFileExtension } from "@/media/formats"; import { decodeLivePhoto } from "@/media/live-photo"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; +import { shuffled } from "@/utils/array"; import { ensure, ensureString } from "@/utils/ensure"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; @@ -129,11 +130,15 @@ export const renderableImageURLs = async function* (castData: CastData) { while (true) { const collection = await getCastCollection(castToken, collectionKey); await syncPublicFiles(castToken, collection, () => {}); - const files = await getLocalFiles(String(collection.id)); + const encryptedFiles = shuffled( + await getEncryptedCollectionFiles(castToken), + ); let haveEligibleFiles = false; - for (const file of files) { + for (const encryptedFile of encryptedFiles) { + const file = await decryptEnteFile(encryptedFile, collectionKey); + if (!isFileEligibleForCast(file)) continue; try { @@ -240,6 +245,70 @@ const getEncryptedCollectionFiles = async ( return files; }; +/** + * Decrypt the given {@link EncryptedEnteFile}, returning a {@link EnteFile}. + */ +const decryptEnteFile = async ( + encryptedFile: EncryptedEnteFile, + collectionKey: string, +): Promise => { + const worker = await ComlinkCryptoWorker.getInstance(); + const { + encryptedKey, + keyDecryptionNonce, + metadata, + magicMetadata, + pubMagicMetadata, + ...restFileProps + } = encryptedFile; + const fileKey = await worker.decryptB64( + encryptedKey, + keyDecryptionNonce, + collectionKey, + ); + const fileMetadata = await worker.decryptMetadata( + metadata.encryptedData, + metadata.decryptionHeader, + fileKey, + ); + let fileMagicMetadata: FileMagicMetadata; + let filePubMagicMetadata: FilePublicMagicMetadata; + if (magicMetadata?.data) { + fileMagicMetadata = { + ...encryptedFile.magicMetadata, + data: await worker.decryptMetadata( + magicMetadata.data, + magicMetadata.header, + fileKey, + ), + }; + } + if (pubMagicMetadata?.data) { + filePubMagicMetadata = { + ...pubMagicMetadata, + data: await worker.decryptMetadata( + pubMagicMetadata.data, + pubMagicMetadata.header, + fileKey, + ), + }; + } + const file = { + ...restFileProps, + key: fileKey, + metadata: fileMetadata, + magicMetadata: fileMagicMetadata, + pubMagicMetadata: filePubMagicMetadata, + }; + if (file.pubMagicMetadata?.data.editedTime) { + file.metadata.creationTime = file.pubMagicMetadata.data.editedTime; + } + if (file.pubMagicMetadata?.data.editedName) { + file.metadata.title = file.pubMagicMetadata.data.editedName; + } + return file; +}; + const syncPublicFiles = async ( castToken: string, collection: Collection, diff --git a/web/packages/utils/array.ts b/web/packages/utils/array.ts index d7c927d30..660aef679 100644 --- a/web/packages/utils/array.ts +++ b/web/packages/utils/array.ts @@ -8,8 +8,8 @@ * then we sort by this key. Since the key is random, the sorted array will have * the original elements in a random order. */ -export const shuffle = (xs: T[]) => +export const shuffled = (xs: T[]) => xs .map((x) => [Math.random(), x]) .sort() - .map(([, x]) => x); + .map(([, x]) => x) as T[]; From 6a6b77f1d2a7e0a2014aefe57dab55840d2f3424 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 18:56:52 +0530 Subject: [PATCH 238/367] Remove unused code --- web/apps/cast/src/services/cast.ts | 300 ----------------------------- 1 file changed, 300 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 535de7d0d..6d1a6f36a 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -6,13 +6,10 @@ import log from "@/next/log"; import { shuffled } from "@/utils/array"; import { ensure, ensureString } from "@/utils/ensure"; import ComlinkCryptoWorker from "@ente/shared/crypto"; -import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; import { getCastFileURL, getEndpoint } from "@ente/shared/network/api"; -import localForage from "@ente/shared/storage/localForage"; import { wait } from "@ente/shared/utils"; import { detectMediaMIMEType } from "services/detect-type"; -import { Collection, CollectionPublicMagicMetadata } from "types/collection"; import { EncryptedEnteFile, EnteFile, @@ -20,13 +17,6 @@ import { FilePublicMagicMetadata, } from "types/file"; -export interface SavedCollectionFiles { - collectionLocalID: string; - files: EnteFile[]; -} - -const COLLECTION_FILES_TABLE = "collection-files"; - /** * Save the data received after pairing with a sender into local storage. * @@ -128,8 +118,6 @@ export const renderableImageURLs = async function* (castData: CastData) { lastYieldTime += 7500; /* 7.5 s */ while (true) { - const collection = await getCastCollection(castToken, collectionKey); - await syncPublicFiles(castToken, collection, () => {}); const encryptedFiles = shuffled( await getEncryptedCollectionFiles(castToken), ); @@ -173,47 +161,6 @@ export const renderableImageURLs = async function* (castData: CastData) { } }; -const getCastCollection = async ( - castToken: string, - collectionKey: string, -): Promise => { - const resp = await HTTPService.get(`${getEndpoint()}/cast/info`, null, { - "Cache-Control": "no-cache", - "X-Cast-Access-Token": castToken, - }); - - const collection = resp.data.collection; - - const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - - const collectionName = - collection.name || - (await cryptoWorker.decryptToUTF8( - collection.encryptedName, - collection.nameDecryptionNonce, - collectionKey, - )); - - let collectionPublicMagicMetadata: CollectionPublicMagicMetadata; - if (collection.pubMagicMetadata?.data) { - collectionPublicMagicMetadata = { - ...collection.pubMagicMetadata, - data: await cryptoWorker.decryptMetadata( - collection.pubMagicMetadata.data, - collection.pubMagicMetadata.header, - collectionKey, - ), - }; - } - - return { - ...collection, - name: collectionName, - key: collectionKey, - pubMagicMetadata: collectionPublicMagicMetadata, - }; -}; - /** * Fetch the list of non-deleted files in the given collection. * @@ -309,253 +256,6 @@ const decryptEnteFile = async ( return file; }; -const syncPublicFiles = async ( - castToken: string, - collection: Collection, - setPublicFiles: (files: EnteFile[]) => void, -) => { - let files: EnteFile[] = []; - const sortAsc = collection?.pubMagicMetadata?.data.asc ?? false; - const collectionUID = String(collection.id); - const localFiles = await getLocalFiles(collectionUID); - files = [...files, ...localFiles]; - try { - const lastSyncTime = await getSyncTime(collectionUID); - if (collection.updationTime === lastSyncTime) { - return sortFiles(files, sortAsc); - } - const fetchedFiles = await fetchFiles( - castToken, - collection, - lastSyncTime, - files, - setPublicFiles, - ); - - files = [...files, ...fetchedFiles]; - const latestVersionFiles = new Map(); - files.forEach((file) => { - const uid = `${file.collectionID}-${file.id}`; - if ( - !latestVersionFiles.has(uid) || - latestVersionFiles.get(uid).updationTime < file.updationTime - ) { - latestVersionFiles.set(uid, file); - } - }); - files = []; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const [_, file] of latestVersionFiles) { - if (file.isDeleted) { - continue; - } - files.push(file); - } - await savecollectionFiles(collectionUID, files); - await updateSyncTime(collectionUID, collection.updationTime); - setPublicFiles([...sortFiles(mergeMetadata(files), sortAsc)]); - } catch (e) { - const parsedError = parseSharingErrorCodes(e); - log.error("failed to sync shared collection files", e); - if (parsedError.message === CustomError.TOKEN_EXPIRED) { - throw e; - } - } - return [...sortFiles(mergeMetadata(files), sortAsc)]; -}; - -const getLastSyncKey = (collectionUID: string) => `${collectionUID}-time`; - -export const getLocalFiles = async ( - collectionUID: string, -): Promise => { - const localSavedcollectionFiles = - (await localForage.getItem( - COLLECTION_FILES_TABLE, - )) || []; - const matchedCollection = localSavedcollectionFiles.find( - (item) => item.collectionLocalID === collectionUID, - ); - return matchedCollection?.files || []; -}; - -const savecollectionFiles = async ( - collectionUID: string, - files: EnteFile[], -) => { - const collectionFiles = - (await localForage.getItem( - COLLECTION_FILES_TABLE, - )) || []; - await localForage.setItem( - COLLECTION_FILES_TABLE, - dedupeCollectionFiles([ - { collectionLocalID: collectionUID, files }, - ...collectionFiles, - ]), - ); -}; - -const dedupeCollectionFiles = (collectionFiles: SavedCollectionFiles[]) => { - const keySet = new Set([]); - return collectionFiles.filter(({ collectionLocalID: collectionUID }) => { - if (!keySet.has(collectionUID)) { - keySet.add(collectionUID); - return true; - } else { - return false; - } - }); -}; - -async function getSyncTime(collectionUID: string): Promise { - const lastSyncKey = getLastSyncKey(collectionUID); - const lastSyncTime = await localForage.getItem(lastSyncKey); - return lastSyncTime ?? 0; -} - -const updateSyncTime = async (collectionUID: string, time: number) => - await localForage.setItem(getLastSyncKey(collectionUID), time); - -const fetchFiles = async ( - castToken: string, - collection: Collection, - sinceTime: number, - files: EnteFile[], - setPublicFiles: (files: EnteFile[]) => void, -): Promise => { - const sortAsc = collection?.pubMagicMetadata?.data.asc ?? false; - let decryptedFiles: EnteFile[] = []; - let time = sinceTime; - let resp; - do { - resp = await HTTPService.get( - `${getEndpoint()}/cast/diff`, - { - sinceTime: time, - }, - { - "Cache-Control": "no-cache", - "X-Cast-Access-Token": castToken, - }, - ); - decryptedFiles = [ - ...decryptedFiles, - ...(await Promise.all( - resp.data.diff.map(async (file: EncryptedEnteFile) => { - if (!file.isDeleted) { - return await decryptFile(file, collection.key); - } else { - return file; - } - }) as Promise[], - )), - ]; - - if (resp.data.diff.length) { - time = resp.data.diff.slice(-1)[0].updationTime; - } - setPublicFiles( - sortFiles( - mergeMetadata( - [...(files || []), ...decryptedFiles].filter( - (item) => !item.isDeleted, - ), - ), - sortAsc, - ), - ); - } while (resp.data.hasMore); - return decryptedFiles; -}; - -export function sortFiles(files: EnteFile[], sortAsc = false) { - // sort based on the time of creation time of the file, - // for files with same creation time, sort based on the time of last modification - const factor = sortAsc ? -1 : 1; - return files.sort((a, b) => { - if (a.metadata.creationTime === b.metadata.creationTime) { - return ( - factor * - (b.metadata.modificationTime - a.metadata.modificationTime) - ); - } - return factor * (b.metadata.creationTime - a.metadata.creationTime); - }); -} - -export async function decryptFile( - file: EncryptedEnteFile, - collectionKey: string, -): Promise { - try { - const worker = await ComlinkCryptoWorker.getInstance(); - const { - encryptedKey, - keyDecryptionNonce, - metadata, - magicMetadata, - pubMagicMetadata, - ...restFileProps - } = file; - const fileKey = await worker.decryptB64( - encryptedKey, - keyDecryptionNonce, - collectionKey, - ); - const fileMetadata = await worker.decryptMetadata( - metadata.encryptedData, - metadata.decryptionHeader, - fileKey, - ); - let fileMagicMetadata: FileMagicMetadata; - let filePubMagicMetadata: FilePublicMagicMetadata; - if (magicMetadata?.data) { - fileMagicMetadata = { - ...file.magicMetadata, - data: await worker.decryptMetadata( - magicMetadata.data, - magicMetadata.header, - fileKey, - ), - }; - } - if (pubMagicMetadata?.data) { - filePubMagicMetadata = { - ...pubMagicMetadata, - data: await worker.decryptMetadata( - pubMagicMetadata.data, - pubMagicMetadata.header, - fileKey, - ), - }; - } - return { - ...restFileProps, - key: fileKey, - metadata: fileMetadata, - magicMetadata: fileMagicMetadata, - pubMagicMetadata: filePubMagicMetadata, - }; - } catch (e) { - log.error("file decryption failed", e); - throw e; - } -} - -export function mergeMetadata(files: EnteFile[]): EnteFile[] { - return files.map((file) => { - if (file.pubMagicMetadata?.data.editedTime) { - file.metadata.creationTime = file.pubMagicMetadata.data.editedTime; - } - if (file.pubMagicMetadata?.data.editedName) { - file.metadata.title = file.pubMagicMetadata.data.editedName; - } - - return file; - }); -} - const isFileEligibleForCast = (file: EnteFile) => { if (!isImageOrLivePhoto(file)) return false; if (file.info.fileSize > 100 * 1024 * 1024) return false; From 34c30e45200cb3a592fa1bdfa417b67b2d559a4a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 18:59:29 +0530 Subject: [PATCH 239/367] Remove unused file --- web/apps/cast/src/types/collection.ts | 100 -------------------------- 1 file changed, 100 deletions(-) delete mode 100644 web/apps/cast/src/types/collection.ts diff --git a/web/apps/cast/src/types/collection.ts b/web/apps/cast/src/types/collection.ts deleted file mode 100644 index c495937ae..000000000 --- a/web/apps/cast/src/types/collection.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { EnteFile } from "types/file"; -import { - EncryptedMagicMetadata, - MagicMetadataCore, - SUB_TYPE, - VISIBILITY_STATE, -} from "types/magicMetadata"; - -export enum COLLECTION_ROLE { - VIEWER = "VIEWER", - OWNER = "OWNER", - COLLABORATOR = "COLLABORATOR", - UNKNOWN = "UNKNOWN", -} - -export interface CollectionUser { - id: number; - email: string; - role: COLLECTION_ROLE; -} - -enum CollectionType { - folder = "folder", - favorites = "favorites", - album = "album", - uncategorized = "uncategorized", -} - -export interface EncryptedCollection { - id: number; - owner: CollectionUser; - // collection name was unencrypted in the past, so we need to keep it as optional - name?: string; - encryptedKey: string; - keyDecryptionNonce: string; - encryptedName: string; - nameDecryptionNonce: string; - type: CollectionType; - attributes: collectionAttributes; - sharees: CollectionUser[]; - publicURLs?: unknown; - updationTime: number; - isDeleted: boolean; - magicMetadata: EncryptedMagicMetadata; - pubMagicMetadata: EncryptedMagicMetadata; - sharedMagicMetadata: EncryptedMagicMetadata; -} - -export interface Collection - extends Omit< - EncryptedCollection, - | "encryptedKey" - | "keyDecryptionNonce" - | "encryptedName" - | "nameDecryptionNonce" - | "magicMetadata" - | "pubMagicMetadata" - | "sharedMagicMetadata" - > { - key: string; - name: string; - magicMetadata: CollectionMagicMetadata; - pubMagicMetadata: CollectionPublicMagicMetadata; - sharedMagicMetadata: CollectionShareeMagicMetadata; -} - -// define a method on Collection interface to return the sync key as collection.id-time -// this is used to store the last sync time of a collection in local storage - -export interface collectionAttributes { - encryptedPath?: string; - pathDecryptionNonce?: string; -} - -export type CollectionToFileMap = Map; - -export interface CollectionMagicMetadataProps { - visibility?: VISIBILITY_STATE; - subType?: SUB_TYPE; - order?: number; -} - -export type CollectionMagicMetadata = - MagicMetadataCore; - -export interface CollectionShareeMetadataProps { - visibility?: VISIBILITY_STATE; -} -export type CollectionShareeMagicMetadata = - MagicMetadataCore; - -export interface CollectionPublicMagicMetadataProps { - asc?: boolean; - coverID?: number; -} - -export type CollectionPublicMagicMetadata = - MagicMetadataCore; - -export type CollectionFilesCount = Map; From fa8f229fcb1f18b0e405c4c6ba0a42efe8429043 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 19:02:07 +0530 Subject: [PATCH 240/367] Help tsc --- web/apps/cast/src/services/cast.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/apps/cast/src/services/cast.ts b/web/apps/cast/src/services/cast.ts index 6d1a6f36a..8ead8962a 100644 --- a/web/apps/cast/src/services/cast.ts +++ b/web/apps/cast/src/services/cast.ts @@ -183,9 +183,9 @@ const getEncryptedCollectionFiles = async ( }, ); const diff = resp.data.diff; - files = files.concat(diff.filter((file) => !file.isDeleted)); + files = files.concat(diff.filter((file: EnteFile) => !file.isDeleted)); sinceTime = diff.reduce( - (max, file) => Math.max(max, file.updationTime), + (max: number, file: EnteFile) => Math.max(max, file.updationTime), sinceTime, ); } while (resp.data.hasMore); From c92380eb10e305c627f6d8df7c750d86ef46a125 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 4 May 2024 19:04:09 +0530 Subject: [PATCH 241/367] Once over --- web/apps/cast/src/pages/index.tsx | 2 +- web/apps/cast/src/services/pair.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index dd0389949..f84e1d64f 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -52,7 +52,7 @@ export default function Index() { storeCastData(data); await router.push("/slideshow"); } catch (e) { - console.log("Failed to get cast data", e); + log.error("Failed to get cast data", e); // Start again from the beginning. setPairingCode(undefined); init(); diff --git a/web/apps/cast/src/services/pair.ts b/web/apps/cast/src/services/pair.ts index 31419927d..66f9feddd 100644 --- a/web/apps/cast/src/services/pair.ts +++ b/web/apps/cast/src/services/pair.ts @@ -116,11 +116,15 @@ export const advertiseCode = ( const namespace = "urn:x-cast:pair-request"; const options = new cast.framework.CastReceiverOptions(); - // TODO(MR): Are any of these options required? - options.maxInactivity = 3600; + // Do not automatically close the connection when the sender disconnects. + options.maxInactivity = 3600; /* 1 hour */ + // TODO:Is this required? The docs say "(The default type of a message bus + // is JSON; if not provided here)." options.customNamespaces = Object.assign({}); options.customNamespaces[namespace] = cast.framework.system.MessageType.JSON; + // TODO: This looks like the only one needed, but a comment with the reason + // might be good. options.disableIdleTimeout = true; // Reply with the code that we have if anyone asks over Chromecast. @@ -144,7 +148,7 @@ export const advertiseCode = ( ); // Shutdown ourselves if the sender disconnects. - // TODO(MR): Does it? + // TODO(MR): I assume the page reloads on shutdown. Is that correct? context.addEventListener( cast.framework.system.EventType.SENDER_DISCONNECTED, () => context.stop(), From 6041cd73bdeacd48039860597dc7164f6f9a797f Mon Sep 17 00:00:00 2001 From: Vishnu Mohandas Date: Sun, 5 May 2024 14:56:43 +0530 Subject: [PATCH 242/367] Fix typo --- mobile/lib/ui/notification/update/change_log_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/ui/notification/update/change_log_page.dart b/mobile/lib/ui/notification/update/change_log_page.dart index 1216b3219..90430fae2 100644 --- a/mobile/lib/ui/notification/update/change_log_page.dart +++ b/mobile/lib/ui/notification/update/change_log_page.dart @@ -124,7 +124,7 @@ class _ChangeLogPageState extends State { ), ChangeLogEntry( "Organize shared photos", - "You can now add shared items to your favorites to any of your personal albums. Ente will create a copy that is fully owned by you and can be organized to your liking.", + "You can now add shared items to your favorites or to any of your personal albums. Ente will create a copy that is fully owned by you and can be organized to your liking.", ), ChangeLogEntry( "Download multiple items", From 0194fe65c75fca50d66500519f70bc1a64edd897 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 09:48:38 +0530 Subject: [PATCH 243/367] Namespace Crowdin PR branches --- .github/workflows/auth-crowdin.yml | 2 +- .github/workflows/mobile-crowdin.yml | 2 +- .github/workflows/web-crowdin.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/auth-crowdin.yml b/.github/workflows/auth-crowdin.yml index 811def939..bd92f1459 100644 --- a/.github/workflows/auth-crowdin.yml +++ b/.github/workflows/auth-crowdin.yml @@ -30,7 +30,7 @@ jobs: upload_sources: true upload_translations: false download_translations: true - localization_branch_name: crowdin-translations-auth + localization_branch_name: translations/auth create_pull_request: true skip_untranslated_strings: true pull_request_title: "[auth] New translations" diff --git a/.github/workflows/mobile-crowdin.yml b/.github/workflows/mobile-crowdin.yml index 5c52b59ad..556ac45f2 100644 --- a/.github/workflows/mobile-crowdin.yml +++ b/.github/workflows/mobile-crowdin.yml @@ -30,7 +30,7 @@ jobs: upload_sources: true upload_translations: false download_translations: true - localization_branch_name: crowdin-translations-mobile + localization_branch_name: translations/mobile create_pull_request: true skip_untranslated_strings: true pull_request_title: "[mobile] New translations" diff --git a/.github/workflows/web-crowdin.yml b/.github/workflows/web-crowdin.yml index d98685065..b20b19ce3 100644 --- a/.github/workflows/web-crowdin.yml +++ b/.github/workflows/web-crowdin.yml @@ -36,7 +36,7 @@ jobs: upload_sources: true upload_translations: false download_translations: true - localization_branch_name: crowdin-translations-web + localization_branch_name: translations/web create_pull_request: true skip_untranslated_strings: true pull_request_title: "[web] New translations" From 51b103a6205cd7b0fbb6c2ad437e28b7f5fb94e5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 09:52:00 +0530 Subject: [PATCH 244/367] Allow force deploying cast and accounts (while they're still in dev) --- .github/workflows/web-deploy-accounts.yml | 2 +- .github/workflows/web-deploy-cast.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/web-deploy-accounts.yml b/.github/workflows/web-deploy-accounts.yml index 61411cac6..33da5ee6f 100644 --- a/.github/workflows/web-deploy-accounts.yml +++ b/.github/workflows/web-deploy-accounts.yml @@ -3,7 +3,7 @@ name: "Deploy (accounts)" on: push: # Run workflow on pushes to the deploy/accounts - branches: [deploy/accounts] + branches: [deploy/accounts, deploy-f/accounts] jobs: deploy: diff --git a/.github/workflows/web-deploy-cast.yml b/.github/workflows/web-deploy-cast.yml index c5bbca954..01e17486d 100644 --- a/.github/workflows/web-deploy-cast.yml +++ b/.github/workflows/web-deploy-cast.yml @@ -3,7 +3,7 @@ name: "Deploy (cast)" on: push: # Run workflow on pushes to the deploy/cast - branches: [deploy/cast] + branches: [deploy/cast, deploy-f/cast] jobs: deploy: From 6df40fe9503dba95698ba86cdbc6e8510a5a4f27 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 6 May 2024 04:23:54 +0000 Subject: [PATCH 245/367] New Crowdin translations by GitHub Action --- mobile/lib/l10n/intl_pt.arb | 4 ++-- mobile/lib/l10n/intl_zh.arb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mobile/lib/l10n/intl_pt.arb b/mobile/lib/l10n/intl_pt.arb index 16a0ea753..08d932cda 100644 --- a/mobile/lib/l10n/intl_pt.arb +++ b/mobile/lib/l10n/intl_pt.arb @@ -1216,8 +1216,8 @@ "customEndpoint": "Conectado a {endpoint}", "createCollaborativeLink": "Criar link colaborativo", "search": "Pesquisar", - "autoPairGoogle": "O Pareamento Automático requer a conexão com servidores do Google e só funciona com dispositivos Chromecast. O Google não receberá dados confidenciais, como suas fotos.", - "manualPairDesc": "Parear com o PIN funciona para qualquer dispositivo de tela grande onde você deseja reproduzir seu álbum.", + "autoPairDesc": "O pareamento automático funciona apenas com dispositivos que suportam o Chromecast.", + "manualPairDesc": "Parear com o PIN funciona com qualquer tela que você deseja ver o seu álbum ativado.", "connectToDevice": "Conectar ao dispositivo", "autoCastDialogBody": "Você verá dispositivos disponíveis para transmitir aqui.", "autoCastiOSPermission": "Certifique-se de que as permissões de Rede local estão ativadas para o aplicativo de Fotos Ente, em Configurações.", diff --git a/mobile/lib/l10n/intl_zh.arb b/mobile/lib/l10n/intl_zh.arb index 370bb6a3c..9a854a4f0 100644 --- a/mobile/lib/l10n/intl_zh.arb +++ b/mobile/lib/l10n/intl_zh.arb @@ -1216,8 +1216,8 @@ "customEndpoint": "已连接至 {endpoint}", "createCollaborativeLink": "创建协作链接", "search": "搜索", - "autoPairGoogle": "自动配对需要连接到 Google 服务器,且仅适用于支持 Chromecast 的设备。Google 不会接收敏感数据,例如您的照片。", - "manualPairDesc": "用 PIN 配对适用于任何大屏幕设备,您可以在这些设备上播放您的相册。", + "autoPairDesc": "自动配对仅适用于支持 Chromecast 的设备。", + "manualPairDesc": "用 PIN 码配对适用于您希望在其上查看相册的任何屏幕。", "connectToDevice": "连接到设备", "autoCastDialogBody": "您将在此处看到可用的 Cast 设备。", "autoCastiOSPermission": "请确保已在“设置”中为 Ente Photos 应用打开本地网络权限。", From e59007590dc07e03ac8b283eab0c2418a7c92302 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 6 May 2024 04:23:57 +0000 Subject: [PATCH 246/367] New Crowdin translations by GitHub Action --- .../next/locales/de-DE/translation.json | 140 +++++++++--------- .../next/locales/pt-BR/translation.json | 8 +- .../next/locales/zh-CN/translation.json | 8 +- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/web/packages/next/locales/de-DE/translation.json b/web/packages/next/locales/de-DE/translation.json index 21875c549..ceb7cf98d 100644 --- a/web/packages/next/locales/de-DE/translation.json +++ b/web/packages/next/locales/de-DE/translation.json @@ -340,11 +340,11 @@ "UPDATE_CREATION_TIME_COMPLETED": "Alle Dateien erfolgreich aktualisiert", "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "Aktualisierung der Dateizeit für einige Dateien fehlgeschlagen, bitte versuche es erneut", "CAPTION_CHARACTER_LIMIT": "Maximal 5000 Zeichen", - "DATE_TIME_ORIGINAL": "", - "DATE_TIME_DIGITIZED": "", - "METADATA_DATE": "", + "DATE_TIME_ORIGINAL": "EXIF:DateTimeOriginal", + "DATE_TIME_DIGITIZED": "EXIF:DateTimeDigitized", + "METADATA_DATE": "EXIF:MetadataDate", "CUSTOM_TIME": "Benutzerdefinierte Zeit", - "REOPEN_PLAN_SELECTOR_MODAL": "", + "REOPEN_PLAN_SELECTOR_MODAL": "Aboauswahl erneut öffnen", "OPEN_PLAN_SELECTOR_MODAL_FAILED": "Fehler beim Öffnen der Pläne", "INSTALL": "Installieren", "SHARING_DETAILS": "Details teilen", @@ -374,7 +374,7 @@ "ADD_MORE": "Mehr hinzufügen", "VIEWERS": "Zuschauer", "OR_ADD_EXISTING": "Oder eine Vorherige auswählen", - "REMOVE_PARTICIPANT_MESSAGE": "", + "REMOVE_PARTICIPANT_MESSAGE": "

{{selectedEmail}} wird vom Album entfernt

Alle Bilder von {{selectedEmail}} werden ebenfalls aus dem Album entfernt

", "NOT_FOUND": "404 - Nicht gefunden", "LINK_EXPIRED": "Link ist abgelaufen", "LINK_EXPIRED_MESSAGE": "Dieser Link ist abgelaufen oder wurde deaktiviert!", @@ -388,9 +388,9 @@ "LINK_EXPIRY": "Ablaufdatum des Links", "NEVER": "Niemals", "DISABLE_FILE_DOWNLOAD": "Download deaktivieren", - "DISABLE_FILE_DOWNLOAD_MESSAGE": "", + "DISABLE_FILE_DOWNLOAD_MESSAGE": "

Bist du sicher, dass du den Downloadbutton für Dateien deaktivieren möchtest?

Betrachter können weiterhin Screenshots machen oder die Bilder mithilfe externer Werkzeuge speichern

", "SHARED_USING": "Freigegeben über ", - "SHARING_REFERRAL_CODE": "", + "SHARING_REFERRAL_CODE": "Benutze den code {{referralCode}} für 10GB extra", "LIVE": "LIVE", "DISABLE_PASSWORD": "Passwort-Sperre deaktivieren", "DISABLE_PASSWORD_MESSAGE": "Sind Sie sicher, dass Sie die Passwort-Sperre deaktivieren möchten?", @@ -400,12 +400,12 @@ "UPLOAD_FILES": "Datei", "UPLOAD_DIRS": "Ordner", "UPLOAD_GOOGLE_TAKEOUT": "Google Takeout", - "DEDUPLICATE_FILES": "", + "DEDUPLICATE_FILES": "Duplikate bereinigen", "NO_DUPLICATES_FOUND": "Du hast keine Duplikate, die gelöscht werden können", "FILES": "dateien", - "EACH": "", - "DEDUPLICATE_BASED_ON_SIZE": "", - "STOP_ALL_UPLOADS_MESSAGE": "", + "EACH": "pro Datei", + "DEDUPLICATE_BASED_ON_SIZE": "Die folgenden Dateien wurden aufgrund ihrer Größe zusammengefasst. Bitte prüfe und lösche Dateien, die du für duplikate hälst", + "STOP_ALL_UPLOADS_MESSAGE": "Bist du sicher, dass du alle laufenden Uploads abbrechen möchtest?", "STOP_UPLOADS_HEADER": "Hochladen stoppen?", "YES_STOP_UPLOADS": "Ja, Hochladen stoppen", "STOP_DOWNLOADS_HEADER": "Downloads anhalten?", @@ -415,14 +415,14 @@ "albums_other": "{{count, number}} Alben", "ALL_ALBUMS": "Alle Alben", "ALBUMS": "Alben", - "ALL_HIDDEN_ALBUMS": "", - "HIDDEN_ALBUMS": "", - "HIDDEN_ITEMS": "", - "HIDDEN_ITEMS_SECTION_NAME": "", + "ALL_HIDDEN_ALBUMS": "Alle versteckten Alben", + "HIDDEN_ALBUMS": "Versteckte Alben", + "HIDDEN_ITEMS": "Versteckte Dateien", + "HIDDEN_ITEMS_SECTION_NAME": "Versteckte_Dateien", "ENTER_TWO_FACTOR_OTP": "Gib den 6-stelligen Code aus\ndeiner Authentifizierungs-App ein.", "CREATE_ACCOUNT": "Account erstellen", "COPIED": "Kopiert", - "WATCH_FOLDERS": "", + "WATCH_FOLDERS": "Überwachte Ordner", "UPGRADE_NOW": "Jetzt upgraden", "RENEW_NOW": "Jetzt erneuern", "STORAGE": "Speicher", @@ -431,21 +431,21 @@ "FAMILY": "Familie", "FREE": "frei", "OF": "von", - "WATCHED_FOLDERS": "", + "WATCHED_FOLDERS": "Überwachte Ordner", "NO_FOLDERS_ADDED": "Noch keine Ordner hinzugefügt!", - "FOLDERS_AUTOMATICALLY_MONITORED": "", - "UPLOAD_NEW_FILES_TO_ENTE": "", + "FOLDERS_AUTOMATICALLY_MONITORED": "Die Ordner, die du hier hinzufügst, werden überwacht, um automatisch", + "UPLOAD_NEW_FILES_TO_ENTE": "Neue Dateien bei Ente zu sichern", "REMOVE_DELETED_FILES_FROM_ENTE": "Gelöschte Dateien aus Ente entfernen", "ADD_FOLDER": "Ordner hinzufügen", - "STOP_WATCHING": "", - "STOP_WATCHING_FOLDER": "", - "STOP_WATCHING_DIALOG_MESSAGE": "", + "STOP_WATCHING": "Nicht mehr überwachen", + "STOP_WATCHING_FOLDER": "Ordner nicht mehr überwachen?", + "STOP_WATCHING_DIALOG_MESSAGE": "Deine bestehenden Dateien werden nicht gelöscht, aber das verknüpfte Ente-Album wird bei Änderungen in diesem Ordner nicht mehr aktualisiert.", "YES_STOP": "Ja, Stopp", - "MONTH_SHORT": "", + "MONTH_SHORT": "M", "YEAR": "Jahr", "FAMILY_PLAN": "Familientarif", "DOWNLOAD_LOGS": "Logs herunterladen", - "DOWNLOAD_LOGS_MESSAGE": "", + "DOWNLOAD_LOGS_MESSAGE": "

Hier kannst du Debug-Logs herunterladen, die du uns zur Fehleranalyse zusenden kannst.

Beachte bitte, dass die Logs Dateinamen enthalten, um Probleme mit bestimmten Dateien nachvollziehen zu können.

", "CHANGE_FOLDER": "Ordner ändern", "TWO_MONTHS_FREE": "Erhalte 2 Monate kostenlos bei Jahresabonnements", "GB": "GB", @@ -453,12 +453,12 @@ "FREE_PLAN_OPTION_LABEL": "Mit kostenloser Testversion fortfahren", "FREE_PLAN_DESCRIPTION": "1 GB für 1 Jahr", "CURRENT_USAGE": "Aktuelle Nutzung ist {{usage}}", - "WEAK_DEVICE": "", - "DRAG_AND_DROP_HINT": "", + "WEAK_DEVICE": "Dein Browser ist nicht leistungsstark genug, um deine Bilder zu verschlüsseln. Versuche, dich an einem Computer bei Ente anzumelden, oder lade dir die Ente-App für dein Gerät (Handy oder Desktop) herunter.", + "DRAG_AND_DROP_HINT": "Oder ziehe Dateien per Drag-and-Drop in das Ente-Fenster", "CONFIRM_ACCOUNT_DELETION_MESSAGE": "Deine hochgeladenen Daten werden zur Löschung vorgemerkt und dein Konto wird endgültig gelöscht.

Dieser Vorgang kann nicht rückgängig gemacht werden.", "AUTHENTICATE": "Authentifizieren", - "UPLOADED_TO_SINGLE_COLLECTION": "", - "UPLOADED_TO_SEPARATE_COLLECTIONS": "", + "UPLOADED_TO_SINGLE_COLLECTION": "In einzelnes Album hochgeladen", + "UPLOADED_TO_SEPARATE_COLLECTIONS": "In separate Alben hochgeladen", "NEVERMIND": "Egal", "UPDATE_AVAILABLE": "Neue Version verfügbar", "UPDATE_INSTALLABLE_MESSAGE": "Eine neue Version von Ente ist für die Installation bereit.", @@ -471,10 +471,10 @@ "YESTERDAY": "Gestern", "NAME_PLACEHOLDER": "Name...", "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "Alben können nicht aus Datei/Ordnermix erstellt werden", - "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "", + "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "

Du hast sowohl Dateien als auch Ordner in das Ente-Fenster gezogen.

Bitte wähle entweder nur Dateien oder nur Ordner aus, wenn separate Alben erstellt werden sollen

", "CHOSE_THEME": "Design auswählen", "ML_SEARCH": "Gesichtserkennung", - "ENABLE_ML_SEARCH_DESCRIPTION": "", + "ENABLE_ML_SEARCH_DESCRIPTION": "

Hiermit wird on-device machine learning aktiviert, und die Gesichtserkennung beginnt damit, die Fotos auf deinem Gerät zu analysieren.

Beim ersten Durchlauf nach der Anmeldung oder Aktivierung der Funktion werden alle Bilder auf dein Gerät heruntergeladen, um analysiert zu werden. Bitte aktiviere diese Funktion nur, wenn du einverstanden bist, dass dein Gerät die dafür benötigte Bandbreite und Rechenleistung aufbringt.

Falls dies das erste Mal ist, dass du diese Funktion aktivierst, werden wir deine Erlaubnis zur Verarbeitung von Gesichtsdaten einholen.

", "ML_MORE_DETAILS": "Weitere Details", "ENABLE_FACE_SEARCH": "Gesichtserkennung aktivieren", "ENABLE_FACE_SEARCH_TITLE": "Gesichtserkennung aktivieren?", @@ -482,18 +482,18 @@ "DISABLE_BETA": "Beta deaktivieren", "DISABLE_FACE_SEARCH": "Gesichtserkennung deaktivieren", "DISABLE_FACE_SEARCH_TITLE": "Gesichtserkennung deaktivieren?", - "DISABLE_FACE_SEARCH_DESCRIPTION": "", + "DISABLE_FACE_SEARCH_DESCRIPTION": "

Ente wird aufhören, Gesichtsdaten zu verarbeiten.

Du kannst die Gesichtserkennung jederzeit wieder aktivieren, wenn du möchtest, daher ist dieser Vorgang risikofrei.

", "ADVANCED": "Erweitert", "FACE_SEARCH_CONFIRMATION": "Ich verstehe und möchte Ente erlauben, Gesichtsgeometrie zu verarbeiten", "LABS": "Experimente", - "YOURS": "", + "YOURS": "von dir", "PASSPHRASE_STRENGTH_WEAK": "Passwortstärke: Schwach", "PASSPHRASE_STRENGTH_MODERATE": "Passwortstärke: Moderat", "PASSPHRASE_STRENGTH_STRONG": "Passwortstärke: Stark", "PREFERENCES": "Einstellungen", "LANGUAGE": "Sprache", "EXPORT_DIRECTORY_DOES_NOT_EXIST": "Ungültiges Exportverzeichnis", - "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "", + "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "

Das von dir gewählte Exportverzeichnis existiert nicht.

Bitte wähle einen gültigen Ordner.

", "SUBSCRIPTION_VERIFICATION_ERROR": "Verifizierung des Abonnements fehlgeschlagen", "STORAGE_UNITS": { "B": "B", @@ -516,39 +516,39 @@ "CREATE_PUBLIC_SHARING": "Öffentlichen Link erstellen", "PUBLIC_LINK_CREATED": "Öffentlicher Link erstellt", "PUBLIC_LINK_ENABLED": "Öffentlicher Link aktiviert", - "COLLECT_PHOTOS": "", - "PUBLIC_COLLECT_SUBTEXT": "", + "COLLECT_PHOTOS": "Bilder sammeln", + "PUBLIC_COLLECT_SUBTEXT": "Erlaube Personen mit diesem Link, Fotos zum gemeinsamen Album hinzuzufügen.", "STOP_EXPORT": "Stop", - "EXPORT_PROGRESS": "", + "EXPORT_PROGRESS": "{{progress.success, number}} / {{progress.total, number}} Dateien synchronisiert", "MIGRATING_EXPORT": "Vorbereiten...", "RENAMING_COLLECTION_FOLDERS": "Albumordner umbenennen...", - "TRASHING_DELETED_FILES": "", - "TRASHING_DELETED_COLLECTIONS": "", - "CONTINUOUS_EXPORT": "", - "PENDING_ITEMS": "", - "EXPORT_STARTING": "", - "DELETE_ACCOUNT_REASON_LABEL": "", - "DELETE_ACCOUNT_REASON_PLACEHOLDER": "", + "TRASHING_DELETED_FILES": "Verschiebe gelöschte Dateien in den Trash-Ordner...", + "TRASHING_DELETED_COLLECTIONS": "Verschiebe gelöschte Alben in den Trash-Ordner...", + "CONTINUOUS_EXPORT": "Stets aktuell halten", + "PENDING_ITEMS": "Ausstehende Dateien", + "EXPORT_STARTING": "Starte Export...", + "DELETE_ACCOUNT_REASON_LABEL": "Was ist der Hauptgrund für die Löschung deines Kontos?", + "DELETE_ACCOUNT_REASON_PLACEHOLDER": "Wähle einen Grund aus", "DELETE_REASON": { - "MISSING_FEATURE": "", - "BROKEN_BEHAVIOR": "", - "FOUND_ANOTHER_SERVICE": "", - "NOT_LISTED": "" + "MISSING_FEATURE": "Es fehlt eine wichtige Funktion die ich benötige", + "BROKEN_BEHAVIOR": "Die App oder eine bestimmte Funktion verhält sich nicht so wie gedacht", + "FOUND_ANOTHER_SERVICE": "Ich habe einen anderen Dienst gefunden, der mir mehr zusagt", + "NOT_LISTED": "Mein Grund ist nicht aufgeführt" }, - "DELETE_ACCOUNT_FEEDBACK_LABEL": "", + "DELETE_ACCOUNT_FEEDBACK_LABEL": "Wir bedauern sehr, dass uns verlässt. Bitte hilf uns besser zu werden, indem du uns sagst warum du gehst.", "DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "Feedback", "CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "Ja, ich möchte dieses Konto und alle enthaltenen Daten endgültig und unwiderruflich löschen", "CONFIRM_DELETE_ACCOUNT": "Kontolöschung bestätigen", - "FEEDBACK_REQUIRED": "", + "FEEDBACK_REQUIRED": "Bitte hilf uns durch das Angeben dieser Daten", "FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "Was macht der andere Dienst besser?", "RECOVER_TWO_FACTOR": "Zwei-Faktor wiederherstellen", - "at": "", + "at": "um", "AUTH_NEXT": "Weiter", - "AUTH_DOWNLOAD_MOBILE_APP": "", + "AUTH_DOWNLOAD_MOBILE_APP": "Lade unsere smartphone App herunter, um deine Schlüssel zu verwalten", "HIDDEN": "Versteckt", "HIDE": "Ausblenden", "UNHIDE": "Einblenden", - "UNHIDE_TO_COLLECTION": "", + "UNHIDE_TO_COLLECTION": "In Album wieder sichtbar machen", "SORT_BY": "Sortieren nach", "NEWEST_FIRST": "Neueste zuerst", "OLDEST_FIRST": "Älteste zuerst", @@ -562,14 +562,14 @@ "DOWNLOAD_PROGRESS": "{{progress.current}} / {{progress.total}} Dateien", "CHRISTMAS": "Weihnachten", "CHRISTMAS_EVE": "Heiligabend", - "NEW_YEAR": "", - "NEW_YEAR_EVE": "", + "NEW_YEAR": "Neujahr", + "NEW_YEAR_EVE": "Silvester", "IMAGE": "Bild", "VIDEO": "Video", "LIVE_PHOTO": "Live-Foto", "CONVERT": "Konvertieren", - "CONFIRM_EDITOR_CLOSE_MESSAGE": "", - "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "", + "CONFIRM_EDITOR_CLOSE_MESSAGE": "Editor wirklich schließen?", + "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "Lade dein bearbeitetes Bild herunter oder speichere es in Ente, um die Änderungen nicht zu verlieren.", "BRIGHTNESS": "Helligkeit", "CONTRAST": "Kontrast", "SATURATION": "Sättigung", @@ -581,7 +581,7 @@ "ROTATE_RIGHT": "Nach rechts drehen", "FLIP_VERTICALLY": "Vertikal spiegeln", "FLIP_HORIZONTALLY": "Horizontal spiegeln", - "DOWNLOAD_EDITED": "", + "DOWNLOAD_EDITED": "Bearbeitetes Bild herunterladen", "SAVE_A_COPY_TO_ENTE": "Kopie in Ente speichern", "RESTORE_ORIGINAL": "Original wiederherstellen", "TRANSFORM": "Transformieren", @@ -590,24 +590,24 @@ "ROTATION": "Drehen", "RESET": "Zurücksetzen", "PHOTO_EDITOR": "Foto-Editor", - "FASTER_UPLOAD": "", - "FASTER_UPLOAD_DESCRIPTION": "", - "MAGIC_SEARCH_STATUS": "", + "FASTER_UPLOAD": "Schnelleres hochladen", + "FASTER_UPLOAD_DESCRIPTION": "Uploads über nahegelegene Server leiten", + "MAGIC_SEARCH_STATUS": "Status der magischen Suche", "INDEXED_ITEMS": "Indizierte Elemente", "CAST_ALBUM_TO_TV": "Album auf Fernseher wiedergeben", "ENTER_CAST_PIN_CODE": "Gib den Code auf dem Fernseher unten ein, um dieses Gerät zu koppeln.", "PAIR_DEVICE_TO_TV": "Geräte koppeln", "TV_NOT_FOUND": "Fernseher nicht gefunden. Hast du die PIN korrekt eingegeben?", - "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_DESC": "", - "PAIR_WITH_PIN": "", - "CHOOSE_DEVICE_FROM_BROWSER": "", - "PAIR_WITH_PIN_DESC": "", - "VISIT_CAST_ENTE_IO": "", - "CAST_AUTO_PAIR_FAILED": "", + "AUTO_CAST_PAIR": "Automatisch verbinden", + "AUTO_CAST_PAIR_DESC": "Automatisches Verbinden funktioniert nur mit Geräten, die Chromecast unterstützen.", + "PAIR_WITH_PIN": "Mit PIN verbinden", + "CHOOSE_DEVICE_FROM_BROWSER": "Wähle ein Cast-Gerät aus dem Browser-Popup aus.", + "PAIR_WITH_PIN_DESC": "\"Mit PIN verbinden\" funktioniert mit jedem Bildschirm, auf dem du dein Album sehen möchtest.", + "VISIT_CAST_ENTE_IO": "Besuche {{url}} auf dem Gerät, das du verbinden möchtest.", + "CAST_AUTO_PAIR_FAILED": "Das automatische Verbinden über Chromecast ist fehlgeschlagen. Bitte versuche es erneut.", "FREEHAND": "Freihand", "APPLY_CROP": "Zuschnitt anwenden", - "PHOTO_EDIT_REQUIRED_TO_SAVE": "", + "PHOTO_EDIT_REQUIRED_TO_SAVE": "Es muss mindestens eine Transformation oder Farbanpassung vorgenommen werden, bevor gespeichert werden kann.", "PASSKEYS": "Passkeys", "DELETE_PASSKEY": "Passkey löschen", "DELETE_PASSKEY_CONFIRMATION": "Bist du sicher, dass du diesen Passkey löschen willst? Dieser Vorgang ist nicht umkehrbar.", @@ -622,6 +622,6 @@ "TRY_AGAIN": "Erneut versuchen", "PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER": "Folge den Schritten in deinem Browser, um mit dem Anmelden fortzufahren.", "LOGIN_WITH_PASSKEY": "Mit Passkey anmelden", - "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_first_album_name": "Mein erstes Album", + "autogenerated_default_album_name": "Neues Album" } diff --git a/web/packages/next/locales/pt-BR/translation.json b/web/packages/next/locales/pt-BR/translation.json index 630c8cf65..aab97f80b 100644 --- a/web/packages/next/locales/pt-BR/translation.json +++ b/web/packages/next/locales/pt-BR/translation.json @@ -598,13 +598,13 @@ "ENTER_CAST_PIN_CODE": "Digite o código que você vê na TV abaixo para parear este dispositivo.", "PAIR_DEVICE_TO_TV": "Parear dispositivos", "TV_NOT_FOUND": "TV não encontrada. Você inseriu o PIN correto?", - "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_DESC": "", + "AUTO_CAST_PAIR": "Pareamento automático", + "AUTO_CAST_PAIR_DESC": "O pareamento automático funciona apenas com dispositivos que suportam o Chromecast.", "PAIR_WITH_PIN": "Parear com PIN", "CHOOSE_DEVICE_FROM_BROWSER": "Escolha um dispositivo compatível com casts no navegador popup.", - "PAIR_WITH_PIN_DESC": "", + "PAIR_WITH_PIN_DESC": "Parear com o PIN funciona com qualquer tela que você deseja ver o seu álbum ativado.", "VISIT_CAST_ENTE_IO": "Acesse {{url}} no dispositivo que você deseja parear.", - "CAST_AUTO_PAIR_FAILED": "", + "CAST_AUTO_PAIR_FAILED": "Falha no pareamento automático do Chromecast. Por favor, tente novamente.", "FREEHAND": "Mão livre", "APPLY_CROP": "Aplicar Recorte", "PHOTO_EDIT_REQUIRED_TO_SAVE": "Pelo menos uma transformação ou ajuste de cor deve ser feito antes de salvar.", diff --git a/web/packages/next/locales/zh-CN/translation.json b/web/packages/next/locales/zh-CN/translation.json index 04d0428fb..3f97a5337 100644 --- a/web/packages/next/locales/zh-CN/translation.json +++ b/web/packages/next/locales/zh-CN/translation.json @@ -598,13 +598,13 @@ "ENTER_CAST_PIN_CODE": "输入您在下面的电视上看到的代码来配对此设备。", "PAIR_DEVICE_TO_TV": "配对设备", "TV_NOT_FOUND": "未找到电视。您输入的 PIN 码正确吗?", - "AUTO_CAST_PAIR": "", - "AUTO_CAST_PAIR_DESC": "", + "AUTO_CAST_PAIR": "自动配对", + "AUTO_CAST_PAIR_DESC": "自动配对仅适用于支持 Chromecast 的设备。", "PAIR_WITH_PIN": "用 PIN 配对", "CHOOSE_DEVICE_FROM_BROWSER": "从浏览器弹出窗口中选择兼容 Cast 的设备。", - "PAIR_WITH_PIN_DESC": "", + "PAIR_WITH_PIN_DESC": "用 PIN 码配对适用于您希望在其上查看相册的任何屏幕。", "VISIT_CAST_ENTE_IO": "在您要配对的设备上访问 {{url}} 。", - "CAST_AUTO_PAIR_FAILED": "", + "CAST_AUTO_PAIR_FAILED": "Chromecast 自动配对失败。请再试一次。", "FREEHAND": "手画", "APPLY_CROP": "应用裁剪", "PHOTO_EDIT_REQUIRED_TO_SAVE": "保存之前必须至少执行一项转换或颜色调整。", From 98b2df1d390100709c498e00202da575433a1313 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 6 May 2024 04:24:05 +0000 Subject: [PATCH 247/367] New Crowdin translations by GitHub Action --- auth/lib/l10n/arb/app_ar.arb | 1 - auth/lib/l10n/arb/app_de.arb | 1 - auth/lib/l10n/arb/app_es.arb | 10 ++++++++-- auth/lib/l10n/arb/app_fa.arb | 1 - auth/lib/l10n/arb/app_fi.arb | 1 - auth/lib/l10n/arb/app_fr.arb | 1 - auth/lib/l10n/arb/app_he.arb | 1 - auth/lib/l10n/arb/app_it.arb | 1 - auth/lib/l10n/arb/app_ja.arb | 1 - auth/lib/l10n/arb/app_ka.arb | 1 - auth/lib/l10n/arb/app_nl.arb | 1 - auth/lib/l10n/arb/app_pl.arb | 1 - auth/lib/l10n/arb/app_pt.arb | 1 - auth/lib/l10n/arb/app_ru.arb | 1 - auth/lib/l10n/arb/app_sv.arb | 1 - auth/lib/l10n/arb/app_ti.arb | 1 - auth/lib/l10n/arb/app_tr.arb | 1 - auth/lib/l10n/arb/app_vi.arb | 1 - auth/lib/l10n/arb/app_zh.arb | 1 - 19 files changed, 8 insertions(+), 20 deletions(-) diff --git a/auth/lib/l10n/arb/app_ar.arb b/auth/lib/l10n/arb/app_ar.arb index 68bd38900..f9d37c7ba 100644 --- a/auth/lib/l10n/arb/app_ar.arb +++ b/auth/lib/l10n/arb/app_ar.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "المصدِّر", "codeSecretKeyHint": "الرمز السري", "codeAccountHint": "الحساب (you@domain.com)", - "accountKeyType": "نوع المفتاح", "sessionExpired": "انتهت صلاحية الجلسة", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_de.arb b/auth/lib/l10n/arb/app_de.arb index be769ecd5..0c4d29eaf 100644 --- a/auth/lib/l10n/arb/app_de.arb +++ b/auth/lib/l10n/arb/app_de.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "Aussteller", "codeSecretKeyHint": "Geheimer Schlüssel", "codeAccountHint": "Konto (you@domain.com)", - "accountKeyType": "Art des Schlüssels", "sessionExpired": "Sitzung abgelaufen", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_es.arb b/auth/lib/l10n/arb/app_es.arb index 41113f0b9..e1660b978 100644 --- a/auth/lib/l10n/arb/app_es.arb +++ b/auth/lib/l10n/arb/app_es.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "Emisor", "codeSecretKeyHint": "Llave Secreta", "codeAccountHint": "Cuenta (tu@dominio.com)", - "accountKeyType": "Tipo de llave", "sessionExpired": "La sesión ha expirado", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" @@ -113,6 +112,7 @@ "copied": "Copiado", "pleaseTryAgain": "Por favor, inténtalo nuevamente", "existingUser": "Usuario existente", + "newUser": "Nuevo a Ente", "delete": "Borrar", "enterYourPasswordHint": "Ingrese su contraseña", "forgotPassword": "Olvidé mi contraseña", @@ -138,6 +138,7 @@ "enterCodeHint": "Ingrese el código de seis dígitos de su aplicación de autenticación", "lostDeviceTitle": "¿Perdió su dispositivo?", "twoFactorAuthTitle": "Autenticación de dos factores", + "verifyPasskey": "Verificar llave de acceso", "recoverAccount": "Recuperar cuenta", "enterRecoveryKeyHint": "Introduzca su clave de recuperación", "recover": "Recuperar", @@ -191,6 +192,8 @@ "recoveryKeySaveDescription": "Nosotros no almacenamos esta clave, por favor guarde dicha clave de 24 palabras en un lugar seguro.", "doThisLater": "Hacer esto más tarde", "saveKey": "Guardar Clave", + "save": "Guardar", + "send": "Enviar", "back": "Atrás", "createAccount": "Crear cuenta", "passwordStrength": "Fortaleza de la contraseña: {passwordStrengthValue}", @@ -397,5 +400,8 @@ "signOutOtherDevices": "Cerrar la sesión de otros dispositivos", "doNotSignOut": "No cerrar la sesión", "hearUsWhereTitle": "¿Cómo conoció Ente? (opcional)", - "hearUsExplanation": "No rastreamos las aplicaciones instaladas. ¡Nos ayudaría si nos dijera dónde nos encontró!" + "hearUsExplanation": "No rastreamos las aplicaciones instaladas. ¡Nos ayudaría si nos dijera dónde nos encontró!", + "passkey": "Llave de acceso", + "developerSettingsWarning": "¿Estás seguro de que quieres modificar los ajustes de desarrollador?", + "developerSettings": "Ajustes de desarrollador" } \ No newline at end of file diff --git a/auth/lib/l10n/arb/app_fa.arb b/auth/lib/l10n/arb/app_fa.arb index 0cba193a9..948aa8b22 100644 --- a/auth/lib/l10n/arb/app_fa.arb +++ b/auth/lib/l10n/arb/app_fa.arb @@ -14,7 +14,6 @@ "codeIssuerHint": "صادر کننده", "codeSecretKeyHint": "کلید مخفی", "codeAccountHint": "حساب (you@domain.com)", - "accountKeyType": "نوع کلید", "sessionExpired": "نشست منقضی شده است", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_fi.arb b/auth/lib/l10n/arb/app_fi.arb index 72309b331..2a0404147 100644 --- a/auth/lib/l10n/arb/app_fi.arb +++ b/auth/lib/l10n/arb/app_fi.arb @@ -12,7 +12,6 @@ "codeIssuerHint": "Myöntäjä", "codeSecretKeyHint": "Salainen avain", "codeAccountHint": "Tili (sinun@jokinosoite.com)", - "accountKeyType": "Avaimen tyyppi", "sessionExpired": "Istunto on vanheutunut", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_fr.arb b/auth/lib/l10n/arb/app_fr.arb index 04a7058c7..71ddc0b31 100644 --- a/auth/lib/l10n/arb/app_fr.arb +++ b/auth/lib/l10n/arb/app_fr.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "Émetteur", "codeSecretKeyHint": "Clé secrète", "codeAccountHint": "Compte (vous@exemple.com)", - "accountKeyType": "Type de clé", "sessionExpired": "Session expirée", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_he.arb b/auth/lib/l10n/arb/app_he.arb index 330585097..8f22e1e82 100644 --- a/auth/lib/l10n/arb/app_he.arb +++ b/auth/lib/l10n/arb/app_he.arb @@ -19,7 +19,6 @@ "codeIssuerHint": "מנפיק", "codeSecretKeyHint": "מפתח סודי", "codeAccountHint": "חשבון(you@domain.com)", - "accountKeyType": "סוג מפתח", "sessionExpired": "זמן החיבור הסתיים", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_it.arb b/auth/lib/l10n/arb/app_it.arb index e35fd11dc..92543ed82 100644 --- a/auth/lib/l10n/arb/app_it.arb +++ b/auth/lib/l10n/arb/app_it.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "Emittente", "codeSecretKeyHint": "Codice segreto", "codeAccountHint": "Account (username@dominio.it)", - "accountKeyType": "Tipo di chiave", "sessionExpired": "Sessione scaduta", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_ja.arb b/auth/lib/l10n/arb/app_ja.arb index 60d0a5150..8fea34c5e 100644 --- a/auth/lib/l10n/arb/app_ja.arb +++ b/auth/lib/l10n/arb/app_ja.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "発行者", "codeSecretKeyHint": "秘密鍵", "codeAccountHint": "アカウント (you@domain.com)", - "accountKeyType": "鍵の種類", "sessionExpired": "セッションが失効しました", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_ka.arb b/auth/lib/l10n/arb/app_ka.arb index cb7dc8281..93631df2d 100644 --- a/auth/lib/l10n/arb/app_ka.arb +++ b/auth/lib/l10n/arb/app_ka.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "მომწოდებელი", "codeSecretKeyHint": "გასაღები", "codeAccountHint": "ანგარიში (you@domain.com)", - "accountKeyType": "გასაღების ტიპი", "sessionExpired": "სესიის დრო ამოიწურა", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_nl.arb b/auth/lib/l10n/arb/app_nl.arb index 2e84ae11b..36280f69d 100644 --- a/auth/lib/l10n/arb/app_nl.arb +++ b/auth/lib/l10n/arb/app_nl.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "Uitgever", "codeSecretKeyHint": "Geheime sleutel", "codeAccountHint": "Account (jij@domein.nl)", - "accountKeyType": "Type sleutel", "sessionExpired": "Sessie verlopen", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_pl.arb b/auth/lib/l10n/arb/app_pl.arb index 8ebc935dc..3132f6660 100644 --- a/auth/lib/l10n/arb/app_pl.arb +++ b/auth/lib/l10n/arb/app_pl.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "Wydawca", "codeSecretKeyHint": "Tajny klucz", "codeAccountHint": "Konto (ty@domena.com)", - "accountKeyType": "Rodzaj klucza", "sessionExpired": "Sesja wygasła", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_pt.arb b/auth/lib/l10n/arb/app_pt.arb index b27a018fb..9b1f5b1b0 100644 --- a/auth/lib/l10n/arb/app_pt.arb +++ b/auth/lib/l10n/arb/app_pt.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "Emissor", "codeSecretKeyHint": "Chave secreta", "codeAccountHint": "Conta (voce@dominio.com)", - "accountKeyType": "Tipo de chave", "sessionExpired": "Sessão expirada", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_ru.arb b/auth/lib/l10n/arb/app_ru.arb index 7ae37a87b..ca98611ee 100644 --- a/auth/lib/l10n/arb/app_ru.arb +++ b/auth/lib/l10n/arb/app_ru.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "Эмитент", "codeSecretKeyHint": "Секретный ключ", "codeAccountHint": "Аккаунт (you@domain.com)", - "accountKeyType": "Тип ключа", "sessionExpired": "Сеанс истек", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_sv.arb b/auth/lib/l10n/arb/app_sv.arb index cfb41d7bd..9761325ce 100644 --- a/auth/lib/l10n/arb/app_sv.arb +++ b/auth/lib/l10n/arb/app_sv.arb @@ -16,7 +16,6 @@ "codeIssuerHint": "Utfärdare", "codeSecretKeyHint": "Secret Key", "codeAccountHint": "Konto (du@domän.com)", - "accountKeyType": "Typ av nyckel", "sessionExpired": "Sessionen har gått ut", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_ti.arb b/auth/lib/l10n/arb/app_ti.arb index 27147ebb6..b41128f6e 100644 --- a/auth/lib/l10n/arb/app_ti.arb +++ b/auth/lib/l10n/arb/app_ti.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "ኣዋጂ", "codeSecretKeyHint": "ምስጢራዊ መፍትሕ", "codeAccountHint": "ሕሳብ (you@domain.com)", - "accountKeyType": "ዓይነት መፍትሕ", "sessionExpired": "ክፍለ ግዜኡ ኣኺሉ።", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_tr.arb b/auth/lib/l10n/arb/app_tr.arb index 9b847faf0..322af5f48 100644 --- a/auth/lib/l10n/arb/app_tr.arb +++ b/auth/lib/l10n/arb/app_tr.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "Yayınlayan", "codeSecretKeyHint": "Gizli Anahtar", "codeAccountHint": "Hesap (ornek@domain.com)", - "accountKeyType": "Anahtar türü", "sessionExpired": "Oturum süresi doldu", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_vi.arb b/auth/lib/l10n/arb/app_vi.arb index e318f9b55..a8cccdbec 100644 --- a/auth/lib/l10n/arb/app_vi.arb +++ b/auth/lib/l10n/arb/app_vi.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "Nhà phát hành", "codeSecretKeyHint": "Khóa bí mật", "codeAccountHint": "Tài khoản (bạn@miền.com)", - "accountKeyType": "Loại khóa", "sessionExpired": "Phiên làm việc đã hết hạn", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" diff --git a/auth/lib/l10n/arb/app_zh.arb b/auth/lib/l10n/arb/app_zh.arb index 077ee26fd..c50e76c1d 100644 --- a/auth/lib/l10n/arb/app_zh.arb +++ b/auth/lib/l10n/arb/app_zh.arb @@ -20,7 +20,6 @@ "codeIssuerHint": "发行人", "codeSecretKeyHint": "私钥", "codeAccountHint": "账户 (you@domain.com)", - "accountKeyType": "密钥类型", "sessionExpired": "会话已过期", "@sessionExpired": { "description": "Title of the dialog when the users current session is invalid/expired" From a288c8305afd550cfeb1ca18aeebef7baf83c87c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 10:07:27 +0530 Subject: [PATCH 248/367] Add TODO about better translation --- web/apps/photos/src/components/PhotoList/dedupe.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/apps/photos/src/components/PhotoList/dedupe.tsx b/web/apps/photos/src/components/PhotoList/dedupe.tsx index 7181f6267..ab7badf1f 100644 --- a/web/apps/photos/src/components/PhotoList/dedupe.tsx +++ b/web/apps/photos/src/components/PhotoList/dedupe.tsx @@ -304,6 +304,10 @@ export function DedupePhotoList({ switch (listItem.itemType) { case ITEM_TYPE.SIZE_AND_COUNT: return ( + /*TODO: Translate the full phrase instead of piecing + together parts like this See: + https://crowdin.com/editor/ente-photos-web/9/enus-de?view=comfortable&filter=basic&value=0#8104 + */ {listItem.fileCount} {t("FILES")},{" "} {convertBytesToHumanReadable(listItem.fileSize || 0)}{" "} From 59d8b9bfbbe149b04c275d405ffe0193e5e3cfa2 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 10:14:57 +0530 Subject: [PATCH 249/367] Remove misused translation strings --- web/apps/photos/src/pages/gallery/index.tsx | 14 +------------- web/packages/next/locales/en-US/translation.json | 1 - 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/web/apps/photos/src/pages/gallery/index.tsx b/web/apps/photos/src/pages/gallery/index.tsx index 20d95ce00..d883cb61c 100644 --- a/web/apps/photos/src/pages/gallery/index.tsx +++ b/web/apps/photos/src/pages/gallery/index.tsx @@ -96,7 +96,6 @@ import { ALL_SECTION, ARCHIVE_SECTION, CollectionSummaryType, - DUMMY_UNCATEGORIZED_COLLECTION, HIDDEN_ITEMS_SECTION, TRASH_SECTION, } from "constants/collection"; @@ -446,18 +445,7 @@ export default function Gallery() { } let collectionURL = ""; if (activeCollectionID !== ALL_SECTION) { - collectionURL += "?collection="; - if (activeCollectionID === ARCHIVE_SECTION) { - collectionURL += t("ARCHIVE_SECTION_NAME"); - } else if (activeCollectionID === TRASH_SECTION) { - collectionURL += t("TRASH"); - } else if (activeCollectionID === DUMMY_UNCATEGORIZED_COLLECTION) { - collectionURL += t("UNCATEGORIZED"); - } else if (activeCollectionID === HIDDEN_ITEMS_SECTION) { - collectionURL += t("HIDDEN_ITEMS_SECTION_NAME"); - } else { - collectionURL += activeCollectionID; - } + collectionURL = `?collection={activeCollectionID}`; } const href = `/gallery${collectionURL}`; router.push(href, undefined, { shallow: true }); diff --git a/web/packages/next/locales/en-US/translation.json b/web/packages/next/locales/en-US/translation.json index 2fb9eadc6..3474f1b71 100644 --- a/web/packages/next/locales/en-US/translation.json +++ b/web/packages/next/locales/en-US/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "All hidden albums", "HIDDEN_ALBUMS": "Hidden albums", "HIDDEN_ITEMS": "Hidden items", - "HIDDEN_ITEMS_SECTION_NAME": "Hidden_items", "ENTER_TWO_FACTOR_OTP": "Enter the 6-digit code from your authenticator app.", "CREATE_ACCOUNT": "Create account", "COPIED": "Copied", From 9804d448f60fe56bc9df29ec6024de13c6127706 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 10:19:55 +0530 Subject: [PATCH 250/367] Enable German (has reached 100%) --- .../src/components/Sidebar/Preferences/LanguageSelector.tsx | 2 ++ web/packages/next/i18n.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/web/apps/photos/src/components/Sidebar/Preferences/LanguageSelector.tsx b/web/apps/photos/src/components/Sidebar/Preferences/LanguageSelector.tsx index a9474a37d..bdc0d5a84 100644 --- a/web/apps/photos/src/components/Sidebar/Preferences/LanguageSelector.tsx +++ b/web/apps/photos/src/components/Sidebar/Preferences/LanguageSelector.tsx @@ -19,6 +19,8 @@ export const localeName = (locale: SupportedLocale) => { return "English"; case "fr-FR": return "Français"; + case "de-DE": + return "Deutsch"; case "zh-CN": return "中文"; case "nl-NL": diff --git a/web/packages/next/i18n.ts b/web/packages/next/i18n.ts index 913ecf746..cdc60e27c 100644 --- a/web/packages/next/i18n.ts +++ b/web/packages/next/i18n.ts @@ -22,6 +22,7 @@ import { object, string } from "yup"; export const supportedLocales = [ "en-US" /* English */, "fr-FR" /* French */, + "de-DE" /* German */, "zh-CN" /* Simplified Chinese */, "nl-NL" /* Dutch */, "es-ES" /* Spanish */, @@ -209,6 +210,8 @@ const closestSupportedLocale = ( return "en-US"; } else if (ls.startsWith("fr")) { return "fr-FR"; + } else if (ls.startsWith("de")) { + return "de-DE"; } else if (ls.startsWith("zh")) { return "zh-CN"; } else if (ls.startsWith("nl")) { From ea57815945edfbae6b2e16b58ba8dce2b865a982 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 10:23:08 +0530 Subject: [PATCH 251/367] Add a TODO and fix typo --- web/apps/photos/src/pages/gallery/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/apps/photos/src/pages/gallery/index.tsx b/web/apps/photos/src/pages/gallery/index.tsx index d883cb61c..ba0d53d60 100644 --- a/web/apps/photos/src/pages/gallery/index.tsx +++ b/web/apps/photos/src/pages/gallery/index.tsx @@ -445,7 +445,8 @@ export default function Gallery() { } let collectionURL = ""; if (activeCollectionID !== ALL_SECTION) { - collectionURL = `?collection={activeCollectionID}`; + // TODO: Is this URL param even used? + collectionURL = `?collection=${activeCollectionID}`; } const href = `/gallery${collectionURL}`; router.push(href, undefined, { shallow: true }); From 6db94b7595ba5199ab624272492008d11ad559af Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 6 May 2024 05:00:53 +0000 Subject: [PATCH 252/367] New Crowdin translations by GitHub Action --- web/packages/next/locales/bg-BG/translation.json | 1 - web/packages/next/locales/de-DE/translation.json | 1 - web/packages/next/locales/es-ES/translation.json | 1 - web/packages/next/locales/fa-IR/translation.json | 1 - web/packages/next/locales/fi-FI/translation.json | 1 - web/packages/next/locales/fr-FR/translation.json | 1 - web/packages/next/locales/it-IT/translation.json | 1 - web/packages/next/locales/ko-KR/translation.json | 1 - web/packages/next/locales/nl-NL/translation.json | 1 - web/packages/next/locales/pt-BR/translation.json | 1 - web/packages/next/locales/pt-PT/translation.json | 1 - web/packages/next/locales/ru-RU/translation.json | 1 - web/packages/next/locales/sv-SE/translation.json | 1 - web/packages/next/locales/th-TH/translation.json | 1 - web/packages/next/locales/tr-TR/translation.json | 1 - web/packages/next/locales/zh-CN/translation.json | 1 - 16 files changed, 16 deletions(-) diff --git a/web/packages/next/locales/bg-BG/translation.json b/web/packages/next/locales/bg-BG/translation.json index 699dae1da..6960de794 100644 --- a/web/packages/next/locales/bg-BG/translation.json +++ b/web/packages/next/locales/bg-BG/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "", "HIDDEN_ALBUMS": "", "HIDDEN_ITEMS": "", - "HIDDEN_ITEMS_SECTION_NAME": "", "ENTER_TWO_FACTOR_OTP": "", "CREATE_ACCOUNT": "", "COPIED": "", diff --git a/web/packages/next/locales/de-DE/translation.json b/web/packages/next/locales/de-DE/translation.json index ceb7cf98d..f2151c8c8 100644 --- a/web/packages/next/locales/de-DE/translation.json +++ b/web/packages/next/locales/de-DE/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "Alle versteckten Alben", "HIDDEN_ALBUMS": "Versteckte Alben", "HIDDEN_ITEMS": "Versteckte Dateien", - "HIDDEN_ITEMS_SECTION_NAME": "Versteckte_Dateien", "ENTER_TWO_FACTOR_OTP": "Gib den 6-stelligen Code aus\ndeiner Authentifizierungs-App ein.", "CREATE_ACCOUNT": "Account erstellen", "COPIED": "Kopiert", diff --git a/web/packages/next/locales/es-ES/translation.json b/web/packages/next/locales/es-ES/translation.json index 2916b5d5c..24b6c0879 100644 --- a/web/packages/next/locales/es-ES/translation.json +++ b/web/packages/next/locales/es-ES/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "", "HIDDEN_ALBUMS": "", "HIDDEN_ITEMS": "", - "HIDDEN_ITEMS_SECTION_NAME": "", "ENTER_TWO_FACTOR_OTP": "Ingrese el código de seis dígitos de su aplicación de autenticación a continuación.", "CREATE_ACCOUNT": "Crear cuenta", "COPIED": "Copiado", diff --git a/web/packages/next/locales/fa-IR/translation.json b/web/packages/next/locales/fa-IR/translation.json index f06066116..05e3c47d6 100644 --- a/web/packages/next/locales/fa-IR/translation.json +++ b/web/packages/next/locales/fa-IR/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "", "HIDDEN_ALBUMS": "", "HIDDEN_ITEMS": "", - "HIDDEN_ITEMS_SECTION_NAME": "", "ENTER_TWO_FACTOR_OTP": "", "CREATE_ACCOUNT": "", "COPIED": "", diff --git a/web/packages/next/locales/fi-FI/translation.json b/web/packages/next/locales/fi-FI/translation.json index 38455b3e2..1408fbbe6 100644 --- a/web/packages/next/locales/fi-FI/translation.json +++ b/web/packages/next/locales/fi-FI/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "", "HIDDEN_ALBUMS": "", "HIDDEN_ITEMS": "", - "HIDDEN_ITEMS_SECTION_NAME": "", "ENTER_TWO_FACTOR_OTP": "", "CREATE_ACCOUNT": "", "COPIED": "", diff --git a/web/packages/next/locales/fr-FR/translation.json b/web/packages/next/locales/fr-FR/translation.json index 89a054c22..1c549b5a6 100644 --- a/web/packages/next/locales/fr-FR/translation.json +++ b/web/packages/next/locales/fr-FR/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "Tous les albums masqués", "HIDDEN_ALBUMS": "Albums masqués", "HIDDEN_ITEMS": "Éléments masqués", - "HIDDEN_ITEMS_SECTION_NAME": "Éléments masqués", "ENTER_TWO_FACTOR_OTP": "Saisir le code à 6 caractères de votre appli d'authentification.", "CREATE_ACCOUNT": "Créer un compte", "COPIED": "Copié", diff --git a/web/packages/next/locales/it-IT/translation.json b/web/packages/next/locales/it-IT/translation.json index eb7a68d45..43898574a 100644 --- a/web/packages/next/locales/it-IT/translation.json +++ b/web/packages/next/locales/it-IT/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "", "HIDDEN_ALBUMS": "", "HIDDEN_ITEMS": "", - "HIDDEN_ITEMS_SECTION_NAME": "", "ENTER_TWO_FACTOR_OTP": "", "CREATE_ACCOUNT": "Crea account", "COPIED": "", diff --git a/web/packages/next/locales/ko-KR/translation.json b/web/packages/next/locales/ko-KR/translation.json index 35aeff339..8c37ab400 100644 --- a/web/packages/next/locales/ko-KR/translation.json +++ b/web/packages/next/locales/ko-KR/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "", "HIDDEN_ALBUMS": "", "HIDDEN_ITEMS": "", - "HIDDEN_ITEMS_SECTION_NAME": "", "ENTER_TWO_FACTOR_OTP": "", "CREATE_ACCOUNT": "", "COPIED": "", diff --git a/web/packages/next/locales/nl-NL/translation.json b/web/packages/next/locales/nl-NL/translation.json index f75bd5e47..feb831bcb 100644 --- a/web/packages/next/locales/nl-NL/translation.json +++ b/web/packages/next/locales/nl-NL/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "Alle verborgen albums", "HIDDEN_ALBUMS": "Verborgen albums", "HIDDEN_ITEMS": "Verborgen bestanden", - "HIDDEN_ITEMS_SECTION_NAME": "Verborgen_items", "ENTER_TWO_FACTOR_OTP": "Voer de 6-cijferige code van uw verificatie app in.", "CREATE_ACCOUNT": "Account aanmaken", "COPIED": "Gekopieerd", diff --git a/web/packages/next/locales/pt-BR/translation.json b/web/packages/next/locales/pt-BR/translation.json index aab97f80b..717c4360f 100644 --- a/web/packages/next/locales/pt-BR/translation.json +++ b/web/packages/next/locales/pt-BR/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "Todos os álbuns ocultos", "HIDDEN_ALBUMS": "Álbuns ocultos", "HIDDEN_ITEMS": "Itens ocultos", - "HIDDEN_ITEMS_SECTION_NAME": "Itens_ocultos", "ENTER_TWO_FACTOR_OTP": "Digite o código de 6 dígitos de\nseu aplicativo autenticador.", "CREATE_ACCOUNT": "Criar uma conta", "COPIED": "Copiado", diff --git a/web/packages/next/locales/pt-PT/translation.json b/web/packages/next/locales/pt-PT/translation.json index 29b36622c..44ec3361c 100644 --- a/web/packages/next/locales/pt-PT/translation.json +++ b/web/packages/next/locales/pt-PT/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "", "HIDDEN_ALBUMS": "", "HIDDEN_ITEMS": "", - "HIDDEN_ITEMS_SECTION_NAME": "", "ENTER_TWO_FACTOR_OTP": "", "CREATE_ACCOUNT": "", "COPIED": "", diff --git a/web/packages/next/locales/ru-RU/translation.json b/web/packages/next/locales/ru-RU/translation.json index 910c9253f..537ba0692 100644 --- a/web/packages/next/locales/ru-RU/translation.json +++ b/web/packages/next/locales/ru-RU/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "Все скрытые альбомы", "HIDDEN_ALBUMS": "Скрытые альбомы", "HIDDEN_ITEMS": "Скрытые предметы", - "HIDDEN_ITEMS_SECTION_NAME": "Скрытые_элементы", "ENTER_TWO_FACTOR_OTP": "Введите 6-значный код из вашего приложения для проверки подлинности.", "CREATE_ACCOUNT": "Создать аккаунт", "COPIED": "Скопированный", diff --git a/web/packages/next/locales/sv-SE/translation.json b/web/packages/next/locales/sv-SE/translation.json index 78e7116e0..69a29a5bf 100644 --- a/web/packages/next/locales/sv-SE/translation.json +++ b/web/packages/next/locales/sv-SE/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "", "HIDDEN_ALBUMS": "", "HIDDEN_ITEMS": "", - "HIDDEN_ITEMS_SECTION_NAME": "", "ENTER_TWO_FACTOR_OTP": "", "CREATE_ACCOUNT": "", "COPIED": "", diff --git a/web/packages/next/locales/th-TH/translation.json b/web/packages/next/locales/th-TH/translation.json index 38455b3e2..1408fbbe6 100644 --- a/web/packages/next/locales/th-TH/translation.json +++ b/web/packages/next/locales/th-TH/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "", "HIDDEN_ALBUMS": "", "HIDDEN_ITEMS": "", - "HIDDEN_ITEMS_SECTION_NAME": "", "ENTER_TWO_FACTOR_OTP": "", "CREATE_ACCOUNT": "", "COPIED": "", diff --git a/web/packages/next/locales/tr-TR/translation.json b/web/packages/next/locales/tr-TR/translation.json index 38455b3e2..1408fbbe6 100644 --- a/web/packages/next/locales/tr-TR/translation.json +++ b/web/packages/next/locales/tr-TR/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "", "HIDDEN_ALBUMS": "", "HIDDEN_ITEMS": "", - "HIDDEN_ITEMS_SECTION_NAME": "", "ENTER_TWO_FACTOR_OTP": "", "CREATE_ACCOUNT": "", "COPIED": "", diff --git a/web/packages/next/locales/zh-CN/translation.json b/web/packages/next/locales/zh-CN/translation.json index 3f97a5337..3ac0e16e8 100644 --- a/web/packages/next/locales/zh-CN/translation.json +++ b/web/packages/next/locales/zh-CN/translation.json @@ -418,7 +418,6 @@ "ALL_HIDDEN_ALBUMS": "所有隐藏的相册", "HIDDEN_ALBUMS": "隐藏的相册", "HIDDEN_ITEMS": "隐藏的项目", - "HIDDEN_ITEMS_SECTION_NAME": "隐藏的项目", "ENTER_TWO_FACTOR_OTP": "请输入您从身份验证应用上获得的6位数代码", "CREATE_ACCOUNT": "创建账户", "COPIED": "已复制", From f37769d3404e16ebb551f68fae109a644d998686 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 14:12:34 +0530 Subject: [PATCH 253/367] [docs] Add note about connecting web/desktop app to custom server --- .../docs/self-hosting/guides/custom-server/index.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/docs/self-hosting/guides/custom-server/index.md b/docs/docs/self-hosting/guides/custom-server/index.md index a5ce76cc2..8e16004a1 100644 --- a/docs/docs/self-hosting/guides/custom-server/index.md +++ b/docs/docs/self-hosting/guides/custom-server/index.md @@ -35,3 +35,16 @@ endpoint: ``` (Another [example](https://github.com/ente-io/ente/blob/main/cli/config.yaml.example)) + +## Web appps and Photos desktop app + +You will need to build the app from source and use the +`NEXT_PUBLIC_ENTE_ENDPOINT` environment variable to tell it which server to +connect to. For example: + +```sh +NEXT_PUBLIC_ENTE_ENDPOINT=http://localhost:8080 yarn dev:photos +``` + +For more details, see [hosting the web +app](https://help.ente.io/self-hosting/guides/web-app). From 06e43bf276f0c9d67965135fbd5b2007ea664456 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 6 May 2024 14:17:59 +0530 Subject: [PATCH 254/367] [mob] Fix cast button refresh state --- .../gallery/gallery_app_bar_widget.dart | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 4a3d9450a..3ca2f6c2a 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -93,6 +93,8 @@ class _GalleryAppBarWidgetState extends State { late bool isInternalUser; late GalleryType galleryType; + final ValueNotifier castNotifier = ValueNotifier(0); + @override void initState() { super.initState(); @@ -328,14 +330,16 @@ class _GalleryAppBarWidgetState extends State { Tooltip( message: "Cast album", child: IconButton( - icon: castService.getActiveSessions().isNotEmpty - ? const Icon(Icons.cast_connected_rounded) - : const Icon(Icons.cast_outlined), + icon: ValueListenableBuilder( + valueListenable: castNotifier, + builder: (context, value, child) { + return castService.getActiveSessions().isNotEmpty + ? const Icon(Icons.cast_connected_rounded) + : const Icon(Icons.cast_outlined); + }, + ), onPressed: () async { await _castChoiceDialog(); - if (mounted) { - setState(() {}); - } }, ), ), @@ -728,6 +732,7 @@ class _GalleryAppBarWidgetState extends State { await castService.closeActiveCasts(); }, ); + castNotifier.value++; return; } @@ -785,7 +790,10 @@ class _GalleryAppBarWidgetState extends State { String lastCode = ''; Future _castPair( - BuildContext bContext, CastGateway gw, String code) async { + BuildContext bContext, + CastGateway gw, + String code, + ) async { try { if (lastCode == code) { return false; @@ -810,6 +818,7 @@ class _GalleryAppBarWidgetState extends State { ); _logger.info("Casted album with token $castToken"); // showToast(bContext, S.of(context).pairingComplete); + castNotifier.value++; return true; } catch (e, s) { lastCode = ''; @@ -823,6 +832,7 @@ class _GalleryAppBarWidgetState extends State { } else { await showGenericErrorDialog(context: bContext, error: e); } + castNotifier.value++; return false; } } From 1d1ee1248ceb46dfae1940bb5470bdff10e380a8 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 6 May 2024 14:19:27 +0530 Subject: [PATCH 255/367] [mob][cast] Send collectionID during pairing --- mobile/plugins/ente_cast_normal/lib/src/service.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/plugins/ente_cast_normal/lib/src/service.dart index 04c501666..b30150d06 100644 --- a/mobile/plugins/ente_cast_normal/lib/src/service.dart +++ b/mobile/plugins/ente_cast_normal/lib/src/service.dart @@ -24,7 +24,9 @@ class CastServiceImpl extends CastService { "got RECEIVER_STATUS, Send request to pair", name: "CastServiceImpl", ); - session.sendMessage(_pairRequestNamespace, {}); + session.sendMessage(_pairRequestNamespace, { + "collectionID": collectionID, + }); } else { if (onMessage != null && message.containsKey("code")) { onMessage( @@ -32,8 +34,9 @@ class CastServiceImpl extends CastService { CastMessageType.pairCode: message, }, ); + } else { + print('receive message: $message'); } - print('receive message: $message'); } }); From d779fc05bdd1e4445fb17755e743d0d935193fd4 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 6 May 2024 14:41:52 +0530 Subject: [PATCH 256/367] [mob][cast] Fix cast progress state --- mobile/lib/ui/cast/auto.dart | 11 +++++------ .../lib/ui/viewer/gallery/gallery_app_bar_widget.dart | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mobile/lib/ui/cast/auto.dart b/mobile/lib/ui/cast/auto.dart index 7b310855e..34c97b34d 100644 --- a/mobile/lib/ui/cast/auto.dart +++ b/mobile/lib/ui/cast/auto.dart @@ -79,12 +79,6 @@ class _AutoCastDialogState extends State { }); try { await _connectToYourApp(context, device); - if (mounted) { - setState(() { - _isDeviceTapInProgress.remove(device); - }); - Navigator.of(context).pop(); - } } catch (e) { if (mounted) { setState(() { @@ -128,6 +122,11 @@ class _AutoCastDialogState extends State { final code = message[CastMessageType.pairCode]!['code']; widget.onConnect(code); } + if (mounted) { + setState(() { + _isDeviceTapInProgress.remove(castDevice); + }); + } }, ); } diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 3ca2f6c2a..4e5c2d96e 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -758,6 +758,7 @@ class _GalleryAppBarWidgetState extends State { return AutoCastDialog( (device) async { await _castPair(bContext, gw, device); + Navigator.pop(bContext); }, ); }, From 371f3b538fb912eee8ca0f00ecdc4ba0231c462c Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 6 May 2024 14:42:58 +0530 Subject: [PATCH 257/367] [mob][cast] Improve logs --- mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 4e5c2d96e..2898143a0 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -810,14 +810,13 @@ class _GalleryAppBarWidgetState extends State { final String castToken = const Uuid().v4().toString(); final castPayload = CollectionsService.instance .getCastData(castToken, widget.collection!, publicKey); - _logger.info("Casting album with token $castToken"); await gw.publishCastPayload( code, castPayload, widget.collection!.id, castToken, ); - _logger.info("Casted album with token $castToken"); + _logger.info("cast album completed"); // showToast(bContext, S.of(context).pairingComplete); castNotifier.value++; return true; From bdcad0515ced4de133022c677c5a94ec1b81492f Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 6 May 2024 14:44:54 +0530 Subject: [PATCH 258/367] [mob][cast] Increase discovery timeout to 7seconds --- mobile/plugins/ente_cast_normal/lib/src/service.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/plugins/ente_cast_normal/lib/src/service.dart index b30150d06..8a1f2aaf1 100644 --- a/mobile/plugins/ente_cast_normal/lib/src/service.dart +++ b/mobile/plugins/ente_cast_normal/lib/src/service.dart @@ -59,7 +59,9 @@ class CastServiceImpl extends CastService { @override Future> searchDevices() { - return CastDiscoveryService().search().then((devices) { + return CastDiscoveryService() + .search(timeout: const Duration(seconds: 7)) + .then((devices) { return devices.map((device) => (device.name, device)).toList(); }); } From aa81e14fed51831700a2972fad0cb2830f057fd6 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 15:22:21 +0530 Subject: [PATCH 259/367] Draft release for testing --- .../workflows/desktop-draft-release.yml | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 desktop/.github/workflows/desktop-draft-release.yml diff --git a/desktop/.github/workflows/desktop-draft-release.yml b/desktop/.github/workflows/desktop-draft-release.yml new file mode 100644 index 000000000..8c0652dfc --- /dev/null +++ b/desktop/.github/workflows/desktop-draft-release.yml @@ -0,0 +1,70 @@ +name: "Draft release" + +# Build the desktop/draft-release branch and update the existing draft release +# with the resultant artifacts. +# +# This is meant for doing tests that require the app to be signed and packaged. +# Such releases should not be published to end users. +# +# Workflow: +# +# 1. Push your changes to the "desktop/draft-release" branch on +# https://github.com/ente-io/ente. +# +# 2. Create a draft release with tag equal to the version in the `package.json`. +# +# 3. Trigger this workflow. You can trigger it multiple times, each time it'll +# just update the artifacts attached to the same draft. +# +# 4. Once testing is done delete the draft. + +on: + # Trigger manually or `gh workflow run desktop-draft-release.yml`. + workflow_dispatch: + +jobs: + release: + runs-on: macos-latest + + defaults: + run: + working-directory: desktop + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + repository: ente-io/ente + ref: desktop/draft-release + submodules: recursive + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: yarn install + + - name: Build + uses: ente-io/action-electron-builder@v1.0.0 + with: + package_root: desktop + + # GitHub token, automatically provided to the action + # (No need to define this secret in the repo settings) + github_token: ${{ secrets.GITHUB_TOKEN }} + + # If the commit is tagged with a version (e.g. "v1.0.0"), + # release the app after building. + release: ${{ startsWith(github.ref, 'refs/tags/v') }} + + mac_certs: ${{ secrets.MAC_CERTS }} + mac_certs_password: ${{ secrets.MAC_CERTS_PASSWORD }} + env: + # macOS notarization credentials key details + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD: + ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + USE_HARD_LINKS: false From 90d56f38863a3b5ab29fadd7f141788b911ba18d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 15:26:26 +0530 Subject: [PATCH 260/367] Show dev tools always --- desktop/src/main/menu.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index b6fa7acfe..e521fdd5c 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -25,7 +25,8 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { process.platform == "darwin" ? options : []; const devOnly = (options: MenuItemConstructorOptions[]) => - isDev ? options : []; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + (isDev ?? true) ? options : []; const handleCheckForUpdates = () => forceCheckForAppUpdates(mainWindow); From c2f5c3968c9ba1a21f8e103a6486d50970db3d0b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 15:30:57 +0530 Subject: [PATCH 261/367] Note what I noticed --- desktop/src/main.ts | 2 +- desktop/src/main/menu.ts | 4 ++-- desktop/src/main/services/app-update.ts | 9 +++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 49b316206..5a3e3dac4 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -375,7 +375,7 @@ const main = () => { // Continue on with the rest of the startup sequence. Menu.setApplicationMenu(await createApplicationMenu(mainWindow)); setupTrayItem(mainWindow); - if (!isDev) setupAutoUpdater(mainWindow); + setupAutoUpdater(mainWindow); try { await deleteLegacyDiskCacheDirIfExists(); diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index e521fdd5c..9d02ae008 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -131,11 +131,11 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { submenu: [ { role: "startSpeaking", - label: "start speaking", + label: "Start Speaking", }, { role: "stopSpeaking", - label: "stop speaking", + label: "Stop Speaking", }, ], }, diff --git a/desktop/src/main/services/app-update.ts b/desktop/src/main/services/app-update.ts index 8d66cb8c3..5788b9b27 100644 --- a/desktop/src/main/services/app-update.ts +++ b/desktop/src/main/services/app-update.ts @@ -6,11 +6,20 @@ import { allowWindowClose } from "../../main"; import { AppUpdate } from "../../types/ipc"; import log from "../log"; import { userPreferences } from "../stores/user-preferences"; +import { isDev } from "../utils/electron"; export const setupAutoUpdater = (mainWindow: BrowserWindow) => { autoUpdater.logger = electronLog; autoUpdater.autoDownload = false; + // Skip checking for updates automatically in dev builds. Installing an + // update would fail anyway since (at least on macOS), the auto update + // process requires signed builds. + // + // Even though this is skipped on app start, we can still use the "Check for + // updates..." menu option to trigger the update if we wish in dev builds. + if (isDev) return; + const oneDay = 1 * 24 * 60 * 60 * 1000; setInterval(() => void checkForUpdatesAndNotify(mainWindow), oneDay); void checkForUpdatesAndNotify(mainWindow); From 0531438cf0a020e95136ae1e5620be1423fe100c Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 6 May 2024 15:33:15 +0530 Subject: [PATCH 262/367] [mob] Bump version 0.8.91+611 --- mobile/lib/generated/intl/messages_pt.dart | 41 ++++++++++++++++++---- mobile/lib/generated/intl/messages_zh.dart | 20 +++++++++++ mobile/pubspec.yaml | 2 +- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/mobile/lib/generated/intl/messages_pt.dart b/mobile/lib/generated/intl/messages_pt.dart index 50552dc66..ef6dc5e54 100644 --- a/mobile/lib/generated/intl/messages_pt.dart +++ b/mobile/lib/generated/intl/messages_pt.dart @@ -280,7 +280,7 @@ class MessageLookup extends MessageLookupByLibrary { "allowAddingPhotos": MessageLookupByLibrary.simpleMessage("Permitir adicionar fotos"), "allowDownloads": - MessageLookupByLibrary.simpleMessage("Permitir transferências"), + MessageLookupByLibrary.simpleMessage("Permitir downloads"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( "Permitir que pessoas adicionem fotos"), "androidBiometricHint": @@ -311,7 +311,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Aplicar código"), "appstoreSubscription": MessageLookupByLibrary.simpleMessage("Assinatura da AppStore"), - "archive": MessageLookupByLibrary.simpleMessage("Arquivado"), + "archive": MessageLookupByLibrary.simpleMessage("Arquivar"), "archiveAlbum": MessageLookupByLibrary.simpleMessage("Arquivar álbum"), "archiving": MessageLookupByLibrary.simpleMessage("Arquivando..."), "areYouSureThatYouWantToLeaveTheFamily": @@ -365,6 +365,14 @@ class MessageLookup extends MessageLookupByLibrary { "Falha na autenticação. Por favor, tente novamente"), "authenticationSuccessful": MessageLookupByLibrary.simpleMessage("Autenticação bem-sucedida!"), + "autoCastDialogBody": MessageLookupByLibrary.simpleMessage( + "Você verá dispositivos disponíveis para transmitir aqui."), + "autoCastiOSPermission": MessageLookupByLibrary.simpleMessage( + "Certifique-se de que as permissões de Rede local estão ativadas para o aplicativo de Fotos Ente, em Configurações."), + "autoPair": + MessageLookupByLibrary.simpleMessage("Pareamento automático"), + "autoPairDesc": MessageLookupByLibrary.simpleMessage( + "O pareamento automático funciona apenas com dispositivos que suportam o Chromecast."), "available": MessageLookupByLibrary.simpleMessage("Disponível"), "backedUpFolders": MessageLookupByLibrary.simpleMessage("Backup de pastas concluído"), @@ -397,6 +405,10 @@ class MessageLookup extends MessageLookupByLibrary { "cannotAddMorePhotosAfterBecomingViewer": m9, "cannotDeleteSharedFiles": MessageLookupByLibrary.simpleMessage( "Não é possível excluir arquivos compartilhados"), + "castIPMismatchBody": MessageLookupByLibrary.simpleMessage( + "Certifique-se de estar na mesma rede que a TV."), + "castIPMismatchTitle": + MessageLookupByLibrary.simpleMessage("Falha ao transmitir álbum"), "castInstruction": MessageLookupByLibrary.simpleMessage( "Visite cast.ente.io no dispositivo que você deseja parear.\n\ndigite o código abaixo para reproduzir o álbum em sua TV."), "centerPoint": MessageLookupByLibrary.simpleMessage("Ponto central"), @@ -470,6 +482,8 @@ class MessageLookup extends MessageLookupByLibrary { "Confirme a chave de recuperação"), "confirmYourRecoveryKey": MessageLookupByLibrary.simpleMessage( "Confirme sua chave de recuperação"), + "connectToDevice": + MessageLookupByLibrary.simpleMessage("Conectar ao dispositivo"), "contactFamilyAdmin": m12, "contactSupport": MessageLookupByLibrary.simpleMessage("Contate o suporte"), @@ -551,7 +565,7 @@ class MessageLookup extends MessageLookupByLibrary { "deleteFromDevice": MessageLookupByLibrary.simpleMessage("Excluir do dispositivo"), "deleteFromEnte": - MessageLookupByLibrary.simpleMessage("Excluir do ente"), + MessageLookupByLibrary.simpleMessage("Excluir do Ente"), "deleteItemCount": m14, "deleteLocation": MessageLookupByLibrary.simpleMessage("Excluir Local"), "deletePhotos": MessageLookupByLibrary.simpleMessage("Excluir fotos"), @@ -667,7 +681,7 @@ class MessageLookup extends MessageLookupByLibrary { "enterCode": MessageLookupByLibrary.simpleMessage("Coloque o código"), "enterCodeDescription": MessageLookupByLibrary.simpleMessage( "Digite o código fornecido pelo seu amigo para reivindicar o armazenamento gratuito para vocês dois"), - "enterEmail": MessageLookupByLibrary.simpleMessage("Digite o email"), + "enterEmail": MessageLookupByLibrary.simpleMessage("Insira o e-mail"), "enterFileName": MessageLookupByLibrary.simpleMessage("Digite o nome do arquivo"), "enterNewPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( @@ -738,6 +752,8 @@ class MessageLookup extends MessageLookupByLibrary { "filesBackedUpInAlbum": m23, "filesDeleted": MessageLookupByLibrary.simpleMessage("Arquivos excluídos"), + "filesSavedToGallery": + MessageLookupByLibrary.simpleMessage("Arquivos salvos na galeria"), "flip": MessageLookupByLibrary.simpleMessage("Inverter"), "forYourMemories": MessageLookupByLibrary.simpleMessage("para suas memórias"), @@ -821,7 +837,7 @@ class MessageLookup extends MessageLookupByLibrary { "A chave de recuperação que você digitou não é válida. Certifique-se de que contém 24 palavras e verifique a ortografia de cada uma.\n\nSe você inseriu um código de recuperação mais antigo, verifique se ele tem 64 caracteres e verifique cada um deles."), "invite": MessageLookupByLibrary.simpleMessage("Convidar"), "inviteToEnte": - MessageLookupByLibrary.simpleMessage("Convidar para o ente"), + MessageLookupByLibrary.simpleMessage("Convidar para o Ente"), "inviteYourFriends": MessageLookupByLibrary.simpleMessage("Convide seus amigos"), "inviteYourFriendsToEnte": @@ -929,6 +945,8 @@ class MessageLookup extends MessageLookupByLibrary { "manageParticipants": MessageLookupByLibrary.simpleMessage("Gerenciar"), "manageSubscription": MessageLookupByLibrary.simpleMessage("Gerenciar assinatura"), + "manualPairDesc": MessageLookupByLibrary.simpleMessage( + "Parear com o PIN funciona com qualquer tela que você deseja ver o seu álbum ativado."), "map": MessageLookupByLibrary.simpleMessage("Mapa"), "maps": MessageLookupByLibrary.simpleMessage("Mapas"), "mastodon": MessageLookupByLibrary.simpleMessage("Mastodon"), @@ -964,6 +982,8 @@ class MessageLookup extends MessageLookupByLibrary { "no": MessageLookupByLibrary.simpleMessage("Não"), "noAlbumsSharedByYouYet": MessageLookupByLibrary.simpleMessage( "Nenhum álbum compartilhado por você ainda"), + "noDeviceFound": MessageLookupByLibrary.simpleMessage( + "Nenhum dispositivo encontrado"), "noDeviceLimit": MessageLookupByLibrary.simpleMessage("Nenhum"), "noDeviceThatCanBeDeleted": MessageLookupByLibrary.simpleMessage( "Você não tem nenhum arquivo neste dispositivo que pode ser excluído"), @@ -1012,6 +1032,9 @@ class MessageLookup extends MessageLookupByLibrary { "orPickAnExistingOne": MessageLookupByLibrary.simpleMessage("Ou escolha um existente"), "pair": MessageLookupByLibrary.simpleMessage("Parear"), + "pairWithPin": MessageLookupByLibrary.simpleMessage("Parear com PIN"), + "pairingComplete": + MessageLookupByLibrary.simpleMessage("Pareamento concluído"), "passkey": MessageLookupByLibrary.simpleMessage("Chave de acesso"), "passkeyAuthTitle": MessageLookupByLibrary.simpleMessage( "Autenticação via Chave de acesso"), @@ -1380,6 +1403,10 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("✨ Bem-sucedido"), "startBackup": MessageLookupByLibrary.simpleMessage("Iniciar backup"), "status": MessageLookupByLibrary.simpleMessage("Estado"), + "stopCastingBody": MessageLookupByLibrary.simpleMessage( + "Você quer parar a transmissão?"), + "stopCastingTitle": + MessageLookupByLibrary.simpleMessage("Parar transmissão"), "storage": MessageLookupByLibrary.simpleMessage("Armazenamento"), "storageBreakupFamily": MessageLookupByLibrary.simpleMessage("Família"), "storageBreakupYou": MessageLookupByLibrary.simpleMessage("Você"), @@ -1425,7 +1452,7 @@ class MessageLookup extends MessageLookupByLibrary { "thankYouForSubscribing": MessageLookupByLibrary.simpleMessage("Obrigado por assinar!"), "theDownloadCouldNotBeCompleted": MessageLookupByLibrary.simpleMessage( - "Não foi possível concluir a transferência"), + "Não foi possível concluir o download"), "theRecoveryKeyYouEnteredIsIncorrect": MessageLookupByLibrary.simpleMessage( "A chave de recuperação inserida está incorreta"), @@ -1525,7 +1552,7 @@ class MessageLookup extends MessageLookupByLibrary { "verificationId": MessageLookupByLibrary.simpleMessage("ID de Verificação"), "verify": MessageLookupByLibrary.simpleMessage("Verificar"), - "verifyEmail": MessageLookupByLibrary.simpleMessage("Verificar email"), + "verifyEmail": MessageLookupByLibrary.simpleMessage("Verificar e-mail"), "verifyEmailID": m65, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("Verificar"), "verifyPasskey": diff --git a/mobile/lib/generated/intl/messages_zh.dart b/mobile/lib/generated/intl/messages_zh.dart index 7be447f89..80cc13569 100644 --- a/mobile/lib/generated/intl/messages_zh.dart +++ b/mobile/lib/generated/intl/messages_zh.dart @@ -320,6 +320,13 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("身份验证失败,请重试"), "authenticationSuccessful": MessageLookupByLibrary.simpleMessage("验证成功"), + "autoCastDialogBody": + MessageLookupByLibrary.simpleMessage("您将在此处看到可用的 Cast 设备。"), + "autoCastiOSPermission": MessageLookupByLibrary.simpleMessage( + "请确保已在“设置”中为 Ente Photos 应用打开本地网络权限。"), + "autoPair": MessageLookupByLibrary.simpleMessage("自动配对"), + "autoPairDesc": + MessageLookupByLibrary.simpleMessage("自动配对仅适用于支持 Chromecast 的设备。"), "available": MessageLookupByLibrary.simpleMessage("可用"), "backedUpFolders": MessageLookupByLibrary.simpleMessage("已备份的文件夹"), "backup": MessageLookupByLibrary.simpleMessage("备份"), @@ -344,6 +351,9 @@ class MessageLookup extends MessageLookupByLibrary { "cannotAddMorePhotosAfterBecomingViewer": m9, "cannotDeleteSharedFiles": MessageLookupByLibrary.simpleMessage("无法删除共享文件"), + "castIPMismatchBody": + MessageLookupByLibrary.simpleMessage("请确保您的设备与电视处于同一网络。"), + "castIPMismatchTitle": MessageLookupByLibrary.simpleMessage("投放相册失败"), "castInstruction": MessageLookupByLibrary.simpleMessage( "在您要配对的设备上访问 cast.ente.io。\n输入下面的代码即可在电视上播放相册。"), "centerPoint": MessageLookupByLibrary.simpleMessage("中心点"), @@ -400,6 +410,7 @@ class MessageLookup extends MessageLookupByLibrary { "confirmRecoveryKey": MessageLookupByLibrary.simpleMessage("确认恢复密钥"), "confirmYourRecoveryKey": MessageLookupByLibrary.simpleMessage("确认您的恢复密钥"), + "connectToDevice": MessageLookupByLibrary.simpleMessage("连接到设备"), "contactFamilyAdmin": m12, "contactSupport": MessageLookupByLibrary.simpleMessage("联系支持"), "contactToManageSubscription": m13, @@ -610,6 +621,8 @@ class MessageLookup extends MessageLookupByLibrary { "filesBackedUpFromDevice": m22, "filesBackedUpInAlbum": m23, "filesDeleted": MessageLookupByLibrary.simpleMessage("文件已删除"), + "filesSavedToGallery": + MessageLookupByLibrary.simpleMessage("多个文件已保存到相册"), "flip": MessageLookupByLibrary.simpleMessage("上下翻转"), "forYourMemories": MessageLookupByLibrary.simpleMessage("为您的回忆"), "forgotPassword": MessageLookupByLibrary.simpleMessage("忘记密码"), @@ -765,6 +778,8 @@ class MessageLookup extends MessageLookupByLibrary { "manageLink": MessageLookupByLibrary.simpleMessage("管理链接"), "manageParticipants": MessageLookupByLibrary.simpleMessage("管理"), "manageSubscription": MessageLookupByLibrary.simpleMessage("管理订阅"), + "manualPairDesc": MessageLookupByLibrary.simpleMessage( + "用 PIN 码配对适用于您希望在其上查看相册的任何屏幕。"), "map": MessageLookupByLibrary.simpleMessage("地图"), "maps": MessageLookupByLibrary.simpleMessage("地图"), "mastodon": MessageLookupByLibrary.simpleMessage("Mastodon"), @@ -797,6 +812,7 @@ class MessageLookup extends MessageLookupByLibrary { "no": MessageLookupByLibrary.simpleMessage("否"), "noAlbumsSharedByYouYet": MessageLookupByLibrary.simpleMessage("您尚未共享任何相册"), + "noDeviceFound": MessageLookupByLibrary.simpleMessage("未发现设备"), "noDeviceLimit": MessageLookupByLibrary.simpleMessage("无"), "noDeviceThatCanBeDeleted": MessageLookupByLibrary.simpleMessage("您在此设备上没有可被删除的文件"), @@ -837,6 +853,8 @@ class MessageLookup extends MessageLookupByLibrary { "orPickAnExistingOne": MessageLookupByLibrary.simpleMessage("或者选择一个现有的"), "pair": MessageLookupByLibrary.simpleMessage("配对"), + "pairWithPin": MessageLookupByLibrary.simpleMessage("用 PIN 配对"), + "pairingComplete": MessageLookupByLibrary.simpleMessage("配对完成"), "passkey": MessageLookupByLibrary.simpleMessage("通行密钥"), "passkeyAuthTitle": MessageLookupByLibrary.simpleMessage("通行密钥认证"), "password": MessageLookupByLibrary.simpleMessage("密码"), @@ -1115,6 +1133,8 @@ class MessageLookup extends MessageLookupByLibrary { "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ 成功"), "startBackup": MessageLookupByLibrary.simpleMessage("开始备份"), "status": MessageLookupByLibrary.simpleMessage("状态"), + "stopCastingBody": MessageLookupByLibrary.simpleMessage("您想停止投放吗?"), + "stopCastingTitle": MessageLookupByLibrary.simpleMessage("停止投放"), "storage": MessageLookupByLibrary.simpleMessage("存储空间"), "storageBreakupFamily": MessageLookupByLibrary.simpleMessage("家庭"), "storageBreakupYou": MessageLookupByLibrary.simpleMessage("您"), diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 385deb769..4152de555 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.90+610 +version: 0.8.91+611 publish_to: none environment: From 51e9383ce0c1d401b89754798033b3c48952f4e1 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 15:33:21 +0530 Subject: [PATCH 263/367] lint --- desktop/src/main/menu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index 9d02ae008..85c37b95f 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -26,7 +26,7 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { const devOnly = (options: MenuItemConstructorOptions[]) => // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - (isDev ?? true) ? options : []; + isDev ?? true ? options : []; const handleCheckForUpdates = () => forceCheckForAppUpdates(mainWindow); From d8e917ac401e64e956884df92fefcb7cebd9f9e9 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 6 May 2024 15:51:15 +0530 Subject: [PATCH 264/367] [cli] Hint user to create a account before adding to cli --- cli/pkg/account.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/pkg/account.go b/cli/pkg/account.go index 9363e2f80..e411ffacd 100644 --- a/cli/pkg/account.go +++ b/cli/pkg/account.go @@ -59,7 +59,7 @@ func (c *ClICtrl) AddAccount(cxt context.Context) { authResponse, flowErr = c.validateTOTP(cxt, authResponse) } if authResponse.EncryptedToken == "" || authResponse.KeyAttributes == nil { - panic("no encrypted token or keyAttributes") + log.Fatalf("missing key attributes or token.\nNote: Please use the mobile,web or desktop app to create a new account.\nIf you are trying to login to an existing account, report a bug.") } secretInfo, decErr := c.decryptAccSecretInfo(cxt, authResponse, keyEncKey) if decErr != nil { From f097535756304921f2e253359a780881e299047a Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 6 May 2024 15:53:47 +0530 Subject: [PATCH 265/367] [cli] Improve ente account add doc --- cli/cmd/account.go | 3 ++- cli/docs/generated/ente.md | 2 +- cli/docs/generated/ente_account.md | 4 ++-- cli/docs/generated/ente_account_add.md | 8 ++++++-- cli/docs/generated/ente_account_get-token.md | 2 +- cli/docs/generated/ente_account_list.md | 2 +- cli/docs/generated/ente_account_update.md | 2 +- cli/docs/generated/ente_admin.md | 2 +- cli/docs/generated/ente_admin_delete-user.md | 2 +- cli/docs/generated/ente_admin_disable-2fa.md | 2 +- cli/docs/generated/ente_admin_get-user-id.md | 2 +- cli/docs/generated/ente_admin_list-users.md | 2 +- cli/docs/generated/ente_admin_update-subscription.md | 2 +- cli/docs/generated/ente_auth.md | 2 +- cli/docs/generated/ente_auth_decrypt.md | 2 +- cli/docs/generated/ente_export.md | 2 +- cli/docs/generated/ente_version.md | 2 +- 17 files changed, 24 insertions(+), 19 deletions(-) diff --git a/cli/cmd/account.go b/cli/cmd/account.go index a4c78fb10..4bc48dcf3 100644 --- a/cli/cmd/account.go +++ b/cli/cmd/account.go @@ -27,7 +27,8 @@ var listAccCmd = &cobra.Command{ // Subcommand for 'account add' var addAccCmd = &cobra.Command{ Use: "add", - Short: "Add a new account", + Short: "login into existing account", + Long: "Use this command to add an existing account to cli. For creating a new account, use the mobile,web or desktop app", Run: func(cmd *cobra.Command, args []string) { recoverWithLog() ctrl.AddAccount(context.Background()) diff --git a/cli/docs/generated/ente.md b/cli/docs/generated/ente.md index b9d3cde17..4f85dd098 100644 --- a/cli/docs/generated/ente.md +++ b/cli/docs/generated/ente.md @@ -25,4 +25,4 @@ ente [flags] * [ente export](ente_export.md) - Starts the export process * [ente version](ente_version.md) - Prints the current version -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_account.md b/cli/docs/generated/ente_account.md index c48a65336..41c37b054 100644 --- a/cli/docs/generated/ente_account.md +++ b/cli/docs/generated/ente_account.md @@ -11,9 +11,9 @@ Manage account settings ### SEE ALSO * [ente](ente.md) - CLI tool for exporting your photos from ente.io -* [ente account add](ente_account_add.md) - Add a new account +* [ente account add](ente_account_add.md) - login into existing account * [ente account get-token](ente_account_get-token.md) - Get token for an account for a specific app * [ente account list](ente_account_list.md) - list configured accounts * [ente account update](ente_account_update.md) - Update an existing account's export directory -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_account_add.md b/cli/docs/generated/ente_account_add.md index 1904ca370..1e86ae12f 100644 --- a/cli/docs/generated/ente_account_add.md +++ b/cli/docs/generated/ente_account_add.md @@ -1,6 +1,10 @@ ## ente account add -Add a new account +login into existing account + +### Synopsis + +Use this command to add an existing account to cli. For creating a new account, use the mobile,web or desktop app ``` ente account add [flags] @@ -16,4 +20,4 @@ ente account add [flags] * [ente account](ente_account.md) - Manage account settings -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_account_get-token.md b/cli/docs/generated/ente_account_get-token.md index d7ee77255..3d8814d7d 100644 --- a/cli/docs/generated/ente_account_get-token.md +++ b/cli/docs/generated/ente_account_get-token.md @@ -18,4 +18,4 @@ ente account get-token [flags] * [ente account](ente_account.md) - Manage account settings -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_account_list.md b/cli/docs/generated/ente_account_list.md index cfc59bb8d..a7677eb85 100644 --- a/cli/docs/generated/ente_account_list.md +++ b/cli/docs/generated/ente_account_list.md @@ -16,4 +16,4 @@ ente account list [flags] * [ente account](ente_account.md) - Manage account settings -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_account_update.md b/cli/docs/generated/ente_account_update.md index acb65412a..8d9c8d7e5 100644 --- a/cli/docs/generated/ente_account_update.md +++ b/cli/docs/generated/ente_account_update.md @@ -19,4 +19,4 @@ ente account update [flags] * [ente account](ente_account.md) - Manage account settings -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_admin.md b/cli/docs/generated/ente_admin.md index aafe51b39..5ac72489d 100644 --- a/cli/docs/generated/ente_admin.md +++ b/cli/docs/generated/ente_admin.md @@ -21,4 +21,4 @@ Commands for admin actions like disable or enabling 2fa, bumping up the storage * [ente admin list-users](ente_admin_list-users.md) - List all users * [ente admin update-subscription](ente_admin_update-subscription.md) - Update subscription for user -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_admin_delete-user.md b/cli/docs/generated/ente_admin_delete-user.md index 56c96841e..a1d52a73d 100644 --- a/cli/docs/generated/ente_admin_delete-user.md +++ b/cli/docs/generated/ente_admin_delete-user.md @@ -18,4 +18,4 @@ ente admin delete-user [flags] * [ente admin](ente_admin.md) - Commands for admin actions -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_admin_disable-2fa.md b/cli/docs/generated/ente_admin_disable-2fa.md index 333f0912e..23cd33080 100644 --- a/cli/docs/generated/ente_admin_disable-2fa.md +++ b/cli/docs/generated/ente_admin_disable-2fa.md @@ -18,4 +18,4 @@ ente admin disable-2fa [flags] * [ente admin](ente_admin.md) - Commands for admin actions -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_admin_get-user-id.md b/cli/docs/generated/ente_admin_get-user-id.md index 3d26f624a..47d632abb 100644 --- a/cli/docs/generated/ente_admin_get-user-id.md +++ b/cli/docs/generated/ente_admin_get-user-id.md @@ -18,4 +18,4 @@ ente admin get-user-id [flags] * [ente admin](ente_admin.md) - Commands for admin actions -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_admin_list-users.md b/cli/docs/generated/ente_admin_list-users.md index 8841df57b..635e8ec3c 100644 --- a/cli/docs/generated/ente_admin_list-users.md +++ b/cli/docs/generated/ente_admin_list-users.md @@ -17,4 +17,4 @@ ente admin list-users [flags] * [ente admin](ente_admin.md) - Commands for admin actions -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_admin_update-subscription.md b/cli/docs/generated/ente_admin_update-subscription.md index cc1fa9623..d0fadcd2b 100644 --- a/cli/docs/generated/ente_admin_update-subscription.md +++ b/cli/docs/generated/ente_admin_update-subscription.md @@ -23,4 +23,4 @@ ente admin update-subscription [flags] * [ente admin](ente_admin.md) - Commands for admin actions -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_auth.md b/cli/docs/generated/ente_auth.md index 5770f36f3..e0e97d84f 100644 --- a/cli/docs/generated/ente_auth.md +++ b/cli/docs/generated/ente_auth.md @@ -13,4 +13,4 @@ Authenticator commands * [ente](ente.md) - CLI tool for exporting your photos from ente.io * [ente auth decrypt](ente_auth_decrypt.md) - Decrypt authenticator export -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_auth_decrypt.md b/cli/docs/generated/ente_auth_decrypt.md index e573db2a3..c9db6ea54 100644 --- a/cli/docs/generated/ente_auth_decrypt.md +++ b/cli/docs/generated/ente_auth_decrypt.md @@ -16,4 +16,4 @@ ente auth decrypt [input] [output] [flags] * [ente auth](ente_auth.md) - Authenticator commands -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_export.md b/cli/docs/generated/ente_export.md index c5783236c..d809e06e4 100644 --- a/cli/docs/generated/ente_export.md +++ b/cli/docs/generated/ente_export.md @@ -16,4 +16,4 @@ ente export [flags] * [ente](ente.md) - CLI tool for exporting your photos from ente.io -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 diff --git a/cli/docs/generated/ente_version.md b/cli/docs/generated/ente_version.md index b51055697..08f384b52 100644 --- a/cli/docs/generated/ente_version.md +++ b/cli/docs/generated/ente_version.md @@ -16,4 +16,4 @@ ente version [flags] * [ente](ente.md) - CLI tool for exporting your photos from ente.io -###### Auto generated by spf13/cobra on 14-Mar-2024 +###### Auto generated by spf13/cobra on 6-May-2024 From 5f964e533c1e9d224c27d89be794e9d24e78d020 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 16:07:35 +0530 Subject: [PATCH 266/367] Show always (for temp debugging) --- desktop/src/main/menu.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index 85c37b95f..279e7f05c 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -10,7 +10,7 @@ import { forceCheckForAppUpdates } from "./services/app-update"; import autoLauncher from "./services/auto-launcher"; import { openLogDirectory } from "./services/dir"; import { userPreferences } from "./stores/user-preferences"; -import { isDev } from "./utils/electron"; +// import { isDev } from "./utils/electron"; /** Create and return the entries in the app's main menu bar */ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { @@ -24,9 +24,10 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { const macOSOnly = (options: MenuItemConstructorOptions[]) => process.platform == "darwin" ? options : []; - const devOnly = (options: MenuItemConstructorOptions[]) => - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - isDev ?? true ? options : []; + // TODO(MR): Desktop-release + // const devOnly = (options: MenuItemConstructorOptions[]) => + // // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + // isDev ?? true ? options : []; const handleCheckForUpdates = () => forceCheckForAppUpdates(mainWindow); @@ -146,9 +147,9 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { label: "View", submenu: [ { label: "Reload", role: "reload" }, - ...devOnly([ - { label: "Toggle Dev Tools", role: "toggleDevTools" }, - ]), + // ...devOnly([ + { label: "Toggle Dev Tools", role: "toggleDevTools" }, + // ]), { type: "separator" }, { label: "Toggle Full Screen", role: "togglefullscreen" }, ], From a8d3a8bfe8c88708edbce504161b551ec053af07 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 16:08:23 +0530 Subject: [PATCH 267/367] Remove duplicate notarization attempt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From build logs: • notarization successful Error: The appleIdPassword property is required when using notarization with password credentials at validateNotaryToolAuthorizationArgs (/Users/runner/work/photos-desktop/photos-desktop/desktop/node_modules/electron-notarize/src/validate-args.ts:107:13) Not sure, but at the face of it (since this was not enabled earlier too), our notarization process kicks in during signing because of the presence of env vars, and specifying it again here starts a separate process we haven't configured. --- desktop/electron-builder.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/desktop/electron-builder.yml b/desktop/electron-builder.yml index f62033fb9..298b1c5f3 100644 --- a/desktop/electron-builder.yml +++ b/desktop/electron-builder.yml @@ -29,5 +29,4 @@ mac: arch: [universal] category: public.app-category.photography hardenedRuntime: true - notarize: true afterSign: electron-builder-notarize From 8a8d29e6b903adae48884bf5d6afea4f94d8a75f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 16:29:51 +0530 Subject: [PATCH 268/367] Fix loading of preload scripts --- desktop/src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 5a3e3dac4..242303ccf 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -142,7 +142,7 @@ const createMainWindow = () => { // Create the main window. This'll show our web content. const window = new BrowserWindow({ webPreferences: { - preload: path.join(app.getAppPath(), "preload.js"), + preload: path.join(__dirname, "preload.js"), sandbox: true, }, // The color to show in the window until the web content gets loaded. From 0af7d2c13e0c99da188ce092caef86f8de7d283f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 16:31:18 +0530 Subject: [PATCH 269/367] Retain the devtools option for perhaps another build to help in extreme cases --- desktop/src/main/menu.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index 279e7f05c..45cbd6362 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -10,7 +10,6 @@ import { forceCheckForAppUpdates } from "./services/app-update"; import autoLauncher from "./services/auto-launcher"; import { openLogDirectory } from "./services/dir"; import { userPreferences } from "./stores/user-preferences"; -// import { isDev } from "./utils/electron"; /** Create and return the entries in the app's main menu bar */ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { @@ -24,11 +23,6 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { const macOSOnly = (options: MenuItemConstructorOptions[]) => process.platform == "darwin" ? options : []; - // TODO(MR): Desktop-release - // const devOnly = (options: MenuItemConstructorOptions[]) => - // // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - // isDev ?? true ? options : []; - const handleCheckForUpdates = () => forceCheckForAppUpdates(mainWindow); const handleViewChangelog = () => @@ -147,9 +141,7 @@ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { label: "View", submenu: [ { label: "Reload", role: "reload" }, - // ...devOnly([ { label: "Toggle Dev Tools", role: "toggleDevTools" }, - // ]), { type: "separator" }, { label: "Toggle Full Screen", role: "togglefullscreen" }, ], From be01f8805802ac371ef5e641b68ed2e1f5215ce5 Mon Sep 17 00:00:00 2001 From: Vishnu Mohandas Date: Mon, 6 May 2024 17:32:14 +0530 Subject: [PATCH 270/367] Update copy for Cast --- web/apps/cast/src/pages/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index f84e1d64f..00a9fdc72 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -83,7 +83,7 @@ export default function Index() { fontWeight: "normal", }} > - Enter this code on ente to pair this TV + Enter this code on Ente Photos to pair this screen
Date: Mon, 6 May 2024 19:17:06 +0530 Subject: [PATCH 271/367] Serve legacy face crops --- desktop/src/main.ts | 26 ++++++++++++++----- desktop/src/main/ipc.ts | 5 ++++ desktop/src/main/services/dir.ts | 15 +++++++++++ desktop/src/preload.ts | 4 +++ .../photos/src/components/ml/PeopleList.tsx | 17 +++++++----- web/packages/next/types/ipc.ts | 22 ++++++++++++++++ 6 files changed, 76 insertions(+), 13 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 242303ccf..0ff7664d1 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -287,13 +287,27 @@ const setupTrayItem = (mainWindow: BrowserWindow) => { /** * Older versions of our app used to maintain a cache dir using the main - * process. This has been deprecated in favor of using a normal web cache. + * process. This has been removed in favor of cache on the web layer. * - * Delete the old cache dir if it exists. This code was added March 2024, and - * can be removed after some time once most people have upgraded to newer - * versions. + * Delete the old cache dir if it exists. + * + * This will happen in two phases. The cache had three subdirectories: + * + * - Two of them, "thumbs" and "files", will be removed now (v1.7.0, May 2024). + * + * - The third one, "face-crops" will be removed once we finish the face search + * changes. See: [Note: Legacy face crops]. + * + * This migration code can be removed after some time once most people have + * upgraded to newer versions. */ const deleteLegacyDiskCacheDirIfExists = async () => { + const removeIfExists = async (dirPath: string) => { + log.info(`Removing legacy disk cache from ${dirPath}`); + await fs.rm(dirPath, { recursive: true }); + }; + // [Note: Getting the cache path] + // // The existing code was passing "cache" as a parameter to getPath. // // However, "cache" is not a valid parameter to getPath. It works! (for @@ -309,8 +323,8 @@ const deleteLegacyDiskCacheDirIfExists = async () => { // @ts-expect-error "cache" works but is not part of the public API. const cacheDir = path.join(app.getPath("cache"), "ente"); if (existsSync(cacheDir)) { - log.info(`Removing legacy disk cache from ${cacheDir}`); - await fs.rm(cacheDir, { recursive: true }); + await removeIfExists(path.join(cacheDir, "thumbs")); + await removeIfExists(path.join(cacheDir, "files")); } }; diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index f59969202..1393f4bfd 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -24,6 +24,7 @@ import { updateOnNextRestart, } from "./services/app-update"; import { + legacyFaceCrop, openDirectory, openLogDirectory, selectDirectory, @@ -198,6 +199,10 @@ export const attachIPCHandlers = () => { faceEmbedding(input), ); + ipcMain.handle("legacyFaceCrop", (_, faceID: string) => + legacyFaceCrop(faceID), + ); + // - Upload ipcMain.handle("listZipItems", (_, zipPath: string) => diff --git a/desktop/src/main/services/dir.ts b/desktop/src/main/services/dir.ts index d375648f6..d95c94f8e 100644 --- a/desktop/src/main/services/dir.ts +++ b/desktop/src/main/services/dir.ts @@ -1,5 +1,7 @@ import { shell } from "electron/common"; import { app, dialog } from "electron/main"; +import { existsSync } from "fs"; +import fs from "node:fs/promises"; import path from "node:path"; import { posixPath } from "../utils/electron"; @@ -49,3 +51,16 @@ export const openLogDirectory = () => openDirectory(logDirectoryPath()); * */ const logDirectoryPath = () => app.getPath("logs"); + +/** + * See: [Note: Legacy face crops] + */ +export const legacyFaceCrop = async ( + faceID: string, +): Promise => { + // See: [Note: Getting the cache path] + // @ts-expect-error "cache" works but is not part of the public API. + const cacheDir = path.join(app.getPath("cache"), "ente"); + const filePath = path.join(cacheDir, "face-crops", faceID); + return existsSync(filePath) ? await fs.readFile(filePath) : undefined; +}; diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index 407e541ff..f9147e288 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -164,6 +164,9 @@ const detectFaces = (input: Float32Array) => const faceEmbedding = (input: Float32Array) => ipcRenderer.invoke("faceEmbedding", input); +const legacyFaceCrop = (faceID: string) => + ipcRenderer.invoke("legacyFaceCrop", faceID); + // - Watch const watchGet = () => ipcRenderer.invoke("watchGet"); @@ -341,6 +344,7 @@ contextBridge.exposeInMainWorld("electron", { clipTextEmbeddingIfAvailable, detectFaces, faceEmbedding, + legacyFaceCrop, // - Watch diff --git a/web/apps/photos/src/components/ml/PeopleList.tsx b/web/apps/photos/src/components/ml/PeopleList.tsx index 8e6bc968f..09d9ebaaf 100644 --- a/web/apps/photos/src/components/ml/PeopleList.tsx +++ b/web/apps/photos/src/components/ml/PeopleList.tsx @@ -1,11 +1,8 @@ -import { cachedOrNew } from "@/next/blob-cache"; -import { ensureLocalUser } from "@/next/local-user"; import log from "@/next/log"; import { Skeleton, styled } from "@mui/material"; import { Legend } from "components/PhotoViewer/styledComponents/Legend"; import { t } from "i18next"; import React, { useEffect, useState } from "react"; -import machineLearningService from "services/machineLearning/machineLearningService"; import { EnteFile } from "types/file"; import { Face, Person } from "types/machineLearning"; import { getPeopleList, getUnidentifiedFaces } from "utils/machineLearning"; @@ -163,8 +160,12 @@ const FaceCropImageView: React.FC = ({ useEffect(() => { let didCancel = false; + const electron = globalThis.electron; - if (cacheKey) { + if (cacheKey && electron) { + electron + .legacyFaceCrop(cacheKey) + /* cachedOrNew("face-crops", cacheKey, async () => { const user = await ensureLocalUser(); return machineLearningService.regenerateFaceCrop( @@ -172,9 +173,11 @@ const FaceCropImageView: React.FC = ({ user.id, faceId, ); - }).then((blob) => { - if (!didCancel) setObjectURL(URL.createObjectURL(blob)); - }); + })*/ + .then((data) => { + const blob = new Blob([data]); + if (!didCancel) setObjectURL(URL.createObjectURL(blob)); + }); } else setObjectURL(undefined); return () => { diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index 4b05838fa..b4ef2b6b2 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -346,6 +346,28 @@ export interface Electron { */ faceEmbedding: (input: Float32Array) => Promise; + /** + * Return a face crop stored by a previous version of ML. + * + * [Note: Legacy face crops] + * + * Older versions of ML generated and stored face crops in a "face-crops" + * cache directory on the Electron side. For the time being, we have + * disabled the face search whilst we put finishing touches to it. However, + * it'll be nice to still show the existing faces that have been clustered + * for people who opted in to the older beta. + * + * So we retain the older "face-crops" disk cache, and use this method to + * serve faces from it when needed. + * + * @param faceID An identifier corresponding to which the face crop had been + * stored by the older version of our app. + * + * @returns the JPEG data of the face crop if a file is found for the given + * {@link faceID}, otherwise undefined. + */ + legacyFaceCrop: (faceID: string) => Promise; + // - Watch /** From 555eda06196b2573519c1f3358b909253aa54cca Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 19:28:05 +0530 Subject: [PATCH 272/367] Check --- desktop/src/main.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 0ff7664d1..9cba9178d 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -303,8 +303,10 @@ const setupTrayItem = (mainWindow: BrowserWindow) => { */ const deleteLegacyDiskCacheDirIfExists = async () => { const removeIfExists = async (dirPath: string) => { - log.info(`Removing legacy disk cache from ${dirPath}`); - await fs.rm(dirPath, { recursive: true }); + if (existsSync(dirPath)) { + log.info(`Removing legacy disk cache from ${dirPath}`); + await fs.rm(dirPath, { recursive: true }); + } }; // [Note: Getting the cache path] // From 66f705ed9bbf5e8f0fdac8c893b693b0edda29e8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 19:32:57 +0530 Subject: [PATCH 273/367] Use the correct key --- web/apps/photos/src/components/ml/PeopleList.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/web/apps/photos/src/components/ml/PeopleList.tsx b/web/apps/photos/src/components/ml/PeopleList.tsx index 09d9ebaaf..3748c0d8c 100644 --- a/web/apps/photos/src/components/ml/PeopleList.tsx +++ b/web/apps/photos/src/components/ml/PeopleList.tsx @@ -58,7 +58,7 @@ export const PeopleList = React.memo((props: PeopleListProps) => { } > @@ -137,7 +137,7 @@ export function UnidentifiedFaces(props: { faces.map((face, index) => ( @@ -148,12 +148,12 @@ export function UnidentifiedFaces(props: { } interface FaceCropImageViewProps { - faceId: string; + faceID: string; cacheKey?: string; } const FaceCropImageView: React.FC = ({ - faceId, + faceID, cacheKey, }) => { const [objectURL, setObjectURL] = useState(); @@ -162,9 +162,9 @@ const FaceCropImageView: React.FC = ({ let didCancel = false; const electron = globalThis.electron; - if (cacheKey && electron) { + if (faceID && electron) { electron - .legacyFaceCrop(cacheKey) + .legacyFaceCrop(faceID) /* cachedOrNew("face-crops", cacheKey, async () => { const user = await ensureLocalUser(); @@ -184,7 +184,7 @@ const FaceCropImageView: React.FC = ({ didCancel = true; if (objectURL) URL.revokeObjectURL(objectURL); }; - }, [faceId, cacheKey]); + }, [faceID, cacheKey]); return objectURL ? ( From 94ec766bc6188e3a4be4d8444404e354e051f53e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 19:42:42 +0530 Subject: [PATCH 274/367] Add missing conditional --- web/apps/photos/src/components/ml/PeopleList.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/apps/photos/src/components/ml/PeopleList.tsx b/web/apps/photos/src/components/ml/PeopleList.tsx index 3748c0d8c..4691d4b65 100644 --- a/web/apps/photos/src/components/ml/PeopleList.tsx +++ b/web/apps/photos/src/components/ml/PeopleList.tsx @@ -175,8 +175,10 @@ const FaceCropImageView: React.FC = ({ ); })*/ .then((data) => { - const blob = new Blob([data]); - if (!didCancel) setObjectURL(URL.createObjectURL(blob)); + if (data) { + const blob = new Blob([data]); + if (!didCancel) setObjectURL(URL.createObjectURL(blob)); + } }); } else setObjectURL(undefined); From e934cc8cb4c3fd5c9df1eca763f638e5bc5fabc1 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 6 May 2024 20:11:27 +0530 Subject: [PATCH 275/367] Allow UID 1 to re-index --- .../photos/src/components/ml/MLSearchSettings.tsx | 4 ++-- web/apps/photos/src/utils/machineLearning/config.ts | 4 ++-- web/apps/photos/src/utils/user/index.ts | 12 +++++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/web/apps/photos/src/components/ml/MLSearchSettings.tsx b/web/apps/photos/src/components/ml/MLSearchSettings.tsx index 9b50c2d6a..409df4fc6 100644 --- a/web/apps/photos/src/components/ml/MLSearchSettings.tsx +++ b/web/apps/photos/src/components/ml/MLSearchSettings.tsx @@ -22,7 +22,7 @@ import { getFaceSearchEnabledStatus, updateFaceSearchEnabledStatus, } from "services/userService"; -import { isInternalUser } from "utils/user"; +import { isInternalUserForML } from "utils/user"; export const MLSearchSettings = ({ open, onClose, onRootClose }) => { const { @@ -280,7 +280,7 @@ function EnableMLSearch({ onClose, enableMlSearch, onRootClose }) {

- {isInternalUser() && ( + {isInternalUserForML() && (