Correctly show shared items in a collaborative nc album

This commit is contained in:
Ming Ming 2023-05-07 01:44:35 +08:00
parent d2886e55c1
commit 4d9644ac18
28 changed files with 1347 additions and 98 deletions

View file

@ -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)}";
}

View file

@ -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 _ {}

View file

@ -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._");
}

View file

@ -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<List<CollectionItem>> 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

View file

@ -41,13 +41,19 @@ class CollectionNcAlbumProvider
List<CollectionCapability> get capabilities => [
CollectionCapability.manualItem,
CollectionCapability.rename,
// CollectionCapability.share,
];
@override
CollectionItemSort get itemSort => CollectionItemSort.dateDescending;
@override
List<CollectionShare> get shares => [];
List<CollectionShare> get shares => album.collaborators
.map((c) => CollectionShare(
userId: c.id,
username: c.label,
))
.toList();
@override
String? getCoverUrl(

View file

@ -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;
}

View file

@ -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}"}}";
}
}

View file

@ -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;
}

View file

@ -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}";
}
}

View file

@ -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<NcAlbumItem>(
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),
);
});
}
});

View file

@ -1,5 +0,0 @@
class NcAlbumItem {
const NcAlbumItem(this.fileId);
final int fileId;
}

View file

@ -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';

View file

@ -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,
);
}
}

View file

@ -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"}}";
}
}

View file

@ -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);

View file

@ -4166,6 +4166,7 @@ class NcAlbum extends DataClass implements Insertable<NcAlbum> {
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<NcAlbum> {
required this.nbItems,
this.location,
this.dateStart,
this.dateEnd});
this.dateEnd,
required this.collaborators});
factory NcAlbum.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return NcAlbum(
@ -4194,6 +4196,8 @@ class NcAlbum extends DataClass implements Insertable<NcAlbum> {
.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<NcAlbum> {
final converter = $NcAlbumsTable.$converter1;
map['date_end'] = Variable<DateTime?>(converter.mapToSql(dateEnd));
}
map['collaborators'] = Variable<String>(collaborators);
return map;
}
@ -4238,6 +4243,7 @@ class NcAlbum extends DataClass implements Insertable<NcAlbum> {
dateEnd: dateEnd == null && nullToAbsent
? const Value.absent()
: Value(dateEnd),
collaborators: Value(collaborators),
);
}
@ -4253,6 +4259,7 @@ class NcAlbum extends DataClass implements Insertable<NcAlbum> {
location: serializer.fromJson<String?>(json['location']),
dateStart: serializer.fromJson<DateTime?>(json['dateStart']),
dateEnd: serializer.fromJson<DateTime?>(json['dateEnd']),
collaborators: serializer.fromJson<String>(json['collaborators']),
);
}
@override
@ -4267,6 +4274,7 @@ class NcAlbum extends DataClass implements Insertable<NcAlbum> {
'location': serializer.toJson<String?>(location),
'dateStart': serializer.toJson<DateTime?>(dateStart),
'dateEnd': serializer.toJson<DateTime?>(dateEnd),
'collaborators': serializer.toJson<String>(collaborators),
};
}
@ -4278,7 +4286,8 @@ class NcAlbum extends DataClass implements Insertable<NcAlbum> {
int? nbItems,
Value<String?> location = const Value.absent(),
Value<DateTime?> dateStart = const Value.absent(),
Value<DateTime?> dateEnd = const Value.absent()}) =>
Value<DateTime?> 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<NcAlbum> {
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<NcAlbum> {
..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<NcAlbum> {
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<NcAlbum> {
@ -4330,6 +4342,7 @@ class NcAlbumsCompanion extends UpdateCompanion<NcAlbum> {
final Value<String?> location;
final Value<DateTime?> dateStart;
final Value<DateTime?> dateEnd;
final Value<String> collaborators;
const NcAlbumsCompanion({
this.rowId = const Value.absent(),
this.account = const Value.absent(),
@ -4339,6 +4352,7 @@ class NcAlbumsCompanion extends UpdateCompanion<NcAlbum> {
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<NcAlbum> {
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<NcAlbum> custom({
Expression<int>? rowId,
Expression<int>? account,
@ -4361,6 +4377,7 @@ class NcAlbumsCompanion extends UpdateCompanion<NcAlbum> {
Expression<String?>? location,
Expression<DateTime?>? dateStart,
Expression<DateTime?>? dateEnd,
Expression<String>? collaborators,
}) {
return RawValuesInsertable({
if (rowId != null) 'row_id': rowId,
@ -4371,6 +4388,7 @@ class NcAlbumsCompanion extends UpdateCompanion<NcAlbum> {
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<NcAlbum> {
Value<int>? nbItems,
Value<String?>? location,
Value<DateTime?>? dateStart,
Value<DateTime?>? dateEnd}) {
Value<DateTime?>? dateEnd,
Value<String>? collaborators}) {
return NcAlbumsCompanion(
rowId: rowId ?? this.rowId,
account: account ?? this.account,
@ -4392,6 +4411,7 @@ class NcAlbumsCompanion extends UpdateCompanion<NcAlbum> {
location: location ?? this.location,
dateStart: dateStart ?? this.dateStart,
dateEnd: dateEnd ?? this.dateEnd,
collaborators: collaborators ?? this.collaborators,
);
}
@ -4425,6 +4445,9 @@ class NcAlbumsCompanion extends UpdateCompanion<NcAlbum> {
final converter = $NcAlbumsTable.$converter1;
map['date_end'] = Variable<DateTime?>(converter.mapToSql(dateEnd.value));
}
if (collaborators.present) {
map['collaborators'] = Variable<String>(collaborators.value);
}
return map;
}
@ -4438,7 +4461,8 @@ class NcAlbumsCompanion extends UpdateCompanion<NcAlbum> {
..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<DateTime?>('date_end', aliasedName, true,
type: const IntType(), requiredDuringInsert: false)
.withConverter<DateTime>($NcAlbumsTable.$converter1);
final VerificationMeta _collaboratorsMeta =
const VerificationMeta('collaborators');
@override
late final GeneratedColumn<String?> collaborators = GeneratedColumn<String?>(
'collaborators', aliasedName, false,
type: const StringType(), requiredDuringInsert: true);
@override
List<GeneratedColumn> 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<NcAlbumItem> {
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<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return NcAlbumItem(
@ -4589,8 +4648,27 @@ class NcAlbumItem extends DataClass implements Insertable<NcAlbumItem> {
.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<NcAlbumItem> {
final map = <String, Expression>{};
map['row_id'] = Variable<int>(rowId);
map['parent'] = Variable<int>(parent);
map['relative_path'] = Variable<String>(relativePath);
map['file_id'] = Variable<int>(fileId);
if (!nullToAbsent || contentLength != null) {
map['content_length'] = Variable<int?>(contentLength);
}
if (!nullToAbsent || contentType != null) {
map['content_type'] = Variable<String?>(contentType);
}
if (!nullToAbsent || etag != null) {
map['etag'] = Variable<String?>(etag);
}
if (!nullToAbsent || lastModified != null) {
final converter = $NcAlbumItemsTable.$converter0;
map['last_modified'] =
Variable<DateTime?>(converter.mapToSql(lastModified));
}
if (!nullToAbsent || hasPreview != null) {
map['has_preview'] = Variable<bool?>(hasPreview);
}
if (!nullToAbsent || isFavorite != null) {
map['is_favorite'] = Variable<bool?>(isFavorite);
}
if (!nullToAbsent || fileMetadataWidth != null) {
map['file_metadata_width'] = Variable<int?>(fileMetadataWidth);
}
if (!nullToAbsent || fileMetadataHeight != null) {
map['file_metadata_height'] = Variable<int?>(fileMetadataHeight);
}
return map;
}
@ -4606,7 +4711,30 @@ class NcAlbumItem extends DataClass implements Insertable<NcAlbumItem> {
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<NcAlbumItem> {
return NcAlbumItem(
rowId: serializer.fromJson<int>(json['rowId']),
parent: serializer.fromJson<int>(json['parent']),
relativePath: serializer.fromJson<String>(json['relativePath']),
fileId: serializer.fromJson<int>(json['fileId']),
contentLength: serializer.fromJson<int?>(json['contentLength']),
contentType: serializer.fromJson<String?>(json['contentType']),
etag: serializer.fromJson<String?>(json['etag']),
lastModified: serializer.fromJson<DateTime?>(json['lastModified']),
hasPreview: serializer.fromJson<bool?>(json['hasPreview']),
isFavorite: serializer.fromJson<bool?>(json['isFavorite']),
fileMetadataWidth: serializer.fromJson<int?>(json['fileMetadataWidth']),
fileMetadataHeight: serializer.fromJson<int?>(json['fileMetadataHeight']),
);
}
@override
@ -4625,69 +4762,203 @@ class NcAlbumItem extends DataClass implements Insertable<NcAlbumItem> {
return <String, dynamic>{
'rowId': serializer.toJson<int>(rowId),
'parent': serializer.toJson<int>(parent),
'relativePath': serializer.toJson<String>(relativePath),
'fileId': serializer.toJson<int>(fileId),
'contentLength': serializer.toJson<int?>(contentLength),
'contentType': serializer.toJson<String?>(contentType),
'etag': serializer.toJson<String?>(etag),
'lastModified': serializer.toJson<DateTime?>(lastModified),
'hasPreview': serializer.toJson<bool?>(hasPreview),
'isFavorite': serializer.toJson<bool?>(isFavorite),
'fileMetadataWidth': serializer.toJson<int?>(fileMetadataWidth),
'fileMetadataHeight': serializer.toJson<int?>(fileMetadataHeight),
};
}
NcAlbumItem copyWith({int? rowId, int? parent, int? fileId}) => NcAlbumItem(
NcAlbumItem copyWith(
{int? rowId,
int? parent,
String? relativePath,
int? fileId,
Value<int?> contentLength = const Value.absent(),
Value<String?> contentType = const Value.absent(),
Value<String?> etag = const Value.absent(),
Value<DateTime?> lastModified = const Value.absent(),
Value<bool?> hasPreview = const Value.absent(),
Value<bool?> isFavorite = const Value.absent(),
Value<int?> fileMetadataWidth = const Value.absent(),
Value<int?> 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<NcAlbumItem> {
final Value<int> rowId;
final Value<int> parent;
final Value<String> relativePath;
final Value<int> fileId;
final Value<int?> contentLength;
final Value<String?> contentType;
final Value<String?> etag;
final Value<DateTime?> lastModified;
final Value<bool?> hasPreview;
final Value<bool?> isFavorite;
final Value<int?> fileMetadataWidth;
final Value<int?> 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<NcAlbumItem> custom({
Expression<int>? rowId,
Expression<int>? parent,
Expression<String>? relativePath,
Expression<int>? fileId,
Expression<int?>? contentLength,
Expression<String?>? contentType,
Expression<String?>? etag,
Expression<DateTime?>? lastModified,
Expression<bool?>? hasPreview,
Expression<bool?>? isFavorite,
Expression<int?>? fileMetadataWidth,
Expression<int?>? 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<int>? rowId, Value<int>? parent, Value<int>? fileId}) {
{Value<int>? rowId,
Value<int>? parent,
Value<String>? relativePath,
Value<int>? fileId,
Value<int?>? contentLength,
Value<String?>? contentType,
Value<String?>? etag,
Value<DateTime?>? lastModified,
Value<bool?>? hasPreview,
Value<bool?>? isFavorite,
Value<int?>? fileMetadataWidth,
Value<int?>? 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<NcAlbumItem> {
if (parent.present) {
map['parent'] = Variable<int>(parent.value);
}
if (relativePath.present) {
map['relative_path'] = Variable<String>(relativePath.value);
}
if (fileId.present) {
map['file_id'] = Variable<int>(fileId.value);
}
if (contentLength.present) {
map['content_length'] = Variable<int?>(contentLength.value);
}
if (contentType.present) {
map['content_type'] = Variable<String?>(contentType.value);
}
if (etag.present) {
map['etag'] = Variable<String?>(etag.value);
}
if (lastModified.present) {
final converter = $NcAlbumItemsTable.$converter0;
map['last_modified'] =
Variable<DateTime?>(converter.mapToSql(lastModified.value));
}
if (hasPreview.present) {
map['has_preview'] = Variable<bool?>(hasPreview.value);
}
if (isFavorite.present) {
map['is_favorite'] = Variable<bool?>(isFavorite.value);
}
if (fileMetadataWidth.present) {
map['file_metadata_width'] = Variable<int?>(fileMetadataWidth.value);
}
if (fileMetadataHeight.present) {
map['file_metadata_height'] = Variable<int?>(fileMetadataHeight.value);
}
return map;
}
@ -4711,7 +5011,16 @@ class NcAlbumItemsCompanion extends UpdateCompanion<NcAlbumItem> {
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<String?> relativePath = GeneratedColumn<String?>(
'relative_path', aliasedName, false,
type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _fileIdMeta = const VerificationMeta('fileId');
@override
late final GeneratedColumn<int?> fileId = GeneratedColumn<int?>(
'file_id', aliasedName, false,
type: const IntType(), requiredDuringInsert: true);
final VerificationMeta _contentLengthMeta =
const VerificationMeta('contentLength');
@override
List<GeneratedColumn> get $columns => [rowId, parent, fileId];
late final GeneratedColumn<int?> contentLength = GeneratedColumn<int?>(
'content_length', aliasedName, true,
type: const IntType(), requiredDuringInsert: false);
final VerificationMeta _contentTypeMeta =
const VerificationMeta('contentType');
@override
late final GeneratedColumn<String?> contentType = GeneratedColumn<String?>(
'content_type', aliasedName, true,
type: const StringType(), requiredDuringInsert: false);
final VerificationMeta _etagMeta = const VerificationMeta('etag');
@override
late final GeneratedColumn<String?> etag = GeneratedColumn<String?>(
'etag', aliasedName, true,
type: const StringType(), requiredDuringInsert: false);
final VerificationMeta _lastModifiedMeta =
const VerificationMeta('lastModified');
@override
late final GeneratedColumnWithTypeConverter<DateTime, DateTime?>
lastModified = GeneratedColumn<DateTime?>(
'last_modified', aliasedName, true,
type: const IntType(), requiredDuringInsert: false)
.withConverter<DateTime>($NcAlbumItemsTable.$converter0);
final VerificationMeta _hasPreviewMeta = const VerificationMeta('hasPreview');
@override
late final GeneratedColumn<bool?> hasPreview = GeneratedColumn<bool?>(
'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<bool?> isFavorite = GeneratedColumn<bool?>(
'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<int?> fileMetadataWidth = GeneratedColumn<int?>(
'file_metadata_width', aliasedName, true,
type: const IntType(), requiredDuringInsert: false);
final VerificationMeta _fileMetadataHeightMeta =
const VerificationMeta('fileMetadataHeight');
@override
late final GeneratedColumn<int?> fileMetadataHeight = GeneratedColumn<int?>(
'file_metadata_height', aliasedName, true,
type: const IntType(), requiredDuringInsert: false);
@override
List<GeneratedColumn> 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<DateTime, DateTime> $converter0 =
const SqliteDateTimeConverter();
}
abstract class _$SqliteDb extends GeneratedDatabase {

View file

@ -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<Set<Column>>? 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<Set<Column>>? get uniqueKeys => [

View file

@ -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<Map>());
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<String, dynamic>()))
.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),
);
}

View file

@ -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));

View file

@ -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';

View file

@ -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);
}
}

View file

@ -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(

View file

@ -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';

View file

@ -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<Object?> 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<NcAlbumCollaborator> collaborators;
}
@toString
class NcAlbumCollaborator with EquatableMixin {
const NcAlbumCollaborator({
required this.id,
required this.label,
required this.type,
});
@override
String toString() => _$toString();
@override
List<Object?> 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<Object?> 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

View file

@ -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"}}";
}
}

View file

@ -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<List<NcAlbumItem>> parse(String response) =>
compute(_parseNcAlbumItemsIsolate, response);
List<NcAlbumItem> _parse(XmlDocument xml) =>
parseT<NcAlbumItem>(xml, _toNcAlbumItem);
/// Map <DAV:response> 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<XmlElement>()) {
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<XmlElement>()
.firstWhere((element) => element.matchQualifiedName("status",
prefix: "DAV:", namespaces: namespaces))
.innerText;
if (!status.contains(" 200 ")) {
continue;
}
final prop = child.children.whereType<XmlElement>().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 <DAV:prop> element contents
void parse(XmlElement element) {
for (final child in element.children.whereType<XmlElement>()) {
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<String, String> namespaces;
DateTime? _lastModified;
String? _etag;
String? _contentType;
int? _contentLength;
int? _fileId;
bool? _favorite;
bool? _hasPreview;
JsonObj? _fileMetadataSize;
}
List<NcAlbumItem> _parseNcAlbumItemsIsolate(String response) {
initLog();
final xml = XmlDocument.parse(response);
return NcAlbumItemParser()._parse(xml);
}

View file

@ -20,6 +20,7 @@ class NcAlbumParser extends XmlResponseParser {
int? nbItems;
String? location;
JsonObj? dateRange;
List<NcAlbumCollaborator>? collaborators;
for (final child in element.children.whereType<XmlElement>()) {
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<XmlElement>()) {
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<XmlElement>()) {
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<NcAlbumCollaborator>? get collaborators => _collaborators;
final Map<String, String> namespaces;
@ -94,6 +127,7 @@ class _PropParser {
int? _nbItems;
String? _location;
JsonObj? _dateRange;
List<NcAlbumCollaborator>? _collaborators;
}
List<NcAlbum> _parseNcAlbumsIsolate(String response) {

View file

@ -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<void> _noAlbum() async {
const xml = """
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns"
xmlns:oc="http://owncloud.org/ns"
xmlns:nc="http://nextcloud.org/ns">
<d:response>
<d:href>/remote.php/dav/photos/admin/albums/</d:href>
<d:propstat>
<d:prop>
<nc:last-photo/>
<nc:nbItems/>
<nc:location/>
<nc:dateRange/>
<nc:collaborators/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
</d:multistatus>
""";
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<void> _empty() async {
const xml = """
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns"
xmlns:oc="http://owncloud.org/ns"
xmlns:nc="http://nextcloud.org/ns">
<d:response>
<d:href>/remote.php/dav/photos/admin/albums/</d:href>
<d:propstat>
<d:prop>
<nc:last-photo/>
<nc:nbItems/>
<nc:location/>
<nc:dateRange/>
<nc:collaborators/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>/remote.php/dav/photos/admin/albums/test/</d:href>
<d:propstat>
<d:prop>
<nc:last-photo>-1</nc:last-photo>
<nc:nbItems>0</nc:nbItems>
<nc:location></nc:location>
<nc:dateRange>{&quot;start&quot;:null,&quot;end&quot;:null}</nc:dateRange>
<nc:collaborators/>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
""";
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: <String, dynamic>{
"start": null,
"end": null,
},
collaborators: [],
),
],
);
}
Future<void> _basic() async {
const xml = """
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns"
xmlns:oc="http://owncloud.org/ns"
xmlns:nc="http://nextcloud.org/ns">
<d:response>
<d:href>/remote.php/dav/photos/admin/albums/</d:href>
<d:propstat>
<d:prop>
<nc:last-photo/>
<nc:nbItems/>
<nc:location/>
<nc:dateRange/>
<nc:collaborators/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>/remote.php/dav/photos/admin/albums/test/</d:href>
<d:propstat>
<d:prop>
<nc:last-photo>1</nc:last-photo>
<nc:nbItems>1</nc:nbItems>
<nc:location></nc:location>
<nc:dateRange>{&quot;start&quot;:1577934245,&quot;end&quot;:1580702706}</nc:dateRange>
<nc:collaborators/>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
""";
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: <String, dynamic>{
"start": 1577934245,
"end": 1580702706,
},
collaborators: [],
),
],
);
}
Future<void> _collaborative() async {
const xml = """
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns"
xmlns:oc="http://owncloud.org/ns"
xmlns:nc="http://nextcloud.org/ns">
<d:response>
<d:href>/remote.php/dav/photos/admin/albums/</d:href>
<d:propstat>
<d:prop>
<nc:last-photo/>
<nc:nbItems/>
<nc:location/>
<nc:dateRange/>
<nc:collaborators/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>/remote.php/dav/photos/admin/albums/test/</d:href>
<d:propstat>
<d:prop>
<nc:last-photo>1</nc:last-photo>
<nc:nbItems>1</nc:nbItems>
<nc:location></nc:location>
<nc:dateRange>{&quot;start&quot;:1577934245,&quot;end&quot;:1580702706}</nc:dateRange>
<nc:collaborators>
<nc:collaborator>
<id>user2</id>
<label>User2</label>
<type>0</type>
</nc:collaborator>
</nc:collaborators>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
""";
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: <String, dynamic>{
"start": 1577934245,
"end": 1580702706,
},
collaborators: [
NcAlbumCollaborator(
id: "user2",
label: "User2",
type: 0,
),
],
),
],
);
}