mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04:00
watch value fix, invitation fix, logging
This commit is contained in:
parent
b3e9cbd4f3
commit
8335e36876
@ -73,13 +73,9 @@ class ValidContactInvitation {
|
||||
..identitySignature = identitySignature.toProto();
|
||||
|
||||
// Write the acceptance to the inbox
|
||||
if (await contactRequestInbox.tryWriteProtobuf(
|
||||
proto.SignedContactResponse.fromBuffer,
|
||||
signedContactResponse,
|
||||
subkey: 1) !=
|
||||
null) {
|
||||
throw Exception('failed to accept contact invitation');
|
||||
}
|
||||
await contactRequestInbox
|
||||
.eventualWriteProtobuf(signedContactResponse, subkey: 1);
|
||||
|
||||
return AcceptedContact(
|
||||
remoteProfile: _contactRequestPrivate.profile,
|
||||
remoteIdentity: _contactIdentityMaster,
|
||||
@ -129,13 +125,8 @@ class ValidContactInvitation {
|
||||
..identitySignature = identitySignature.toProto();
|
||||
|
||||
// Write the rejection to the inbox
|
||||
if (await contactRequestInbox.tryWriteProtobuf(
|
||||
proto.SignedContactResponse.fromBuffer, signedContactResponse,
|
||||
subkey: 1) !=
|
||||
null) {
|
||||
log.error('failed to reject contact invitation');
|
||||
return false;
|
||||
}
|
||||
await contactRequestInbox.eventualWriteProtobuf(signedContactResponse,
|
||||
subkey: 1);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../contacts/contacts.dart';
|
||||
@ -222,6 +223,16 @@ class InvitationDialogState extends State<InvitationDialog> {
|
||||
_validInvitation = null;
|
||||
widget.onValidationFailed();
|
||||
});
|
||||
} on VeilidAPIException {
|
||||
final errorText = translate('invitation_dialog.invalid_invitation');
|
||||
if (mounted) {
|
||||
showErrorToast(context, errorText);
|
||||
}
|
||||
setState(() {
|
||||
_isValidating = false;
|
||||
_validInvitation = null;
|
||||
widget.onValidationFailed();
|
||||
});
|
||||
} on Exception catch (e) {
|
||||
log.debug('exception: $e', e);
|
||||
setState(() {
|
||||
@ -233,6 +244,48 @@ class InvitationDialogState extends State<InvitationDialog> {
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildPreAccept() => <Widget>[
|
||||
if (!_isValidating && _validInvitation == null)
|
||||
widget.buildInviteControl(context, this, _validateInviteData),
|
||||
if (_isValidating)
|
||||
Column(children: [
|
||||
Text(translate('invitation_dialog.validating'))
|
||||
.paddingLTRB(0, 0, 0, 16),
|
||||
buildProgressIndicator().paddingAll(16),
|
||||
]).toCenter(),
|
||||
if (_validInvitation == null &&
|
||||
!_isValidating &&
|
||||
widget.inviteControlIsValid())
|
||||
Column(children: [
|
||||
Text(translate('invitation_dialog.invalid_invitation')),
|
||||
const Icon(Icons.error).paddingAll(16)
|
||||
]).toCenter(),
|
||||
if (_validInvitation != null && !_isValidating)
|
||||
Column(children: [
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxHeight: 64),
|
||||
width: double.infinity,
|
||||
child:
|
||||
ProfileWidget(profile: _validInvitation!.remoteProfile))
|
||||
.paddingLTRB(0, 0, 0, 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.check_circle),
|
||||
label: Text(translate('button.accept')),
|
||||
onPressed: _onAccept,
|
||||
).paddingLTRB(0, 0, 8, 0),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.cancel),
|
||||
label: Text(translate('button.reject')),
|
||||
onPressed: _onReject,
|
||||
).paddingLTRB(8, 0, 0, 0)
|
||||
],
|
||||
),
|
||||
])
|
||||
];
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
@ -240,63 +293,20 @@ class InvitationDialogState extends State<InvitationDialog> {
|
||||
// final scale = theme.extension<ScaleScheme>()!;
|
||||
// final textTheme = theme.textTheme;
|
||||
// final height = MediaQuery.of(context).size.height;
|
||||
final dismissible = !_isAccepting && !_isValidating;
|
||||
|
||||
if (_isAccepting) {
|
||||
return SizedBox(
|
||||
height: 300,
|
||||
width: 300,
|
||||
child: buildProgressIndicator().toCenter())
|
||||
.paddingAll(16);
|
||||
}
|
||||
return ConstrainedBox(
|
||||
final dialog = ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 400, maxWidth: 400),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
widget.buildInviteControl(context, this, _validateInviteData),
|
||||
if (_isValidating)
|
||||
Column(children: [
|
||||
Text(translate('invitation_dialog.validating'))
|
||||
.paddingLTRB(0, 0, 0, 16),
|
||||
buildProgressIndicator().paddingAll(16),
|
||||
]).toCenter(),
|
||||
if (_validInvitation == null &&
|
||||
!_isValidating &&
|
||||
widget.inviteControlIsValid())
|
||||
Column(children: [
|
||||
Text(translate('invitation_dialog.invalid_invitation')),
|
||||
const Icon(Icons.error)
|
||||
]).paddingAll(16).toCenter(),
|
||||
if (_validInvitation != null && !_isValidating)
|
||||
Column(children: [
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxHeight: 64),
|
||||
width: double.infinity,
|
||||
child: ProfileWidget(
|
||||
profile: _validInvitation!.remoteProfile))
|
||||
.paddingLTRB(0, 0, 0, 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.check_circle),
|
||||
label: Text(translate('button.accept')),
|
||||
onPressed: _onAccept,
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.cancel),
|
||||
label: Text(translate('button.reject')),
|
||||
onPressed: _onReject,
|
||||
)
|
||||
],
|
||||
),
|
||||
])
|
||||
]),
|
||||
children: _isAccepting
|
||||
? [buildProgressIndicator().paddingAll(16)]
|
||||
: _buildPreAccept()),
|
||||
),
|
||||
);
|
||||
return PopControl(dismissible: dismissible, child: dialog);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -17,10 +17,13 @@ class PasteInvitationDialog extends StatefulWidget {
|
||||
PasteInvitationDialogState createState() => PasteInvitationDialogState();
|
||||
|
||||
static Future<void> show(BuildContext context) async {
|
||||
await StyledDialog.show<void>(
|
||||
final modalContext = context;
|
||||
|
||||
await showPopControlDialog<void>(
|
||||
context: context,
|
||||
title: translate('paste_invitation_dialog.title'),
|
||||
child: PasteInvitationDialog(modalContext: context));
|
||||
builder: (context) => StyledDialog(
|
||||
title: translate('paste_invitation_dialog.title'),
|
||||
child: PasteInvitationDialog(modalContext: modalContext)));
|
||||
}
|
||||
|
||||
final BuildContext modalContext;
|
||||
@ -67,8 +70,13 @@ class PasteInvitationDialogState extends State<PasteInvitationDialog> {
|
||||
.sublist(firstline, lastline)
|
||||
.join()
|
||||
.replaceAll(RegExp(r'[^A-Za-z0-9\-_]'), '');
|
||||
final inviteData = base64UrlNoPadDecode(inviteDataBase64);
|
||||
|
||||
var inviteData = Uint8List(0);
|
||||
try {
|
||||
inviteData = base64UrlNoPadDecode(inviteDataBase64);
|
||||
} on Exception {
|
||||
//
|
||||
}
|
||||
await validateInviteData(inviteData: inviteData);
|
||||
}
|
||||
|
||||
@ -105,7 +113,7 @@ class PasteInvitationDialogState extends State<PasteInvitationDialog> {
|
||||
return Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text(
|
||||
translate('paste_invitation_dialog.paste_invite_here'),
|
||||
).paddingLTRB(0, 0, 0, 8),
|
||||
).paddingLTRB(0, 0, 0, 16),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
child: TextField(
|
||||
|
@ -110,10 +110,12 @@ class ScanInvitationDialog extends StatefulWidget {
|
||||
ScanInvitationDialogState createState() => ScanInvitationDialogState();
|
||||
|
||||
static Future<void> show(BuildContext context) async {
|
||||
await StyledDialog.show<void>(
|
||||
final modalContext = context;
|
||||
await showPopControlDialog<void>(
|
||||
context: context,
|
||||
title: translate('scan_invitation_dialog.title'),
|
||||
child: ScanInvitationDialog(modalContext: context));
|
||||
builder: (context) => StyledDialog(
|
||||
title: translate('scan_invitation_dialog.title'),
|
||||
child: ScanInvitationDialog(modalContext: modalContext)));
|
||||
}
|
||||
|
||||
final BuildContext modalContext;
|
||||
|
@ -24,7 +24,8 @@ class VeilidChatGlobalInit {
|
||||
await ProcessorRepository.instance.startup();
|
||||
|
||||
// DHT Record Pool
|
||||
await DHTRecordPool.init();
|
||||
await DHTRecordPool.init(
|
||||
logger: (message) => log.debug('DHTRecordPool: $message'));
|
||||
}
|
||||
|
||||
// Initialize repositories
|
||||
|
@ -22,7 +22,9 @@ class PopControl extends StatelessWidget {
|
||||
|
||||
final route = ModalRoute.of(context);
|
||||
if (route != null && route is PopControlDialogRoute) {
|
||||
route.barrierDismissible = dismissible;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
route.barrierDismissible = dismissible;
|
||||
});
|
||||
}
|
||||
|
||||
return PopScope(
|
||||
@ -65,6 +67,7 @@ class PopControlDialogRoute<T> extends DialogRoute<T> {
|
||||
|
||||
set barrierDismissible(bool d) {
|
||||
_barrierDismissible = d;
|
||||
changedInternalState();
|
||||
}
|
||||
|
||||
bool _barrierDismissible;
|
||||
|
@ -17,6 +17,8 @@ part 'dht_record.dart';
|
||||
const int watchBackoffMultiplier = 2;
|
||||
const int watchBackoffMax = 30;
|
||||
|
||||
typedef DHTRecordPoolLogger = void Function(String message);
|
||||
|
||||
/// Record pool that managed DHTRecords and allows for tagged deletion
|
||||
/// String versions of keys due to IMap<> json unsupported in key
|
||||
@freezed
|
||||
@ -90,6 +92,12 @@ class OpenedRecordInfo {
|
||||
defaultRoutingContext: defaultRoutingContext);
|
||||
SharedDHTRecordData shared;
|
||||
Set<DHTRecord> records = {};
|
||||
|
||||
String get debugNames {
|
||||
final r = records.toList()
|
||||
..sort((a, b) => a.key.toString().compareTo(b.key.toString()));
|
||||
return '[${r.map((x) => x.debugName).join(',')}]';
|
||||
}
|
||||
}
|
||||
|
||||
class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
||||
@ -100,6 +108,9 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
||||
_routingContext = routingContext,
|
||||
_veilid = veilid;
|
||||
|
||||
// Logger
|
||||
DHTRecordPoolLogger? _logger;
|
||||
|
||||
// Persistent DHT record list
|
||||
DHTRecordPoolAllocations _state;
|
||||
// Create/open Mutex
|
||||
@ -136,15 +147,21 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
||||
|
||||
static DHTRecordPool get instance => _singleton!;
|
||||
|
||||
static Future<void> init() async {
|
||||
static Future<void> init({DHTRecordPoolLogger? logger}) async {
|
||||
final routingContext = await Veilid.instance.routingContext();
|
||||
final globalPool = DHTRecordPool._(Veilid.instance, routingContext);
|
||||
globalPool._state = await globalPool.load();
|
||||
globalPool
|
||||
.._logger = logger
|
||||
.._state = await globalPool.load();
|
||||
_singleton = globalPool;
|
||||
}
|
||||
|
||||
Veilid get veilid => _veilid;
|
||||
|
||||
void log(String message) {
|
||||
_logger?.call(message);
|
||||
}
|
||||
|
||||
Future<OpenedRecordInfo> _recordCreateInner(
|
||||
{required String debugName,
|
||||
required VeilidRoutingContext dhtctx,
|
||||
@ -156,6 +173,8 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
||||
// Create the record
|
||||
final recordDescriptor = await dhtctx.createDHTRecord(schema);
|
||||
|
||||
log('createDHTRecord: debugName=$debugName key=${recordDescriptor.key}');
|
||||
|
||||
// Reopen if a writer is specified to ensure
|
||||
// we switch the default writer
|
||||
if (writer != null) {
|
||||
@ -185,6 +204,8 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
||||
TypedKey? parent}) async {
|
||||
assert(_mutex.isLocked, 'should be locked here');
|
||||
|
||||
log('openDHTRecord: debugName=$debugName key=$recordKey');
|
||||
|
||||
// If we are opening a key that already exists
|
||||
// make sure we are using the same parent if one was specified
|
||||
_validateParentInner(parent, recordKey);
|
||||
@ -238,6 +259,9 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
||||
Future<void> _recordClosed(DHTRecord record) async {
|
||||
await _mutex.protect(() async {
|
||||
final key = record.key;
|
||||
|
||||
log('closeDHTRecord: debugName=${record.debugName} key=$key');
|
||||
|
||||
final openedRecordInfo = _opened[key];
|
||||
if (openedRecordInfo == null ||
|
||||
!openedRecordInfo.records.remove(record)) {
|
||||
@ -284,6 +308,8 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
||||
}
|
||||
|
||||
Future<void> _deleteInner(TypedKey recordKey) async {
|
||||
log('deleteDHTRecord: key=$recordKey');
|
||||
|
||||
// Remove this child from parents
|
||||
await _removeDependenciesInner([recordKey]);
|
||||
await _routingContext.deleteDHTRecord(recordKey);
|
||||
@ -676,9 +702,14 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
||||
var success = false;
|
||||
try {
|
||||
success = await dhtctx.cancelDHTWatch(openedRecordKey);
|
||||
|
||||
log('cancelDHTWatch: key=$openedRecordKey, success=$success, '
|
||||
'debugNames=${openedRecordInfo.debugNames}');
|
||||
|
||||
openedRecordInfo.shared.needsWatchStateUpdate = false;
|
||||
} on VeilidAPIException {
|
||||
} on VeilidAPIException catch (e) {
|
||||
// Failed to cancel DHT watch, try again next tick
|
||||
log('Exception in watch cancel: $e');
|
||||
}
|
||||
return success;
|
||||
});
|
||||
@ -687,12 +718,21 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
||||
// Record needs new watch
|
||||
var success = false;
|
||||
try {
|
||||
final subkeys = watchState.subkeys?.toList();
|
||||
final count = watchState.count;
|
||||
final expiration = watchState.expiration;
|
||||
|
||||
final realExpiration = await dhtctx.watchDHTValues(
|
||||
openedRecordKey,
|
||||
subkeys: watchState.subkeys?.toList(),
|
||||
count: watchState.count,
|
||||
expiration: watchState.expiration);
|
||||
|
||||
log('watchDHTValues: key=$openedRecordKey, subkeys=$subkeys, '
|
||||
'count=$count, expiration=$expiration, '
|
||||
'realExpiration=$realExpiration, '
|
||||
'debugNames=${openedRecordInfo.debugNames}');
|
||||
|
||||
// Update watch states with real expiration
|
||||
if (realExpiration.value != BigInt.zero) {
|
||||
openedRecordInfo.shared.needsWatchStateUpdate = false;
|
||||
@ -700,8 +740,9 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
||||
openedRecordInfo.records, realExpiration);
|
||||
success = true;
|
||||
}
|
||||
} on VeilidAPIException {
|
||||
} on VeilidAPIException catch (e) {
|
||||
// Failed to cancel DHT watch, try again next tick
|
||||
log('Exception in watch update: $e');
|
||||
}
|
||||
return success;
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user