blob: ecac39fe3f00de7c8c37b0285deab74e35860779 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
// ignore_for_file: implementation_imports
import 'package:sledge/src/document/change.dart';
import 'package:sledge/src/uint8list_ops.dart';
import 'package:sledge/src/document/values/key_value.dart';
import 'entry.dart';
/// StorageState is a key-value storage with timestamp per key.
/// It stores timestamps for deleted keys (time of deletion).
/// Used to fake Ledger's KeyValue storage.
class StorageState {
final Map<Uint8List, Entry> _storage = newUint8ListOrderedMap<Entry>();
static int globalIncrementalTimer = 0;
final void Function(Change change) _onChangeCallback;
StorageState([this._onChangeCallback]);
/// Applies [change] to storage. Uses [timestamp] as a time of update.
void applyChange(Change change, [int timestamp]) {
if (change.changedEntries.isEmpty && change.deletedKeys.isEmpty) {
return;
}
timestamp ??= globalIncrementalTimer++;
for (final entry in change.changedEntries) {
_storage[entry.key] = Entry(entry.value, timestamp);
}
for (final key in change.deletedKeys) {
_storage[key] = Entry.deleted(timestamp);
}
// TODO: check that changedEntries and deletedKeys do not intersect.
_onChangeCallback?.call(change);
}
/// Updates state of this with respect to [other]. Returns change done to this.
Change updateWith(StorageState other) {
final changedEntries = <KeyValue>[];
final deletedKeys = <Uint8List>[];
for (final entry in other._storage.entries) {
// Check which storage has the most recent value for the entry's key.
if (!_storage.containsKey(entry.key) ||
_storage[entry.key].timestamp < entry.value.timestamp) {
// Use other's value for this key.
if (other._storage[entry.key].isDeleted) {
// To handle deleted on [other] keys following strategy is used:
// If [key] was never presented on [this] - do nothing.
// In the other case copy the deletion entry from [other] to [this].
if (_storage.containsKey(entry.key) &&
!_storage[entry.key].isDeleted) {
// [key] should be added to [deletedKeys] only if is currently
// presented in [this].
deletedKeys.add(entry.key);
}
if (_storage.containsKey(entry.key)) {
_storage[entry.key] = entry.value;
}
} else {
changedEntries.add(KeyValue(entry.key, entry.value.value));
_storage[entry.key] = other._storage[entry.key];
}
}
}
final change = Change(changedEntries, deletedKeys);
if (change.changedEntries.isNotEmpty || change.deletedKeys.isNotEmpty) {
_onChangeCallback?.call(change);
}
return change;
}
List<KeyValue> getEntries(Uint8List keyPrefix) {
final entries = <KeyValue>[];
for (final key in _storage.keys) {
Entry value = _storage[key];
if (!value.isDeleted && hasPrefix(key, keyPrefix)) {
entries.add(KeyValue(key, value.value));
}
}
return entries;
}
}