From 6ee4ca73cb8497b1e502135e3d5ecec749646867 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Fri, 11 Jun 2021 11:40:38 +0200 Subject: [PATCH] feat: Implement hive --- lib/pages/views/chat_details_view.dart | 4 +- .../flutter_famedly_sdk_hive_database.dart | 148 ++++++++++++++++++ .../fluffy_client.dart | 6 +- lib/utils/room_status_extension.dart | 3 +- pubspec.lock | 20 ++- pubspec.yaml | 3 +- 6 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 lib/utils/database/flutter_famedly_sdk_hive_database.dart diff --git a/lib/pages/views/chat_details_view.dart b/lib/pages/views/chat_details_view.dart index c4593dcf..a95a333a 100644 --- a/lib/pages/views/chat_details_view.dart +++ b/lib/pages/views/chat_details_view.dart @@ -38,8 +38,8 @@ class ChatDetailsView extends StatelessWidget { } controller.members.removeWhere((u) => u.membership == Membership.leave); - final actualMembersCount = - room.mInvitedMemberCount + room.mJoinedMemberCount; + final actualMembersCount = (room.summary?.mInvitedMemberCount ?? 0) + + (room.summary?.mJoinedMemberCount ?? 0); final canRequestMoreMembers = controller.members.length < actualMembersCount; return StreamBuilder( diff --git a/lib/utils/database/flutter_famedly_sdk_hive_database.dart b/lib/utils/database/flutter_famedly_sdk_hive_database.dart new file mode 100644 index 00000000..3299881a --- /dev/null +++ b/lib/utils/database/flutter_famedly_sdk_hive_database.dart @@ -0,0 +1,148 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:famedlysdk/src/utils/crypto/encrypted_file.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:hive/hive.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:path_provider/path_provider.dart'; + +class FlutterFamedlySdkHiveDatabase extends FamedlySdkHiveDatabase { + FlutterFamedlySdkHiveDatabase(String name, {HiveCipher encryptionCipher}) + : super( + name, + encryptionCipher: encryptionCipher, + ) { + _clearOldFiles(); + Hive.registerAdapter(EncryptedFileAdapter()); + } + + static bool _hiveInitialized = false; + static const String _hiveCipherStorageKey = 'hive_encryption_key'; + + static Future hiveDatabaseBuilder( + Client client) async { + if (!kIsWeb && !_hiveInitialized) { + Logs().i('Init Hive database...'); + await Hive.initFlutter(); + _hiveInitialized = true; + } + HiveCipher hiverCipher; + try { + final secureStorage = const FlutterSecureStorage(); + final containsEncryptionKey = + await secureStorage.containsKey(key: _hiveCipherStorageKey); + if (!containsEncryptionKey) { + final key = Hive.generateSecureKey(); + await secureStorage.write( + key: _hiveCipherStorageKey, + value: base64UrlEncode(key), + ); + } + + final encryptionKey = base64Url.decode( + await secureStorage.read(key: _hiveCipherStorageKey), + ); + hiverCipher = HiveAesCipher(encryptionKey); + } on MissingPluginException catch (_) { + Logs() + .i('Hive encryption is not supported on ${Platform.operatingSystem}'); + } + final db = FamedlySdkHiveDatabase( + client.clientName, + encryptionCipher: hiverCipher, + ); + Logs().i('Open Hive database...'); + await db.open(); + Logs().i('Hive database is ready!'); + return db; + } + + @override + int get maxFileSize => PlatformInfos.isMobile ? 100 * 1024 * 1024 : 0; + @override + bool get supportsFileStoring => PlatformInfos.isMobile; + + LazyBox _fileEncryptionKeysBox; + static const String __fileEncryptionKeysBoxName = 'box.file_encryption_keys'; + + @override + Future open() async { + await super.open(); + _fileEncryptionKeysBox ??= await Hive.openLazyBox( + __fileEncryptionKeysBoxName, + encryptionCipher: encryptionCipher, + ); + } + + @override + Future getFile(String mxcUri) async { + if (!PlatformInfos.isMobile) return null; + final tempDirectory = (await getTemporaryDirectory()).path; + final file = File('$tempDirectory/$mxcUri'); + if (await file.exists() == false) return null; + final bytes = await file.readAsBytes(); + final encryptedFile = await _fileEncryptionKeysBox.get(mxcUri); + encryptedFile.data = bytes; + return await decryptFile(encryptedFile); + } + + @override + Future storeFile(String mxcUri, Uint8List bytes, int time) async { + if (!PlatformInfos.isMobile) return null; + final tempDirectory = (await getTemporaryDirectory()).path; + final file = File('$tempDirectory/$mxcUri'); + if (await file.exists()) return; + final encryptedFile = await encryptFile(bytes); + await _fileEncryptionKeysBox.put(mxcUri, encryptedFile); + await file.writeAsBytes(encryptedFile.data); + return; + } + + static const int _maxAllowedFileAge = 1000 * 60 * 60 * 24 * 30; + + @override + Future clear(int clientId) async { + await super.clear(clientId); + await _clearOldFiles(true); + } + + Future _clearOldFiles([bool clearAll = false]) async { + if (!PlatformInfos.isMobile) return null; + final tempDirectory = (await getTemporaryDirectory()); + final entities = tempDirectory.listSync(); + for (final entity in entities) { + final file = File(entity.path); + final createdAt = await file.lastModified(); + final age = DateTime.now().millisecondsSinceEpoch - + createdAt.millisecondsSinceEpoch; + if (clearAll || age > _maxAllowedFileAge) { + final mxcUri = file.path.split('/').last; + Logs().v('Delete old cashed file: $mxcUri'); + await file.delete(); + await _fileEncryptionKeysBox.delete(mxcUri); + } + } + } +} + +class EncryptedFileAdapter extends TypeAdapter { + @override + final typeId = 0; + + @override + EncryptedFile read(BinaryReader reader) { + final map = reader.read(); + return EncryptedFile(k: map['k'], iv: map['iv'], sha256: map['sha256']); + } + + @override + void write(BinaryWriter writer, EncryptedFile obj) { + writer.write({'k': obj.k, 'iv': obj.iv, 'sha256': obj.sha256}); + } +} diff --git a/lib/utils/matrix_sdk_extensions.dart/fluffy_client.dart b/lib/utils/matrix_sdk_extensions.dart/fluffy_client.dart index 50e6c7ce..6514c3ca 100644 --- a/lib/utils/matrix_sdk_extensions.dart/fluffy_client.dart +++ b/lib/utils/matrix_sdk_extensions.dart/fluffy_client.dart @@ -1,5 +1,6 @@ import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/encryption.dart'; +import 'package:fluffychat/utils/database/flutter_famedly_sdk_hive_database.dart'; import 'package:matrix_api_lite/fake_matrix_api.dart'; import '../platform_infos.dart'; import '../famedlysdk_store.dart'; @@ -25,7 +26,10 @@ class FluffyClient extends Client { importantStateEvents: { 'im.ponies.room_emotes', // we want emotes to work properly }, - databaseBuilder: testMode ? null : getDatabase, + databaseBuilder: testMode + ? null + : FlutterFamedlySdkHiveDatabase.hiveDatabaseBuilder, + legacyDatabaseBuilder: testMode ? null : getDatabase, supportedLoginTypes: { AuthenticationTypes.password, if (PlatformInfos.isMobile || PlatformInfos.isWeb) diff --git a/lib/utils/room_status_extension.dart b/lib/utils/room_status_extension.dart index b3bf8e9f..757b5e19 100644 --- a/lib/utils/room_status_extension.dart +++ b/lib/utils/room_status_extension.dart @@ -30,7 +30,8 @@ extension RoomStatusExtension on Room { } return L10n.of(context).lastSeenLongTimeAgo; } - return L10n.of(context).countParticipants(mJoinedMemberCount.toString()); + return L10n.of(context) + .countParticipants(summary.mJoinedMemberCount.toString()); } String getLocalizedTypingText(BuildContext context) { diff --git a/pubspec.lock b/pubspec.lock index 329cd50f..6f602e6d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -222,8 +222,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: "6fae2e1426fa440b09bafb9bc23e8791037c6efe" + ref: "krille/hive" + resolved-ref: bc9672d82489cdaf058c9ac00183a4611b443573 url: "https://gitlab.com/famedly/famedlysdk.git" source: git version: "0.1.0" @@ -459,6 +459,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.7.0" + hive: + dependency: transitive + description: + name: hive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" html: dependency: transitive description: @@ -584,7 +598,7 @@ packages: name: matrix_api_lite url: "https://pub.dartlang.org" source: hosted - version: "0.3.1" + version: "0.3.3" matrix_link_text: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index bd9e41bc..25a23d28 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: famedlysdk: git: url: https://gitlab.com/famedly/famedlysdk.git - ref: main + ref: krille/hive fcm_shared_isolate: git: url: https://gitlab.com/famedly/libraries/fcm_shared_isolate.git @@ -41,6 +41,7 @@ dependencies: flutter_svg: ^0.21.0+1 flutter_typeahead: ^3.1.1 future_loading_dialog: ^0.1.2 + hive_flutter: ^1.0.0 image_picker: ^0.7.4 intl: any localstorage: ^4.0.0+1