From 4d9644ac186f02317de1d7244c3ba517b9eaeb1c Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Sun, 7 May 2023 01:44:35 +0800 Subject: [PATCH] Correctly show shared items in a collaborative nc album --- app/lib/api/api_util.dart | 15 +- app/lib/api/entity_converter.dart | 59 ++- app/lib/api/entity_converter.g.dart | 4 +- .../entity/collection/adapter/nc_album.dart | 23 +- .../collection/content_provider/nc_album.dart | 8 +- .../nc_album_item_adapter.dart | 20 + .../nc_album_item_adapter.g.dart | 15 + app/lib/entity/nc_album.dart | 31 +- app/lib/entity/nc_album.g.dart | 7 + app/lib/entity/nc_album/data_source.dart | 70 ++- app/lib/entity/nc_album/item.dart | 5 - app/lib/entity/nc_album/repo.dart | 2 +- app/lib/entity/nc_album_item.dart | 95 ++++ app/lib/entity/nc_album_item.g.dart | 14 + app/lib/entity/sqlite/database.dart | 6 +- app/lib/entity/sqlite/database.g.dart | 465 +++++++++++++++++- app/lib/entity/sqlite/table.dart | 11 + app/lib/entity/sqlite/type_converter.dart | 56 ++- .../use_case/nc_album/list_nc_album_item.dart | 2 +- app/lib/widget/collection_browser.dart | 1 + app/lib/widget/collection_browser/type.dart | 16 +- app/lib/widget/network_thumbnail.dart | 9 + np_api/lib/np_api.dart | 1 + np_api/lib/src/entity/entity.dart | 73 ++- np_api/lib/src/entity/entity.g.dart | 16 +- .../lib/src/entity/nc_album_item_parser.dart | 140 ++++++ np_api/lib/src/entity/nc_album_parser.dart | 34 ++ np_api/test/entity/nc_album_parser_test.dart | 247 ++++++++++ 28 files changed, 1347 insertions(+), 98 deletions(-) create mode 100644 app/lib/entity/collection_item/nc_album_item_adapter.dart create mode 100644 app/lib/entity/collection_item/nc_album_item_adapter.g.dart delete mode 100644 app/lib/entity/nc_album/item.dart create mode 100644 app/lib/entity/nc_album_item.dart create mode 100644 app/lib/entity/nc_album_item.g.dart create mode 100644 np_api/lib/src/entity/nc_album_item_parser.dart create mode 100644 np_api/test/entity/nc_album_parser_test.dart diff --git a/app/lib/api/api_util.dart b/app/lib/api/api_util.dart index 7716b5ff..59988002 100644 --- a/app/lib/api/api_util.dart +++ b/app/lib/api/api_util.dart @@ -7,8 +7,9 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; +import 'package:nc_photos/entity/nc_album_item.dart'; import 'package:nc_photos/exception.dart'; -import 'package:np_api/np_api.dart'; +import 'package:np_api/np_api.dart' hide NcAlbumItem; import 'package:to_string/to_string.dart'; part 'api_util.g.dart'; @@ -76,6 +77,18 @@ String getFilePreviewUrlByFileId( return url; } +/// Return the preview image URL for an item in [NcAlbum]. We can't use the +/// generic file preview url because collaborative albums do not create a file +/// share for photos not belonging to you, that means you can only access the +/// file view the Photos API +String getNcAlbumFilePreviewUrl( + Account account, + NcAlbumItem item, { + required int width, + required int height, +}) => + "${account.url}/apps/photos/api/v1/preview/${item.fileId}?x=$width&y=$height"; + String getFileUrl(Account account, FileDescriptor file) { return "${account.url}/${getFileUrlRelative(file)}"; } diff --git a/app/lib/api/entity_converter.dart b/app/lib/api/entity_converter.dart index 5afc0c5f..52b21522 100644 --- a/app/lib/api/entity_converter.dart +++ b/app/lib/api/entity_converter.dart @@ -5,6 +5,7 @@ import 'package:nc_photos/entity/face.dart'; import 'package:nc_photos/entity/favorite.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/nc_album.dart'; +import 'package:nc_photos/entity/nc_album_item.dart'; import 'package:nc_photos/entity/person.dart'; import 'package:nc_photos/entity/share.dart'; import 'package:nc_photos/entity/sharee.dart'; @@ -35,7 +36,6 @@ class ApiFavoriteConverter { } } -@npLog class ApiFileConverter { static File fromApi(api.File file) { final metadata = file.customProperties?["com.nkming.nc_photos:metadata"] @@ -79,26 +79,12 @@ class ApiFileConverter { ?.run((obj) => ImageLocation.fromJson(jsonDecode(obj))), ); } - - static String _hrefToPath(String href) { - final rawPath = href.trimLeftAny("/"); - final pos = rawPath.indexOf("remote.php"); - if (pos == -1) { - // what? - _log.warning("[_hrefToPath] Unknown href value: $rawPath"); - return rawPath; - } else { - return rawPath.substring(pos); - } - } - - static final _log = _$ApiFileConverterNpLog.log; } class ApiNcAlbumConverter { static NcAlbum fromApi(api.NcAlbum album) { return NcAlbum( - path: album.href, + path: _hrefToPath(album.href), lastPhoto: (album.lastPhoto ?? -1) < 0 ? null : album.lastPhoto, nbItems: album.nbItems ?? 0, location: album.location, @@ -106,7 +92,30 @@ class ApiNcAlbumConverter { ?.run((d) => DateTime.fromMillisecondsSinceEpoch(d * 1000)), dateEnd: (album.dateRange?["end"] as int?) ?.run((d) => DateTime.fromMillisecondsSinceEpoch(d * 1000)), - collaborators: const [], + collaborators: album.collaborators + .map((c) => NcAlbumCollaborator( + id: c.id.toCi(), + label: c.label, + type: c.type, + )) + .toList(), + ); + } +} + +class ApiNcAlbumItemConverter { + static NcAlbumItem fromApi(api.NcAlbumItem item) { + return NcAlbumItem( + path: _hrefToPath(item.href), + fileId: item.fileId!, + contentLength: item.contentLength, + contentType: item.contentType, + etag: item.etag, + lastModified: item.lastModified, + hasPreview: item.hasPreview, + isFavorite: item.favorite, + fileMetadataWidth: item.fileMetadataSize?["width"], + fileMetadataHeight: item.fileMetadataSize?["height"], ); } } @@ -188,3 +197,19 @@ class ApiTaggedFileConverter { ); } } + +String _hrefToPath(String href) { + final rawPath = href.trimLeftAny("/"); + final pos = rawPath.indexOf("remote.php"); + if (pos == -1) { + // what? + _$_NpLog.log.warning("[_hrefToPath] Unknown href value: $rawPath"); + return rawPath; + } else { + return rawPath.substring(pos); + } +} + +@npLog +// ignore: camel_case_types +class _ {} diff --git a/app/lib/api/entity_converter.g.dart b/app/lib/api/entity_converter.g.dart index 52f114c6..dcfd8fe7 100644 --- a/app/lib/api/entity_converter.g.dart +++ b/app/lib/api/entity_converter.g.dart @@ -6,9 +6,9 @@ part of 'entity_converter.dart'; // NpLogGenerator // ************************************************************************** -extension _$ApiFileConverterNpLog on ApiFileConverter { +extension _$_NpLog on _ { // ignore: unused_element Logger get _log => log; - static final log = Logger("api.entity_converter.ApiFileConverter"); + static final log = Logger("api.entity_converter._"); } diff --git a/app/lib/entity/collection/adapter/nc_album.dart b/app/lib/entity/collection/adapter/nc_album.dart index d3f4cd5c..a2b19961 100644 --- a/app/lib/entity/collection/adapter/nc_album.dart +++ b/app/lib/entity/collection/adapter/nc_album.dart @@ -9,6 +9,7 @@ import 'package:nc_photos/entity/collection/adapter/adapter_mixin.dart'; import 'package:nc_photos/entity/collection/content_provider/nc_album.dart'; import 'package:nc_photos/entity/collection_item.dart'; import 'package:nc_photos/entity/collection_item/basic_item.dart'; +import 'package:nc_photos/entity/collection_item/nc_album_item_adapter.dart'; import 'package:nc_photos/entity/collection_item/new_item.dart'; import 'package:nc_photos/entity/collection_item/util.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; @@ -41,14 +42,20 @@ class CollectionNcAlbumAdapter @override Stream> listItem() { return ListNcAlbumItem(_c)(account, _provider.album) - .asyncMap((items) => FindFileDescriptor(_c)( - account, - items.map((e) => e.fileId).toList(), - onFileNotFound: (fileId) { - _log.severe("[listItem] File not found: $fileId"); - }, - )) - .map((files) => files.map(BasicCollectionFileItem.new).toList()); + .asyncMap((items) async { + final found = await FindFileDescriptor(_c)( + account, + items.map((e) => e.fileId).toList(), + onFileNotFound: (fileId) { + // happens when this is a file shared with you + _log.warning("[listItem] File not found: $fileId"); + }, + ); + return items.map((i) { + final f = found.firstWhereOrNull((e) => e.fdId == i.fileId); + return CollectionFileItemNcAlbumItemAdapter(i, f); + }).toList(); + }); } @override diff --git a/app/lib/entity/collection/content_provider/nc_album.dart b/app/lib/entity/collection/content_provider/nc_album.dart index f2b00286..044e9c00 100644 --- a/app/lib/entity/collection/content_provider/nc_album.dart +++ b/app/lib/entity/collection/content_provider/nc_album.dart @@ -41,13 +41,19 @@ class CollectionNcAlbumProvider List get capabilities => [ CollectionCapability.manualItem, CollectionCapability.rename, + // CollectionCapability.share, ]; @override CollectionItemSort get itemSort => CollectionItemSort.dateDescending; @override - List get shares => []; + List get shares => album.collaborators + .map((c) => CollectionShare( + userId: c.id, + username: c.label, + )) + .toList(); @override String? getCoverUrl( diff --git a/app/lib/entity/collection_item/nc_album_item_adapter.dart b/app/lib/entity/collection_item/nc_album_item_adapter.dart new file mode 100644 index 00000000..a497b1df --- /dev/null +++ b/app/lib/entity/collection_item/nc_album_item_adapter.dart @@ -0,0 +1,20 @@ +import 'package:nc_photos/entity/collection_item.dart'; +import 'package:nc_photos/entity/file_descriptor.dart'; +import 'package:nc_photos/entity/nc_album_item.dart'; +import 'package:to_string/to_string.dart'; + +part 'nc_album_item_adapter.g.dart'; + +@toString +class CollectionFileItemNcAlbumItemAdapter extends CollectionFileItem { + const CollectionFileItemNcAlbumItemAdapter(this.item, [this.localFile]); + + @override + String toString() => _$toString(); + + @override + FileDescriptor get file => localFile ?? item.toFile(); + + final NcAlbumItem item; + final FileDescriptor? localFile; +} diff --git a/app/lib/entity/collection_item/nc_album_item_adapter.g.dart b/app/lib/entity/collection_item/nc_album_item_adapter.g.dart new file mode 100644 index 00000000..383462d7 --- /dev/null +++ b/app/lib/entity/collection_item/nc_album_item_adapter.g.dart @@ -0,0 +1,15 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'nc_album_item_adapter.dart'; + +// ************************************************************************** +// ToStringGenerator +// ************************************************************************** + +extension _$CollectionFileItemNcAlbumItemAdapterToString + on CollectionFileItemNcAlbumItemAdapter { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "CollectionFileItemNcAlbumItemAdapter {item: $item, localFile: ${localFile == null ? null : "${localFile!.fdPath}"}}"; + } +} diff --git a/app/lib/entity/nc_album.dart b/app/lib/entity/nc_album.dart index 9250c967..b54492de 100644 --- a/app/lib/entity/nc_album.dart +++ b/app/lib/entity/nc_album.dart @@ -1,7 +1,9 @@ import 'package:copy_with/copy_with.dart'; import 'package:equatable/equatable.dart'; import 'package:nc_photos/account.dart'; +import 'package:np_common/ci_string.dart'; import 'package:np_common/string_extension.dart'; +import 'package:np_common/type.dart'; import 'package:to_string/to_string.dart'; part 'nc_album.g.dart'; @@ -109,4 +111,31 @@ extension NcAlbumExtension on NcAlbum { int get identityHashCode => path.hashCode; } -class NcAlbumCollaborator {} +@toString +class NcAlbumCollaborator { + const NcAlbumCollaborator({ + required this.id, + required this.label, + required this.type, + }); + + factory NcAlbumCollaborator.fromJson(JsonObj json) => NcAlbumCollaborator( + id: CiString(json["id"]), + label: json["label"], + type: json["type"], + ); + + JsonObj toJson() => { + "id": id.raw, + "label": label, + "type": type, + }; + + @override + String toString() => _$toString(); + + final CiString id; + final String label; + // right now it's unclear what this variable represents + final int type; +} diff --git a/app/lib/entity/nc_album.g.dart b/app/lib/entity/nc_album.g.dart index 727ad4a6..c3f7ca31 100644 --- a/app/lib/entity/nc_album.g.dart +++ b/app/lib/entity/nc_album.g.dart @@ -67,3 +67,10 @@ extension _$NcAlbumToString on NcAlbum { return "NcAlbum {path: $path, lastPhoto: $lastPhoto, nbItems: $nbItems, location: $location, dateStart: $dateStart, dateEnd: $dateEnd, collaborators: [length: ${collaborators.length}]}"; } } + +extension _$NcAlbumCollaboratorToString on NcAlbumCollaborator { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "NcAlbumCollaborator {id: $id, label: $label, type: $type}"; + } +} diff --git a/app/lib/entity/nc_album/data_source.dart b/app/lib/entity/nc_album/data_source.dart index 85a5b4e3..1a1e48a9 100644 --- a/app/lib/entity/nc_album/data_source.dart +++ b/app/lib/entity/nc_album/data_source.dart @@ -4,8 +4,8 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/entity_converter.dart'; import 'package:nc_photos/entity/nc_album.dart'; -import 'package:nc_photos/entity/nc_album/item.dart'; import 'package:nc_photos/entity/nc_album/repo.dart'; +import 'package:nc_photos/entity/nc_album_item.dart'; import 'package:nc_photos/entity/sqlite/database.dart' as sql; import 'package:nc_photos/entity/sqlite/type_converter.dart'; import 'package:nc_photos/exception.dart'; @@ -85,9 +85,21 @@ class NcAlbumRemoteDataSource implements NcAlbumDataSource { _log.info( "[getItems] account: ${account.userId}, album: ${album.strippedPath}"); final response = await ApiUtil.fromAccount(account).files().propfind( - path: album.path, - fileid: 1, - ); + path: album.path, + getlastmodified: 1, + getetag: 1, + getcontenttype: 1, + getcontentlength: 1, + hasPreview: 1, + fileid: 1, + favorite: 1, + customProperties: [ + "nc:file-metadata-size", + "nc:face-detections", + "nc:realpath", + "oc:permissions", + ], + ); if (!response.isGood) { _log.severe("[getItems] Failed requesting server: $response"); throw ApiException( @@ -96,11 +108,10 @@ class NcAlbumRemoteDataSource implements NcAlbumDataSource { ); } - final apiFiles = await api.FileParser().parse(response.body); + final apiFiles = await api.NcAlbumItemParser().parse(response.body); return apiFiles .where((f) => f.fileId != null) - .map(ApiFileConverter.fromApi) - .map((f) => NcAlbumItem(f.fileId!)) + .map(ApiNcAlbumItemConverter.fromApi) .toList(); } } @@ -161,7 +172,19 @@ class NcAlbumSqliteDbDataSource implements NcAlbumCacheDataSource { parentRelativePath: album.strippedPath, ); }); - return dbItems.map((i) => NcAlbumItem(i.fileId)).toList(); + return dbItems + .map((i) { + try { + return SqliteNcAlbumItemConverter.fromSql( + account.userId.toString(), album.strippedPath, i); + } catch (e, stackTrace) { + _log.severe( + "[getItems] Failed while converting DB entry", e, stackTrace); + return null; + } + }) + .whereNotNull() + .toList(); } @override @@ -213,25 +236,32 @@ class NcAlbumSqliteDbDataSource implements NcAlbumCacheDataSource { final existingItems = await db.ncAlbumItemsByParent( parent: dbAlbum!, ); - final idDiff = list_util.diff( - existingItems.map((e) => e.fileId).sorted((a, b) => a.compareTo(b)), - remote.map((e) => e.fileId).sorted((a, b) => a.compareTo(b)), + final diff = list_util.diffWith( + existingItems + .map((e) => SqliteNcAlbumItemConverter.fromSql( + account.userId.raw, album.strippedPath, e)) + .sorted(NcAlbumItemExtension.identityComparator), + remote.sorted(NcAlbumItemExtension.identityComparator), + NcAlbumItemExtension.identityComparator, ); - if (idDiff.onlyInA.isNotEmpty || idDiff.onlyInB.isNotEmpty) { + if (diff.onlyInA.isNotEmpty || diff.onlyInB.isNotEmpty) { await db.batch((batch) async { - for (final id in idDiff.onlyInB) { + for (final item in diff.onlyInB) { // new batch.insert( db.ncAlbumItems, - SqliteNcAlbumItemConverter.toSql(dbAlbum, id), + SqliteNcAlbumItemConverter.toSql(dbAlbum, item), + ); + } + final rmIds = diff.onlyInA.map((e) => e.fileId).toList(); + if (rmIds.isNotEmpty) { + // removed + batch.deleteWhere( + db.ncAlbumItems, + (sql.$NcAlbumItemsTable t) => + t.parent.equals(dbAlbum.rowId) & t.fileId.isIn(rmIds), ); } - // removed - batch.deleteWhere( - db.ncAlbumItems, - (sql.$NcAlbumItemsTable t) => - t.parent.equals(dbAlbum.rowId) & t.fileId.isIn(idDiff.onlyInA), - ); }); } }); diff --git a/app/lib/entity/nc_album/item.dart b/app/lib/entity/nc_album/item.dart deleted file mode 100644 index bb7bd76b..00000000 --- a/app/lib/entity/nc_album/item.dart +++ /dev/null @@ -1,5 +0,0 @@ -class NcAlbumItem { - const NcAlbumItem(this.fileId); - - final int fileId; -} diff --git a/app/lib/entity/nc_album/repo.dart b/app/lib/entity/nc_album/repo.dart index d27a124a..fa95e1d6 100644 --- a/app/lib/entity/nc_album/repo.dart +++ b/app/lib/entity/nc_album/repo.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/entity/nc_album.dart'; -import 'package:nc_photos/entity/nc_album/item.dart'; +import 'package:nc_photos/entity/nc_album_item.dart'; import 'package:np_codegen/np_codegen.dart'; part 'repo.g.dart'; diff --git a/app/lib/entity/nc_album_item.dart b/app/lib/entity/nc_album_item.dart new file mode 100644 index 00000000..1ef40c93 --- /dev/null +++ b/app/lib/entity/nc_album_item.dart @@ -0,0 +1,95 @@ +import 'package:nc_photos/entity/file.dart'; +import 'package:np_common/string_extension.dart'; +import 'package:to_string/to_string.dart'; + +part 'nc_album_item.g.dart'; + +@ToString(ignoreNull: true) +class NcAlbumItem { + const NcAlbumItem({ + required this.path, + required this.fileId, + this.contentLength, + this.contentType, + this.etag, + this.lastModified, + this.hasPreview, + this.isFavorite, + this.fileMetadataWidth, + this.fileMetadataHeight, + }); + + @override + String toString() => _$toString(); + + final String path; + final int fileId; + final int? contentLength; + final String? contentType; + final String? etag; + final DateTime? lastModified; + final bool? hasPreview; + final bool? isFavorite; + final int? fileMetadataWidth; + final int? fileMetadataHeight; +} + +extension NcAlbumItemExtension on NcAlbumItem { + /// Return the path of this file with the DAV part stripped + /// + /// WebDAV file path: remote.php/dav/photos/{userId}/albums/{album}/{strippedPath}. + /// If this path points to the user's root album path, return "." + String get strippedPath { + if (!path.startsWith("remote.php/dav/photos/")) { + return path; + } + var begin = "remote.php/dav/photos/".length; + begin = path.indexOf("/", begin); + if (begin == -1) { + return path; + } + if (path.slice(begin, begin + 7) != "/albums") { + return path; + } + // /albums/ + begin += 8; + begin = path.indexOf("/", begin); + if (begin == -1) { + return path; + } + final stripped = path.slice(begin + 1); + if (stripped.isEmpty) { + return "."; + } else { + return stripped; + } + } + + bool compareIdentity(NcAlbumItem other) => fileId == other.fileId; + + int get identityHashCode => fileId.hashCode; + + static int identityComparator(NcAlbumItem a, NcAlbumItem b) => + a.fileId.compareTo(b.fileId); + + File toFile() { + Metadata? metadata; + if (fileMetadataWidth != null && fileMetadataHeight != null) { + metadata = Metadata( + imageWidth: fileMetadataWidth, + imageHeight: fileMetadataHeight, + ); + } + return File( + path: path, + fileId: fileId, + contentLength: contentLength, + contentType: contentType, + etag: etag, + lastModified: lastModified, + hasPreview: hasPreview, + isFavorite: isFavorite, + metadata: metadata, + ); + } +} diff --git a/app/lib/entity/nc_album_item.g.dart b/app/lib/entity/nc_album_item.g.dart new file mode 100644 index 00000000..1250127c --- /dev/null +++ b/app/lib/entity/nc_album_item.g.dart @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'nc_album_item.dart'; + +// ************************************************************************** +// ToStringGenerator +// ************************************************************************** + +extension _$NcAlbumItemToString on NcAlbumItem { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "NcAlbumItem {path: $path, fileId: $fileId, ${contentLength == null ? "" : "contentLength: $contentLength, "}${contentType == null ? "" : "contentType: $contentType, "}${etag == null ? "" : "etag: $etag, "}${lastModified == null ? "" : "lastModified: $lastModified, "}${hasPreview == null ? "" : "hasPreview: $hasPreview, "}${isFavorite == null ? "" : "isFavorite: $isFavorite, "}${fileMetadataWidth == null ? "" : "fileMetadataWidth: $fileMetadataWidth, "}${fileMetadataHeight == null ? "" : "fileMetadataHeight: $fileMetadataHeight"}}"; + } +} diff --git a/app/lib/entity/sqlite/database.dart b/app/lib/entity/sqlite/database.dart index 4bd8ad6a..7d2259db 100644 --- a/app/lib/entity/sqlite/database.dart +++ b/app/lib/entity/sqlite/database.dart @@ -49,7 +49,7 @@ class SqliteDb extends _$SqliteDb { SqliteDb.connect(DatabaseConnection connection) : super.connect(connection); @override - get schemaVersion => 4; + get schemaVersion => 5; @override get migration => MigrationStrategy( @@ -98,6 +98,10 @@ class SqliteDb extends _$SqliteDb { if (from < 4) { await m.addColumn(albums, albums.fileEtag); } + if (from < 5) { + await m.createTable(ncAlbums); + await m.createTable(ncAlbumItems); + } }); } catch (e, stackTrace) { _log.shout("[onUpgrade] Failed upgrading sqlite db", e, stackTrace); diff --git a/app/lib/entity/sqlite/database.g.dart b/app/lib/entity/sqlite/database.g.dart index a1de07c5..c987f095 100644 --- a/app/lib/entity/sqlite/database.g.dart +++ b/app/lib/entity/sqlite/database.g.dart @@ -4166,6 +4166,7 @@ class NcAlbum extends DataClass implements Insertable { final String? location; final DateTime? dateStart; final DateTime? dateEnd; + final String collaborators; NcAlbum( {required this.rowId, required this.account, @@ -4174,7 +4175,8 @@ class NcAlbum extends DataClass implements Insertable { required this.nbItems, this.location, this.dateStart, - this.dateEnd}); + this.dateEnd, + required this.collaborators}); factory NcAlbum.fromData(Map data, {String? prefix}) { final effectivePrefix = prefix ?? ''; return NcAlbum( @@ -4194,6 +4196,8 @@ class NcAlbum extends DataClass implements Insertable { .mapFromDatabaseResponse(data['${effectivePrefix}date_start'])), dateEnd: $NcAlbumsTable.$converter1.mapToDart(const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}date_end'])), + collaborators: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}collaborators'])!, ); } @override @@ -4217,6 +4221,7 @@ class NcAlbum extends DataClass implements Insertable { final converter = $NcAlbumsTable.$converter1; map['date_end'] = Variable(converter.mapToSql(dateEnd)); } + map['collaborators'] = Variable(collaborators); return map; } @@ -4238,6 +4243,7 @@ class NcAlbum extends DataClass implements Insertable { dateEnd: dateEnd == null && nullToAbsent ? const Value.absent() : Value(dateEnd), + collaborators: Value(collaborators), ); } @@ -4253,6 +4259,7 @@ class NcAlbum extends DataClass implements Insertable { location: serializer.fromJson(json['location']), dateStart: serializer.fromJson(json['dateStart']), dateEnd: serializer.fromJson(json['dateEnd']), + collaborators: serializer.fromJson(json['collaborators']), ); } @override @@ -4267,6 +4274,7 @@ class NcAlbum extends DataClass implements Insertable { 'location': serializer.toJson(location), 'dateStart': serializer.toJson(dateStart), 'dateEnd': serializer.toJson(dateEnd), + 'collaborators': serializer.toJson(collaborators), }; } @@ -4278,7 +4286,8 @@ class NcAlbum extends DataClass implements Insertable { int? nbItems, Value location = const Value.absent(), Value dateStart = const Value.absent(), - Value dateEnd = const Value.absent()}) => + Value dateEnd = const Value.absent(), + String? collaborators}) => NcAlbum( rowId: rowId ?? this.rowId, account: account ?? this.account, @@ -4288,6 +4297,7 @@ class NcAlbum extends DataClass implements Insertable { location: location.present ? location.value : this.location, dateStart: dateStart.present ? dateStart.value : this.dateStart, dateEnd: dateEnd.present ? dateEnd.value : this.dateEnd, + collaborators: collaborators ?? this.collaborators, ); @override String toString() { @@ -4299,14 +4309,15 @@ class NcAlbum extends DataClass implements Insertable { ..write('nbItems: $nbItems, ') ..write('location: $location, ') ..write('dateStart: $dateStart, ') - ..write('dateEnd: $dateEnd') + ..write('dateEnd: $dateEnd, ') + ..write('collaborators: $collaborators') ..write(')')) .toString(); } @override int get hashCode => Object.hash(rowId, account, relativePath, lastPhoto, - nbItems, location, dateStart, dateEnd); + nbItems, location, dateStart, dateEnd, collaborators); @override bool operator ==(Object other) => identical(this, other) || @@ -4318,7 +4329,8 @@ class NcAlbum extends DataClass implements Insertable { other.nbItems == this.nbItems && other.location == this.location && other.dateStart == this.dateStart && - other.dateEnd == this.dateEnd); + other.dateEnd == this.dateEnd && + other.collaborators == this.collaborators); } class NcAlbumsCompanion extends UpdateCompanion { @@ -4330,6 +4342,7 @@ class NcAlbumsCompanion extends UpdateCompanion { final Value location; final Value dateStart; final Value dateEnd; + final Value collaborators; const NcAlbumsCompanion({ this.rowId = const Value.absent(), this.account = const Value.absent(), @@ -4339,6 +4352,7 @@ class NcAlbumsCompanion extends UpdateCompanion { this.location = const Value.absent(), this.dateStart = const Value.absent(), this.dateEnd = const Value.absent(), + this.collaborators = const Value.absent(), }); NcAlbumsCompanion.insert({ this.rowId = const Value.absent(), @@ -4349,9 +4363,11 @@ class NcAlbumsCompanion extends UpdateCompanion { this.location = const Value.absent(), this.dateStart = const Value.absent(), this.dateEnd = const Value.absent(), + required String collaborators, }) : account = Value(account), relativePath = Value(relativePath), - nbItems = Value(nbItems); + nbItems = Value(nbItems), + collaborators = Value(collaborators); static Insertable custom({ Expression? rowId, Expression? account, @@ -4361,6 +4377,7 @@ class NcAlbumsCompanion extends UpdateCompanion { Expression? location, Expression? dateStart, Expression? dateEnd, + Expression? collaborators, }) { return RawValuesInsertable({ if (rowId != null) 'row_id': rowId, @@ -4371,6 +4388,7 @@ class NcAlbumsCompanion extends UpdateCompanion { if (location != null) 'location': location, if (dateStart != null) 'date_start': dateStart, if (dateEnd != null) 'date_end': dateEnd, + if (collaborators != null) 'collaborators': collaborators, }); } @@ -4382,7 +4400,8 @@ class NcAlbumsCompanion extends UpdateCompanion { Value? nbItems, Value? location, Value? dateStart, - Value? dateEnd}) { + Value? dateEnd, + Value? collaborators}) { return NcAlbumsCompanion( rowId: rowId ?? this.rowId, account: account ?? this.account, @@ -4392,6 +4411,7 @@ class NcAlbumsCompanion extends UpdateCompanion { location: location ?? this.location, dateStart: dateStart ?? this.dateStart, dateEnd: dateEnd ?? this.dateEnd, + collaborators: collaborators ?? this.collaborators, ); } @@ -4425,6 +4445,9 @@ class NcAlbumsCompanion extends UpdateCompanion { final converter = $NcAlbumsTable.$converter1; map['date_end'] = Variable(converter.mapToSql(dateEnd.value)); } + if (collaborators.present) { + map['collaborators'] = Variable(collaborators.value); + } return map; } @@ -4438,7 +4461,8 @@ class NcAlbumsCompanion extends UpdateCompanion { ..write('nbItems: $nbItems, ') ..write('location: $location, ') ..write('dateStart: $dateStart, ') - ..write('dateEnd: $dateEnd') + ..write('dateEnd: $dateEnd, ') + ..write('collaborators: $collaborators') ..write(')')) .toString(); } @@ -4496,6 +4520,12 @@ class $NcAlbumsTable extends NcAlbums with TableInfo<$NcAlbumsTable, NcAlbum> { GeneratedColumn('date_end', aliasedName, true, type: const IntType(), requiredDuringInsert: false) .withConverter($NcAlbumsTable.$converter1); + final VerificationMeta _collaboratorsMeta = + const VerificationMeta('collaborators'); + @override + late final GeneratedColumn collaborators = GeneratedColumn( + 'collaborators', aliasedName, false, + type: const StringType(), requiredDuringInsert: true); @override List get $columns => [ rowId, @@ -4505,7 +4535,8 @@ class $NcAlbumsTable extends NcAlbums with TableInfo<$NcAlbumsTable, NcAlbum> { nbItems, location, dateStart, - dateEnd + dateEnd, + collaborators ]; @override String get aliasedName => _alias ?? 'nc_albums'; @@ -4550,6 +4581,14 @@ class $NcAlbumsTable extends NcAlbums with TableInfo<$NcAlbumsTable, NcAlbum> { } context.handle(_dateStartMeta, const VerificationResult.success()); context.handle(_dateEndMeta, const VerificationResult.success()); + if (data.containsKey('collaborators')) { + context.handle( + _collaboratorsMeta, + collaborators.isAcceptableOrUnknown( + data['collaborators']!, _collaboratorsMeta)); + } else if (isInserting) { + context.missing(_collaboratorsMeta); + } return context; } @@ -4579,9 +4618,29 @@ class $NcAlbumsTable extends NcAlbums with TableInfo<$NcAlbumsTable, NcAlbum> { class NcAlbumItem extends DataClass implements Insertable { final int rowId; final int parent; + final String relativePath; final int fileId; + final int? contentLength; + final String? contentType; + final String? etag; + final DateTime? lastModified; + final bool? hasPreview; + final bool? isFavorite; + final int? fileMetadataWidth; + final int? fileMetadataHeight; NcAlbumItem( - {required this.rowId, required this.parent, required this.fileId}); + {required this.rowId, + required this.parent, + required this.relativePath, + required this.fileId, + this.contentLength, + this.contentType, + this.etag, + this.lastModified, + this.hasPreview, + this.isFavorite, + this.fileMetadataWidth, + this.fileMetadataHeight}); factory NcAlbumItem.fromData(Map data, {String? prefix}) { final effectivePrefix = prefix ?? ''; return NcAlbumItem( @@ -4589,8 +4648,27 @@ class NcAlbumItem extends DataClass implements Insertable { .mapFromDatabaseResponse(data['${effectivePrefix}row_id'])!, parent: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}parent'])!, + relativePath: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}relative_path'])!, fileId: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}file_id'])!, + contentLength: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}content_length']), + contentType: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}content_type']), + etag: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}etag']), + lastModified: $NcAlbumItemsTable.$converter0.mapToDart( + const DateTimeType().mapFromDatabaseResponse( + data['${effectivePrefix}last_modified'])), + hasPreview: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}has_preview']), + isFavorite: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}is_favorite']), + fileMetadataWidth: const IntType().mapFromDatabaseResponse( + data['${effectivePrefix}file_metadata_width']), + fileMetadataHeight: const IntType().mapFromDatabaseResponse( + data['${effectivePrefix}file_metadata_height']), ); } @override @@ -4598,7 +4676,34 @@ class NcAlbumItem extends DataClass implements Insertable { final map = {}; map['row_id'] = Variable(rowId); map['parent'] = Variable(parent); + map['relative_path'] = Variable(relativePath); map['file_id'] = Variable(fileId); + if (!nullToAbsent || contentLength != null) { + map['content_length'] = Variable(contentLength); + } + if (!nullToAbsent || contentType != null) { + map['content_type'] = Variable(contentType); + } + if (!nullToAbsent || etag != null) { + map['etag'] = Variable(etag); + } + if (!nullToAbsent || lastModified != null) { + final converter = $NcAlbumItemsTable.$converter0; + map['last_modified'] = + Variable(converter.mapToSql(lastModified)); + } + if (!nullToAbsent || hasPreview != null) { + map['has_preview'] = Variable(hasPreview); + } + if (!nullToAbsent || isFavorite != null) { + map['is_favorite'] = Variable(isFavorite); + } + if (!nullToAbsent || fileMetadataWidth != null) { + map['file_metadata_width'] = Variable(fileMetadataWidth); + } + if (!nullToAbsent || fileMetadataHeight != null) { + map['file_metadata_height'] = Variable(fileMetadataHeight); + } return map; } @@ -4606,7 +4711,30 @@ class NcAlbumItem extends DataClass implements Insertable { return NcAlbumItemsCompanion( rowId: Value(rowId), parent: Value(parent), + relativePath: Value(relativePath), fileId: Value(fileId), + contentLength: contentLength == null && nullToAbsent + ? const Value.absent() + : Value(contentLength), + contentType: contentType == null && nullToAbsent + ? const Value.absent() + : Value(contentType), + etag: etag == null && nullToAbsent ? const Value.absent() : Value(etag), + lastModified: lastModified == null && nullToAbsent + ? const Value.absent() + : Value(lastModified), + hasPreview: hasPreview == null && nullToAbsent + ? const Value.absent() + : Value(hasPreview), + isFavorite: isFavorite == null && nullToAbsent + ? const Value.absent() + : Value(isFavorite), + fileMetadataWidth: fileMetadataWidth == null && nullToAbsent + ? const Value.absent() + : Value(fileMetadataWidth), + fileMetadataHeight: fileMetadataHeight == null && nullToAbsent + ? const Value.absent() + : Value(fileMetadataHeight), ); } @@ -4616,7 +4744,16 @@ class NcAlbumItem extends DataClass implements Insertable { return NcAlbumItem( rowId: serializer.fromJson(json['rowId']), parent: serializer.fromJson(json['parent']), + relativePath: serializer.fromJson(json['relativePath']), fileId: serializer.fromJson(json['fileId']), + contentLength: serializer.fromJson(json['contentLength']), + contentType: serializer.fromJson(json['contentType']), + etag: serializer.fromJson(json['etag']), + lastModified: serializer.fromJson(json['lastModified']), + hasPreview: serializer.fromJson(json['hasPreview']), + isFavorite: serializer.fromJson(json['isFavorite']), + fileMetadataWidth: serializer.fromJson(json['fileMetadataWidth']), + fileMetadataHeight: serializer.fromJson(json['fileMetadataHeight']), ); } @override @@ -4625,69 +4762,203 @@ class NcAlbumItem extends DataClass implements Insertable { return { 'rowId': serializer.toJson(rowId), 'parent': serializer.toJson(parent), + 'relativePath': serializer.toJson(relativePath), 'fileId': serializer.toJson(fileId), + 'contentLength': serializer.toJson(contentLength), + 'contentType': serializer.toJson(contentType), + 'etag': serializer.toJson(etag), + 'lastModified': serializer.toJson(lastModified), + 'hasPreview': serializer.toJson(hasPreview), + 'isFavorite': serializer.toJson(isFavorite), + 'fileMetadataWidth': serializer.toJson(fileMetadataWidth), + 'fileMetadataHeight': serializer.toJson(fileMetadataHeight), }; } - NcAlbumItem copyWith({int? rowId, int? parent, int? fileId}) => NcAlbumItem( + NcAlbumItem copyWith( + {int? rowId, + int? parent, + String? relativePath, + int? fileId, + Value contentLength = const Value.absent(), + Value contentType = const Value.absent(), + Value etag = const Value.absent(), + Value lastModified = const Value.absent(), + Value hasPreview = const Value.absent(), + Value isFavorite = const Value.absent(), + Value fileMetadataWidth = const Value.absent(), + Value fileMetadataHeight = const Value.absent()}) => + NcAlbumItem( rowId: rowId ?? this.rowId, parent: parent ?? this.parent, + relativePath: relativePath ?? this.relativePath, fileId: fileId ?? this.fileId, + contentLength: + contentLength.present ? contentLength.value : this.contentLength, + contentType: contentType.present ? contentType.value : this.contentType, + etag: etag.present ? etag.value : this.etag, + lastModified: + lastModified.present ? lastModified.value : this.lastModified, + hasPreview: hasPreview.present ? hasPreview.value : this.hasPreview, + isFavorite: isFavorite.present ? isFavorite.value : this.isFavorite, + fileMetadataWidth: fileMetadataWidth.present + ? fileMetadataWidth.value + : this.fileMetadataWidth, + fileMetadataHeight: fileMetadataHeight.present + ? fileMetadataHeight.value + : this.fileMetadataHeight, ); @override String toString() { return (StringBuffer('NcAlbumItem(') ..write('rowId: $rowId, ') ..write('parent: $parent, ') - ..write('fileId: $fileId') + ..write('relativePath: $relativePath, ') + ..write('fileId: $fileId, ') + ..write('contentLength: $contentLength, ') + ..write('contentType: $contentType, ') + ..write('etag: $etag, ') + ..write('lastModified: $lastModified, ') + ..write('hasPreview: $hasPreview, ') + ..write('isFavorite: $isFavorite, ') + ..write('fileMetadataWidth: $fileMetadataWidth, ') + ..write('fileMetadataHeight: $fileMetadataHeight') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(rowId, parent, fileId); + int get hashCode => Object.hash( + rowId, + parent, + relativePath, + fileId, + contentLength, + contentType, + etag, + lastModified, + hasPreview, + isFavorite, + fileMetadataWidth, + fileMetadataHeight); @override bool operator ==(Object other) => identical(this, other) || (other is NcAlbumItem && other.rowId == this.rowId && other.parent == this.parent && - other.fileId == this.fileId); + other.relativePath == this.relativePath && + other.fileId == this.fileId && + other.contentLength == this.contentLength && + other.contentType == this.contentType && + other.etag == this.etag && + other.lastModified == this.lastModified && + other.hasPreview == this.hasPreview && + other.isFavorite == this.isFavorite && + other.fileMetadataWidth == this.fileMetadataWidth && + other.fileMetadataHeight == this.fileMetadataHeight); } class NcAlbumItemsCompanion extends UpdateCompanion { final Value rowId; final Value parent; + final Value relativePath; final Value fileId; + final Value contentLength; + final Value contentType; + final Value etag; + final Value lastModified; + final Value hasPreview; + final Value isFavorite; + final Value fileMetadataWidth; + final Value fileMetadataHeight; const NcAlbumItemsCompanion({ this.rowId = const Value.absent(), this.parent = const Value.absent(), + this.relativePath = const Value.absent(), this.fileId = const Value.absent(), + this.contentLength = const Value.absent(), + this.contentType = const Value.absent(), + this.etag = const Value.absent(), + this.lastModified = const Value.absent(), + this.hasPreview = const Value.absent(), + this.isFavorite = const Value.absent(), + this.fileMetadataWidth = const Value.absent(), + this.fileMetadataHeight = const Value.absent(), }); NcAlbumItemsCompanion.insert({ this.rowId = const Value.absent(), required int parent, + required String relativePath, required int fileId, + this.contentLength = const Value.absent(), + this.contentType = const Value.absent(), + this.etag = const Value.absent(), + this.lastModified = const Value.absent(), + this.hasPreview = const Value.absent(), + this.isFavorite = const Value.absent(), + this.fileMetadataWidth = const Value.absent(), + this.fileMetadataHeight = const Value.absent(), }) : parent = Value(parent), + relativePath = Value(relativePath), fileId = Value(fileId); static Insertable custom({ Expression? rowId, Expression? parent, + Expression? relativePath, Expression? fileId, + Expression? contentLength, + Expression? contentType, + Expression? etag, + Expression? lastModified, + Expression? hasPreview, + Expression? isFavorite, + Expression? fileMetadataWidth, + Expression? fileMetadataHeight, }) { return RawValuesInsertable({ if (rowId != null) 'row_id': rowId, if (parent != null) 'parent': parent, + if (relativePath != null) 'relative_path': relativePath, if (fileId != null) 'file_id': fileId, + if (contentLength != null) 'content_length': contentLength, + if (contentType != null) 'content_type': contentType, + if (etag != null) 'etag': etag, + if (lastModified != null) 'last_modified': lastModified, + if (hasPreview != null) 'has_preview': hasPreview, + if (isFavorite != null) 'is_favorite': isFavorite, + if (fileMetadataWidth != null) 'file_metadata_width': fileMetadataWidth, + if (fileMetadataHeight != null) + 'file_metadata_height': fileMetadataHeight, }); } NcAlbumItemsCompanion copyWith( - {Value? rowId, Value? parent, Value? fileId}) { + {Value? rowId, + Value? parent, + Value? relativePath, + Value? fileId, + Value? contentLength, + Value? contentType, + Value? etag, + Value? lastModified, + Value? hasPreview, + Value? isFavorite, + Value? fileMetadataWidth, + Value? fileMetadataHeight}) { return NcAlbumItemsCompanion( rowId: rowId ?? this.rowId, parent: parent ?? this.parent, + relativePath: relativePath ?? this.relativePath, fileId: fileId ?? this.fileId, + contentLength: contentLength ?? this.contentLength, + contentType: contentType ?? this.contentType, + etag: etag ?? this.etag, + lastModified: lastModified ?? this.lastModified, + hasPreview: hasPreview ?? this.hasPreview, + isFavorite: isFavorite ?? this.isFavorite, + fileMetadataWidth: fileMetadataWidth ?? this.fileMetadataWidth, + fileMetadataHeight: fileMetadataHeight ?? this.fileMetadataHeight, ); } @@ -4700,9 +4971,38 @@ class NcAlbumItemsCompanion extends UpdateCompanion { if (parent.present) { map['parent'] = Variable(parent.value); } + if (relativePath.present) { + map['relative_path'] = Variable(relativePath.value); + } if (fileId.present) { map['file_id'] = Variable(fileId.value); } + if (contentLength.present) { + map['content_length'] = Variable(contentLength.value); + } + if (contentType.present) { + map['content_type'] = Variable(contentType.value); + } + if (etag.present) { + map['etag'] = Variable(etag.value); + } + if (lastModified.present) { + final converter = $NcAlbumItemsTable.$converter0; + map['last_modified'] = + Variable(converter.mapToSql(lastModified.value)); + } + if (hasPreview.present) { + map['has_preview'] = Variable(hasPreview.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (fileMetadataWidth.present) { + map['file_metadata_width'] = Variable(fileMetadataWidth.value); + } + if (fileMetadataHeight.present) { + map['file_metadata_height'] = Variable(fileMetadataHeight.value); + } return map; } @@ -4711,7 +5011,16 @@ class NcAlbumItemsCompanion extends UpdateCompanion { return (StringBuffer('NcAlbumItemsCompanion(') ..write('rowId: $rowId, ') ..write('parent: $parent, ') - ..write('fileId: $fileId') + ..write('relativePath: $relativePath, ') + ..write('fileId: $fileId, ') + ..write('contentLength: $contentLength, ') + ..write('contentType: $contentType, ') + ..write('etag: $etag, ') + ..write('lastModified: $lastModified, ') + ..write('hasPreview: $hasPreview, ') + ..write('isFavorite: $isFavorite, ') + ..write('fileMetadataWidth: $fileMetadataWidth, ') + ..write('fileMetadataHeight: $fileMetadataHeight') ..write(')')) .toString(); } @@ -4737,13 +5046,83 @@ class $NcAlbumItemsTable extends NcAlbumItems type: const IntType(), requiredDuringInsert: true, defaultConstraints: 'REFERENCES nc_albums (row_id) ON DELETE CASCADE'); + final VerificationMeta _relativePathMeta = + const VerificationMeta('relativePath'); + @override + late final GeneratedColumn relativePath = GeneratedColumn( + 'relative_path', aliasedName, false, + type: const StringType(), requiredDuringInsert: true); final VerificationMeta _fileIdMeta = const VerificationMeta('fileId'); @override late final GeneratedColumn fileId = GeneratedColumn( 'file_id', aliasedName, false, type: const IntType(), requiredDuringInsert: true); + final VerificationMeta _contentLengthMeta = + const VerificationMeta('contentLength'); @override - List get $columns => [rowId, parent, fileId]; + late final GeneratedColumn contentLength = GeneratedColumn( + 'content_length', aliasedName, true, + type: const IntType(), requiredDuringInsert: false); + final VerificationMeta _contentTypeMeta = + const VerificationMeta('contentType'); + @override + late final GeneratedColumn contentType = GeneratedColumn( + 'content_type', aliasedName, true, + type: const StringType(), requiredDuringInsert: false); + final VerificationMeta _etagMeta = const VerificationMeta('etag'); + @override + late final GeneratedColumn etag = GeneratedColumn( + 'etag', aliasedName, true, + type: const StringType(), requiredDuringInsert: false); + final VerificationMeta _lastModifiedMeta = + const VerificationMeta('lastModified'); + @override + late final GeneratedColumnWithTypeConverter + lastModified = GeneratedColumn( + 'last_modified', aliasedName, true, + type: const IntType(), requiredDuringInsert: false) + .withConverter($NcAlbumItemsTable.$converter0); + final VerificationMeta _hasPreviewMeta = const VerificationMeta('hasPreview'); + @override + late final GeneratedColumn hasPreview = GeneratedColumn( + 'has_preview', aliasedName, true, + type: const BoolType(), + requiredDuringInsert: false, + defaultConstraints: 'CHECK (has_preview IN (0, 1))'); + final VerificationMeta _isFavoriteMeta = const VerificationMeta('isFavorite'); + @override + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', aliasedName, true, + type: const BoolType(), + requiredDuringInsert: false, + defaultConstraints: 'CHECK (is_favorite IN (0, 1))'); + final VerificationMeta _fileMetadataWidthMeta = + const VerificationMeta('fileMetadataWidth'); + @override + late final GeneratedColumn fileMetadataWidth = GeneratedColumn( + 'file_metadata_width', aliasedName, true, + type: const IntType(), requiredDuringInsert: false); + final VerificationMeta _fileMetadataHeightMeta = + const VerificationMeta('fileMetadataHeight'); + @override + late final GeneratedColumn fileMetadataHeight = GeneratedColumn( + 'file_metadata_height', aliasedName, true, + type: const IntType(), requiredDuringInsert: false); + @override + List get $columns => [ + rowId, + parent, + relativePath, + fileId, + contentLength, + contentType, + etag, + lastModified, + hasPreview, + isFavorite, + fileMetadataWidth, + fileMetadataHeight + ]; @override String get aliasedName => _alias ?? 'nc_album_items'; @override @@ -4763,12 +5142,61 @@ class $NcAlbumItemsTable extends NcAlbumItems } else if (isInserting) { context.missing(_parentMeta); } + if (data.containsKey('relative_path')) { + context.handle( + _relativePathMeta, + relativePath.isAcceptableOrUnknown( + data['relative_path']!, _relativePathMeta)); + } else if (isInserting) { + context.missing(_relativePathMeta); + } if (data.containsKey('file_id')) { context.handle(_fileIdMeta, fileId.isAcceptableOrUnknown(data['file_id']!, _fileIdMeta)); } else if (isInserting) { context.missing(_fileIdMeta); } + if (data.containsKey('content_length')) { + context.handle( + _contentLengthMeta, + contentLength.isAcceptableOrUnknown( + data['content_length']!, _contentLengthMeta)); + } + if (data.containsKey('content_type')) { + context.handle( + _contentTypeMeta, + contentType.isAcceptableOrUnknown( + data['content_type']!, _contentTypeMeta)); + } + if (data.containsKey('etag')) { + context.handle( + _etagMeta, etag.isAcceptableOrUnknown(data['etag']!, _etagMeta)); + } + context.handle(_lastModifiedMeta, const VerificationResult.success()); + if (data.containsKey('has_preview')) { + context.handle( + _hasPreviewMeta, + hasPreview.isAcceptableOrUnknown( + data['has_preview']!, _hasPreviewMeta)); + } + if (data.containsKey('is_favorite')) { + context.handle( + _isFavoriteMeta, + isFavorite.isAcceptableOrUnknown( + data['is_favorite']!, _isFavoriteMeta)); + } + if (data.containsKey('file_metadata_width')) { + context.handle( + _fileMetadataWidthMeta, + fileMetadataWidth.isAcceptableOrUnknown( + data['file_metadata_width']!, _fileMetadataWidthMeta)); + } + if (data.containsKey('file_metadata_height')) { + context.handle( + _fileMetadataHeightMeta, + fileMetadataHeight.isAcceptableOrUnknown( + data['file_metadata_height']!, _fileMetadataHeightMeta)); + } return context; } @@ -4788,6 +5216,9 @@ class $NcAlbumItemsTable extends NcAlbumItems $NcAlbumItemsTable createAlias(String alias) { return $NcAlbumItemsTable(attachedDatabase, alias); } + + static TypeConverter $converter0 = + const SqliteDateTimeConverter(); } abstract class _$SqliteDb extends GeneratedDatabase { diff --git a/app/lib/entity/sqlite/table.dart b/app/lib/entity/sqlite/table.dart index 965fb018..71ee6dcd 100644 --- a/app/lib/entity/sqlite/table.dart +++ b/app/lib/entity/sqlite/table.dart @@ -137,6 +137,7 @@ class NcAlbums extends Table { dateTime().map(const SqliteDateTimeConverter()).nullable()(); DateTimeColumn get dateEnd => dateTime().map(const SqliteDateTimeConverter()).nullable()(); + TextColumn get collaborators => text()(); @override List>? get uniqueKeys => [ @@ -148,7 +149,17 @@ class NcAlbumItems extends Table { IntColumn get rowId => integer().autoIncrement()(); IntColumn get parent => integer().references(NcAlbums, #rowId, onDelete: KeyAction.cascade)(); + TextColumn get relativePath => text()(); IntColumn get fileId => integer()(); + IntColumn get contentLength => integer().nullable()(); + TextColumn get contentType => text().nullable()(); + TextColumn get etag => text().nullable()(); + DateTimeColumn get lastModified => + dateTime().map(const SqliteDateTimeConverter()).nullable()(); + BoolColumn get hasPreview => boolean().nullable()(); + BoolColumn get isFavorite => boolean().nullable()(); + IntColumn get fileMetadataWidth => integer().nullable()(); + IntColumn get fileMetadataHeight => integer().nullable()(); @override List>? get uniqueKeys => [ diff --git a/app/lib/entity/sqlite/type_converter.dart b/app/lib/entity/sqlite/type_converter.dart index c772238d..438f2ec1 100644 --- a/app/lib/entity/sqlite/type_converter.dart +++ b/app/lib/entity/sqlite/type_converter.dart @@ -9,6 +9,7 @@ import 'package:nc_photos/entity/exif.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/nc_album.dart'; +import 'package:nc_photos/entity/nc_album_item.dart'; import 'package:nc_photos/entity/person.dart'; import 'package:nc_photos/entity/sqlite/database.dart' as sql; import 'package:nc_photos/entity/tag.dart'; @@ -254,15 +255,21 @@ class SqlitePersonConverter { } class SqliteNcAlbumConverter { - static NcAlbum fromSql(String userId, sql.NcAlbum ncAlbum) => NcAlbum( - path: "remote.php/dav/photos/$userId/albums/${ncAlbum.relativePath}", - lastPhoto: ncAlbum.lastPhoto, - nbItems: ncAlbum.nbItems, - location: ncAlbum.location, - dateStart: ncAlbum.dateStart, - dateEnd: ncAlbum.dateEnd, - collaborators: [], - ); + static NcAlbum fromSql(String userId, sql.NcAlbum ncAlbum) { + final json = ncAlbum.collaborators + .run((obj) => (jsonDecode(obj) as List).cast()); + return NcAlbum( + path: "remote.php/dav/photos/$userId/albums/${ncAlbum.relativePath}", + lastPhoto: ncAlbum.lastPhoto, + nbItems: ncAlbum.nbItems, + location: ncAlbum.location, + dateStart: ncAlbum.dateStart, + dateEnd: ncAlbum.dateEnd, + collaborators: json + .map((e) => NcAlbumCollaborator.fromJson(e.cast())) + .toList(), + ); + } static sql.NcAlbumsCompanion toSql(sql.Account? dbAccount, NcAlbum ncAlbum) => sql.NcAlbumsCompanion( @@ -274,19 +281,44 @@ class SqliteNcAlbumConverter { location: Value(ncAlbum.location), dateStart: Value(ncAlbum.dateStart), dateEnd: Value(ncAlbum.dateEnd), + collaborators: Value( + jsonEncode(ncAlbum.collaborators.map((c) => c.toJson()).toList())), ); } class SqliteNcAlbumItemConverter { - static int fromSql(sql.NcAlbumItem item) => item.fileId; + static NcAlbumItem fromSql( + String userId, String albumRelativePath, sql.NcAlbumItem item) => + NcAlbumItem( + path: + "remote.php/dav/photos/$userId/albums/$albumRelativePath/${item.relativePath}", + fileId: item.fileId, + contentLength: item.contentLength, + contentType: item.contentType, + etag: item.etag, + lastModified: item.lastModified, + hasPreview: item.hasPreview, + isFavorite: item.isFavorite, + fileMetadataWidth: item.fileMetadataWidth, + fileMetadataHeight: item.fileMetadataHeight, + ); static sql.NcAlbumItemsCompanion toSql( sql.NcAlbum parent, - int fileId, + NcAlbumItem item, ) => sql.NcAlbumItemsCompanion( parent: Value(parent.rowId), - fileId: Value(fileId), + relativePath: Value(item.strippedPath), + fileId: Value(item.fileId), + contentLength: Value(item.contentLength), + contentType: Value(item.contentType), + etag: Value(item.etag), + lastModified: Value(item.lastModified), + hasPreview: Value(item.hasPreview), + isFavorite: Value(item.isFavorite), + fileMetadataWidth: Value(item.fileMetadataWidth), + fileMetadataHeight: Value(item.fileMetadataHeight), ); } diff --git a/app/lib/use_case/nc_album/list_nc_album_item.dart b/app/lib/use_case/nc_album/list_nc_album_item.dart index 43f6de21..df494ca7 100644 --- a/app/lib/use_case/nc_album/list_nc_album_item.dart +++ b/app/lib/use_case/nc_album/list_nc_album_item.dart @@ -1,7 +1,7 @@ import 'package:nc_photos/account.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/nc_album.dart'; -import 'package:nc_photos/entity/nc_album/item.dart'; +import 'package:nc_photos/entity/nc_album_item.dart'; class ListNcAlbumItem { ListNcAlbumItem(this._c) : assert(require(_c)); diff --git a/app/lib/widget/collection_browser.dart b/app/lib/widget/collection_browser.dart index 050f0a10..fc12b990 100644 --- a/app/lib/widget/collection_browser.dart +++ b/app/lib/widget/collection_browser.dart @@ -25,6 +25,7 @@ import 'package:nc_photos/download_handler.dart'; import 'package:nc_photos/entity/collection.dart'; import 'package:nc_photos/entity/collection/adapter.dart'; import 'package:nc_photos/entity/collection_item.dart'; +import 'package:nc_photos/entity/collection_item/nc_album_item_adapter.dart'; import 'package:nc_photos/entity/collection_item/new_item.dart'; import 'package:nc_photos/entity/collection_item/sorter.dart'; import 'package:nc_photos/entity/collection_item/util.dart'; diff --git a/app/lib/widget/collection_browser/type.dart b/app/lib/widget/collection_browser/type.dart index 0706dd2d..bc166cea 100644 --- a/app/lib/widget/collection_browser/type.dart +++ b/app/lib/widget/collection_browser/type.dart @@ -47,7 +47,8 @@ class _PhotoItem extends _FileItem { required super.original, required super.file, required this.account, - }) : _previewUrl = NetworkRectThumbnail.imageUrlForFile(account, file); + }) : _previewUrl = _getCollectionFilePreviewUrl( + account, original as CollectionFileItem); @override StaggeredTile get staggeredTile => const StaggeredTile.count(1, 1); @@ -75,7 +76,8 @@ class _VideoItem extends _FileItem { required super.original, required super.file, required this.account, - }) : _previewUrl = NetworkRectThumbnail.imageUrlForFile(account, file); + }) : _previewUrl = _getCollectionFilePreviewUrl( + account, original as CollectionFileItem); @override StaggeredTile get staggeredTile => const StaggeredTile.count(1, 1); @@ -174,3 +176,13 @@ class _DateItem extends _Item { final DateTime date; } + +String _getCollectionFilePreviewUrl(Account account, CollectionFileItem item) { + if (item is CollectionFileItemNcAlbumItemAdapter) { + return item.localFile == null + ? NetworkRectThumbnail.imageUrlForNcAlbumFile(account, item.item) + : NetworkRectThumbnail.imageUrlForFile(account, item.file); + } else { + return NetworkRectThumbnail.imageUrlForFile(account, item.file); + } +} diff --git a/app/lib/widget/network_thumbnail.dart b/app/lib/widget/network_thumbnail.dart index 50e5a413..40ee9113 100644 --- a/app/lib/widget/network_thumbnail.dart +++ b/app/lib/widget/network_thumbnail.dart @@ -5,6 +5,7 @@ import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/cache_manager_util.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; +import 'package:nc_photos/entity/nc_album_item.dart'; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/np_api_util.dart'; @@ -36,6 +37,14 @@ class NetworkRectThumbnail extends StatelessWidget { isKeepAspectRatio: true, ); + static String imageUrlForNcAlbumFile(Account account, NcAlbumItem item) => + api_util.getNcAlbumFilePreviewUrl( + account, + item, + width: k.photoThumbSize, + height: k.photoThumbSize, + ); + @override Widget build(BuildContext context) { final child = FittedBox( diff --git a/np_api/lib/np_api.dart b/np_api/lib/np_api.dart index e8d9a56e..5354a393 100644 --- a/np_api/lib/np_api.dart +++ b/np_api/lib/np_api.dart @@ -3,6 +3,7 @@ export 'src/entity/entity.dart'; export 'src/entity/face_parser.dart'; export 'src/entity/favorite_parser.dart'; export 'src/entity/file_parser.dart'; +export 'src/entity/nc_album_item_parser.dart'; export 'src/entity/nc_album_parser.dart'; export 'src/entity/person_parser.dart'; export 'src/entity/share_parser.dart'; diff --git a/np_api/lib/src/entity/entity.dart b/np_api/lib/src/entity/entity.dart index 1f4071d5..90a484f2 100644 --- a/np_api/lib/src/entity/entity.dart +++ b/np_api/lib/src/entity/entity.dart @@ -111,6 +111,55 @@ class NcAlbum with EquatableMixin { required this.nbItems, required this.location, required this.dateRange, + required this.collaborators, + }); + + @override + String toString() => _$toString(); + + @override + List get props => + [href, lastPhoto, nbItems, location, dateRange, collaborators]; + + final String href; + final int? lastPhoto; + final int? nbItems; + final String? location; + final JsonObj? dateRange; + final List collaborators; +} + +@toString +class NcAlbumCollaborator with EquatableMixin { + const NcAlbumCollaborator({ + required this.id, + required this.label, + required this.type, + }); + + @override + String toString() => _$toString(); + + @override + List get props => [id, label, type]; + + final String id; + final String label; + final int type; +} + +@ToString(ignoreNull: true) +class NcAlbumItem with EquatableMixin { + const NcAlbumItem({ + required this.href, + this.fileId, + this.contentLength, + this.contentType, + this.etag, + this.lastModified, + this.hasPreview, + this.favorite, + this.fileMetadataSize, }); @override @@ -119,17 +168,25 @@ class NcAlbum with EquatableMixin { @override List get props => [ href, - lastPhoto, - nbItems, - location, - dateRange, + fileId, + contentLength, + contentType, + etag, + lastModified, + hasPreview, + favorite, + fileMetadataSize, ]; final String href; - final int? lastPhoto; - final int? nbItems; - final String? location; - final JsonObj? dateRange; + final int? fileId; + final int? contentLength; + final String? contentType; + final String? etag; + final DateTime? lastModified; + final bool? hasPreview; + final bool? favorite; + final JsonObj? fileMetadataSize; } @toString diff --git a/np_api/lib/src/entity/entity.g.dart b/np_api/lib/src/entity/entity.g.dart index dff92e0d..fcf821e8 100644 --- a/np_api/lib/src/entity/entity.g.dart +++ b/np_api/lib/src/entity/entity.g.dart @@ -30,7 +30,21 @@ extension _$FileToString on File { extension _$NcAlbumToString on NcAlbum { String _$toString() { // ignore: unnecessary_string_interpolations - return "NcAlbum {href: $href, lastPhoto: $lastPhoto, nbItems: $nbItems, location: $location, dateRange: $dateRange}"; + return "NcAlbum {href: $href, lastPhoto: $lastPhoto, nbItems: $nbItems, location: $location, dateRange: $dateRange, collaborators: $collaborators}"; + } +} + +extension _$NcAlbumCollaboratorToString on NcAlbumCollaborator { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "NcAlbumCollaborator {id: $id, label: $label, type: $type}"; + } +} + +extension _$NcAlbumItemToString on NcAlbumItem { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "NcAlbumItem {href: $href, ${fileId == null ? "" : "fileId: $fileId, "}${contentLength == null ? "" : "contentLength: $contentLength, "}${contentType == null ? "" : "contentType: $contentType, "}${etag == null ? "" : "etag: $etag, "}${lastModified == null ? "" : "lastModified: $lastModified, "}${hasPreview == null ? "" : "hasPreview: $hasPreview, "}${favorite == null ? "" : "favorite: $favorite, "}${fileMetadataSize == null ? "" : "fileMetadataSize: $fileMetadataSize"}}"; } } diff --git a/np_api/lib/src/entity/nc_album_item_parser.dart b/np_api/lib/src/entity/nc_album_item_parser.dart new file mode 100644 index 00000000..5873538d --- /dev/null +++ b/np_api/lib/src/entity/nc_album_item_parser.dart @@ -0,0 +1,140 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:np_api/src/entity/entity.dart'; +import 'package:np_api/src/entity/parser.dart'; +import 'package:np_common/log.dart'; +import 'package:np_common/type.dart'; +import 'package:xml/xml.dart'; + +class NcAlbumItemParser extends XmlResponseParser { + Future> parse(String response) => + compute(_parseNcAlbumItemsIsolate, response); + + List _parse(XmlDocument xml) => + parseT(xml, _toNcAlbumItem); + + /// Map contents to NcAlbumItem + NcAlbumItem _toNcAlbumItem(XmlElement element) { + String? href; + int? fileId; + int? contentLength; + String? contentType; + String? etag; + DateTime? lastModified; + bool? hasPreview; + bool? favorite; + JsonObj? fileMetadataSize; + // unclear what the value types are + // "nc:face-detections" + // "nc:realpath" + // "oc:permissions" + + for (final child in element.children.whereType()) { + if (child.matchQualifiedName("href", + prefix: "DAV:", namespaces: namespaces)) { + href = Uri.decodeComponent(child.innerText); + } else if (child.matchQualifiedName("propstat", + prefix: "DAV:", namespaces: namespaces)) { + final status = child.children + .whereType() + .firstWhere((element) => element.matchQualifiedName("status", + prefix: "DAV:", namespaces: namespaces)) + .innerText; + if (!status.contains(" 200 ")) { + continue; + } + final prop = child.children.whereType().firstWhere( + (element) => element.matchQualifiedName("prop", + prefix: "DAV:", namespaces: namespaces)); + final propParser = _PropParser(namespaces: namespaces); + propParser.parse(prop); + fileId = propParser.fileId; + contentLength = propParser.contentLength; + contentType = propParser.contentType; + etag = propParser.etag; + lastModified = propParser.lastModified; + hasPreview = propParser.hasPreview; + favorite = propParser.favorite; + fileMetadataSize = propParser.fileMetadataSize; + } + } + + return NcAlbumItem( + href: href!, + fileId: fileId, + contentLength: contentLength, + contentType: contentType, + etag: etag, + lastModified: lastModified, + hasPreview: hasPreview, + favorite: favorite, + fileMetadataSize: fileMetadataSize, + ); + } +} + +class _PropParser { + _PropParser({ + this.namespaces = const {}, + }); + + /// Parse element contents + void parse(XmlElement element) { + for (final child in element.children.whereType()) { + if (child.matchQualifiedName("getlastmodified", + prefix: "DAV:", namespaces: namespaces)) { + _lastModified = HttpDate.parse(child.innerText); + } else if (child.matchQualifiedName("getetag", + prefix: "DAV:", namespaces: namespaces)) { + _etag = child.innerText.replaceAll("\"", ""); + } else if (child.matchQualifiedName("getcontenttype", + prefix: "DAV:", namespaces: namespaces)) { + _contentType = child.innerText; + } else if (child.matchQualifiedName("getcontentlength", + prefix: "DAV:", namespaces: namespaces)) { + _contentLength = int.parse(child.innerText); + } else if (child.matchQualifiedName("fileid", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _fileId = int.parse(child.innerText); + } else if (child.matchQualifiedName("favorite", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _favorite = child.innerText != "0"; + } else if (child.matchQualifiedName("has-preview", + prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { + _hasPreview = child.innerText == "true"; + } else if (child.matchQualifiedName("file-metadata-size", + prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { + _fileMetadataSize = + child.innerText.isEmpty ? null : jsonDecode(child.innerText); + } + } + } + + DateTime? get lastModified => _lastModified; + String? get etag => _etag; + String? get contentType => _contentType; + int? get contentLength => _contentLength; + int? get fileId => _fileId; + bool? get favorite => _favorite; + bool? get hasPreview => _hasPreview; + JsonObj? get fileMetadataSize => _fileMetadataSize; + + final Map namespaces; + + DateTime? _lastModified; + String? _etag; + String? _contentType; + int? _contentLength; + int? _fileId; + bool? _favorite; + bool? _hasPreview; + JsonObj? _fileMetadataSize; +} + +List _parseNcAlbumItemsIsolate(String response) { + initLog(); + final xml = XmlDocument.parse(response); + return NcAlbumItemParser()._parse(xml); +} diff --git a/np_api/lib/src/entity/nc_album_parser.dart b/np_api/lib/src/entity/nc_album_parser.dart index fd890f7e..3fc8b3d6 100644 --- a/np_api/lib/src/entity/nc_album_parser.dart +++ b/np_api/lib/src/entity/nc_album_parser.dart @@ -20,6 +20,7 @@ class NcAlbumParser extends XmlResponseParser { int? nbItems; String? location; JsonObj? dateRange; + List? collaborators; for (final child in element.children.whereType()) { if (child.matchQualifiedName("href", @@ -44,6 +45,7 @@ class NcAlbumParser extends XmlResponseParser { nbItems = propParser.nbItems; location = propParser.location; dateRange = propParser.dateRange; + collaborators = propParser.collaborators; } } @@ -53,6 +55,7 @@ class NcAlbumParser extends XmlResponseParser { nbItems: nbItems, location: location, dateRange: dateRange, + collaborators: collaborators ?? const [], ); } } @@ -79,14 +82,44 @@ class _PropParser { prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { _dateRange = child.innerText.isEmpty ? null : jsonDecode(child.innerText); + } else if (child.matchQualifiedName("collaborators", + prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { + for (final cc in child.children.whereType()) { + if (cc.matchQualifiedName("collaborator", + prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { + _collaborators ??= []; + _collaborators!.add(_parseCollaborator(cc)); + } + } } } } + NcAlbumCollaborator _parseCollaborator(XmlElement element) { + late String id; + late String label; + late int type; + for (final child in element.children.whereType()) { + switch (child.localName) { + case "id": + id = child.innerText; + break; + case "label": + label = child.innerText; + break; + case "type": + type = int.parse(child.innerText); + break; + } + } + return NcAlbumCollaborator(id: id, label: label, type: type); + } + int? get lastPhoto => _lastPhoto; int? get nbItems => _nbItems; String? get location => _location; JsonObj? get dateRange => _dateRange; + List? get collaborators => _collaborators; final Map namespaces; @@ -94,6 +127,7 @@ class _PropParser { int? _nbItems; String? _location; JsonObj? _dateRange; + List? _collaborators; } List _parseNcAlbumsIsolate(String response) { diff --git a/np_api/test/entity/nc_album_parser_test.dart b/np_api/test/entity/nc_album_parser_test.dart new file mode 100644 index 00000000..f299b8bc --- /dev/null +++ b/np_api/test/entity/nc_album_parser_test.dart @@ -0,0 +1,247 @@ +import 'package:np_api/np_api.dart'; +import 'package:test/test.dart'; + +void main() { + group("NcAlbumParser", () { + test("no album", _noAlbum); + test("empty", _empty); + test("basic", _basic); + test("collaborative", _collaborative); + }); +} + +Future _noAlbum() async { + const xml = """ + + + + /remote.php/dav/photos/admin/albums/ + + + + + + + + + HTTP/1.1 404 Not Found + + + +"""; + final results = await NcAlbumParser().parse(xml); + expect( + results, + [ + const NcAlbum( + href: "/remote.php/dav/photos/admin/albums/", + lastPhoto: null, + nbItems: null, + location: null, + dateRange: null, + collaborators: [], + ), + ], + ); +} + +Future _empty() async { + const xml = """ + + + + /remote.php/dav/photos/admin/albums/ + + + + + + + + + HTTP/1.1 404 Not Found + + + + /remote.php/dav/photos/admin/albums/test/ + + + -1 + 0 + + {"start":null,"end":null} + + + HTTP/1.1 200 OK + + + +"""; + final results = await NcAlbumParser().parse(xml); + expect( + results, + [ + const NcAlbum( + href: "/remote.php/dav/photos/admin/albums/", + lastPhoto: null, + nbItems: null, + location: null, + dateRange: null, + collaborators: [], + ), + const NcAlbum( + href: "/remote.php/dav/photos/admin/albums/test/", + lastPhoto: -1, + nbItems: 0, + location: null, + dateRange: { + "start": null, + "end": null, + }, + collaborators: [], + ), + ], + ); +} + +Future _basic() async { + const xml = """ + + + + /remote.php/dav/photos/admin/albums/ + + + + + + + + + HTTP/1.1 404 Not Found + + + + /remote.php/dav/photos/admin/albums/test/ + + + 1 + 1 + + {"start":1577934245,"end":1580702706} + + + HTTP/1.1 200 OK + + + +"""; + final results = await NcAlbumParser().parse(xml); + expect( + results, + [ + const NcAlbum( + href: "/remote.php/dav/photos/admin/albums/", + lastPhoto: null, + nbItems: null, + location: null, + dateRange: null, + collaborators: [], + ), + const NcAlbum( + href: "/remote.php/dav/photos/admin/albums/test/", + lastPhoto: 1, + nbItems: 1, + location: null, + dateRange: { + "start": 1577934245, + "end": 1580702706, + }, + collaborators: [], + ), + ], + ); +} + +Future _collaborative() async { + const xml = """ + + + + /remote.php/dav/photos/admin/albums/ + + + + + + + + + HTTP/1.1 404 Not Found + + + + /remote.php/dav/photos/admin/albums/test/ + + + 1 + 1 + + {"start":1577934245,"end":1580702706} + + + user2 + + 0 + + + + HTTP/1.1 200 OK + + + +"""; + final results = await NcAlbumParser().parse(xml); + expect( + results, + [ + const NcAlbum( + href: "/remote.php/dav/photos/admin/albums/", + lastPhoto: null, + nbItems: null, + location: null, + dateRange: null, + collaborators: [], + ), + const NcAlbum( + href: "/remote.php/dav/photos/admin/albums/test/", + lastPhoto: 1, + nbItems: 1, + location: null, + dateRange: { + "start": 1577934245, + "end": 1580702706, + }, + collaborators: [ + NcAlbumCollaborator( + id: "user2", + label: "User2", + type: 0, + ), + ], + ), + ], + ); +}