| // 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; |
| } |
| } |