2024-01-30 17:03:14 -05:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:convert';
|
|
|
|
|
2024-06-15 00:01:08 -04:00
|
|
|
import 'package:async_tools/async_tools.dart';
|
|
|
|
import 'package:protobuf/protobuf.dart';
|
2024-01-30 17:03:14 -05:00
|
|
|
import 'package:veilid_support/veilid_support.dart';
|
|
|
|
|
2024-06-18 21:20:06 -04:00
|
|
|
import '../../account_manager/account_manager.dart';
|
2024-06-15 23:29:15 -04:00
|
|
|
import '../../conversation/conversation.dart';
|
2024-01-30 17:03:14 -05:00
|
|
|
import '../../proto/proto.dart' as proto;
|
|
|
|
import '../../tools/tools.dart';
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
// Mutable state for per-account contacts
|
|
|
|
|
|
|
|
class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|
|
|
ContactListCubit({
|
2024-06-18 21:20:06 -04:00
|
|
|
required AccountInfo accountInfo,
|
2024-06-15 23:29:15 -04:00
|
|
|
required OwnedDHTRecordPointer contactListRecordPointer,
|
2024-06-18 21:20:06 -04:00
|
|
|
}) : _accountInfo = accountInfo,
|
2024-04-03 21:55:49 -04:00
|
|
|
super(
|
2024-06-18 21:20:06 -04:00
|
|
|
open: () =>
|
|
|
|
_open(accountInfo.accountRecordKey, contactListRecordPointer),
|
2024-01-30 17:03:14 -05:00
|
|
|
decodeElement: proto.Contact.fromBuffer);
|
|
|
|
|
2024-06-15 23:29:15 -04:00
|
|
|
static Future<DHTShortArray> _open(TypedKey accountRecordKey,
|
|
|
|
OwnedDHTRecordPointer contactListRecordPointer) async {
|
|
|
|
final dhtRecord = await DHTShortArray.openOwned(contactListRecordPointer,
|
2024-04-03 21:55:49 -04:00
|
|
|
debugName: 'ContactListCubit::_open::ContactList',
|
2024-01-30 17:03:14 -05:00
|
|
|
parent: accountRecordKey);
|
|
|
|
|
|
|
|
return dhtRecord;
|
|
|
|
}
|
|
|
|
|
2024-06-15 00:01:08 -04:00
|
|
|
@override
|
|
|
|
Future<void> close() async {
|
|
|
|
await _contactProfileUpdateMap.close();
|
|
|
|
await super.close();
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Public Interface
|
|
|
|
|
|
|
|
void followContactProfileChanges(TypedKey localConversationRecordKey,
|
|
|
|
Stream<proto.Profile?> profileStream, proto.Profile? profileState) {
|
|
|
|
_contactProfileUpdateMap
|
|
|
|
.follow(localConversationRecordKey, profileStream, profileState,
|
|
|
|
(remoteProfile) async {
|
|
|
|
if (remoteProfile == null) {
|
|
|
|
return;
|
|
|
|
}
|
2024-06-15 23:29:15 -04:00
|
|
|
return updateContactProfile(
|
2024-06-15 00:01:08 -04:00
|
|
|
localConversationRecordKey: localConversationRecordKey,
|
2024-06-15 23:29:15 -04:00
|
|
|
profile: remoteProfile);
|
2024-06-15 00:01:08 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-06-15 23:29:15 -04:00
|
|
|
Future<void> updateContactProfile({
|
2024-06-15 00:01:08 -04:00
|
|
|
required TypedKey localConversationRecordKey,
|
2024-06-15 23:29:15 -04:00
|
|
|
required proto.Profile profile,
|
2024-06-15 00:01:08 -04:00
|
|
|
}) async {
|
|
|
|
// Update contact's remoteProfile
|
|
|
|
await operateWriteEventual((writer) async {
|
|
|
|
for (var pos = 0; pos < writer.length; pos++) {
|
|
|
|
final c = await writer.getProtobuf(proto.Contact.fromBuffer, pos);
|
|
|
|
if (c != null &&
|
|
|
|
c.localConversationRecordKey.toVeilid() ==
|
|
|
|
localConversationRecordKey) {
|
2024-06-15 23:29:15 -04:00
|
|
|
if (c.profile == profile) {
|
2024-06-15 00:01:08 -04:00
|
|
|
// Unchanged
|
|
|
|
break;
|
|
|
|
}
|
2024-06-15 23:29:15 -04:00
|
|
|
final newContact = c.deepCopy()..profile = profile;
|
2024-06-15 00:01:08 -04:00
|
|
|
final updated = await writer.tryWriteItemProtobuf(
|
|
|
|
proto.Contact.fromBuffer, pos, newContact);
|
|
|
|
if (!updated) {
|
|
|
|
throw DHTExceptionTryAgain();
|
|
|
|
}
|
2024-06-15 23:29:15 -04:00
|
|
|
break;
|
2024-06-15 00:01:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-01-30 17:03:14 -05:00
|
|
|
Future<void> createContact({
|
2024-06-15 23:29:15 -04:00
|
|
|
required proto.Profile profile,
|
2024-06-07 14:42:04 -04:00
|
|
|
required SuperIdentity remoteSuperIdentity,
|
2024-01-30 17:03:14 -05:00
|
|
|
required TypedKey localConversationRecordKey,
|
2024-06-15 23:29:15 -04:00
|
|
|
required TypedKey remoteConversationRecordKey,
|
2024-01-30 17:03:14 -05:00
|
|
|
}) async {
|
|
|
|
// Create Contact
|
|
|
|
final contact = proto.Contact()
|
2024-06-15 23:29:15 -04:00
|
|
|
..profile = profile
|
2024-06-07 14:42:04 -04:00
|
|
|
..superIdentityJson = jsonEncode(remoteSuperIdentity.toJson())
|
|
|
|
..identityPublicKey =
|
|
|
|
remoteSuperIdentity.currentInstance.typedPublicKey.toProto()
|
2024-01-30 17:03:14 -05:00
|
|
|
..localConversationRecordKey = localConversationRecordKey.toProto()
|
2024-06-15 23:29:15 -04:00
|
|
|
..remoteConversationRecordKey = remoteConversationRecordKey.toProto()
|
2024-01-30 17:03:14 -05:00
|
|
|
..showAvailability = false;
|
|
|
|
|
|
|
|
// Add Contact to account's list
|
|
|
|
// if this fails, don't keep retrying, user can try again later
|
2024-04-01 09:04:54 -04:00
|
|
|
await operateWrite((writer) async {
|
2024-06-09 14:43:28 -04:00
|
|
|
await writer.add(contact.writeToBuffer());
|
2024-02-27 12:45:58 -05:00
|
|
|
});
|
2024-01-30 17:03:14 -05:00
|
|
|
}
|
|
|
|
|
2024-06-15 23:29:15 -04:00
|
|
|
Future<void> deleteContact(
|
|
|
|
{required TypedKey localConversationRecordKey}) async {
|
2024-01-30 17:03:14 -05:00
|
|
|
// Remove Contact from account's list
|
2024-05-13 10:03:25 -04:00
|
|
|
final deletedItem = await operateWrite((writer) async {
|
2024-04-01 09:04:54 -04:00
|
|
|
for (var i = 0; i < writer.length; i++) {
|
2024-05-28 22:01:50 -04:00
|
|
|
final item = await writer.getProtobuf(proto.Contact.fromBuffer, i);
|
2024-02-27 12:45:58 -05:00
|
|
|
if (item == null) {
|
|
|
|
throw Exception('Failed to get contact');
|
|
|
|
}
|
2024-06-15 23:29:15 -04:00
|
|
|
if (item.localConversationRecordKey.toVeilid() ==
|
|
|
|
localConversationRecordKey) {
|
2024-05-28 22:01:50 -04:00
|
|
|
await writer.remove(i);
|
2024-05-13 10:03:25 -04:00
|
|
|
return item;
|
2024-02-27 12:45:58 -05:00
|
|
|
}
|
2024-01-30 17:03:14 -05:00
|
|
|
}
|
2024-03-28 22:46:26 -04:00
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
2024-05-13 10:03:25 -04:00
|
|
|
if (deletedItem != null) {
|
2024-02-27 12:45:58 -05:00
|
|
|
try {
|
2024-04-03 21:55:49 -04:00
|
|
|
// Make a conversation cubit to manipulate the conversation
|
|
|
|
final conversationCubit = ConversationCubit(
|
2024-06-18 21:20:06 -04:00
|
|
|
accountInfo: _accountInfo,
|
2024-06-15 23:29:15 -04:00
|
|
|
remoteIdentityPublicKey: deletedItem.identityPublicKey.toVeilid(),
|
|
|
|
localConversationRecordKey:
|
|
|
|
deletedItem.localConversationRecordKey.toVeilid(),
|
|
|
|
remoteConversationRecordKey:
|
|
|
|
deletedItem.remoteConversationRecordKey.toVeilid(),
|
2024-04-03 21:55:49 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
// Delete the local and remote conversation records
|
|
|
|
await conversationCubit.delete();
|
2024-02-27 12:45:58 -05:00
|
|
|
} on Exception catch (e) {
|
2024-04-03 21:55:49 -04:00
|
|
|
log.debug('error deleting conversation records: $e', e);
|
2024-01-30 17:03:14 -05:00
|
|
|
}
|
2024-03-28 22:46:26 -04:00
|
|
|
}
|
2024-01-30 17:03:14 -05:00
|
|
|
}
|
2024-04-03 21:55:49 -04:00
|
|
|
|
2024-06-15 00:01:08 -04:00
|
|
|
final _contactProfileUpdateMap =
|
|
|
|
SingleStateProcessorMap<TypedKey, proto.Profile?>();
|
2024-06-18 21:20:06 -04:00
|
|
|
final AccountInfo _accountInfo;
|
2024-01-30 17:03:14 -05:00
|
|
|
}
|