diff --git a/flutter/lib/nav_layout.dart b/flutter/lib/components/layout/nav_layout.dart similarity index 100% rename from flutter/lib/nav_layout.dart rename to flutter/lib/components/layout/nav_layout.dart diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 8297ef3..ecf3455 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -2,31 +2,19 @@ import 'dart:io'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:provider/provider.dart'; -import 'package:hive_flutter/hive_flutter.dart'; -import 'package:simplecloudnotifier/api/api_client.dart'; -import 'package:simplecloudnotifier/models/channel.dart'; -import 'package:simplecloudnotifier/models/client.dart'; -import 'package:simplecloudnotifier/models/keytoken.dart'; -import 'package:simplecloudnotifier/models/scn_message.dart'; -import 'package:simplecloudnotifier/nav_layout.dart'; -import 'package:simplecloudnotifier/pages/channel_view/channel_view.dart'; -import 'package:simplecloudnotifier/pages/message_view/message_view.dart'; +import 'package:simplecloudnotifier/main_messaging.dart'; +import 'package:simplecloudnotifier/main_utils.dart'; +import 'package:simplecloudnotifier/components/layout/nav_layout.dart'; import 'package:simplecloudnotifier/state/app_settings.dart'; import 'package:simplecloudnotifier/state/app_bar_state.dart'; -import 'package:simplecloudnotifier/state/app_events.dart'; import 'package:simplecloudnotifier/state/app_theme.dart'; import 'package:simplecloudnotifier/state/application_log.dart'; -import 'package:simplecloudnotifier/state/fb_message.dart'; import 'package:simplecloudnotifier/state/globals.dart'; -import 'package:simplecloudnotifier/state/request_log.dart'; import 'package:simplecloudnotifier/state/app_auth.dart'; import 'package:firebase_core/firebase_core.dart'; -import 'package:simplecloudnotifier/state/scn_data_cache.dart'; import 'package:simplecloudnotifier/utils/navi.dart'; -import 'package:simplecloudnotifier/utils/notifier.dart'; import 'package:toastification/toastification.dart'; import 'firebase_options.dart'; @@ -38,86 +26,16 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); print('[INIT] Init Globals...'); - await Globals().init(); print('[INIT] Init Hive...'); + await initHive(); - await Hive.initFlutter(); + print('[INIT] Register Hive-Adapters'); + await registerHiveAdapter(); - Hive.registerAdapter(SCNRequestAdapter()); - Hive.registerAdapter(SCNLogAdapter()); - Hive.registerAdapter(SCNLogLevelAdapter()); - Hive.registerAdapter(SCNMessageAdapter()); - Hive.registerAdapter(ChannelAdapter()); - Hive.registerAdapter(FBMessageAdapter()); - Hive.registerAdapter(KeyTokenAdapter()); - - print('[INIT] Load Hive...'); - - try { - await Hive.openBox('scn-logs'); - } catch (exc, trace) { - Hive.deleteBoxFromDisk('scn-logs'); - await Hive.openBox('scn-logs'); - ApplicationLog.error('Failed to open Hive-Box: scn-logs: ' + exc.toString(), trace: trace); - ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-logs', {'error': exc.toString(), 'trace': trace}); - } - - print('[INIT] Load Hive...'); - - try { - await Hive.openBox('scn-requests'); - } catch (exc, trace) { - Hive.deleteBoxFromDisk('scn-requests'); - await Hive.openBox('scn-requests'); - ApplicationLog.error('Failed to open Hive-Box: scn-requests: ' + exc.toString(), trace: trace); - ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-requests', {'error': exc.toString(), 'trace': trace}); - } - - print('[INIT] Load Hive...'); - - try { - await Hive.openBox('scn-message-cache'); - } catch (exc, trace) { - Hive.deleteBoxFromDisk('scn-message-cache'); - await Hive.openBox('scn-message-cache'); - ApplicationLog.error('Failed to open Hive-Box: scn-message-cache' + exc.toString(), trace: trace); - ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-message-cache', {'error': exc.toString(), 'trace': trace}); - } - - print('[INIT] Load Hive...'); - - try { - await Hive.openBox('scn-channel-cache'); - } catch (exc, trace) { - Hive.deleteBoxFromDisk('scn-channel-cache'); - await Hive.openBox('scn-channel-cache'); - ApplicationLog.error('Failed to open Hive-Box: scn-channel-cache' + exc.toString(), trace: trace); - ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-channel-cache', {'error': exc.toString(), 'trace': trace}); - } - - print('[INIT] Load Hive...'); - - try { - await Hive.openBox('scn-fb-messages'); - } catch (exc, trace) { - Hive.deleteBoxFromDisk('scn-fb-messages'); - await Hive.openBox('scn-fb-messages'); - ApplicationLog.error('Failed to open Hive-Box: scn-fb-messages' + exc.toString(), trace: trace); - ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-fb-messages', {'error': exc.toString(), 'trace': trace}); - } - - print('[INIT] Load Hive...'); - - try { - await Hive.openBox('scn-keytoken-value-cache'); - } catch (exc, trace) { - Hive.deleteBoxFromDisk('scn-keytoken-value-cache'); - await Hive.openBox('scn-keytoken-value-cache'); - ApplicationLog.error('Failed to open Hive-Box: scn-keytoken-value-cache' + exc.toString(), trace: trace); - ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-keytoken-value-cache', {'error': exc.toString(), 'trace': trace}); - } + print('[INIT] Open Hive-Boxes'); + await openHiveBoxes(true); print('[INIT] Load AppAuth...'); @@ -168,8 +86,8 @@ void main() async { ApplicationLog.error('Failed to get+set firebase token: ' + exc.toString(), trace: trace); } - FirebaseMessaging.onBackgroundMessage(_onBackgroundMessage); - FirebaseMessaging.onMessage.listen(_onForegroundMessage); + FirebaseMessaging.onBackgroundMessage(onBackgroundMessage); + FirebaseMessaging.onMessage.listen(onForegroundMessage); } else { print('[INIT] Skip Firebase init (Platform == Linux)...'); } @@ -188,7 +106,7 @@ void main() async { requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true, - onDidReceiveLocalNotification: _receiveLocalDarwinNotification, + onDidReceiveLocalNotification: receiveLocalDarwinNotification, notificationCategories: getDarwinNotificationCategories(), ); final initializationSettingsLinux = LinuxInitializationSettings(defaultActionName: 'Open notification'); @@ -199,8 +117,8 @@ void main() async { ); flutterLocalNotificationsPlugin.initialize( initializationSettings, - onDidReceiveNotificationResponse: _receiveLocalNotification, - onDidReceiveBackgroundNotificationResponse: _notificationTapBackground, + onDidReceiveNotificationResponse: receiveLocalNotification, + onDidReceiveBackgroundNotificationResponse: notificationTapBackground, ); final appLaunchNotification = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); @@ -210,7 +128,7 @@ void main() async { //TODO same on iOS, somehow?? ApplicationLog.info('App launched by notification: ${appLaunchNotification.notificationResponse?.id}'); - _handleNotificationClickAction(appLaunchNotification.notificationResponse?.payload, Duration(milliseconds: 600)); + handleNotificationClickAction(appLaunchNotification.notificationResponse?.payload, Duration(milliseconds: 600)); } } @@ -262,177 +180,3 @@ class SCNApp extends StatelessWidget { ); } } - -@pragma('vm:entry-point') -void _notificationTapBackground(NotificationResponse notificationResponse) { - // I think only iOS triggers this, TODO - ApplicationLog.info('Received local notification: ${notificationResponse.id}'); -} - -void setFirebaseToken(String fcmToken) async { - final acc = AppAuth(); - - final oldToken = Globals().getPrefFCMToken(); - - await Globals().setPrefFCMToken(fcmToken); - - ApplicationLog.info('New firebase token received', additional: 'Token: $fcmToken (old: $oldToken)'); - - if (!acc.isAuth()) return; - - Client? client; - try { - client = await acc.loadClient(forceIfOlder: Duration(seconds: 60)); - } catch (exc, trace) { - ApplicationLog.error('Failed to get client: ' + exc.toString(), trace: trace); - return; - } - - if (oldToken != null && oldToken == fcmToken && client != null && client.fcmToken == fcmToken) { - ApplicationLog.info('Firebase token unchanged - do nothing', additional: 'Token: $fcmToken'); - return; - } - - if (client == null) { - // should not really happen - perhaps someone externally deleted the client? - final newClient = await APIClient.addClient(acc, fcmToken, Globals().deviceModel, Globals().version, Globals().hostname, Globals().clientType); - acc.setClientAndClientID(newClient); - await acc.save(); - } else { - final newClient = await APIClient.updateClient(acc, client.clientID, fcmToken: fcmToken, agentModel: Globals().deviceModel, name: Globals().hostname, agentVersion: Globals().version); - acc.setClientAndClientID(newClient); - await acc.save(); - } -} - -@pragma('vm:entry-point') -Future _onBackgroundMessage(RemoteMessage message) async { - // a firebase message was received while the app was in the background or terminated - await _receiveMessage(message, false); -} - -@pragma('vm:entry-point') -void _onForegroundMessage(RemoteMessage message) { - // a firebase message was received while the app was in the foreground - _receiveMessage(message, true); -} - -Future _receiveMessage(RemoteMessage message, bool foreground) async { - try { - // ensure globals init - if (!Globals().isInitialized) { - print('[LATE-INIT] Init Globals() - to ensure working _receiveMessage($foreground)...'); - await Globals().init(); - } - - // ensure hive init - if (!Hive.isBoxOpen('scn-logs')) { - print('[LATE-INIT] Init Hive - to ensure working _receiveMessage($foreground)...'); - - await Hive.initFlutter(); - Hive.registerAdapter(SCNRequestAdapter()); - Hive.registerAdapter(SCNLogAdapter()); - Hive.registerAdapter(SCNLogLevelAdapter()); - Hive.registerAdapter(SCNMessageAdapter()); - Hive.registerAdapter(ChannelAdapter()); - Hive.registerAdapter(FBMessageAdapter()); - } - - print('[LATE-INIT] Ensure hive boxes are open for _receiveMessage($foreground)...'); - - await Hive.openBox('scn-logs'); - await Hive.openBox('scn-fb-messages'); - await Hive.openBox('scn-message-cache'); - await Hive.openBox('scn-requests'); - } catch (exc, trace) { - ApplicationLog.writeRawFailure('Failed to init hive', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground}); - ApplicationLog.error('Failed to init hive:' + exc.toString(), trace: trace); - Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (init failed)", null, null); - return; - } - - ApplicationLog.info('Received FB message (${foreground ? 'foreground' : 'background'}): ${message.messageId ?? 'NULL'}'); - - String scn_msg_id; - - try { - scn_msg_id = message.data['scn_msg_id'] as String; - - final timestamp = DateTime.fromMillisecondsSinceEpoch(int.parse(message.data['timestamp'] as String) * 1000); - final title = message.data['title'] as String; - final channel = message.data['channel'] as String; - final channel_id = message.data['channel_id'] as String; - final body = message.data['body'] as String; - final prio = int.parse(message.data['priority'] as String); - - Notifier.showLocalNotification(scn_msg_id, channel_id, channel, 'Channel: ${channel}', title, body, timestamp, prio); - } catch (exc, trace) { - ApplicationLog.writeRawFailure('Failed to decode received FB message', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground}); - ApplicationLog.error('Failed to decode received FB message' + exc.toString(), trace: trace); - Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (decode failed)", null, null); - return; - } - - try { - FBMessageLog.insert(message); - } catch (exc, trace) { - ApplicationLog.writeRawFailure('Failed to persist received FB message', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground}); - ApplicationLog.error('Failed to persist received FB message' + exc.toString(), trace: trace); - Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (persist failed)", null, null); - return; - } - - try { - final msg = await APIClient.getMessage(AppAuth(), scn_msg_id); - SCNDataCache().addToMessageCache([msg]); - if (foreground) AppEvents().notifyMessageReceivedListeners(msg); - } catch (exc, trace) { - ApplicationLog.writeRawFailure('Failed to query+persist message', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground}); - ApplicationLog.error('Failed to query+persist message: ' + exc.toString(), trace: trace); - return; - } -} - -void _receiveLocalDarwinNotification(int id, String? title, String? body, String? payload) { - //TODO iOS? - ApplicationLog.info('Received local notification: $id -> [$title]'); -} - -void _receiveLocalNotification(NotificationResponse details) { - // User has tapped a flutter_local notification, while the app was running - ApplicationLog.info('Tapped local notification: [[${details.id} | ${details.actionId} | ${details.input} | ${details.notificationResponseType} | ${details.payload}]]'); - - _handleNotificationClickAction(details.payload, Duration.zero); -} - -void _handleNotificationClickAction(String? payload, Duration delay) { - final parts = payload?.split('\n') ?? []; - - if (parts.length == 4 && parts[0] == '@SCN_MESSAGE') { - final messageID = parts[1]; - () async { - await Future.delayed(delay, () {}); - - SchedulerBinding.instance.addPostFrameCallback((_) { - ApplicationLog.info('Handle notification action @SCN_MESSAGE --> ${messageID}'); - Navi.push(SCNApp.materialKey.currentContext!, () => MessageViewPage(messageID: messageID, preloadedData: null)); - }); - }(); - } else if (parts.length == 3 && parts[0] == '@SCN_MESSAGE_SUMMARY') { - final channelID = parts[1]; - () async { - await Future.delayed(delay, () {}); - - SchedulerBinding.instance.addPostFrameCallback((_) { - ApplicationLog.info('Handle notification action @SCN_MESSAGE_SUMMARY --> ${channelID}'); - Navi.push(SCNApp.materialKey.currentContext!, () => ChannelViewPage(channelID: channelID, preloadedData: null, needsReload: null)); - }); - }(); - } -} - -List getDarwinNotificationCategories() { - return [ - //TODO ?!? - ]; -} diff --git a/flutter/lib/main_messaging.dart b/flutter/lib/main_messaging.dart new file mode 100644 index 0000000..a002a15 --- /dev/null +++ b/flutter/lib/main_messaging.dart @@ -0,0 +1,152 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:simplecloudnotifier/api/api_client.dart'; +import 'package:simplecloudnotifier/main.dart'; +import 'package:simplecloudnotifier/main_utils.dart'; +import 'package:simplecloudnotifier/pages/channel_view/channel_view.dart'; +import 'package:simplecloudnotifier/pages/message_view/message_view.dart'; +import 'package:simplecloudnotifier/state/app_events.dart'; +import 'package:simplecloudnotifier/state/application_log.dart'; +import 'package:simplecloudnotifier/state/fb_message.dart'; +import 'package:simplecloudnotifier/state/globals.dart'; +import 'package:simplecloudnotifier/state/app_auth.dart'; +import 'package:simplecloudnotifier/state/scn_data_cache.dart'; +import 'package:simplecloudnotifier/utils/navi.dart'; +import 'package:simplecloudnotifier/utils/notifier.dart'; + +@pragma('vm:entry-point') +void notificationTapBackground(NotificationResponse notificationResponse) { + // I think only iOS triggers this, TODO + ApplicationLog.info('Received local notification: ${notificationResponse.id}'); +} + +@pragma('vm:entry-point') +Future onBackgroundMessage(RemoteMessage message) async { + // a firebase message was received while the app was in the background or terminated + await _receiveMessage(message, false); +} + +@pragma('vm:entry-point') +void onForegroundMessage(RemoteMessage message) { + // a firebase message was received while the app was in the foreground + _receiveMessage(message, true); +} + +Future _receiveMessage(RemoteMessage message, bool foreground) async { + try { + // ensure globals init + if (!Globals().isInitialized) { + print('[LATE-INIT] Init Globals() - to ensure working _receiveMessage($foreground)...'); + await Globals().init(); + } + + // ensure hive init + if (!Globals().hiveInitialized) { + print('[LATE-INIT] Init Hive - to ensure working _receiveMessage($foreground)...'); + await initHive(); + } + + // ensure hive init + if (!Globals().hiveAdaptersRegistered) { + print('[LATE-INIT] Init Hive-Adapter - to ensure working _receiveMessage($foreground)...'); + await registerHiveAdapter(); + } + + // ensure hive init + if (!Globals().hiveBoxesOpened) { + print('[LATE-INIT] Ensure hive boxes are open for _receiveMessage($foreground)...'); + await openHiveBoxes(false); + } + } catch (exc, trace) { + ApplicationLog.writeRawFailure('Failed to init hive', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground}); + ApplicationLog.error('Failed to init hive:' + exc.toString(), trace: trace); + Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (init failed)", null, null); + return; + } + + ApplicationLog.info('Received FB message (${foreground ? 'foreground' : 'background'}): ${message.messageId ?? 'NULL'}'); + + String scn_msg_id; + + try { + scn_msg_id = message.data['scn_msg_id'] as String; + + final timestamp = DateTime.fromMillisecondsSinceEpoch(int.parse(message.data['timestamp'] as String) * 1000); + final title = message.data['title'] as String; + final channel = message.data['channel'] as String; + final channel_id = message.data['channel_id'] as String; + final body = message.data['body'] as String; + final prio = int.parse(message.data['priority'] as String); + + Notifier.showLocalNotification(scn_msg_id, channel_id, channel, 'Channel: ${channel}', title, body, timestamp, prio); + } catch (exc, trace) { + ApplicationLog.writeRawFailure('Failed to decode received FB message', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground}); + ApplicationLog.error('Failed to decode received FB message' + exc.toString(), trace: trace); + Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (decode failed)", null, null); + return; + } + + try { + FBMessageLog.insert(message); + } catch (exc, trace) { + ApplicationLog.writeRawFailure('Failed to persist received FB message', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground}); + ApplicationLog.error('Failed to persist received FB message' + exc.toString(), trace: trace); + Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (persist failed)", null, null); + return; + } + + try { + final msg = await APIClient.getMessage(AppAuth(), scn_msg_id); + SCNDataCache().addToMessageCache([msg]); + if (foreground) AppEvents().notifyMessageReceivedListeners(msg); + } catch (exc, trace) { + ApplicationLog.writeRawFailure('Failed to query+persist message', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground}); + ApplicationLog.error('Failed to query+persist message: ' + exc.toString(), trace: trace); + return; + } +} + +void receiveLocalDarwinNotification(int id, String? title, String? body, String? payload) { + //TODO iOS? + ApplicationLog.info('Received local notification: $id -> [$title]'); +} + +void receiveLocalNotification(NotificationResponse details) { + // User has tapped a flutter_local notification, while the app was running + ApplicationLog.info('Tapped local notification: [[${details.id} | ${details.actionId} | ${details.input} | ${details.notificationResponseType} | ${details.payload}]]'); + + handleNotificationClickAction(details.payload, Duration.zero); +} + +void handleNotificationClickAction(String? payload, Duration delay) { + final parts = payload?.split('\n') ?? []; + + if (parts.length == 4 && parts[0] == '@SCN_MESSAGE') { + final messageID = parts[1]; + () async { + await Future.delayed(delay, () {}); + + SchedulerBinding.instance.addPostFrameCallback((_) { + ApplicationLog.info('Handle notification action @SCN_MESSAGE --> ${messageID}'); + Navi.push(SCNApp.materialKey.currentContext!, () => MessageViewPage(messageID: messageID, preloadedData: null)); + }); + }(); + } else if (parts.length == 3 && parts[0] == '@SCN_MESSAGE_SUMMARY') { + final channelID = parts[1]; + () async { + await Future.delayed(delay, () {}); + + SchedulerBinding.instance.addPostFrameCallback((_) { + ApplicationLog.info('Handle notification action @SCN_MESSAGE_SUMMARY --> ${channelID}'); + Navi.push(SCNApp.materialKey.currentContext!, () => ChannelViewPage(channelID: channelID, preloadedData: null, needsReload: null)); + }); + }(); + } +} + +List getDarwinNotificationCategories() { + return [ + //TODO ?!? + ]; +} diff --git a/flutter/lib/main_utils.dart b/flutter/lib/main_utils.dart new file mode 100644 index 0000000..72a3892 --- /dev/null +++ b/flutter/lib/main_utils.dart @@ -0,0 +1,155 @@ + +import 'package:mutex/mutex.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:simplecloudnotifier/api/api_client.dart'; +import 'package:simplecloudnotifier/models/channel.dart'; +import 'package:simplecloudnotifier/models/client.dart'; +import 'package:simplecloudnotifier/models/keytoken.dart'; +import 'package:simplecloudnotifier/models/scn_message.dart'; +import 'package:simplecloudnotifier/state/application_log.dart'; +import 'package:simplecloudnotifier/state/fb_message.dart'; +import 'package:simplecloudnotifier/state/globals.dart'; +import 'package:simplecloudnotifier/state/request_log.dart'; +import 'package:simplecloudnotifier/state/app_auth.dart'; + +final lockHive = Mutex(); + +void setFirebaseToken(String fcmToken) async { + final acc = AppAuth(); + + final oldToken = Globals().getPrefFCMToken(); + + await Globals().setPrefFCMToken(fcmToken); + + ApplicationLog.info('New firebase token received', additional: 'Token: $fcmToken (old: $oldToken)'); + + if (!acc.isAuth()) return; + + Client? client; + try { + client = await acc.loadClient(forceIfOlder: Duration(seconds: 60)); + } catch (exc, trace) { + ApplicationLog.error('Failed to get client: ' + exc.toString(), trace: trace); + return; + } + + if (oldToken != null && oldToken == fcmToken && client != null && client.fcmToken == fcmToken) { + ApplicationLog.info('Firebase token unchanged - do nothing', additional: 'Token: $fcmToken'); + return; + } + + if (client == null) { + // should not really happen - perhaps someone externally deleted the client? + final newClient = await APIClient.addClient(acc, fcmToken, Globals().deviceModel, Globals().version, Globals().hostname, Globals().clientType); + acc.setClientAndClientID(newClient); + await acc.save(); + } else { + final newClient = await APIClient.updateClient(acc, client.clientID, fcmToken: fcmToken, agentModel: Globals().deviceModel, name: Globals().hostname, agentVersion: Globals().version); + acc.setClientAndClientID(newClient); + await acc.save(); + } +} + +Future initHive() async { + await lockHive.protect(() async { + if (Globals().hiveAdaptersRegistered) return; + + await Hive.initFlutter(); + Globals().hiveInitialized = true; + }); +} + +Future openHiveBoxes(bool safe) async { + await lockHive.protect(() async { + if (Globals().hiveBoxesOpened) return; + + if (!safe) { + await Hive.openBox('scn-logs'); + await Hive.openBox('scn-requests'); + await Hive.openBox('scn-message-cache'); + await Hive.openBox('scn-channel-cache'); + await Hive.openBox('scn-fb-messages'); + await Hive.openBox('scn-keytoken-value-cache'); + Globals().hiveBoxesOpened = true; + return; + } + + try { + print('[INIT] Load Hive...'); + await Hive.openBox('scn-logs'); + } catch (exc, trace) { + Hive.deleteBoxFromDisk('scn-logs'); + await Hive.openBox('scn-logs'); + ApplicationLog.error('Failed to open Hive-Box: scn-logs: ' + exc.toString(), trace: trace); + ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-logs', {'error': exc.toString(), 'trace': trace}); + } + + try { + print('[INIT] Load Hive...'); + await Hive.openBox('scn-requests'); + } catch (exc, trace) { + Hive.deleteBoxFromDisk('scn-requests'); + await Hive.openBox('scn-requests'); + ApplicationLog.error('Failed to open Hive-Box: scn-requests: ' + exc.toString(), trace: trace); + ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-requests', {'error': exc.toString(), 'trace': trace}); + } + + try { + print('[INIT] Load Hive...'); + await Hive.openBox('scn-message-cache'); + } catch (exc, trace) { + Hive.deleteBoxFromDisk('scn-message-cache'); + await Hive.openBox('scn-message-cache'); + ApplicationLog.error('Failed to open Hive-Box: scn-message-cache' + exc.toString(), trace: trace); + ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-message-cache', {'error': exc.toString(), 'trace': trace}); + } + + try { + print('[INIT] Load Hive...'); + await Hive.openBox('scn-channel-cache'); + } catch (exc, trace) { + Hive.deleteBoxFromDisk('scn-channel-cache'); + await Hive.openBox('scn-channel-cache'); + ApplicationLog.error('Failed to open Hive-Box: scn-channel-cache' + exc.toString(), trace: trace); + ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-channel-cache', {'error': exc.toString(), 'trace': trace}); + } + + try { + print('[INIT] Load Hive...'); + await Hive.openBox('scn-fb-messages'); + } catch (exc, trace) { + Hive.deleteBoxFromDisk('scn-fb-messages'); + await Hive.openBox('scn-fb-messages'); + ApplicationLog.error('Failed to open Hive-Box: scn-fb-messages' + exc.toString(), trace: trace); + ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-fb-messages', {'error': exc.toString(), 'trace': trace}); + } + + try { + print('[INIT] Load Hive...'); + await Hive.openBox('scn-keytoken-value-cache'); + } catch (exc, trace) { + Hive.deleteBoxFromDisk('scn-keytoken-value-cache'); + await Hive.openBox('scn-keytoken-value-cache'); + ApplicationLog.error('Failed to open Hive-Box: scn-keytoken-value-cache' + exc.toString(), trace: trace); + ApplicationLog.writeRawFailure('Failed to open Hive-Box: scn-keytoken-value-cache', {'error': exc.toString(), 'trace': trace}); + } + + Globals().hiveBoxesOpened = true; + }); +} + +Future registerHiveAdapter() async { + await lockHive.protect(() async { + if (Globals().hiveAdaptersRegistered) return; + + Hive.registerAdapter(SCNRequestAdapter()); + Hive.registerAdapter(SCNLogAdapter()); + Hive.registerAdapter(SCNLogLevelAdapter()); + Hive.registerAdapter(SCNMessageAdapter()); + Hive.registerAdapter(ChannelAdapter()); + Hive.registerAdapter(FBMessageAdapter()); + Hive.registerAdapter(KeyTokenAdapter()); + + Globals().hiveAdaptersRegistered = true; + }); +} diff --git a/flutter/lib/state/globals.dart b/flutter/lib/state/globals.dart index d8c14fe..9dbf446 100644 --- a/flutter/lib/state/globals.dart +++ b/flutter/lib/state/globals.dart @@ -18,6 +18,9 @@ class Globals { bool _initialized = false; bool appWidgetInitialized = false; + bool hiveInitialized = false; + bool hiveAdaptersRegistered = false; + bool hiveBoxesOpened = false; String appName = ''; String packageName = ''; diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 7774411..869d595 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -668,6 +668,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.7" + mutex: + dependency: "direct main" + description: + name: mutex + sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2" + url: "https://pub.dev" + source: hosted + version: "3.1.0" nested: dependency: transitive description: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 5f9369e..3ddb84f 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: settings_ui: ^2.0.2 git_stamp: ^5.10.0 action_slider: ^0.7.0 + mutex: ^3.1.0 dependency_overrides: font_awesome_flutter: path: deps/font_awesome_flutter