From b35b618a4d33e285de4804784179c67b529b0729 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 25 Jan 2024 20:33:56 -0500 Subject: [PATCH] more refactor --- .../models/active_account_info.dart | 5 +- .../contact_invitation.dart | 1 + .../models/accepted_contact.dart | 19 ++ lib/contact_invitation/models/models.dart | 2 + .../contact_invitation_repository.dart} | 199 +++++++----------- .../valid_contact_invitation.dart | 54 ++--- lib/layout/home.dart | 5 +- .../{account.dart => account_page.dart} | 4 +- .../{chats.dart => chats_page.dart} | 0 .../lib/dht_support/src/dht_short_array.dart | 25 ++- 10 files changed, 162 insertions(+), 152 deletions(-) create mode 100644 lib/contact_invitation/models/accepted_contact.dart create mode 100644 lib/contact_invitation/models/models.dart rename lib/{old_to_refactor/providers/contact_invitation_list_manager.dart => contact_invitation/repository/contact_invitation_repository.dart} (73%) rename lib/contact_invitation/{ => repository}/valid_contact_invitation.dart (77%) rename lib/layout/main_pager/{account.dart => account_page.dart} (98%) rename lib/layout/main_pager/{chats.dart => chats_page.dart} (100%) diff --git a/lib/account_manager/models/active_account_info.dart b/lib/account_manager/models/active_account_info.dart index b20dd6e..6a6afe0 100644 --- a/lib/account_manager/models/active_account_info.dart +++ b/lib/account_manager/models/active_account_info.dart @@ -13,7 +13,10 @@ class ActiveAccountInfo { }); // - KeyPair getConversationWriter() { + TypedKey get accountRecordKey => + userLogin.accountRecordInfo.accountRecord.recordKey; + + KeyPair get conversationWriter { final identityKey = localAccount.identityMaster.identityPublicKey; final identitySecret = userLogin.identitySecret; return KeyPair(key: identityKey, secret: identitySecret.value); diff --git a/lib/contact_invitation/contact_invitation.dart b/lib/contact_invitation/contact_invitation.dart index 83d1303..d46a57e 100644 --- a/lib/contact_invitation/contact_invitation.dart +++ b/lib/contact_invitation/contact_invitation.dart @@ -1 +1,2 @@ export 'views/views.dart'; +export 'repository/contact_invitation_repository.dart'; diff --git a/lib/contact_invitation/models/accepted_contact.dart b/lib/contact_invitation/models/accepted_contact.dart new file mode 100644 index 0000000..3f60811 --- /dev/null +++ b/lib/contact_invitation/models/accepted_contact.dart @@ -0,0 +1,19 @@ +import 'package:meta/meta.dart'; +import 'package:veilid_support/veilid_support.dart'; + +import '../../proto/proto.dart' as proto; + +@immutable +class AcceptedContact { + const AcceptedContact({ + required this.profile, + required this.remoteIdentity, + required this.remoteConversationRecordKey, + required this.localConversationRecordKey, + }); + + final proto.Profile profile; + final IdentityMaster remoteIdentity; + final TypedKey remoteConversationRecordKey; + final TypedKey localConversationRecordKey; +} diff --git a/lib/contact_invitation/models/models.dart b/lib/contact_invitation/models/models.dart new file mode 100644 index 0000000..0936f63 --- /dev/null +++ b/lib/contact_invitation/models/models.dart @@ -0,0 +1,2 @@ +export 'accepted_contact.dart'; +export 'valid_contact_invitation.dart'; diff --git a/lib/old_to_refactor/providers/contact_invitation_list_manager.dart b/lib/contact_invitation/repository/contact_invitation_repository.dart similarity index 73% rename from lib/old_to_refactor/providers/contact_invitation_list_manager.dart rename to lib/contact_invitation/repository/contact_invitation_repository.dart index 04cb273..2e8d49c 100644 --- a/lib/old_to_refactor/providers/contact_invitation_list_manager.dart +++ b/lib/contact_invitation/repository/contact_invitation_repository.dart @@ -1,16 +1,11 @@ -import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fixnum/fixnum.dart'; import 'package:flutter/foundation.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:mutex/mutex.dart'; +import 'package:veilid_support/veilid_support.dart'; -import '../../entities/entities.dart'; +import '../../account_manager/account_manager.dart'; import '../../proto/proto.dart' as proto; import '../../tools/tools.dart'; -import '../../../packages/veilid_support/veilid_support.dart'; -import 'account.dart'; - -part 'contact_invitation_list_manager.g.dart'; +import '../models/models.dart'; ////////////////////////////////////////////////// @@ -24,22 +19,6 @@ typedef GetEncryptionKeyCallback = Future Function( EncryptionKeyType encryptionKeyType, Uint8List encryptedSecret); -////////////////////////////////////////////////// -@immutable -class AcceptedContact { - const AcceptedContact({ - required this.profile, - required this.remoteIdentity, - required this.remoteConversationRecordKey, - required this.localConversationRecordKey, - }); - - final proto.Profile profile; - final IdentityMaster remoteIdentity; - final TypedKey remoteConversationRecordKey; - final TypedKey localConversationRecordKey; -} - @immutable class InvitationStatus { const InvitationStatus({required this.acceptedContact}); @@ -50,75 +29,65 @@ class InvitationStatus { ////////////////////////////////////////////////// // Mutable state for per-account contact invitations -@riverpod -class ContactInvitationListManager extends _$ContactInvitationListManager { - ContactInvitationListManager._({ +class ContactInvitationRepository { + ContactInvitationRepository._({ required ActiveAccountInfo activeAccountInfo, + required proto.Account account, required DHTShortArray dhtRecord, }) : _activeAccountInfo = activeAccountInfo, - _dhtRecord = dhtRecord, - _records = IList(); + _account = account, + _dhtRecord = dhtRecord; - @override - FutureOr> build( - ActiveAccountInfo activeAccountInfo) async { - // Load initial todo list from the remote repository - ref.onDispose xxxx call close and pass dhtrecord through... could use a context object - and a DHTValueChangeProvider that we watch in build that updates when dht records change - return _open(activeAccountInfo); - } - - static Future _open( - ActiveAccountInfo activeAccountInfo) async { + static Future open( + ActiveAccountInfo activeAccountInfo, proto.Account account) async { final accountRecordKey = activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; - final dhtRecord = await DHTShortArray.openOwned( + final contactInvitationListRecordKey = proto.OwnedDHTRecordPointerProto.fromProto( - activeAccountInfo.account.contactInvitationRecords), + account.contactInvitationRecords); + + final dhtRecord = await DHTShortArray.openOwned( + contactInvitationListRecordKey, parent: accountRecordKey); - return ContactInvitationListManager._( - activeAccountInfo: activeAccountInfo, dhtRecord: dhtRecord); + return ContactInvitationRepository._( + activeAccountInfo: activeAccountInfo, + account: account, + dhtRecord: dhtRecord); } Future close() async { - state = ""; await _dhtRecord.close(); } - Future refresh() async { - for (var i = 0; i < _dhtRecord.length; i++) { - final cir = await _dhtRecord.getItem(i); - if (cir == null) { - throw Exception('Failed to get contact invitation record'); - } - _records = _records.add(proto.ContactInvitationRecord.fromBuffer(cir)); - } - } + // Future refresh() async { + // for (var i = 0; i < _dhtRecord.length; i++) { + // final cir = await _dhtRecord.getItem(i); + // if (cir == null) { + // throw Exception('Failed to get contact invitation record'); + // } + // _records = _records.add(proto.ContactInvitationRecord.fromBuffer(cir)); + // } + // } Future createInvitation( {required EncryptionKeyType encryptionKeyType, required String encryptionKey, required String message, required Timestamp? expiration}) async { - final pool = await DHTRecordPool.instance(); - final accountRecordKey = - _activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; - final identityKey = - _activeAccountInfo.localAccount.identityMaster.identityPublicKey; - final identitySecret = _activeAccountInfo.userLogin.identitySecret.value; + final pool = DHTRecordPool.instance; // Generate writer keypair to share with new contact final cs = await pool.veilid.bestCryptoSystem(); final contactRequestWriter = await cs.generateKeyPair(); - final conversationWriter = _activeAccountInfo.getConversationWriter(); + final conversationWriter = _activeAccountInfo.conversationWriter; // Encrypt the writer secret with the encryption key final encryptedSecret = await encryptionKeyType.encryptSecretToBytes( - secret: contactRequestWriter.secret, - cryptoKind: cs.kind(), - encryptionKey: encryptionKey, + secret: contactRequestWriter.secret, + cryptoKind: cs.kind(), + encryptionKey: encryptionKey, ); // Create local chat DHT record with the account record key as its parent @@ -127,7 +96,7 @@ class ContactInvitationListManager extends _$ContactInvitationListManager { // identity key late final Uint8List signedContactInvitationBytes; await (await pool.create( - parent: accountRecordKey, + parent: _activeAccountInfo.accountRecordKey, schema: DHTSchema.smpl(oCnt: 0, members: [ DHTSchemaMember(mKey: conversationWriter.key, mCnt: 1) ]))) @@ -136,7 +105,7 @@ class ContactInvitationListManager extends _$ContactInvitationListManager { // Make ContactRequestPrivate and encrypt with the writer secret final crpriv = proto.ContactRequestPrivate() ..writerKey = contactRequestWriter.key.toProto() - ..profile = _activeAccountInfo.account.profile + ..profile = _account.profile ..identityMasterRecordKey = _activeAccountInfo.userLogin.accountMasterRecordKey.toProto() ..chatRecordKey = localConversation.key.toProto() @@ -152,7 +121,7 @@ class ContactInvitationListManager extends _$ContactInvitationListManager { // Create DHT unicast inbox for ContactRequest await (await pool.create( - parent: accountRecordKey, + parent: _activeAccountInfo.accountRecordKey, schema: DHTSchema.smpl(oCnt: 1, members: [ DHTSchemaMember(mCnt: 1, mKey: contactRequestWriter.key) ]), @@ -168,8 +137,9 @@ class ContactInvitationListManager extends _$ContactInvitationListManager { final cinvbytes = cinv.writeToBuffer(); final scinv = proto.SignedContactInvitation() ..contactInvitation = cinvbytes - ..identitySignature = - (await cs.sign(identityKey, identitySecret, cinvbytes)).toProto(); + ..identitySignature = (await cs.sign( + conversationWriter.key, conversationWriter.secret, cinvbytes)) + .toProto(); signedContactInvitationBytes = scinv.writeToBuffer(); // Create ContactInvitationRecord @@ -185,15 +155,9 @@ class ContactInvitationListManager extends _$ContactInvitationListManager { // Add ContactInvitationRecord to account's list // if this fails, don't keep retrying, user can try again later - await (await DHTShortArray.openOwned( - proto.OwnedDHTRecordPointerProto.fromProto( - _activeAccountInfo.account.contactInvitationRecords), - parent: accountRecordKey)) - .scope((cirList) async { - if (await cirList.tryAddItem(cinvrec.writeToBuffer()) == false) { - throw Exception('Failed to add contact invitation record'); - } - }); + if (await _dhtRecord.tryAddItem(cinvrec.writeToBuffer()) == false) { + throw Exception('Failed to add contact invitation record'); + } }); }); @@ -203,77 +167,71 @@ class ContactInvitationListManager extends _$ContactInvitationListManager { Future deleteInvitation( {required bool accepted, required proto.ContactInvitationRecord contactInvitationRecord}) async { - final pool = await DHTRecordPool.instance(); + final pool = DHTRecordPool.instance; final accountRecordKey = _activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; // Remove ContactInvitationRecord from account's list - await (await DHTShortArray.openOwned( + for (var i = 0; i < _dhtRecord.length; i++) { + final item = await _dhtRecord.getItemProtobuf( + proto.ContactInvitationRecord.fromBuffer, i); + if (item == null) { + throw Exception('Failed to get contact invitation record'); + } + if (item.contactRequestInbox.recordKey == + contactInvitationRecord.contactRequestInbox.recordKey) { + await _dhtRecord.tryRemoveItem(i); + break; + } + } + await (await pool.openOwned( proto.OwnedDHTRecordPointerProto.fromProto( - _activeAccountInfo.account.contactInvitationRecords), + contactInvitationRecord.contactRequestInbox), parent: accountRecordKey)) - .scope((cirList) async { - for (var i = 0; i < cirList.length; i++) { - final item = await cirList.getItemProtobuf( - proto.ContactInvitationRecord.fromBuffer, i); - if (item == null) { - throw Exception('Failed to get contact invitation record'); - } - if (item.contactRequestInbox.recordKey == - contactInvitationRecord.contactRequestInbox.recordKey) { - await cirList.tryRemoveItem(i); - break; - } - } - await (await pool.openOwned( - proto.OwnedDHTRecordPointerProto.fromProto( - contactInvitationRecord.contactRequestInbox), - parent: accountRecordKey)) - .scope((contactRequestInbox) async { - // Wipe out old invitation so it shows up as invalid - await contactRequestInbox.tryWriteBytes(Uint8List(0)); - await contactRequestInbox.delete(); - }); - if (!accepted) { - await (await pool.openRead( - proto.TypedKeyProto.fromProto( - contactInvitationRecord.localConversationRecordKey), - parent: accountRecordKey)) - .delete(); - } + .scope((contactRequestInbox) async { + // Wipe out old invitation so it shows up as invalid + await contactRequestInbox.tryWriteBytes(Uint8List(0)); + await contactRequestInbox.delete(); }); + if (!accepted) { + await (await pool.openRead( + proto.TypedKeyProto.fromProto( + contactInvitationRecord.localConversationRecordKey), + parent: accountRecordKey)) + .delete(); + } } Future validateInvitation( {required Uint8List inviteData, required GetEncryptionKeyCallback getEncryptionKeyCallback}) async { - final accountRecordKey = - _activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; + final pool = DHTRecordPool.instance; + // Get contact request inbox from invitation final signedContactInvitation = proto.SignedContactInvitation.fromBuffer(inviteData); - final contactInvitationBytes = Uint8List.fromList(signedContactInvitation.contactInvitation); final contactInvitation = proto.ContactInvitation.fromBuffer(contactInvitationBytes); - final contactRequestInboxKey = proto.TypedKeyProto.fromProto(contactInvitation.contactRequestInboxKey); ValidContactInvitation? out; - final pool = await DHTRecordPool.instance(); final cs = await pool.veilid.getCryptoSystem(contactRequestInboxKey.kind); // See if we're chatting to ourselves, if so, don't delete it here + final isSelf = _contactIdentityMaster.identityPublicKey == + _activeAccountInfo.localAccount.identityMaster.identityPublicKey; +xxx this doesn't work and the upper one doesnt either final isSelf = _records.indexWhere((cir) => proto.TypedKeyProto.fromProto(cir.contactRequestInbox.recordKey) == contactRequestInboxKey) != -1; await (await pool.openRead(contactRequestInboxKey, - parent: accountRecordKey)) + parent: _activeAccountInfo.accountRecordKey)) .maybeDeleteScope(!isSelf, (contactRequestInbox) async { // final contactRequest = await contactRequestInbox @@ -315,8 +273,8 @@ class ContactInvitationListManager extends _$ContactInvitationListManager { key: proto.CryptoKeyProto.fromProto(contactRequestPrivate.writerKey), secret: writerSecret); - out = ValidContactInvitation._( - contactInvitationManager: this, + out = ValidContactInvitation( + activeAccountInfo: _activeAccountInfo, signedContactInvitation: signedContactInvitation, contactInvitation: contactInvitation, contactRequestInboxKey: contactRequestInboxKey, @@ -334,7 +292,7 @@ class ContactInvitationListManager extends _$ContactInvitationListManager { required proto.ContactInvitationRecord contactInvitationRecord}) async { // Open the contact request inbox try { - final pool = await DHTRecordPool.instance(); + final pool = DHTRecordPool.instance; final accountRecordKey = activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; final writerKey = @@ -436,6 +394,7 @@ class ContactInvitationListManager extends _$ContactInvitationListManager { // final ActiveAccountInfo _activeAccountInfo; + final proto.Account _account; final DHTShortArray _dhtRecord; - IList _records; + //IList _records; } diff --git a/lib/contact_invitation/valid_contact_invitation.dart b/lib/contact_invitation/repository/valid_contact_invitation.dart similarity index 77% rename from lib/contact_invitation/valid_contact_invitation.dart rename to lib/contact_invitation/repository/valid_contact_invitation.dart index ec21444..2af9c30 100644 --- a/lib/contact_invitation/valid_contact_invitation.dart +++ b/lib/contact_invitation/repository/valid_contact_invitation.dart @@ -1,9 +1,19 @@ +import 'package:meta/meta.dart'; +import 'package:veilid_support/veilid_support.dart'; + +import '../../account_manager/account_manager.dart'; +import '../../proto/proto.dart' as proto; +import '../../tools/tools.dart'; +import '../models/models.dart'; +import 'contact_invitation_repository.dart'; + ////////////////////////////////////////////////// /// class ValidContactInvitation { - ValidContactInvitation._( - {required ContactInvitationListManager contactInvitationManager, + @internal + ValidContactInvitation( + {required ActiveAccountInfo activeAccountInfo, required proto.SignedContactInvitation signedContactInvitation, required proto.ContactInvitation contactInvitation, required TypedKey contactRequestInboxKey, @@ -11,7 +21,7 @@ class ValidContactInvitation { required proto.ContactRequestPrivate contactRequestPrivate, required IdentityMaster contactIdentityMaster, required KeyPair writer}) - : _contactInvitationManager = contactInvitationManager, + : _activeAccountInfo = activeAccountInfo, _signedContactInvitation = signedContactInvitation, _contactInvitation = contactInvitation, _contactRequestInboxKey = contactRequestInboxKey, @@ -21,14 +31,12 @@ class ValidContactInvitation { _writer = writer; Future accept() async { - final pool = await DHTRecordPool.instance(); - final activeAccountInfo = _contactInvitationManager._activeAccountInfo; + final pool = DHTRecordPool.instance; try { // Ensure we don't delete this if we're trying to chat to self final isSelf = _contactIdentityMaster.identityPublicKey == - activeAccountInfo.localAccount.identityMaster.identityPublicKey; - final accountRecordKey = - activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; + _activeAccountInfo.localAccount.identityMaster.identityPublicKey; + final accountRecordKey = _activeAccountInfo.accountRecordKey; return (await pool.openWrite(_contactRequestInboxKey, _writer, parent: accountRecordKey)) @@ -37,14 +45,14 @@ class ValidContactInvitation { // Create local conversation key for this // contact and send via contact response return createConversation( - activeAccountInfo: activeAccountInfo, + activeAccountInfo: _activeAccountInfo, remoteIdentityPublicKey: _contactIdentityMaster.identityPublicTypedKey(), callback: (localConversation) async { final contactResponse = proto.ContactResponse() ..accept = true ..remoteConversationRecordKey = localConversation.key.toProto() - ..identityMasterRecordKey = activeAccountInfo + ..identityMasterRecordKey = _activeAccountInfo .localAccount.identityMaster.masterRecordKey .toProto(); final contactResponseBytes = contactResponse.writeToBuffer(); @@ -53,9 +61,8 @@ class ValidContactInvitation { .getCryptoSystem(_contactRequestInboxKey.kind); final identitySignature = await cs.sign( - activeAccountInfo - .localAccount.identityMaster.identityPublicKey, - activeAccountInfo.userLogin.identitySecret.value, + _activeAccountInfo.conversationWriter.key, + _activeAccountInfo.conversationWriter.secret, contactResponseBytes); final signedContactResponse = proto.SignedContactResponse() @@ -86,14 +93,13 @@ class ValidContactInvitation { } Future reject() async { - final pool = await DHTRecordPool.instance(); - final activeAccountInfo = _contactInvitationManager._activeAccountInfo; + final pool = DHTRecordPool.instance; // Ensure we don't delete this if we're trying to chat to self final isSelf = _contactIdentityMaster.identityPublicKey == - activeAccountInfo.localAccount.identityMaster.identityPublicKey; + _activeAccountInfo.localAccount.identityMaster.identityPublicKey; final accountRecordKey = - activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; + _activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; return (await pool.openWrite(_contactRequestInboxKey, _writer, parent: accountRecordKey)) @@ -103,14 +109,14 @@ class ValidContactInvitation { final contactResponse = proto.ContactResponse() ..accept = false - ..identityMasterRecordKey = activeAccountInfo + ..identityMasterRecordKey = _activeAccountInfo .localAccount.identityMaster.masterRecordKey .toProto(); final contactResponseBytes = contactResponse.writeToBuffer(); final identitySignature = await cs.sign( - activeAccountInfo.localAccount.identityMaster.identityPublicKey, - activeAccountInfo.userLogin.identitySecret.value, + _activeAccountInfo.conversationWriter.key, + _activeAccountInfo.conversationWriter.secret, contactResponseBytes); final signedContactResponse = proto.SignedContactResponse() @@ -130,12 +136,12 @@ class ValidContactInvitation { } // - ContactInvitationListManager _contactInvitationManager; + final ActiveAccountInfo _activeAccountInfo; + final TypedKey _contactRequestInboxKey; + final IdentityMaster _contactIdentityMaster; + final KeyPair _writer; proto.SignedContactInvitation _signedContactInvitation; proto.ContactInvitation _contactInvitation; - TypedKey _contactRequestInboxKey; proto.ContactRequest _contactRequest; proto.ContactRequestPrivate _contactRequestPrivate; - IdentityMaster _contactIdentityMaster; - KeyPair _writer; } diff --git a/lib/layout/home.dart b/lib/layout/home.dart index 438ec6a..8826958 100644 --- a/lib/layout/home.dart +++ b/lib/layout/home.dart @@ -7,7 +7,6 @@ import 'package:flutter_translate/flutter_translate.dart'; import 'package:go_router/go_router.dart'; import 'package:veilid_support/veilid_support.dart'; -import '../../proto/proto.dart' as proto; import '../account_manager/account_manager.dart'; import '../chat/chat.dart'; import '../theme/theme.dart'; @@ -115,7 +114,7 @@ class HomePageState extends State with TickerProviderStateMixin { } final account = AccountRepository.instance - .getAccountInfo(accountMasterRecordKey: activeUserLogin); + .getAccountInfo(accountMasterRecordKey: activeUserLogin)!; switch (account.status) { case AccountInfoStatus.noAccount: @@ -152,7 +151,7 @@ class HomePageState extends State with TickerProviderStateMixin { context, localAccounts, activeUserLogin, - account.accountRecord!, + account.activeAccountInfo!.accountRecord, ); } }); diff --git a/lib/layout/main_pager/account.dart b/lib/layout/main_pager/account_page.dart similarity index 98% rename from lib/layout/main_pager/account.dart rename to lib/layout/main_pager/account_page.dart index 3f25171..ef83d8e 100644 --- a/lib/layout/main_pager/account.dart +++ b/lib/layout/main_pager/account_page.dart @@ -1,5 +1,3 @@ -// ignore_for_file: prefer_const_constructors - import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/foundation.dart'; @@ -58,6 +56,8 @@ class AccountPageState extends State { final textTheme = theme.textTheme; final scale = theme.extension()!; + final records = widget.account.contactInvitationRecords; + final contactInvitationRecordList = ref.watch(fetchContactInvitationRecordsProvider).asData?.value ?? const IListConst([]); diff --git a/lib/layout/main_pager/chats.dart b/lib/layout/main_pager/chats_page.dart similarity index 100% rename from lib/layout/main_pager/chats.dart rename to lib/layout/main_pager/chats_page.dart diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array.dart index 136520c..55f28e3 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array.dart @@ -497,6 +497,9 @@ class DHTShortArray { } Future trySwapItem(int aPos, int bPos) async { + if (aPos == bPos) { + return false; + } await _refreshHead(onlyUpdates: true); final oldHead = _DHTShortArrayCache.from(_head); @@ -517,6 +520,9 @@ class DHTShortArray { _head = oldHead; return false; } + + // A change happened, notify any listeners + _watchController?.sink.add(null); return true; } @@ -539,7 +545,12 @@ class DHTShortArray { return null; } - return record!.get(subkey: recordSubkey); + final result = await record!.get(subkey: recordSubkey); + if (result != null) { + // A change happened, notify any listeners + _watchController?.sink.add(null); + } + return result; } on Exception catch (_) { // Exception on write means state needs to be reverted _head = oldHead; @@ -575,6 +586,10 @@ class DHTShortArray { _head = oldHead; return false; } + + // A change happened, notify any listeners + _watchController?.sink.add(null); + return true; } @@ -591,7 +606,13 @@ class DHTShortArray { final record = await _getOrCreateLinkedRecord(recordNumber); final recordSubkey = (index % _stride) + ((recordNumber == 0) ? 1 : 0); - return record.tryWriteBytes(newValue, subkey: recordSubkey); + final result = await record.tryWriteBytes(newValue, subkey: recordSubkey); + + if (result != null) { + // A change happened, notify any listeners + _watchController?.sink.add(null); + } + return result; } Future eventualWriteItem(int pos, Uint8List newValue) async {