mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04:00
concurrency work in prep for speeding things up
refactor splash screen to process initialization in a better way more async tools for async cubit constructors greatly improved StateMapFollower class
This commit is contained in:
parent
8da1dc7d32
commit
9bb20f4dd2
@ -3,24 +3,13 @@ import 'dart:async';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../init.dart';
|
|
||||||
import '../repository/account_repository/account_repository.dart';
|
import '../repository/account_repository/account_repository.dart';
|
||||||
|
|
||||||
class ActiveLocalAccountCubit extends Cubit<TypedKey?> {
|
class ActiveLocalAccountCubit extends Cubit<TypedKey?> {
|
||||||
ActiveLocalAccountCubit(AccountRepository accountRepository)
|
ActiveLocalAccountCubit(AccountRepository accountRepository)
|
||||||
: _accountRepository = accountRepository,
|
: _accountRepository = accountRepository,
|
||||||
super(null) {
|
super(accountRepository.getActiveLocalAccount()) {
|
||||||
// Subscribe to streams
|
// Subscribe to streams
|
||||||
_initAccountRepositorySubscription();
|
|
||||||
|
|
||||||
// Initialize when we can
|
|
||||||
Future.delayed(Duration.zero, () async {
|
|
||||||
await eventualInitialized.future;
|
|
||||||
emit(_accountRepository.getActiveLocalAccount());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initAccountRepositorySubscription() {
|
|
||||||
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
|
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
|
||||||
switch (change) {
|
switch (change) {
|
||||||
case AccountRepositoryChange.activeLocalAccount:
|
case AccountRepositoryChange.activeLocalAccount:
|
||||||
|
@ -3,25 +3,14 @@ import 'dart:async';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
|
||||||
import '../../init.dart';
|
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
import '../repository/account_repository/account_repository.dart';
|
import '../repository/account_repository/account_repository.dart';
|
||||||
|
|
||||||
class LocalAccountsCubit extends Cubit<IList<LocalAccount>> {
|
class LocalAccountsCubit extends Cubit<IList<LocalAccount>> {
|
||||||
LocalAccountsCubit(AccountRepository accountRepository)
|
LocalAccountsCubit(AccountRepository accountRepository)
|
||||||
: _accountRepository = accountRepository,
|
: _accountRepository = accountRepository,
|
||||||
super(IList<LocalAccount>()) {
|
super(accountRepository.getLocalAccounts()) {
|
||||||
// Subscribe to streams
|
// Subscribe to streams
|
||||||
_initAccountRepositorySubscription();
|
|
||||||
|
|
||||||
// Initialize when we can
|
|
||||||
Future.delayed(Duration.zero, () async {
|
|
||||||
await eventualInitialized.future;
|
|
||||||
emit(_accountRepository.getLocalAccounts());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initAccountRepositorySubscription() {
|
|
||||||
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
|
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
|
||||||
switch (change) {
|
switch (change) {
|
||||||
case AccountRepositoryChange.localAccounts:
|
case AccountRepositoryChange.localAccounts:
|
||||||
|
@ -3,25 +3,14 @@ import 'dart:async';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
|
||||||
import '../../init.dart';
|
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
import '../repository/account_repository/account_repository.dart';
|
import '../repository/account_repository/account_repository.dart';
|
||||||
|
|
||||||
class UserLoginsCubit extends Cubit<IList<UserLogin>> {
|
class UserLoginsCubit extends Cubit<IList<UserLogin>> {
|
||||||
UserLoginsCubit(AccountRepository accountRepository)
|
UserLoginsCubit(AccountRepository accountRepository)
|
||||||
: _accountRepository = accountRepository,
|
: _accountRepository = accountRepository,
|
||||||
super(IList<UserLogin>()) {
|
super(accountRepository.getUserLogins()) {
|
||||||
// Subscribe to streams
|
// Subscribe to streams
|
||||||
_initAccountRepositorySubscription();
|
|
||||||
|
|
||||||
// Initialize when we can
|
|
||||||
Future.delayed(Duration.zero, () async {
|
|
||||||
await eventualInitialized.future;
|
|
||||||
emit(_accountRepository.getUserLogins());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initAccountRepositorySubscription() {
|
|
||||||
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
|
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
|
||||||
switch (change) {
|
switch (change) {
|
||||||
case AccountRepositoryChange.userLogins:
|
case AccountRepositoryChange.userLogins:
|
||||||
|
@ -202,18 +202,24 @@ class AccountRepository {
|
|||||||
createAccountCallback: (parent) async {
|
createAccountCallback: (parent) async {
|
||||||
// Make empty contact list
|
// Make empty contact list
|
||||||
log.debug('Creating contacts list');
|
log.debug('Creating contacts list');
|
||||||
final contactList = await (await DHTShortArray.create(parent: parent))
|
final contactList = await (await DHTShortArray.create(
|
||||||
|
debugName: 'AccountRepository::_newLocalAccount::Contacts',
|
||||||
|
parent: parent))
|
||||||
.scope((r) async => r.recordPointer);
|
.scope((r) async => r.recordPointer);
|
||||||
|
|
||||||
// Make empty contact invitation record list
|
// Make empty contact invitation record list
|
||||||
log.debug('Creating contact invitation records list');
|
log.debug('Creating contact invitation records list');
|
||||||
final contactInvitationRecords =
|
final contactInvitationRecords = await (await DHTShortArray.create(
|
||||||
await (await DHTShortArray.create(parent: parent))
|
debugName:
|
||||||
.scope((r) async => r.recordPointer);
|
'AccountRepository::_newLocalAccount::ContactInvitations',
|
||||||
|
parent: parent))
|
||||||
|
.scope((r) async => r.recordPointer);
|
||||||
|
|
||||||
// Make empty chat record list
|
// Make empty chat record list
|
||||||
log.debug('Creating chat records list');
|
log.debug('Creating chat records list');
|
||||||
final chatRecords = await (await DHTShortArray.create(parent: parent))
|
final chatRecords = await (await DHTShortArray.create(
|
||||||
|
debugName: 'AccountRepository::_newLocalAccount::Chats',
|
||||||
|
parent: parent))
|
||||||
.scope((r) async => r.recordPointer);
|
.scope((r) async => r.recordPointer);
|
||||||
|
|
||||||
// Make account object
|
// Make account object
|
||||||
@ -391,6 +397,7 @@ class AccountRepository {
|
|||||||
final pool = DHTRecordPool.instance;
|
final pool = DHTRecordPool.instance;
|
||||||
final record = await pool.openOwned(
|
final record = await pool.openOwned(
|
||||||
userLogin.accountRecordInfo.accountRecord,
|
userLogin.accountRecordInfo.accountRecord,
|
||||||
|
debugName: 'AccountRepository::openAccountRecord::AccountRecord',
|
||||||
parent: localAccount.identityMaster.identityRecordKey);
|
parent: localAccount.identityMaster.identityRecordKey);
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
|
112
lib/app.dart
112
lib/app.dart
@ -5,8 +5,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'account_manager/account_manager.dart';
|
import 'account_manager/account_manager.dart';
|
||||||
|
import 'init.dart';
|
||||||
|
import 'layout/splash.dart';
|
||||||
import 'router/router.dart';
|
import 'router/router.dart';
|
||||||
import 'settings/settings.dart';
|
import 'settings/settings.dart';
|
||||||
import 'tick.dart';
|
import 'tick.dart';
|
||||||
@ -23,57 +26,66 @@ class VeilidChatApp extends StatelessWidget {
|
|||||||
final ThemeData initialThemeData;
|
final ThemeData initialThemeData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) => FutureProvider<VeilidChatGlobalInit?>(
|
||||||
final localizationDelegate = LocalizedApp.of(context).delegate;
|
initialData: null,
|
||||||
|
create: (context) async => VeilidChatGlobalInit.initialize(),
|
||||||
return ThemeProvider(
|
builder: (context, child) {
|
||||||
initTheme: initialThemeData,
|
final globalInit = context.watch<VeilidChatGlobalInit?>();
|
||||||
builder: (_, theme) => LocalizationProvider(
|
if (globalInit == null) {
|
||||||
state: LocalizationProvider.of(context).state,
|
// Splash screen until we're done with init
|
||||||
child: MultiBlocProvider(
|
return const Splash();
|
||||||
providers: [
|
}
|
||||||
BlocProvider<ConnectionStateCubit>(
|
// Once init is done, we proceed with the app
|
||||||
create: (context) =>
|
final localizationDelegate = LocalizedApp.of(context).delegate;
|
||||||
ConnectionStateCubit(ProcessorRepository.instance)),
|
return ThemeProvider(
|
||||||
BlocProvider<RouterCubit>(
|
initTheme: initialThemeData,
|
||||||
create: (context) =>
|
builder: (_, theme) => LocalizationProvider(
|
||||||
RouterCubit(AccountRepository.instance),
|
state: LocalizationProvider.of(context).state,
|
||||||
),
|
child: MultiBlocProvider(
|
||||||
BlocProvider<LocalAccountsCubit>(
|
providers: [
|
||||||
create: (context) =>
|
BlocProvider<ConnectionStateCubit>(
|
||||||
LocalAccountsCubit(AccountRepository.instance),
|
create: (context) => ConnectionStateCubit(
|
||||||
),
|
ProcessorRepository.instance)),
|
||||||
BlocProvider<UserLoginsCubit>(
|
BlocProvider<RouterCubit>(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
UserLoginsCubit(AccountRepository.instance),
|
RouterCubit(AccountRepository.instance),
|
||||||
),
|
),
|
||||||
BlocProvider<ActiveLocalAccountCubit>(
|
BlocProvider<LocalAccountsCubit>(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
ActiveLocalAccountCubit(AccountRepository.instance),
|
LocalAccountsCubit(AccountRepository.instance),
|
||||||
),
|
),
|
||||||
BlocProvider<PreferencesCubit>(
|
BlocProvider<UserLoginsCubit>(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
PreferencesCubit(PreferencesRepository.instance),
|
UserLoginsCubit(AccountRepository.instance),
|
||||||
)
|
),
|
||||||
],
|
BlocProvider<ActiveLocalAccountCubit>(
|
||||||
child: BackgroundTicker(
|
create: (context) => ActiveLocalAccountCubit(
|
||||||
builder: (context) => MaterialApp.router(
|
AccountRepository.instance),
|
||||||
debugShowCheckedModeBanner: false,
|
),
|
||||||
routerConfig: context.watch<RouterCubit>().router(),
|
BlocProvider<PreferencesCubit>(
|
||||||
title: translate('app.title'),
|
create: (context) =>
|
||||||
theme: theme,
|
PreferencesCubit(PreferencesRepository.instance),
|
||||||
localizationsDelegates: [
|
)
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
FormBuilderLocalizations.delegate,
|
|
||||||
localizationDelegate
|
|
||||||
],
|
],
|
||||||
supportedLocales: localizationDelegate.supportedLocales,
|
child: BackgroundTicker(
|
||||||
locale: localizationDelegate.currentLocale,
|
builder: (context) => MaterialApp.router(
|
||||||
),
|
debugShowCheckedModeBanner: false,
|
||||||
)),
|
routerConfig: context.watch<RouterCubit>().router(),
|
||||||
));
|
title: translate('app.title'),
|
||||||
}
|
theme: theme,
|
||||||
|
localizationsDelegates: [
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
FormBuilderLocalizations.delegate,
|
||||||
|
localizationDelegate
|
||||||
|
],
|
||||||
|
supportedLocales:
|
||||||
|
localizationDelegate.supportedLocales,
|
||||||
|
locale: localizationDelegate.currentLocale,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
@ -38,11 +38,13 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
_messagesUpdateQueue = StreamController(),
|
_messagesUpdateQueue = StreamController(),
|
||||||
super(const AsyncValue.loading()) {
|
super(const AsyncValue.loading()) {
|
||||||
// Async Init
|
// Async Init
|
||||||
Future.delayed(Duration.zero, _init);
|
_initWait.add(_init);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
|
await _initWait();
|
||||||
|
|
||||||
await _messagesUpdateQueue.close();
|
await _messagesUpdateQueue.close();
|
||||||
await _localSubscription?.cancel();
|
await _localSubscription?.cancel();
|
||||||
await _remoteSubscription?.cancel();
|
await _remoteSubscription?.cancel();
|
||||||
@ -89,7 +91,10 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
_localMessagesCubit = DHTShortArrayCubit(
|
_localMessagesCubit = DHTShortArrayCubit(
|
||||||
open: () async => DHTShortArray.openWrite(
|
open: () async => DHTShortArray.openWrite(
|
||||||
_localMessagesRecordKey, writer,
|
_localMessagesRecordKey, writer,
|
||||||
parent: _localConversationRecordKey, crypto: _messagesCrypto),
|
debugName:
|
||||||
|
'SingleContactMessagesCubit::_initLocalMessages::LocalMessages',
|
||||||
|
parent: _localConversationRecordKey,
|
||||||
|
crypto: _messagesCrypto),
|
||||||
decodeElement: proto.Message.fromBuffer);
|
decodeElement: proto.Message.fromBuffer);
|
||||||
_localSubscription =
|
_localSubscription =
|
||||||
_localMessagesCubit!.stream.listen(_updateLocalMessagesState);
|
_localMessagesCubit!.stream.listen(_updateLocalMessagesState);
|
||||||
@ -100,7 +105,10 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
Future<void> _initRemoteMessages() async {
|
Future<void> _initRemoteMessages() async {
|
||||||
_remoteMessagesCubit = DHTShortArrayCubit(
|
_remoteMessagesCubit = DHTShortArrayCubit(
|
||||||
open: () async => DHTShortArray.openRead(_remoteMessagesRecordKey,
|
open: () async => DHTShortArray.openRead(_remoteMessagesRecordKey,
|
||||||
parent: _remoteConversationRecordKey, crypto: _messagesCrypto),
|
debugName: 'SingleContactMessagesCubit::_initRemoteMessages::'
|
||||||
|
'RemoteMessages',
|
||||||
|
parent: _remoteConversationRecordKey,
|
||||||
|
crypto: _messagesCrypto),
|
||||||
decodeElement: proto.Message.fromBuffer);
|
decodeElement: proto.Message.fromBuffer);
|
||||||
_remoteSubscription =
|
_remoteSubscription =
|
||||||
_remoteMessagesCubit!.stream.listen(_updateRemoteMessagesState);
|
_remoteMessagesCubit!.stream.listen(_updateRemoteMessagesState);
|
||||||
@ -114,6 +122,9 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
|
|
||||||
_reconciledChatMessagesCubit = DHTShortArrayCubit(
|
_reconciledChatMessagesCubit = DHTShortArrayCubit(
|
||||||
open: () async => DHTShortArray.openOwned(_reconciledChatRecord,
|
open: () async => DHTShortArray.openOwned(_reconciledChatRecord,
|
||||||
|
debugName:
|
||||||
|
'SingleContactMessagesCubit::_initReconciledChatMessages::'
|
||||||
|
'ReconciledChat',
|
||||||
parent: accountRecordKey),
|
parent: accountRecordKey),
|
||||||
decodeElement: proto.Message.fromBuffer);
|
decodeElement: proto.Message.fromBuffer);
|
||||||
_reconciledChatSubscription =
|
_reconciledChatSubscription =
|
||||||
@ -237,6 +248,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
|
|
||||||
// Force refresh of messages
|
// Force refresh of messages
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
|
await _initWait();
|
||||||
|
|
||||||
final lcc = _localMessagesCubit;
|
final lcc = _localMessagesCubit;
|
||||||
final rcc = _remoteMessagesCubit;
|
final rcc = _remoteMessagesCubit;
|
||||||
|
|
||||||
@ -249,10 +262,13 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addMessage({required proto.Message message}) async {
|
Future<void> addMessage({required proto.Message message}) async {
|
||||||
|
await _initWait();
|
||||||
|
|
||||||
await _localMessagesCubit!
|
await _localMessagesCubit!
|
||||||
.operateWrite((writer) => writer.tryAddItem(message.writeToBuffer()));
|
.operateWrite((writer) => writer.tryAddItem(message.writeToBuffer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final WaitSet _initWait = WaitSet();
|
||||||
final ActiveAccountInfo _activeAccountInfo;
|
final ActiveAccountInfo _activeAccountInfo;
|
||||||
final TypedKey _remoteIdentityPublicKey;
|
final TypedKey _remoteIdentityPublicKey;
|
||||||
final TypedKey _localConversationRecordKey;
|
final TypedKey _localConversationRecordKey;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import 'package:async_tools/async_tools.dart';
|
import 'package:async_tools/async_tools.dart';
|
||||||
import 'package:bloc_tools/bloc_tools.dart';
|
import 'package:bloc_tools/bloc_tools.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../account_manager/account_manager.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
import '../../contacts/contacts.dart';
|
import '../../contacts/contacts.dart';
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
|
import 'cubits.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class ActiveConversationState extends Equatable {
|
class ActiveConversationState extends Equatable {
|
||||||
@ -39,9 +39,7 @@ typedef ActiveConversationsBlocMapState
|
|||||||
// archived chats or contacts that are not actively in a chat.
|
// archived chats or contacts that are not actively in a chat.
|
||||||
class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||||
AsyncValue<ActiveConversationState>, ActiveConversationCubit>
|
AsyncValue<ActiveConversationState>, ActiveConversationCubit>
|
||||||
with
|
with StateMapFollower<ChatListCubitState, TypedKey, proto.Chat> {
|
||||||
StateFollower<BlocBusyState<AsyncValue<IList<proto.Chat>>>, TypedKey,
|
|
||||||
proto.Chat> {
|
|
||||||
ActiveConversationsBlocMapCubit(
|
ActiveConversationsBlocMapCubit(
|
||||||
{required ActiveAccountInfo activeAccountInfo,
|
{required ActiveAccountInfo activeAccountInfo,
|
||||||
required ContactListCubit contactListCubit})
|
required ContactListCubit contactListCubit})
|
||||||
@ -77,18 +75,6 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||||||
|
|
||||||
/// StateFollower /////////////////////////
|
/// StateFollower /////////////////////////
|
||||||
|
|
||||||
@override
|
|
||||||
IMap<TypedKey, proto.Chat> getStateMap(
|
|
||||||
BlocBusyState<AsyncValue<IList<proto.Chat>>> state) {
|
|
||||||
final stateValue = state.state.data?.value;
|
|
||||||
if (stateValue == null) {
|
|
||||||
return IMap();
|
|
||||||
}
|
|
||||||
return IMap.fromIterable(stateValue,
|
|
||||||
keyMapper: (e) => e.remoteConversationRecordKey.toVeilid(),
|
|
||||||
valueMapper: (e) => e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import 'chat_list_cubit.dart';
|
|||||||
class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||||
AsyncValue<IList<proto.Message>>, SingleContactMessagesCubit>
|
AsyncValue<IList<proto.Message>>, SingleContactMessagesCubit>
|
||||||
with
|
with
|
||||||
StateFollower<ActiveConversationsBlocMapState, TypedKey,
|
StateMapFollower<ActiveConversationsBlocMapState, TypedKey,
|
||||||
AsyncValue<ActiveConversationState>> {
|
AsyncValue<ActiveConversationState>> {
|
||||||
ActiveSingleContactChatBlocMapCubit(
|
ActiveSingleContactChatBlocMapCubit(
|
||||||
{required ActiveAccountInfo activeAccountInfo,
|
{required ActiveAccountInfo activeAccountInfo,
|
||||||
@ -49,11 +49,6 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||||||
|
|
||||||
/// StateFollower /////////////////////////
|
/// StateFollower /////////////////////////
|
||||||
|
|
||||||
@override
|
|
||||||
IMap<TypedKey, AsyncValue<ActiveConversationState>> getStateMap(
|
|
||||||
ActiveConversationsBlocMapState state) =>
|
|
||||||
state;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:async_tools/async_tools.dart';
|
||||||
|
import 'package:bloc_tools/bloc_tools.dart';
|
||||||
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../account_manager/account_manager.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
@ -11,8 +14,10 @@ import '../../tools/tools.dart';
|
|||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// Mutable state for per-account chat list
|
// Mutable state for per-account chat list
|
||||||
|
typedef ChatListCubitState = BlocBusyState<AsyncValue<IList<proto.Chat>>>;
|
||||||
|
|
||||||
class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||||
|
with StateMapFollowable<ChatListCubitState, TypedKey, proto.Chat> {
|
||||||
ChatListCubit({
|
ChatListCubit({
|
||||||
required ActiveAccountInfo activeAccountInfo,
|
required ActiveAccountInfo activeAccountInfo,
|
||||||
required proto.Account account,
|
required proto.Account account,
|
||||||
@ -30,7 +35,7 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
|||||||
final chatListRecordKey = account.chatList.toVeilid();
|
final chatListRecordKey = account.chatList.toVeilid();
|
||||||
|
|
||||||
final dhtRecord = await DHTShortArray.openOwned(chatListRecordKey,
|
final dhtRecord = await DHTShortArray.openOwned(chatListRecordKey,
|
||||||
parent: accountRecordKey);
|
debugName: 'ChatListCubit::_open::ChatList', parent: accountRecordKey);
|
||||||
|
|
||||||
return dhtRecord;
|
return dhtRecord;
|
||||||
}
|
}
|
||||||
@ -61,9 +66,11 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
|||||||
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
|
|
||||||
// Make a record that can store the reconciled version of the chat
|
// Make a record that can store the reconciled version of the chat
|
||||||
final reconciledChatRecord =
|
final reconciledChatRecord = await (await DHTShortArray.create(
|
||||||
await (await DHTShortArray.create(parent: accountRecordKey))
|
debugName:
|
||||||
.scope((r) async => r.recordPointer);
|
'ChatListCubit::getOrCreateChatSingleContact::ReconciledChat',
|
||||||
|
parent: accountRecordKey))
|
||||||
|
.scope((r) async => r.recordPointer);
|
||||||
|
|
||||||
// Create conversation type Chat
|
// Create conversation type Chat
|
||||||
final chat = proto.Chat()
|
final chat = proto.Chat()
|
||||||
@ -86,26 +93,30 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
|||||||
|
|
||||||
// Remove Chat from account's list
|
// Remove Chat from account's list
|
||||||
// if this fails, don't keep retrying, user can try again later
|
// if this fails, don't keep retrying, user can try again later
|
||||||
final (deletedItem, success) = await operateWrite((writer) async {
|
final (deletedItem, success) =
|
||||||
if (activeChatCubit.state == remoteConversationRecordKey) {
|
// Ensure followers get their changes before we return
|
||||||
activeChatCubit.setActiveChat(null);
|
await syncFollowers(() => operateWrite((writer) async {
|
||||||
}
|
if (activeChatCubit.state == remoteConversationRecordKey) {
|
||||||
for (var i = 0; i < writer.length; i++) {
|
activeChatCubit.setActiveChat(null);
|
||||||
final cbuf = await writer.getItem(i);
|
}
|
||||||
if (cbuf == null) {
|
for (var i = 0; i < writer.length; i++) {
|
||||||
throw Exception('Failed to get chat');
|
final cbuf = await writer.getItem(i);
|
||||||
}
|
if (cbuf == null) {
|
||||||
final c = proto.Chat.fromBuffer(cbuf);
|
throw Exception('Failed to get chat');
|
||||||
if (c.remoteConversationRecordKey == remoteConversationKey) {
|
}
|
||||||
// Found the right chat
|
final c = proto.Chat.fromBuffer(cbuf);
|
||||||
if (await writer.tryRemoveItem(i) != null) {
|
if (c.remoteConversationRecordKey == remoteConversationKey) {
|
||||||
return c;
|
// Found the right chat
|
||||||
}
|
if (await writer.tryRemoveItem(i) != null) {
|
||||||
return null;
|
return c;
|
||||||
}
|
}
|
||||||
}
|
return null;
|
||||||
return null;
|
}
|
||||||
});
|
}
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
// Since followers are synced, we can safetly remove the reconciled
|
||||||
|
// chat record now
|
||||||
if (success && deletedItem != null) {
|
if (success && deletedItem != null) {
|
||||||
try {
|
try {
|
||||||
await DHTRecordPool.instance
|
await DHTRecordPool.instance
|
||||||
@ -116,6 +127,18 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// StateMapFollowable /////////////////////////
|
||||||
|
@override
|
||||||
|
IMap<TypedKey, proto.Chat> getStateMap(ChatListCubitState state) {
|
||||||
|
final stateValue = state.state.data?.value;
|
||||||
|
if (stateValue == null) {
|
||||||
|
return IMap();
|
||||||
|
}
|
||||||
|
return IMap.fromIterable(stateValue,
|
||||||
|
keyMapper: (e) => e.remoteConversationRecordKey.toVeilid(),
|
||||||
|
valueMapper: (e) => e);
|
||||||
|
}
|
||||||
|
|
||||||
final ActiveChatCubit activeChatCubit;
|
final ActiveChatCubit activeChatCubit;
|
||||||
final ActiveAccountInfo _activeAccountInfo;
|
final ActiveAccountInfo _activeAccountInfo;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:async_tools/async_tools.dart';
|
||||||
|
import 'package:bloc_tools/bloc_tools.dart';
|
||||||
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
@ -23,11 +26,16 @@ typedef GetEncryptionKeyCallback = Future<SecretKey?> Function(
|
|||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
|
|
||||||
|
typedef ContactInvitiationListState
|
||||||
|
= BlocBusyState<AsyncValue<IList<proto.ContactInvitationRecord>>>;
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// Mutable state for per-account contact invitations
|
// Mutable state for per-account contact invitations
|
||||||
|
|
||||||
class ContactInvitationListCubit
|
class ContactInvitationListCubit
|
||||||
extends DHTShortArrayCubit<proto.ContactInvitationRecord> {
|
extends DHTShortArrayCubit<proto.ContactInvitationRecord>
|
||||||
|
with
|
||||||
|
StateMapFollowable<ContactInvitiationListState, TypedKey,
|
||||||
|
proto.ContactInvitationRecord> {
|
||||||
ContactInvitationListCubit({
|
ContactInvitationListCubit({
|
||||||
required ActiveAccountInfo activeAccountInfo,
|
required ActiveAccountInfo activeAccountInfo,
|
||||||
required proto.Account account,
|
required proto.Account account,
|
||||||
@ -47,6 +55,7 @@ class ContactInvitationListCubit
|
|||||||
|
|
||||||
final dhtRecord = await DHTShortArray.openOwned(
|
final dhtRecord = await DHTShortArray.openOwned(
|
||||||
contactInvitationListRecordPointer,
|
contactInvitationListRecordPointer,
|
||||||
|
debugName: 'ContactInvitationListCubit::_open::ContactInvitationList',
|
||||||
parent: accountRecordKey);
|
parent: accountRecordKey);
|
||||||
|
|
||||||
return dhtRecord;
|
return dhtRecord;
|
||||||
@ -78,6 +87,8 @@ class ContactInvitationListCubit
|
|||||||
// identity key
|
// identity key
|
||||||
late final Uint8List signedContactInvitationBytes;
|
late final Uint8List signedContactInvitationBytes;
|
||||||
await (await pool.create(
|
await (await pool.create(
|
||||||
|
debugName: 'ContactInvitationListCubit::createInvitation::'
|
||||||
|
'LocalConversation',
|
||||||
parent: _activeAccountInfo.accountRecordKey,
|
parent: _activeAccountInfo.accountRecordKey,
|
||||||
schema: DHTSchema.smpl(oCnt: 0, members: [
|
schema: DHTSchema.smpl(oCnt: 0, members: [
|
||||||
DHTSchemaMember(mKey: conversationWriter.key, mCnt: 1)
|
DHTSchemaMember(mKey: conversationWriter.key, mCnt: 1)
|
||||||
@ -105,6 +116,8 @@ class ContactInvitationListCubit
|
|||||||
// Subkey 0 is the ContactRequest from the initiator
|
// Subkey 0 is the ContactRequest from the initiator
|
||||||
// Subkey 1 will contain the invitation response accept/reject eventually
|
// Subkey 1 will contain the invitation response accept/reject eventually
|
||||||
await (await pool.create(
|
await (await pool.create(
|
||||||
|
debugName: 'ContactInvitationListCubit::createInvitation::'
|
||||||
|
'ContactRequestInbox',
|
||||||
parent: _activeAccountInfo.accountRecordKey,
|
parent: _activeAccountInfo.accountRecordKey,
|
||||||
schema: DHTSchema.smpl(oCnt: 1, members: [
|
schema: DHTSchema.smpl(oCnt: 1, members: [
|
||||||
DHTSchemaMember(mCnt: 1, mKey: contactRequestWriter.key)
|
DHTSchemaMember(mCnt: 1, mKey: contactRequestWriter.key)
|
||||||
@ -180,6 +193,8 @@ class ContactInvitationListCubit
|
|||||||
// Delete the contact request inbox
|
// Delete the contact request inbox
|
||||||
final contactRequestInbox = deletedItem.contactRequestInbox.toVeilid();
|
final contactRequestInbox = deletedItem.contactRequestInbox.toVeilid();
|
||||||
await (await pool.openOwned(contactRequestInbox,
|
await (await pool.openOwned(contactRequestInbox,
|
||||||
|
debugName: 'ContactInvitationListCubit::deleteInvitation::'
|
||||||
|
'ContactRequestInbox',
|
||||||
parent: accountRecordKey))
|
parent: accountRecordKey))
|
||||||
.scope((contactRequestInbox) async {
|
.scope((contactRequestInbox) async {
|
||||||
// Wipe out old invitation so it shows up as invalid
|
// Wipe out old invitation so it shows up as invalid
|
||||||
@ -229,6 +244,8 @@ class ContactInvitationListCubit
|
|||||||
-1;
|
-1;
|
||||||
|
|
||||||
await (await pool.openRead(contactRequestInboxKey,
|
await (await pool.openRead(contactRequestInboxKey,
|
||||||
|
debugName: 'ContactInvitationListCubit::validateInvitation::'
|
||||||
|
'ContactRequestInbox',
|
||||||
parent: _activeAccountInfo.accountRecordKey))
|
parent: _activeAccountInfo.accountRecordKey))
|
||||||
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
|
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
|
||||||
//
|
//
|
||||||
@ -282,6 +299,19 @@ class ContactInvitationListCubit
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// StateMapFollowable /////////////////////////
|
||||||
|
@override
|
||||||
|
IMap<TypedKey, proto.ContactInvitationRecord> getStateMap(
|
||||||
|
ContactInvitiationListState state) {
|
||||||
|
final stateValue = state.state.data?.value;
|
||||||
|
if (stateValue == null) {
|
||||||
|
return IMap();
|
||||||
|
}
|
||||||
|
return IMap.fromIterable(stateValue,
|
||||||
|
keyMapper: (e) => e.contactRequestInbox.recordKey.toVeilid(),
|
||||||
|
valueMapper: (e) => e);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
final ActiveAccountInfo _activeAccountInfo;
|
final ActiveAccountInfo _activeAccountInfo;
|
||||||
final proto.Account _account;
|
final proto.Account _account;
|
||||||
|
@ -33,6 +33,8 @@ class ContactRequestInboxCubit
|
|||||||
final writer = TypedKeyPair(
|
final writer = TypedKeyPair(
|
||||||
kind: recordKey.kind, key: writerKey, secret: writerSecret);
|
kind: recordKey.kind, key: writerKey, secret: writerSecret);
|
||||||
return pool.openRead(recordKey,
|
return pool.openRead(recordKey,
|
||||||
|
debugName: 'ContactRequestInboxCubit::_open::'
|
||||||
|
'ContactRequestInbox',
|
||||||
crypto: await DHTRecordCryptoPrivate.fromTypedKeyPair(writer),
|
crypto: await DHTRecordCryptoPrivate.fromTypedKeyPair(writer),
|
||||||
parent: accountRecordKey,
|
parent: accountRecordKey,
|
||||||
defaultSubkey: 1);
|
defaultSubkey: 1);
|
||||||
|
@ -16,7 +16,7 @@ typedef WaitingInvitationsBlocMapState
|
|||||||
class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||||
AsyncValue<InvitationStatus>, WaitingInvitationCubit>
|
AsyncValue<InvitationStatus>, WaitingInvitationCubit>
|
||||||
with
|
with
|
||||||
StateFollower<
|
StateMapFollower<
|
||||||
BlocBusyState<AsyncValue<IList<proto.ContactInvitationRecord>>>,
|
BlocBusyState<AsyncValue<IList<proto.ContactInvitationRecord>>>,
|
||||||
TypedKey,
|
TypedKey,
|
||||||
proto.ContactInvitationRecord> {
|
proto.ContactInvitationRecord> {
|
||||||
@ -37,17 +37,6 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||||||
contactInvitationRecord: contactInvitationRecord)));
|
contactInvitationRecord: contactInvitationRecord)));
|
||||||
|
|
||||||
/// StateFollower /////////////////////////
|
/// StateFollower /////////////////////////
|
||||||
@override
|
|
||||||
IMap<TypedKey, proto.ContactInvitationRecord> getStateMap(
|
|
||||||
BlocBusyState<AsyncValue<IList<proto.ContactInvitationRecord>>> state) {
|
|
||||||
final stateValue = state.state.data?.value;
|
|
||||||
if (stateValue == null) {
|
|
||||||
return IMap();
|
|
||||||
}
|
|
||||||
return IMap.fromIterable(stateValue,
|
|
||||||
keyMapper: (e) => e.contactRequestInbox.recordKey.toVeilid(),
|
|
||||||
valueMapper: (e) => e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||||
|
@ -38,6 +38,8 @@ class ValidContactInvitation {
|
|||||||
final accountRecordKey = _activeAccountInfo.accountRecordKey;
|
final accountRecordKey = _activeAccountInfo.accountRecordKey;
|
||||||
|
|
||||||
return (await pool.openWrite(_contactRequestInboxKey, _writer,
|
return (await pool.openWrite(_contactRequestInboxKey, _writer,
|
||||||
|
debugName: 'ValidContactInvitation::accept::'
|
||||||
|
'ContactRequestInbox',
|
||||||
parent: accountRecordKey))
|
parent: accountRecordKey))
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
|
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
|
||||||
@ -103,6 +105,8 @@ class ValidContactInvitation {
|
|||||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
|
|
||||||
return (await pool.openWrite(_contactRequestInboxKey, _writer,
|
return (await pool.openWrite(_contactRequestInboxKey, _writer,
|
||||||
|
debugName: 'ValidContactInvitation::reject::'
|
||||||
|
'ContactRequestInbox',
|
||||||
parent: accountRecordKey))
|
parent: accountRecordKey))
|
||||||
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
|
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
|
||||||
final cs =
|
final cs =
|
||||||
|
@ -6,6 +6,7 @@ import 'package:veilid_support/veilid_support.dart';
|
|||||||
import '../../account_manager/account_manager.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
|
import 'conversation_cubit.dart';
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// Mutable state for per-account contacts
|
// Mutable state for per-account contacts
|
||||||
@ -14,7 +15,8 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
ContactListCubit({
|
ContactListCubit({
|
||||||
required ActiveAccountInfo activeAccountInfo,
|
required ActiveAccountInfo activeAccountInfo,
|
||||||
required proto.Account account,
|
required proto.Account account,
|
||||||
}) : super(
|
}) : _activeAccountInfo = activeAccountInfo,
|
||||||
|
super(
|
||||||
open: () => _open(activeAccountInfo, account),
|
open: () => _open(activeAccountInfo, account),
|
||||||
decodeElement: proto.Contact.fromBuffer);
|
decodeElement: proto.Contact.fromBuffer);
|
||||||
|
|
||||||
@ -26,6 +28,7 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
final contactListRecordKey = account.contactList.toVeilid();
|
final contactListRecordKey = account.contactList.toVeilid();
|
||||||
|
|
||||||
final dhtRecord = await DHTShortArray.openOwned(contactListRecordKey,
|
final dhtRecord = await DHTShortArray.openOwned(contactListRecordKey,
|
||||||
|
debugName: 'ContactListCubit::_open::ContactList',
|
||||||
parent: accountRecordKey);
|
parent: accountRecordKey);
|
||||||
|
|
||||||
return dhtRecord;
|
return dhtRecord;
|
||||||
@ -60,9 +63,10 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteContact({required proto.Contact contact}) async {
|
Future<void> deleteContact({required proto.Contact contact}) async {
|
||||||
final pool = DHTRecordPool.instance;
|
final remoteIdentityPublicKey = contact.identityPublicKey.toVeilid();
|
||||||
final localConversationKey = contact.localConversationRecordKey.toVeilid();
|
final localConversationRecordKey =
|
||||||
final remoteConversationKey =
|
contact.localConversationRecordKey.toVeilid();
|
||||||
|
final remoteConversationRecordKey =
|
||||||
contact.remoteConversationRecordKey.toVeilid();
|
contact.remoteConversationRecordKey.toVeilid();
|
||||||
|
|
||||||
// Remove Contact from account's list
|
// Remove Contact from account's list
|
||||||
@ -85,17 +89,21 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
|
|
||||||
if (success && deletedItem != null) {
|
if (success && deletedItem != null) {
|
||||||
try {
|
try {
|
||||||
await pool.delete(localConversationKey);
|
// Make a conversation cubit to manipulate the conversation
|
||||||
|
final conversationCubit = ConversationCubit(
|
||||||
|
activeAccountInfo: _activeAccountInfo,
|
||||||
|
remoteIdentityPublicKey: remoteIdentityPublicKey,
|
||||||
|
localConversationRecordKey: localConversationRecordKey,
|
||||||
|
remoteConversationRecordKey: remoteConversationRecordKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Delete the local and remote conversation records
|
||||||
|
await conversationCubit.delete();
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
log.debug('error removing local conversation record key: $e', e);
|
log.debug('error deleting conversation records: $e', e);
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (localConversationKey != remoteConversationKey) {
|
|
||||||
await pool.delete(remoteConversationKey);
|
|
||||||
}
|
|
||||||
} on Exception catch (e) {
|
|
||||||
log.debug('error removing remote conversation record key: $e', e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ActiveAccountInfo _activeAccountInfo;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import 'package:veilid_support/veilid_support.dart';
|
|||||||
|
|
||||||
import '../../account_manager/account_manager.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
|
import '../../tools/tools.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class ConversationState extends Equatable {
|
class ConversationState extends Equatable {
|
||||||
@ -36,11 +37,9 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
_localConversationRecordKey = localConversationRecordKey,
|
_localConversationRecordKey = localConversationRecordKey,
|
||||||
_remoteIdentityPublicKey = remoteIdentityPublicKey,
|
_remoteIdentityPublicKey = remoteIdentityPublicKey,
|
||||||
_remoteConversationRecordKey = remoteConversationRecordKey,
|
_remoteConversationRecordKey = remoteConversationRecordKey,
|
||||||
_incrementalState = const ConversationState(
|
|
||||||
localConversation: null, remoteConversation: null),
|
|
||||||
super(const AsyncValue.loading()) {
|
super(const AsyncValue.loading()) {
|
||||||
if (_localConversationRecordKey != null) {
|
if (_localConversationRecordKey != null) {
|
||||||
Future.delayed(Duration.zero, () async {
|
_initWait.add(() async {
|
||||||
await _setLocalConversation(() async {
|
await _setLocalConversation(() async {
|
||||||
final accountRecordKey = _activeAccountInfo
|
final accountRecordKey = _activeAccountInfo
|
||||||
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
@ -51,14 +50,16 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
final writer = _activeAccountInfo.conversationWriter;
|
final writer = _activeAccountInfo.conversationWriter;
|
||||||
final record = await pool.openWrite(
|
final record = await pool.openWrite(
|
||||||
_localConversationRecordKey!, writer,
|
_localConversationRecordKey!, writer,
|
||||||
parent: accountRecordKey, crypto: crypto);
|
debugName: 'ConversationCubit::LocalConversation',
|
||||||
|
parent: accountRecordKey,
|
||||||
|
crypto: crypto);
|
||||||
return record;
|
return record;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_remoteConversationRecordKey != null) {
|
if (_remoteConversationRecordKey != null) {
|
||||||
Future.delayed(Duration.zero, () async {
|
_initWait.add(() async {
|
||||||
await _setRemoteConversation(() async {
|
await _setRemoteConversation(() async {
|
||||||
final accountRecordKey = _activeAccountInfo
|
final accountRecordKey = _activeAccountInfo
|
||||||
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
@ -67,7 +68,9 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
final pool = DHTRecordPool.instance;
|
final pool = DHTRecordPool.instance;
|
||||||
final crypto = await _cachedConversationCrypto();
|
final crypto = await _cachedConversationCrypto();
|
||||||
final record = await pool.openRead(_remoteConversationRecordKey,
|
final record = await pool.openRead(_remoteConversationRecordKey,
|
||||||
parent: accountRecordKey, crypto: crypto);
|
debugName: 'ConversationCubit::RemoteConversation',
|
||||||
|
parent: accountRecordKey,
|
||||||
|
crypto: crypto);
|
||||||
return record;
|
return record;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -76,6 +79,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
|
await _initWait();
|
||||||
await _localSubscription?.cancel();
|
await _localSubscription?.cancel();
|
||||||
await _remoteSubscription?.cancel();
|
await _remoteSubscription?.cancel();
|
||||||
await _localConversationCubit?.close();
|
await _localConversationCubit?.close();
|
||||||
@ -84,7 +88,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
await super.close();
|
await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateLocalConversationState(AsyncValue<proto.Conversation> avconv) {
|
void _updateLocalConversationState(AsyncValue<proto.Conversation> avconv) {
|
||||||
final newState = avconv.when(
|
final newState = avconv.when(
|
||||||
data: (conv) {
|
data: (conv) {
|
||||||
_incrementalState = ConversationState(
|
_incrementalState = ConversationState(
|
||||||
@ -106,7 +110,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
emit(newState);
|
emit(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateRemoteConversationState(AsyncValue<proto.Conversation> avconv) {
|
void _updateRemoteConversationState(AsyncValue<proto.Conversation> avconv) {
|
||||||
final newState = avconv.when(
|
final newState = avconv.when(
|
||||||
data: (conv) {
|
data: (conv) {
|
||||||
_incrementalState = ConversationState(
|
_incrementalState = ConversationState(
|
||||||
@ -135,7 +139,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
_localConversationCubit = DefaultDHTRecordCubit(
|
_localConversationCubit = DefaultDHTRecordCubit(
|
||||||
open: open, decodeState: proto.Conversation.fromBuffer);
|
open: open, decodeState: proto.Conversation.fromBuffer);
|
||||||
_localSubscription =
|
_localSubscription =
|
||||||
_localConversationCubit!.stream.listen(updateLocalConversationState);
|
_localConversationCubit!.stream.listen(_updateLocalConversationState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open remote converation key
|
// Open remote converation key
|
||||||
@ -145,7 +149,57 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
_remoteConversationCubit = DefaultDHTRecordCubit(
|
_remoteConversationCubit = DefaultDHTRecordCubit(
|
||||||
open: open, decodeState: proto.Conversation.fromBuffer);
|
open: open, decodeState: proto.Conversation.fromBuffer);
|
||||||
_remoteSubscription =
|
_remoteSubscription =
|
||||||
_remoteConversationCubit!.stream.listen(updateRemoteConversationState);
|
_remoteConversationCubit!.stream.listen(_updateRemoteConversationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> delete() async {
|
||||||
|
final pool = DHTRecordPool.instance;
|
||||||
|
|
||||||
|
await _initWait();
|
||||||
|
final localConversationCubit = _localConversationCubit;
|
||||||
|
final remoteConversationCubit = _remoteConversationCubit;
|
||||||
|
|
||||||
|
final deleteSet = DelayedWaitSet();
|
||||||
|
|
||||||
|
if (localConversationCubit != null) {
|
||||||
|
final data = localConversationCubit.state.data;
|
||||||
|
if (data == null) {
|
||||||
|
log.warning('could not delete local conversation');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSet.add(() async {
|
||||||
|
_localConversationCubit = null;
|
||||||
|
await localConversationCubit.close();
|
||||||
|
final conversation = data.value;
|
||||||
|
final messagesKey = conversation.messages.toVeilid();
|
||||||
|
await pool.delete(messagesKey);
|
||||||
|
await pool.delete(_localConversationRecordKey!);
|
||||||
|
_localConversationRecordKey = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remoteConversationCubit != null) {
|
||||||
|
final data = remoteConversationCubit.state.data;
|
||||||
|
if (data == null) {
|
||||||
|
log.warning('could not delete remote conversation');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSet.add(() async {
|
||||||
|
_remoteConversationCubit = null;
|
||||||
|
await remoteConversationCubit.close();
|
||||||
|
final conversation = data.value;
|
||||||
|
final messagesKey = conversation.messages.toVeilid();
|
||||||
|
await pool.delete(messagesKey);
|
||||||
|
await pool.delete(_remoteConversationRecordKey!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the delete futures
|
||||||
|
await deleteSet();
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize a local conversation
|
// Initialize a local conversation
|
||||||
@ -174,23 +228,25 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
if (existingConversationRecordKey != null) {
|
if (existingConversationRecordKey != null) {
|
||||||
localConversationRecord = await pool.openWrite(
|
localConversationRecord = await pool.openWrite(
|
||||||
existingConversationRecordKey, writer,
|
existingConversationRecordKey, writer,
|
||||||
parent: accountRecordKey, crypto: crypto);
|
debugName:
|
||||||
|
'ConversationCubit::initLocalConversation::LocalConversation',
|
||||||
|
parent: accountRecordKey,
|
||||||
|
crypto: crypto);
|
||||||
} else {
|
} else {
|
||||||
final localConversationRecordCreate = await pool.create(
|
localConversationRecord = await pool.create(
|
||||||
|
debugName:
|
||||||
|
'ConversationCubit::initLocalConversation::LocalConversation',
|
||||||
parent: accountRecordKey,
|
parent: accountRecordKey,
|
||||||
crypto: crypto,
|
crypto: crypto,
|
||||||
|
writer: writer,
|
||||||
schema: DHTSchema.smpl(
|
schema: DHTSchema.smpl(
|
||||||
oCnt: 0, members: [DHTSchemaMember(mKey: writer.key, mCnt: 1)]));
|
oCnt: 0, members: [DHTSchemaMember(mKey: writer.key, mCnt: 1)]));
|
||||||
await localConversationRecordCreate.close();
|
|
||||||
localConversationRecord = await pool.openWrite(
|
|
||||||
localConversationRecordCreate.key, writer,
|
|
||||||
parent: accountRecordKey, crypto: crypto);
|
|
||||||
}
|
}
|
||||||
final out = localConversationRecord
|
final out = localConversationRecord
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
.deleteScope((localConversation) async {
|
.deleteScope((localConversation) async {
|
||||||
// Make messages log
|
// Make messages log
|
||||||
return initLocalMessages(
|
return _initLocalMessages(
|
||||||
activeAccountInfo: _activeAccountInfo,
|
activeAccountInfo: _activeAccountInfo,
|
||||||
remoteIdentityPublicKey: _remoteIdentityPublicKey,
|
remoteIdentityPublicKey: _remoteIdentityPublicKey,
|
||||||
localConversationKey: localConversation.key,
|
localConversationKey: localConversation.key,
|
||||||
@ -211,7 +267,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
final out = await callback(localConversation);
|
final out = await callback(localConversation);
|
||||||
|
|
||||||
// Upon success emit the local conversation record to the state
|
// Upon success emit the local conversation record to the state
|
||||||
updateLocalConversationState(AsyncValue.data(conversation));
|
_updateLocalConversationState(AsyncValue.data(conversation));
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
});
|
});
|
||||||
@ -225,7 +281,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize local messages
|
// Initialize local messages
|
||||||
Future<T> initLocalMessages<T>({
|
Future<T> _initLocalMessages<T>({
|
||||||
required ActiveAccountInfo activeAccountInfo,
|
required ActiveAccountInfo activeAccountInfo,
|
||||||
required TypedKey remoteIdentityPublicKey,
|
required TypedKey remoteIdentityPublicKey,
|
||||||
required TypedKey localConversationKey,
|
required TypedKey localConversationKey,
|
||||||
@ -236,12 +292,17 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
final writer = activeAccountInfo.conversationWriter;
|
final writer = activeAccountInfo.conversationWriter;
|
||||||
|
|
||||||
return (await DHTShortArray.create(
|
return (await DHTShortArray.create(
|
||||||
parent: localConversationKey, crypto: crypto, smplWriter: writer))
|
debugName: 'ConversationCubit::initLocalMessages::LocalMessages',
|
||||||
|
parent: localConversationKey,
|
||||||
|
crypto: crypto,
|
||||||
|
smplWriter: writer))
|
||||||
.deleteScope((messages) async => await callback(messages));
|
.deleteScope((messages) async => await callback(messages));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force refresh of conversation keys
|
// Force refresh of conversation keys
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
|
await _initWait();
|
||||||
|
|
||||||
final lcc = _localConversationCubit;
|
final lcc = _localConversationCubit;
|
||||||
final rcc = _remoteConversationCubit;
|
final rcc = _remoteConversationCubit;
|
||||||
|
|
||||||
@ -260,7 +321,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
.tryWriteProtobuf(proto.Conversation.fromBuffer, conversation);
|
.tryWriteProtobuf(proto.Conversation.fromBuffer, conversation);
|
||||||
|
|
||||||
if (update != null) {
|
if (update != null) {
|
||||||
updateLocalConversationState(AsyncValue.data(conversation));
|
_updateLocalConversationState(AsyncValue.data(conversation));
|
||||||
}
|
}
|
||||||
|
|
||||||
return update;
|
return update;
|
||||||
@ -286,7 +347,9 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
DefaultDHTRecordCubit<proto.Conversation>? _remoteConversationCubit;
|
DefaultDHTRecordCubit<proto.Conversation>? _remoteConversationCubit;
|
||||||
StreamSubscription<AsyncValue<proto.Conversation>>? _localSubscription;
|
StreamSubscription<AsyncValue<proto.Conversation>>? _localSubscription;
|
||||||
StreamSubscription<AsyncValue<proto.Conversation>>? _remoteSubscription;
|
StreamSubscription<AsyncValue<proto.Conversation>>? _remoteSubscription;
|
||||||
ConversationState _incrementalState;
|
ConversationState _incrementalState = const ConversationState(
|
||||||
|
localConversation: null, remoteConversation: null);
|
||||||
//
|
//
|
||||||
DHTRecordCrypto? _conversationCrypto;
|
DHTRecordCrypto? _conversationCrypto;
|
||||||
|
final WaitSet _initWait = WaitSet();
|
||||||
}
|
}
|
||||||
|
@ -8,34 +8,38 @@ import 'app.dart';
|
|||||||
import 'tools/tools.dart';
|
import 'tools/tools.dart';
|
||||||
import 'veilid_processor/veilid_processor.dart';
|
import 'veilid_processor/veilid_processor.dart';
|
||||||
|
|
||||||
final Completer<void> eventualInitialized = Completer<void>();
|
class VeilidChatGlobalInit {
|
||||||
|
VeilidChatGlobalInit._();
|
||||||
|
|
||||||
// Initialize Veilid
|
// Initialize Veilid
|
||||||
Future<void> initializeVeilid() async {
|
Future<void> _initializeVeilid() async {
|
||||||
// Init Veilid
|
// Init Veilid
|
||||||
Veilid.instance.initializeVeilidCore(
|
Veilid.instance.initializeVeilidCore(
|
||||||
getDefaultVeilidPlatformConfig(kIsWeb, VeilidChatApp.name));
|
getDefaultVeilidPlatformConfig(kIsWeb, VeilidChatApp.name));
|
||||||
|
|
||||||
// Veilid logging
|
// Veilid logging
|
||||||
initVeilidLog(kDebugMode);
|
initVeilidLog(kDebugMode);
|
||||||
|
|
||||||
// Startup Veilid
|
// Startup Veilid
|
||||||
await ProcessorRepository.instance.startup();
|
await ProcessorRepository.instance.startup();
|
||||||
|
|
||||||
// DHT Record Pool
|
// DHT Record Pool
|
||||||
await DHTRecordPool.init();
|
await DHTRecordPool.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize repositories
|
// Initialize repositories
|
||||||
Future<void> initializeRepositories() async {
|
Future<void> _initializeRepositories() async {
|
||||||
await AccountRepository.instance.init();
|
await AccountRepository.instance.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initializeVeilidChat() async {
|
static Future<VeilidChatGlobalInit> initialize() async {
|
||||||
log.info('Initializing Veilid');
|
final veilidChatGlobalInit = VeilidChatGlobalInit._();
|
||||||
await initializeVeilid();
|
|
||||||
log.info('Initializing Repositories');
|
|
||||||
await initializeRepositories();
|
|
||||||
|
|
||||||
eventualInitialized.complete();
|
log.info('Initializing Veilid');
|
||||||
|
await veilidChatGlobalInit._initializeVeilid();
|
||||||
|
log.info('Initializing Repositories');
|
||||||
|
await veilidChatGlobalInit._initializeRepositories();
|
||||||
|
|
||||||
|
return veilidChatGlobalInit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,17 +137,17 @@ class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
|
|||||||
create: (context) => ActiveConversationsBlocMapCubit(
|
create: (context) => ActiveConversationsBlocMapCubit(
|
||||||
activeAccountInfo: widget.activeAccountInfo,
|
activeAccountInfo: widget.activeAccountInfo,
|
||||||
contactListCubit: context.read<ContactListCubit>())
|
contactListCubit: context.read<ContactListCubit>())
|
||||||
..followBloc(context.read<ChatListCubit>())),
|
..follow(context.read<ChatListCubit>())),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => ActiveSingleContactChatBlocMapCubit(
|
create: (context) => ActiveSingleContactChatBlocMapCubit(
|
||||||
activeAccountInfo: widget.activeAccountInfo,
|
activeAccountInfo: widget.activeAccountInfo,
|
||||||
contactListCubit: context.read<ContactListCubit>(),
|
contactListCubit: context.read<ContactListCubit>(),
|
||||||
chatListCubit: context.read<ChatListCubit>())
|
chatListCubit: context.read<ChatListCubit>())
|
||||||
..followBloc(context.read<ActiveConversationsBlocMapCubit>())),
|
..follow(context.read<ActiveConversationsBlocMapCubit>())),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => WaitingInvitationsBlocMapCubit(
|
create: (context) => WaitingInvitationsBlocMapCubit(
|
||||||
activeAccountInfo: widget.activeAccountInfo, account: account)
|
activeAccountInfo: widget.activeAccountInfo, account: account)
|
||||||
..followBloc(context.read<ContactInvitationListCubit>()))
|
..follow(context.read<ContactInvitationListCubit>()))
|
||||||
],
|
],
|
||||||
child: MultiBlocListener(listeners: [
|
child: MultiBlocListener(listeners: [
|
||||||
BlocListener<WaitingInvitationsBlocMapCubit,
|
BlocListener<WaitingInvitationsBlocMapCubit,
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
|
||||||
import 'package:radix_colors/radix_colors.dart';
|
|
||||||
|
|
||||||
import '../tools/tools.dart';
|
|
||||||
|
|
||||||
class IndexPage extends StatefulWidget {
|
|
||||||
const IndexPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<IndexPage> createState() => _IndexPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _IndexPageState extends State<IndexPage> {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
await changeWindowSetup(
|
|
||||||
TitleBarStyle.hidden, OrientationCapability.normal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
final textTheme = theme.textTheme;
|
|
||||||
final monoTextStyle = textTheme.labelSmall!
|
|
||||||
.copyWith(fontFamily: 'Source Code Pro', fontSize: 11);
|
|
||||||
final emojiTextStyle = textTheme.labelSmall!
|
|
||||||
.copyWith(fontFamily: 'Noto Color Emoji', fontSize: 11);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: <Color>[
|
|
||||||
RadixColors.dark.plum.step4,
|
|
||||||
RadixColors.dark.plum.step2,
|
|
||||||
])),
|
|
||||||
child: Center(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(maxHeight: 300),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
// Hack to preload fonts
|
|
||||||
Offstage(child: Text('🧱', style: emojiTextStyle)),
|
|
||||||
// Hack to preload fonts
|
|
||||||
Offstage(child: Text('A', style: monoTextStyle)),
|
|
||||||
// Splash Screen
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
'assets/images/icon.svg',
|
|
||||||
)),
|
|
||||||
Expanded(
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
'assets/images/title.svg',
|
|
||||||
))
|
|
||||||
]))),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
export 'default_app_bar.dart';
|
export 'default_app_bar.dart';
|
||||||
export 'home/home.dart';
|
export 'home/home.dart';
|
||||||
export 'home/home_account_ready/main_pager/main_pager.dart';
|
export 'home/home_account_ready/main_pager/main_pager.dart';
|
||||||
export 'index.dart';
|
export 'splash.dart';
|
||||||
|
53
lib/layout/splash.dart
Normal file
53
lib/layout/splash.dart
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:radix_colors/radix_colors.dart';
|
||||||
|
|
||||||
|
import '../tools/tools.dart';
|
||||||
|
|
||||||
|
class Splash extends StatefulWidget {
|
||||||
|
const Splash({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Splash> createState() => _SplashState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SplashState extends State<Splash> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await changeWindowSetup(
|
||||||
|
TitleBarStyle.hidden, OrientationCapability.normal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: <Color>[
|
||||||
|
RadixColors.dark.plum.step4,
|
||||||
|
RadixColors.dark.plum.step2,
|
||||||
|
])),
|
||||||
|
child: Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 300),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Splash Screen
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/images/icon.svg',
|
||||||
|
)),
|
||||||
|
Expanded(
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/images/title.svg',
|
||||||
|
))
|
||||||
|
]))),
|
||||||
|
);
|
||||||
|
}
|
@ -8,7 +8,6 @@ import 'package:flutter_translate/flutter_translate.dart';
|
|||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
|
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
import 'init.dart';
|
|
||||||
import 'settings/preferences_repository.dart';
|
import 'settings/preferences_repository.dart';
|
||||||
import 'theme/theme.dart';
|
import 'theme/theme.dart';
|
||||||
import 'tools/tools.dart';
|
import 'tools/tools.dart';
|
||||||
@ -45,9 +44,6 @@ void main() async {
|
|||||||
fallbackLocale: 'en_US', supportedLocales: ['en_US']);
|
fallbackLocale: 'en_US', supportedLocales: ['en_US']);
|
||||||
await initializeDateFormatting();
|
await initializeDateFormatting();
|
||||||
|
|
||||||
// Start up Veilid and Veilid processor in the background
|
|
||||||
unawaited(initializeVeilidChat());
|
|
||||||
|
|
||||||
// Run the app
|
// Run the app
|
||||||
// Hot reloads will only restart this part, not Veilid
|
// Hot reloads will only restart this part, not Veilid
|
||||||
runApp(LocalizedApp(localizationDelegate,
|
runApp(LocalizedApp(localizationDelegate,
|
||||||
|
@ -9,7 +9,6 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:stream_transform/stream_transform.dart';
|
import 'package:stream_transform/stream_transform.dart';
|
||||||
|
|
||||||
import '../../../account_manager/account_manager.dart';
|
import '../../../account_manager/account_manager.dart';
|
||||||
import '../../init.dart';
|
|
||||||
import '../../layout/layout.dart';
|
import '../../layout/layout.dart';
|
||||||
import '../../settings/settings.dart';
|
import '../../settings/settings.dart';
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
@ -24,19 +23,10 @@ final _homeNavKey = GlobalKey<NavigatorState>(debugLabel: 'homeNavKey');
|
|||||||
|
|
||||||
class RouterCubit extends Cubit<RouterState> {
|
class RouterCubit extends Cubit<RouterState> {
|
||||||
RouterCubit(AccountRepository accountRepository)
|
RouterCubit(AccountRepository accountRepository)
|
||||||
: super(const RouterState(
|
: super(RouterState(
|
||||||
isInitialized: false,
|
hasAnyAccount: accountRepository.getLocalAccounts().isNotEmpty,
|
||||||
hasAnyAccount: false,
|
|
||||||
hasActiveChat: false,
|
hasActiveChat: false,
|
||||||
)) {
|
)) {
|
||||||
// Watch for changes that the router will care about
|
|
||||||
Future.delayed(Duration.zero, () async {
|
|
||||||
await eventualInitialized.future;
|
|
||||||
emit(state.copyWith(
|
|
||||||
isInitialized: true,
|
|
||||||
hasAnyAccount: accountRepository.getLocalAccounts().isNotEmpty));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subscribe to repository streams
|
// Subscribe to repository streams
|
||||||
_accountRepositorySubscription = accountRepository.stream.listen((event) {
|
_accountRepositorySubscription = accountRepository.stream.listen((event) {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
@ -63,10 +53,6 @@ class RouterCubit extends Cubit<RouterState> {
|
|||||||
|
|
||||||
/// Our application routes
|
/// Our application routes
|
||||||
List<RouteBase> get routes => [
|
List<RouteBase> get routes => [
|
||||||
GoRoute(
|
|
||||||
path: '/',
|
|
||||||
builder: (context, state) => const IndexPage(),
|
|
||||||
),
|
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
navigatorKey: _homeNavKey,
|
navigatorKey: _homeNavKey,
|
||||||
builder: (context, state, child) => HomeShell(
|
builder: (context, state, child) => HomeShell(
|
||||||
@ -75,11 +61,11 @@ class RouterCubit extends Cubit<RouterState> {
|
|||||||
HomeAccountReadyShell(context: context, child: child))),
|
HomeAccountReadyShell(context: context, child: child))),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/home',
|
path: '/',
|
||||||
builder: (context, state) => const HomeAccountReadyMain(),
|
builder: (context, state) => const HomeAccountReadyMain(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/home/chat',
|
path: '/chat',
|
||||||
builder: (context, state) => const HomeAccountReadyChat(),
|
builder: (context, state) => const HomeAccountReadyChat(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -103,17 +89,9 @@ class RouterCubit extends Cubit<RouterState> {
|
|||||||
// No matter where we are, if there's not
|
// No matter where we are, if there's not
|
||||||
|
|
||||||
switch (goRouterState.matchedLocation) {
|
switch (goRouterState.matchedLocation) {
|
||||||
case '/':
|
|
||||||
|
|
||||||
// Wait for initialization to complete
|
|
||||||
if (!eventualInitialized.isCompleted) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return state.hasAnyAccount ? '/home' : '/new_account';
|
|
||||||
case '/new_account':
|
case '/new_account':
|
||||||
return state.hasAnyAccount ? '/home' : null;
|
return state.hasAnyAccount ? '/' : null;
|
||||||
case '/home':
|
case '/':
|
||||||
if (!state.hasAnyAccount) {
|
if (!state.hasAnyAccount) {
|
||||||
return '/new_account';
|
return '/new_account';
|
||||||
}
|
}
|
||||||
@ -123,11 +101,11 @@ class RouterCubit extends Cubit<RouterState> {
|
|||||||
tabletLandscape: false,
|
tabletLandscape: false,
|
||||||
desktop: false)) {
|
desktop: false)) {
|
||||||
if (state.hasActiveChat) {
|
if (state.hasActiveChat) {
|
||||||
return '/home/chat';
|
return '/chat';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
case '/home/chat':
|
case '/chat':
|
||||||
if (!state.hasAnyAccount) {
|
if (!state.hasAnyAccount) {
|
||||||
return '/new_account';
|
return '/new_account';
|
||||||
}
|
}
|
||||||
@ -137,10 +115,10 @@ class RouterCubit extends Cubit<RouterState> {
|
|||||||
tabletLandscape: false,
|
tabletLandscape: false,
|
||||||
desktop: false)) {
|
desktop: false)) {
|
||||||
if (!state.hasActiveChat) {
|
if (!state.hasActiveChat) {
|
||||||
return '/home';
|
return '/';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return '/home';
|
return '/';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
case '/settings':
|
case '/settings':
|
||||||
|
@ -20,7 +20,6 @@ RouterState _$RouterStateFromJson(Map<String, dynamic> json) {
|
|||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$RouterState {
|
mixin _$RouterState {
|
||||||
bool get isInitialized => throw _privateConstructorUsedError;
|
|
||||||
bool get hasAnyAccount => throw _privateConstructorUsedError;
|
bool get hasAnyAccount => throw _privateConstructorUsedError;
|
||||||
bool get hasActiveChat => throw _privateConstructorUsedError;
|
bool get hasActiveChat => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
@ -36,7 +35,7 @@ abstract class $RouterStateCopyWith<$Res> {
|
|||||||
RouterState value, $Res Function(RouterState) then) =
|
RouterState value, $Res Function(RouterState) then) =
|
||||||
_$RouterStateCopyWithImpl<$Res, RouterState>;
|
_$RouterStateCopyWithImpl<$Res, RouterState>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({bool isInitialized, bool hasAnyAccount, bool hasActiveChat});
|
$Res call({bool hasAnyAccount, bool hasActiveChat});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -52,15 +51,10 @@ class _$RouterStateCopyWithImpl<$Res, $Val extends RouterState>
|
|||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? isInitialized = null,
|
|
||||||
Object? hasAnyAccount = null,
|
Object? hasAnyAccount = null,
|
||||||
Object? hasActiveChat = null,
|
Object? hasActiveChat = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
isInitialized: null == isInitialized
|
|
||||||
? _value.isInitialized
|
|
||||||
: isInitialized // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
hasAnyAccount: null == hasAnyAccount
|
hasAnyAccount: null == hasAnyAccount
|
||||||
? _value.hasAnyAccount
|
? _value.hasAnyAccount
|
||||||
: hasAnyAccount // ignore: cast_nullable_to_non_nullable
|
: hasAnyAccount // ignore: cast_nullable_to_non_nullable
|
||||||
@ -81,7 +75,7 @@ abstract class _$$RouterStateImplCopyWith<$Res>
|
|||||||
__$$RouterStateImplCopyWithImpl<$Res>;
|
__$$RouterStateImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({bool isInitialized, bool hasAnyAccount, bool hasActiveChat});
|
$Res call({bool hasAnyAccount, bool hasActiveChat});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -95,15 +89,10 @@ class __$$RouterStateImplCopyWithImpl<$Res>
|
|||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? isInitialized = null,
|
|
||||||
Object? hasAnyAccount = null,
|
Object? hasAnyAccount = null,
|
||||||
Object? hasActiveChat = null,
|
Object? hasActiveChat = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$RouterStateImpl(
|
return _then(_$RouterStateImpl(
|
||||||
isInitialized: null == isInitialized
|
|
||||||
? _value.isInitialized
|
|
||||||
: isInitialized // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
hasAnyAccount: null == hasAnyAccount
|
hasAnyAccount: null == hasAnyAccount
|
||||||
? _value.hasAnyAccount
|
? _value.hasAnyAccount
|
||||||
: hasAnyAccount // ignore: cast_nullable_to_non_nullable
|
: hasAnyAccount // ignore: cast_nullable_to_non_nullable
|
||||||
@ -120,15 +109,11 @@ class __$$RouterStateImplCopyWithImpl<$Res>
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState {
|
class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState {
|
||||||
const _$RouterStateImpl(
|
const _$RouterStateImpl(
|
||||||
{required this.isInitialized,
|
{required this.hasAnyAccount, required this.hasActiveChat});
|
||||||
required this.hasAnyAccount,
|
|
||||||
required this.hasActiveChat});
|
|
||||||
|
|
||||||
factory _$RouterStateImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$RouterStateImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$RouterStateImplFromJson(json);
|
_$$RouterStateImplFromJson(json);
|
||||||
|
|
||||||
@override
|
|
||||||
final bool isInitialized;
|
|
||||||
@override
|
@override
|
||||||
final bool hasAnyAccount;
|
final bool hasAnyAccount;
|
||||||
@override
|
@override
|
||||||
@ -136,7 +121,7 @@ class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||||
return 'RouterState(isInitialized: $isInitialized, hasAnyAccount: $hasAnyAccount, hasActiveChat: $hasActiveChat)';
|
return 'RouterState(hasAnyAccount: $hasAnyAccount, hasActiveChat: $hasActiveChat)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -144,7 +129,6 @@ class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState {
|
|||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties
|
properties
|
||||||
..add(DiagnosticsProperty('type', 'RouterState'))
|
..add(DiagnosticsProperty('type', 'RouterState'))
|
||||||
..add(DiagnosticsProperty('isInitialized', isInitialized))
|
|
||||||
..add(DiagnosticsProperty('hasAnyAccount', hasAnyAccount))
|
..add(DiagnosticsProperty('hasAnyAccount', hasAnyAccount))
|
||||||
..add(DiagnosticsProperty('hasActiveChat', hasActiveChat));
|
..add(DiagnosticsProperty('hasActiveChat', hasActiveChat));
|
||||||
}
|
}
|
||||||
@ -154,8 +138,6 @@ class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState {
|
|||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$RouterStateImpl &&
|
other is _$RouterStateImpl &&
|
||||||
(identical(other.isInitialized, isInitialized) ||
|
|
||||||
other.isInitialized == isInitialized) &&
|
|
||||||
(identical(other.hasAnyAccount, hasAnyAccount) ||
|
(identical(other.hasAnyAccount, hasAnyAccount) ||
|
||||||
other.hasAnyAccount == hasAnyAccount) &&
|
other.hasAnyAccount == hasAnyAccount) &&
|
||||||
(identical(other.hasActiveChat, hasActiveChat) ||
|
(identical(other.hasActiveChat, hasActiveChat) ||
|
||||||
@ -164,8 +146,7 @@ class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState {
|
|||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => Object.hash(runtimeType, hasAnyAccount, hasActiveChat);
|
||||||
Object.hash(runtimeType, isInitialized, hasAnyAccount, hasActiveChat);
|
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
@ -183,15 +164,12 @@ class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState {
|
|||||||
|
|
||||||
abstract class _RouterState implements RouterState {
|
abstract class _RouterState implements RouterState {
|
||||||
const factory _RouterState(
|
const factory _RouterState(
|
||||||
{required final bool isInitialized,
|
{required final bool hasAnyAccount,
|
||||||
required final bool hasAnyAccount,
|
|
||||||
required final bool hasActiveChat}) = _$RouterStateImpl;
|
required final bool hasActiveChat}) = _$RouterStateImpl;
|
||||||
|
|
||||||
factory _RouterState.fromJson(Map<String, dynamic> json) =
|
factory _RouterState.fromJson(Map<String, dynamic> json) =
|
||||||
_$RouterStateImpl.fromJson;
|
_$RouterStateImpl.fromJson;
|
||||||
|
|
||||||
@override
|
|
||||||
bool get isInitialized;
|
|
||||||
@override
|
@override
|
||||||
bool get hasAnyAccount;
|
bool get hasAnyAccount;
|
||||||
@override
|
@override
|
||||||
|
@ -8,14 +8,12 @@ part of 'router_cubit.dart';
|
|||||||
|
|
||||||
_$RouterStateImpl _$$RouterStateImplFromJson(Map<String, dynamic> json) =>
|
_$RouterStateImpl _$$RouterStateImplFromJson(Map<String, dynamic> json) =>
|
||||||
_$RouterStateImpl(
|
_$RouterStateImpl(
|
||||||
isInitialized: json['is_initialized'] as bool,
|
|
||||||
hasAnyAccount: json['has_any_account'] as bool,
|
hasAnyAccount: json['has_any_account'] as bool,
|
||||||
hasActiveChat: json['has_active_chat'] as bool,
|
hasActiveChat: json['has_active_chat'] as bool,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$RouterStateImplToJson(_$RouterStateImpl instance) =>
|
Map<String, dynamic> _$$RouterStateImplToJson(_$RouterStateImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'is_initialized': instance.isInitialized,
|
|
||||||
'has_any_account': instance.hasAnyAccount,
|
'has_any_account': instance.hasAnyAccount,
|
||||||
'has_active_chat': instance.hasActiveChat,
|
'has_active_chat': instance.hasActiveChat,
|
||||||
};
|
};
|
||||||
|
@ -3,8 +3,7 @@ part of 'router_cubit.dart';
|
|||||||
@freezed
|
@freezed
|
||||||
class RouterState with _$RouterState {
|
class RouterState with _$RouterState {
|
||||||
const factory RouterState(
|
const factory RouterState(
|
||||||
{required bool isInitialized,
|
{required bool hasAnyAccount,
|
||||||
required bool hasAnyAccount,
|
|
||||||
required bool hasActiveChat}) = _RouterState;
|
required bool hasActiveChat}) = _RouterState;
|
||||||
|
|
||||||
factory RouterState.fromJson(dynamic json) =>
|
factory RouterState.fromJson(dynamic json) =>
|
||||||
|
@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import 'init.dart';
|
|
||||||
import 'veilid_processor/veilid_processor.dart';
|
import 'veilid_processor/veilid_processor.dart';
|
||||||
|
|
||||||
class BackgroundTicker extends StatefulWidget {
|
class BackgroundTicker extends StatefulWidget {
|
||||||
@ -53,10 +52,6 @@ class BackgroundTickerState extends State<BackgroundTicker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onTick() async {
|
Future<void> _onTick() async {
|
||||||
// Don't tick until we are initialized
|
|
||||||
if (!eventualInitialized.isCompleted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!ProcessorRepository
|
if (!ProcessorRepository
|
||||||
.instance.processorConnectionState.isPublicInternetReady) {
|
.instance.processorConnectionState.isPublicInternetReady) {
|
||||||
return;
|
return;
|
||||||
|
@ -3,7 +3,9 @@ library;
|
|||||||
|
|
||||||
export 'src/async_tag_lock.dart';
|
export 'src/async_tag_lock.dart';
|
||||||
export 'src/async_value.dart';
|
export 'src/async_value.dart';
|
||||||
|
export 'src/delayed_wait_set.dart';
|
||||||
export 'src/serial_future.dart';
|
export 'src/serial_future.dart';
|
||||||
export 'src/single_future.dart';
|
export 'src/single_future.dart';
|
||||||
export 'src/single_state_processor.dart';
|
export 'src/single_state_processor.dart';
|
||||||
export 'src/single_stateless_processor.dart';
|
export 'src/single_stateless_processor.dart';
|
||||||
|
export 'src/wait_set.dart';
|
||||||
|
18
packages/async_tools/lib/src/delayed_wait_set.dart
Normal file
18
packages/async_tools/lib/src/delayed_wait_set.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
class DelayedWaitSet {
|
||||||
|
DelayedWaitSet();
|
||||||
|
|
||||||
|
void add(Future<void> Function() closure) {
|
||||||
|
_closures.add(closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> call() async {
|
||||||
|
final futures = _closures.map((c) => c()).toList();
|
||||||
|
_closures = [];
|
||||||
|
if (futures.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await futures.wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Future<void> Function()> _closures = [];
|
||||||
|
}
|
@ -4,14 +4,14 @@ import 'async_tag_lock.dart';
|
|||||||
|
|
||||||
AsyncTagLock<Object> _keys = AsyncTagLock();
|
AsyncTagLock<Object> _keys = AsyncTagLock();
|
||||||
|
|
||||||
// Process a single future at a time per tag
|
/// Process a single future at a time per tag
|
||||||
//
|
///
|
||||||
// The closure function is called to produce the future that is to be executed.
|
/// The closure function is called to produce the future that is to be executed.
|
||||||
// If a future with a particular tag is still executing, the onBusy callback
|
/// If a future with a particular tag is still executing, the onBusy callback
|
||||||
// is called.
|
/// is called.
|
||||||
// When a tagged singleFuture finishes executing, the onDone callback is called.
|
/// When a tagged singleFuture finishes executing, the onDone callback is called.
|
||||||
// If an unhandled exception happens in the closure future, the onError callback
|
/// If an unhandled exception happens in the closure future, the onError callback
|
||||||
// is called.
|
/// is called.
|
||||||
void singleFuture<T>(Object tag, Future<T> Function() closure,
|
void singleFuture<T>(Object tag, Future<T> Function() closure,
|
||||||
{void Function()? onBusy,
|
{void Function()? onBusy,
|
||||||
void Function(T)? onDone,
|
void Function(T)? onDone,
|
||||||
@ -40,3 +40,6 @@ void singleFuture<T>(Object tag, Future<T> Function() closure,
|
|||||||
}
|
}
|
||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> singleFuturePause(Object tag) async => _keys.lockTag(tag);
|
||||||
|
void singleFutureResume(Object tag) => _keys.unlockTag(tag);
|
||||||
|
@ -22,15 +22,19 @@ class SingleStateProcessor<State> {
|
|||||||
|
|
||||||
singleFuture(this, () async {
|
singleFuture(this, () async {
|
||||||
var newState = newInputState;
|
var newState = newInputState;
|
||||||
|
var newClosure = closure;
|
||||||
var done = false;
|
var done = false;
|
||||||
while (!done) {
|
while (!done) {
|
||||||
await closure(newState);
|
await newClosure(newState);
|
||||||
|
|
||||||
// See if there's another state change to process
|
// See if there's another state change to process
|
||||||
final next = _nextState;
|
final nextState = _nextState;
|
||||||
|
final nextClosure = _nextClosure;
|
||||||
_nextState = null;
|
_nextState = null;
|
||||||
if (next != null) {
|
_nextClosure = null;
|
||||||
newState = next;
|
if (nextState != null) {
|
||||||
|
newState = nextState;
|
||||||
|
newClosure = nextClosure!;
|
||||||
} else {
|
} else {
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
@ -38,8 +42,26 @@ class SingleStateProcessor<State> {
|
|||||||
}, onBusy: () {
|
}, onBusy: () {
|
||||||
// Keep this state until we process again
|
// Keep this state until we process again
|
||||||
_nextState = newInputState;
|
_nextState = newInputState;
|
||||||
|
_nextClosure = closure;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> pause() => singleFuturePause(this);
|
||||||
|
Future<void> resume() async {
|
||||||
|
// Process any next state before resuming the singlefuture
|
||||||
|
try {
|
||||||
|
final nextState = _nextState;
|
||||||
|
final nextClosure = _nextClosure;
|
||||||
|
_nextState = null;
|
||||||
|
_nextClosure = null;
|
||||||
|
if (nextState != null) {
|
||||||
|
await nextClosure!(nextState);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
singleFutureResume(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
State? _nextState;
|
State? _nextState;
|
||||||
|
Future<void> Function(State)? _nextClosure;
|
||||||
}
|
}
|
||||||
|
18
packages/async_tools/lib/src/wait_set.dart
Normal file
18
packages/async_tools/lib/src/wait_set.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
class WaitSet {
|
||||||
|
WaitSet();
|
||||||
|
|
||||||
|
void add(Future<void> Function() closure) {
|
||||||
|
_futures.add(Future.delayed(Duration.zero, closure));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> call() async {
|
||||||
|
final futures = _futures;
|
||||||
|
_futures = [];
|
||||||
|
if (futures.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await futures.wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Future<void>> _futures = [];
|
||||||
|
}
|
@ -6,6 +6,6 @@ export 'src/bloc_busy_wrapper.dart';
|
|||||||
export 'src/bloc_map_cubit.dart';
|
export 'src/bloc_map_cubit.dart';
|
||||||
export 'src/bloc_tools_extension.dart';
|
export 'src/bloc_tools_extension.dart';
|
||||||
export 'src/future_cubit.dart';
|
export 'src/future_cubit.dart';
|
||||||
export 'src/state_follower.dart';
|
export 'src/state_map_follower.dart';
|
||||||
export 'src/stream_wrapper_cubit.dart';
|
export 'src/stream_wrapper_cubit.dart';
|
||||||
export 'src/transformer_cubit.dart';
|
export 'src/transformer_cubit.dart';
|
||||||
|
@ -14,6 +14,7 @@ class AsyncTransformerCubit<T, S> extends Cubit<AsyncValue<T>> {
|
|||||||
_asyncTransform(input.state);
|
_asyncTransform(input.state);
|
||||||
_subscription = input.stream.listen(_asyncTransform);
|
_subscription = input.stream.listen(_asyncTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _asyncTransform(AsyncValue<S> newInputState) {
|
void _asyncTransform(AsyncValue<S> newInputState) {
|
||||||
_singleStateProcessor.updateState(newInputState, (newState) async {
|
_singleStateProcessor.updateState(newInputState, (newState) async {
|
||||||
// Emit the transformed state
|
// Emit the transformed state
|
||||||
|
@ -3,6 +3,9 @@ import 'dart:async';
|
|||||||
import 'package:async_tools/async_tools.dart';
|
import 'package:async_tools/async_tools.dart';
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
import 'state_map_follower.dart';
|
||||||
|
|
||||||
typedef BlocMapState<K, S> = IMap<K, S>;
|
typedef BlocMapState<K, S> = IMap<K, S>;
|
||||||
|
|
||||||
@ -18,14 +21,15 @@ class _ItemEntry<S, B> {
|
|||||||
// cubits.
|
// cubits.
|
||||||
//
|
//
|
||||||
// K = Key type for the bloc map, used to look up some mapped cubit
|
// K = Key type for the bloc map, used to look up some mapped cubit
|
||||||
// S = State type for the value, keys will look up values of this type
|
// V = State type for the value, keys will look up values of this type
|
||||||
// B = Bloc/cubit type for the value, output states of type S
|
// B = Bloc/cubit type for the value, output states of type S
|
||||||
abstract class BlocMapCubit<K, S, B extends BlocBase<S>>
|
abstract class BlocMapCubit<K, V, B extends BlocBase<V>>
|
||||||
extends Cubit<BlocMapState<K, S>> {
|
extends Cubit<BlocMapState<K, V>>
|
||||||
|
with StateMapFollowable<BlocMapState<K, V>, K, V> {
|
||||||
BlocMapCubit()
|
BlocMapCubit()
|
||||||
: _entries = {},
|
: _entries = {},
|
||||||
_tagLock = AsyncTagLock(),
|
_tagLock = AsyncTagLock(),
|
||||||
super(IMap<K, S>());
|
super(IMap<K, V>());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
@ -34,6 +38,13 @@ abstract class BlocMapCubit<K, S, B extends BlocBase<S>>
|
|||||||
await super.close();
|
await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
@override
|
||||||
|
// ignore: unnecessary_overrides
|
||||||
|
void emit(BlocMapState<K, V> state) {
|
||||||
|
super.emit(state);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> add(MapEntry<K, B> Function() create) {
|
Future<void> add(MapEntry<K, B> Function() create) {
|
||||||
// Create new element
|
// Create new element
|
||||||
final newElement = create();
|
final newElement = create();
|
||||||
@ -56,7 +67,7 @@ abstract class BlocMapCubit<K, S, B extends BlocBase<S>>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addState(K key, S value) =>
|
Future<void> addState(K key, V value) =>
|
||||||
_tagLock.protect(key, closure: () async {
|
_tagLock.protect(key, closure: () async {
|
||||||
// Remove entry with the same key if it exists
|
// Remove entry with the same key if it exists
|
||||||
await _internalRemove(key);
|
await _internalRemove(key);
|
||||||
@ -107,6 +118,10 @@ abstract class BlocMapCubit<K, S, B extends BlocBase<S>>
|
|||||||
return closure(entry.bloc);
|
return closure(entry.bloc);
|
||||||
});
|
});
|
||||||
|
|
||||||
final Map<K, _ItemEntry<S, B>> _entries;
|
/// StateMapFollowable /////////////////////////
|
||||||
|
@override
|
||||||
|
IMap<K, V> getStateMap(BlocMapState<K, V> s) => s;
|
||||||
|
|
||||||
|
final Map<K, _ItemEntry<V, B>> _entries;
|
||||||
final AsyncTagLock<K> _tagLock;
|
final AsyncTagLock<K> _tagLock;
|
||||||
}
|
}
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:async_tools/async_tools.dart';
|
|
||||||
import 'package:bloc/bloc.dart';
|
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
|
||||||
|
|
||||||
// Mixin that automatically keeps two blocs/cubits in sync with each other
|
|
||||||
// Useful for having a BlocMapCubit 'follow' the state of another input cubit.
|
|
||||||
// As the state of the input cubit changes, the BlocMapCubit can add/remove
|
|
||||||
// mapped Cubits that automatically process the input state reactively.
|
|
||||||
//
|
|
||||||
// S = Input state type
|
|
||||||
// K = Key derived from elements of input state
|
|
||||||
// V = Value derived from elements of input state
|
|
||||||
abstract mixin class StateFollower<S extends Object, K, V> {
|
|
||||||
void follow({
|
|
||||||
required S initialInputState,
|
|
||||||
required Stream<S> stream,
|
|
||||||
}) {
|
|
||||||
//
|
|
||||||
_lastInputStateMap = IMap();
|
|
||||||
_updateFollow(initialInputState);
|
|
||||||
_subscription = stream.listen(_updateFollow);
|
|
||||||
}
|
|
||||||
|
|
||||||
void followBloc<B extends BlocBase<S>>(B bloc) =>
|
|
||||||
follow(initialInputState: bloc.state, stream: bloc.stream);
|
|
||||||
|
|
||||||
Future<void> close() async {
|
|
||||||
await _subscription.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
IMap<K, V> getStateMap(S state);
|
|
||||||
Future<void> removeFromState(K key);
|
|
||||||
Future<void> updateState(K key, V value);
|
|
||||||
|
|
||||||
void _updateFollow(S newInputState) {
|
|
||||||
_singleStateProcessor.updateState(getStateMap(newInputState),
|
|
||||||
(newStateMap) async {
|
|
||||||
for (final k in _lastInputStateMap.keys) {
|
|
||||||
if (!newStateMap.containsKey(k)) {
|
|
||||||
// deleted
|
|
||||||
await removeFromState(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (final newEntry in newStateMap.entries) {
|
|
||||||
final v = _lastInputStateMap.get(newEntry.key);
|
|
||||||
if (v == null || v != newEntry.value) {
|
|
||||||
// added or changed
|
|
||||||
await updateState(newEntry.key, newEntry.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep this state map for the next time
|
|
||||||
_lastInputStateMap = newStateMap;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
late IMap<K, V> _lastInputStateMap;
|
|
||||||
late final StreamSubscription<S> _subscription;
|
|
||||||
final SingleStateProcessor<IMap<K, V>> _singleStateProcessor =
|
|
||||||
SingleStateProcessor();
|
|
||||||
}
|
|
125
packages/bloc_tools/lib/src/state_map_follower.dart
Normal file
125
packages/bloc_tools/lib/src/state_map_follower.dart
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:async_tools/async_tools.dart';
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
/// Mixin that automatically keeps two blocs/cubits in sync with each other
|
||||||
|
/// Useful for having a BlocMapCubit 'follow' the state of another input cubit.
|
||||||
|
/// As the state of the input cubit changes, the BlocMapCubit can add/remove
|
||||||
|
/// mapped Cubits that automatically process the input state reactively.
|
||||||
|
///
|
||||||
|
/// S = Input state type
|
||||||
|
/// K = Key derived from elements of input state
|
||||||
|
/// V = Value derived from elements of input state
|
||||||
|
mixin StateMapFollower<S extends Object, K, V> on Closable {
|
||||||
|
void follow(StateMapFollowable<S, K, V> followable) {
|
||||||
|
assert(_following == null, 'can only follow one followable at a time');
|
||||||
|
_following = followable;
|
||||||
|
_lastInputStateMap = IMap();
|
||||||
|
_subscription = followable.registerFollower(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> unfollow() async {
|
||||||
|
await _subscription?.cancel();
|
||||||
|
_subscription = null;
|
||||||
|
_following?.unregisterFollower(this);
|
||||||
|
_following = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@mustCallSuper
|
||||||
|
Future<void> close() async {
|
||||||
|
await unfollow();
|
||||||
|
await super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeFromState(K key);
|
||||||
|
Future<void> updateState(K key, V value);
|
||||||
|
|
||||||
|
void _updateFollow(IMap<K, V> newInputState) {
|
||||||
|
final following = _following;
|
||||||
|
if (following == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_singleStateProcessor.updateState(newInputState, (newStateMap) async {
|
||||||
|
for (final k in _lastInputStateMap.keys) {
|
||||||
|
if (!newStateMap.containsKey(k)) {
|
||||||
|
// deleted
|
||||||
|
await removeFromState(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final newEntry in newStateMap.entries) {
|
||||||
|
final v = _lastInputStateMap.get(newEntry.key);
|
||||||
|
if (v == null || v != newEntry.value) {
|
||||||
|
// added or changed
|
||||||
|
await updateState(newEntry.key, newEntry.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep this state map for the next time
|
||||||
|
_lastInputStateMap = newStateMap;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
StateMapFollowable<S, K, V>? _following;
|
||||||
|
late IMap<K, V> _lastInputStateMap;
|
||||||
|
late StreamSubscription<IMap<K, V>>? _subscription;
|
||||||
|
final SingleStateProcessor<IMap<K, V>> _singleStateProcessor =
|
||||||
|
SingleStateProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface that allows a StateMapFollower to follow some other class's
|
||||||
|
/// state changes
|
||||||
|
abstract mixin class StateMapFollowable<S extends Object, K, V>
|
||||||
|
implements StateStreamable<S> {
|
||||||
|
IMap<K, V> getStateMap(S state);
|
||||||
|
|
||||||
|
StreamSubscription<IMap<K, V>> registerFollower(
|
||||||
|
StateMapFollower<S, K, V> follower) {
|
||||||
|
final stateMapTransformer = StreamTransformer<S, IMap<K, V>>.fromHandlers(
|
||||||
|
handleData: (d, s) => s.add(getStateMap(d)));
|
||||||
|
|
||||||
|
if (_followers.isEmpty) {
|
||||||
|
// start transforming stream
|
||||||
|
_transformedStream = stream.transform(stateMapTransformer);
|
||||||
|
}
|
||||||
|
_followers.add(follower);
|
||||||
|
follower._updateFollow(getStateMap(state));
|
||||||
|
return _transformedStream!.listen((s) => follower._updateFollow(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
void unregisterFollower(StateMapFollower<S, K, V> follower) {
|
||||||
|
_followers.remove(follower);
|
||||||
|
if (_followers.isEmpty) {
|
||||||
|
// stop transforming stream
|
||||||
|
_transformedStream = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T> syncFollowers<T>(Future<T> Function() closure) async {
|
||||||
|
// pause all followers
|
||||||
|
await _followers.map((f) => f._singleStateProcessor.pause()).wait;
|
||||||
|
|
||||||
|
// run closure
|
||||||
|
final out = await closure();
|
||||||
|
|
||||||
|
// resume all followers and wait for current state map to be updated
|
||||||
|
final resumeState = getStateMap(state);
|
||||||
|
await _followers.map((f) async {
|
||||||
|
// Ensure the latest state has been updated
|
||||||
|
try {
|
||||||
|
f._updateFollow(resumeState);
|
||||||
|
} finally {
|
||||||
|
// Resume processing of the follower
|
||||||
|
await f._singleStateProcessor.resume();
|
||||||
|
}
|
||||||
|
}).wait;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<IMap<K, V>>? _transformedStream;
|
||||||
|
final List<StateMapFollower<S, K, V>> _followers = [];
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
export 'default_dht_record_cubit.dart';
|
||||||
export 'dht_record_crypto.dart';
|
export 'dht_record_crypto.dart';
|
||||||
export 'dht_record_cubit.dart';
|
export 'dht_record_cubit.dart';
|
||||||
export 'dht_record_pool.dart';
|
export 'dht_record_pool.dart';
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import '../../../veilid_support.dart';
|
||||||
|
|
||||||
|
/// Cubit that watches the default subkey value of a dhtrecord
|
||||||
|
class DefaultDHTRecordCubit<T> extends DHTRecordCubit<T> {
|
||||||
|
DefaultDHTRecordCubit({
|
||||||
|
required super.open,
|
||||||
|
required T Function(List<int> data) decodeState,
|
||||||
|
}) : super(
|
||||||
|
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) =>
|
||||||
|
(record) async {
|
||||||
|
final initialData = await record.get();
|
||||||
|
if (initialData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return decodeState(initialData);
|
||||||
|
};
|
||||||
|
|
||||||
|
static StateFunction<T> _makeStateFunction<T>(
|
||||||
|
T Function(List<int> data) decodeState) =>
|
||||||
|
(record, subkeys, updatedata) async {
|
||||||
|
final defaultSubkey = record.subkeyOrDefault(-1);
|
||||||
|
if (subkeys.containsSubkey(defaultSubkey)) {
|
||||||
|
final Uint8List data;
|
||||||
|
final firstSubkey = subkeys.firstOrNull!.low;
|
||||||
|
if (firstSubkey != defaultSubkey || updatedata == null) {
|
||||||
|
final maybeData = await record.get(forceRefresh: true);
|
||||||
|
if (maybeData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
data = maybeData;
|
||||||
|
} else {
|
||||||
|
data = updatedata;
|
||||||
|
}
|
||||||
|
final newState = decodeState(data);
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
static WatchFunction _makeWatchFunction() => (record) async {
|
||||||
|
final defaultSubkey = record.subkeyOrDefault(-1);
|
||||||
|
await record.watch(subkeys: [ValueSubkeyRange.single(defaultSubkey)]);
|
||||||
|
};
|
||||||
|
|
||||||
|
Future<void> refreshDefault() async {
|
||||||
|
await initWait();
|
||||||
|
|
||||||
|
final defaultSubkey = record.subkeyOrDefault(-1);
|
||||||
|
await refresh([ValueSubkeyRange(low: defaultSubkey, high: defaultSubkey)]);
|
||||||
|
}
|
||||||
|
}
|
@ -16,12 +16,13 @@ class DHTRecordWatchChange extends Equatable {
|
|||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
class DHTRecord {
|
class DHTRecord {
|
||||||
DHTRecord(
|
DHTRecord._(
|
||||||
{required VeilidRoutingContext routingContext,
|
{required VeilidRoutingContext routingContext,
|
||||||
required SharedDHTRecordData sharedDHTRecordData,
|
required SharedDHTRecordData sharedDHTRecordData,
|
||||||
required int defaultSubkey,
|
required int defaultSubkey,
|
||||||
required KeyPair? writer,
|
required KeyPair? writer,
|
||||||
required DHTRecordCrypto crypto})
|
required DHTRecordCrypto crypto,
|
||||||
|
required this.debugName})
|
||||||
: _crypto = crypto,
|
: _crypto = crypto,
|
||||||
_routingContext = routingContext,
|
_routingContext = routingContext,
|
||||||
_defaultSubkey = defaultSubkey,
|
_defaultSubkey = defaultSubkey,
|
||||||
@ -34,6 +35,7 @@ class DHTRecord {
|
|||||||
final int _defaultSubkey;
|
final int _defaultSubkey;
|
||||||
final KeyPair? _writer;
|
final KeyPair? _writer;
|
||||||
final DHTRecordCrypto _crypto;
|
final DHTRecordCrypto _crypto;
|
||||||
|
final String debugName;
|
||||||
|
|
||||||
bool _open;
|
bool _open;
|
||||||
@internal
|
@internal
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:async_tools/async_tools.dart';
|
import 'package:async_tools/async_tools.dart';
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../../../veilid_support.dart';
|
import '../../../veilid_support.dart';
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ class DHTRecordCubit<T> extends Cubit<AsyncValue<T>> {
|
|||||||
}) : _wantsCloseRecord = false,
|
}) : _wantsCloseRecord = false,
|
||||||
_stateFunction = stateFunction,
|
_stateFunction = stateFunction,
|
||||||
super(const AsyncValue.loading()) {
|
super(const AsyncValue.loading()) {
|
||||||
Future.delayed(Duration.zero, () async {
|
initWait.add(() async {
|
||||||
// Do record open/create
|
// Do record open/create
|
||||||
_record = await open();
|
_record = await open();
|
||||||
_wantsCloseRecord = true;
|
_wantsCloseRecord = true;
|
||||||
@ -73,6 +74,7 @@ class DHTRecordCubit<T> extends Cubit<AsyncValue<T>> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
|
await initWait();
|
||||||
await _record.cancelWatch();
|
await _record.cancelWatch();
|
||||||
await _subscription?.cancel();
|
await _subscription?.cancel();
|
||||||
_subscription = null;
|
_subscription = null;
|
||||||
@ -84,6 +86,8 @@ class DHTRecordCubit<T> extends Cubit<AsyncValue<T>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refresh(List<ValueSubkeyRange> subkeys) async {
|
Future<void> refresh(List<ValueSubkeyRange> subkeys) async {
|
||||||
|
await initWait();
|
||||||
|
|
||||||
var updateSubkeys = [...subkeys];
|
var updateSubkeys = [...subkeys];
|
||||||
|
|
||||||
for (final skr in subkeys) {
|
for (final skr in subkeys) {
|
||||||
@ -107,69 +111,11 @@ class DHTRecordCubit<T> extends Cubit<AsyncValue<T>> {
|
|||||||
|
|
||||||
DHTRecord get record => _record;
|
DHTRecord get record => _record;
|
||||||
|
|
||||||
|
@protected
|
||||||
|
final WaitSet initWait = WaitSet();
|
||||||
|
|
||||||
StreamSubscription<DHTRecordWatchChange>? _subscription;
|
StreamSubscription<DHTRecordWatchChange>? _subscription;
|
||||||
late DHTRecord _record;
|
late DHTRecord _record;
|
||||||
bool _wantsCloseRecord;
|
bool _wantsCloseRecord;
|
||||||
final StateFunction<T> _stateFunction;
|
final StateFunction<T> _stateFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cubit that watches the default subkey value of a dhtrecord
|
|
||||||
class DefaultDHTRecordCubit<T> extends DHTRecordCubit<T> {
|
|
||||||
DefaultDHTRecordCubit({
|
|
||||||
required super.open,
|
|
||||||
required T Function(List<int> data) decodeState,
|
|
||||||
}) : super(
|
|
||||||
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) =>
|
|
||||||
(record) async {
|
|
||||||
final initialData = await record.get();
|
|
||||||
if (initialData == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return decodeState(initialData);
|
|
||||||
};
|
|
||||||
|
|
||||||
static StateFunction<T> _makeStateFunction<T>(
|
|
||||||
T Function(List<int> data) decodeState) =>
|
|
||||||
(record, subkeys, updatedata) async {
|
|
||||||
final defaultSubkey = record.subkeyOrDefault(-1);
|
|
||||||
if (subkeys.containsSubkey(defaultSubkey)) {
|
|
||||||
final Uint8List data;
|
|
||||||
final firstSubkey = subkeys.firstOrNull!.low;
|
|
||||||
if (firstSubkey != defaultSubkey || updatedata == null) {
|
|
||||||
final maybeData = await record.get(forceRefresh: true);
|
|
||||||
if (maybeData == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
data = maybeData;
|
|
||||||
} else {
|
|
||||||
data = updatedata;
|
|
||||||
}
|
|
||||||
final newState = decodeState(data);
|
|
||||||
return newState;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
static WatchFunction _makeWatchFunction() => (record) async {
|
|
||||||
final defaultSubkey = record.subkeyOrDefault(-1);
|
|
||||||
await record.watch(subkeys: [ValueSubkeyRange.single(defaultSubkey)]);
|
|
||||||
};
|
|
||||||
|
|
||||||
Future<void> refreshDefault() async {
|
|
||||||
final defaultSubkey = _record.subkeyOrDefault(-1);
|
|
||||||
await refresh([ValueSubkeyRange(low: defaultSubkey, high: defaultSubkey)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -18,14 +18,16 @@ const int watchBackoffMultiplier = 2;
|
|||||||
const int watchBackoffMax = 30;
|
const int watchBackoffMax = 30;
|
||||||
|
|
||||||
/// Record pool that managed DHTRecords and allows for tagged deletion
|
/// Record pool that managed DHTRecords and allows for tagged deletion
|
||||||
|
/// String versions of keys due to IMap<> json unsupported in key
|
||||||
@freezed
|
@freezed
|
||||||
class DHTRecordPoolAllocations with _$DHTRecordPoolAllocations {
|
class DHTRecordPoolAllocations with _$DHTRecordPoolAllocations {
|
||||||
const factory DHTRecordPoolAllocations({
|
const factory DHTRecordPoolAllocations({
|
||||||
required IMap<String, ISet<TypedKey>>
|
@Default(IMapConst<String, ISet<TypedKey>>({}))
|
||||||
childrenByParent, // String key due to IMap<> json unsupported in key
|
IMap<String, ISet<TypedKey>> childrenByParent,
|
||||||
required IMap<String, TypedKey>
|
@Default(IMapConst<String, TypedKey>({}))
|
||||||
parentByChild, // String key due to IMap<> json unsupported in key
|
IMap<String, TypedKey> parentByChild,
|
||||||
required ISet<TypedKey> rootRecords,
|
@Default(ISetConst<TypedKey>({})) ISet<TypedKey> rootRecords,
|
||||||
|
@Default(IMapConst<String, String>({})) IMap<String, String> debugNames,
|
||||||
}) = _DHTRecordPoolAllocations;
|
}) = _DHTRecordPoolAllocations;
|
||||||
|
|
||||||
factory DHTRecordPoolAllocations.fromJson(dynamic json) =>
|
factory DHTRecordPoolAllocations.fromJson(dynamic json) =>
|
||||||
@ -92,10 +94,7 @@ class OpenedRecordInfo {
|
|||||||
|
|
||||||
class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
||||||
DHTRecordPool._(Veilid veilid, VeilidRoutingContext routingContext)
|
DHTRecordPool._(Veilid veilid, VeilidRoutingContext routingContext)
|
||||||
: _state = DHTRecordPoolAllocations(
|
: _state = const DHTRecordPoolAllocations(),
|
||||||
childrenByParent: IMap(),
|
|
||||||
parentByChild: IMap(),
|
|
||||||
rootRecords: ISet()),
|
|
||||||
_mutex = Mutex(),
|
_mutex = Mutex(),
|
||||||
_opened = <TypedKey, OpenedRecordInfo>{},
|
_opened = <TypedKey, OpenedRecordInfo>{},
|
||||||
_routingContext = routingContext,
|
_routingContext = routingContext,
|
||||||
@ -129,8 +128,7 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
@override
|
@override
|
||||||
DHTRecordPoolAllocations valueFromJson(Object? obj) => obj != null
|
DHTRecordPoolAllocations valueFromJson(Object? obj) => obj != null
|
||||||
? DHTRecordPoolAllocations.fromJson(obj)
|
? DHTRecordPoolAllocations.fromJson(obj)
|
||||||
: DHTRecordPoolAllocations(
|
: const DHTRecordPoolAllocations();
|
||||||
childrenByParent: IMap(), parentByChild: IMap(), rootRecords: ISet());
|
|
||||||
@override
|
@override
|
||||||
Object? valueToJson(DHTRecordPoolAllocations val) => val.toJson();
|
Object? valueToJson(DHTRecordPoolAllocations val) => val.toJson();
|
||||||
|
|
||||||
@ -148,7 +146,8 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
Veilid get veilid => _veilid;
|
Veilid get veilid => _veilid;
|
||||||
|
|
||||||
Future<OpenedRecordInfo> _recordCreateInner(
|
Future<OpenedRecordInfo> _recordCreateInner(
|
||||||
{required VeilidRoutingContext dhtctx,
|
{required String debugName,
|
||||||
|
required VeilidRoutingContext dhtctx,
|
||||||
required DHTSchema schema,
|
required DHTSchema schema,
|
||||||
KeyPair? writer,
|
KeyPair? writer,
|
||||||
TypedKey? parent}) async {
|
TypedKey? parent}) async {
|
||||||
@ -169,13 +168,18 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
_opened[recordDescriptor.key] = openedRecordInfo;
|
_opened[recordDescriptor.key] = openedRecordInfo;
|
||||||
|
|
||||||
// Register the dependency
|
// Register the dependency
|
||||||
await _addDependencyInner(parent, recordDescriptor.key);
|
await _addDependencyInner(
|
||||||
|
parent,
|
||||||
|
recordDescriptor.key,
|
||||||
|
debugName: debugName,
|
||||||
|
);
|
||||||
|
|
||||||
return openedRecordInfo;
|
return openedRecordInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<OpenedRecordInfo> _recordOpenInner(
|
Future<OpenedRecordInfo> _recordOpenInner(
|
||||||
{required VeilidRoutingContext dhtctx,
|
{required String debugName,
|
||||||
|
required VeilidRoutingContext dhtctx,
|
||||||
required TypedKey recordKey,
|
required TypedKey recordKey,
|
||||||
KeyPair? writer,
|
KeyPair? writer,
|
||||||
TypedKey? parent}) async {
|
TypedKey? parent}) async {
|
||||||
@ -198,7 +202,11 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
_opened[recordDescriptor.key] = newOpenedRecordInfo;
|
_opened[recordDescriptor.key] = newOpenedRecordInfo;
|
||||||
|
|
||||||
// Register the dependency
|
// Register the dependency
|
||||||
await _addDependencyInner(parent, recordKey);
|
await _addDependencyInner(
|
||||||
|
parent,
|
||||||
|
recordKey,
|
||||||
|
debugName: debugName,
|
||||||
|
);
|
||||||
|
|
||||||
return newOpenedRecordInfo;
|
return newOpenedRecordInfo;
|
||||||
}
|
}
|
||||||
@ -218,7 +226,11 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register the dependency
|
// Register the dependency
|
||||||
await _addDependencyInner(parent, recordKey);
|
await _addDependencyInner(
|
||||||
|
parent,
|
||||||
|
recordKey,
|
||||||
|
debugName: debugName,
|
||||||
|
);
|
||||||
|
|
||||||
return openedRecordInfo;
|
return openedRecordInfo;
|
||||||
}
|
}
|
||||||
@ -259,6 +271,18 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
return allDeps.reversedView;
|
return allDeps.reversedView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _debugPrintChildren(TypedKey recordKey, {List<TypedKey>? allDeps}) {
|
||||||
|
allDeps ??= _collectChildrenInner(recordKey);
|
||||||
|
// ignore: avoid_print
|
||||||
|
print('Parent: $recordKey (${_state.debugNames[recordKey.toString()]})');
|
||||||
|
for (final dep in allDeps) {
|
||||||
|
if (dep != recordKey) {
|
||||||
|
// ignore: avoid_print
|
||||||
|
print(' Child: $dep (${_state.debugNames[dep.toString()]})');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _deleteInner(TypedKey recordKey) async {
|
Future<void> _deleteInner(TypedKey recordKey) async {
|
||||||
// Remove this child from parents
|
// Remove this child from parents
|
||||||
await _removeDependenciesInner([recordKey]);
|
await _removeDependenciesInner([recordKey]);
|
||||||
@ -269,7 +293,10 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
await _mutex.protect(() async {
|
await _mutex.protect(() async {
|
||||||
final allDeps = _collectChildrenInner(recordKey);
|
final allDeps = _collectChildrenInner(recordKey);
|
||||||
|
|
||||||
assert(allDeps.singleOrNull == recordKey, 'must delete children first');
|
if (allDeps.singleOrNull != recordKey) {
|
||||||
|
_debugPrintChildren(recordKey, allDeps: allDeps);
|
||||||
|
assert(false, 'must delete children first');
|
||||||
|
}
|
||||||
|
|
||||||
final ori = _opened[recordKey];
|
final ori = _opened[recordKey];
|
||||||
if (ori != null) {
|
if (ori != null) {
|
||||||
@ -301,15 +328,17 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _addDependencyInner(TypedKey? parent, TypedKey child) async {
|
Future<void> _addDependencyInner(TypedKey? parent, TypedKey child,
|
||||||
|
{required String debugName}) async {
|
||||||
assert(_mutex.isLocked, 'should be locked here');
|
assert(_mutex.isLocked, 'should be locked here');
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
if (_state.rootRecords.contains(child)) {
|
if (_state.rootRecords.contains(child)) {
|
||||||
// Dependency already added
|
// Dependency already added
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_state = await store(
|
_state = await store(_state.copyWith(
|
||||||
_state.copyWith(rootRecords: _state.rootRecords.add(child)));
|
rootRecords: _state.rootRecords.add(child),
|
||||||
|
debugNames: _state.debugNames.add(child.toJson(), debugName)));
|
||||||
} else {
|
} else {
|
||||||
final childrenOfParent =
|
final childrenOfParent =
|
||||||
_state.childrenByParent[parent.toJson()] ?? ISet<TypedKey>();
|
_state.childrenByParent[parent.toJson()] ?? ISet<TypedKey>();
|
||||||
@ -320,7 +349,8 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
_state = await store(_state.copyWith(
|
_state = await store(_state.copyWith(
|
||||||
childrenByParent: _state.childrenByParent
|
childrenByParent: _state.childrenByParent
|
||||||
.add(parent.toJson(), childrenOfParent.add(child)),
|
.add(parent.toJson(), childrenOfParent.add(child)),
|
||||||
parentByChild: _state.parentByChild.add(child.toJson(), parent)));
|
parentByChild: _state.parentByChild.add(child.toJson(), parent),
|
||||||
|
debugNames: _state.debugNames.add(child.toJson(), debugName)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,7 +361,9 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
|
|
||||||
for (final child in childList) {
|
for (final child in childList) {
|
||||||
if (_state.rootRecords.contains(child)) {
|
if (_state.rootRecords.contains(child)) {
|
||||||
state = state.copyWith(rootRecords: state.rootRecords.remove(child));
|
state = state.copyWith(
|
||||||
|
rootRecords: state.rootRecords.remove(child),
|
||||||
|
debugNames: state.debugNames.remove(child.toJson()));
|
||||||
} else {
|
} else {
|
||||||
final parent = state.parentByChild[child.toJson()];
|
final parent = state.parentByChild[child.toJson()];
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
@ -341,12 +373,14 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
if (children.isEmpty) {
|
if (children.isEmpty) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
childrenByParent: state.childrenByParent.remove(parent.toJson()),
|
childrenByParent: state.childrenByParent.remove(parent.toJson()),
|
||||||
parentByChild: state.parentByChild.remove(child.toJson()));
|
parentByChild: state.parentByChild.remove(child.toJson()),
|
||||||
|
debugNames: state.debugNames.remove(child.toJson()));
|
||||||
} else {
|
} else {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
childrenByParent:
|
childrenByParent:
|
||||||
state.childrenByParent.add(parent.toJson(), children),
|
state.childrenByParent.add(parent.toJson(), children),
|
||||||
parentByChild: state.parentByChild.remove(child.toJson()));
|
parentByChild: state.parentByChild.remove(child.toJson()),
|
||||||
|
debugNames: state.debugNames.remove(child.toJson()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,6 +394,7 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
|
|
||||||
/// Create a root DHTRecord that has no dependent records
|
/// Create a root DHTRecord that has no dependent records
|
||||||
Future<DHTRecord> create({
|
Future<DHTRecord> create({
|
||||||
|
required String debugName,
|
||||||
VeilidRoutingContext? routingContext,
|
VeilidRoutingContext? routingContext,
|
||||||
TypedKey? parent,
|
TypedKey? parent,
|
||||||
DHTSchema schema = const DHTSchema.dflt(oCnt: 1),
|
DHTSchema schema = const DHTSchema.dflt(oCnt: 1),
|
||||||
@ -371,9 +406,14 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
final dhtctx = routingContext ?? _routingContext;
|
final dhtctx = routingContext ?? _routingContext;
|
||||||
|
|
||||||
final openedRecordInfo = await _recordCreateInner(
|
final openedRecordInfo = await _recordCreateInner(
|
||||||
dhtctx: dhtctx, schema: schema, writer: writer, parent: parent);
|
debugName: debugName,
|
||||||
|
dhtctx: dhtctx,
|
||||||
|
schema: schema,
|
||||||
|
writer: writer,
|
||||||
|
parent: parent);
|
||||||
|
|
||||||
final rec = DHTRecord(
|
final rec = DHTRecord._(
|
||||||
|
debugName: debugName,
|
||||||
routingContext: dhtctx,
|
routingContext: dhtctx,
|
||||||
defaultSubkey: defaultSubkey,
|
defaultSubkey: defaultSubkey,
|
||||||
sharedDHTRecordData: openedRecordInfo.shared,
|
sharedDHTRecordData: openedRecordInfo.shared,
|
||||||
@ -391,7 +431,8 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
|
|
||||||
/// Open a DHTRecord readonly
|
/// Open a DHTRecord readonly
|
||||||
Future<DHTRecord> openRead(TypedKey recordKey,
|
Future<DHTRecord> openRead(TypedKey recordKey,
|
||||||
{VeilidRoutingContext? routingContext,
|
{required String debugName,
|
||||||
|
VeilidRoutingContext? routingContext,
|
||||||
TypedKey? parent,
|
TypedKey? parent,
|
||||||
int defaultSubkey = 0,
|
int defaultSubkey = 0,
|
||||||
DHTRecordCrypto? crypto}) async =>
|
DHTRecordCrypto? crypto}) async =>
|
||||||
@ -399,9 +440,13 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
final dhtctx = routingContext ?? _routingContext;
|
final dhtctx = routingContext ?? _routingContext;
|
||||||
|
|
||||||
final openedRecordInfo = await _recordOpenInner(
|
final openedRecordInfo = await _recordOpenInner(
|
||||||
dhtctx: dhtctx, recordKey: recordKey, parent: parent);
|
debugName: debugName,
|
||||||
|
dhtctx: dhtctx,
|
||||||
|
recordKey: recordKey,
|
||||||
|
parent: parent);
|
||||||
|
|
||||||
final rec = DHTRecord(
|
final rec = DHTRecord._(
|
||||||
|
debugName: debugName,
|
||||||
routingContext: dhtctx,
|
routingContext: dhtctx,
|
||||||
defaultSubkey: defaultSubkey,
|
defaultSubkey: defaultSubkey,
|
||||||
sharedDHTRecordData: openedRecordInfo.shared,
|
sharedDHTRecordData: openedRecordInfo.shared,
|
||||||
@ -417,6 +462,7 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
Future<DHTRecord> openWrite(
|
Future<DHTRecord> openWrite(
|
||||||
TypedKey recordKey,
|
TypedKey recordKey,
|
||||||
KeyPair writer, {
|
KeyPair writer, {
|
||||||
|
required String debugName,
|
||||||
VeilidRoutingContext? routingContext,
|
VeilidRoutingContext? routingContext,
|
||||||
TypedKey? parent,
|
TypedKey? parent,
|
||||||
int defaultSubkey = 0,
|
int defaultSubkey = 0,
|
||||||
@ -426,12 +472,14 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
final dhtctx = routingContext ?? _routingContext;
|
final dhtctx = routingContext ?? _routingContext;
|
||||||
|
|
||||||
final openedRecordInfo = await _recordOpenInner(
|
final openedRecordInfo = await _recordOpenInner(
|
||||||
|
debugName: debugName,
|
||||||
dhtctx: dhtctx,
|
dhtctx: dhtctx,
|
||||||
recordKey: recordKey,
|
recordKey: recordKey,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
writer: writer);
|
writer: writer);
|
||||||
|
|
||||||
final rec = DHTRecord(
|
final rec = DHTRecord._(
|
||||||
|
debugName: debugName,
|
||||||
routingContext: dhtctx,
|
routingContext: dhtctx,
|
||||||
defaultSubkey: defaultSubkey,
|
defaultSubkey: defaultSubkey,
|
||||||
writer: writer,
|
writer: writer,
|
||||||
@ -453,6 +501,7 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
/// parent must be specified.
|
/// parent must be specified.
|
||||||
Future<DHTRecord> openOwned(
|
Future<DHTRecord> openOwned(
|
||||||
OwnedDHTRecordPointer ownedDHTRecordPointer, {
|
OwnedDHTRecordPointer ownedDHTRecordPointer, {
|
||||||
|
required String debugName,
|
||||||
required TypedKey parent,
|
required TypedKey parent,
|
||||||
VeilidRoutingContext? routingContext,
|
VeilidRoutingContext? routingContext,
|
||||||
int defaultSubkey = 0,
|
int defaultSubkey = 0,
|
||||||
@ -461,6 +510,7 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
openWrite(
|
openWrite(
|
||||||
ownedDHTRecordPointer.recordKey,
|
ownedDHTRecordPointer.recordKey,
|
||||||
ownedDHTRecordPointer.owner,
|
ownedDHTRecordPointer.owner,
|
||||||
|
debugName: debugName,
|
||||||
routingContext: routingContext,
|
routingContext: routingContext,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
defaultSubkey: defaultSubkey,
|
defaultSubkey: defaultSubkey,
|
||||||
|
@ -22,11 +22,12 @@ DHTRecordPoolAllocations _$DHTRecordPoolAllocationsFromJson(
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$DHTRecordPoolAllocations {
|
mixin _$DHTRecordPoolAllocations {
|
||||||
IMap<String, ISet<Typed<FixedEncodedString43>>> get childrenByParent =>
|
IMap<String, ISet<Typed<FixedEncodedString43>>> get childrenByParent =>
|
||||||
throw _privateConstructorUsedError; // String key due to IMap<> json unsupported in key
|
throw _privateConstructorUsedError;
|
||||||
IMap<String, Typed<FixedEncodedString43>> get parentByChild =>
|
IMap<String, Typed<FixedEncodedString43>> get parentByChild =>
|
||||||
throw _privateConstructorUsedError; // String key due to IMap<> json unsupported in key
|
throw _privateConstructorUsedError;
|
||||||
ISet<Typed<FixedEncodedString43>> get rootRecords =>
|
ISet<Typed<FixedEncodedString43>> get rootRecords =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
IMap<String, String> get debugNames => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@ -43,7 +44,8 @@ abstract class $DHTRecordPoolAllocationsCopyWith<$Res> {
|
|||||||
$Res call(
|
$Res call(
|
||||||
{IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent,
|
{IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent,
|
||||||
IMap<String, Typed<FixedEncodedString43>> parentByChild,
|
IMap<String, Typed<FixedEncodedString43>> parentByChild,
|
||||||
ISet<Typed<FixedEncodedString43>> rootRecords});
|
ISet<Typed<FixedEncodedString43>> rootRecords,
|
||||||
|
IMap<String, String> debugNames});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -63,6 +65,7 @@ class _$DHTRecordPoolAllocationsCopyWithImpl<$Res,
|
|||||||
Object? childrenByParent = null,
|
Object? childrenByParent = null,
|
||||||
Object? parentByChild = null,
|
Object? parentByChild = null,
|
||||||
Object? rootRecords = null,
|
Object? rootRecords = null,
|
||||||
|
Object? debugNames = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
childrenByParent: null == childrenByParent
|
childrenByParent: null == childrenByParent
|
||||||
@ -77,6 +80,10 @@ class _$DHTRecordPoolAllocationsCopyWithImpl<$Res,
|
|||||||
? _value.rootRecords
|
? _value.rootRecords
|
||||||
: rootRecords // ignore: cast_nullable_to_non_nullable
|
: rootRecords // ignore: cast_nullable_to_non_nullable
|
||||||
as ISet<Typed<FixedEncodedString43>>,
|
as ISet<Typed<FixedEncodedString43>>,
|
||||||
|
debugNames: null == debugNames
|
||||||
|
? _value.debugNames
|
||||||
|
: debugNames // ignore: cast_nullable_to_non_nullable
|
||||||
|
as IMap<String, String>,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,7 +100,8 @@ abstract class _$$DHTRecordPoolAllocationsImplCopyWith<$Res>
|
|||||||
$Res call(
|
$Res call(
|
||||||
{IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent,
|
{IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent,
|
||||||
IMap<String, Typed<FixedEncodedString43>> parentByChild,
|
IMap<String, Typed<FixedEncodedString43>> parentByChild,
|
||||||
ISet<Typed<FixedEncodedString43>> rootRecords});
|
ISet<Typed<FixedEncodedString43>> rootRecords,
|
||||||
|
IMap<String, String> debugNames});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -112,6 +120,7 @@ class __$$DHTRecordPoolAllocationsImplCopyWithImpl<$Res>
|
|||||||
Object? childrenByParent = null,
|
Object? childrenByParent = null,
|
||||||
Object? parentByChild = null,
|
Object? parentByChild = null,
|
||||||
Object? rootRecords = null,
|
Object? rootRecords = null,
|
||||||
|
Object? debugNames = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$DHTRecordPoolAllocationsImpl(
|
return _then(_$DHTRecordPoolAllocationsImpl(
|
||||||
childrenByParent: null == childrenByParent
|
childrenByParent: null == childrenByParent
|
||||||
@ -126,6 +135,10 @@ class __$$DHTRecordPoolAllocationsImplCopyWithImpl<$Res>
|
|||||||
? _value.rootRecords
|
? _value.rootRecords
|
||||||
: rootRecords // ignore: cast_nullable_to_non_nullable
|
: rootRecords // ignore: cast_nullable_to_non_nullable
|
||||||
as ISet<Typed<FixedEncodedString43>>,
|
as ISet<Typed<FixedEncodedString43>>,
|
||||||
|
debugNames: null == debugNames
|
||||||
|
? _value.debugNames
|
||||||
|
: debugNames // ignore: cast_nullable_to_non_nullable
|
||||||
|
as IMap<String, String>,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,25 +147,30 @@ class __$$DHTRecordPoolAllocationsImplCopyWithImpl<$Res>
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$DHTRecordPoolAllocationsImpl implements _DHTRecordPoolAllocations {
|
class _$DHTRecordPoolAllocationsImpl implements _DHTRecordPoolAllocations {
|
||||||
const _$DHTRecordPoolAllocationsImpl(
|
const _$DHTRecordPoolAllocationsImpl(
|
||||||
{required this.childrenByParent,
|
{this.childrenByParent = const IMapConst<String, ISet<TypedKey>>({}),
|
||||||
required this.parentByChild,
|
this.parentByChild = const IMapConst<String, TypedKey>({}),
|
||||||
required this.rootRecords});
|
this.rootRecords = const ISetConst<TypedKey>({}),
|
||||||
|
this.debugNames = const IMapConst<String, String>({})});
|
||||||
|
|
||||||
factory _$DHTRecordPoolAllocationsImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$DHTRecordPoolAllocationsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$DHTRecordPoolAllocationsImplFromJson(json);
|
_$$DHTRecordPoolAllocationsImplFromJson(json);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@JsonKey()
|
||||||
final IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent;
|
final IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent;
|
||||||
// String key due to IMap<> json unsupported in key
|
|
||||||
@override
|
@override
|
||||||
|
@JsonKey()
|
||||||
final IMap<String, Typed<FixedEncodedString43>> parentByChild;
|
final IMap<String, Typed<FixedEncodedString43>> parentByChild;
|
||||||
// String key due to IMap<> json unsupported in key
|
|
||||||
@override
|
@override
|
||||||
|
@JsonKey()
|
||||||
final ISet<Typed<FixedEncodedString43>> rootRecords;
|
final ISet<Typed<FixedEncodedString43>> rootRecords;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final IMap<String, String> debugNames;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'DHTRecordPoolAllocations(childrenByParent: $childrenByParent, parentByChild: $parentByChild, rootRecords: $rootRecords)';
|
return 'DHTRecordPoolAllocations(childrenByParent: $childrenByParent, parentByChild: $parentByChild, rootRecords: $rootRecords, debugNames: $debugNames)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -165,13 +183,15 @@ class _$DHTRecordPoolAllocationsImpl implements _DHTRecordPoolAllocations {
|
|||||||
(identical(other.parentByChild, parentByChild) ||
|
(identical(other.parentByChild, parentByChild) ||
|
||||||
other.parentByChild == parentByChild) &&
|
other.parentByChild == parentByChild) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other.rootRecords, rootRecords));
|
.equals(other.rootRecords, rootRecords) &&
|
||||||
|
(identical(other.debugNames, debugNames) ||
|
||||||
|
other.debugNames == debugNames));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, childrenByParent, parentByChild,
|
int get hashCode => Object.hash(runtimeType, childrenByParent, parentByChild,
|
||||||
const DeepCollectionEquality().hash(rootRecords));
|
const DeepCollectionEquality().hash(rootRecords), debugNames);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
@ -190,22 +210,23 @@ class _$DHTRecordPoolAllocationsImpl implements _DHTRecordPoolAllocations {
|
|||||||
|
|
||||||
abstract class _DHTRecordPoolAllocations implements DHTRecordPoolAllocations {
|
abstract class _DHTRecordPoolAllocations implements DHTRecordPoolAllocations {
|
||||||
const factory _DHTRecordPoolAllocations(
|
const factory _DHTRecordPoolAllocations(
|
||||||
{required final IMap<String, ISet<Typed<FixedEncodedString43>>>
|
{final IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent,
|
||||||
childrenByParent,
|
final IMap<String, Typed<FixedEncodedString43>> parentByChild,
|
||||||
required final IMap<String, Typed<FixedEncodedString43>> parentByChild,
|
final ISet<Typed<FixedEncodedString43>> rootRecords,
|
||||||
required final ISet<Typed<FixedEncodedString43>>
|
final IMap<String, String> debugNames}) = _$DHTRecordPoolAllocationsImpl;
|
||||||
rootRecords}) = _$DHTRecordPoolAllocationsImpl;
|
|
||||||
|
|
||||||
factory _DHTRecordPoolAllocations.fromJson(Map<String, dynamic> json) =
|
factory _DHTRecordPoolAllocations.fromJson(Map<String, dynamic> json) =
|
||||||
_$DHTRecordPoolAllocationsImpl.fromJson;
|
_$DHTRecordPoolAllocationsImpl.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
IMap<String, ISet<Typed<FixedEncodedString43>>> get childrenByParent;
|
IMap<String, ISet<Typed<FixedEncodedString43>>> get childrenByParent;
|
||||||
@override // String key due to IMap<> json unsupported in key
|
@override
|
||||||
IMap<String, Typed<FixedEncodedString43>> get parentByChild;
|
IMap<String, Typed<FixedEncodedString43>> get parentByChild;
|
||||||
@override // String key due to IMap<> json unsupported in key
|
@override
|
||||||
ISet<Typed<FixedEncodedString43>> get rootRecords;
|
ISet<Typed<FixedEncodedString43>> get rootRecords;
|
||||||
@override
|
@override
|
||||||
|
IMap<String, String> get debugNames;
|
||||||
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$DHTRecordPoolAllocationsImplCopyWith<_$DHTRecordPoolAllocationsImpl>
|
_$$DHTRecordPoolAllocationsImplCopyWith<_$DHTRecordPoolAllocationsImpl>
|
||||||
get copyWith => throw _privateConstructorUsedError;
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
@ -9,19 +9,29 @@ part of 'dht_record_pool.dart';
|
|||||||
_$DHTRecordPoolAllocationsImpl _$$DHTRecordPoolAllocationsImplFromJson(
|
_$DHTRecordPoolAllocationsImpl _$$DHTRecordPoolAllocationsImplFromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
_$DHTRecordPoolAllocationsImpl(
|
_$DHTRecordPoolAllocationsImpl(
|
||||||
childrenByParent:
|
childrenByParent: json['childrenByParent'] == null
|
||||||
IMap<String, ISet<Typed<FixedEncodedString43>>>.fromJson(
|
? const IMapConst<String, ISet<TypedKey>>({})
|
||||||
|
: IMap<String, ISet<Typed<FixedEncodedString43>>>.fromJson(
|
||||||
json['childrenByParent'] as Map<String, dynamic>,
|
json['childrenByParent'] as Map<String, dynamic>,
|
||||||
(value) => value as String,
|
(value) => value as String,
|
||||||
(value) => ISet<Typed<FixedEncodedString43>>.fromJson(value,
|
(value) => ISet<Typed<FixedEncodedString43>>.fromJson(value,
|
||||||
(value) => Typed<FixedEncodedString43>.fromJson(value))),
|
(value) => Typed<FixedEncodedString43>.fromJson(value))),
|
||||||
parentByChild: IMap<String, Typed<FixedEncodedString43>>.fromJson(
|
parentByChild: json['parentByChild'] == null
|
||||||
json['parentByChild'] as Map<String, dynamic>,
|
? const IMapConst<String, TypedKey>({})
|
||||||
(value) => value as String,
|
: IMap<String, Typed<FixedEncodedString43>>.fromJson(
|
||||||
(value) => Typed<FixedEncodedString43>.fromJson(value)),
|
json['parentByChild'] as Map<String, dynamic>,
|
||||||
rootRecords: ISet<Typed<FixedEncodedString43>>.fromJson(
|
(value) => value as String,
|
||||||
json['rootRecords'],
|
(value) => Typed<FixedEncodedString43>.fromJson(value)),
|
||||||
(value) => Typed<FixedEncodedString43>.fromJson(value)),
|
rootRecords: json['rootRecords'] == null
|
||||||
|
? const ISetConst<TypedKey>({})
|
||||||
|
: ISet<Typed<FixedEncodedString43>>.fromJson(json['rootRecords'],
|
||||||
|
(value) => Typed<FixedEncodedString43>.fromJson(value)),
|
||||||
|
debugNames: json['debugNames'] == null
|
||||||
|
? const IMapConst<String, String>({})
|
||||||
|
: IMap<String, String>.fromJson(
|
||||||
|
json['debugNames'] as Map<String, dynamic>,
|
||||||
|
(value) => value as String,
|
||||||
|
(value) => value as String),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$DHTRecordPoolAllocationsImplToJson(
|
Map<String, dynamic> _$$DHTRecordPoolAllocationsImplToJson(
|
||||||
@ -40,6 +50,10 @@ Map<String, dynamic> _$$DHTRecordPoolAllocationsImplToJson(
|
|||||||
'rootRecords': instance.rootRecords.toJson(
|
'rootRecords': instance.rootRecords.toJson(
|
||||||
(value) => value,
|
(value) => value,
|
||||||
),
|
),
|
||||||
|
'debugNames': instance.debugNames.toJson(
|
||||||
|
(value) => value,
|
||||||
|
(value) => value,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
_$OwnedDHTRecordPointerImpl _$$OwnedDHTRecordPointerImplFromJson(
|
_$OwnedDHTRecordPointerImpl _$$OwnedDHTRecordPointerImplFromJson(
|
||||||
|
@ -28,7 +28,8 @@ class DHTShortArray {
|
|||||||
// if smplWriter is specified, uses a SMPL schema with a single writer
|
// if smplWriter is specified, uses a SMPL schema with a single writer
|
||||||
// rather than the key owner
|
// rather than the key owner
|
||||||
static Future<DHTShortArray> create(
|
static Future<DHTShortArray> create(
|
||||||
{int stride = maxElements,
|
{required String debugName,
|
||||||
|
int stride = maxElements,
|
||||||
VeilidRoutingContext? routingContext,
|
VeilidRoutingContext? routingContext,
|
||||||
TypedKey? parent,
|
TypedKey? parent,
|
||||||
DHTRecordCrypto? crypto,
|
DHTRecordCrypto? crypto,
|
||||||
@ -42,6 +43,7 @@ class DHTShortArray {
|
|||||||
oCnt: 0,
|
oCnt: 0,
|
||||||
members: [DHTSchemaMember(mKey: smplWriter.key, mCnt: stride + 1)]);
|
members: [DHTSchemaMember(mKey: smplWriter.key, mCnt: stride + 1)]);
|
||||||
dhtRecord = await pool.create(
|
dhtRecord = await pool.create(
|
||||||
|
debugName: debugName,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
routingContext: routingContext,
|
routingContext: routingContext,
|
||||||
schema: schema,
|
schema: schema,
|
||||||
@ -50,6 +52,7 @@ class DHTShortArray {
|
|||||||
} else {
|
} else {
|
||||||
final schema = DHTSchema.dflt(oCnt: stride + 1);
|
final schema = DHTSchema.dflt(oCnt: stride + 1);
|
||||||
dhtRecord = await pool.create(
|
dhtRecord = await pool.create(
|
||||||
|
debugName: debugName,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
routingContext: routingContext,
|
routingContext: routingContext,
|
||||||
schema: schema,
|
schema: schema,
|
||||||
@ -72,11 +75,15 @@ class DHTShortArray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<DHTShortArray> openRead(TypedKey headRecordKey,
|
static Future<DHTShortArray> openRead(TypedKey headRecordKey,
|
||||||
{VeilidRoutingContext? routingContext,
|
{required String debugName,
|
||||||
|
VeilidRoutingContext? routingContext,
|
||||||
TypedKey? parent,
|
TypedKey? parent,
|
||||||
DHTRecordCrypto? crypto}) async {
|
DHTRecordCrypto? crypto}) async {
|
||||||
final dhtRecord = await DHTRecordPool.instance.openRead(headRecordKey,
|
final dhtRecord = await DHTRecordPool.instance.openRead(headRecordKey,
|
||||||
parent: parent, routingContext: routingContext, crypto: crypto);
|
debugName: debugName,
|
||||||
|
parent: parent,
|
||||||
|
routingContext: routingContext,
|
||||||
|
crypto: crypto);
|
||||||
try {
|
try {
|
||||||
final dhtShortArray = DHTShortArray._(headRecord: dhtRecord);
|
final dhtShortArray = DHTShortArray._(headRecord: dhtRecord);
|
||||||
await dhtShortArray._head.operate((head) => head._loadHead());
|
await dhtShortArray._head.operate((head) => head._loadHead());
|
||||||
@ -90,13 +97,17 @@ class DHTShortArray {
|
|||||||
static Future<DHTShortArray> openWrite(
|
static Future<DHTShortArray> openWrite(
|
||||||
TypedKey headRecordKey,
|
TypedKey headRecordKey,
|
||||||
KeyPair writer, {
|
KeyPair writer, {
|
||||||
|
required String debugName,
|
||||||
VeilidRoutingContext? routingContext,
|
VeilidRoutingContext? routingContext,
|
||||||
TypedKey? parent,
|
TypedKey? parent,
|
||||||
DHTRecordCrypto? crypto,
|
DHTRecordCrypto? crypto,
|
||||||
}) async {
|
}) async {
|
||||||
final dhtRecord = await DHTRecordPool.instance.openWrite(
|
final dhtRecord = await DHTRecordPool.instance.openWrite(
|
||||||
headRecordKey, writer,
|
headRecordKey, writer,
|
||||||
parent: parent, routingContext: routingContext, crypto: crypto);
|
debugName: debugName,
|
||||||
|
parent: parent,
|
||||||
|
routingContext: routingContext,
|
||||||
|
crypto: crypto);
|
||||||
try {
|
try {
|
||||||
final dhtShortArray = DHTShortArray._(headRecord: dhtRecord);
|
final dhtShortArray = DHTShortArray._(headRecord: dhtRecord);
|
||||||
await dhtShortArray._head.operate((head) => head._loadHead());
|
await dhtShortArray._head.operate((head) => head._loadHead());
|
||||||
@ -109,6 +120,7 @@ class DHTShortArray {
|
|||||||
|
|
||||||
static Future<DHTShortArray> openOwned(
|
static Future<DHTShortArray> openOwned(
|
||||||
OwnedDHTRecordPointer ownedDHTRecordPointer, {
|
OwnedDHTRecordPointer ownedDHTRecordPointer, {
|
||||||
|
required String debugName,
|
||||||
required TypedKey parent,
|
required TypedKey parent,
|
||||||
VeilidRoutingContext? routingContext,
|
VeilidRoutingContext? routingContext,
|
||||||
DHTRecordCrypto? crypto,
|
DHTRecordCrypto? crypto,
|
||||||
@ -116,6 +128,7 @@ class DHTShortArray {
|
|||||||
openWrite(
|
openWrite(
|
||||||
ownedDHTRecordPointer.recordKey,
|
ownedDHTRecordPointer.recordKey,
|
||||||
ownedDHTRecordPointer.owner,
|
ownedDHTRecordPointer.owner,
|
||||||
|
debugName: debugName,
|
||||||
routingContext: routingContext,
|
routingContext: routingContext,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
crypto: crypto,
|
crypto: crypto,
|
||||||
|
@ -17,13 +17,13 @@ class DHTShortArrayCubit<T> extends Cubit<DHTShortArrayBusyState<T>>
|
|||||||
required T Function(List<int> data) decodeElement,
|
required T Function(List<int> data) decodeElement,
|
||||||
}) : _decodeElement = decodeElement,
|
}) : _decodeElement = decodeElement,
|
||||||
super(const BlocBusyState(AsyncValue.loading())) {
|
super(const BlocBusyState(AsyncValue.loading())) {
|
||||||
_initFuture = Future(() async {
|
_initWait.add(() async {
|
||||||
// Open DHT record
|
// Open DHT record
|
||||||
_shortArray = await open();
|
_shortArray = await open();
|
||||||
_wantsCloseRecord = true;
|
_wantsCloseRecord = true;
|
||||||
|
|
||||||
// Make initial state update
|
// Make initial state update
|
||||||
unawaited(_refreshNoWait());
|
await _refreshNoWait();
|
||||||
_subscription = await _shortArray.listen(_update);
|
_subscription = await _shortArray.listen(_update);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ class DHTShortArrayCubit<T> extends Cubit<DHTShortArrayBusyState<T>>
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
Future<void> refresh({bool forceRefresh = false}) async {
|
Future<void> refresh({bool forceRefresh = false}) async {
|
||||||
await _initFuture;
|
await _initWait();
|
||||||
await _refreshNoWait(forceRefresh: forceRefresh);
|
await _refreshNoWait(forceRefresh: forceRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ class DHTShortArrayCubit<T> extends Cubit<DHTShortArrayBusyState<T>>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await _initFuture;
|
await _initWait();
|
||||||
await _subscription?.cancel();
|
await _subscription?.cancel();
|
||||||
_subscription = null;
|
_subscription = null;
|
||||||
if (_wantsCloseRecord) {
|
if (_wantsCloseRecord) {
|
||||||
@ -85,24 +85,24 @@ class DHTShortArrayCubit<T> extends Cubit<DHTShortArrayBusyState<T>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<R?> operate<R>(Future<R?> Function(DHTShortArrayRead) closure) async {
|
Future<R?> operate<R>(Future<R?> Function(DHTShortArrayRead) closure) async {
|
||||||
await _initFuture;
|
await _initWait();
|
||||||
return _shortArray.operate(closure);
|
return _shortArray.operate(closure);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<(R?, bool)> operateWrite<R>(
|
Future<(R?, bool)> operateWrite<R>(
|
||||||
Future<R?> Function(DHTShortArrayWrite) closure) async {
|
Future<R?> Function(DHTShortArrayWrite) closure) async {
|
||||||
await _initFuture;
|
await _initWait();
|
||||||
return _shortArray.operateWrite(closure);
|
return _shortArray.operateWrite(closure);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> operateWriteEventual(
|
Future<void> operateWriteEventual(
|
||||||
Future<bool> Function(DHTShortArrayWrite) closure,
|
Future<bool> Function(DHTShortArrayWrite) closure,
|
||||||
{Duration? timeout}) async {
|
{Duration? timeout}) async {
|
||||||
await _initFuture;
|
await _initWait();
|
||||||
return _shortArray.operateWriteEventual(closure, timeout: timeout);
|
return _shortArray.operateWriteEventual(closure, timeout: timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final Future<void> _initFuture;
|
final WaitSet _initWait = WaitSet();
|
||||||
late final DHTShortArray _shortArray;
|
late final DHTShortArray _shortArray;
|
||||||
final T Function(List<int> data) _decodeElement;
|
final T Function(List<int> data) _decodeElement;
|
||||||
StreamSubscription<void>? _subscription;
|
StreamSubscription<void>? _subscription;
|
||||||
|
@ -184,7 +184,7 @@ class _DHTShortArrayHead {
|
|||||||
final oldRecord = oldRecords[newKey];
|
final oldRecord = oldRecords[newKey];
|
||||||
if (oldRecord == null) {
|
if (oldRecord == null) {
|
||||||
// Open the new record
|
// Open the new record
|
||||||
final newRecord = await _openLinkedRecord(newKey);
|
final newRecord = await _openLinkedRecord(newKey, n);
|
||||||
newRecords[newKey] = newRecord;
|
newRecords[newKey] = newRecord;
|
||||||
updatedLinkedRecords.add(newRecord);
|
updatedLinkedRecords.add(newRecord);
|
||||||
} else {
|
} else {
|
||||||
@ -263,6 +263,7 @@ class _DHTShortArrayHead {
|
|||||||
oCnt: 0,
|
oCnt: 0,
|
||||||
members: [DHTSchemaMember(mKey: smplWriter.key, mCnt: _stride)]);
|
members: [DHTSchemaMember(mKey: smplWriter.key, mCnt: _stride)]);
|
||||||
final dhtRecord = await pool.create(
|
final dhtRecord = await pool.create(
|
||||||
|
debugName: '${_headRecord.debugName}_linked_$recordNumber',
|
||||||
parent: parent,
|
parent: parent,
|
||||||
routingContext: routingContext,
|
routingContext: routingContext,
|
||||||
schema: schema,
|
schema: schema,
|
||||||
@ -279,17 +280,20 @@ class _DHTShortArrayHead {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Open a linked record for reading or writing, same as the head record
|
/// Open a linked record for reading or writing, same as the head record
|
||||||
Future<DHTRecord> _openLinkedRecord(TypedKey recordKey) async {
|
Future<DHTRecord> _openLinkedRecord(
|
||||||
|
TypedKey recordKey, int recordNumber) async {
|
||||||
final writer = _headRecord.writer;
|
final writer = _headRecord.writer;
|
||||||
return (writer != null)
|
return (writer != null)
|
||||||
? await DHTRecordPool.instance.openWrite(
|
? await DHTRecordPool.instance.openWrite(
|
||||||
recordKey,
|
recordKey,
|
||||||
writer,
|
writer,
|
||||||
|
debugName: '${_headRecord.debugName}_linked_$recordNumber',
|
||||||
parent: _headRecord.key,
|
parent: _headRecord.key,
|
||||||
routingContext: _headRecord.routingContext,
|
routingContext: _headRecord.routingContext,
|
||||||
)
|
)
|
||||||
: await DHTRecordPool.instance.openRead(
|
: await DHTRecordPool.instance.openRead(
|
||||||
recordKey,
|
recordKey,
|
||||||
|
debugName: '${_headRecord.debugName}_linked_$recordNumber',
|
||||||
parent: _headRecord.key,
|
parent: _headRecord.key,
|
||||||
routingContext: _headRecord.routingContext,
|
routingContext: _headRecord.routingContext,
|
||||||
);
|
);
|
||||||
|
@ -132,7 +132,10 @@ extension IdentityMasterExtension on IdentityMaster {
|
|||||||
|
|
||||||
late final List<AccountRecordInfo> accountRecordInfo;
|
late final List<AccountRecordInfo> accountRecordInfo;
|
||||||
await (await pool.openRead(identityRecordKey,
|
await (await pool.openRead(identityRecordKey,
|
||||||
parent: masterRecordKey, crypto: identityRecordCrypto))
|
debugName:
|
||||||
|
'IdentityMaster::readAccountsFromIdentity::IdentityRecord',
|
||||||
|
parent: masterRecordKey,
|
||||||
|
crypto: identityRecordCrypto))
|
||||||
.scope((identityRec) async {
|
.scope((identityRec) async {
|
||||||
final identity = await identityRec.getJson(Identity.fromJson);
|
final identity = await identityRec.getJson(Identity.fromJson);
|
||||||
if (identity == null) {
|
if (identity == null) {
|
||||||
@ -161,14 +164,17 @@ extension IdentityMasterExtension on IdentityMaster {
|
|||||||
/////// Add account with profile to DHT
|
/////// Add account with profile to DHT
|
||||||
|
|
||||||
// Open identity key for writing
|
// Open identity key for writing
|
||||||
veilidLoggy.debug('Opening master identity');
|
veilidLoggy.debug('Opening identity record');
|
||||||
return (await pool.openWrite(
|
return (await pool.openWrite(
|
||||||
identityRecordKey, identityWriter(identitySecret),
|
identityRecordKey, identityWriter(identitySecret),
|
||||||
|
debugName: 'IdentityMaster::addAccountToIdentity::IdentityRecord',
|
||||||
parent: masterRecordKey))
|
parent: masterRecordKey))
|
||||||
.scope((identityRec) async {
|
.scope((identityRec) async {
|
||||||
// Create new account to insert into identity
|
// Create new account to insert into identity
|
||||||
veilidLoggy.debug('Creating new account');
|
veilidLoggy.debug('Creating new account');
|
||||||
return (await pool.create(parent: identityRec.key))
|
return (await pool.create(
|
||||||
|
debugName: 'IdentityMaster::addAccountToIdentity::AccountRecord',
|
||||||
|
parent: identityRec.key))
|
||||||
.deleteScope((accountRec) async {
|
.deleteScope((accountRec) async {
|
||||||
final account = await createAccountCallback(accountRec.key);
|
final account = await createAccountCallback(accountRec.key);
|
||||||
// Write account key
|
// Write account key
|
||||||
@ -222,11 +228,16 @@ class IdentityMasterWithSecrets {
|
|||||||
|
|
||||||
// IdentityMaster DHT record is public/unencrypted
|
// IdentityMaster DHT record is public/unencrypted
|
||||||
veilidLoggy.debug('Creating master identity record');
|
veilidLoggy.debug('Creating master identity record');
|
||||||
return (await pool.create(crypto: const DHTRecordCryptoPublic()))
|
return (await pool.create(
|
||||||
|
debugName:
|
||||||
|
'IdentityMasterWithSecrets::create::IdentityMasterRecord',
|
||||||
|
crypto: const DHTRecordCryptoPublic()))
|
||||||
.deleteScope((masterRec) async {
|
.deleteScope((masterRec) async {
|
||||||
veilidLoggy.debug('Creating identity record');
|
veilidLoggy.debug('Creating identity record');
|
||||||
// Identity record is private
|
// Identity record is private
|
||||||
return (await pool.create(parent: masterRec.key))
|
return (await pool.create(
|
||||||
|
debugName: 'IdentityMasterWithSecrets::create::IdentityRecord',
|
||||||
|
parent: masterRec.key))
|
||||||
.scope((identityRec) async {
|
.scope((identityRec) async {
|
||||||
// Make IdentityMaster
|
// Make IdentityMaster
|
||||||
final masterRecordKey = masterRec.key;
|
final masterRecordKey = masterRec.key;
|
||||||
@ -282,7 +293,9 @@ Future<IdentityMaster> openIdentityMaster(
|
|||||||
final pool = DHTRecordPool.instance;
|
final pool = DHTRecordPool.instance;
|
||||||
|
|
||||||
// IdentityMaster DHT record is public/unencrypted
|
// IdentityMaster DHT record is public/unencrypted
|
||||||
return (await pool.openRead(identityMasterRecordKey))
|
return (await pool.openRead(identityMasterRecordKey,
|
||||||
|
debugName:
|
||||||
|
'IdentityMaster::openIdentityMaster::IdentityMasterRecord'))
|
||||||
.deleteScope((masterRec) async {
|
.deleteScope((masterRec) async {
|
||||||
final identityMaster =
|
final identityMaster =
|
||||||
(await masterRec.getJson(IdentityMaster.fromJson, forceRefresh: true))!;
|
(await masterRec.getJson(IdentityMaster.fromJson, forceRefresh: true))!;
|
||||||
|
Loading…
Reference in New Issue
Block a user