Automatically migrate from local storage to IndexedDb

This commit is contained in:
Simon Binder 2020-03-23 13:36:08 +01:00
parent 29eec7f784
commit cdd57f340d
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
4 changed files with 115 additions and 9 deletions

View File

@ -80,3 +80,17 @@ object via `window.db`. If you need to quickly run a query to check the state of
If you need to delete your databases, there stored using local storage. You can clear all your data with `localStorage.clear()`.
Web support is experimental at the moment, so please [report all issues](https://github.com/simolus3/moor/issues/new) you find.
## Using IndexedDb
The default `WebDatabase` uses local storage to store the raw sqlite database file. On browsers that support it, you can also
use `IndexedDb` to store that blob. In general, browsers allow a larger size for `IndexedDb`. The implementation is also more
performant, since we don't have to encode binary blobs as strings.
To use this implementation on browsers that support it, replace `WebDatabase(name)` with:
```dart
WebDatabase.withStorage(MoorWebStorage.indexedDbIfSupported(name))
```
Moor will automatically migrate data from local storage to `IndexeDb` when it is available.

View File

@ -8,6 +8,7 @@ library moor_web;
import 'dart:async';
import 'dart:html';
import 'dart:indexed_db';
import 'dart:js';
import 'package:meta/meta.dart';

View File

@ -29,9 +29,30 @@ abstract class MoorWebStorage {
/// This implementation is significantly faster than the default
/// implementation in local storage. Browsers also tend to allow more data
/// to be saved in IndexedDB.
///
/// When the [migrateFromLocalStorage] parameter (defaults to `true`) is set,
/// old data saved using the default [MoorWebStorage] will be migrated to the
/// IndexedDB based implementation. This parameter can be turned off for
/// applications that never used the local storage implementation as a small
/// performance improvement.
///
/// However, older browsers might not support IndexedDB.
@experimental
factory MoorWebStorage.indexedDb(String name) = _IndexedDbStorage;
factory MoorWebStorage.indexedDb(String name,
{bool migrateFromLocalStorage}) = _IndexedDbStorage;
/// Uses [MoorWebStorage.indexedDb] if the current browser supports it.
/// Otherwise, falls back to the local storage based implementation.
factory MoorWebStorage.indexedDbIfSupported(String name) {
return supportsIndexedDb
? MoorWebStorage.indexedDb(name)
: MoorWebStorage(name);
}
/// Attempts to check whether the current browser supports the
/// [MoorWebStorage.indexedDb] storage implementation.
static bool get supportsIndexedDb =>
IdbFactory.supported && context.hasProperty('FileReader');
}
abstract class _CustomSchemaVersionSave implements MoorWebStorage {
@ -39,11 +60,27 @@ abstract class _CustomSchemaVersionSave implements MoorWebStorage {
set schemaVersion(int value);
}
String _persistenceKeyForLocalStorage(String name) {
return 'moor_db_str_$name';
}
String _legacyVersionKeyForLocalStorage(String name) {
return 'moor_db_version_$name';
}
Uint8List /*?*/ _restoreLocalStorage(String name) {
final raw = window.localStorage[_persistenceKeyForLocalStorage(name)];
if (raw != null) {
return bin2str.decode(raw);
}
return null;
}
class _LocalStorageImpl implements MoorWebStorage, _CustomSchemaVersionSave {
final String name;
String get _persistenceKey => 'moor_db_str_$name';
String get _versionKey => 'moor_db_version_$name';
String get _persistenceKey => _persistenceKeyForLocalStorage(name);
String get _versionKey => _legacyVersionKeyForLocalStorage(name);
const _LocalStorageImpl(this.name);
@ -69,11 +106,7 @@ class _LocalStorageImpl implements MoorWebStorage, _CustomSchemaVersionSave {
@override
Future<Uint8List> restore() async {
final raw = window.localStorage[_persistenceKey];
if (raw != null) {
return bin2str.decode(raw);
}
return null;
return _restoreLocalStorage(name);
}
@override
@ -89,21 +122,33 @@ class _IndexedDbStorage implements MoorWebStorage {
static const _objectStoreName = 'moor_databases';
final String name;
final bool migrateFromLocalStorage;
Database _database;
_IndexedDbStorage(this.name);
_IndexedDbStorage(this.name, {this.migrateFromLocalStorage = true});
@override
Future<void> open() async {
var wasCreated = false;
_database = await window.indexedDB.open(
_objectStoreName,
version: 1,
onUpgradeNeeded: (event) {
final database = event.target.result as Database;
database.createObjectStore(_objectStoreName);
wasCreated = true;
},
);
if (migrateFromLocalStorage && wasCreated) {
final fromLocalStorage = _restoreLocalStorage(name);
if (fromLocalStorage != null) {
await store(fromLocalStorage);
}
}
}
@override

View File

@ -0,0 +1,46 @@
@TestOn('browser')
import 'dart:convert';
import 'package:moor/moor.dart';
import 'package:moor/moor_web.dart';
import 'package:test/test.dart';
final _allBytes = Uint8List.fromList(List.generate(256, (index) => index));
void main() {
test('can migrate from local storage to IndexedDb', () async {
const local = MoorWebStorage('name');
final idb = MoorWebStorage.indexedDb('name');
await local.open();
await local.store(_allBytes);
await local.close();
await idb.open();
final restored = await idb.restore();
await idb.close();
expect(restored, _allBytes);
});
test('does not migrate when idb database already exists', () async {
final otherPayload = Uint8List.fromList(utf8.encode('hello world'));
const local = MoorWebStorage('name');
final idb = MoorWebStorage.indexedDb('name');
await idb.open();
await idb.store(otherPayload);
await idb.close();
await local.open();
await local.store(_allBytes);
await local.close();
await idb.open();
final restored = await idb.restore();
await idb.close();
expect(restored, otherPayload);
});
}