From f7345925f194efe75136cfe8ddcffe6cb37dbe8b Mon Sep 17 00:00:00 2001 From: Matthias Nehlsen Date: Fri, 3 May 2024 18:21:01 +0200 Subject: [PATCH 1/4] feat: hide LLM embedding behind feature flag --- lib/database/journal_db/config_flags.dart | 8 +++++++- lib/logic/ai/ai_logic.dart | 10 ++++++++++ lib/pages/settings/flags_page.dart | 1 + lib/utils/consts.dart | 1 + pubspec.yaml | 2 +- test/database/database_test.dart | 5 +++++ 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/database/journal_db/config_flags.dart b/lib/database/journal_db/config_flags.dart index b94f1e9de..9d7419490 100644 --- a/lib/database/journal_db/config_flags.dart +++ b/lib/database/journal_db/config_flags.dart @@ -14,7 +14,13 @@ Future initConfigFlags( status: true, ), ); - + await db.insertFlagIfNotExists( + const ConfigFlag( + name: attemptEmbedding, + description: 'Create LLM embedding', + status: false, + ), + ); await db.insertFlagIfNotExists( const ConfigFlag( name: allowInvalidCertFlag, diff --git a/lib/logic/ai/ai_logic.dart b/lib/logic/ai/ai_logic.dart index d802356a8..58ec81305 100644 --- a/lib/logic/ai/ai_logic.dart +++ b/lib/logic/ai/ai_logic.dart @@ -1,6 +1,9 @@ import 'package:flutter/foundation.dart'; import 'package:langchain/langchain.dart'; import 'package:lotti/classes/journal_entities.dart'; +import 'package:lotti/database/database.dart'; +import 'package:lotti/get_it.dart'; +import 'package:lotti/utils/consts.dart'; import 'package:lotti/utils/platform.dart'; import 'package:ollama_dart/ollama_dart.dart'; @@ -13,6 +16,13 @@ class AiLogic { JournalEntity? journalEntity, { String? linkedFromId, }) async { + final shouldAttemptEmbedding = await getIt().getConfigFlag( + attemptEmbedding, + ); + if (!shouldAttemptEmbedding) { + return; + } + final markdown = journalEntity?.entryText?.markdown; final headline = switch (journalEntity) { Task() => journalEntity.data.title, diff --git a/lib/pages/settings/flags_page.dart b/lib/pages/settings/flags_page.dart index dd676816a..a78fa0c32 100644 --- a/lib/pages/settings/flags_page.dart +++ b/lib/pages/settings/flags_page.dart @@ -22,6 +22,7 @@ class FlagsPage extends StatelessWidget { const displayedItems = { privateFlag, + attemptEmbedding, enableNotificationsFlag, autoTranscribeFlag, recordLocationFlag, diff --git a/lib/utils/consts.dart b/lib/utils/consts.dart index c516fdeaa..c2df400ef 100644 --- a/lib/utils/consts.dart +++ b/lib/utils/consts.dart @@ -6,3 +6,4 @@ const recordLocationFlag = 'record_location'; const autoTranscribeFlag = 'auto_transcribe'; const enableMatrixFlag = 'enable_matrix'; const resendAttachments = 'resend_attachments'; +const attemptEmbedding = 'attempt_embedding'; diff --git a/pubspec.yaml b/pubspec.yaml index ace5a75b9..b1f246938 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: lotti description: Achieve your goals and keep your data private with Lotti. publish_to: 'none' -version: 0.9.458+2493 +version: 0.9.459+2494 msix_config: display_name: LottiApp diff --git a/test/database/database_test.dart b/test/database/database_test.dart index 61ee19e29..0a84a47b4 100644 --- a/test/database/database_test.dart +++ b/test/database/database_test.dart @@ -16,6 +16,11 @@ final expectedFlags = { description: 'Show private entries?', status: true, ), + const ConfigFlag( + name: attemptEmbedding, + description: 'Create LLM embedding', + status: false, + ), const ConfigFlag( name: autoTranscribeFlag, description: 'Automatically transcribe audio', From e37791527daa16f061a0d865211d61d32b7949ad Mon Sep 17 00:00:00 2001 From: Matthias Nehlsen Date: Fri, 3 May 2024 18:28:16 +0200 Subject: [PATCH 2/4] feat: add database update notification service --- lib/get_it.dart | 4 ++++ lib/services/db_notification.dart | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 lib/services/db_notification.dart diff --git a/lib/get_it.dart b/lib/get_it.dart index 14a9d095f..fea56a7ef 100644 --- a/lib/get_it.dart +++ b/lib/get_it.dart @@ -14,6 +14,7 @@ import 'package:lotti/logic/ai/ai_logic.dart'; import 'package:lotti/logic/health_import.dart'; import 'package:lotti/logic/persistence_logic.dart'; import 'package:lotti/services/asr_service.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/editor_state_service.dart'; import 'package:lotti/services/entities_cache_service.dart'; import 'package:lotti/services/link_service.dart'; @@ -47,6 +48,9 @@ Future registerSingletons() async { ..registerSingleton(Fts5Db()) ..registerSingleton(getLoggingDb()) ..registerSingleton(getJournalDb()) + ..registerSingleton( + DatabaseUpdateNotifications(), + ) ..registerSingleton(EditorDb()) ..registerSingleton(TagsService()) ..registerSingleton(EntitiesCacheService()) diff --git a/lib/services/db_notification.dart b/lib/services/db_notification.dart new file mode 100644 index 000000000..0aa099593 --- /dev/null +++ b/lib/services/db_notification.dart @@ -0,0 +1,21 @@ +import 'package:flutter/foundation.dart'; +import 'package:lotti/database/database.dart'; +import 'package:lotti/get_it.dart'; + +class DatabaseUpdateNotifications { + DatabaseUpdateNotifications() { + listen(); + } + + final JournalDb _journalDb = getIt(); + + void listen() { + _journalDb.countJournalEntries().watch().listen((event) async { + final start = DateTime.now(); + final count = await _journalDb.countJournalEntries().getSingle(); + final end = DateTime.now(); + final duration = end.difference(start).inMicroseconds / 1000; + debugPrint('DatabaseUpdateNotifications $count - $duration ms'); + }); + } +} From 7af9459c33b283ed5c5f8f7114fdd4b5bf7aef6d Mon Sep 17 00:00:00 2001 From: Matthias Nehlsen Date: Fri, 3 May 2024 18:53:16 +0200 Subject: [PATCH 3/4] refactor: move databases back into main isolate --- lib/database/common.dart | 91 +++++------------------------------ lib/database/database.dart | 34 ++++++++++--- lib/database/logging_db.dart | 4 -- lib/database/settings_db.dart | 4 -- lib/database/sync_db.dart | 4 -- lib/get_it.dart | 30 +++--------- lib/main.dart | 12 +---- pubspec.lock | 2 +- pubspec.yaml | 3 +- 9 files changed, 50 insertions(+), 134 deletions(-) diff --git a/lib/database/common.dart b/lib/database/common.dart index bf797f2a6..120dc3e27 100644 --- a/lib/database/common.dart +++ b/lib/database/common.dart @@ -1,15 +1,14 @@ import 'dart:async'; import 'dart:io'; -import 'dart:isolate'; import 'package:drift/drift.dart'; -import 'package:drift/isolate.dart'; import 'package:drift/native.dart'; -import 'package:flutter/foundation.dart'; import 'package:intl/intl.dart'; -import 'package:lotti/get_it.dart'; import 'package:lotti/utils/file_utils.dart'; import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +import 'package:sqlite3/sqlite3.dart'; +import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart'; Future getDatabaseFile(String dbFileName) async { final dbFolder = getDocumentsDirectory(); @@ -33,83 +32,15 @@ LazyDatabase openDbConnection( return NativeDatabase.memory(); } - final file = await getDatabaseFile(fileName); - debugPrint('DB LazyDatabase ${file.path}'); + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, fileName)); - return NativeDatabase(file); - }); -} - -Future createDriftIsolate( - String dbFileName, { - bool inMemory = false, -}) async { - // this method is called from the main isolate. Since we can't use - // getApplicationDocumentsDirectory on a background isolate, we calculate - // the database path in the foreground isolate and then inform the - // background isolate about the path. - final dir = getDocumentsDirectory(); - final path = p.join(dir.path, dbFileName); - final receivePort = ReceivePort(); - - await Isolate.spawn( - inMemory ? _startBackgroundInMem : _startBackground, - _IsolateStartRequest(receivePort.sendPort, path), - ); - - // _startBackground will send the DriftIsolate to this ReceivePort - return await receivePort.first as DriftIsolate; -} - -void _startBackground(_IsolateStartRequest request) { - // this is the entry point from the background isolate! Let's create - // the database from the path we received - final executor = NativeDatabase(File(request.targetPath)); - // we're using DriftIsolate.inCurrent here as this method already runs on a - // background isolate. If we used DriftIsolate.spawn, a third isolate would be - // started which is not what we want! - final driftIsolate = DriftIsolate.inCurrent( - () => DatabaseConnection(executor), - ); - // inform the starting isolate about this, so that it can call .connect() - request.sendDriftIsolate.send(driftIsolate); -} - -void _startBackgroundInMem(_IsolateStartRequest request) { - final executor = NativeDatabase.memory(); - final driftIsolate = DriftIsolate.inCurrent( - () => DatabaseConnection(executor), - ); - request.sendDriftIsolate.send(driftIsolate); -} - -// used to bundle the SendPort and the target path, since isolate entry point -// functions can only take one parameter. -class _IsolateStartRequest { - _IsolateStartRequest( - this.sendDriftIsolate, - this.targetPath, - ); - - final SendPort sendDriftIsolate; - final String targetPath; -} + if (Platform.isAndroid) { + await applyWorkaroundToOpenSqlite3OnOldAndroidVersions(); + } -DatabaseConnection getDatabaseConnection(String dbFileName) { - return DatabaseConnection.delayed( - Future.sync(() async { - final isolate = await getIt>( - instanceName: dbFileName, - ); - return isolate.connect(); - }), - ); -} + sqlite3.tempDirectory = (await getTemporaryDirectory()).path; -DatabaseConnection getDbConnFromIsolate(DriftIsolate isolate) { - return DatabaseConnection.delayed( - Future.sync(() async { - return isolate.connect(); - }), - ); + return NativeDatabase.createInBackground(file); + }); } diff --git a/lib/database/database.dart b/lib/database/database.dart index 17b6e9a6b..2db2dd7fa 100644 --- a/lib/database/database.dart +++ b/lib/database/database.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; import 'package:enum_to_string/enum_to_string.dart'; import 'package:flutter/foundation.dart'; import 'package:lotti/classes/entity_definitions.dart'; @@ -16,6 +18,10 @@ import 'package:lotti/get_it.dart'; import 'package:lotti/sync/vector_clock.dart'; import 'package:lotti/utils/file_utils.dart'; import 'package:lotti/widgets/journal/entry_tools.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +import 'package:sqlite3/sqlite3.dart'; +import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart'; part 'database.g.dart'; @@ -34,13 +40,33 @@ class JournalDb extends _$JournalDb { this.inMemoryDatabase = false, String? overriddenFilename, }) : super( - openDbConnection( + _openConnection( overriddenFilename ?? journalDbFileName, inMemoryDatabase: inMemoryDatabase, ), ); - JournalDb.connect(super.connection) : super.connect(); + static LazyDatabase _openConnection( + String fileName, { + bool inMemoryDatabase = false, + }) { + return LazyDatabase(() async { + if (inMemoryDatabase) { + return NativeDatabase.memory(); + } + + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, fileName)); + + if (Platform.isAndroid) { + await applyWorkaroundToOpenSqlite3OnOldAndroidVersions(); + } + + sqlite3.tempDirectory = (await getTemporaryDirectory()).path; + + return NativeDatabase.createInBackground(file); + }); + } bool inMemoryDatabase = false; @@ -726,7 +752,3 @@ class JournalDb extends _$JournalDb { return linesAffected; } } - -JournalDb getJournalDb() { - return JournalDb.connect(getDatabaseConnection(journalDbFileName)); -} diff --git a/lib/database/logging_db.dart b/lib/database/logging_db.dart index 1be991178..17480018d 100644 --- a/lib/database/logging_db.dart +++ b/lib/database/logging_db.dart @@ -135,7 +135,3 @@ class LoggingDb extends _$LoggingDb { return allLogEntries(limit).watch(); } } - -LoggingDb getLoggingDb() { - return LoggingDb.connect(getDatabaseConnection(loggingDbFileName)); -} diff --git a/lib/database/settings_db.dart b/lib/database/settings_db.dart index 61f1f7155..cc47c39c4 100644 --- a/lib/database/settings_db.dart +++ b/lib/database/settings_db.dart @@ -55,7 +55,3 @@ class SettingsDb extends _$SettingsDb { } } } - -SettingsDb getSettingsDb() { - return SettingsDb.connect(getDatabaseConnection(settingsDbFileName)); -} diff --git a/lib/database/sync_db.dart b/lib/database/sync_db.dart index 961977b77..0903adc59 100644 --- a/lib/database/sync_db.dart +++ b/lib/database/sync_db.dart @@ -97,7 +97,3 @@ class SyncDatabase extends _$SyncDatabase { @override int get schemaVersion => 1; } - -SyncDatabase getSyncDatabase() { - return SyncDatabase.connect(getDatabaseConnection(syncDbFileName)); -} diff --git a/lib/get_it.dart b/lib/get_it.dart index fea56a7ef..a0597faf9 100644 --- a/lib/get_it.dart +++ b/lib/get_it.dart @@ -1,8 +1,6 @@ import 'dart:async'; -import 'package:drift/isolate.dart'; import 'package:get_it/get_it.dart'; -import 'package:lotti/database/common.dart'; import 'package:lotti/database/database.dart'; import 'package:lotti/database/editor_db.dart'; import 'package:lotti/database/fts5_db.dart'; @@ -14,7 +12,6 @@ import 'package:lotti/logic/ai/ai_logic.dart'; import 'package:lotti/logic/health_import.dart'; import 'package:lotti/logic/persistence_logic.dart'; import 'package:lotti/services/asr_service.dart'; -import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/editor_state_service.dart'; import 'package:lotti/services/entities_cache_service.dart'; import 'package:lotti/services/link_service.dart'; @@ -29,32 +26,17 @@ import 'package:lotti/sync/outbox/outbox_service.dart'; final getIt = GetIt.instance; Future registerSingletons() async { - await getIt.registerSingleton>( - createDriftIsolate(journalDbFileName), - instanceName: journalDbFileName, - ); - - await getIt.registerSingleton>( - createDriftIsolate(loggingDbFileName), - instanceName: loggingDbFileName, - ); - - await getIt.registerSingleton>( - createDriftIsolate(syncDbFileName), - instanceName: syncDbFileName, - ); - getIt ..registerSingleton(Fts5Db()) - ..registerSingleton(getLoggingDb()) - ..registerSingleton(getJournalDb()) - ..registerSingleton( - DatabaseUpdateNotifications(), - ) + ..registerSingleton(LoggingDb()) + ..registerSingleton(JournalDb()) + // ..registerSingleton( + // DatabaseUpdateNotifications(), + // ) ..registerSingleton(EditorDb()) ..registerSingleton(TagsService()) ..registerSingleton(EntitiesCacheService()) - ..registerSingleton(getSyncDatabase()) + ..registerSingleton(SyncDatabase()) ..registerSingleton(AsrService()) ..registerSingleton(VectorClockService()) ..registerSingleton(TimeService()) diff --git a/lib/main.dart b/lib/main.dart index c162b19ab..8b54e4860 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,13 +1,11 @@ import 'dart:async'; import 'dart:io'; -import 'package:drift/isolate.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:lotti/beamer/beamer_app.dart'; -import 'package:lotti/database/common.dart'; import 'package:lotti/database/logging_db.dart'; import 'package:lotti/database/settings_db.dart'; import 'package:lotti/get_it.dart'; @@ -34,14 +32,8 @@ Future main() async { getIt ..registerSingleton(SecureStorage()) - ..registerSingleton(docDir); - - await getIt.registerSingleton>( - createDriftIsolate(settingsDbFileName), - instanceName: settingsDbFileName, - ); - getIt - ..registerSingleton(getSettingsDb()) + ..registerSingleton(docDir) + ..registerSingleton(SettingsDb()) ..registerSingleton(WindowService()); await getIt().restore(); diff --git a/pubspec.lock b/pubspec.lock index 3f66c730d..af2a3ea7d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2501,7 +2501,7 @@ packages: source: hosted version: "2.3.3" sqlite3: - dependency: transitive + dependency: "direct main" description: name: sqlite3 sha256: "1abbeb84bf2b1a10e5e1138c913123c8aa9d83cd64e5f9a0dd847b3c83063202" diff --git a/pubspec.yaml b/pubspec.yaml index b1f246938..33a6ebc54 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: lotti description: Achieve your goals and keep your data private with Lotti. publish_to: 'none' -version: 0.9.459+2494 +version: 0.9.459+2496 msix_config: display_name: LottiApp @@ -138,6 +138,7 @@ dependencies: share_plus: ^9.0.0 sqflite: ^2.0.1 sqflite_common_ffi: ^2.3.3 + sqlite3: ^2.4.2 sqlite3_flutter_libs: ^0.5.15 timezone: ^0.9.1 tinycolor2: ^3.0.0 From 2458890a45e6fd072430a046483aaa516dfb6570 Mon Sep 17 00:00:00 2001 From: Matthias Nehlsen Date: Fri, 3 May 2024 20:19:16 +0200 Subject: [PATCH 4/4] feat: use UpdateNotifications service --- integration_test/matrix_service_test.dart | 13 ++++++++ lib/blocs/journal/journal_page_cubit.dart | 21 +++++++----- lib/database/database.dart | 33 +++---------------- lib/get_it.dart | 5 ++- lib/services/db_notification.dart | 31 +++++++++-------- pubspec.yaml | 2 +- test/blocs/journal/entry_cubit_test.dart | 7 ++++ .../journal/journal_page_cubit_test.dart | 7 ++++ test/database/database_test.dart | 13 ++++++++ test/logic/persistence_logic_test.dart | 8 +++++ test/mocks/mocks.dart | 3 ++ .../journal/infinite_journal_page_test.dart | 7 ++++ test/themes/themes_service_test.dart | 13 ++++++-- test/themes/utils_test.dart | 13 ++++++-- .../journal/editor/editor_widget_test.dart | 7 ++++ .../entry_datetime_widget_test.dart | 7 ++++ .../entry_detail_footer_test.dart | 7 ++++ .../entry_detail_header_test.dart | 7 ++++ .../share_button_widget_test.dart | 7 ++++ 19 files changed, 149 insertions(+), 62 deletions(-) diff --git a/integration_test/matrix_service_test.dart b/integration_test/matrix_service_test.dart index 1fbe026d9..7594ddf6d 100644 --- a/integration_test/matrix_service_test.dart +++ b/integration_test/matrix_service_test.dart @@ -13,6 +13,7 @@ import 'package:lotti/database/database.dart'; import 'package:lotti/database/logging_db.dart'; import 'package:lotti/database/settings_db.dart'; import 'package:lotti/get_it.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/sync/matrix/matrix_service.dart'; import 'package:lotti/sync/secure_storage.dart'; import 'package:lotti/sync/vector_clock.dart'; @@ -39,6 +40,18 @@ void main() { // create separate databases for each simulated device & suppress warning drift.driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; + + final mockUpdateNotifications = MockUpdateNotifications(); + + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); + + when(() => mockUpdateNotifications.notifyUpdate(DatabaseType.journal)) + .thenAnswer((_) {}); + + getIt.registerSingleton(mockUpdateNotifications); + final aliceDb = JournalDb(overriddenFilename: 'alice_db.sqlite'); final bobDb = JournalDb(overriddenFilename: 'bob_db.sqlite'); diff --git a/lib/blocs/journal/journal_page_cubit.dart b/lib/blocs/journal/journal_page_cubit.dart index 1b3969443..cb629bee2 100644 --- a/lib/blocs/journal/journal_page_cubit.dart +++ b/lib/blocs/journal/journal_page_cubit.dart @@ -12,6 +12,7 @@ import 'package:lotti/database/database.dart'; import 'package:lotti/database/fts5_db.dart'; import 'package:lotti/database/settings_db.dart'; import 'package:lotti/get_it.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/utils/platform.dart'; import 'package:rxdart/rxdart.dart'; import 'package:visibility_detector/visibility_detector.dart'; @@ -101,14 +102,12 @@ class JournalPageCubit extends Cubit { } }); } else { - _db - .watchJournalCount() + _updateNotifications.updateStream .throttleTime( - const Duration(seconds: 2), - leading: false, - trailing: true, - ) - .where(makeDuplicateFilter()) + const Duration(seconds: 1), + leading: false, + trailing: true, + ) .listen((_) { if (_isVisible) { refreshQuery(); @@ -121,6 +120,7 @@ class JournalPageCubit extends Cubit { static const selectedEntryTypesKey = 'SELECTED_ENTRY_TYPES'; final JournalDb _db = getIt(); + final UpdateNotifications _updateNotifications = getIt(); bool _isVisible = false; static const _pageSize = 50; Set _selectedEntryTypes = entryTypes.toSet(); @@ -308,6 +308,7 @@ class JournalPageCubit extends Cubit { .first; final isLastPage = newItems.length < _pageSize; + if (isLastPage) { state.pagingController.appendLastPage(newItems); } else { @@ -315,8 +316,10 @@ class JournalPageCubit extends Cubit { state.pagingController.appendPage(newItems, nextPageKey); } final finished = DateTime.now(); - final duration = finished.difference(start); - debugPrint('_fetchPage $showTasks duration $duration'); + final duration = finished.difference(start).inMicroseconds / 1000; + debugPrint( + '_fetchPage ${showTasks ? 'TASK' : 'JOURNAL'} duration $duration ms', + ); } catch (error) { state.pagingController.error = error; } diff --git a/lib/database/database.dart b/lib/database/database.dart index 2db2dd7fa..8066b0f59 100644 --- a/lib/database/database.dart +++ b/lib/database/database.dart @@ -1,10 +1,8 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; import 'package:enum_to_string/enum_to_string.dart'; import 'package:flutter/foundation.dart'; import 'package:lotti/classes/entity_definitions.dart'; @@ -15,13 +13,10 @@ import 'package:lotti/database/common.dart'; import 'package:lotti/database/conversions.dart'; import 'package:lotti/database/logging_db.dart'; import 'package:lotti/get_it.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/sync/vector_clock.dart'; import 'package:lotti/utils/file_utils.dart'; import 'package:lotti/widgets/journal/entry_tools.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart'; -import 'package:sqlite3/sqlite3.dart'; -import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart'; part 'database.g.dart'; @@ -40,35 +35,14 @@ class JournalDb extends _$JournalDb { this.inMemoryDatabase = false, String? overriddenFilename, }) : super( - _openConnection( + openDbConnection( overriddenFilename ?? journalDbFileName, inMemoryDatabase: inMemoryDatabase, ), ); - static LazyDatabase _openConnection( - String fileName, { - bool inMemoryDatabase = false, - }) { - return LazyDatabase(() async { - if (inMemoryDatabase) { - return NativeDatabase.memory(); - } - - final dbFolder = await getApplicationDocumentsDirectory(); - final file = File(p.join(dbFolder.path, fileName)); - - if (Platform.isAndroid) { - await applyWorkaroundToOpenSqlite3OnOldAndroidVersions(); - } - - sqlite3.tempDirectory = (await getTemporaryDirectory()).path; - - return NativeDatabase.createInBackground(file); - }); - } - bool inMemoryDatabase = false; + final UpdateNotifications _updateNotifications = getIt(); @override int get schemaVersion => 19; @@ -749,6 +723,7 @@ class JournalDb extends _$JournalDb { dashboard: upsertDashboardDefinition, categoryDefinition: upsertCategoryDefinition, ); + _updateNotifications.notifyUpdate(DatabaseType.journal); return linesAffected; } } diff --git a/lib/get_it.dart b/lib/get_it.dart index a0597faf9..2f2bad7f6 100644 --- a/lib/get_it.dart +++ b/lib/get_it.dart @@ -12,6 +12,7 @@ import 'package:lotti/logic/ai/ai_logic.dart'; import 'package:lotti/logic/health_import.dart'; import 'package:lotti/logic/persistence_logic.dart'; import 'package:lotti/services/asr_service.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/editor_state_service.dart'; import 'package:lotti/services/entities_cache_service.dart'; import 'package:lotti/services/link_service.dart'; @@ -29,10 +30,8 @@ Future registerSingletons() async { getIt ..registerSingleton(Fts5Db()) ..registerSingleton(LoggingDb()) + ..registerSingleton(UpdateNotifications()) ..registerSingleton(JournalDb()) - // ..registerSingleton( - // DatabaseUpdateNotifications(), - // ) ..registerSingleton(EditorDb()) ..registerSingleton(TagsService()) ..registerSingleton(EntitiesCacheService()) diff --git a/lib/services/db_notification.dart b/lib/services/db_notification.dart index 0aa099593..1e5bf8ad8 100644 --- a/lib/services/db_notification.dart +++ b/lib/services/db_notification.dart @@ -1,21 +1,20 @@ -import 'package:flutter/foundation.dart'; -import 'package:lotti/database/database.dart'; -import 'package:lotti/get_it.dart'; +import 'dart:async'; -class DatabaseUpdateNotifications { - DatabaseUpdateNotifications() { - listen(); - } +enum DatabaseType { + journal, + setting, + sync, + logging, +} + +class UpdateNotifications { + UpdateNotifications(); + + final _updateStreamController = StreamController.broadcast(); - final JournalDb _journalDb = getIt(); + Stream get updateStream => _updateStreamController.stream; - void listen() { - _journalDb.countJournalEntries().watch().listen((event) async { - final start = DateTime.now(); - final count = await _journalDb.countJournalEntries().getSingle(); - final end = DateTime.now(); - final duration = end.difference(start).inMicroseconds / 1000; - debugPrint('DatabaseUpdateNotifications $count - $duration ms'); - }); + void notifyUpdate(DatabaseType databaseType) { + _updateStreamController.add(databaseType); } } diff --git a/pubspec.yaml b/pubspec.yaml index 33a6ebc54..3e8c4eb34 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: lotti description: Achieve your goals and keep your data private with Lotti. publish_to: 'none' -version: 0.9.459+2496 +version: 0.9.459+2498 msix_config: display_name: LottiApp diff --git a/test/blocs/journal/entry_cubit_test.dart b/test/blocs/journal/entry_cubit_test.dart index 190182e6a..15a50c7d8 100644 --- a/test/blocs/journal/entry_cubit_test.dart +++ b/test/blocs/journal/entry_cubit_test.dart @@ -9,6 +9,7 @@ import 'package:lotti/database/settings_db.dart'; import 'package:lotti/database/sync_db.dart'; import 'package:lotti/get_it.dart'; import 'package:lotti/logic/persistence_logic.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/editor_state_service.dart'; import 'package:lotti/services/time_service.dart'; import 'package:lotti/services/vector_clock_service.dart'; @@ -28,6 +29,12 @@ void main() { var vcMockNext = '1'; setUpAll(() { + final mockUpdateNotifications = MockUpdateNotifications(); + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); + getIt.registerSingleton(mockUpdateNotifications); + final secureStorageMock = MockSecureStorage(); final settingsDb = SettingsDb(inMemoryDatabase: true); final mockTimeService = MockTimeService(); diff --git a/test/blocs/journal/journal_page_cubit_test.dart b/test/blocs/journal/journal_page_cubit_test.dart index 8dd85f6be..4fe7527c6 100644 --- a/test/blocs/journal/journal_page_cubit_test.dart +++ b/test/blocs/journal/journal_page_cubit_test.dart @@ -10,6 +10,7 @@ import 'package:lotti/database/settings_db.dart'; import 'package:lotti/database/sync_db.dart'; import 'package:lotti/get_it.dart'; import 'package:lotti/logic/persistence_logic.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/editor_state_service.dart'; import 'package:lotti/services/time_service.dart'; import 'package:lotti/services/vector_clock_service.dart'; @@ -23,6 +24,7 @@ import '../../test_data/sync_config_test_data.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); + final mockUpdateNotifications = MockUpdateNotifications(); group('JournalPageCubit Tests - ', () { var vcMockNext = '1'; @@ -32,6 +34,10 @@ void main() { final settingsDb = SettingsDb(inMemoryDatabase: true); final mockTimeService = MockTimeService(); + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); + when(() => secureStorageMock.readValue(hostKey)) .thenAnswer((_) async => 'some_host'); @@ -46,6 +52,7 @@ void main() { }); getIt + ..registerSingleton(mockUpdateNotifications) ..registerSingleton(settingsDb) ..registerSingleton(SyncDatabase(inMemoryDatabase: true)) ..registerSingleton(JournalDb(inMemoryDatabase: true)) diff --git a/test/database/database_test.dart b/test/database/database_test.dart index 0a84a47b4..2f57162c7 100644 --- a/test/database/database_test.dart +++ b/test/database/database_test.dart @@ -3,7 +3,12 @@ import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:lotti/database/database.dart'; import 'package:lotti/database/journal_db/config_flags.dart'; +import 'package:lotti/get_it.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/utils/consts.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../mocks/mocks.dart'; final expectedActiveFlagNames = { privateFlag, @@ -63,14 +68,22 @@ final expectedMacFlags = { void main() { JournalDb? db; + final mockUpdateNotifications = MockUpdateNotifications(); group('Database Tests - ', () { setUp(() async { + getIt.registerSingleton(mockUpdateNotifications); + db = JournalDb(inMemoryDatabase: true); await initConfigFlags(db!, inMemoryDatabase: true); + + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); }); tearDown(() async { await db?.close(); + getIt.unregister(); }); test( diff --git a/test/logic/persistence_logic_test.dart b/test/logic/persistence_logic_test.dart index 3a7b6ee32..ddc5cd123 100644 --- a/test/logic/persistence_logic_test.dart +++ b/test/logic/persistence_logic_test.dart @@ -15,6 +15,7 @@ import 'package:lotti/database/sync_db.dart'; import 'package:lotti/get_it.dart'; import 'package:lotti/logic/ai/ai_logic.dart'; import 'package:lotti/logic/persistence_logic.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/notification_service.dart'; import 'package:lotti/services/tags_service.dart'; import 'package:lotti/services/vector_clock_service.dart'; @@ -36,6 +37,7 @@ void main() { registerFallbackValue(FakeJournalEntity()); final mockNotificationService = MockNotificationService(); + final mockUpdateNotifications = MockUpdateNotifications(); final mockAiLogic = MockAiLogic(); final mockFts5Db = MockFts5Db(); @@ -49,6 +51,8 @@ void main() { setUpAll(() async { setFakeDocumentsPath(); + getIt.registerSingleton(mockUpdateNotifications); + final settingsDb = SettingsDb(inMemoryDatabase: true); final journalDb = JournalDb(inMemoryDatabase: true); await initConfigFlags(journalDb, inMemoryDatabase: true); @@ -66,6 +70,10 @@ void main() { when(mockNotificationService.updateBadge).thenAnswer((_) async {}); + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); + when(() => mockFts5Db.insertText(any())).thenAnswer((_) async {}); when( diff --git a/test/mocks/mocks.dart b/test/mocks/mocks.dart index 2a2260a1a..a5a70771f 100644 --- a/test/mocks/mocks.dart +++ b/test/mocks/mocks.dart @@ -19,6 +19,7 @@ import 'package:lotti/logic/ai/ai_logic.dart'; import 'package:lotti/logic/health_import.dart'; import 'package:lotti/logic/persistence_logic.dart'; import 'package:lotti/services/asr_service.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/editor_state_service.dart'; import 'package:lotti/services/entities_cache_service.dart'; import 'package:lotti/services/link_service.dart'; @@ -115,6 +116,8 @@ class MockEditorStateService extends Mock implements EditorStateService {} class MockLinkService extends Mock implements LinkService {} +class MockUpdateNotifications extends Mock implements UpdateNotifications {} + class MockEntryCubit extends MockBloc implements EntryCubit {} diff --git a/test/pages/journal/infinite_journal_page_test.dart b/test/pages/journal/infinite_journal_page_test.dart index 945f4559e..e2b6733f5 100644 --- a/test/pages/journal/infinite_journal_page_test.dart +++ b/test/pages/journal/infinite_journal_page_test.dart @@ -15,6 +15,7 @@ import 'package:lotti/get_it.dart'; import 'package:lotti/logic/persistence_logic.dart'; import 'package:lotti/pages/journal/infinite_journal_page.dart'; import 'package:lotti/services/asr_service.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/entities_cache_service.dart'; import 'package:lotti/services/tags_service.dart'; import 'package:lotti/services/time_service.dart'; @@ -39,6 +40,7 @@ void main() { final mockSettingsDb = MockSettingsDb(); var mockPersistenceLogic = MockPersistenceLogic(); final mockEntitiesCacheService = MockEntitiesCacheService(); + final mockUpdateNotifications = MockUpdateNotifications(); final entryTypeStrings = entryTypes.toList(); @@ -100,6 +102,7 @@ void main() { getIt ..registerSingleton(await getApplicationDocumentsDirectory()) ..registerSingleton(MockLoggingDb()) + ..registerSingleton(mockUpdateNotifications) ..registerSingleton(mockSettingsDb) ..registerSingleton(MockAsrService()) ..registerSingleton(mockTagsService) @@ -115,6 +118,10 @@ void main() { (_) => Stream>.fromIterable([[]]), ); + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); + when(mockJournalDb.watchConfigFlags).thenAnswer( (_) => Stream>.fromIterable([ { diff --git a/test/themes/themes_service_test.dart b/test/themes/themes_service_test.dart index 00d5130f2..f7b0032df 100644 --- a/test/themes/themes_service_test.dart +++ b/test/themes/themes_service_test.dart @@ -1,15 +1,24 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:lotti/database/database.dart'; import 'package:lotti/get_it.dart'; +import 'package:lotti/services/db_notification.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../mocks/mocks.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('ThemesService test -', () { setUpAll(() { - final db = JournalDb(inMemoryDatabase: true); + final mockUpdateNotifications = MockUpdateNotifications(); + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); - getIt.registerSingleton(db); + getIt + ..registerSingleton(mockUpdateNotifications) + ..registerSingleton(JournalDb(inMemoryDatabase: true)); }); tearDownAll(() async { await getIt.reset(); diff --git a/test/themes/utils_test.dart b/test/themes/utils_test.dart index 24139d959..20d1c16a3 100644 --- a/test/themes/utils_test.dart +++ b/test/themes/utils_test.dart @@ -2,8 +2,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:lotti/classes/tag_type_definitions.dart'; import 'package:lotti/database/database.dart'; import 'package:lotti/get_it.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/themes/colors.dart'; import 'package:lotti/themes/utils.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../mocks/mocks.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -11,9 +15,14 @@ void main() { group('Theme Utils test -', () { setUpAll(() { - final db = JournalDb(inMemoryDatabase: true); + final mockUpdateNotifications = MockUpdateNotifications(); + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); - getIt.registerSingleton(db); + getIt + ..registerSingleton(mockUpdateNotifications) + ..registerSingleton(JournalDb(inMemoryDatabase: true)); }); tearDownAll(() async { await getIt.reset(); diff --git a/test/widgets/journal/editor/editor_widget_test.dart b/test/widgets/journal/editor/editor_widget_test.dart index 6f757a70b..462afdc8b 100644 --- a/test/widgets/journal/editor/editor_widget_test.dart +++ b/test/widgets/journal/editor/editor_widget_test.dart @@ -10,6 +10,7 @@ import 'package:lotti/database/editor_db.dart'; import 'package:lotti/database/logging_db.dart'; import 'package:lotti/get_it.dart'; import 'package:lotti/logic/persistence_logic.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/editor_state_service.dart'; import 'package:lotti/services/tags_service.dart'; import 'package:lotti/services/time_service.dart'; @@ -27,7 +28,13 @@ void main() { final mockTimeService = MockTimeService(); setUpAll(() { + final mockUpdateNotifications = MockUpdateNotifications(); + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); + getIt + ..registerSingleton(mockUpdateNotifications) ..registerSingleton(LoggingDb(inMemoryDatabase: true)) ..registerSingleton(MockVectorClockService()) ..registerSingleton(JournalDb(inMemoryDatabase: true)) diff --git a/test/widgets/journal/entry_details/entry_datetime_widget_test.dart b/test/widgets/journal/entry_details/entry_datetime_widget_test.dart index 04100ce88..b0716a03f 100644 --- a/test/widgets/journal/entry_details/entry_datetime_widget_test.dart +++ b/test/widgets/journal/entry_details/entry_datetime_widget_test.dart @@ -4,6 +4,7 @@ import 'package:lotti/blocs/journal/entry_cubit.dart'; import 'package:lotti/blocs/journal/entry_state.dart'; import 'package:lotti/database/database.dart'; import 'package:lotti/get_it.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/tags_service.dart'; import 'package:lotti/widgets/journal/entry_details/entry_datetime_widget.dart'; import 'package:lotti/widgets/journal/entry_tools.dart'; @@ -18,7 +19,13 @@ void main() { final entryCubit = MockEntryCubit(); setUpAll(() { + final mockUpdateNotifications = MockUpdateNotifications(); + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); + getIt + ..registerSingleton(mockUpdateNotifications) ..registerSingleton(JournalDb(inMemoryDatabase: true)) ..registerSingleton(TagsService()); diff --git a/test/widgets/journal/entry_details/entry_detail_footer_test.dart b/test/widgets/journal/entry_details/entry_detail_footer_test.dart index 90d931b36..90fe207a7 100644 --- a/test/widgets/journal/entry_details/entry_detail_footer_test.dart +++ b/test/widgets/journal/entry_details/entry_detail_footer_test.dart @@ -7,6 +7,7 @@ import 'package:lotti/blocs/journal/entry_state.dart'; import 'package:lotti/classes/journal_entities.dart'; import 'package:lotti/database/database.dart'; import 'package:lotti/get_it.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/tags_service.dart'; import 'package:lotti/services/time_service.dart'; import 'package:lotti/widgets/journal/entry_details/entry_detail_footer.dart'; @@ -23,7 +24,13 @@ void main() { final mockTimeService = MockTimeService(); setUpAll(() { + final mockUpdateNotifications = MockUpdateNotifications(); + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); + getIt + ..registerSingleton(mockUpdateNotifications) ..registerSingleton(JournalDb(inMemoryDatabase: true)) ..registerSingleton(TagsService()) ..registerSingleton(mockTimeService); diff --git a/test/widgets/journal/entry_details/entry_detail_header_test.dart b/test/widgets/journal/entry_details/entry_detail_header_test.dart index 4f10e8a5c..17f18ee56 100644 --- a/test/widgets/journal/entry_details/entry_detail_header_test.dart +++ b/test/widgets/journal/entry_details/entry_detail_header_test.dart @@ -5,6 +5,7 @@ import 'package:lotti/blocs/journal/entry_cubit.dart'; import 'package:lotti/blocs/journal/entry_state.dart'; import 'package:lotti/database/database.dart'; import 'package:lotti/get_it.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/link_service.dart'; import 'package:lotti/services/tags_service.dart'; import 'package:lotti/widgets/journal/entry_details/entry_detail_header.dart'; @@ -20,7 +21,13 @@ void main() { final entryCubit = MockEntryCubit(); setUpAll(() { + final mockUpdateNotifications = MockUpdateNotifications(); + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); + getIt + ..registerSingleton(mockUpdateNotifications) ..registerSingleton(JournalDb(inMemoryDatabase: true)) ..registerSingleton(MockLinkService()) ..registerSingleton(TagsService()); diff --git a/test/widgets/journal/entry_details/share_button_widget_test.dart b/test/widgets/journal/entry_details/share_button_widget_test.dart index ea3a62805..d5c0b4f4e 100644 --- a/test/widgets/journal/entry_details/share_button_widget_test.dart +++ b/test/widgets/journal/entry_details/share_button_widget_test.dart @@ -6,6 +6,7 @@ import 'package:lotti/blocs/journal/entry_cubit.dart'; import 'package:lotti/blocs/journal/entry_state.dart'; import 'package:lotti/database/database.dart'; import 'package:lotti/get_it.dart'; +import 'package:lotti/services/db_notification.dart'; import 'package:lotti/services/tags_service.dart'; import 'package:lotti/widgets/journal/entry_details/share_button_widget.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @@ -24,7 +25,13 @@ void main() { setUpAll(() async { setFakeDocumentsPath(); + final mockUpdateNotifications = MockUpdateNotifications(); + when(() => mockUpdateNotifications.updateStream).thenAnswer( + (_) => Stream.fromIterable([]), + ); + getIt + ..registerSingleton(mockUpdateNotifications) ..registerSingleton(await getApplicationDocumentsDirectory()) ..registerSingleton(JournalDb(inMemoryDatabase: true)) ..registerSingleton(TagsService());