mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-07-23 06:31:13 -04:00
checkpoint
This commit is contained in:
parent
bf813d7d0f
commit
20bdf07f24
10 changed files with 329 additions and 218 deletions
28
lib/repositories/local_account_repository.dart
Normal file
28
lib/repositories/local_account_repository.dart
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import 'package:veilid/veilid.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import '../entities/entities.dart';
|
||||||
|
import '../entities/proto.dart' as proto;
|
||||||
|
|
||||||
|
import 'local_account_repository_impl.dart';
|
||||||
|
|
||||||
|
part 'local_account_repository.g.dart';
|
||||||
|
|
||||||
|
// Local account manager
|
||||||
|
abstract class LocalAccountRepository {
|
||||||
|
/// Creates a new master identity and returns it with its secrets
|
||||||
|
Future<IdentityMasterWithSecrets> newIdentityMaster();
|
||||||
|
|
||||||
|
/// Creates a new account associated with master identity
|
||||||
|
Future<LocalAccount> newAccount(
|
||||||
|
IdentityMaster identityMaster,
|
||||||
|
SecretKey identitySecret,
|
||||||
|
EncryptionKeyType encryptionKeyType,
|
||||||
|
String encryptionKey,
|
||||||
|
proto.Account account);
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<LocalAccountRepository> localAccountManager(
|
||||||
|
LocalAccountManagerRef ref) async {
|
||||||
|
return await LocalAccountRepositoryImpl.open();
|
||||||
|
}
|
27
lib/repositories/local_account_repository.g.dart
Normal file
27
lib/repositories/local_account_repository.g.dart
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'local_account_repository.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$localAccountManagerHash() =>
|
||||||
|
r'0ecdc84f868edb33f6e62bfe116f18568b424267';
|
||||||
|
|
||||||
|
/// See also [localAccountManager].
|
||||||
|
@ProviderFor(localAccountManager)
|
||||||
|
final localAccountManagerProvider =
|
||||||
|
AutoDisposeFutureProvider<LocalAccountRepository>.internal(
|
||||||
|
localAccountManager,
|
||||||
|
name: r'localAccountManagerProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$localAccountManagerHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef LocalAccountManagerRef
|
||||||
|
= AutoDisposeFutureProviderRef<LocalAccountRepository>;
|
||||||
|
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions
|
159
lib/repositories/local_account_repository_impl.dart
Normal file
159
lib/repositories/local_account_repository_impl.dart
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
import 'package:veilid/veilid.dart';
|
||||||
|
|
||||||
|
import '../tools/tools.dart';
|
||||||
|
import '../entities/entities.dart';
|
||||||
|
import '../entities/proto.dart' as proto;
|
||||||
|
|
||||||
|
import 'local_account_repository.dart';
|
||||||
|
|
||||||
|
// Local account manager
|
||||||
|
class LocalAccountRepositoryImpl extends LocalAccountRepository {
|
||||||
|
IList<LocalAccount> _localAccounts;
|
||||||
|
|
||||||
|
static const localAccountManagerTable = "local_account_manager";
|
||||||
|
static const localAccountsKey = "local_accounts";
|
||||||
|
|
||||||
|
LocalAccountRepositoryImpl._({required IList<LocalAccount> localAccounts})
|
||||||
|
: _localAccounts = localAccounts;
|
||||||
|
|
||||||
|
/// Gets or creates a local account manager
|
||||||
|
static Future<LocalAccountRepository> open() async {
|
||||||
|
// Load accounts from tabledb
|
||||||
|
final localAccounts =
|
||||||
|
await tableScope(localAccountManagerTable, (tdb) async {
|
||||||
|
final localAccountsJson = await tdb.loadStringJson(0, localAccountsKey);
|
||||||
|
return localAccountsJson != null
|
||||||
|
? IList<LocalAccount>.fromJson(
|
||||||
|
localAccountsJson, genericFromJson(LocalAccount.fromJson))
|
||||||
|
: IList<LocalAccount>();
|
||||||
|
});
|
||||||
|
|
||||||
|
return LocalAccountRepositoryImpl._(localAccounts: localAccounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store things back to storage
|
||||||
|
Future<void> flush() async {
|
||||||
|
await tableScope(localAccountManagerTable, (tdb) async {
|
||||||
|
await tdb.storeStringJson(0, localAccountsKey, _localAccounts);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new master identity and returns it with its secrets
|
||||||
|
@override
|
||||||
|
Future<IdentityMasterWithSecrets> newIdentityMaster() async {
|
||||||
|
final crypto = await Veilid.instance.bestCryptoSystem();
|
||||||
|
final dhtctx = (await Veilid.instance.routingContext())
|
||||||
|
.withPrivacy()
|
||||||
|
.withSequencing(Sequencing.ensureOrdered);
|
||||||
|
|
||||||
|
return (await DHTRecord.create(dhtctx)).deleteScope((masterRec) async {
|
||||||
|
return (await DHTRecord.create(dhtctx)).deleteScope((identityRec) async {
|
||||||
|
// Make IdentityMaster
|
||||||
|
final masterRecordKey = masterRec.key();
|
||||||
|
final masterOwner = masterRec.ownerKeyPair()!;
|
||||||
|
final masterSigBuf = masterRecordKey.decode()
|
||||||
|
..addAll(masterOwner.key.decode());
|
||||||
|
|
||||||
|
final identityRecordKey = identityRec.key();
|
||||||
|
final identityOwner = identityRec.ownerKeyPair()!;
|
||||||
|
final identitySigBuf = identityRecordKey.decode()
|
||||||
|
..addAll(identityOwner.key.decode());
|
||||||
|
|
||||||
|
final identitySignature =
|
||||||
|
await crypto.signWithKeyPair(masterOwner, identitySigBuf);
|
||||||
|
final masterSignature =
|
||||||
|
await crypto.signWithKeyPair(identityOwner, masterSigBuf);
|
||||||
|
|
||||||
|
final identityMaster = IdentityMaster(
|
||||||
|
identityRecordKey: identityRecordKey,
|
||||||
|
identityPublicKey: identityOwner.key,
|
||||||
|
masterRecordKey: masterRecordKey,
|
||||||
|
masterPublicKey: masterOwner.key,
|
||||||
|
identitySignature: identitySignature,
|
||||||
|
masterSignature: masterSignature);
|
||||||
|
|
||||||
|
// Write identity master to master dht key
|
||||||
|
await masterRec.eventualWriteJson(identityMaster);
|
||||||
|
|
||||||
|
// Make empty identity
|
||||||
|
const identity = Identity(accountRecords: IMapConst({}));
|
||||||
|
|
||||||
|
// Write empty identity to identity dht key
|
||||||
|
await identityRec.eventualWriteJson(identity);
|
||||||
|
|
||||||
|
return IdentityMasterWithSecrets(
|
||||||
|
identityMaster: identityMaster,
|
||||||
|
masterSecret: masterOwner.secret,
|
||||||
|
identitySecret: identityOwner.secret);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new account associated with master identity
|
||||||
|
@override
|
||||||
|
Future<LocalAccount> newAccount(
|
||||||
|
IdentityMaster identityMaster,
|
||||||
|
SecretKey identitySecret,
|
||||||
|
EncryptionKeyType encryptionKeyType,
|
||||||
|
String encryptionKey,
|
||||||
|
proto.Account account) async {
|
||||||
|
// Encrypt identitySecret with key
|
||||||
|
final cs = await Veilid.instance.bestCryptoSystem();
|
||||||
|
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
||||||
|
final nonce = await cs.randomNonce();
|
||||||
|
final eksalt = nonce.decode();
|
||||||
|
SharedSecret sharedSecret = await cs.deriveSharedSecret(ekbytes, eksalt);
|
||||||
|
final identitySecretBytes =
|
||||||
|
await cs.cryptNoAuth(identitySecret.decode(), nonce, sharedSecret);
|
||||||
|
|
||||||
|
// Create local account object
|
||||||
|
final localAccount = LocalAccount(
|
||||||
|
identityMaster: identityMaster,
|
||||||
|
identitySecretKeyBytes: identitySecretBytes,
|
||||||
|
identitySecretSaltBytes: eksalt,
|
||||||
|
encryptionKeyType: encryptionKeyType,
|
||||||
|
biometricsEnabled: false,
|
||||||
|
hiddenAccount: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
/////// Add account with profile to DHT
|
||||||
|
|
||||||
|
// Create private routing context
|
||||||
|
final dhtctx = (await Veilid.instance.routingContext())
|
||||||
|
.withPrivacy()
|
||||||
|
.withSequencing(Sequencing.ensureOrdered);
|
||||||
|
|
||||||
|
// Open identity key for writing
|
||||||
|
(await DHTRecord.open(dhtctx, identityMaster.identityRecordKey,
|
||||||
|
identityMaster.identityWriter(identitySecret)))
|
||||||
|
.scope((identityRec) async {
|
||||||
|
// Create new account to insert into identity
|
||||||
|
(await DHTRecord.create(dhtctx)).deleteScope((accountRec) async {
|
||||||
|
// Write account key
|
||||||
|
await accountRec.eventualWriteProtobuf(account);
|
||||||
|
|
||||||
|
// Update identity key to include account
|
||||||
|
final newAccountRecordOwner = accountRec.ownerKeyPair()!;
|
||||||
|
final newAccountRecordInfo = AccountRecordInfo(
|
||||||
|
key: accountRec.key(), owner: newAccountRecordOwner);
|
||||||
|
|
||||||
|
await identityRec.eventualUpdateJson(Identity.fromJson,
|
||||||
|
(oldIdentity) async {
|
||||||
|
final accountRecords = IMapOfSets.from(oldIdentity.accountRecords)
|
||||||
|
.add("VeilidChat", newAccountRecordInfo)
|
||||||
|
.asIMap();
|
||||||
|
return oldIdentity.copyWith(accountRecords: accountRecords);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add local account object to internal store
|
||||||
|
|
||||||
|
// Return local account object
|
||||||
|
return localAccount;
|
||||||
|
}
|
||||||
|
}
|
1
lib/repositories/repositories.dart
Normal file
1
lib/repositories/repositories.dart
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export 'local_account_repository.dart';
|
|
@ -1,217 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
import 'package:veilid/veilid.dart';
|
|
||||||
import 'package:veilidchat/tools/tools.dart';
|
|
||||||
|
|
||||||
import '../entities/entities.dart';
|
|
||||||
import '../entities/proto.dart' as proto;
|
|
||||||
|
|
||||||
part 'local_account_manager.g.dart';
|
|
||||||
|
|
||||||
// Local account manager
|
|
||||||
class LocalAccountManager {
|
|
||||||
final VeilidTableDB _localAccountsTable;
|
|
||||||
final IList<LocalAccount> _localAccounts;
|
|
||||||
|
|
||||||
const LocalAccountManager(
|
|
||||||
{required VeilidTableDB localAccountsTable,
|
|
||||||
required IList<LocalAccount> localAccounts})
|
|
||||||
: _localAccountsTable = localAccountsTable,
|
|
||||||
_localAccounts = localAccounts;
|
|
||||||
|
|
||||||
/// Gets or creates a local account manager
|
|
||||||
static Future<LocalAccountManager> open() async {
|
|
||||||
final localAccountsTable =
|
|
||||||
await Veilid.instance.openTableDB("local_account_manager", 1);
|
|
||||||
final localAccounts =
|
|
||||||
(await localAccountsTable.loadStringJson(0, "local_accounts") ??
|
|
||||||
const IListConst([])) as IList<LocalAccount>;
|
|
||||||
return LocalAccountManager(
|
|
||||||
localAccountsTable: localAccountsTable, localAccounts: localAccounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Flush things to storage
|
|
||||||
Future<void> flush() async {}
|
|
||||||
|
|
||||||
/// Creates a new master identity and returns it with its secrets
|
|
||||||
Future<IdentityMasterWithSecrets> newIdentityMaster() async {
|
|
||||||
final dhtctx = (await Veilid.instance.routingContext())
|
|
||||||
.withPrivacy()
|
|
||||||
.withSequencing(Sequencing.ensureOrdered);
|
|
||||||
DHTRecordDescriptor? masterRec;
|
|
||||||
DHTRecordDescriptor? identityRec;
|
|
||||||
try {
|
|
||||||
masterRec = await dhtctx.createDHTRecord(const DHTSchema.dflt(oCnt: 1));
|
|
||||||
identityRec = await dhtctx.createDHTRecord(const DHTSchema.dflt(oCnt: 1));
|
|
||||||
final crypto = await Veilid.instance.bestCryptoSystem();
|
|
||||||
assert(masterRec.key.kind == crypto.kind());
|
|
||||||
assert(identityRec.key.kind == crypto.kind());
|
|
||||||
|
|
||||||
// IdentityMaster
|
|
||||||
final masterRecordKey = masterRec.key;
|
|
||||||
final masterPublicKey = masterRec.owner;
|
|
||||||
final masterSecret = masterRec.ownerSecret!;
|
|
||||||
final masterSigBuf = masterRecordKey.decode()
|
|
||||||
..addAll(masterPublicKey.decode());
|
|
||||||
|
|
||||||
final identityRecordKey = identityRec.key;
|
|
||||||
final identityPublicKey = identityRec.owner;
|
|
||||||
final identitySecret = identityRec.ownerSecret!;
|
|
||||||
final identitySigBuf = identityRecordKey.decode()
|
|
||||||
..addAll(identityPublicKey.decode());
|
|
||||||
|
|
||||||
final identitySignature =
|
|
||||||
await crypto.sign(masterPublicKey, masterSecret, identitySigBuf);
|
|
||||||
final masterSignature =
|
|
||||||
await crypto.sign(identityPublicKey, identitySecret, masterSigBuf);
|
|
||||||
|
|
||||||
final identityMaster = IdentityMaster(
|
|
||||||
identityRecordKey: identityRecordKey,
|
|
||||||
identityPublicKey: identityPublicKey,
|
|
||||||
masterRecordKey: masterRecordKey,
|
|
||||||
masterPublicKey: masterPublicKey,
|
|
||||||
identitySignature: identitySignature,
|
|
||||||
masterSignature: masterSignature);
|
|
||||||
|
|
||||||
// Write identity master to master dht key
|
|
||||||
final identityMasterBytes =
|
|
||||||
Uint8List.fromList(utf8.encode(jsonEncode(identityMaster)));
|
|
||||||
await dhtctx.setDHTValue(masterRecordKey, 0, identityMasterBytes);
|
|
||||||
|
|
||||||
// Write empty identity to identity dht key
|
|
||||||
const identity = Identity(accountKeyPairs: {});
|
|
||||||
final identityBytes =
|
|
||||||
Uint8List.fromList(utf8.encode(jsonEncode(identity)));
|
|
||||||
await dhtctx.setDHTValue(identityRecordKey, 0, identityBytes);
|
|
||||||
|
|
||||||
return IdentityMasterWithSecrets(
|
|
||||||
identityMaster: identityMaster,
|
|
||||||
masterSecret: masterSecret,
|
|
||||||
identitySecret: identitySecret);
|
|
||||||
} catch (e) {
|
|
||||||
if (masterRec != null) {
|
|
||||||
await dhtctx.deleteDHTRecord(masterRec.key);
|
|
||||||
}
|
|
||||||
if (identityRec != null) {
|
|
||||||
await dhtctx.deleteDHTRecord(identityRec.key);
|
|
||||||
}
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a 'VeilidChat' account record to an identity key
|
|
||||||
Future<void> updateIdentityKey(
|
|
||||||
VeilidRoutingContext dhtctx,
|
|
||||||
TypedKey identityRecordKey,
|
|
||||||
TypedKey accountRecordKey,
|
|
||||||
KeyPair accountRecordOwner) async {
|
|
||||||
// Account record to add
|
|
||||||
final accountRecordInfo =
|
|
||||||
AccountRecordInfo(key: accountRecordKey, owner: accountRecordOwner);
|
|
||||||
|
|
||||||
// Eventually update identity key
|
|
||||||
eventuallyConsistentUpdate(
|
|
||||||
dhtctx,
|
|
||||||
identityRecordKey,
|
|
||||||
0,
|
|
||||||
true,
|
|
||||||
jsonUpdate(Identity.fromJson, (oldObj) async {
|
|
||||||
final accountRecords = IMapOfSets.from(oldObj.accountRecords)
|
|
||||||
.add("VeilidChat", accountRecordInfo)
|
|
||||||
.asIMap();
|
|
||||||
return oldObj.copyWith(accountRecords: accountRecords);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Get existing identity key
|
|
||||||
ValueData? identityValueData =
|
|
||||||
await dhtctx.getDHTValue(identityRecordKey, 0, true);
|
|
||||||
do {
|
|
||||||
if (identityValueData == null) {
|
|
||||||
throw const FormatException("Identity does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
var identity = identityValueData.readJsonData(Identity.fromJson);
|
|
||||||
// Update identity key to include account
|
|
||||||
final accountRecords = IMapOfSets.from(identity.accountRecords)
|
|
||||||
.add("VeilidChat", accountRecordInfo)
|
|
||||||
.asIMap();
|
|
||||||
identity = identity.copyWith(accountRecords: accountRecords);
|
|
||||||
|
|
||||||
final identityBytes = jsonEncodeBytes(identity);
|
|
||||||
identityValueData =
|
|
||||||
await dhtctx.setDHTValue(identityRecordKey, 0, identityBytes);
|
|
||||||
} while (identityValueData != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new account associated with master identity
|
|
||||||
Future<LocalAccount> newAccount(
|
|
||||||
IdentityMaster identityMaster,
|
|
||||||
SecretKey identitySecret,
|
|
||||||
EncryptionKeyType encryptionKeyType,
|
|
||||||
String encryptionKey,
|
|
||||||
proto.Account account) async {
|
|
||||||
// Encrypt identitySecret with key
|
|
||||||
final cs = await Veilid.instance.bestCryptoSystem();
|
|
||||||
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
|
||||||
final nonce = await cs.randomNonce();
|
|
||||||
final eksalt = nonce.decode();
|
|
||||||
SharedSecret sharedSecret = await cs.deriveSharedSecret(ekbytes, eksalt);
|
|
||||||
final identitySecretBytes =
|
|
||||||
await cs.cryptNoAuth(identitySecret.decode(), nonce, sharedSecret);
|
|
||||||
|
|
||||||
// Create local account object
|
|
||||||
final localAccount = LocalAccount(
|
|
||||||
identityMaster: identityMaster,
|
|
||||||
identitySecretKeyBytes: identitySecretBytes,
|
|
||||||
identitySecretSaltBytes: eksalt,
|
|
||||||
encryptionKeyType: encryptionKeyType,
|
|
||||||
biometricsEnabled: false,
|
|
||||||
hiddenAccount: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add account with profile to DHT
|
|
||||||
final dhtctx = (await Veilid.instance.routingContext())
|
|
||||||
.withPrivacy()
|
|
||||||
.withSequencing(Sequencing.ensureOrdered);
|
|
||||||
DHTRecordDescriptor? identityRec;
|
|
||||||
DHTRecordDescriptor? accountRec;
|
|
||||||
try {
|
|
||||||
identityRec = await dhtctx.openDHTRecord(identityMaster.identityRecordKey,
|
|
||||||
identityMaster.identityWriter(identitySecret));
|
|
||||||
accountRec = await dhtctx.createDHTRecord(const DHTSchema.dflt(oCnt: 1));
|
|
||||||
final crypto = await Veilid.instance.bestCryptoSystem();
|
|
||||||
assert(identityRec.key.kind == crypto.kind());
|
|
||||||
assert(accountRec.key.kind == crypto.kind());
|
|
||||||
|
|
||||||
// Write account key
|
|
||||||
assert(await dhtctx.setDHTValue(
|
|
||||||
accountRec.key, 0, account.writeToBuffer()) ==
|
|
||||||
null);
|
|
||||||
|
|
||||||
// Update identity key to include account
|
|
||||||
await updateIdentityKey(dhtctx, identityRec.key, accountRec.key,
|
|
||||||
KeyPair(key: accountRec.owner, secret: accountRec.ownerSecret!));
|
|
||||||
} catch (e) {
|
|
||||||
if (identityRec != null) {
|
|
||||||
await dhtctx.closeDHTRecord(identityRec.key);
|
|
||||||
}
|
|
||||||
if (accountRec != null) {
|
|
||||||
await dhtctx.deleteDHTRecord(accountRec.key);
|
|
||||||
}
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add local account object to internal store
|
|
||||||
|
|
||||||
// Return local account object
|
|
||||||
return localAccount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@riverpod
|
|
||||||
Future<LocalAccountManager> localAccountManager(LocalAccountManagerRef ref) {
|
|
||||||
return LocalAccountManager.open();
|
|
||||||
}
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
import 'package:veilid/veilid.dart';
|
import 'package:veilid/veilid.dart';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'tools.dart';
|
import 'tools.dart';
|
||||||
|
@ -38,6 +39,49 @@ class DHTRecord {
|
||||||
|
|
||||||
int _subkey(int subkey) => (subkey == -1) ? _defaultSubkey : subkey;
|
int _subkey(int subkey) => (subkey == -1) ? _defaultSubkey : subkey;
|
||||||
|
|
||||||
|
TypedKey key() {
|
||||||
|
return _recordDescriptor.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
PublicKey owner() {
|
||||||
|
return _recordDescriptor.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyPair? ownerKeyPair() {
|
||||||
|
final ownerSecret = _recordDescriptor.ownerSecret;
|
||||||
|
if (ownerSecret == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return KeyPair(key: _recordDescriptor.owner, secret: ownerSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> close() async {
|
||||||
|
await _dhtctx.closeDHTRecord(_recordDescriptor.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> delete() async {
|
||||||
|
await _dhtctx.deleteDHTRecord(_recordDescriptor.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T> scope<T>(Future<T> Function(DHTRecord) scopeFunction) async {
|
||||||
|
try {
|
||||||
|
return await scopeFunction(this);
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T> deleteScope<T>(Future<T> Function(DHTRecord) scopeFunction) async {
|
||||||
|
try {
|
||||||
|
return await scopeFunction(this);
|
||||||
|
} catch (_) {
|
||||||
|
delete();
|
||||||
|
rethrow;
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Uint8List?> get({int subkey = -1, bool forceRefresh = false}) async {
|
Future<Uint8List?> get({int subkey = -1, bool forceRefresh = false}) async {
|
||||||
ValueData? valueData = await _dhtctx.getDHTValue(
|
ValueData? valueData = await _dhtctx.getDHTValue(
|
||||||
_recordDescriptor.key, _subkey(subkey), false);
|
_recordDescriptor.key, _subkey(subkey), false);
|
||||||
|
@ -101,9 +145,21 @@ class DHTRecord {
|
||||||
return eventualWriteBytes(jsonEncodeBytes(newValue), subkey: subkey);
|
return eventualWriteBytes(jsonEncodeBytes(newValue), subkey: subkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> eventualWriteProtobuf<T extends GeneratedMessage>(T newValue,
|
||||||
|
{int subkey = -1}) {
|
||||||
|
return eventualWriteBytes(newValue.writeToBuffer(), subkey: subkey);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> eventualUpdateJson<T>(
|
Future<void> eventualUpdateJson<T>(
|
||||||
T Function(Map<String, dynamic>) fromJson, Future<T> Function(T) update,
|
T Function(Map<String, dynamic>) fromJson, Future<T> Function(T) update,
|
||||||
{int subkey = -1}) {
|
{int subkey = -1}) {
|
||||||
return eventualUpdateBytes(jsonUpdate(fromJson, update), subkey: subkey);
|
return eventualUpdateBytes(jsonUpdate(fromJson, update), subkey: subkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> eventualUpdateProtobuf<T extends GeneratedMessage>(
|
||||||
|
T Function(List<int>) fromBuffer, Future<T> Function(T) update,
|
||||||
|
{int subkey = -1}) {
|
||||||
|
return eventualUpdateBytes(protobufUpdate(fromBuffer, update),
|
||||||
|
subkey: subkey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
|
||||||
import 'package:veilid/veilid.dart';
|
import 'package:veilid/veilid.dart';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
@ -28,3 +27,10 @@ Future<Uint8List> Function(Uint8List) jsonUpdate<T>(
|
||||||
return jsonUpdateBytes(fromJson, oldBytes, update);
|
return jsonUpdateBytes(fromJson, oldBytes, update);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T Function(Object?) genericFromJson<T>(
|
||||||
|
T Function(Map<String, dynamic>) fromJsonMap) {
|
||||||
|
return (Object? json) {
|
||||||
|
return fromJsonMap(json as Map<String, dynamic>);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
19
lib/tools/protobuf_tools.dart
Normal file
19
lib/tools/protobuf_tools.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
Future<Uint8List> protobufUpdateBytes<T extends GeneratedMessage>(
|
||||||
|
T Function(List<int>) fromBuffer,
|
||||||
|
Uint8List oldBytes,
|
||||||
|
Future<T> Function(T) update) async {
|
||||||
|
T oldObj = fromBuffer(oldBytes);
|
||||||
|
T newObj = await update(oldObj);
|
||||||
|
return Uint8List.fromList(newObj.writeToBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Uint8List> Function(Uint8List)
|
||||||
|
protobufUpdate<T extends GeneratedMessage>(
|
||||||
|
T Function(List<int>) fromBuffer, Future<T> Function(T) update) {
|
||||||
|
return (Uint8List oldBytes) {
|
||||||
|
return protobufUpdateBytes(fromBuffer, oldBytes, update);
|
||||||
|
};
|
||||||
|
}
|
30
lib/tools/table_db.dart
Normal file
30
lib/tools/table_db.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:veilid/veilid.dart';
|
||||||
|
|
||||||
|
Future<T> tableScope<T>(
|
||||||
|
String name, Future<T> Function(VeilidTableDB tdb) callback,
|
||||||
|
{int columnCount = 1}) async {
|
||||||
|
VeilidTableDB tableDB = await Veilid.instance.openTableDB(name, columnCount);
|
||||||
|
try {
|
||||||
|
return await callback(tableDB);
|
||||||
|
} finally {
|
||||||
|
tableDB.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T> transactionScope<T>(
|
||||||
|
VeilidTableDB tdb,
|
||||||
|
Future<T> Function(VeilidTableDBTransaction tdbt) callback,
|
||||||
|
) async {
|
||||||
|
VeilidTableDBTransaction tdbt = tdb.transact();
|
||||||
|
try {
|
||||||
|
final ret = await callback(tdbt);
|
||||||
|
if (!tdbt.isDone()) {
|
||||||
|
await tdbt.commit();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} finally {
|
||||||
|
if (!tdbt.isDone()) {
|
||||||
|
await tdbt.rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,3 +2,5 @@ export 'external_stream_state.dart';
|
||||||
export 'dht_record.dart';
|
export 'dht_record.dart';
|
||||||
export 'json_tools.dart';
|
export 'json_tools.dart';
|
||||||
export 'phono_byte.dart';
|
export 'phono_byte.dart';
|
||||||
|
export 'protobuf_tools.dart';
|
||||||
|
export 'table_db.dart';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue