fix head issue and refactor

This commit is contained in:
Christien Rioux 2024-04-01 09:04:54 -04:00
parent c48305cba1
commit 48c9c67ab8
20 changed files with 229 additions and 327 deletions

View File

@ -6,8 +6,8 @@ import '../../proto/proto.dart' as proto;
class AccountRecordCubit extends DefaultDHTRecordCubit<proto.Account> {
AccountRecordCubit({
required super.record,
}) : super.value(decodeState: proto.Account.fromBuffer);
required super.open,
}) : super(decodeState: proto.Account.fromBuffer);
@override
Future<void> close() async {

View File

@ -11,7 +11,7 @@ class ActiveAccountInfo {
const ActiveAccountInfo({
required this.localAccount,
required this.userLogin,
required this.accountRecord,
//required this.accountRecord,
});
//
@ -41,5 +41,5 @@ class ActiveAccountInfo {
//
final LocalAccount localAccount;
final UserLogin userLogin;
final DHTRecord accountRecord;
//final DHTRecord accountRecord;
}

View File

@ -49,7 +49,6 @@ class AccountRepository {
final TableDBValue<IList<UserLogin>> _userLogins;
final TableDBValue<TypedKey?> _activeLocalAccount;
final StreamController<AccountRepositoryChange> _streamController;
final Map<TypedKey, DHTRecord> _openedAccountRecords = {};
//////////////////////////////////////////////////////////////
/// Singleton initialization
@ -60,11 +59,10 @@ class AccountRepository {
await _localAccounts.get();
await _userLogins.get();
await _activeLocalAccount.get();
await _openLoggedInDHTRecords();
}
Future<void> close() async {
await _closeLoggedInDHTRecords();
// ???
}
//////////////////////////////////////////////////////////////
@ -139,25 +137,12 @@ class AccountRepository {
activeAccountInfo: null);
}
// Pull the account DHT key, decode it and return it
final accountRecord =
_getAccountRecord(userLogin.accountRecordInfo.accountRecord.recordKey);
if (accountRecord == null) {
// Account could not be read or decrypted from DHT
return AccountInfo(
status: AccountInfoStatus.accountInvalid,
active: active,
activeAccountInfo: null);
}
// Got account, decrypted and decoded
return AccountInfo(
status: AccountInfoStatus.accountReady,
active: active,
activeAccountInfo: ActiveAccountInfo(
localAccount: localAccount,
userLogin: userLogin,
accountRecord: accountRecord),
activeAccountInfo:
ActiveAccountInfo(localAccount: localAccount, userLogin: userLogin),
);
}
@ -344,9 +329,6 @@ class AccountRepository {
await _userLogins.set(newUserLogins);
await _activeLocalAccount.set(identityMaster.masterRecordKey);
// Ensure all logins are opened
await _openLoggedInDHTRecords();
_streamController
..add(AccountRepositoryChange.userLogins)
..add(AccountRepositoryChange.activeLocalAccount);
@ -395,12 +377,6 @@ class AccountRepository {
return;
}
// Close DHT records for this account
final accountRecordKey =
logoutUserLogin.accountRecordInfo.accountRecord.recordKey;
final accountRecord = _openedAccountRecords.remove(accountRecordKey);
await accountRecord?.close();
// Remove user from active logins list
final newUserLogins = (await _userLogins.get())
.removeWhere((ul) => ul.accountMasterRecordKey == logoutUser);
@ -408,32 +384,7 @@ class AccountRepository {
_streamController.add(AccountRepositoryChange.userLogins);
}
Future<void> _openLoggedInDHTRecords() async {
// For all user logins if they arent open yet
final userLogins = await _userLogins.get();
for (final userLogin in userLogins) {
await _openAccountRecord(userLogin);
}
}
Future<void> _closeLoggedInDHTRecords() async {
final userLogins = await _userLogins.get();
for (final userLogin in userLogins) {
//// Account record key /////////////////////////////
final accountRecordKey =
userLogin.accountRecordInfo.accountRecord.recordKey;
await _closeAccountRecord(accountRecordKey);
}
}
Future<DHTRecord> _openAccountRecord(UserLogin userLogin) async {
final accountRecordKey =
userLogin.accountRecordInfo.accountRecord.recordKey;
final existingAccountRecord = _openedAccountRecords[accountRecordKey];
if (existingAccountRecord != null) {
return existingAccountRecord;
}
Future<DHTRecord> openAccountRecord(UserLogin userLogin) async {
final localAccount = fetchLocalAccount(userLogin.accountMasterRecordKey)!;
// Record not yet open, do it
@ -442,19 +393,6 @@ class AccountRepository {
userLogin.accountRecordInfo.accountRecord,
parent: localAccount.identityMaster.identityRecordKey);
_openedAccountRecords[accountRecordKey] = record;
// Watch the record's only (default) key
await record.watch();
return record;
}
DHTRecord? _getAccountRecord(TypedKey accountRecordKey) =>
_openedAccountRecords[accountRecordKey];
Future<void> _closeAccountRecord(TypedKey accountRecordKey) async {
final accountRecord = _openedAccountRecords.remove(accountRecordKey);
await accountRecord?.close();
}
}

View File

@ -154,7 +154,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
}
Future<void> _mergeMessagesInner(
{required DHTShortArray reconciledMessages,
{required DHTShortArrayWrite reconciledMessagesWriter,
required IList<proto.Message> messages}) async {
// Ensure remoteMessages is sorted by timestamp
final newMessages = messages
@ -162,8 +162,12 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
.removeDuplicates();
// Existing messages will always be sorted by timestamp so merging is easy
final existingMessages =
_reconciledChatMessagesCubit!.state.state.data!.value.toList();
final existingMessages = await reconciledMessagesWriter
.getAllItemsProtobuf(proto.Message.fromBuffer);
if (existingMessages == null) {
throw Exception(
'Could not load existing reconciled messages at this time');
}
var ePos = 0;
var nPos = 0;
@ -180,7 +184,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// New message belongs here
// Insert into dht backing array
await reconciledMessages.tryInsertItem(
await reconciledMessagesWriter.tryInsertItem(
ePos, newMessage.writeToBuffer());
// Insert into local copy as well for this operation
existingMessages.insert(ePos, newMessage);
@ -202,7 +206,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
final newMessage = newMessages[nPos];
// Append to dht backing array
await reconciledMessages.tryAddItem(newMessage.writeToBuffer());
await reconciledMessagesWriter.tryAddItem(newMessage.writeToBuffer());
// Insert into local copy as well for this operation
existingMessages.add(newMessage);
@ -215,16 +219,17 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
final reconciledChatMessagesCubit = _reconciledChatMessagesCubit!;
// Merge remote and local messages into the reconciled chat log
await reconciledChatMessagesCubit.operate((reconciledMessages) async {
await reconciledChatMessagesCubit
.operateWrite((reconciledMessagesWriter) async {
// xxx for now, keep two lists, but can probable simplify this out soon
if (entry.localMessages != null) {
await _mergeMessagesInner(
reconciledMessages: reconciledMessages,
reconciledMessagesWriter: reconciledMessagesWriter,
messages: entry.localMessages!);
}
if (entry.remoteMessages != null) {
await _mergeMessagesInner(
reconciledMessages: reconciledMessages,
reconciledMessagesWriter: reconciledMessagesWriter,
messages: entry.remoteMessages!);
}
});
@ -244,8 +249,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
}
Future<void> addMessage({required proto.Message message}) async {
await _localMessagesCubit!.operate(
(shortArray) => shortArray.tryAddItem(message.writeToBuffer()));
await _localMessagesCubit!
.operateWrite((writer) => writer.tryAddItem(message.writeToBuffer()));
}
final ActiveAccountInfo _activeAccountInfo;

View File

@ -41,13 +41,13 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
}) async {
// Add Chat to account's list
// if this fails, don't keep retrying, user can try again later
await operate((shortArray) async {
await operateWrite((writer) async {
final remoteConversationRecordKeyProto =
remoteConversationRecordKey.toProto();
// See if we have added this chat already
for (var i = 0; i < shortArray.length; i++) {
final cbuf = await shortArray.getItem(i);
for (var i = 0; i < writer.length; i++) {
final cbuf = await writer.getItem(i);
if (cbuf == null) {
throw Exception('Failed to get chat');
}
@ -72,7 +72,7 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
..reconciledChatRecord = reconciledChatRecord.toProto();
// Add chat
final added = await shortArray.tryAddItem(chat.writeToBuffer());
final added = await writer.tryAddItem(chat.writeToBuffer());
if (!added) {
throw Exception('Failed to add chat');
}
@ -86,19 +86,19 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
// Remove Chat from account's list
// if this fails, don't keep retrying, user can try again later
final deletedItem = await operate((shortArray) async {
final (deletedItem, success) = await operateWrite((writer) async {
if (activeChatCubit.state == remoteConversationRecordKey) {
activeChatCubit.setActiveChat(null);
}
for (var i = 0; i < shortArray.length; i++) {
final cbuf = await shortArray.getItem(i);
for (var i = 0; i < writer.length; i++) {
final cbuf = await writer.getItem(i);
if (cbuf == null) {
throw Exception('Failed to get chat');
}
final c = proto.Chat.fromBuffer(cbuf);
if (c.remoteConversationRecordKey == remoteConversationKey) {
// Found the right chat
if (await shortArray.tryRemoveItem(i) != null) {
if (await writer.tryRemoveItem(i) != null) {
return c;
}
return null;
@ -106,7 +106,7 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
}
return null;
});
if (deletedItem != null) {
if (success && deletedItem != null) {
try {
await DHTRecordPool.instance
.delete(deletedItem.reconciledChatRecord.toVeilid().recordKey);

View File

@ -139,8 +139,8 @@ class ContactInvitationListCubit
// Add ContactInvitationRecord to account's list
// if this fails, don't keep retrying, user can try again later
await operate((shortArray) async {
if (await shortArray.tryAddItem(cinvrec.writeToBuffer()) == false) {
await operateWrite((writer) async {
if (await writer.tryAddItem(cinvrec.writeToBuffer()) == false) {
throw Exception('Failed to add contact invitation record');
}
});
@ -158,16 +158,16 @@ class ContactInvitationListCubit
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
// Remove ContactInvitationRecord from account's list
final deletedItem = await operate((shortArray) async {
for (var i = 0; i < shortArray.length; i++) {
final item = await shortArray.getItemProtobuf(
final (deletedItem, success) = await operateWrite((writer) async {
for (var i = 0; i < writer.length; i++) {
final item = await writer.getItemProtobuf(
proto.ContactInvitationRecord.fromBuffer, i);
if (item == null) {
throw Exception('Failed to get contact invitation record');
}
if (item.contactRequestInbox.recordKey.toVeilid() ==
contactRequestInboxRecordKey) {
if (await shortArray.tryRemoveItem(i) != null) {
if (await writer.tryRemoveItem(i) != null) {
return item;
}
return null;
@ -176,7 +176,7 @@ class ContactInvitationListCubit
return null;
});
if (deletedItem != null) {
if (success && deletedItem != null) {
// Delete the contact request inbox
final contactRequestInbox = deletedItem.contactRequestInbox.toVeilid();
await (await pool.openOwned(contactRequestInbox,

View File

@ -14,11 +14,11 @@ class ContactRequestInboxCubit
contactInvitationRecord: contactInvitationRecord),
decodeState: proto.SignedContactResponse.fromBuffer);
ContactRequestInboxCubit.value(
{required super.record,
required this.activeAccountInfo,
required this.contactInvitationRecord})
: super.value(decodeState: proto.SignedContactResponse.fromBuffer);
// ContactRequestInboxCubit.value(
// {required super.record,
// required this.activeAccountInfo,
// required this.contactInvitationRecord})
// : super.value(decodeState: proto.SignedContactResponse.fromBuffer);
static Future<DHTRecord> _open(
{required ActiveAccountInfo activeAccountInfo,

View File

@ -23,7 +23,7 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
WaitingInvitationsBlocMapCubit(
{required this.activeAccountInfo, required this.account});
Future<void> addWaitingInvitation(
Future<void> _addWaitingInvitation(
{required proto.ContactInvitationRecord
contactInvitationRecord}) async =>
add(() => MapEntry(
@ -54,7 +54,7 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
@override
Future<void> updateState(TypedKey key, proto.ContactInvitationRecord value) =>
addWaitingInvitation(contactInvitationRecord: value);
_addWaitingInvitation(contactInvitationRecord: value);
////
final ActiveAccountInfo activeAccountInfo;

View File

@ -52,8 +52,8 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
// Add Contact to account's list
// if this fails, don't keep retrying, user can try again later
await operate((shortArray) async {
if (await shortArray.tryAddItem(contact.writeToBuffer()) == false) {
await operateWrite((writer) async {
if (!await writer.tryAddItem(contact.writeToBuffer())) {
throw Exception('Failed to add contact record');
}
});
@ -66,16 +66,15 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
contact.remoteConversationRecordKey.toVeilid();
// Remove Contact from account's list
final deletedItem = await operate((shortArray) async {
for (var i = 0; i < shortArray.length; i++) {
final item =
await shortArray.getItemProtobuf(proto.Contact.fromBuffer, i);
final (deletedItem, success) = await operateWrite((writer) async {
for (var i = 0; i < writer.length; i++) {
final item = await writer.getItemProtobuf(proto.Contact.fromBuffer, i);
if (item == null) {
throw Exception('Failed to get contact');
}
if (item.remoteConversationRecordKey ==
contact.remoteConversationRecordKey) {
if (await shortArray.tryRemoveItem(i) != null) {
if (await writer.tryRemoveItem(i) != null) {
return item;
}
return null;
@ -84,7 +83,7 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
return null;
});
if (deletedItem != null) {
if (success && deletedItem != null) {
try {
await pool.delete(localConversationKey);
} on Exception catch (e) {

View File

@ -41,31 +41,35 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
super(const AsyncValue.loading()) {
if (_localConversationRecordKey != null) {
Future.delayed(Duration.zero, () async {
final accountRecordKey = _activeAccountInfo
.userLogin.accountRecordInfo.accountRecord.recordKey;
await _setLocalConversation(() async {
final accountRecordKey = _activeAccountInfo
.userLogin.accountRecordInfo.accountRecord.recordKey;
// Open local record key if it is specified
final pool = DHTRecordPool.instance;
final crypto = await _cachedConversationCrypto();
final writer = _activeAccountInfo.conversationWriter;
final record = await pool.openWrite(
_localConversationRecordKey!, writer,
parent: accountRecordKey, crypto: crypto);
await _setLocalConversation(record);
// Open local record key if it is specified
final pool = DHTRecordPool.instance;
final crypto = await _cachedConversationCrypto();
final writer = _activeAccountInfo.conversationWriter;
final record = await pool.openWrite(
_localConversationRecordKey!, writer,
parent: accountRecordKey, crypto: crypto);
return record;
});
});
}
if (_remoteConversationRecordKey != null) {
Future.delayed(Duration.zero, () async {
final accountRecordKey = _activeAccountInfo
.userLogin.accountRecordInfo.accountRecord.recordKey;
await _setRemoteConversation(() async {
final accountRecordKey = _activeAccountInfo
.userLogin.accountRecordInfo.accountRecord.recordKey;
// Open remote record key if it is specified
final pool = DHTRecordPool.instance;
final crypto = await _cachedConversationCrypto();
final record = await pool.openRead(_remoteConversationRecordKey,
parent: accountRecordKey, crypto: crypto);
await _setRemoteConversation(record);
// Open remote record key if it is specified
final pool = DHTRecordPool.instance;
final crypto = await _cachedConversationCrypto();
final record = await pool.openRead(_remoteConversationRecordKey,
parent: accountRecordKey, crypto: crypto);
return record;
});
});
}
}
@ -74,6 +78,9 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
Future<void> close() async {
await _localSubscription?.cancel();
await _remoteSubscription?.cancel();
await _localConversationCubit?.close();
await _remoteConversationCubit?.close();
await super.close();
}
@ -122,24 +129,21 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
}
// Open local converation key
Future<void> _setLocalConversation(DHTRecord localConversationRecord) async {
Future<void> _setLocalConversation(Future<DHTRecord> Function() open) async {
assert(_localConversationCubit == null,
'shoud not set local conversation twice');
_localConversationCubit = DefaultDHTRecordCubit.value(
record: localConversationRecord,
decodeState: proto.Conversation.fromBuffer);
_localConversationCubit = DefaultDHTRecordCubit(
open: open, decodeState: proto.Conversation.fromBuffer);
_localSubscription =
_localConversationCubit!.stream.listen(updateLocalConversationState);
}
// Open remote converation key
Future<void> _setRemoteConversation(
DHTRecord remoteConversationRecord) async {
Future<void> _setRemoteConversation(Future<DHTRecord> Function() open) async {
assert(_remoteConversationCubit == null,
'shoud not set remote conversation twice');
_remoteConversationCubit = DefaultDHTRecordCubit.value(
record: remoteConversationRecord,
decodeState: proto.Conversation.fromBuffer);
_remoteConversationCubit = DefaultDHTRecordCubit(
open: open, decodeState: proto.Conversation.fromBuffer);
_remoteSubscription =
_remoteConversationCubit!.stream.listen(updateRemoteConversationState);
}
@ -215,7 +219,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
// If success, save the new local conversation record key in this object
_localConversationRecordKey = localConversationRecord.key;
await _setLocalConversation(localConversationRecord);
await _setLocalConversation(() async => localConversationRecord);
return out;
}

View File

@ -19,14 +19,11 @@ class HomeAccountReadyShell extends StatefulWidget {
// These must exist in order for the account to
// be considered 'ready' for this widget subtree
final activeLocalAccount = context.read<ActiveLocalAccountCubit>().state!;
final accountInfo =
AccountRepository.instance.getAccountInfo(activeLocalAccount);
final activeAccountInfo = accountInfo.activeAccountInfo!;
final activeAccountInfo = context.read<ActiveAccountInfo>();
final routerCubit = context.read<RouterCubit>();
return HomeAccountReadyShell._(
activeLocalAccount: activeLocalAccount,
accountInfo: accountInfo,
activeAccountInfo: activeAccountInfo,
routerCubit: routerCubit,
key: key,
@ -34,7 +31,6 @@ class HomeAccountReadyShell extends StatefulWidget {
}
const HomeAccountReadyShell._(
{required this.activeLocalAccount,
required this.accountInfo,
required this.activeAccountInfo,
required this.routerCubit,
required this.child,
@ -45,7 +41,6 @@ class HomeAccountReadyShell extends StatefulWidget {
final Widget child;
final TypedKey activeLocalAccount;
final AccountInfo accountInfo;
final ActiveAccountInfo activeAccountInfo;
final RouterCubit routerCubit;
@ -55,7 +50,6 @@ class HomeAccountReadyShell extends StatefulWidget {
properties
..add(DiagnosticsProperty<TypedKey>(
'activeLocalAccount', activeLocalAccount))
..add(DiagnosticsProperty<AccountInfo>('accountInfo', accountInfo))
..add(DiagnosticsProperty<ActiveAccountInfo>(
'activeAccountInfo', activeAccountInfo))
..add(DiagnosticsProperty<RouterCubit>('routerCubit', routerCubit));
@ -114,61 +108,52 @@ class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
}
@override
Widget build(BuildContext context) => Provider<ActiveAccountInfo>.value(
value: widget.activeAccountInfo,
child: BlocProvider(
create: (context) => AccountRecordCubit(
record: widget.activeAccountInfo.accountRecord),
child: Builder(builder: (context) {
final account =
context.watch<AccountRecordCubit>().state.data?.value;
if (account == null) {
return waitingPage();
}
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => ContactInvitationListCubit(
activeAccountInfo: widget.activeAccountInfo,
account: account)),
BlocProvider(
create: (context) => ContactListCubit(
activeAccountInfo: widget.activeAccountInfo,
account: account)),
BlocProvider(
create: (context) => ActiveChatCubit(null)
..withStateListen((event) {
widget.routerCubit.setHasActiveChat(event != null);
})),
BlocProvider(
create: (context) => ChatListCubit(
activeAccountInfo: widget.activeAccountInfo,
activeChatCubit: context.read<ActiveChatCubit>(),
account: account)),
BlocProvider(
create: (context) => ActiveConversationsBlocMapCubit(
activeAccountInfo: widget.activeAccountInfo,
contactListCubit: context.read<ContactListCubit>())
..followBloc(context.read<ChatListCubit>())),
BlocProvider(
create: (context) => ActiveSingleContactChatBlocMapCubit(
activeAccountInfo: widget.activeAccountInfo,
contactListCubit: context.read<ContactListCubit>(),
chatListCubit: context.read<ChatListCubit>())
..followBloc(
context.read<ActiveConversationsBlocMapCubit>())),
BlocProvider(
create: (context) => WaitingInvitationsBlocMapCubit(
activeAccountInfo: widget.activeAccountInfo,
account: account)
..followBloc(
context.read<ContactInvitationListCubit>()))
],
child: MultiBlocListener(listeners: [
BlocListener<WaitingInvitationsBlocMapCubit,
WaitingInvitationsBlocMapState>(
listener: _invitationStatusListener,
)
], child: widget.child));
})));
Widget build(BuildContext context) {
final account = context.watch<AccountRecordCubit>().state.data?.value;
if (account == null) {
return waitingPage();
}
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => ContactInvitationListCubit(
activeAccountInfo: widget.activeAccountInfo,
account: account)),
BlocProvider(
create: (context) => ContactListCubit(
activeAccountInfo: widget.activeAccountInfo,
account: account)),
BlocProvider(
create: (context) => ActiveChatCubit(null)
..withStateListen((event) {
widget.routerCubit.setHasActiveChat(event != null);
})),
BlocProvider(
create: (context) => ChatListCubit(
activeAccountInfo: widget.activeAccountInfo,
activeChatCubit: context.read<ActiveChatCubit>(),
account: account)),
BlocProvider(
create: (context) => ActiveConversationsBlocMapCubit(
activeAccountInfo: widget.activeAccountInfo,
contactListCubit: context.read<ContactListCubit>())
..followBloc(context.read<ChatListCubit>())),
BlocProvider(
create: (context) => ActiveSingleContactChatBlocMapCubit(
activeAccountInfo: widget.activeAccountInfo,
contactListCubit: context.read<ContactListCubit>(),
chatListCubit: context.read<ChatListCubit>())
..followBloc(context.read<ActiveConversationsBlocMapCubit>())),
BlocProvider(
create: (context) => WaitingInvitationsBlocMapCubit(
activeAccountInfo: widget.activeAccountInfo, account: account)
..followBloc(context.read<ContactInvitationListCubit>()))
],
child: MultiBlocListener(listeners: [
BlocListener<WaitingInvitationsBlocMapCubit,
WaitingInvitationsBlocMapState>(
listener: _invitationStatusListener,
)
], child: widget.child));
}
}

View File

@ -55,7 +55,9 @@ class HomeShellState extends State<HomeShell> {
value: accountInfo.activeAccountInfo!,
child: BlocProvider(
create: (context) => AccountRecordCubit(
record: accountInfo.activeAccountInfo!.accountRecord),
open: () async => AccountRepository.instance
.openAccountRecord(
accountInfo.activeAccountInfo!.userLogin)),
child: widget.accountReadyBuilder));
}
}

View File

@ -72,7 +72,7 @@ SPEC CHECKSUMS:
pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
smart_auth: b38e3ab4bfe089eacb1e233aca1a2340f96c28e9
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec

View File

@ -24,7 +24,8 @@ class SingleStatelessProcessor {
});
}
// Like update, but with a busy wrapper that clears once the updating is finished
// Like update, but with a busy wrapper that
// clears once the updating is finished
void busyUpdate<T, S>(
Future<void> Function(Future<void> Function(void Function(S))) busy,
Future<void> Function(void Function(S)) closure) {

View File

@ -3,6 +3,11 @@ import 'dart:async';
import 'package:async_tools/async_tools.dart';
import 'package:bloc/bloc.dart';
// A cubit with state T that wraps another input cubit of state S and
// produces T fro S via an asynchronous transform closure
// The input cubit becomes 'owned' by the AsyncTransformerCubit and will
// be closed when the AsyncTransformerCubit closes.
class AsyncTransformerCubit<T, S> extends Cubit<AsyncValue<T>> {
AsyncTransformerCubit(this.input, {required this.transform})
: super(const AsyncValue.loading()) {

View File

@ -342,7 +342,7 @@ class DHTRecord {
void _addValueChange(
{required bool local,
required Uint8List data,
required Uint8List? data,
required List<ValueSubkeyRange> subkeys}) {
final ws = watchState;
if (ws != null) {
@ -378,6 +378,6 @@ class DHTRecord {
void _addRemoteValueChange(VeilidUpdateValueChange update) {
_addValueChange(
local: false, data: update.value.data, subkeys: update.subkeys);
local: false, data: update.value?.data, subkeys: update.subkeys);
}
}

View File

@ -28,19 +28,19 @@ class DHTRecordCubit<T> extends Cubit<AsyncValue<T>> {
});
}
DHTRecordCubit.value({
required DHTRecord record,
required InitialStateFunction<T> initialStateFunction,
required StateFunction<T> stateFunction,
required WatchFunction watchFunction,
}) : _record = record,
_stateFunction = stateFunction,
_wantsCloseRecord = false,
super(const AsyncValue.loading()) {
Future.delayed(Duration.zero, () async {
await _init(initialStateFunction, stateFunction, watchFunction);
});
}
// DHTRecordCubit.value({
// required DHTRecord record,
// required InitialStateFunction<T> initialStateFunction,
// required StateFunction<T> stateFunction,
// required WatchFunction watchFunction,
// }) : _record = record,
// _stateFunction = stateFunction,
// _wantsCloseRecord = false,
// super(const AsyncValue.loading()) {
// Future.delayed(Duration.zero, () async {
// await _init(initialStateFunction, stateFunction, watchFunction);
// });
// }
Future<void> _init(
InitialStateFunction<T> initialStateFunction,
@ -123,13 +123,13 @@ class DefaultDHTRecordCubit<T> extends DHTRecordCubit<T> {
stateFunction: _makeStateFunction(decodeState),
watchFunction: _makeWatchFunction());
DefaultDHTRecordCubit.value({
required super.record,
required T Function(List<int> data) decodeState,
}) : super.value(
initialStateFunction: _makeInitialStateFunction(decodeState),
stateFunction: _makeStateFunction(decodeState),
watchFunction: _makeWatchFunction());
// DefaultDHTRecordCubit.value({
// required super.record,
// required T Function(List<int> data) decodeState,
// }) : super.value(
// initialStateFunction: _makeInitialStateFunction(decodeState),
// stateFunction: _makeStateFunction(decodeState),
// watchFunction: _makeWatchFunction());
static InitialStateFunction<T> _makeInitialStateFunction<T>(
T Function(List<int> data) decodeState) =>

View File

@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:async_tools/async_tools.dart';
import 'package:mutex/mutex.dart';
import 'package:protobuf/protobuf.dart';
@ -169,90 +168,36 @@ class DHTShortArray {
}
/// Runs a closure allowing read-only access to the shortarray
Future<T> operate<T>(Future<T> Function(DHTShortArrayRead) closure) async =>
Future<T?> operate<T>(Future<T?> Function(DHTShortArrayRead) closure) async =>
_head.operate((head) async {
final reader = _DHTShortArrayRead._(head);
return closure(reader);
});
/// Runs a closure allowing read-write access to the shortarray
/// Makes only one attempt to consistently write the changes to the DHT
/// Returns (result, true) of the closure if the write could be performed
/// Returns (null, false) if the write could not be performed at this time
Future<(T?, bool)> operateWrite<T>(
Future<T> Function(DHTShortArrayWrite) closure) async =>
Future<T?> Function(DHTShortArrayWrite) closure) async =>
_head.operateWrite((head) async {
final writer = _DHTShortArrayWrite._(head);
return closure(writer);
});
/// Set an item at position 'pos' of the DHTShortArray. Retries until the
/// value being written is successfully made the newest value of the element.
/// This may throw an exception if the position elements the built-in limit of
/// 'maxElements = 256' entries.
Future<void> eventualWriteItem(int pos, Uint8List newValue,
{Duration? timeout}) async {
await _head.operateWriteEventual((head) async {
bool wasSet;
(_, wasSet) = await _tryWriteItemInner(head, pos, newValue);
return wasSet;
}, timeout: timeout);
}
/// Change an item at position 'pos' of the DHTShortArray.
/// Runs with the value of the old element at that position such that it can
/// be changed to the returned value from tha closure. Retries until the
/// value being written is successfully made the newest value of the element.
/// This may throw an exception if the position elements the built-in limit of
/// 'maxElements = 256' entries.
Future<void> eventualUpdateItem(
int pos, Future<Uint8List> Function(Uint8List? oldValue) update,
{Duration? timeout}) async {
await _head.operateWriteEventual((head) async {
final oldData = await getItem(pos);
// Update the data
final updatedData = await update(oldData);
// Set it back
bool wasSet;
(_, wasSet) = await _tryWriteItemInner(head, pos, updatedData);
return wasSet;
}, timeout: timeout);
}
/// Convenience function:
/// Like eventualWriteItem but also encodes the input value as JSON and parses
/// the returned element as JSON
Future<void> eventualWriteItemJson<T>(int pos, T newValue,
{Duration? timeout}) =>
eventualWriteItem(pos, jsonEncodeBytes(newValue), timeout: timeout);
/// Convenience function:
/// Like eventualWriteItem but also encodes the input value as a protobuf
/// object and parses the returned element as a protobuf object
Future<void> eventualWriteItemProtobuf<T extends GeneratedMessage>(
int pos, T newValue,
{int subkey = -1, Duration? timeout}) =>
eventualWriteItem(pos, newValue.writeToBuffer(), timeout: timeout);
/// Convenience function:
/// Like eventualUpdateItem but also encodes the input value as JSON
Future<void> eventualUpdateItemJson<T>(
T Function(dynamic) fromJson, int pos, Future<T> Function(T?) update,
{Duration? timeout}) =>
eventualUpdateItem(pos, jsonUpdate(fromJson, update), timeout: timeout);
/// Convenience function:
/// Like eventualUpdateItem but also encodes the input value as a protobuf
/// object
Future<void> eventualUpdateItemProtobuf<T extends GeneratedMessage>(
T Function(List<int>) fromBuffer,
int pos,
Future<T> Function(T?) update,
{Duration? timeout}) =>
eventualUpdateItem(pos, protobufUpdate(fromBuffer, update),
timeout: timeout);
/// Runs a closure allowing read-write access to the shortarray
/// Will execute the closure multiple times if a consistent write to the DHT
/// is not achieved. Timeout if specified will be thrown as a
/// TimeoutException. The closure should return true if its changes also
/// succeeded, returning false will trigger another eventual consistency
/// attempt.
Future<void> operateWriteEventual(
Future<bool> Function(DHTShortArrayWrite) closure,
{Duration? timeout}) async =>
_head.operateWriteEventual((head) async {
final writer = _DHTShortArrayWrite._(head);
return closure(writer);
}, timeout: timeout);
Future<StreamSubscription<void>> listen(
void Function() onChanged,

View File

@ -4,7 +4,6 @@ import 'package:async_tools/async_tools.dart';
import 'package:bloc/bloc.dart';
import 'package:bloc_tools/bloc_tools.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:mutex/mutex.dart';
import '../../../veilid_support.dart';
@ -29,18 +28,18 @@ class DHTShortArrayCubit<T> extends Cubit<DHTShortArrayBusyState<T>>
});
}
DHTShortArrayCubit.value({
required DHTShortArray shortArray,
required T Function(List<int> data) decodeElement,
}) : _shortArray = shortArray,
_decodeElement = decodeElement,
super(const BlocBusyState(AsyncValue.loading())) {
_initFuture = Future(() async {
// Make initial state update
unawaited(_refreshNoWait());
_subscription = await shortArray.listen(_update);
});
}
// DHTShortArrayCubit.value({
// required DHTShortArray shortArray,
// required T Function(List<int> data) decodeElement,
// }) : _shortArray = shortArray,
// _decodeElement = decodeElement,
// super(const BlocBusyState(AsyncValue.loading())) {
// _initFuture = Future(() async {
// // Make initial state update
// unawaited(_refreshNoWait());
// _subscription = await shortArray.listen(_update);
// });
// }
Future<void> refresh({bool forceRefresh = false}) async {
await _initFuture;
@ -48,16 +47,15 @@ class DHTShortArrayCubit<T> extends Cubit<DHTShortArrayBusyState<T>>
}
Future<void> _refreshNoWait({bool forceRefresh = false}) async =>
busy((emit) async => _operateMutex.protect(
() async => _refreshInner(emit, forceRefresh: forceRefresh)));
busy((emit) async => _refreshInner(emit, forceRefresh: forceRefresh));
Future<void> _refreshInner(void Function(AsyncValue<IList<T>>) emit,
{bool forceRefresh = false}) async {
try {
final newState =
(await _shortArray.getAllItems(forceRefresh: forceRefresh))
?.map(_decodeElement)
.toIList();
final newState = (await _shortArray.operate(
(reader) => reader.getAllItems(forceRefresh: forceRefresh)))
?.map(_decodeElement)
.toIList();
if (newState != null) {
emit(AsyncValue.data(newState));
}
@ -71,8 +69,8 @@ class DHTShortArrayCubit<T> extends Cubit<DHTShortArrayBusyState<T>>
// Because this is async, we could get an update while we're
// still processing the last one. Only called after init future has run
// so we dont have to wait for that here.
_sspUpdate.busyUpdate<T, AsyncValue<IList<T>>>(busy,
(emit) async => _operateMutex.protect(() async => _refreshInner(emit)));
_sspUpdate.busyUpdate<T, AsyncValue<IList<T>>>(
busy, (emit) async => _refreshInner(emit));
}
@override
@ -86,12 +84,24 @@ class DHTShortArrayCubit<T> extends Cubit<DHTShortArrayBusyState<T>>
await super.close();
}
Future<R?> operate<R>(Future<R?> Function(DHTShortArray) closure) async {
Future<R?> operate<R>(Future<R?> Function(DHTShortArrayRead) closure) async {
await _initFuture;
return _operateMutex.protect(() async => closure(_shortArray));
return _shortArray.operate(closure);
}
Future<(R?, bool)> operateWrite<R>(
Future<R?> Function(DHTShortArrayWrite) closure) async {
await _initFuture;
return _shortArray.operateWrite(closure);
}
Future<void> operateWriteEventual(
Future<bool> Function(DHTShortArrayWrite) closure,
{Duration? timeout}) async {
await _initFuture;
return _shortArray.operateWriteEventual(closure, timeout: timeout);
}
final _operateMutex = Mutex();
late final Future<void> _initFuture;
late final DHTShortArray _shortArray;
final T Function(List<int> data) _decodeElement;

View File

@ -32,7 +32,7 @@ class _DHTShortArrayHead {
final head = proto.DHTShortArray();
head.keys.addAll(_linkedRecords.map((lr) => lr.key.toProto()));
head.index.addAll(_index);
head.index = List.of(_index);
head.seqs.addAll(_seqs);
// Do not serialize free list, it gets recreated
// Do not serialize local seqs, they are only locally relevant
@ -58,7 +58,7 @@ class _DHTShortArrayHead {
});
Future<(T?, bool)> operateWrite<T>(
Future<T> Function(_DHTShortArrayHead) closure) async =>
Future<T?> Function(_DHTShortArrayHead) closure) async =>
_headMutex.protect(() async {
final oldLinkedRecords = List.of(_linkedRecords);
final oldIndex = List.of(_index);
@ -111,14 +111,22 @@ class _DHTShortArrayHead {
oldSeqs = List.of(_seqs);
// Try to do the element write
do {
while (true) {
if (timeoutTs != null) {
final now = Veilid.instance.now();
if (now >= timeoutTs) {
throw TimeoutException('timeout reached');
}
}
} while (!await closure(this));
if (await closure(this)) {
break;
}
// Failed to write in closure resets state
_linkedRecords = List.of(oldLinkedRecords);
_index = List.of(oldIndex);
_free = List.of(oldFree);
_seqs = List.of(oldSeqs);
}
// Try to do the head write
} while (!await _writeHead());