resumePendingRotation method
Finishes a rotation that was interrupted, if there is one.
Idempotent, and safe to call when nothing is pending. Called by openDatabase and reopenDatabase before they touch a store, which is why an interrupted rotation is invisible in practice.
Every journalled store is tried under the key in force and then under the
pending one: whichever opens it decides whether it still has to be
re-encrypted. Throws StateError when localId is open (close it first)
or when a store opens under neither key.
Implementation
Future<void> resumePendingRotation(String localId) async {
final rotationKey = _rotationEntryKey(localId);
final journal = await _keyStore.getKey(rotationKey);
if (journal == null) return;
if (_databases.containsKey(localId)) {
throw StateError(
'Close database "$localId" before finishing its passphrase rotation.',
);
}
final (newKey, collections) = _decodeRotation(journal);
final entryKey = '${_config.appId}/$localId';
final current = await _keyStore.getKey(entryKey) ?? newKey;
if (!_sameKey(current, newKey)) {
await Database.rememberPreviousPassphraseKeyIn(
_keyStore, entryKey, current);
}
for (final collection in collections) {
await _finishRekey(localId, collection, from: current, to: newKey);
}
await _keyStore.setKey(entryKey, newKey);
_databaseKeys[localId] = newKey;
await _keyStore.clear(rotationKey);
}