mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04:00
contact reject
This commit is contained in:
parent
9be3d100e4
commit
b12cbcf684
@ -34,9 +34,10 @@
|
|||||||
3. Set ContactRequest unicastinbox DHT record writer subkey with SignedContactResponse, encrypted with writer secret
|
3. Set ContactRequest unicastinbox DHT record writer subkey with SignedContactResponse, encrypted with writer secret
|
||||||
|
|
||||||
## Receiving an accept/reject
|
## Receiving an accept/reject
|
||||||
1. Decrypt with writer secret
|
1. Open and get SignedContactResponse from ContactRequest unicaseinbox DHT record
|
||||||
2. Get DHT record for contact's AccountMaster
|
2. Decrypt with writer secret
|
||||||
3. Validate the SignedContactResponse signature
|
3. Get DHT record for contact's AccountMaster
|
||||||
|
4. Validate the SignedContactResponse signature
|
||||||
|
|
||||||
If accept == false:
|
If accept == false:
|
||||||
1. Announce rejection
|
1. Announce rejection
|
||||||
|
@ -57,6 +57,7 @@ class ContactInvitationItemWidget extends ConsumerWidget {
|
|||||||
await ref.read(fetchActiveAccountProvider.future);
|
await ref.read(fetchActiveAccountProvider.future);
|
||||||
if (activeAccountInfo != null) {
|
if (activeAccountInfo != null) {
|
||||||
await deleteContactInvitation(
|
await deleteContactInvitation(
|
||||||
|
accepted: false,
|
||||||
activeAccountInfo: activeAccountInfo,
|
activeAccountInfo: activeAccountInfo,
|
||||||
contactInvitationRecord: contactInvitationRecord);
|
contactInvitationRecord: contactInvitationRecord);
|
||||||
ref.invalidate(fetchContactInvitationRecordsProvider);
|
ref.invalidate(fetchContactInvitationRecordsProvider);
|
||||||
|
@ -1575,7 +1575,7 @@ class ContactResponse extends $pb.GeneratedMessage {
|
|||||||
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactResponse', createEmptyInstance: create)
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactResponse', createEmptyInstance: create)
|
||||||
..aOB(1, _omitFieldNames ? '' : 'accept')
|
..aOB(1, _omitFieldNames ? '' : 'accept')
|
||||||
..aOM<TypedKey>(2, _omitFieldNames ? '' : 'accountMasterRecordKey', subBuilder: TypedKey.create)
|
..aOM<TypedKey>(2, _omitFieldNames ? '' : 'identityMasterRecordKey', subBuilder: TypedKey.create)
|
||||||
..aOM<TypedKey>(3, _omitFieldNames ? '' : 'remoteConversationKey', subBuilder: TypedKey.create)
|
..aOM<TypedKey>(3, _omitFieldNames ? '' : 'remoteConversationKey', subBuilder: TypedKey.create)
|
||||||
..hasRequiredFields = false
|
..hasRequiredFields = false
|
||||||
;
|
;
|
||||||
@ -1611,15 +1611,15 @@ class ContactResponse extends $pb.GeneratedMessage {
|
|||||||
void clearAccept() => clearField(1);
|
void clearAccept() => clearField(1);
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
TypedKey get accountMasterRecordKey => $_getN(1);
|
TypedKey get identityMasterRecordKey => $_getN(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
set accountMasterRecordKey(TypedKey v) { setField(2, v); }
|
set identityMasterRecordKey(TypedKey v) { setField(2, v); }
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.bool hasAccountMasterRecordKey() => $_has(1);
|
$core.bool hasIdentityMasterRecordKey() => $_has(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearAccountMasterRecordKey() => clearField(2);
|
void clearIdentityMasterRecordKey() => clearField(2);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
TypedKey ensureAccountMasterRecordKey() => $_ensure(1);
|
TypedKey ensureIdentityMasterRecordKey() => $_ensure(1);
|
||||||
|
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
TypedKey get remoteConversationKey => $_getN(2);
|
TypedKey get remoteConversationKey => $_getN(2);
|
||||||
|
@ -427,17 +427,17 @@ const ContactResponse$json = {
|
|||||||
'1': 'ContactResponse',
|
'1': 'ContactResponse',
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'accept', '3': 1, '4': 1, '5': 8, '10': 'accept'},
|
{'1': 'accept', '3': 1, '4': 1, '5': 8, '10': 'accept'},
|
||||||
{'1': 'account_master_record_key', '3': 2, '4': 1, '5': 11, '6': '.TypedKey', '10': 'accountMasterRecordKey'},
|
{'1': 'identity_master_record_key', '3': 2, '4': 1, '5': 11, '6': '.TypedKey', '10': 'identityMasterRecordKey'},
|
||||||
{'1': 'remote_conversation_key', '3': 3, '4': 1, '5': 11, '6': '.TypedKey', '10': 'remoteConversationKey'},
|
{'1': 'remote_conversation_key', '3': 3, '4': 1, '5': 11, '6': '.TypedKey', '10': 'remoteConversationKey'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Descriptor for `ContactResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `ContactResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List contactResponseDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List contactResponseDescriptor = $convert.base64Decode(
|
||||||
'Cg9Db250YWN0UmVzcG9uc2USFgoGYWNjZXB0GAEgASgIUgZhY2NlcHQSRAoZYWNjb3VudF9tYX'
|
'Cg9Db250YWN0UmVzcG9uc2USFgoGYWNjZXB0GAEgASgIUgZhY2NlcHQSRgoaaWRlbnRpdHlfbW'
|
||||||
'N0ZXJfcmVjb3JkX2tleRgCIAEoCzIJLlR5cGVkS2V5UhZhY2NvdW50TWFzdGVyUmVjb3JkS2V5'
|
'FzdGVyX3JlY29yZF9rZXkYAiABKAsyCS5UeXBlZEtleVIXaWRlbnRpdHlNYXN0ZXJSZWNvcmRL'
|
||||||
'EkEKF3JlbW90ZV9jb252ZXJzYXRpb25fa2V5GAMgASgLMgkuVHlwZWRLZXlSFXJlbW90ZUNvbn'
|
'ZXkSQQoXcmVtb3RlX2NvbnZlcnNhdGlvbl9rZXkYAyABKAsyCS5UeXBlZEtleVIVcmVtb3RlQ2'
|
||||||
'ZlcnNhdGlvbktleQ==');
|
'9udmVyc2F0aW9uS2V5');
|
||||||
|
|
||||||
@$core.Deprecated('Use signedContactResponseDescriptor instead')
|
@$core.Deprecated('Use signedContactResponseDescriptor instead')
|
||||||
const SignedContactResponse$json = {
|
const SignedContactResponse$json = {
|
||||||
|
@ -307,7 +307,7 @@ message ContactRequestPrivate {
|
|||||||
CryptoKey writer_key = 1;
|
CryptoKey writer_key = 1;
|
||||||
// Snapshot of profile
|
// Snapshot of profile
|
||||||
Profile profile = 2;
|
Profile profile = 2;
|
||||||
// Identity master dht key
|
// Identity master DHT record key
|
||||||
TypedKey identity_master_record_key = 3;
|
TypedKey identity_master_record_key = 3;
|
||||||
// Local chat DHT record key
|
// Local chat DHT record key
|
||||||
TypedKey chat_record_key = 4;
|
TypedKey chat_record_key = 4;
|
||||||
@ -319,8 +319,8 @@ message ContactRequestPrivate {
|
|||||||
message ContactResponse {
|
message ContactResponse {
|
||||||
// Accept or reject
|
// Accept or reject
|
||||||
bool accept = 1;
|
bool accept = 1;
|
||||||
// Account master record key
|
// Remote identity master DHT record key
|
||||||
TypedKey account_master_record_key = 2;
|
TypedKey identity_master_record_key = 2;
|
||||||
// Remote chat DHT record key if accepted
|
// Remote chat DHT record key if accepted
|
||||||
TypedKey remote_conversation_key = 3;
|
TypedKey remote_conversation_key = 3;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@ -5,10 +7,13 @@ import 'package:split_view/split_view.dart';
|
|||||||
import 'package:signal_strength_indicator/signal_strength_indicator.dart';
|
import 'package:signal_strength_indicator/signal_strength_indicator.dart';
|
||||||
|
|
||||||
import '../components/chat_component.dart';
|
import '../components/chat_component.dart';
|
||||||
|
import '../providers/account.dart';
|
||||||
|
import '../providers/contact.dart';
|
||||||
import '../providers/local_accounts.dart';
|
import '../providers/local_accounts.dart';
|
||||||
import '../providers/logins.dart';
|
import '../providers/logins.dart';
|
||||||
import '../providers/window_control.dart';
|
import '../providers/window_control.dart';
|
||||||
import '../tools/tools.dart';
|
import '../tools/tools.dart';
|
||||||
|
import '../veilid_support/dht_support/dht_record_pool.dart';
|
||||||
import 'main_pager/main_pager.dart';
|
import 'main_pager/main_pager.dart';
|
||||||
|
|
||||||
class HomePage extends ConsumerStatefulWidget {
|
class HomePage extends ConsumerStatefulWidget {
|
||||||
@ -19,10 +24,17 @@ class HomePage extends ConsumerStatefulWidget {
|
|||||||
HomePageState createState() => HomePageState();
|
HomePageState createState() => HomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX Eliminate this when we have ValueChanged
|
||||||
|
const int ticksPerContactInvitationCheck = 5;
|
||||||
|
|
||||||
class HomePageState extends ConsumerState<HomePage>
|
class HomePageState extends ConsumerState<HomePage>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
final _unfocusNode = FocusNode();
|
final _unfocusNode = FocusNode();
|
||||||
|
|
||||||
|
Timer? _homeTickTimer;
|
||||||
|
bool _inHomeTick = false;
|
||||||
|
int _contactInvitationCheckTick = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -31,15 +43,69 @@ class HomePageState extends ConsumerState<HomePage>
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
await ref.read(windowControlProvider.notifier).changeWindowSetup(
|
await ref.read(windowControlProvider.notifier).changeWindowSetup(
|
||||||
TitleBarStyle.normal, OrientationCapability.normal);
|
TitleBarStyle.normal, OrientationCapability.normal);
|
||||||
|
|
||||||
|
_homeTickTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
if (!_inHomeTick) {
|
||||||
|
unawaited(_onHomeTick());
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
final homeTickTimer = _homeTickTimer;
|
||||||
|
if (homeTickTimer != null) {
|
||||||
|
homeTickTimer.cancel();
|
||||||
|
}
|
||||||
_unfocusNode.dispose();
|
_unfocusNode.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onHomeTick() async {
|
||||||
|
_inHomeTick = true;
|
||||||
|
try {
|
||||||
|
// Check extant contact invitations once every 5 seconds
|
||||||
|
_contactInvitationCheckTick += 1;
|
||||||
|
if (_contactInvitationCheckTick >= ticksPerContactInvitationCheck) {
|
||||||
|
_contactInvitationCheckTick = 0;
|
||||||
|
await _doContactInvitationCheck();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_inHomeTick = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _doContactInvitationCheck() async {
|
||||||
|
final contactInvitationRecords =
|
||||||
|
await ref.read(fetchContactInvitationRecordsProvider.future);
|
||||||
|
final activeAccountInfo = await ref.read(fetchActiveAccountProvider.future);
|
||||||
|
if (contactInvitationRecords == null || activeAccountInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final allChecks = <Future<void>>[];
|
||||||
|
for (final contactInvitationRecord in contactInvitationRecords) {
|
||||||
|
allChecks.add(() async {
|
||||||
|
final acceptReject = await checkAcceptRejectContact(
|
||||||
|
activeAccountInfo: activeAccountInfo,
|
||||||
|
contactInvitationRecord: contactInvitationRecord);
|
||||||
|
if (acceptReject != null) {
|
||||||
|
if (acceptReject) {
|
||||||
|
// Accept
|
||||||
|
ref
|
||||||
|
..invalidate(fetchContactInvitationRecordsProvider)
|
||||||
|
..invalidate(fetchContactListProvider);
|
||||||
|
} else {
|
||||||
|
// Reject
|
||||||
|
ref.invalidate(fetchContactInvitationRecordsProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
await Future.wait(allChecks);
|
||||||
|
}
|
||||||
|
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget buildPhone(BuildContext context) {
|
Widget buildPhone(BuildContext context) {
|
||||||
//
|
//
|
||||||
|
@ -14,8 +14,8 @@ import '../entities/proto.dart'
|
|||||||
ContactInvitationRecord,
|
ContactInvitationRecord,
|
||||||
ContactRequest,
|
ContactRequest,
|
||||||
ContactRequestPrivate,
|
ContactRequestPrivate,
|
||||||
SignedContactInvitation,
|
|
||||||
ContactResponse,
|
ContactResponse,
|
||||||
|
SignedContactInvitation,
|
||||||
SignedContactResponse;
|
SignedContactResponse;
|
||||||
import '../log/loggy.dart';
|
import '../log/loggy.dart';
|
||||||
import '../tools/tools.dart';
|
import '../tools/tools.dart';
|
||||||
@ -24,9 +24,90 @@ import 'account.dart';
|
|||||||
|
|
||||||
part 'contact.g.dart';
|
part 'contact.g.dart';
|
||||||
|
|
||||||
Future<void> deleteContactInvitation(
|
Future<bool?> checkAcceptRejectContact(
|
||||||
{required ActiveAccountInfo activeAccountInfo,
|
{required ActiveAccountInfo activeAccountInfo,
|
||||||
required ContactInvitationRecord contactInvitationRecord}) async {
|
required ContactInvitationRecord contactInvitationRecord}) async {
|
||||||
|
// Open the contact request inbox
|
||||||
|
try {
|
||||||
|
final pool = await DHTRecordPool.instance();
|
||||||
|
final accountRecordKey =
|
||||||
|
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
|
final writerKey =
|
||||||
|
proto.CryptoKeyProto.fromProto(contactInvitationRecord.writerKey);
|
||||||
|
final writerSecret =
|
||||||
|
proto.CryptoKeyProto.fromProto(contactInvitationRecord.writerSecret);
|
||||||
|
final writer = TypedKeyPair(
|
||||||
|
kind: contactInvitationRecord.contactRequestInbox.recordKey.kind,
|
||||||
|
key: writerKey,
|
||||||
|
secret: writerSecret);
|
||||||
|
final acceptReject = await (await pool.openRead(
|
||||||
|
proto.TypedKeyProto.fromProto(
|
||||||
|
contactInvitationRecord.contactRequestInbox.recordKey),
|
||||||
|
crypto: await DHTRecordCryptoPrivate.fromTypedKeyPair(writer),
|
||||||
|
parent: accountRecordKey,
|
||||||
|
defaultSubkey: 1))
|
||||||
|
.scope((contactRequestInbox) async {
|
||||||
|
//
|
||||||
|
final signedContactResponse = await contactRequestInbox
|
||||||
|
.getProtobuf(SignedContactResponse.fromBuffer, forceRefresh: true);
|
||||||
|
if (signedContactResponse == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final contactResponseBytes =
|
||||||
|
Uint8List.fromList(signedContactResponse.contactResponse);
|
||||||
|
final contactResponse = ContactResponse.fromBuffer(contactResponseBytes);
|
||||||
|
final contactIdentityMasterRecordKey = proto.TypedKeyProto.fromProto(
|
||||||
|
contactResponse.identityMasterRecordKey);
|
||||||
|
final cs = await pool.veilid.getCryptoSystem(
|
||||||
|
contactInvitationRecord.contactRequestInbox.recordKey.kind);
|
||||||
|
|
||||||
|
// Fetch the remote contact's account master
|
||||||
|
final contactIdentityMaster = await openIdentityMaster(
|
||||||
|
identityMasterRecordKey: contactIdentityMasterRecordKey);
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
final signature = proto.SignatureProto.fromProto(
|
||||||
|
signedContactResponse.identitySignature);
|
||||||
|
try {
|
||||||
|
await cs.verify(contactIdentityMaster.identityPublicKey,
|
||||||
|
contactResponseBytes, signature);
|
||||||
|
} on Exception catch (e) {
|
||||||
|
log.error('Bad identity used, failed to verify: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return contactResponse.accept;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (acceptReject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add contact if accepted
|
||||||
|
if (acceptReject) {
|
||||||
|
//
|
||||||
|
await deleteContactInvitation(
|
||||||
|
accepted: true,
|
||||||
|
activeAccountInfo: activeAccountInfo,
|
||||||
|
contactInvitationRecord: contactInvitationRecord);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
await deleteContactInvitation(
|
||||||
|
accepted: false,
|
||||||
|
activeAccountInfo: activeAccountInfo,
|
||||||
|
contactInvitationRecord: contactInvitationRecord);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
log.error('Exception in checkAcceptRejectContact: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteContactInvitation(
|
||||||
|
{required bool accepted,
|
||||||
|
required ActiveAccountInfo activeAccountInfo,
|
||||||
|
required ContactInvitationRecord contactInvitationRecord}) async {
|
||||||
final pool = await DHTRecordPool.instance();
|
final pool = await DHTRecordPool.instance();
|
||||||
final accountRecordKey =
|
final accountRecordKey =
|
||||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
@ -53,12 +134,18 @@ Future<void> deleteContactInvitation(
|
|||||||
proto.OwnedDHTRecordPointerProto.fromProto(
|
proto.OwnedDHTRecordPointerProto.fromProto(
|
||||||
contactInvitationRecord.contactRequestInbox),
|
contactInvitationRecord.contactRequestInbox),
|
||||||
parent: accountRecordKey))
|
parent: accountRecordKey))
|
||||||
.delete();
|
.scope((contactRequestInbox) async {
|
||||||
await (await pool.openOwned(
|
// Wipe out old invitation so it shows up as invalid
|
||||||
proto.OwnedDHTRecordPointerProto.fromProto(
|
await contactRequestInbox.tryWriteBytes(Uint8List(0));
|
||||||
contactInvitationRecord.localConversation),
|
await contactRequestInbox.delete();
|
||||||
parent: accountRecordKey))
|
});
|
||||||
.delete();
|
if (!accepted) {
|
||||||
|
await (await pool.openOwned(
|
||||||
|
proto.OwnedDHTRecordPointerProto.fromProto(
|
||||||
|
contactInvitationRecord.localConversation),
|
||||||
|
parent: accountRecordKey))
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +342,7 @@ Future<void> acceptContactInvitation(ActiveAccountInfo activeAccountInfo,
|
|||||||
// xxx
|
// xxx
|
||||||
final contactResponse = ContactResponse()
|
final contactResponse = ContactResponse()
|
||||||
..accept = false
|
..accept = false
|
||||||
..accountMasterRecordKey = activeAccountInfo
|
..identityMasterRecordKey = activeAccountInfo
|
||||||
.localAccount.identityMaster.masterRecordKey
|
.localAccount.identityMaster.masterRecordKey
|
||||||
.toProto();
|
.toProto();
|
||||||
final contactResponseBytes = contactResponse.writeToBuffer();
|
final contactResponseBytes = contactResponse.writeToBuffer();
|
||||||
@ -290,7 +377,7 @@ Future<void> rejectContactInvitation(ActiveAccountInfo activeAccountInfo,
|
|||||||
|
|
||||||
final contactResponse = ContactResponse()
|
final contactResponse = ContactResponse()
|
||||||
..accept = false
|
..accept = false
|
||||||
..accountMasterRecordKey = activeAccountInfo
|
..identityMasterRecordKey = activeAccountInfo
|
||||||
.localAccount.identityMaster.masterRecordKey
|
.localAccount.identityMaster.masterRecordKey
|
||||||
.toProto();
|
.toProto();
|
||||||
final contactResponseBytes = contactResponse.writeToBuffer();
|
final contactResponseBytes = contactResponse.writeToBuffer();
|
||||||
|
@ -2,7 +2,6 @@ import 'dart:async';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:protobuf/protobuf.dart';
|
import 'package:protobuf/protobuf.dart';
|
||||||
import 'package:veilid/veilid.dart';
|
|
||||||
|
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../veilid_support.dart';
|
import '../veilid_support.dart';
|
||||||
@ -51,7 +50,7 @@ class DHTRecord {
|
|||||||
}
|
}
|
||||||
final pool = await DHTRecordPool.instance();
|
final pool = await DHTRecordPool.instance();
|
||||||
await _routingContext.closeDHTRecord(_recordDescriptor.key);
|
await _routingContext.closeDHTRecord(_recordDescriptor.key);
|
||||||
pool.recordClosed(this);
|
pool.recordClosed(_recordDescriptor.key);
|
||||||
_open = false;
|
_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +70,9 @@ class DHTRecord {
|
|||||||
try {
|
try {
|
||||||
return await scopeFunction(this);
|
return await scopeFunction(this);
|
||||||
} finally {
|
} finally {
|
||||||
await close();
|
if (_valid) {
|
||||||
|
await close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,10 +80,14 @@ class DHTRecord {
|
|||||||
FutureOr<T> Function(DHTRecord) scopeFunction) async {
|
FutureOr<T> Function(DHTRecord) scopeFunction) async {
|
||||||
try {
|
try {
|
||||||
final out = await scopeFunction(this);
|
final out = await scopeFunction(this);
|
||||||
await close();
|
if (_valid && _open) {
|
||||||
|
await close();
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
} on Exception catch (_) {
|
} on Exception catch (_) {
|
||||||
await delete();
|
if (_valid) {
|
||||||
|
await delete();
|
||||||
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:mutex/mutex.dart';
|
||||||
|
|
||||||
import '../../log/loggy.dart';
|
import '../../log/loggy.dart';
|
||||||
import '../veilid_support.dart';
|
import '../veilid_support.dart';
|
||||||
@ -38,14 +39,14 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
DHTRecordPool._(Veilid veilid, VeilidRoutingContext routingContext)
|
DHTRecordPool._(Veilid veilid, VeilidRoutingContext routingContext)
|
||||||
: _state = DHTRecordPoolAllocations(
|
: _state = DHTRecordPoolAllocations(
|
||||||
childrenByParent: IMap(), parentByChild: IMap()),
|
childrenByParent: IMap(), parentByChild: IMap()),
|
||||||
_opened = <TypedKey, DHTRecord>{},
|
_opened = <TypedKey, Mutex>{},
|
||||||
_routingContext = routingContext,
|
_routingContext = routingContext,
|
||||||
_veilid = veilid;
|
_veilid = veilid;
|
||||||
|
|
||||||
// Persistent DHT record list
|
// Persistent DHT record list
|
||||||
DHTRecordPoolAllocations _state;
|
DHTRecordPoolAllocations _state;
|
||||||
// Which DHT records are currently open
|
// Which DHT records are currently open
|
||||||
final Map<TypedKey, DHTRecord> _opened;
|
final Map<TypedKey, Mutex> _opened;
|
||||||
// Default routing context to use for new keys
|
// Default routing context to use for new keys
|
||||||
final VeilidRoutingContext _routingContext;
|
final VeilidRoutingContext _routingContext;
|
||||||
// Convenience accessor
|
// Convenience accessor
|
||||||
@ -89,14 +90,20 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
|
|
||||||
Veilid get veilid => _veilid;
|
Veilid get veilid => _veilid;
|
||||||
|
|
||||||
void _recordOpened(DHTRecord record) {
|
Future<void> _recordOpened(TypedKey key) async {
|
||||||
assert(!_opened.containsKey(record.key), 'record already opened');
|
// no race because dart is single threaded until async breaks
|
||||||
_opened[record.key] = record;
|
final m = _opened[key] ?? Mutex();
|
||||||
|
_opened[key] = m;
|
||||||
|
await m.acquire();
|
||||||
|
_opened[key] = m;
|
||||||
}
|
}
|
||||||
|
|
||||||
void recordClosed(DHTRecord record) {
|
void recordClosed(TypedKey key) {
|
||||||
assert(_opened.containsKey(record.key), 'record already closed');
|
final m = _opened.remove(key);
|
||||||
_opened.remove(record.key);
|
if (m == null) {
|
||||||
|
throw StateError('record already closed');
|
||||||
|
}
|
||||||
|
m.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteDeep(TypedKey parent) async {
|
Future<void> deleteDeep(TypedKey parent) async {
|
||||||
@ -191,7 +198,8 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
await _addDependency(parent, rec.key);
|
await _addDependency(parent, rec.key);
|
||||||
}
|
}
|
||||||
_recordOpened(rec);
|
|
||||||
|
await _recordOpened(rec.key);
|
||||||
|
|
||||||
return rec;
|
return rec;
|
||||||
}
|
}
|
||||||
@ -202,25 +210,32 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
TypedKey? parent,
|
TypedKey? parent,
|
||||||
int defaultSubkey = 0,
|
int defaultSubkey = 0,
|
||||||
DHTRecordCrypto? crypto}) async {
|
DHTRecordCrypto? crypto}) async {
|
||||||
// If we are opening a key that already exists
|
await _recordOpened(recordKey);
|
||||||
// make sure we are using the same parent if one was specified
|
|
||||||
final existingParent = _state.parentByChild[recordKey.toJson()];
|
|
||||||
assert(existingParent == parent, 'wrong parent for opened key');
|
|
||||||
|
|
||||||
// Open from the veilid api
|
late final DHTRecord rec;
|
||||||
final dhtctx = routingContext ?? _routingContext;
|
try {
|
||||||
final recordDescriptor = await dhtctx.openDHTRecord(recordKey, null);
|
// If we are opening a key that already exists
|
||||||
final rec = DHTRecord(
|
// make sure we are using the same parent if one was specified
|
||||||
routingContext: dhtctx,
|
final existingParent = _state.parentByChild[recordKey.toJson()];
|
||||||
recordDescriptor: recordDescriptor,
|
assert(existingParent == parent, 'wrong parent for opened key');
|
||||||
defaultSubkey: defaultSubkey,
|
|
||||||
crypto: crypto ?? const DHTRecordCryptoPublic());
|
|
||||||
|
|
||||||
// Register the dependency if specified
|
// Open from the veilid api
|
||||||
if (parent != null) {
|
final dhtctx = routingContext ?? _routingContext;
|
||||||
await _addDependency(parent, rec.key);
|
final recordDescriptor = await dhtctx.openDHTRecord(recordKey, null);
|
||||||
|
rec = DHTRecord(
|
||||||
|
routingContext: dhtctx,
|
||||||
|
recordDescriptor: recordDescriptor,
|
||||||
|
defaultSubkey: defaultSubkey,
|
||||||
|
crypto: crypto ?? const DHTRecordCryptoPublic());
|
||||||
|
|
||||||
|
// Register the dependency if specified
|
||||||
|
if (parent != null) {
|
||||||
|
await _addDependency(parent, rec.key);
|
||||||
|
}
|
||||||
|
} on Exception catch (_) {
|
||||||
|
recordClosed(recordKey);
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
_recordOpened(rec);
|
|
||||||
|
|
||||||
return rec;
|
return rec;
|
||||||
}
|
}
|
||||||
@ -234,28 +249,35 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
int defaultSubkey = 0,
|
int defaultSubkey = 0,
|
||||||
DHTRecordCrypto? crypto,
|
DHTRecordCrypto? crypto,
|
||||||
}) async {
|
}) async {
|
||||||
// If we are opening a key that already exists
|
await _recordOpened(recordKey);
|
||||||
// make sure we are using the same parent if one was specified
|
|
||||||
final existingParent = _state.parentByChild[recordKey.toJson()];
|
|
||||||
assert(existingParent == parent, 'wrong parent for opened key');
|
|
||||||
|
|
||||||
// Open from the veilid api
|
late final DHTRecord rec;
|
||||||
final dhtctx = routingContext ?? _routingContext;
|
try {
|
||||||
final recordDescriptor = await dhtctx.openDHTRecord(recordKey, writer);
|
// If we are opening a key that already exists
|
||||||
final rec = DHTRecord(
|
// make sure we are using the same parent if one was specified
|
||||||
routingContext: dhtctx,
|
final existingParent = _state.parentByChild[recordKey.toJson()];
|
||||||
recordDescriptor: recordDescriptor,
|
assert(existingParent == parent, 'wrong parent for opened key');
|
||||||
defaultSubkey: defaultSubkey,
|
|
||||||
writer: writer,
|
|
||||||
crypto: crypto ??
|
|
||||||
await DHTRecordCryptoPrivate.fromTypedKeyPair(
|
|
||||||
TypedKeyPair.fromKeyPair(recordKey.kind, writer)));
|
|
||||||
|
|
||||||
// Register the dependency if specified
|
// Open from the veilid api
|
||||||
if (parent != null) {
|
final dhtctx = routingContext ?? _routingContext;
|
||||||
await _addDependency(parent, rec.key);
|
final recordDescriptor = await dhtctx.openDHTRecord(recordKey, writer);
|
||||||
|
rec = DHTRecord(
|
||||||
|
routingContext: dhtctx,
|
||||||
|
recordDescriptor: recordDescriptor,
|
||||||
|
defaultSubkey: defaultSubkey,
|
||||||
|
writer: writer,
|
||||||
|
crypto: crypto ??
|
||||||
|
await DHTRecordCryptoPrivate.fromTypedKeyPair(
|
||||||
|
TypedKeyPair.fromKeyPair(recordKey.kind, writer)));
|
||||||
|
|
||||||
|
// Register the dependency if specified
|
||||||
|
if (parent != null) {
|
||||||
|
await _addDependency(parent, rec.key);
|
||||||
|
}
|
||||||
|
} on Exception catch (_) {
|
||||||
|
recordClosed(recordKey);
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
_recordOpened(rec);
|
|
||||||
|
|
||||||
return rec;
|
return rec;
|
||||||
}
|
}
|
||||||
|
@ -781,6 +781,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.8"
|
version: "2.7.8"
|
||||||
|
mutex:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: mutex
|
||||||
|
sha256: "03116a4e46282a671b46c12de649d72c0ed18188ffe12a8d0fc63e83f4ad88f4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
octo_image:
|
octo_image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -44,6 +44,7 @@ dependencies:
|
|||||||
json_annotation: ^4.8.1
|
json_annotation: ^4.8.1
|
||||||
loggy: ^2.0.3
|
loggy: ^2.0.3
|
||||||
motion_toast: ^2.7.8
|
motion_toast: ^2.7.8
|
||||||
|
mutex: ^3.0.1
|
||||||
path: ^1.8.2
|
path: ^1.8.2
|
||||||
path_provider: ^2.0.11
|
path_provider: ^2.0.11
|
||||||
pinput: ^2.3.0
|
pinput: ^2.3.0
|
||||||
|
Loading…
Reference in New Issue
Block a user