From 0e7f2462b6b50a12f095b143345fe947d744496d Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Fri, 25 Jun 2021 00:26:56 +0800 Subject: [PATCH] Abstract album content provider --- lib/app_db.dart | 1 + lib/entity/album.dart | 100 ++++++---- lib/entity/album/provider.dart | 85 ++++++++ lib/entity/album/upgrader.dart | 26 +++ lib/use_case/remove.dart | 18 +- lib/use_case/resync_album.dart | 10 +- lib/widget/album_picker_dialog.dart | 4 +- lib/widget/album_viewer.dart | 21 +- lib/widget/home_albums.dart | 11 +- lib/widget/home_photos.dart | 12 +- lib/widget/new_album_dialog.dart | 5 +- lib/widget/viewer_detail_pane.dart | 12 +- test/entity/album_test.dart | 296 +++++++++++++++++++--------- 13 files changed, 452 insertions(+), 149 deletions(-) create mode 100644 lib/entity/album/provider.dart diff --git a/lib/app_db.dart b/lib/app_db.dart index 5c34a26b..b6a56e31 100644 --- a/lib/app_db.dart +++ b/lib/app_db.dart @@ -138,6 +138,7 @@ class AppDbAlbumEntry { Album.fromJson( json["album"].cast(), upgraderV1: AlbumUpgraderV1(), + upgraderV2: AlbumUpgraderV2(), ), ); } diff --git a/lib/entity/album.dart b/lib/entity/album.dart index 6fcd511e..7c474524 100644 --- a/lib/entity/album.dart +++ b/lib/entity/album.dart @@ -1,4 +1,3 @@ -import 'dart:collection'; import 'dart:convert'; import 'dart:math'; @@ -8,6 +7,7 @@ import 'package:idb_sqflite/idb_sqflite.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_db.dart'; +import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/album/upgrader.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file/data_source.dart'; @@ -126,15 +126,15 @@ class Album with EquatableMixin { Album({ DateTime lastUpdated, @required String name, - @required List items, + @required this.provider, this.albumFile, }) : this.lastUpdated = (lastUpdated ?? DateTime.now()).toUtc(), - this.name = name ?? "", - this.items = UnmodifiableListView(items); + this.name = name ?? ""; factory Album.fromJson( Map json, { AlbumUpgraderV1 upgraderV1, + AlbumUpgraderV2 upgraderV2, }) { final jsonVersion = json["version"]; if (jsonVersion < 2) { @@ -144,14 +144,20 @@ class Album with EquatableMixin { return null; } } + if (jsonVersion < 3) { + json = upgraderV2?.call(json); + if (json == null) { + _log.info("[fromJson] Version $jsonVersion not compatible"); + return null; + } + } return Album( lastUpdated: json["lastUpdated"] == null ? null : DateTime.parse(json["lastUpdated"]), name: json["name"], - items: (json["items"] as List) - .map((e) => AlbumItem.fromJson(e.cast())) - .toList(), + provider: + AlbumProvider.fromJson(json["provider"].cast()), albumFile: json["albumFile"] == null ? null : File.fromJson(json["albumFile"].cast()), @@ -160,12 +166,10 @@ class Album with EquatableMixin { @override toString({bool isDeep = false}) { - final itemsStr = - isDeep ? items.toReadableString() : "List {length: ${items.length}}"; return "$runtimeType {" "lastUpdated: $lastUpdated, " "name: $name, " - "items: $itemsStr, " + "provider: ${provider.toString(isDeep: isDeep)}, " "albumFile: $albumFile, " "}"; } @@ -178,13 +182,13 @@ class Album with EquatableMixin { Album copyWith({ DateTime lastUpdated, String name, - List items, + AlbumProvider provider, File albumFile, }) { return Album( lastUpdated: lastUpdated, name: name ?? this.name, - items: items ?? this.items, + provider: provider ?? this.provider, albumFile: albumFile ?? this.albumFile, ); } @@ -194,7 +198,7 @@ class Album with EquatableMixin { "version": version, "lastUpdated": lastUpdated.toIso8601String(), "name": name, - "items": items.map((e) => e.toJson()).toList(), + "provider": provider.toJson(), // ignore albumFile }; } @@ -204,7 +208,7 @@ class Album with EquatableMixin { "version": version, "lastUpdated": lastUpdated.toIso8601String(), "name": name, - "items": items.map((e) => e.toJson()).toList(), + "provider": provider.toJson(), if (albumFile != null) "albumFile": albumFile.toJson(), }; } @@ -213,15 +217,14 @@ class Album with EquatableMixin { get props => [ lastUpdated, name, - items, + provider, albumFile, ]; final DateTime lastUpdated; final String name; - /// Immutable list of items. Modifying the list will result in an error - final List items; + final AlbumProvider provider; /// How is this album stored on server /// @@ -229,7 +232,7 @@ class Album with EquatableMixin { final File albumFile; /// versioning of this class, use to upgrade old persisted album - static const version = 2; + static const version = 3; } class AlbumRepo { @@ -281,6 +284,7 @@ class AlbumRemoteDataSource implements AlbumDataSource { return Album.fromJson( jsonDecode(utf8.decode(data)), upgraderV1: AlbumUpgraderV1(), + upgraderV2: AlbumUpgraderV2(), ).copyWith(albumFile: albumFile); } catch (e, stacktrace) { dynamic d = data; @@ -343,11 +347,19 @@ class AlbumAppDbDataSource implements AlbumDataSource { if (results?.isNotEmpty == true) { final entries = results .map((e) => AppDbAlbumEntry.fromJson(e.cast())); - final items = entries.map((e) { - _log.info("[get] ${e.path}[${e.index}]"); - return e.album.items; - }).reduce((value, element) => value + element); - return entries.first.album.copyWith(items: items); + if (entries.length > 1) { + final items = entries.map((e) { + _log.info("[get] ${e.path}[${e.index}]"); + return AlbumStaticProvider.of(e.album).items; + }).reduce((value, element) => value + element); + return entries.first.album.copyWith( + provider: AlbumStaticProvider( + items: items, + ), + ); + } else { + return entries.first.album; + } } else { throw CacheNotFoundException("No entry: $path"); } @@ -461,27 +473,37 @@ Future _cacheAlbum( final range = KeyRange.bound([path, 0], [path, int_util.int32Max]); // count number of entries for this album final count = await index.count(range); - int newCount = 0; - var albumItemLists = - partition(album.items, AppDbAlbumEntry.maxDataSize).toList(); - if (albumItemLists.isEmpty) { - albumItemLists = [[]]; + // cut large album into smaller pieces, needed to workaround Android DB + // limitation + final entries = []; + if (album.provider is AlbumStaticProvider) { + var albumItemLists = partition( + AlbumStaticProvider.of(album).items, AppDbAlbumEntry.maxDataSize) + .toList(); + if (albumItemLists.isEmpty) { + albumItemLists = [[]]; + } + entries.addAll(albumItemLists.withIndex().map((pair) => AppDbAlbumEntry( + path, + pair.item1, + album.copyWith( + provider: AlbumStaticProvider(items: pair.item2), + )))); + } else { + entries.add(AppDbAlbumEntry(path, 0, album)); } - for (final pair in albumItemLists.withIndex()) { - _log.info( - "[_cacheAlbum] Caching $path[${pair.item1}], length: ${pair.item2.length}"); - await store.put( - AppDbAlbumEntry(path, pair.item1, album.copyWith(items: pair.item2)) - .toJson(), - AppDbAlbumEntry.toPrimaryKey(account, album.albumFile, pair.item1), - ); - ++newCount; + for (final e in entries) { + _log.info("[_cacheAlbum] Caching ${e.path}[${e.index}]"); + await store.put(e.toJson(), + AppDbAlbumEntry.toPrimaryKey(account, e.album.albumFile, e.index)); } - if (count > newCount) { + + if (count > entries.length) { // index is 0-based - final rmRange = KeyRange.bound([path, newCount], [path, int_util.int32Max]); + final rmRange = + KeyRange.bound([path, entries.length], [path, int_util.int32Max]); final rmKeys = await index .openKeyCursor(range: rmRange, autoAdvance: true) .map((cursor) => cursor.primaryKey) diff --git a/lib/entity/album/provider.dart b/lib/entity/album/provider.dart new file mode 100644 index 00000000..6db824f2 --- /dev/null +++ b/lib/entity/album/provider.dart @@ -0,0 +1,85 @@ +import 'dart:collection'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; +import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/iterable_extension.dart'; + +abstract class AlbumProvider with EquatableMixin { + const AlbumProvider(); + + factory AlbumProvider.fromJson(Map json) { + final type = json["type"]; + final content = json["content"]; + switch (type) { + case AlbumStaticProvider._type: + return AlbumStaticProvider.fromJson(content.cast()); + default: + _log.shout("[fromJson] Unknown type: $type"); + throw ArgumentError.value(type, "type"); + } + } + + Map toJson() { + String getType() { + if (this is AlbumStaticProvider) { + return AlbumStaticProvider._type; + } else { + throw StateError("Unknwon subtype"); + } + } + + return { + "type": getType(), + "content": _toContentJson(), + }; + } + + @override + toString({bool isDeep = false}); + + Map _toContentJson(); + + static final _log = Logger("entity.album.provider.AlbumProvider"); +} + +class AlbumStaticProvider extends AlbumProvider { + AlbumStaticProvider({ + @required List items, + }) : this.items = UnmodifiableListView(items); + + factory AlbumStaticProvider.fromJson(Map json) { + return AlbumStaticProvider( + items: (json["items"] as List) + .map((e) => AlbumItem.fromJson(e.cast())) + .toList(), + ); + } + + @override + toString({bool isDeep = false}) { + final itemsStr = + isDeep ? items.toReadableString() : "List {length: ${items.length}}"; + return "$runtimeType {" + "items: $itemsStr, " + "}"; + } + + @override + get props => [ + items, + ]; + + @override + _toContentJson() { + return { + "items": items.map((e) => e.toJson()).toList(), + }; + } + + /// Immutable list of items. Modifying the list will result in an error + final List items; + + static const _type = "static"; +} diff --git a/lib/entity/album/upgrader.dart b/lib/entity/album/upgrader.dart index 7fcb456c..cf768e18 100644 --- a/lib/entity/album/upgrader.dart +++ b/lib/entity/album/upgrader.dart @@ -23,3 +23,29 @@ class AlbumUpgraderV1 implements AlbumUpgrader { static final _log = Logger("entity.album.upgrader.AlbumUpgraderV1"); } + +/// Upgrade v2 Album to v3 +class AlbumUpgraderV2 implements AlbumUpgrader { + AlbumUpgraderV2({ + this.logFilePath, + }); + + Map call(Map json) { + // move v2 items to v3 provider + _log.fine("[call] Upgrade v2 Album for file: $logFilePath"); + final result = Map.from(json); + result["provider"] = { + "type": "static", + "content": { + "items": result["items"], + } + }; + result.remove("items"); + return result; + } + + /// File path for logging only + final String logFilePath; + + static final _log = Logger("entity.album.upgrader.AlbumUpgraderV2"); +} diff --git a/lib/use_case/remove.dart b/lib/use_case/remove.dart index 87f1247a..27e031f9 100644 --- a/lib/use_case/remove.dart +++ b/lib/use_case/remove.dart @@ -3,6 +3,7 @@ import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/event/event.dart'; import 'package:nc_photos/use_case/list_album.dart'; @@ -23,18 +24,27 @@ class Remove { Future _cleanUpAlbums(Account account, File file) async { final albums = await ListAlbum(fileRepo, albumRepo)(account); - for (final a in albums) { + // clean up only make sense for static albums + for (final a + in albums.where((element) => element.provider is AlbumStaticProvider)) { try { - if (a.items.any((element) => + final provider = AlbumStaticProvider.of(a); + if (provider.items.any((element) => element is AlbumFileItem && element.file.path == file.path)) { - final newItems = a.items.where((element) { + final newItems = provider.items.where((element) { if (element is AlbumFileItem) { return element.file.path != file.path; } else { return true; } }).toList(); - await UpdateAlbum(albumRepo)(account, a.copyWith(items: newItems)); + await UpdateAlbum(albumRepo)( + account, + a.copyWith( + provider: AlbumStaticProvider( + items: newItems, + ), + )); } } catch (e, stacktrace) { _log.shout( diff --git a/lib/use_case/resync_album.dart b/lib/use_case/resync_album.dart index 4c249d61..f80b98ca 100644 --- a/lib/use_case/resync_album.dart +++ b/lib/use_case/resync_album.dart @@ -4,18 +4,24 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_db.dart'; import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; /// Resync files inside an album with the file db class ResyncAlbum { Future call(Account account, Album album) async { + if (album.provider is! AlbumStaticProvider) { + _log.warning( + "[call] Resync only make sense for static albums: ${album.name}"); + return album; + } return await AppDb.use((db) async { final transaction = db.transaction(AppDb.fileDbStoreName, idbModeReadWrite); final store = transaction.objectStore(AppDb.fileDbStoreName); final index = store.index(AppDbFileDbEntry.indexName); final newItems = []; - for (final item in album.items) { + for (final item in AlbumStaticProvider.of(album).items) { if (item is AlbumFileItem) { try { newItems.add(await _syncOne(account, item, store, index)); @@ -30,7 +36,7 @@ class ResyncAlbum { newItems.add(item); } } - return album.copyWith(items: newItems); + return album.copyWith(provider: AlbumStaticProvider(items: newItems)); }); } diff --git a/lib/widget/album_picker_dialog.dart b/lib/widget/album_picker_dialog.dart index 5de67bf4..dba474ed 100644 --- a/lib/widget/album_picker_dialog.dart +++ b/lib/widget/album_picker_dialog.dart @@ -7,6 +7,7 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/bloc/list_album.dart'; import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/snack_bar_manager.dart'; @@ -139,7 +140,8 @@ class _AlbumPickerDialogState extends State { void _transformItems(List albums) { _items.clear(); - _items.addAll(albums); + _items.addAll( + albums.where((element) => element.provider is AlbumStaticProvider)); } void _reqQuery() { diff --git a/lib/widget/album_viewer.dart b/lib/widget/album_viewer.dart index 8cb402eb..b18caf4c 100644 --- a/lib/widget/album_viewer.dart +++ b/lib/widget/album_viewer.dart @@ -8,6 +8,7 @@ import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/exception_util.dart' as exception_util; @@ -77,6 +78,7 @@ class _AlbumViewerState extends State get itemStreamListCellSize => _thumbSize; void _initAlbum() { + assert(widget.album.provider is AlbumStaticProvider); ResyncAlbum()(widget.account, widget.album).then((album) { if (_shouldPropagateResyncedAlbum(album)) { UpdateAlbum(AlbumRepo(AlbumCachedDataSource()))(widget.account, album) @@ -247,7 +249,7 @@ class _AlbumViewerState extends State final selectedIndexes = selectedListItems.map((e) => itemStreamListItems.indexOf(e)).toList(); final selectedFiles = _backingFiles.takeIndex(selectedIndexes).toList(); - final newItems = _album.items.where((element) { + final newItems = _getAlbumItemsOf(_album).where((element) { if (element is AlbumFileItem) { return !selectedFiles.any((select) => select.path == element.file.path); } else { @@ -256,7 +258,9 @@ class _AlbumViewerState extends State }).toList(); final albumRepo = AlbumRepo(AlbumCachedDataSource()); final newAlbum = _album.copyWith( - items: newItems, + provider: AlbumStaticProvider( + items: newItems, + ), ); UpdateAlbum(albumRepo)(widget.account, newAlbum).then((_) { SnackBarManager().showSnackBar(SnackBar( @@ -286,7 +290,7 @@ class _AlbumViewerState extends State } void _transformItems() { - _backingFiles = _album.items + _backingFiles = _getAlbumItemsOf(_album) .whereType() .map((e) => e.file) .where((element) => file_util.isSupportedFormat(element)) @@ -320,12 +324,14 @@ class _AlbumViewerState extends State } bool _shouldPropagateResyncedAlbum(Album album) { - if (widget.album.items.length != album.items.length) { + final origItems = _getAlbumItemsOf(widget.album); + final resyncItems = _getAlbumItemsOf(album); + if (origItems.length != resyncItems.length) { _log.info( - "[_shouldPropagateResyncedAlbum] Item length differ: ${widget.album.items.length}, ${album.items.length}"); + "[_shouldPropagateResyncedAlbum] Item length differ: ${origItems.length}, ${resyncItems.length}"); return true; } - for (final z in zip([widget.album.items, album.items])) { + for (final z in zip([origItems, resyncItems])) { final a = z[0], b = z[1]; bool isEqual; if (a is AlbumFileItem && b is AlbumFileItem) { @@ -344,6 +350,9 @@ class _AlbumViewerState extends State return false; } + static List _getAlbumItemsOf(Album a) => + AlbumStaticProvider.of(a).items; + int get _thumbSize { switch (_thumbZoomLevel) { case 1: diff --git a/lib/widget/home_albums.dart b/lib/widget/home_albums.dart index 11c48d00..5de12689 100644 --- a/lib/widget/home_albums.dart +++ b/lib/widget/home_albums.dart @@ -12,6 +12,7 @@ import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/bloc/list_album.dart'; import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file/data_source.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; @@ -175,10 +176,15 @@ class _HomeAlbumsState extends State { Widget _buildAlbumItem(BuildContext context, int index) { final item = _items[index]; + var subtitle = ""; + if (item.album.provider is AlbumStaticProvider) { + subtitle = AppLocalizations.of(context) + .albumSize(AlbumStaticProvider.of(item.album).items.length); + } return AlbumGridItem( cover: _buildAlbumCover(context, item.album), title: item.album.name, - subtitle: AppLocalizations.of(context).albumSize(item.album.items.length), + subtitle: subtitle, isSelected: _selectedItems.contains(item), onTap: () => _onItemTap(item), onLongPress: _isSelectionMode ? null : () => _onItemLongPress(item), @@ -386,7 +392,8 @@ class _HomeAlbumsState extends State { final sortedAlbums = albums.map((e) { // find the latest file in this album try { - final lastItem = e.items + final lastItem = AlbumStaticProvider.of(e) + .items .whereType() .map((e) => e.file) .where((element) => file_util.isSupportedFormat(element)) diff --git a/lib/widget/home_photos.dart b/lib/widget/home_photos.dart index 18f4f82d..8dc80167 100644 --- a/lib/widget/home_photos.dart +++ b/lib/widget/home_photos.dart @@ -12,6 +12,7 @@ import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/bloc/scan_dir.dart'; import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file/data_source.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; @@ -311,6 +312,7 @@ class _HomePhotosState extends State } Future _addSelectedToAlbum(BuildContext context, Album album) async { + assert(album.provider is AlbumStaticProvider); final selected = selectedListItems .whereType<_FileListItem>() .map((e) => AlbumFileItem(file: e.file)) @@ -320,10 +322,12 @@ class _HomePhotosState extends State await UpdateAlbum(albumRepo)( widget.account, album.copyWith( - items: makeDistinctAlbumItems([ - ...album.items, - ...selected, - ]), + provider: AlbumStaticProvider( + items: makeDistinctAlbumItems([ + ...AlbumStaticProvider.of(album).items, + ...selected, + ]), + ), )); } catch (e, stacktrace) { _log.shout( diff --git a/lib/widget/new_album_dialog.dart b/lib/widget/new_album_dialog.dart index 831d0352..671dc2e4 100644 --- a/lib/widget/new_album_dialog.dart +++ b/lib/widget/new_album_dialog.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/use_case/create_album.dart'; /// Dialog to create a new album @@ -63,7 +64,9 @@ class _NewAlbumDialogState extends State { _formKey.currentState.save(); final album = Album( name: _formValue.name, - items: const [], + provider: AlbumStaticProvider( + items: const [], + ), ); _log.info("[_onOkPressed] Creating album: $album"); final albumRepo = AlbumRepo(AlbumCachedDataSource()); diff --git a/lib/widget/viewer_detail_pane.dart b/lib/widget/viewer_detail_pane.dart index 338d630a..5bcc3d1b 100644 --- a/lib/widget/viewer_detail_pane.dart +++ b/lib/widget/viewer_detail_pane.dart @@ -9,6 +9,7 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/double_extension.dart'; import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file/data_source.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; @@ -348,10 +349,12 @@ class _ViewerDetailPaneState extends State { } Future _addToAlbum(BuildContext context, Album album) async { + assert(album.provider is AlbumStaticProvider); try { final albumRepo = AlbumRepo(AlbumCachedDataSource()); final newItem = AlbumFileItem(file: widget.file); - if (album.items + if (AlbumStaticProvider.of(album) + .items .whereType() .containsIf(newItem, (a, b) => a.file.path == b.file.path)) { // already added, do nothing @@ -366,7 +369,12 @@ class _ViewerDetailPaneState extends State { await UpdateAlbum(albumRepo)( widget.account, album.copyWith( - items: [...album.items, AlbumFileItem(file: widget.file)], + provider: AlbumStaticProvider( + items: [ + ...AlbumStaticProvider.of(album).items, + AlbumFileItem(file: widget.file), + ], + ), )); } catch (e, stacktrace) { _log.shout("[_addToAlbum] Failed while updating album", e, stacktrace); diff --git a/test/entity/album_test.dart b/test/entity/album_test.dart index 21b67e17..8f982cee 100644 --- a/test/entity/album_test.dart +++ b/test/entity/album_test.dart @@ -1,4 +1,5 @@ import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/album/upgrader.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:test/test.dart'; @@ -10,14 +11,21 @@ void main() { final json = { "version": Album.version, "lastUpdated": "2020-01-02T03:04:05.678901Z", - "items": [], + "provider": { + "type": "static", + "content": { + "items": [], + }, + }, }; expect( Album.fromJson(json), Album( lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901), name: "", - items: [], + provider: AlbumStaticProvider( + items: [], + ), )); }); @@ -26,54 +34,68 @@ void main() { "version": Album.version, "lastUpdated": "2020-01-02T03:04:05.678901Z", "name": "album", - "items": [], + "provider": { + "type": "static", + "content": { + "items": [], + }, + }, }; expect( Album.fromJson(json), Album( lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901), name: "album", - items: [], + provider: AlbumStaticProvider( + items: [], + ), )); }); - test("items", () { + test("AlbumStaticProvider", () { final json = { "version": Album.version, "lastUpdated": "2020-01-02T03:04:05.678901Z", "name": "", - "items": [ - { - "type": "file", - "content": { - "file": { - "path": "remote.php/dav/files/admin/test1.jpg", + "provider": { + "type": "static", + "content": { + "items": [ + { + "type": "file", + "content": { + "file": { + "path": "remote.php/dav/files/admin/test1.jpg", + }, + }, }, - }, - }, - { - "type": "file", - "content": { - "file": { - "path": "remote.php/dav/files/admin/test2.jpg", + { + "type": "file", + "content": { + "file": { + "path": "remote.php/dav/files/admin/test2.jpg", + }, + }, }, - }, + ], }, - ] + }, }; expect( Album.fromJson(json), Album( lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901), name: "", - items: [ - AlbumFileItem( - file: File(path: "remote.php/dav/files/admin/test1.jpg"), - ), - AlbumFileItem( - file: File(path: "remote.php/dav/files/admin/test2.jpg"), - ), - ], + provider: AlbumStaticProvider( + items: [ + AlbumFileItem( + file: File(path: "remote.php/dav/files/admin/test1.jpg"), + ), + AlbumFileItem( + file: File(path: "remote.php/dav/files/admin/test2.jpg"), + ), + ], + ), )); }); @@ -81,7 +103,12 @@ void main() { final json = { "version": Album.version, "lastUpdated": "2020-01-02T03:04:05.678901Z", - "items": [], + "provider": { + "type": "static", + "content": { + "items": [], + }, + }, "albumFile": { "path": "remote.php/dav/files/admin/test1.jpg", }, @@ -91,7 +118,9 @@ void main() { Album( lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901), name: "", - items: [], + provider: AlbumStaticProvider( + items: [], + ), albumFile: File(path: "remote.php/dav/files/admin/test1.jpg"), )); }); @@ -102,13 +131,20 @@ void main() { final album = Album( lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901), name: "", - items: [], + provider: AlbumStaticProvider( + items: [], + ), ); expect(album.toRemoteJson(), { "version": Album.version, "lastUpdated": "2020-01-02T03:04:05.678901Z", "name": "", - "items": [], + "provider": { + "type": "static", + "content": { + "items": [], + }, + }, }); }); @@ -116,51 +152,65 @@ void main() { final album = Album( lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901), name: "album", - items: [], + provider: AlbumStaticProvider( + items: [], + ), ); expect(album.toRemoteJson(), { "version": Album.version, "lastUpdated": "2020-01-02T03:04:05.678901Z", "name": "album", - "items": [], + "provider": { + "type": "static", + "content": { + "items": [], + }, + }, }); }); - test("items", () { + test("AlbumStaticProvider", () { final album = Album( lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901), name: "", - items: [ - AlbumFileItem( - file: File(path: "remote.php/dav/files/admin/test1.jpg"), - ), - AlbumFileItem( - file: File(path: "remote.php/dav/files/admin/test2.jpg"), - ), - ], + provider: AlbumStaticProvider( + items: [ + AlbumFileItem( + file: File(path: "remote.php/dav/files/admin/test1.jpg"), + ), + AlbumFileItem( + file: File(path: "remote.php/dav/files/admin/test2.jpg"), + ), + ], + ), ); expect(album.toRemoteJson(), { "version": Album.version, "lastUpdated": "2020-01-02T03:04:05.678901Z", "name": "", - "items": [ - { - "type": "file", - "content": { - "file": { - "path": "remote.php/dav/files/admin/test1.jpg", + "provider": { + "type": "static", + "content": { + "items": [ + { + "type": "file", + "content": { + "file": { + "path": "remote.php/dav/files/admin/test1.jpg", + }, + }, }, - }, - }, - { - "type": "file", - "content": { - "file": { - "path": "remote.php/dav/files/admin/test2.jpg", + { + "type": "file", + "content": { + "file": { + "path": "remote.php/dav/files/admin/test2.jpg", + }, + }, }, - }, + ], }, - ] + }, }); }); }); @@ -170,13 +220,20 @@ void main() { final album = Album( lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901), name: "", - items: [], + provider: AlbumStaticProvider( + items: [], + ), ); expect(album.toAppDbJson(), { "version": Album.version, "lastUpdated": "2020-01-02T03:04:05.678901Z", "name": "", - "items": [], + "provider": { + "type": "static", + "content": { + "items": [], + }, + }, }); }); @@ -184,51 +241,65 @@ void main() { final album = Album( lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901), name: "album", - items: [], + provider: AlbumStaticProvider( + items: [], + ), ); expect(album.toAppDbJson(), { "version": Album.version, "lastUpdated": "2020-01-02T03:04:05.678901Z", "name": "album", - "items": [], + "provider": { + "type": "static", + "content": { + "items": [], + }, + }, }); }); - test("items", () { + test("AlbumStaticProvider", () { final album = Album( lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901), name: "", - items: [ - AlbumFileItem( - file: File(path: "remote.php/dav/files/admin/test1.jpg"), - ), - AlbumFileItem( - file: File(path: "remote.php/dav/files/admin/test2.jpg"), - ), - ], + provider: AlbumStaticProvider( + items: [ + AlbumFileItem( + file: File(path: "remote.php/dav/files/admin/test1.jpg"), + ), + AlbumFileItem( + file: File(path: "remote.php/dav/files/admin/test2.jpg"), + ), + ], + ), ); expect(album.toAppDbJson(), { "version": Album.version, "lastUpdated": "2020-01-02T03:04:05.678901Z", "name": "", - "items": [ - { - "type": "file", - "content": { - "file": { - "path": "remote.php/dav/files/admin/test1.jpg", + "provider": { + "type": "static", + "content": { + "items": [ + { + "type": "file", + "content": { + "file": { + "path": "remote.php/dav/files/admin/test1.jpg", + }, + }, }, - }, - }, - { - "type": "file", - "content": { - "file": { - "path": "remote.php/dav/files/admin/test2.jpg", + { + "type": "file", + "content": { + "file": { + "path": "remote.php/dav/files/admin/test2.jpg", + }, + }, }, - }, + ], }, - ] + }, }); }); @@ -236,14 +307,21 @@ void main() { final album = Album( lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901), name: "", - items: [], + provider: AlbumStaticProvider( + items: [], + ), albumFile: File(path: "remote.php/dav/files/admin/test1.jpg"), ); expect(album.toAppDbJson(), { "version": Album.version, "lastUpdated": "2020-01-02T03:04:05.678901Z", "name": "", - "items": [], + "provider": { + "type": "static", + "content": { + "items": [], + }, + }, "albumFile": { "path": "remote.php/dav/files/admin/test1.jpg", }, @@ -278,5 +356,47 @@ void main() { }, }); }); + + test("AlbumUpgraderV2", () { + final json = { + "version": 2, + "lastUpdated": "2020-01-02T03:04:05.678901Z", + "items": [ + { + "type": "file", + "content": { + "file": { + "path": "remote.php/dav/files/admin/test1.jpg", + }, + }, + }, + ], + "albumFile": { + "path": "remote.php/dav/files/admin/test1.json", + }, + }; + expect(AlbumUpgraderV2()(json), { + "version": 2, + "lastUpdated": "2020-01-02T03:04:05.678901Z", + "provider": { + "type": "static", + "content": { + "items": [ + { + "type": "file", + "content": { + "file": { + "path": "remote.php/dav/files/admin/test1.jpg", + }, + }, + }, + ], + }, + }, + "albumFile": { + "path": "remote.php/dav/files/admin/test1.json", + }, + }); + }); }); }