From 3c944378ae84f97f3708018cd7c7a428238ec85b Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Fri, 28 Apr 2023 14:23:01 -0400 Subject: [PATCH] Add initial auto-update timers wired into updating all contacts first --- lib/di_initialization.dart | 41 ++--- lib/services/auth_service.dart | 9 ++ lib/services/connections_manager.dart | 186 +++++++++++----------- lib/services/persistent_info_service.dart | 71 +++++++++ lib/update_timer_initialization.dart | 46 ++++++ 5 files changed, 243 insertions(+), 110 deletions(-) create mode 100644 lib/services/persistent_info_service.dart create mode 100644 lib/update_timer_initialization.dart diff --git a/lib/di_initialization.dart b/lib/di_initialization.dart index bbd6b1f..a0c4ed9 100644 --- a/lib/di_initialization.dart +++ b/lib/di_initialization.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:logging/logging.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; import 'data/interfaces/connections_repo_intf.dart'; import 'data/interfaces/groups_repo.intf.dart'; @@ -26,14 +28,26 @@ import 'services/interactions_manager.dart'; import 'services/media_upload_attachment_helper.dart'; import 'services/network_status_service.dart'; import 'services/notifications_manager.dart'; +import 'services/persistent_info_service.dart'; import 'services/secrets_service.dart'; import 'services/setting_service.dart'; import 'services/timeline_manager.dart'; +import 'update_timer_initialization.dart'; import 'utils/active_profile_selector.dart'; final _logger = Logger('DI_Init'); Future dependencyInjectionInitialization() async { + final appSupportdir = await getApplicationSupportDirectory(); + getIt.registerSingleton>( + ActiveProfileSelector( + (profile) { + final profilePersistencePath = + p.join(appSupportdir.path, '${profile.id}.json'); + return PersistentInfoService(profilePersistencePath)..load(); + }, + ), + ); final objectBoxCache = await ObjectBoxCache.create(); getIt.registerSingleton(objectBoxCache); getIt.registerSingleton(ObjectBoxHashtagRepo()); @@ -73,26 +87,11 @@ Future dependencyInjectionInitialization() async { getIt.registerSingleton>( ActiveProfileSelector( - (p) { - final manager = ConnectionsManager( - getIt>().getForProfile(p).value, - getIt>().getForProfile(p).value, - ); - - Future.delayed(Duration.zero, () async { - if (!settingsService.lowBandwidthMode) { - await manager.updateAllContacts(); - } - }); - - Timer.periodic(const Duration(hours: 8), (_) async { - if (!settingsService.lowBandwidthMode) { - await manager.updateAllContacts(); - } - }); - - return manager; - }, + (p) => ConnectionsManager( + p, + getIt>().getForProfile(p).value, + getIt>().getForProfile(p).value, + ), )); getIt.registerSingleton>( @@ -124,6 +123,8 @@ Future dependencyInjectionInitialization() async { getIt.registerLazySingleton( () => MediaUploadAttachmentHelper()); + + setupUpdateTimers(); } Future updateProfileDependencyInjectors(Profile profile) async { diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 87599ea..2b392a1 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -10,6 +10,7 @@ import '../friendica_client/friendica_client.dart'; import '../models/auth/credentials_intf.dart'; import '../models/auth/profile.dart'; import '../models/exec_error.dart'; +import '../update_timer_initialization.dart'; import 'secrets_service.dart'; class AccountsService extends ChangeNotifier { @@ -47,6 +48,10 @@ class AccountsService extends ChangeNotifier { final profile = pr.value; if (profile.id.isNotEmpty && profile.id == lastActiveProfile) { _currentProfile = profile; + Future.delayed( + const Duration(seconds: 10), + () async => await executeUpdatesForProfile(profile), + ); } } } @@ -152,6 +157,10 @@ class AccountsService extends ChangeNotifier { Future setActiveProfile(Profile profile, {bool withNotification = true}) async { _currentProfile = profile; + Future.delayed( + const Duration(seconds: 10), + () async => await executeUpdatesForProfile(profile), + ); if (withNotification) { notifyListeners(); } diff --git a/lib/services/connections_manager.dart b/lib/services/connections_manager.dart index b36bc82..aab6e7f 100644 --- a/lib/services/connections_manager.dart +++ b/lib/services/connections_manager.dart @@ -10,29 +10,37 @@ import '../data/interfaces/groups_repo.intf.dart'; import '../friendica_client/friendica_client.dart'; import '../friendica_client/paging_data.dart'; import '../globals.dart'; +import '../models/auth/profile.dart'; import '../models/connection.dart'; import '../models/exec_error.dart'; import '../models/group_data.dart'; -import 'auth_service.dart'; - -const _notUpdatedYetMesasge = 'Not updated since app started'; +import '../utils/active_profile_selector.dart'; +import 'persistent_info_service.dart'; class ConnectionsManager extends ChangeNotifier { static final _logger = Logger('$ConnectionsManager'); late final IConnectionsRepo conRepo; late final IGroupsRepo groupsRepo; + late final Profile profile; var groupsNotInitialized = true; - var _lastUpdateStatus = _notUpdatedYetMesasge; + var _lastUpdateStatus = ''; - String get lastUpdateStatus => _lastUpdateStatus; + String get lastUpdateStatus => _lastUpdateStatus.isNotEmpty + ? _lastUpdateStatus + : getIt>() + .getForProfile(profile) + .transform( + (info) => 'Last updated at: ${info.lastMyConnectionsUpdate}') + .withResult((text) => _lastUpdateStatus = text) + .getValueOrElse(() => 'Unknown'); - ConnectionsManager(this.conRepo, this.groupsRepo); + ConnectionsManager(this.profile, this.conRepo, this.groupsRepo); void clear() { conRepo.clear(); groupsRepo.clear(); groupsNotInitialized = true; - _lastUpdateStatus = _notUpdatedYetMesasge; + _lastUpdateStatus = ''; notifyListeners(); } @@ -65,9 +73,7 @@ class ConnectionsManager extends ChangeNotifier { Future acceptFollowRequest(Connection connection) async { _logger.finest( 'Attempting to accept follow request ${connection.name}: ${connection.status}'); - await RelationshipsClient(getIt().currentProfile) - .acceptFollow(connection) - .match( + await RelationshipsClient(profile).acceptFollow(connection).match( onSuccess: (update) { _logger .finest('Successfully followed ${update.name}: ${update.status}'); @@ -83,9 +89,7 @@ class ConnectionsManager extends ChangeNotifier { Future rejectFollowRequest(Connection connection) async { _logger.finest( 'Attempting to accept follow request ${connection.name}: ${connection.status}'); - await RelationshipsClient(getIt().currentProfile) - .rejectFollow(connection) - .match( + await RelationshipsClient(profile).rejectFollow(connection).match( onSuccess: (update) { _logger .finest('Successfully followed ${update.name}: ${update.status}'); @@ -101,9 +105,7 @@ class ConnectionsManager extends ChangeNotifier { Future ignoreFollowRequest(Connection connection) async { _logger.finest( 'Attempting to accept follow request ${connection.name}: ${connection.status}'); - await RelationshipsClient(getIt().currentProfile) - .ignoreFollow(connection) - .match( + await RelationshipsClient(profile).ignoreFollow(connection).match( onSuccess: (update) { _logger .finest('Successfully followed ${update.name}: ${update.status}'); @@ -119,9 +121,7 @@ class ConnectionsManager extends ChangeNotifier { Future follow(Connection connection) async { _logger.finest( 'Attempting to follow ${connection.name}: ${connection.status}'); - await RelationshipsClient(getIt().currentProfile) - .followConnection(connection) - .match( + await RelationshipsClient(profile).followConnection(connection).match( onSuccess: (update) { _logger .finest('Successfully followed ${update.name}: ${update.status}'); @@ -137,9 +137,7 @@ class ConnectionsManager extends ChangeNotifier { Future unfollow(Connection connection) async { _logger.finest( 'Attempting to unfollow ${connection.name}: ${connection.status}'); - await RelationshipsClient(getIt().currentProfile) - .unFollowConnection(connection) - .match( + await RelationshipsClient(profile).unFollowConnection(connection).match( onSuccess: (update) { _logger .finest('Successfully unfollowed ${update.name}: ${update.status}'); @@ -159,65 +157,77 @@ class ConnectionsManager extends ChangeNotifier { Future updateAllContacts() async { _logger.fine('Updating all contacts'); _lastUpdateStatus = 'Updating'; + final persistentInfo = getIt>() + .getForProfile(profile) + .value; + final originalTime = persistentInfo.lastMyConnectionsUpdate; + await persistentInfo.updateLastMyConnectionUpdate(DateTime.now()); notifyListeners(); - final client = RelationshipsClient(getIt().currentProfile); - final results = {}; - var moreResults = true; - var maxId = -1; - const limit = 50; - var currentPage = PagingData(limit: limit); - final originalContacts = conRepo.getMyContacts().toSet(); - while (moreResults) { - await client.getMyFollowers(currentPage).match(onSuccess: (followers) { - for (final f in followers.data) { - originalContacts.remove(f); - results[f.id] = f.copy(status: ConnectionStatus.theyFollowYou); - int id = int.parse(f.id); - maxId = max(maxId, id); - } - if (followers.next != null) { - currentPage = followers.next!; - } - moreResults = followers.next != null; - }, onError: (error) { - _logger.severe('Error getting followers data: $error'); - }); - await Future.delayed(Duration.zero); - } - - moreResults = true; - currentPage = PagingData(limit: limit); - while (moreResults) { - await client.getMyFollowing(currentPage).match(onSuccess: (following) { - for (final f in following.data) { - originalContacts.remove(f); - if (results.containsKey(f.id)) { - results[f.id] = f.copy(status: ConnectionStatus.mutual); - } else { - results[f.id] = f.copy(status: ConnectionStatus.youFollowThem); + try { + final client = RelationshipsClient(profile); + final results = {}; + var moreResults = true; + var maxId = -1; + const limit = 50; + var currentPage = PagingData(limit: limit); + final originalContacts = conRepo.getMyContacts().toSet(); + while (moreResults) { + await client.getMyFollowers(currentPage).match(onSuccess: (followers) { + for (final f in followers.data) { + originalContacts.remove(f); + results[f.id] = f.copy(status: ConnectionStatus.theyFollowYou); + int id = int.parse(f.id); + maxId = max(maxId, id); } - int id = int.parse(f.id); - maxId = max(maxId, id); - } - if (following.next != null) { - currentPage = following.next!; - } - moreResults = following.next != null; - }, onError: (error) { - _logger.severe('Error getting followers data: $error'); - }); - await Future.delayed(Duration.zero); - } + if (followers.next != null) { + currentPage = followers.next!; + } + moreResults = followers.next != null; + }, onError: (error) { + _logger.severe('Error getting followers data: $error'); + }); + await Future.delayed(Duration.zero); + } - for (final noLongerFollowed in originalContacts) { - results[noLongerFollowed.id] = - noLongerFollowed.copy(status: ConnectionStatus.none); + moreResults = true; + currentPage = PagingData(limit: limit); + while (moreResults) { + await client.getMyFollowing(currentPage).match(onSuccess: (following) { + for (final f in following.data) { + originalContacts.remove(f); + if (results.containsKey(f.id)) { + results[f.id] = f.copy(status: ConnectionStatus.mutual); + } else { + results[f.id] = f.copy(status: ConnectionStatus.youFollowThem); + } + int id = int.parse(f.id); + maxId = max(maxId, id); + } + if (following.next != null) { + currentPage = following.next!; + } + moreResults = following.next != null; + }, onError: (error) { + _logger.severe('Error getting followers data: $error'); + }); + await Future.delayed(Duration.zero); + } + + for (final noLongerFollowed in originalContacts) { + results[noLongerFollowed.id] = + noLongerFollowed.copy(status: ConnectionStatus.none); + } + upsertAllConnections(results.values); + final myContacts = conRepo.getMyContacts().toList(); + myContacts.sort((c1, c2) => c1.name.compareTo(c2.name)); + await persistentInfo.updateLastMyConnectionUpdate(DateTime.now()); + _logger.fine('# Contacts:${myContacts.length}'); + } catch (e) { + await persistentInfo.updateLastMyConnectionUpdate(originalTime); } - upsertAllConnections(results.values); - final myContacts = conRepo.getMyContacts().toList(); - myContacts.sort((c1, c2) => c1.name.compareTo(c2.name)); - _logger.fine('# Contacts:${myContacts.length}'); - _lastUpdateStatus = 'Last updated at: ${DateTime.now()}'; + _lastUpdateStatus = + 'Last updated at: ${persistentInfo.lastMyConnectionsUpdate}'; + notifyListeners(); } @@ -242,7 +252,7 @@ class ConnectionsManager extends ChangeNotifier { } FutureResult createGroup(String newName) async { - final result = await GroupsClient(getIt().currentProfile) + final result = await GroupsClient(profile) .createGroup(newName) .withResultAsync((newGroup) async { groupsRepo.upsertGroup(newGroup); @@ -253,7 +263,7 @@ class ConnectionsManager extends ChangeNotifier { FutureResult renameGroup( String id, String newName) async { - final result = await GroupsClient(getIt().currentProfile) + final result = await GroupsClient(profile) .renameGroup(id, newName) .withResultAsync((renamedGroup) async { groupsRepo.upsertGroup(renamedGroup); @@ -263,7 +273,7 @@ class ConnectionsManager extends ChangeNotifier { } FutureResult deleteGroup(GroupData groupData) async { - final result = await GroupsClient(getIt().currentProfile) + final result = await GroupsClient(profile) .deleteGroup(groupData) .withResultAsync((_) async { groupsRepo.deleteGroup(groupData); @@ -278,7 +288,7 @@ class ConnectionsManager extends ChangeNotifier { Future refreshGroupMemberships(GroupData group) async { var page = PagingData(limit: 50); - final client = GroupsClient(getIt().currentProfile); + final client = GroupsClient(profile); final allResults = {}; var moreResults = true; while (moreResults) { @@ -319,7 +329,7 @@ class ConnectionsManager extends ChangeNotifier { FutureResult addUserToGroup( GroupData group, Connection connection) async { _logger.finest('Adding ${connection.name} to group: ${group.name}'); - return await GroupsClient(getIt().currentProfile) + return await GroupsClient(profile) .addConnectionToGroup(group, connection) .withResultAsync((_) async => refreshGroupMemberships(group)) .withResult((_) => notifyListeners()) @@ -333,7 +343,7 @@ class ConnectionsManager extends ChangeNotifier { FutureResult removeUserFromGroup( GroupData group, Connection connection) async { _logger.finest('Removing ${connection.name} from group: ${group.name}'); - return GroupsClient(getIt().currentProfile) + return GroupsClient(profile) .removeConnectionFromGroup(group, connection) .withResultAsync((_) async => refreshGroupMemberships(group)) .withResult((_) => notifyListeners()) @@ -382,9 +392,7 @@ class ConnectionsManager extends ChangeNotifier { Future _refreshGroupListData(String id, bool withNotification) async { _logger.finest('Refreshing member list data for Connection $id'); - await GroupsClient(getIt().currentProfile) - .getMemberGroupsForConnection(id) - .match( + await GroupsClient(profile).getMemberGroupsForConnection(id).match( onSuccess: (groups) { groupsRepo.updateConnectionGroupData(id, groups); if (withNotification) { @@ -400,7 +408,7 @@ class ConnectionsManager extends ChangeNotifier { Future _refreshConnection( Connection connection, bool withNotification) async { _logger.finest('Refreshing connection data for ${connection.name}'); - await RelationshipsClient(getIt().currentProfile) + await RelationshipsClient(profile) .getConnectionWithStatus(connection) .match( onSuccess: (update) { @@ -417,9 +425,7 @@ class ConnectionsManager extends ChangeNotifier { Future _updateMyGroups(bool withNotification) async { _logger.finest('Refreshing my groups list'); - await GroupsClient(getIt().currentProfile) - .getGroups() - .match( + await GroupsClient(profile).getGroups().match( onSuccess: (groups) { _logger.finest('Got updated groups:${groups.map((e) => e.name)}'); groupsRepo.clearMyGroups(); diff --git a/lib/services/persistent_info_service.dart b/lib/services/persistent_info_service.dart new file mode 100644 index 0000000..faefc48 --- /dev/null +++ b/lib/services/persistent_info_service.dart @@ -0,0 +1,71 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:result_monad/result_monad.dart'; + +class PersistentInfoService { + static final _logger = Logger('$PersistentInfoService'); + final String filePath; + + DateTime _lastMyConnectionsUpdate; + + DateTime get lastMyConnectionsUpdate => _lastMyConnectionsUpdate; + + Future updateLastMyConnectionUpdate(DateTime value) async { + _lastMyConnectionsUpdate = value; + await save(); + } + + PersistentInfoService(this.filePath) + : _lastMyConnectionsUpdate = DateTime(1970); + + void load() { + final file = File(filePath); + if (!file.existsSync()) { + return; + } + + Result.ok('') + .transform((_) => File(filePath).readAsStringSync()) + .transform((str) => jsonDecode(str)) + .transform((json) => _PersistentInfo.fromJson(json)) + .match(onSuccess: (info) { + _lastMyConnectionsUpdate = info.lastMyConnectionsUpdate; + }, onError: (error) { + _logger.severe('Error reading Persistence: $error'); + }); + } + + Future save() async { + Result.ok( + _PersistentInfo( + lastMyConnectionsUpdate: _lastMyConnectionsUpdate, + ).toJson(), + ) + .transform((json) => jsonEncode(json)) + .withResultAsync((json) async => File(filePath).writeAsString(json)) + .withError( + (error) => _logger.severe('Error reading Persistence: $error'), + ); + } +} + +class _PersistentInfo { + final DateTime lastMyConnectionsUpdate; + + const _PersistentInfo({ + required this.lastMyConnectionsUpdate, + }); + + Map toJson() => { + 'lastMyConnectionsUpdate': lastMyConnectionsUpdate.toIso8601String(), + }; + + factory _PersistentInfo.fromJson(Map json) => + _PersistentInfo( + lastMyConnectionsUpdate: + DateTime.tryParse(json['lastMyConnectionsUpdate']) ?? + DateTime(1970), + ); +} diff --git a/lib/update_timer_initialization.dart b/lib/update_timer_initialization.dart new file mode 100644 index 0000000..dc56193 --- /dev/null +++ b/lib/update_timer_initialization.dart @@ -0,0 +1,46 @@ +import 'dart:async'; + +import 'package:logging/logging.dart'; +import 'package:result_monad/result_monad.dart'; + +import 'globals.dart'; +import 'models/auth/profile.dart'; +import 'services/auth_service.dart'; +import 'services/connections_manager.dart'; +import 'services/persistent_info_service.dart'; +import 'services/setting_service.dart'; +import 'utils/active_profile_selector.dart'; + +const _connectionsRefreshInterval = Duration(hours: 12); +const _timerRefresh = Duration(minutes: 1); + +final _logger = Logger('UpdateTimer'); + +void setupUpdateTimers() { + Timer.periodic(_timerRefresh, (_) async { + executeUpdatesForProfile(getIt().currentProfile); + }); +} + +Future executeUpdatesForProfile(Profile profile) async { + final lowBandwidthMode = getIt().lowBandwidthMode; + + if (lowBandwidthMode) { + return; + } + + await getIt>() + .getForProfile(profile) + .withResultAsync((info) async { + final dt = DateTime.now().difference(info.lastMyConnectionsUpdate); + _logger.info('Time since last connections update: $dt'); + if (dt >= _connectionsRefreshInterval) { + await getIt>() + .getForProfile(profile) + .withResultAsync((m) async => await m.updateAllContacts()) + .withError((error) { + _logger.severe('Error running connections update: $error'); + }); + } + }); +}