From cdd57f340db7b76fbabff7a3d46e037f99841dcc Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 23 Mar 2020 13:36:08 +0100 Subject: [PATCH] Automatically migrate from local storage to IndexedDb --- docs/content/en/docs/Other engines/web.md | 14 +++++ moor/lib/moor_web.dart | 1 + moor/lib/src/web/storage.dart | 63 ++++++++++++++++--- .../web/local_storage_to_indexed_db_test.dart | 46 ++++++++++++++ 4 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 moor/test/web/local_storage_to_indexed_db_test.dart diff --git a/docs/content/en/docs/Other engines/web.md b/docs/content/en/docs/Other engines/web.md index b054d833..0c1583d4 100644 --- a/docs/content/en/docs/Other engines/web.md +++ b/docs/content/en/docs/Other engines/web.md @@ -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. \ No newline at end of file diff --git a/moor/lib/moor_web.dart b/moor/lib/moor_web.dart index 2ef8bfae..5a3d4b59 100644 --- a/moor/lib/moor_web.dart +++ b/moor/lib/moor_web.dart @@ -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'; diff --git a/moor/lib/src/web/storage.dart b/moor/lib/src/web/storage.dart index e354d1ad..3a886ff1 100644 --- a/moor/lib/src/web/storage.dart +++ b/moor/lib/src/web/storage.dart @@ -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 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 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 diff --git a/moor/test/web/local_storage_to_indexed_db_test.dart b/moor/test/web/local_storage_to_indexed_db_test.dart new file mode 100644 index 00000000..607d3190 --- /dev/null +++ b/moor/test/web/local_storage_to_indexed_db_test.dart @@ -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); + }); +}