mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-12-26 08:09:37 -05:00
checkpoint
This commit is contained in:
parent
031d7aea82
commit
fcccacfafa
@ -1,12 +1,28 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.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 'package:meta/meta.dart';
|
||||||
import 'package:protobuf/protobuf.dart';
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
|
||||||
import '../../../veilid_support.dart';
|
import '../../../veilid_support.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class DHTRecordWatchChange extends Equatable {
|
||||||
|
const DHTRecordWatchChange(
|
||||||
|
{required this.local, required this.data, required this.subkeys});
|
||||||
|
|
||||||
|
final bool local;
|
||||||
|
final Uint8List data;
|
||||||
|
final List<ValueSubkeyRange> subkeys;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [local, data, subkeys];
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
class DHTRecord {
|
class DHTRecord {
|
||||||
DHTRecord(
|
DHTRecord(
|
||||||
{required VeilidRoutingContext routingContext,
|
{required VeilidRoutingContext routingContext,
|
||||||
@ -34,7 +50,7 @@ class DHTRecord {
|
|||||||
bool _open;
|
bool _open;
|
||||||
bool _valid;
|
bool _valid;
|
||||||
@internal
|
@internal
|
||||||
StreamController<VeilidUpdateValueChange>? watchController;
|
StreamController<DHTRecordWatchChange>? watchController;
|
||||||
@internal
|
@internal
|
||||||
bool needsWatchStateUpdate;
|
bool needsWatchStateUpdate;
|
||||||
@internal
|
@internal
|
||||||
@ -160,76 +176,100 @@ class DHTRecord {
|
|||||||
Future<Uint8List?> tryWriteBytes(Uint8List newValue,
|
Future<Uint8List?> tryWriteBytes(Uint8List newValue,
|
||||||
{int subkey = -1}) async {
|
{int subkey = -1}) async {
|
||||||
subkey = subkeyOrDefault(subkey);
|
subkey = subkeyOrDefault(subkey);
|
||||||
newValue = await _crypto.encrypt(newValue, subkey);
|
final lastSeq = _subkeySeqCache[subkey];
|
||||||
|
final encryptedNewValue = await _crypto.encrypt(newValue, subkey);
|
||||||
|
|
||||||
// Set the new data if possible
|
// Set the new data if possible
|
||||||
var valueData = await _routingContext.setDHTValue(
|
var newValueData = await _routingContext.setDHTValue(
|
||||||
_recordDescriptor.key, subkey, newValue);
|
_recordDescriptor.key, subkey, encryptedNewValue);
|
||||||
if (valueData == null) {
|
if (newValueData == null) {
|
||||||
// Get the data to check its sequence number
|
// A newer value wasn't found on the set, but
|
||||||
valueData = await _routingContext.getDHTValue(
|
// we may get a newer value when getting the value for the sequence number
|
||||||
|
newValueData = await _routingContext.getDHTValue(
|
||||||
_recordDescriptor.key, subkey, false);
|
_recordDescriptor.key, subkey, false);
|
||||||
assert(valueData != null, "can't get value that was just set");
|
if (newValueData == null) {
|
||||||
_subkeySeqCache[subkey] = valueData!.seq;
|
assert(newValueData != null, "can't get value that was just set");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
_subkeySeqCache[subkey] = valueData.seq;
|
}
|
||||||
return valueData.data;
|
|
||||||
|
// Record new sequence number
|
||||||
|
final isUpdated = newValueData.seq != lastSeq;
|
||||||
|
_subkeySeqCache[subkey] = newValueData.seq;
|
||||||
|
|
||||||
|
// See if the encrypted data returned is exactly the same
|
||||||
|
// if so, shortcut and don't bother decrypting it
|
||||||
|
if (newValueData.data == encryptedNewValue) {
|
||||||
|
if (isUpdated) {
|
||||||
|
addLocalValueChange(newValue, subkey);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt value to return it
|
||||||
|
final decryptedNewValue = await _crypto.decrypt(newValueData.data, subkey);
|
||||||
|
if (isUpdated) {
|
||||||
|
addLocalValueChange(decryptedNewValue, subkey);
|
||||||
|
}
|
||||||
|
return decryptedNewValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> eventualWriteBytes(Uint8List newValue, {int subkey = -1}) async {
|
Future<void> eventualWriteBytes(Uint8List newValue, {int subkey = -1}) async {
|
||||||
subkey = subkeyOrDefault(subkey);
|
subkey = subkeyOrDefault(subkey);
|
||||||
newValue = await _crypto.encrypt(newValue, subkey);
|
final lastSeq = _subkeySeqCache[subkey];
|
||||||
|
final encryptedNewValue = await _crypto.encrypt(newValue, subkey);
|
||||||
|
|
||||||
ValueData? valueData;
|
ValueData? newValueData;
|
||||||
|
do {
|
||||||
do {
|
do {
|
||||||
// Set the new data
|
// Set the new data
|
||||||
valueData = await _routingContext.setDHTValue(
|
newValueData = await _routingContext.setDHTValue(
|
||||||
_recordDescriptor.key, subkey, newValue);
|
_recordDescriptor.key, subkey, encryptedNewValue);
|
||||||
|
|
||||||
// Repeat if newer data on the network was found
|
// Repeat if newer data on the network was found
|
||||||
} while (valueData != null);
|
} while (newValueData != null);
|
||||||
|
|
||||||
// Get the data to check its sequence number
|
// Get the data to check its sequence number
|
||||||
valueData =
|
newValueData = await _routingContext.getDHTValue(
|
||||||
await _routingContext.getDHTValue(_recordDescriptor.key, subkey, false);
|
_recordDescriptor.key, subkey, false);
|
||||||
assert(valueData != null, "can't get value that was just set");
|
if (newValueData == null) {
|
||||||
_subkeySeqCache[subkey] = valueData!.seq;
|
assert(newValueData != null, "can't get value that was just set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record new sequence number
|
||||||
|
_subkeySeqCache[subkey] = newValueData.seq;
|
||||||
|
|
||||||
|
// The encrypted data returned should be exactly the same
|
||||||
|
// as what we are trying to set,
|
||||||
|
// otherwise we still need to keep trying to set the value
|
||||||
|
} while (newValueData.data != encryptedNewValue);
|
||||||
|
|
||||||
|
final isUpdated = newValueData.seq != lastSeq;
|
||||||
|
if (isUpdated) {
|
||||||
|
addLocalValueChange(newValue, subkey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> eventualUpdateBytes(
|
Future<void> eventualUpdateBytes(
|
||||||
Future<Uint8List> Function(Uint8List oldValue) update,
|
Future<Uint8List> Function(Uint8List? oldValue) update,
|
||||||
{int subkey = -1}) async {
|
{int subkey = -1}) async {
|
||||||
subkey = subkeyOrDefault(subkey);
|
subkey = subkeyOrDefault(subkey);
|
||||||
// Get existing identity key, do not allow force refresh here
|
|
||||||
|
// Get the existing data, do not allow force refresh here
|
||||||
// because if we need a refresh the setDHTValue will fail anyway
|
// because if we need a refresh the setDHTValue will fail anyway
|
||||||
var valueData =
|
var oldValue =
|
||||||
await _routingContext.getDHTValue(_recordDescriptor.key, subkey, false);
|
await get(subkey: subkey, forceRefresh: false, onlyUpdates: false);
|
||||||
// Ensure it exists already
|
|
||||||
if (valueData == null) {
|
|
||||||
throw const FormatException('value does not exist');
|
|
||||||
}
|
|
||||||
do {
|
do {
|
||||||
// Update cache
|
|
||||||
_subkeySeqCache[subkey] = valueData!.seq;
|
|
||||||
|
|
||||||
// Update the data
|
// Update the data
|
||||||
final oldData = await _crypto.decrypt(valueData.data, subkey);
|
final updatedValue = await update(oldValue);
|
||||||
final updatedData = await update(oldData);
|
|
||||||
final newData = await _crypto.encrypt(updatedData, subkey);
|
|
||||||
|
|
||||||
// Set it back
|
// Try to write it back to the network
|
||||||
valueData = await _routingContext.setDHTValue(
|
oldValue = await tryWriteBytes(updatedValue, subkey: subkey);
|
||||||
_recordDescriptor.key, subkey, newData);
|
|
||||||
|
|
||||||
// Repeat if newer data on the network was found
|
// Repeat update if newer data on the network was found
|
||||||
} while (valueData != null);
|
} while (oldValue != null);
|
||||||
|
|
||||||
// Get the data to check its sequence number
|
|
||||||
valueData =
|
|
||||||
await _routingContext.getDHTValue(_recordDescriptor.key, subkey, false);
|
|
||||||
assert(valueData != null, "can't get value that was just set");
|
|
||||||
_subkeySeqCache[subkey] = valueData!.seq;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T?> tryWriteJson<T>(T Function(dynamic) fromJson, T newValue,
|
Future<T?> tryWriteJson<T>(T Function(dynamic) fromJson, T newValue,
|
||||||
@ -259,12 +299,12 @@ class DHTRecord {
|
|||||||
eventualWriteBytes(newValue.writeToBuffer(), subkey: subkey);
|
eventualWriteBytes(newValue.writeToBuffer(), subkey: subkey);
|
||||||
|
|
||||||
Future<void> eventualUpdateJson<T>(
|
Future<void> eventualUpdateJson<T>(
|
||||||
T Function(dynamic) fromJson, Future<T> Function(T) update,
|
T Function(dynamic) fromJson, Future<T> Function(T?) update,
|
||||||
{int subkey = -1}) =>
|
{int subkey = -1}) =>
|
||||||
eventualUpdateBytes(jsonUpdate(fromJson, update), subkey: subkey);
|
eventualUpdateBytes(jsonUpdate(fromJson, update), subkey: subkey);
|
||||||
|
|
||||||
Future<void> eventualUpdateProtobuf<T extends GeneratedMessage>(
|
Future<void> eventualUpdateProtobuf<T extends GeneratedMessage>(
|
||||||
T Function(List<int>) fromBuffer, Future<T> Function(T) update,
|
T Function(List<int>) fromBuffer, Future<T> Function(T?) update,
|
||||||
{int subkey = -1}) =>
|
{int subkey = -1}) =>
|
||||||
eventualUpdateBytes(protobufUpdate(fromBuffer, update), subkey: subkey);
|
eventualUpdateBytes(protobufUpdate(fromBuffer, update), subkey: subkey);
|
||||||
|
|
||||||
@ -281,25 +321,34 @@ class DHTRecord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<StreamSubscription<VeilidUpdateValueChange>> listen(
|
Future<StreamSubscription<DHTRecordWatchChange>> listen(
|
||||||
Future<void> Function(
|
Future<void> Function(
|
||||||
DHTRecord record, Uint8List data, List<ValueSubkeyRange> subkeys)
|
DHTRecord record, Uint8List data, List<ValueSubkeyRange> subkeys)
|
||||||
onUpdate,
|
onUpdate,
|
||||||
) async {
|
{bool localChanges = true}) async {
|
||||||
// Set up watch requirements
|
// Set up watch requirements
|
||||||
watchController ??=
|
watchController ??=
|
||||||
StreamController<VeilidUpdateValueChange>.broadcast(onCancel: () {
|
StreamController<DHTRecordWatchChange>.broadcast(onCancel: () {
|
||||||
// If there are no more listeners then we can get rid of the controller
|
// If there are no more listeners then we can get rid of the controller
|
||||||
watchController = null;
|
watchController = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return watchController!.stream.listen(
|
return watchController!.stream.listen(
|
||||||
(update) {
|
(change) {
|
||||||
|
if (change.local && !localChanges) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Future.delayed(Duration.zero, () async {
|
Future.delayed(Duration.zero, () async {
|
||||||
final out = await _crypto.decrypt(
|
final Uint8List data;
|
||||||
update.valueData.data, update.subkeys.first.low);
|
if (change.local) {
|
||||||
|
// local changes are not encrypted
|
||||||
await onUpdate(this, out, update.subkeys);
|
data = change.data;
|
||||||
|
} else {
|
||||||
|
// incoming/remote changes are encrypted
|
||||||
|
data =
|
||||||
|
await _crypto.decrypt(change.data, change.subkeys.first.low);
|
||||||
|
}
|
||||||
|
await onUpdate(this, data, change.subkeys);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
cancelOnError: true,
|
cancelOnError: true,
|
||||||
@ -316,4 +365,14 @@ class DHTRecord {
|
|||||||
needsWatchStateUpdate = true;
|
needsWatchStateUpdate = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addLocalValueChange(Uint8List data, int subkey) {
|
||||||
|
watchController?.add(DHTRecordWatchChange(
|
||||||
|
local: true, data: data, subkeys: [ValueSubkeyRange.single(subkey)]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addRemoteValueChange(VeilidUpdateValueChange update) {
|
||||||
|
watchController?.add(DHTRecordWatchChange(
|
||||||
|
local: false, data: update.valueData.data, subkeys: update.subkeys));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ class DHTRecordCubit<T> extends Cubit<AsyncValue<T>> {
|
|||||||
|
|
||||||
DHTRecord get record => _record;
|
DHTRecord get record => _record;
|
||||||
|
|
||||||
StreamSubscription<VeilidUpdateValueChange>? _subscription;
|
StreamSubscription<DHTRecordWatchChange>? _subscription;
|
||||||
late DHTRecord _record;
|
late DHTRecord _record;
|
||||||
bool _wantsCloseRecord;
|
bool _wantsCloseRecord;
|
||||||
final StateFunction<T> _stateFunction;
|
final StateFunction<T> _stateFunction;
|
||||||
|
@ -351,7 +351,7 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
|
|||||||
// Change
|
// Change
|
||||||
for (final kv in _opened.entries) {
|
for (final kv in _opened.entries) {
|
||||||
if (kv.key == updateValueChange.key) {
|
if (kv.key == updateValueChange.key) {
|
||||||
kv.value.watchController?.add(updateValueChange);
|
kv.value.addRemoteValueChange(updateValueChange);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,8 +63,7 @@ class DHTShortArray {
|
|||||||
_DHTShortArrayCache _head;
|
_DHTShortArrayCache _head;
|
||||||
|
|
||||||
// Subscription to head and linked record internal changes
|
// Subscription to head and linked record internal changes
|
||||||
final Map<TypedKey, StreamSubscription<VeilidUpdateValueChange>>
|
final Map<TypedKey, StreamSubscription<DHTRecordWatchChange>> _subscriptions;
|
||||||
_subscriptions;
|
|
||||||
// Stream of external changes
|
// Stream of external changes
|
||||||
StreamController<void>? _watchController;
|
StreamController<void>? _watchController;
|
||||||
// Watch mutex to ensure we keep the representation valid
|
// Watch mutex to ensure we keep the representation valid
|
||||||
@ -545,10 +544,10 @@ class DHTShortArray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final result = await record!.get(subkey: recordSubkey);
|
final result = await record!.get(subkey: recordSubkey);
|
||||||
if (result != null) {
|
|
||||||
// A change happened, notify any listeners
|
// A change happened, notify any listeners
|
||||||
_watchController?.sink.add(null);
|
_watchController?.sink.add(null);
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
} on Exception catch (_) {
|
} on Exception catch (_) {
|
||||||
// Exception on write means state needs to be reverted
|
// Exception on write means state needs to be reverted
|
||||||
@ -607,8 +606,8 @@ class DHTShortArray {
|
|||||||
final recordSubkey = (index % _stride) + ((recordNumber == 0) ? 1 : 0);
|
final recordSubkey = (index % _stride) + ((recordNumber == 0) ? 1 : 0);
|
||||||
final result = await record.tryWriteBytes(newValue, subkey: recordSubkey);
|
final result = await record.tryWriteBytes(newValue, subkey: recordSubkey);
|
||||||
|
|
||||||
if (result != null) {
|
if (result == null) {
|
||||||
// A change happened, notify any listeners
|
// A newer value was not found, so the change took
|
||||||
_watchController?.sink.add(null);
|
_watchController?.sink.add(null);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -625,7 +624,7 @@ class DHTShortArray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> eventualUpdateItem(
|
Future<void> eventualUpdateItem(
|
||||||
int pos, Future<Uint8List> Function(Uint8List oldValue) update) async {
|
int pos, Future<Uint8List> Function(Uint8List? oldValue) update) async {
|
||||||
var oldData = await getItem(pos);
|
var oldData = await getItem(pos);
|
||||||
// Ensure it exists already
|
// Ensure it exists already
|
||||||
if (oldData == null) {
|
if (oldData == null) {
|
||||||
@ -633,7 +632,7 @@ class DHTShortArray {
|
|||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
// Update the data
|
// Update the data
|
||||||
final updatedData = await update(oldData!);
|
final updatedData = await update(oldData);
|
||||||
|
|
||||||
// Set it back
|
// Set it back
|
||||||
oldData = await tryWriteItem(pos, updatedData);
|
oldData = await tryWriteItem(pos, updatedData);
|
||||||
@ -673,14 +672,14 @@ class DHTShortArray {
|
|||||||
Future<void> eventualUpdateItemJson<T>(
|
Future<void> eventualUpdateItemJson<T>(
|
||||||
T Function(dynamic) fromJson,
|
T Function(dynamic) fromJson,
|
||||||
int pos,
|
int pos,
|
||||||
Future<T> Function(T) update,
|
Future<T> Function(T?) update,
|
||||||
) =>
|
) =>
|
||||||
eventualUpdateItem(pos, jsonUpdate(fromJson, update));
|
eventualUpdateItem(pos, jsonUpdate(fromJson, update));
|
||||||
|
|
||||||
Future<void> eventualUpdateItemProtobuf<T extends GeneratedMessage>(
|
Future<void> eventualUpdateItemProtobuf<T extends GeneratedMessage>(
|
||||||
T Function(List<int>) fromBuffer,
|
T Function(List<int>) fromBuffer,
|
||||||
int pos,
|
int pos,
|
||||||
Future<T> Function(T) update,
|
Future<T> Function(T?) update,
|
||||||
) =>
|
) =>
|
||||||
eventualUpdateItem(pos, protobufUpdate(fromBuffer, update));
|
eventualUpdateItem(pos, protobufUpdate(fromBuffer, update));
|
||||||
|
|
||||||
@ -692,14 +691,17 @@ class DHTShortArray {
|
|||||||
.wait;
|
.wait;
|
||||||
|
|
||||||
// Update changes to the head record
|
// Update changes to the head record
|
||||||
|
// Don't watch for local changes because this class already handles
|
||||||
|
// notifying listeners and knows when it makes local changes
|
||||||
if (!_subscriptions.containsKey(_headRecord.key)) {
|
if (!_subscriptions.containsKey(_headRecord.key)) {
|
||||||
_subscriptions[_headRecord.key] =
|
_subscriptions[_headRecord.key] =
|
||||||
await _headRecord.listen(_onUpdateRecord);
|
await _headRecord.listen(localChanges: false, _onUpdateRecord);
|
||||||
}
|
}
|
||||||
// Update changes to any linked records
|
// Update changes to any linked records
|
||||||
for (final lr in _head.linkedRecords) {
|
for (final lr in _head.linkedRecords) {
|
||||||
if (!_subscriptions.containsKey(lr.key)) {
|
if (!_subscriptions.containsKey(lr.key)) {
|
||||||
_subscriptions[lr.key] = await lr.listen(_onUpdateRecord);
|
_subscriptions[lr.key] =
|
||||||
|
await lr.listen(localChanges: false, _onUpdateRecord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on Exception {
|
} on Exception {
|
||||||
|
@ -13,14 +13,15 @@ Uint8List jsonEncodeBytes(Object? object,
|
|||||||
utf8.encode(jsonEncode(object, toEncodable: toEncodable)));
|
utf8.encode(jsonEncode(object, toEncodable: toEncodable)));
|
||||||
|
|
||||||
Future<Uint8List> jsonUpdateBytes<T>(T Function(dynamic) fromJson,
|
Future<Uint8List> jsonUpdateBytes<T>(T Function(dynamic) fromJson,
|
||||||
Uint8List oldBytes, Future<T> Function(T) update) async {
|
Uint8List? oldBytes, Future<T> Function(T?) update) async {
|
||||||
final oldObj = fromJson(jsonDecode(utf8.decode(oldBytes)));
|
final oldObj =
|
||||||
|
oldBytes == null ? null : fromJson(jsonDecode(utf8.decode(oldBytes)));
|
||||||
final newObj = await update(oldObj);
|
final newObj = await update(oldObj);
|
||||||
return jsonEncodeBytes(newObj);
|
return jsonEncodeBytes(newObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List> Function(Uint8List) jsonUpdate<T>(
|
Future<Uint8List> Function(Uint8List?) jsonUpdate<T>(
|
||||||
T Function(dynamic) fromJson, Future<T> Function(T) update) =>
|
T Function(dynamic) fromJson, Future<T> Function(T?) update) =>
|
||||||
(oldBytes) => jsonUpdateBytes(fromJson, oldBytes, update);
|
(oldBytes) => jsonUpdateBytes(fromJson, oldBytes, update);
|
||||||
|
|
||||||
T Function(Object?) genericFromJson<T>(
|
T Function(Object?) genericFromJson<T>(
|
||||||
|
@ -4,14 +4,14 @@ import 'package:protobuf/protobuf.dart';
|
|||||||
|
|
||||||
Future<Uint8List> protobufUpdateBytes<T extends GeneratedMessage>(
|
Future<Uint8List> protobufUpdateBytes<T extends GeneratedMessage>(
|
||||||
T Function(List<int>) fromBuffer,
|
T Function(List<int>) fromBuffer,
|
||||||
Uint8List oldBytes,
|
Uint8List? oldBytes,
|
||||||
Future<T> Function(T) update) async {
|
Future<T> Function(T?) update) async {
|
||||||
final oldObj = fromBuffer(oldBytes);
|
final oldObj = oldBytes == null ? null : fromBuffer(oldBytes);
|
||||||
final newObj = await update(oldObj);
|
final newObj = await update(oldObj);
|
||||||
return Uint8List.fromList(newObj.writeToBuffer());
|
return Uint8List.fromList(newObj.writeToBuffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List> Function(Uint8List)
|
Future<Uint8List> Function(Uint8List?)
|
||||||
protobufUpdate<T extends GeneratedMessage>(
|
protobufUpdate<T extends GeneratedMessage>(
|
||||||
T Function(List<int>) fromBuffer, Future<T> Function(T) update) =>
|
T Function(List<int>) fromBuffer, Future<T> Function(T?) update) =>
|
||||||
(oldBytes) => protobufUpdateBytes(fromBuffer, oldBytes, update);
|
(oldBytes) => protobufUpdateBytes(fromBuffer, oldBytes, update);
|
||||||
|
@ -411,7 +411,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
mutex:
|
mutex:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../mutex"
|
path: "../mutex"
|
||||||
relative: true
|
relative: true
|
||||||
|
@ -16,6 +16,9 @@ dependencies:
|
|||||||
json_annotation: ^4.8.1
|
json_annotation: ^4.8.1
|
||||||
loggy: ^2.0.3
|
loggy: ^2.0.3
|
||||||
meta: ^1.10.0
|
meta: ^1.10.0
|
||||||
|
mutex:
|
||||||
|
path: ../mutex
|
||||||
|
|
||||||
protobuf: ^3.0.0
|
protobuf: ^3.0.0
|
||||||
veilid:
|
veilid:
|
||||||
# veilid: ^0.0.1
|
# veilid: ^0.0.1
|
||||||
|
Loading…
Reference in New Issue
Block a user