From 5204984f9cc2220f404ac378d29d1897a262a7f1 Mon Sep 17 00:00:00 2001 From: otsmr Date: Sun, 15 Mar 2026 19:59:29 +0100 Subject: [PATCH] fix issue with workmanager --- CHANGELOG.md | 2 +- .../callback_dispatcher.background.dart | 57 ++++++++++++------- lib/src/utils/exclusive_access.dart | 51 +++++++++++++++++ lib/src/utils/log.dart | 46 ++------------- pubspec.yaml | 2 +- 5 files changed, 94 insertions(+), 64 deletions(-) create mode 100644 lib/src/utils/exclusive_access.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 458a935..662859a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.0.99 +## 0.1.0 - New: Groups can now collect flames as well - New: Background execution to pre-load messages diff --git a/lib/src/services/background/callback_dispatcher.background.dart b/lib/src/services/background/callback_dispatcher.background.dart index 07297b2..15838fa 100644 --- a/lib/src/services/background/callback_dispatcher.background.dart +++ b/lib/src/services/background/callback_dispatcher.background.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:mutex/mutex.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:twonly/globals.dart'; @@ -6,6 +7,7 @@ import 'package:twonly/src/constants/keyvalue.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/api.service.dart'; import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; +import 'package:twonly/src/utils/exclusive_access.dart'; import 'package:twonly/src/utils/keyvalue.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/storage.dart'; @@ -14,12 +16,14 @@ import 'package:workmanager/workmanager.dart'; // ignore: unreachable_from_main Future initializeBackgroundTaskManager() async { await Workmanager().initialize(callbackDispatcher); + await Workmanager().cancelByUniqueName('fetch_data_from_server'); await Workmanager().registerPeriodicTask( 'fetch_data_from_server', 'eu.twonly.periodic_task', frequency: const Duration(minutes: 20), initialDelay: const Duration(minutes: 5), + existingWorkPolicy: ExistingPeriodicWorkPolicy.update, constraints: Constraints( networkType: NetworkType.connected, ), @@ -64,26 +68,35 @@ Future initBackgroundExecution() async { return true; } -Future handlePeriodicTask() async { - final lastExecution = - await KeyValueStore.get(KeyValueKeys.lastPeriodicTaskExecution); - if (lastExecution == null || !lastExecution.containsKey('timestamp')) { - final lastExecutionTime = lastExecution?['timestamp'] as int?; - if (lastExecutionTime != null) { - final lastExecution = - DateTime.fromMillisecondsSinceEpoch(lastExecutionTime); - if (DateTime.now().difference(lastExecution).inMinutes < 2) { - Log.info( - 'eu.twonly.periodic_task not executed as last execution was within the last two minutes.', - ); - return true; - } - } - } +final Mutex _keyValueMutex = Mutex(); - await KeyValueStore.put(KeyValueKeys.lastPeriodicTaskExecution, { - 'timestamp': DateTime.now().millisecondsSinceEpoch, - }); +Future handlePeriodicTask() async { + final shouldBeExecuted = await exclusiveAccess( + lockName: 'periodic_task', + mutex: _keyValueMutex, + action: () async { + final lastExecution = await KeyValueStore.get( + KeyValueKeys.lastPeriodicTaskExecution, + ); + if (lastExecution != null && lastExecution.containsKey('timestamp')) { + final lastExecutionTime = lastExecution['timestamp'] as int?; + if (lastExecutionTime != null) { + final lastExecutionDate = DateTime.fromMillisecondsSinceEpoch( + lastExecutionTime, + ); + if (DateTime.now().difference(lastExecutionDate).inMinutes < 2) { + return false; + } + } + } + await KeyValueStore.put(KeyValueKeys.lastPeriodicTaskExecution, { + 'timestamp': DateTime.now().millisecondsSinceEpoch, + }); + return false; + }, + ); + + if (!shouldBeExecuted) return; Log.info('eu.twonly.periodic_task was called.'); @@ -91,12 +104,12 @@ Future handlePeriodicTask() async { if (!await apiService.connect()) { Log.info('Could not connect to the api. Returning early.'); - return false; + return; } if (!apiService.isAuthenticated) { Log.info('Api is not authenticated. Returning early.'); - return false; + return; } while (!globalGotMessageFromServer) { @@ -119,7 +132,7 @@ Future handlePeriodicTask() async { stopwatch.stop(); Log.info('eu.twonly.periodic_task finished after ${stopwatch.elapsed}.'); - return true; + return; } Future handleProcessingTask() async { diff --git a/lib/src/utils/exclusive_access.dart b/lib/src/utils/exclusive_access.dart new file mode 100644 index 0000000..d2a4253 --- /dev/null +++ b/lib/src/utils/exclusive_access.dart @@ -0,0 +1,51 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:mutex/mutex.dart'; +import 'package:twonly/globals.dart'; + +Future exclusiveAccess({ + required String lockName, + required Future Function() action, + required Mutex mutex, +}) async { + final lockFile = File('$globalApplicationSupportDirectory/$lockName.lock'); + return mutex.protect(() async { + var lockAcquired = false; + + while (!lockAcquired) { + try { + lockFile.createSync(exclusive: true); + lockAcquired = true; + } on FileSystemException catch (e) { + final isExists = e is PathExistsException || e.osError?.errorCode == 17; + if (!isExists) { + break; + } + try { + final stat = lockFile.statSync(); + if (stat.type != FileSystemEntityType.notFound) { + final age = DateTime.now().difference(stat.modified).inMilliseconds; + if (age > 1000) { + lockFile.deleteSync(); + continue; + } + } + } catch (_) {} + await Future.delayed(const Duration(milliseconds: 50)); + } catch (_) { + break; + } + } + try { + return await action(); + } finally { + if (lockAcquired) { + try { + if (lockFile.existsSync()) { + lockFile.deleteSync(); + } + } catch (_) {} + } + } + }); +} diff --git a/lib/src/utils/log.dart b/lib/src/utils/log.dart index fcba8bb..84b5009 100644 --- a/lib/src/utils/log.dart +++ b/lib/src/utils/log.dart @@ -6,6 +6,7 @@ import 'package:logging/logging.dart'; import 'package:mutex/mutex.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/utils/exclusive_access.dart'; void initLogger() { // Logger.root.level = kReleaseMode ? Level.INFO : Level.ALL; @@ -91,46 +92,11 @@ Future readLast1000Lines() async { final Mutex _logMutex = Mutex(); Future _protectFileAccess(Future Function() action) async { - return _logMutex.protect(() async { - final lockFile = File('$globalApplicationSupportDirectory/app.log.lock'); - var lockAcquired = false; - - while (!lockAcquired) { - try { - lockFile.createSync(exclusive: true); - lockAcquired = true; - } on FileSystemException catch (e) { - final isExists = e is PathExistsException || e.osError?.errorCode == 17; - if (!isExists) { - break; - } - try { - final stat = lockFile.statSync(); - if (stat.type != FileSystemEntityType.notFound) { - final age = DateTime.now().difference(stat.modified).inMilliseconds; - if (age > 1000) { - lockFile.deleteSync(); - continue; - } - } - } catch (_) {} - await Future.delayed(const Duration(milliseconds: 50)); - } catch (_) { - break; - } - } - try { - return await action(); - } finally { - if (lockAcquired) { - try { - if (lockFile.existsSync()) { - lockFile.deleteSync(); - } - } catch (_) {} - } - } - }); + return exclusiveAccess( + lockName: 'app.log', + action: action, + mutex: _logMutex, + ); } Future _writeLogToFile(LogRecord record) async { diff --git a/pubspec.yaml b/pubspec.yaml index bddd5a9..7b07725 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec publish_to: 'none' -version: 0.0.99+99 +version: 0.1.0+100 environment: sdk: ^3.11.0