Migrate album to save files as file descriptors

This commit is contained in:
Ming Ming 2024-02-14 01:12:04 +08:00
parent 07ee28d0cb
commit 1c999f6fab
35 changed files with 1162 additions and 466 deletions

View file

@ -9,6 +9,7 @@ import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/entity/share.dart';
import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/use_case/list_share.dart';
@ -28,12 +29,12 @@ class ListAlbumShareOutlierItem with EquatableMixin {
String toString() => _$toString();
@override
get props => [
List<Object?> get props => [
file,
shareItems,
];
final File file;
final FileDescriptor file;
@Format(r"${$?.toReadableString()}")
final List<ListAlbumShareOutlierShareItem> shareItems;
}
@ -164,7 +165,6 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
ListAlbumShareOutlierBlocState> {
ListAlbumShareOutlierBloc(this._c)
: assert(require(_c)),
assert(ListShare.require(_c)),
super(ListAlbumShareOutlierBlocInit()) {
on<ListAlbumShareOutlierBlocEvent>(_onEvent);
}
@ -282,7 +282,7 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
});
} catch (e, stackTrace) {
_log.severe(
"[_processAlbumItems] Failed while _processSingleFile: ${logFilename(fi.file.path)}",
"[_processAlbumItems] Failed while _processSingleFile: ${logFilename(fi.file.fdPath)}",
e,
stackTrace);
errors.add(e);
@ -312,7 +312,7 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
.map((s) => s.userId)
.toSet();
_log.info(
"[_processSingleFileItem] Sharees: ${albumSharees.map((s) => managedAlbumSharees.contains(s) ? "(managed)$s" : s).toReadableString()} for file: ${logFilename(fileItem.file.path)}");
"[_processSingleFileItem] Sharees: ${albumSharees.map((s) => managedAlbumSharees.contains(s) ? "(managed)$s" : s).toReadableString()} for file: ${logFilename(fileItem.file.fdPath)}");
// check all shares (including reshares) against sharees that are managed by
// us
@ -320,10 +320,10 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
var missings = managedAlbumSharees
.difference(allSharees)
// Can't share to ourselves or the file owner
.where((s) => s != account.userId && s != fileItem.file.ownerId)
.where((s) => s != account.userId && s != fileItem.ownerId)
.toList();
_log.info(
"[_processSingleFileItem] Missing shares: ${missings.toReadableString()} for file: ${logFilename(fileItem.file.path)}");
"[_processSingleFileItem] Missing shares: ${missings.toReadableString()} for file: ${logFilename(fileItem.file.fdPath)}");
for (final m in missings) {
final as = albumShares[m]!;
shareItems.add(
@ -338,14 +338,14 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
.toSet();
final extras = ownedSharees.difference(albumSharees);
_log.info(
"[_processSingleFileItem] Extra shares: ${extras.toReadableString()} for file: ${logFilename(fileItem.file.path)}");
"[_processSingleFileItem] Extra shares: ${extras.toReadableString()} for file: ${logFilename(fileItem.file.fdPath)}");
for (final e in extras) {
try {
shareItems.add(ListAlbumShareOutlierExtraShareItem(
shares.firstWhere((s) => s.shareWith == e)));
} catch (e, stackTrace) {
_log.severe(
"[_processSingleFileItem] Failed while processing extra share for file: ${logFilename(fileItem.file.path)}",
"[_processSingleFileItem] Failed while processing extra share for file: ${logFilename(fileItem.file.fdPath)}",
e,
stackTrace);
errors.add(e);

View file

@ -21,7 +21,7 @@ extension _$ListAlbumShareOutlierBlocNpLog on ListAlbumShareOutlierBloc {
extension _$ListAlbumShareOutlierItemToString on ListAlbumShareOutlierItem {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "ListAlbumShareOutlierItem {file: ${file.path}, shareItems: ${shareItems.toReadableString()}}";
return "ListAlbumShareOutlierItem {file: ${file.fdPath}, shareItems: ${shareItems.toReadableString()}}";
}
}

View file

@ -101,6 +101,13 @@ class Album with EquatableMixin {
return null;
}
}
if (jsonVersion < 10) {
result = upgraderFactory?.buildV9()?.doJson(result);
if (result == null) {
_log.info("[fromJson] Version $jsonVersion not compatible");
return null;
}
}
if (jsonVersion > version) {
_log.warning(
"[fromJson] Reading album with newer version: $jsonVersion > $version");
@ -224,7 +231,7 @@ class Album with EquatableMixin {
final int savedVersion;
/// versioning of this class, use to upgrade old persisted album
static const version = 9;
static const version = 10;
static final _log = _$AlbumNpLog.log;
}

View file

@ -4,7 +4,6 @@ import 'package:logging/logging.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:np_codegen/np_codegen.dart';
@ -78,11 +77,8 @@ class AlbumAutoCoverProvider extends AlbumCoverProvider {
return items
.whereType<AlbumFileItem>()
.map((e) => e.file)
.where((element) =>
file_util.isSupportedFormat(element) &&
(element.hasPreview ?? false) &&
element.fileId != null)
.sorted(compareFileDateTimeDescending)
.where(file_util.isSupportedFormat)
.sorted(compareFileDescriptorDateTimeDescending)
.firstOrNull;
}

View file

@ -147,6 +147,11 @@ class AlbumSqliteDbDataSource2 implements AlbumDataSource2 {
if (dbAlbum.version < 9) {
dbAlbum = AlbumUpgraderV8(logFilePath: file.path).doDb(dbAlbum)!;
}
if (dbAlbum.version < 10) {
dbAlbum =
AlbumUpgraderV9(account: account, logFilePath: file.path)
.doDb(dbAlbum)!;
}
return DbAlbumConverter.fromDb(file, dbAlbum);
} catch (e, stackTrace) {
_log.severe(

View file

@ -1,9 +1,8 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_common/or_null.dart';
import 'package:np_common/type.dart';
import 'package:np_string/np_string.dart';
import 'package:to_string/to_string.dart';
@ -57,11 +56,13 @@ abstract class AlbumItem with EquatableMixin {
JsonObj toContentJson();
bool compareServerIdentity(AlbumItem other);
@override
String toString() => _$toString();
@override
get props => [
List<Object?> get props => [
addedBy,
addedAt,
];
@ -75,29 +76,19 @@ abstract class AlbumItem with EquatableMixin {
@toString
class AlbumFileItem extends AlbumItem {
AlbumFileItem({
required CiString addedBy,
required DateTime addedAt,
required super.addedBy,
required super.addedAt,
required this.file,
}) : super(addedBy: addedBy, addedAt: addedAt);
@override
// ignore: hash_and_equals
bool operator ==(Object? other) => equals(other, isDeep: true);
bool equals(Object? other, {bool isDeep = false}) {
if (other is AlbumFileItem) {
return super == other && (file.equals(other.file, isDeep: isDeep));
} else {
return false;
}
}
required this.ownerId,
});
factory AlbumFileItem.fromJson(
JsonObj json, CiString addedBy, DateTime addedAt) {
return AlbumFileItem(
addedBy: addedBy,
addedAt: addedAt,
file: File.fromJson(json["file"].cast<String, dynamic>()),
file: FileDescriptor.fromJson(json["file"].cast<String, dynamic>()),
ownerId: (json["ownerId"] as String).toCi(),
);
}
@ -105,37 +96,43 @@ class AlbumFileItem extends AlbumItem {
String toString() => _$toString();
@override
toContentJson() {
JsonObj toContentJson() {
return {
"file": file.toJson(),
"file": file.toFdJson(),
"ownerId": ownerId.raw,
};
}
@override
bool compareServerIdentity(AlbumItem other) =>
other is AlbumFileItem &&
file.compareServerIdentity(other.file) &&
addedBy == other.addedBy &&
addedAt == other.addedAt;
AlbumFileItem copyWith({
CiString? addedBy,
DateTime? addedAt,
File? file,
FileDescriptor? file,
CiString? ownerId,
}) {
return AlbumFileItem(
addedBy: addedBy ?? this.addedBy,
addedAt: addedAt ?? this.addedAt,
file: file ?? this.file,
ownerId: ownerId ?? this.ownerId,
);
}
AlbumFileItem minimize() => AlbumFileItem(
addedBy: addedBy,
addedAt: addedAt,
file: file.copyWith(metadata: const OrNull(null)),
);
@override
get props => [
List<Object?> get props => [
...super.props,
// file is handled separately, see [equals]
file,
ownerId,
];
final File file;
final FileDescriptor file;
final CiString ownerId;
static const _type = "file";
}
@ -161,12 +158,19 @@ class AlbumLabelItem extends AlbumItem {
String toString() => _$toString();
@override
toContentJson() {
JsonObj toContentJson() {
return {
"text": text,
};
}
@override
bool compareServerIdentity(AlbumItem other) =>
other is AlbumLabelItem &&
text == other.text &&
addedBy == other.addedBy &&
addedAt == other.addedAt;
AlbumLabelItem copyWith({
CiString? addedBy,
DateTime? addedAt,
@ -180,7 +184,7 @@ class AlbumLabelItem extends AlbumItem {
}
@override
get props => [
List<Object?> get props => [
...super.props,
text,
];

View file

@ -27,7 +27,7 @@ extension _$AlbumItemToString on AlbumItem {
extension _$AlbumFileItemToString on AlbumFileItem {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "AlbumFileItem {addedBy: $addedBy, addedAt: $addedAt, file: ${file.path}}";
return "AlbumFileItem {addedBy: $addedBy, addedAt: $addedAt, file: ${file.fdPath}, ownerId: $ownerId}";
}
}

View file

@ -350,6 +350,64 @@ class AlbumUpgraderV8 implements AlbumUpgrader {
final String? logFilePath;
}
/// Upgrade v9 Album to v10
///
/// In v10, file items are now stored as FileDescriptor instead of File
@npLog
class AlbumUpgraderV9 implements AlbumUpgrader {
const AlbumUpgraderV9({
required this.account,
this.logFilePath,
});
@override
JsonObj? doJson(JsonObj json) {
_log.fine("[doJson] Upgrade v9 Album for file: $logFilePath");
final result = JsonObj.from(json);
if (result["provider"]["type"] != "static") {
return result;
}
for (final item in (result["provider"]["content"]["items"] as List)) {
if (item["type"] != "file") {
continue;
}
final originalFile =
(item["content"]["file"] as Map).cast<String, dynamic>();
item["content"]["file"] =
AlbumUpgraderV8._fileJsonToFileDescriptorJson(originalFile);
item["content"]["ownerId"] =
originalFile["ownerId"] ?? account.userId.raw;
}
return result;
}
@override
DbAlbum? doDb(DbAlbum dbObj) {
_log.fine("[doDb] Upgrade v9 Album for file: $logFilePath");
if (dbObj.providerType != "static") {
return dbObj;
}
final content = Map.of(dbObj.providerContent);
for (final item in content["items"] as List) {
if (item["type"] != "file") {
continue;
}
final originalFile =
(item["content"]["file"] as Map).cast<String, dynamic>();
item["content"]["file"] =
AlbumUpgraderV8._fileJsonToFileDescriptorJson(originalFile);
item["content"]["ownerId"] =
originalFile["ownerId"] ?? account.userId.raw;
}
return dbObj.copyWith(providerContent: content);
}
final Account account;
/// File path for logging only
final String? logFilePath;
}
abstract class AlbumUpgraderFactory {
const AlbumUpgraderFactory();
@ -361,6 +419,7 @@ abstract class AlbumUpgraderFactory {
AlbumUpgraderV6? buildV6();
AlbumUpgraderV7? buildV7();
AlbumUpgraderV8? buildV8();
AlbumUpgraderV9? buildV9();
}
class DefaultAlbumUpgraderFactory extends AlbumUpgraderFactory {
@ -371,32 +430,38 @@ class DefaultAlbumUpgraderFactory extends AlbumUpgraderFactory {
});
@override
buildV1() => AlbumUpgraderV1(logFilePath: logFilePath);
AlbumUpgraderV1 buildV1() => AlbumUpgraderV1(logFilePath: logFilePath);
@override
buildV2() => AlbumUpgraderV2(logFilePath: logFilePath);
AlbumUpgraderV2 buildV2() => AlbumUpgraderV2(logFilePath: logFilePath);
@override
buildV3() => AlbumUpgraderV3(logFilePath: logFilePath);
AlbumUpgraderV3 buildV3() => AlbumUpgraderV3(logFilePath: logFilePath);
@override
buildV4() => AlbumUpgraderV4(logFilePath: logFilePath);
AlbumUpgraderV4 buildV4() => AlbumUpgraderV4(logFilePath: logFilePath);
@override
buildV5() => AlbumUpgraderV5(
AlbumUpgraderV5 buildV5() => AlbumUpgraderV5(
account,
albumFile: albumFile,
logFilePath: logFilePath,
);
@override
buildV6() => AlbumUpgraderV6(logFilePath: logFilePath);
AlbumUpgraderV6 buildV6() => AlbumUpgraderV6(logFilePath: logFilePath);
@override
buildV7() => AlbumUpgraderV7(logFilePath: logFilePath);
AlbumUpgraderV7 buildV7() => AlbumUpgraderV7(logFilePath: logFilePath);
@override
AlbumUpgraderV8? buildV8() => AlbumUpgraderV8(logFilePath: logFilePath);
AlbumUpgraderV8 buildV8() => AlbumUpgraderV8(logFilePath: logFilePath);
@override
AlbumUpgraderV9 buildV9() => AlbumUpgraderV9(
account: account,
logFilePath: logFilePath,
);
final Account account;
final File? albumFile;

View file

@ -61,3 +61,10 @@ extension _$AlbumUpgraderV8NpLog on AlbumUpgraderV8 {
static final log = Logger("entity.album.upgrader.AlbumUpgraderV8");
}
extension _$AlbumUpgraderV9NpLog on AlbumUpgraderV9 {
// ignore: unused_element
Logger get _log => log;
static final log = Logger("entity.album.upgrader.AlbumUpgraderV9");
}

View file

@ -192,7 +192,7 @@ class CollectionAlbumAdapter implements CollectionAdapter {
_provider.album,
sharee,
onShareFileFailed: (f, e, stackTrace) {
_log.severe("[share] Failed to share file: ${logFilename(f.path)}", e,
_log.severe("[share] Failed to share file: ${logFilename(f.fdPath)}", e,
stackTrace);
fileFailed = true;
},

View file

@ -50,6 +50,7 @@ class CollectionExporter {
addedBy: account.userId,
addedAt: clock.now().toUtc(),
file: f,
ownerId: f.ownerId ?? account.userId,
);
}
} else if (e is CollectionLabelItem) {

View file

@ -55,7 +55,7 @@ class FileDescriptor with EquatableMixin {
JsonObj toFdJson() => toJson(this);
@override
get props => [
List<Object?> get props => [
fdPath,
fdId,
fdMime,
@ -136,3 +136,23 @@ extension FileDescriptorExtension on FileDescriptor {
);
}
}
class FileDescriptorServerIdentityComparator {
const FileDescriptorServerIdentityComparator(this.file);
@override
bool operator ==(Object other) {
if (other is FileDescriptorServerIdentityComparator) {
return file.compareServerIdentity(other.file);
} else if (other is FileDescriptor) {
return file.compareServerIdentity(other);
} else {
return false;
}
}
@override
int get hashCode => file.fdId.hashCode;
final FileDescriptor file;
}

View file

@ -27,7 +27,6 @@ class AddFileToAlbum {
static bool require(DiContainer c) =>
DiContainer.has(c, DiType.albumRepo) &&
DiContainer.has(c, DiType.shareRepo) &&
ListShare.require(c) &&
PreProcessAlbum.require(c);
/// Add list of files to [album]
@ -47,7 +46,8 @@ class AddFileToAlbum {
.map((f) => AlbumFileItem(
addedBy: account.userId,
addedAt: clock.now(),
file: f,
file: f.toDescriptor(),
ownerId: f.ownerId ?? account.userId,
))
.where((i) => itemSet.add(OverrideComparator<AlbumItem>(
i, _isItemFileEqual, _getItemHashCode)))
@ -63,18 +63,14 @@ class AddFileToAlbum {
);
// UpdateAlbumWithActualItems only persists when there are changes to
// several properties, so we can't rely on it
newAlbum = await UpdateAlbumWithActualItems(null)(
account,
newAlbum,
newItems,
);
newAlbum =
await UpdateAlbumWithActualItems(null)(account, newAlbum, newItems);
await UpdateAlbum(_c.albumRepo)(account, newAlbum);
if (album.shares?.isNotEmpty == true) {
final newFiles =
addItems.whereType<AlbumFileItem>().map((e) => e.file).toList();
if (newFiles.isNotEmpty) {
await _shareFiles(account, newAlbum, newFiles);
final newFileItems = addItems.whereType<AlbumFileItem>().toList();
if (newFileItems.isNotEmpty) {
await _shareFiles(account, newAlbum, newFileItems);
}
}
@ -82,7 +78,7 @@ class AddFileToAlbum {
}
Future<void> _shareFiles(
Account account, Album album, List<File> files) async {
Account account, Album album, List<AlbumFileItem> fileItems) async {
final albumShares = (album.shares!.map((e) => e.userId).toList()
..add(album.albumFile!.ownerId ?? account.userId))
.where((element) => element != account.userId)
@ -90,30 +86,30 @@ class AddFileToAlbum {
if (albumShares.isEmpty) {
return;
}
for (final f in files) {
for (final i in fileItems) {
try {
final fileShares = (await ListShare(_c)(account, f))
final fileShares = (await ListShare(_c)(account, i.file))
.where((element) => element.shareType == ShareType.user)
.map((e) => e.shareWith!)
.toSet();
final diffShares = albumShares.difference(fileShares);
for (final s in diffShares) {
if (s == f.ownerId) {
if (s == i.ownerId) {
// skip files already owned by the target user
continue;
}
try {
await CreateUserShare(_c.shareRepo)(account, f, s.raw);
await CreateUserShare(_c.shareRepo)(account, i.file, s.raw);
} catch (e, stackTrace) {
_log.shout(
"[_shareFiles] Failed while CreateUserShare: ${logFilename(f.path)}",
"[_shareFiles] Failed while CreateUserShare: ${logFilename(i.file.fdPath)}",
e,
stackTrace);
}
}
} catch (e, stackTrace) {
_log.shout(
"[_shareFiles] Failed while listing shares: ${logFilename(f.path)}",
"[_shareFiles] Failed while listing shares: ${logFilename(i.file.fdPath)}",
e,
stackTrace);
}
@ -133,7 +129,7 @@ bool _isItemFileEqual(AlbumItem a, AlbumItem b) {
int _getItemHashCode(AlbumItem a) {
if (a is AlbumFileItem) {
return a.file.fileId?.hashCode ?? a.file.path.hashCode;
return a.file.fdId.hashCode;
} else {
return a.hashCode;
}

View file

@ -19,7 +19,6 @@ part 'remove_album.g.dart';
class RemoveAlbum {
RemoveAlbum(this._c)
: assert(require(_c)),
assert(ListShare.require(_c)),
assert(UnshareFileFromAlbum.require(_c));
static bool require(DiContainer c) =>

View file

@ -30,9 +30,6 @@ class RemoveFromAlbum {
/// Remove a list of AlbumItems from [album]
///
/// The items are compared with [identical], so it must come from [album] for
/// it to work
///
/// If [shouldUnshare] is false, files will not be unshared after removing
/// from the album
Future<Album> call(
@ -65,7 +62,8 @@ class RemoveFromAlbum {
.toList();
final provider = album.provider as AlbumStaticProvider;
final newItems = provider.items
.where((element) => !filtered.containsIdentical(element))
.where((e) =>
!filtered.containsIf(e, (a, b) => a.compareServerIdentity(b)))
.toList();
var newAlbum = album.copyWith(
provider: AlbumStaticProvider.of(album).copyWith(
@ -108,7 +106,7 @@ class RemoveFromAlbum {
isNeedUpdate = true;
break;
}
if (fileItem.file.bestDateTime == newAlbum.provider.latestItemTime) {
if (fileItem.file.fdDateTime == newAlbum.provider.latestItemTime) {
isNeedUpdate = true;
break;
}
@ -130,7 +128,7 @@ class RemoveFromAlbum {
}
Future<void> _unshareFiles(
Account account, Album album, List<File> files) async {
Account account, Album album, List<FileDescriptor> files) async {
final albumShares = (album.shares!.map((e) => e.userId).toList()
..add(album.albumFile!.ownerId ?? account.userId))
.where((element) => element != account.userId)

View file

@ -4,7 +4,7 @@ import 'package:nc_photos/debug_util.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/entity/share.dart';
import 'package:nc_photos/entity/sharee.dart';
import 'package:nc_photos/use_case/create_share.dart';
@ -25,7 +25,7 @@ class ShareAlbumWithUser {
Account account,
Album album,
Sharee sharee, {
ErrorWithValueHandler<File>? onShareFileFailed,
ErrorWithValueHandler<FileDescriptor>? onShareFileFailed,
}) async {
assert(album.provider is AlbumStaticProvider);
final newShares = (album.shares ?? [])
@ -56,12 +56,12 @@ class ShareAlbumWithUser {
Account account,
Album album,
CiString shareWith, {
ErrorWithValueHandler<File>? onShareFileFailed,
ErrorWithValueHandler<FileDescriptor>? onShareFileFailed,
}) async {
final files = AlbumStaticProvider.of(album)
.items
.whereType<AlbumFileItem>()
.where((item) => item.file.ownerId != shareWith)
.where((item) => item.ownerId != shareWith)
.map((e) => e.file);
try {
await CreateUserShare(shareRepo)(
@ -74,12 +74,12 @@ class ShareAlbumWithUser {
onShareFileFailed?.call(album.albumFile!, e, stackTrace);
}
for (final f in files) {
_log.info("[_createFileShares] Sharing '${f.path}' with '$shareWith'");
_log.info("[_createFileShares] Sharing '${f.fdPath}' with '$shareWith'");
try {
await CreateUserShare(shareRepo)(account, f, shareWith.raw);
} catch (e, stackTrace) {
_log.severe(
"[_createFileShares] Failed sharing file '${logFilename(f.path)}' with '$shareWith'",
"[_createFileShares] Failed sharing file '${logFilename(f.fdPath)}' with '$shareWith'",
e,
stackTrace);
onShareFileFailed?.call(f, e, stackTrace);

View file

@ -21,7 +21,6 @@ part 'unshare_album_with_user.g.dart';
class UnshareAlbumWithUser {
UnshareAlbumWithUser(this._c)
: assert(require(_c)),
assert(ListShare.require(_c)),
assert(UnshareFileFromAlbum.require(_c));
static bool require(DiContainer c) =>

View file

@ -5,7 +5,6 @@ import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/entity/share.dart';
import 'package:nc_photos/stream_extension.dart';
@ -22,8 +21,7 @@ part 'unshare_file_from_album.g.dart';
class UnshareFileFromAlbum {
UnshareFileFromAlbum(this._c)
: assert(require(_c)),
assert(ListAlbum.require(_c)),
assert(ListShare.require(_c));
assert(ListAlbum.require(_c));
static bool require(DiContainer c) => DiContainer.has(c, DiType.shareRepo);
@ -34,7 +32,7 @@ class UnshareFileFromAlbum {
Future<void> call(
Account account,
Album album,
List<File> files,
List<FileDescriptor> files,
List<CiString> unshareWith, {
ErrorWithValueHandler<Share>? onUnshareFileFailed,
}) async {
@ -57,7 +55,7 @@ class UnshareFileFromAlbum {
exclusiveShares.addAll(
shares.where((element) => unshareWith.contains(element.shareWith)));
} catch (e, stackTrace) {
_log.severe("[call] Failed while ListShare: '${logFilename(f.path)}'",
_log.severe("[call] Failed while ListShare: '${logFilename(f.fdPath)}'",
e, stackTrace);
}
}
@ -77,7 +75,7 @@ class UnshareFileFromAlbum {
// remove files shared as part of this other shared album
exclusiveShares.removeWhere((s) =>
sharesOfInterest.any((i) => i.userId == s.shareWith) &&
albumFiles.any((f) => f.fileId == s.itemSource));
albumFiles.any((f) => f.fdId == s.itemSource));
}
_log.fine("[call] Post-filter shares: $exclusiveShares");

View file

@ -45,7 +45,7 @@ class RemoveFromNcAlbum {
var count = fileItems.length;
await Remove(_c)(
account,
fileItems.map((e) => e.file.toFile()).toList(),
fileItems.map((e) => e.file).toList(),
onError: (i, f, e, stackTrace) {
--count;
try {

View file

@ -55,6 +55,7 @@ class PopulateAlbum {
addedBy: account.userId,
addedAt: clock.now(),
file: f,
ownerId: f.ownerId ?? account.userId,
)));
}
}
@ -72,6 +73,7 @@ class PopulateAlbum {
addedBy: account.userId,
addedAt: clock.now(),
file: f,
ownerId: f.ownerId ?? account.userId,
)));
return products;
}

View file

@ -14,8 +14,7 @@ import 'package:nc_photos/use_case/resync_album.dart';
class PreProcessAlbum {
PreProcessAlbum(this._c)
: assert(require(_c)),
assert(PopulateAlbum.require(_c)),
assert(ResyncAlbum.require(_c));
assert(PopulateAlbum.require(_c));
static bool require(DiContainer c) => true;

View file

@ -7,7 +7,6 @@ import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/event/event.dart';
import 'package:nc_photos/stream_extension.dart';
@ -61,7 +60,7 @@ class Remove {
Account account, List<FileDescriptor> removes) async {
final albums = await ListAlbum(_c)(account).whereType<Album>().toList();
// figure out which files need to be unshared with whom
final unshares = <FileServerIdentityComparator, Set<CiString>>{};
final unshares = <FileDescriptorServerIdentityComparator, Set<CiString>>{};
// clean up only make sense for static albums
for (final a in albums.where((a) => a.provider is AlbumStaticProvider)) {
try {
@ -69,22 +68,21 @@ class Remove {
final itemsToRemove = provider.items
.whereType<AlbumFileItem>()
.where((i) =>
(i.file.isOwned(account.userId) ||
i.addedBy == account.userId) &&
(i.ownerId == account.userId || i.addedBy == account.userId) &&
removes.any((r) => r.compareServerIdentity(i.file)))
.toList();
if (itemsToRemove.isEmpty) {
continue;
}
for (final i in itemsToRemove) {
final key = FileServerIdentityComparator(i.file);
final key = FileDescriptorServerIdentityComparator(i.file);
final value = (a.shares?.map((s) => s.userId).toList() ?? [])
..add(a.albumFile!.ownerId!)
..remove(account.userId);
(unshares[key] ??= <CiString>{}).addAll(value);
}
_log.fine(
"[_cleanUpAlbums] Removing from album '${a.name}': ${itemsToRemove.map((e) => e.file.path).toReadableString()}");
"[_cleanUpAlbums] Removing from album '${a.name}': ${itemsToRemove.map((e) => e.file.fdPath).toReadableString()}");
// skip unsharing as we'll handle it ourselves
await RemoveFromAlbum(_c)(account, a, itemsToRemove,
shouldUnshare: false);

View file

@ -6,7 +6,7 @@ import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/use_case/find_file.dart';
import 'package:nc_photos/use_case/find_file_descriptor.dart';
import 'package:np_codegen/np_codegen.dart';
part 'resync_album.g.dart';
@ -14,9 +14,7 @@ part 'resync_album.g.dart';
/// Resync files inside an album with the file db
@npLog
class ResyncAlbum {
ResyncAlbum(this._c) : assert(require(_c));
static bool require(DiContainer c) => true;
const ResyncAlbum(this._c);
Future<List<AlbumItem>> call(Account account, Album album) async {
_log.info("[call] Resync album: ${album.name}");
@ -26,11 +24,11 @@ class ResyncAlbum {
}
final items = AlbumStaticProvider.of(album).items;
final files = await FindFile(_c)(
final files = await FindFileDescriptor(_c)(
account,
items
.whereType<AlbumFileItem>()
.map((i) => i.file.fileId)
.map((i) => i.file.fdId)
.whereNotNull()
.toList(),
onFileNotFound: (_) {},
@ -40,19 +38,18 @@ class ResyncAlbum {
return items.map((i) {
if (i is AlbumFileItem) {
try {
if (i.file.fileId! == nextFile?.fileId) {
final newItem = i.copyWith(
file: nextFile,
);
if (i.file.fdId == nextFile?.fdId) {
final newItem = i.copyWith(file: nextFile);
nextFile = fileIt.moveNext() ? fileIt.current : null;
return newItem;
} else {
_log.warning("[call] File not found: ${logFilename(i.file.path)}");
_log.warning(
"[call] File not found: ${logFilename(i.file.fdPath)}");
return i;
}
} catch (e, stackTrace) {
_log.shout(
"[call] Failed syncing file in album: ${logFilename(i.file.path)}",
"[call] Failed syncing file in album: ${logFilename(i.file.fdPath)}",
e,
stackTrace);
return i;

View file

@ -1,11 +1,9 @@
import 'package:nc_photos/account.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/exception.dart';
class UpdateAlbum {
UpdateAlbum(this.albumRepo);
const UpdateAlbum(this.albumRepo);
Future<void> call(Account account, Album album) async {
if (album.savedVersion > Album.version) {
@ -13,23 +11,7 @@ class UpdateAlbum {
throw AlbumDowngradeException(
"Not allowed to downgrade album '${album.name}'");
}
final provider = album.provider;
if (provider is AlbumStaticProvider) {
await albumRepo.update(
account,
album.copyWith(
provider: provider.copyWith(
items: _minimizeItems(provider.items),
),
),
);
} else {
await albumRepo.update(account, album);
}
}
List<AlbumItem> _minimizeItems(List<AlbumItem> items) {
return items.map((e) => e is AlbumFileItem ? e.minimize() : e).toList();
await albumRepo.update(account, album);
}
final AlbumRepo albumRepo;

View file

@ -2,7 +2,6 @@ import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/entity/album/sort_provider.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:np_common/or_null.dart';
@ -49,7 +48,7 @@ class UpdateAlbumTime {
.map((e) => e.file)
.where((element) => file_util.isSupportedFormat(element))
.first;
latestItemTime = latestFile.bestDateTime;
latestItemTime = latestFile.fdDateTime;
} catch (_) {
latestItemTime = null;
}

View file

@ -45,8 +45,8 @@ class UpdateAutoAlbumCover {
final coverFile = sortedItems
.whereType<AlbumFileItem>()
.map((e) => e.file)
.where((element) => file_util.isSupportedFormat(element))
.firstWhere((element) => element.hasPreview ?? false);
.where(file_util.isSupportedFormat)
.first;
// cache the result for later use
if ((album.coverProvider as AlbumAutoCoverProvider)
.coverFile

View file

@ -7,7 +7,6 @@ import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc/list_album_share_outlier.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/file.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/share.dart';
@ -190,7 +189,7 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
Widget _buildMissingShareeItem(
BuildContext context, _MissingShareeItem item) {
final Widget trailing;
switch (_getItemStatus(item.file.path, item.shareWith)) {
switch (_getItemStatus(item.file.fdPath, item.shareWith)) {
case null:
trailing = _buildFixButton(
onPressed: () {
@ -223,7 +222,7 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
Widget _buildExtraShareItem(BuildContext context, _ExtraShareItem item) {
final Widget trailing;
switch (_getItemStatus(item.file.path, item.share.shareWith!)) {
switch (_getItemStatus(item.file.fdPath, item.share.shareWith!)) {
case null:
trailing = _buildFixButton(
onPressed: () {
@ -254,7 +253,7 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
);
}
Widget _buildFileThumbnail(File file) {
Widget _buildFileThumbnail(FileDescriptor file) {
if (file_util.isAlbumFile(widget.account, file)) {
return const SizedBox.square(
dimension: 56,
@ -270,8 +269,8 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
}
}
String _buildFilename(File file) {
if (widget.album.albumFile?.path.equalsIgnoreCase(file.path) == true) {
String _buildFilename(FileDescriptor file) {
if (widget.album.albumFile?.path.equalsIgnoreCase(file.fdPath) == true) {
return widget.album.name;
} else {
return file.filename;
@ -326,9 +325,9 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
// select only items that are not fixed/being fixed
final items = _items.where((i) {
if (i is _MissingShareeItem) {
return _getItemStatus(i.file.path, i.shareWith) == null;
return _getItemStatus(i.file.fdPath, i.shareWith) == null;
} else if (i is _ExtraShareItem) {
return _getItemStatus(i.file.path, i.share.shareWith!) == null;
return _getItemStatus(i.file.fdPath, i.share.shareWith!) == null;
} else {
// ?
return false;
@ -337,10 +336,10 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
setState(() {
for (final i in items) {
if (i is _MissingShareeItem) {
_setItemStatus(i.file.path, i.shareWith, _ItemStatus.processing);
_setItemStatus(i.file.fdPath, i.shareWith, _ItemStatus.processing);
} else if (i is _ExtraShareItem) {
_setItemStatus(
i.file.path, i.share.shareWith!, _ItemStatus.processing);
i.file.fdPath, i.share.shareWith!, _ItemStatus.processing);
}
}
});
@ -372,14 +371,14 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
Future<void> _fixMissingSharee(_MissingShareeItem item) async {
final shareRepo = ShareRepo(ShareRemoteDataSource());
setState(() {
_setItemStatus(item.file.path, item.shareWith, _ItemStatus.processing);
_setItemStatus(item.file.fdPath, item.shareWith, _ItemStatus.processing);
});
try {
await CreateUserShare(shareRepo)(
widget.account, item.file, item.shareWith.raw);
if (mounted) {
setState(() {
_setItemStatus(item.file.path, item.shareWith, _ItemStatus.fixed);
_setItemStatus(item.file.fdPath, item.shareWith, _ItemStatus.fixed);
});
}
} catch (e, stackTrace) {
@ -391,7 +390,7 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
));
if (mounted) {
setState(() {
_removeItemStatus(item.file.path, item.shareWith);
_removeItemStatus(item.file.fdPath, item.shareWith);
});
}
}
@ -401,14 +400,14 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
final shareRepo = ShareRepo(ShareRemoteDataSource());
setState(() {
_setItemStatus(
item.file.path, item.share.shareWith!, _ItemStatus.processing);
item.file.fdPath, item.share.shareWith!, _ItemStatus.processing);
});
try {
await RemoveShare(shareRepo)(widget.account, item.share);
if (mounted) {
setState(() {
_setItemStatus(
item.file.path, item.share.shareWith!, _ItemStatus.fixed);
item.file.fdPath, item.share.shareWith!, _ItemStatus.fixed);
});
}
} catch (e, stackTrace) {
@ -419,7 +418,7 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
));
if (mounted) {
setState(() {
_removeItemStatus(item.file.path, item.share.shareWith!);
_removeItemStatus(item.file.fdPath, item.share.shareWith!);
});
}
}
@ -466,7 +465,7 @@ abstract class _ListItem {
class _ExtraShareItem extends _ListItem {
const _ExtraShareItem(this.file, this.share);
final File file;
final FileDescriptor file;
final Share share;
}
@ -474,7 +473,7 @@ class _MissingShareeItem extends _ListItem {
const _MissingShareeItem(
this.file, this.shareWith, this.shareWithDisplayName);
final File file;
final FileDescriptor file;
final CiString shareWith;
final String? shareWithDisplayName;
}

View file

@ -178,7 +178,8 @@ Future<void> _dbUpdateExisting() async {
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2021, 2, 3, 4, 5, 6),
file: files[1],
file: files[1].toDescriptor(),
ownerId: "admin".toCi(),
),
],
),
@ -258,7 +259,8 @@ Future<void> _dbUpdateShares() async {
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2021, 2, 3, 4, 5, 6),
file: files[1],
file: files[1].toDescriptor(),
ownerId: "admin".toCi(),
),
],
),
@ -320,7 +322,8 @@ Future<void> _dbUpdateDeleteShares() async {
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2021, 2, 3, 4, 5, 6),
file: files[1],
file: files[1].toDescriptor(),
ownerId: "admin".toCi(),
),
],
),

View file

@ -16,6 +16,7 @@ import 'package:test/test.dart';
import '../test_util.dart' as util;
part 'album_test/album_upgrader_v8.dart';
part 'album_test/album_upgrader_v9.dart';
void main() {
group("Album", () {
@ -93,74 +94,7 @@ void main() {
});
group("AlbumStaticProvider", () {
test("AlbumFileItem", () {
final json = <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
},
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test2.jpg",
},
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
};
expect(
Album.fromJson(
json,
upgraderFactory: const _NullAlbumUpgraderFactory(),
),
Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
),
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
),
],
),
coverProvider: const AlbumAutoCoverProvider(),
sortProvider: const AlbumNullSortProvider(),
));
});
test("AlbumFileItem", _fromJsonStaticProviderFileItem);
test("AlbumLabelItem", () {
final json = <String, dynamic>{
"version": Album.version,
@ -455,69 +389,7 @@ void main() {
});
group("AlbumStaticProvider", () {
test("AlbumFileItem", () {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
),
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
),
],
),
coverProvider: const AlbumAutoCoverProvider(),
sortProvider: const AlbumNullSortProvider(),
);
expect(album.toRemoteJson(), <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
},
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test2.jpg",
},
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
});
});
test("AlbumFileItem", _toRemoteJsonStaticProviderFileItem);
test("AlbumLabelItem", () {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
@ -751,69 +623,7 @@ void main() {
});
group("AlbumStaticProvider", () {
test("AlbumFileItem", () {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
),
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
),
],
),
coverProvider: const AlbumAutoCoverProvider(),
sortProvider: const AlbumNullSortProvider(),
);
expect(album.toAppDbJson(), <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
},
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test2.jpg",
},
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
});
});
test("AlbumFileItem", _toDbJsonStaticProviderFileItem);
test("AlbumLabelItem", () {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
@ -1829,6 +1639,304 @@ void main() {
});
});
});
group("AlbumUpgraderV9", () {
group("doJson", () {
test("non static provider", _upgradeV9JsonNonStatic);
group("static provider", () {
test("normal", _upgradeV9JsonStaticNormal);
test("w/o ownerId", _upgradeV9JsonStaticNoOwnerId);
test("other ownerId", _upgradeV9JsonStaticOtherOwnerId);
});
});
group("doDb", () {
test("non static provider", _upgradeV9DbNonStatic);
group("static provider", () {
test("normal", _upgradeV9DbStaticNormal);
test("w/o ownerId", _upgradeV9DbStaticNoOwnerId);
test("other ownerId", _upgradeV9DbStaticOtherOwnerId);
});
});
});
});
}
void _fromJsonStaticProviderFileItem() {
final json = <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test1.jpg",
"fdId": 1,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-02T03:04:05.678901Z",
},
"ownerId": "admin",
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test2.jpg",
"fdId": 2,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-01T03:04:05.678901Z",
},
"ownerId": "admin",
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
};
expect(
Album.fromJson(
json,
upgraderFactory: const _NullAlbumUpgraderFactory(),
),
Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: FileDescriptor(
fdPath: "remote.php/dav/files/admin/test1.jpg",
fdId: 1,
fdMime: "image/jpeg",
fdIsArchived: false,
fdIsFavorite: false,
fdDateTime: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
),
ownerId: "admin".toCi(),
),
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: FileDescriptor(
fdPath: "remote.php/dav/files/admin/test2.jpg",
fdId: 2,
fdMime: "image/jpeg",
fdIsArchived: false,
fdIsFavorite: false,
fdDateTime: DateTime.utc(2020, 1, 1, 3, 4, 5, 678, 901),
),
ownerId: "admin".toCi(),
),
],
),
coverProvider: const AlbumAutoCoverProvider(),
sortProvider: const AlbumNullSortProvider(),
),
);
}
void _toRemoteJsonStaticProviderFileItem() {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: FileDescriptor(
fdPath: "remote.php/dav/files/admin/test1.jpg",
fdId: 1,
fdMime: "image/jpeg",
fdIsArchived: false,
fdIsFavorite: false,
fdDateTime: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
),
ownerId: "admin".toCi(),
),
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: FileDescriptor(
fdPath: "remote.php/dav/files/admin/test2.jpg",
fdId: 2,
fdMime: "image/jpeg",
fdIsArchived: false,
fdIsFavorite: false,
fdDateTime: DateTime.utc(2020, 1, 1, 3, 4, 5, 678, 901),
),
ownerId: "admin".toCi(),
),
],
),
coverProvider: const AlbumAutoCoverProvider(),
sortProvider: const AlbumNullSortProvider(),
);
expect(album.toRemoteJson(), <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test1.jpg",
"fdId": 1,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-02T03:04:05.678901Z",
},
"ownerId": "admin",
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test2.jpg",
"fdId": 2,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-01T03:04:05.678901Z",
},
"ownerId": "admin",
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
});
}
void _toDbJsonStaticProviderFileItem() {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: FileDescriptor(
fdPath: "remote.php/dav/files/admin/test1.jpg",
fdId: 1,
fdMime: "image/jpeg",
fdIsArchived: false,
fdIsFavorite: false,
fdDateTime: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
),
ownerId: "admin".toCi(),
),
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
file: FileDescriptor(
fdPath: "remote.php/dav/files/admin/test2.jpg",
fdId: 2,
fdMime: "image/jpeg",
fdIsArchived: false,
fdIsFavorite: false,
fdDateTime: DateTime.utc(2020, 1, 1, 3, 4, 5, 678, 901),
),
ownerId: "admin".toCi(),
),
],
),
coverProvider: const AlbumAutoCoverProvider(),
sortProvider: const AlbumNullSortProvider(),
);
expect(album.toAppDbJson(), <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test1.jpg",
"fdId": 1,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-02T03:04:05.678901Z",
},
"ownerId": "admin",
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test2.jpg",
"fdId": 2,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-01T03:04:05.678901Z",
},
"ownerId": "admin",
},
"addedBy": "admin",
"addedAt": "2020-01-02T03:04:05.678901Z",
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
});
}
@ -1958,19 +2066,21 @@ class _NullAlbumUpgraderFactory extends AlbumUpgraderFactory {
const _NullAlbumUpgraderFactory();
@override
buildV1() => null;
AlbumUpgraderV1? buildV1() => null;
@override
buildV2() => null;
AlbumUpgraderV2? buildV2() => null;
@override
buildV3() => null;
AlbumUpgraderV3? buildV3() => null;
@override
buildV4() => null;
AlbumUpgraderV4? buildV4() => null;
@override
buildV5() => null;
AlbumUpgraderV5? buildV5() => null;
@override
buildV6() => null;
AlbumUpgraderV6? buildV6() => null;
@override
buildV7() => null;
AlbumUpgraderV7? buildV7() => null;
@override
AlbumUpgraderV8? buildV8() => null;
@override
AlbumUpgraderV9? buildV9() => null;
}

View file

@ -0,0 +1,498 @@
part of '../album_test.dart';
void _upgradeV9JsonNonStatic() {
final account = util.buildAccount();
final json = <String, dynamic>{
"version": 9,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"provider": <String, dynamic>{
"type": "tag",
"content": <String, dynamic>{
"tags": [],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
"albumFile": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.json",
},
};
expect(AlbumUpgraderV9(account: account).doJson(json), json);
}
void _upgradeV9JsonStaticNormal() {
final account = util.buildAccount();
final json = <String, dynamic>{
"version": 9,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
"fileId": 1,
"contentLength": 12345,
"contentType": "image/jpeg",
"lastModified": "2020-01-02T03:04:05.678901Z",
"ownerId": "admin",
},
},
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
"albumFile": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.json",
},
};
expect(
AlbumUpgraderV9(account: account).doJson(json),
<String, dynamic>{
"version": 9,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test1.jpg",
"fdId": 1,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-02T03:04:05.678901Z",
},
"ownerId": "admin",
},
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
"albumFile": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.json",
},
},
);
}
void _upgradeV9JsonStaticNoOwnerId() {
final account = util.buildAccount();
final json = <String, dynamic>{
"version": 9,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
"fileId": 1,
"contentLength": 12345,
"contentType": "image/jpeg",
"lastModified": "2020-01-02T03:04:05.678901Z",
},
},
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
"albumFile": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.json",
},
};
expect(
AlbumUpgraderV9(account: account).doJson(json),
<String, dynamic>{
"version": 9,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test1.jpg",
"fdId": 1,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-02T03:04:05.678901Z",
},
"ownerId": "admin",
},
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
"albumFile": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.json",
},
},
);
}
void _upgradeV9JsonStaticOtherOwnerId() {
final account = util.buildAccount();
final json = <String, dynamic>{
"version": 9,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
"fileId": 1,
"contentLength": 12345,
"contentType": "image/jpeg",
"lastModified": "2020-01-02T03:04:05.678901Z",
"ownerId": "user1",
},
},
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
"albumFile": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.json",
},
};
expect(
AlbumUpgraderV9(account: account).doJson(json),
<String, dynamic>{
"version": 9,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test1.jpg",
"fdId": 1,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-02T03:04:05.678901Z",
},
"ownerId": "user1",
},
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
"albumFile": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.json",
},
},
);
}
void _upgradeV9DbNonStatic() {
final account = util.buildAccount();
final dbObj = DbAlbum(
fileId: 1,
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
version: 9,
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
name: "test1",
providerType: "tag",
providerContent: {"tags": []},
coverProviderType: "auto",
coverProviderContent: {},
sortProviderType: "null",
sortProviderContent: {},
shares: [],
);
expect(
AlbumUpgraderV9(account: account).doDb(dbObj),
DbAlbum(
fileId: 1,
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
version: 9,
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
name: "test1",
providerType: "tag",
providerContent: {"tags": []},
coverProviderType: "auto",
coverProviderContent: {},
sortProviderType: "null",
sortProviderContent: {},
shares: [],
),
);
}
void _upgradeV9DbStaticNormal() {
final account = util.buildAccount();
final dbObj = DbAlbum(
fileId: 1,
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
version: 9,
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
name: "test1",
providerType: "static",
providerContent: {
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
"fileId": 2,
"contentLength": 12345,
"contentType": "image/jpeg",
"lastModified": "2020-01-02T03:04:05.678901Z",
"ownerId": "admin",
},
},
},
],
},
coverProviderType: "auto",
coverProviderContent: {},
sortProviderType: "null",
sortProviderContent: {},
shares: [],
);
expect(
AlbumUpgraderV9(account: account).doDb(dbObj),
DbAlbum(
fileId: 1,
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
version: 9,
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
name: "test1",
providerType: "static",
providerContent: {
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test1.jpg",
"fdId": 2,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-02T03:04:05.678901Z",
},
"ownerId": "admin",
},
},
]
},
coverProviderType: "auto",
coverProviderContent: {},
sortProviderType: "null",
sortProviderContent: {},
shares: [],
),
);
}
void _upgradeV9DbStaticNoOwnerId() {
final account = util.buildAccount();
final dbObj = DbAlbum(
fileId: 1,
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
version: 9,
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
name: "test1",
providerType: "static",
providerContent: {
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
"fileId": 2,
"contentLength": 12345,
"contentType": "image/jpeg",
"lastModified": "2020-01-02T03:04:05.678901Z",
},
},
},
],
},
coverProviderType: "auto",
coverProviderContent: {},
sortProviderType: "null",
sortProviderContent: {},
shares: [],
);
expect(
AlbumUpgraderV9(account: account).doDb(dbObj),
DbAlbum(
fileId: 1,
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
version: 9,
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
name: "test1",
providerType: "static",
providerContent: {
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test1.jpg",
"fdId": 2,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-02T03:04:05.678901Z",
},
"ownerId": "admin",
},
},
]
},
coverProviderType: "auto",
coverProviderContent: {},
sortProviderType: "null",
sortProviderContent: {},
shares: [],
),
);
}
void _upgradeV9DbStaticOtherOwnerId() {
final account = util.buildAccount();
final dbObj = DbAlbum(
fileId: 1,
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
version: 9,
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
name: "test1",
providerType: "static",
providerContent: {
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
"fileId": 2,
"contentLength": 12345,
"contentType": "image/jpeg",
"lastModified": "2020-01-02T03:04:05.678901Z",
"ownerId": "user1",
},
},
},
],
},
coverProviderType: "auto",
coverProviderContent: {},
sortProviderType: "null",
sortProviderContent: {},
shares: [],
);
expect(
AlbumUpgraderV9(account: account).doDb(dbObj),
DbAlbum(
fileId: 1,
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
version: 9,
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
name: "test1",
providerType: "static",
providerContent: {
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"fdPath": "remote.php/dav/files/admin/test1.jpg",
"fdId": 2,
"fdMime": "image/jpeg",
"fdIsArchived": false,
"fdIsFavorite": false,
"fdDateTime": "2020-01-02T03:04:05.678901Z",
},
"ownerId": "user1",
},
},
]
},
coverProviderType: "auto",
coverProviderContent: {},
sortProviderType: "null",
sortProviderContent: {},
shares: [],
),
);
}

View file

@ -57,13 +57,13 @@ class MockAlbumMemoryRepo extends MockAlbumRepo {
]) : albums = initialData.map((a) => a.copyWith()).toList();
@override
get(Account account, File albumFile) async {
Future<Album> get(Account account, File albumFile) async {
return albums.firstWhere((element) =>
element.albumFile?.compareServerIdentity(albumFile) == true);
}
@override
getAll(Account account, List<File> albumFiles) async* {
Stream<dynamic> getAll(Account account, List<File> albumFiles) async* {
final results = await waitOr(
albumFiles.map((f) => get(account, f)),
(error, stackTrace) => ExceptionEvent(error, stackTrace),
@ -74,7 +74,7 @@ class MockAlbumMemoryRepo extends MockAlbumRepo {
}
@override
update(Account account, Album album) async {
Future<void> update(Account account, Album album) async {
final i = albums.indexWhere((element) =>
element.albumFile?.compareServerIdentity(album.albumFile!) == true);
albums[i] = album;
@ -373,7 +373,7 @@ class MockFileMemoryRepo2 extends BasicFileRepo {
/// Mock of [ShareRepo] where all methods will throw UnimplementedError
class MockShareRepo implements ShareRepo {
@override
Future<Share> create(Account account, File file, String shareWith) {
Future<Share> create(Account account, FileDescriptor file, String shareWith) {
throw UnimplementedError();
}
@ -393,7 +393,7 @@ class MockShareRepo implements ShareRepo {
@override
Future<List<Share>> list(
Account account,
File file, {
FileDescriptor file, {
bool? isIncludeReshare,
}) {
throw UnimplementedError();
@ -429,13 +429,13 @@ class MockShareMemoryRepo extends MockShareRepo {
}
@override
list(
Future<List<Share>> list(
Account account,
File file, {
FileDescriptor file, {
bool? isIncludeReshare,
}) async {
return shares.where((s) {
if (s.itemSource != file.fileId) {
if (s.itemSource != file.fdId) {
return false;
} else if (isIncludeReshare == true || s.uidOwner == account.userId) {
return true;
@ -446,18 +446,19 @@ class MockShareMemoryRepo extends MockShareRepo {
}
@override
create(Account account, File file, String shareWith) async {
Future<Share> create(
Account account, FileDescriptor file, String shareWith) async {
final share = Share(
id: (_id++).toString(),
shareType: ShareType.user,
stime: DateTime.utc(2020, 1, 2, 3, 4, 5),
uidOwner: account.userId,
displaynameOwner: account.username2,
uidFileOwner: file.ownerId!,
uidFileOwner: account.userId,
path: file.strippedPath,
itemType: ShareItemType.file,
mimeType: file.contentType ?? "",
itemSource: file.fileId!,
mimeType: file.fdMime ?? "",
itemSource: file.fdId,
shareWith: shareWith.toCi(),
shareWithDisplayName: shareWith,
);

View file

@ -201,8 +201,7 @@ class AlbumBuilder {
Album build() {
final latestFileItem = items
.whereType<AlbumFileItem>()
.stableSorted(
(a, b) => a.file.lastModified!.compareTo(b.file.lastModified!))
.stableSorted((a, b) => a.file.fdDateTime.compareTo(b.file.fdDateTime))
.reversed
.firstOrNull;
return Album(
@ -210,7 +209,7 @@ class AlbumBuilder {
name: name,
provider: AlbumStaticProvider(
items: items,
latestItemTime: latestFileItem?.file.lastModified,
latestItemTime: latestFileItem?.file.fdDateTime,
),
coverProvider: cover == null
? AlbumAutoCoverProvider(coverFile: latestFileItem?.file)
@ -233,15 +232,17 @@ class AlbumBuilder {
/// If [isCover] is true, the coverProvider of the album will become
/// [AlbumManualCoverProvider]
void addFileItem(
File file, {
FileDescriptor file, {
String addedBy = "admin",
DateTime? addedAt,
bool isCover = false,
String? ownerId,
}) {
final fileItem = AlbumFileItem(
file: file,
addedBy: addedBy.toCi(),
addedAt: addedAt ?? file.lastModified!,
addedAt: addedAt ?? file.fdDateTime,
ownerId: ownerId?.toCi() ?? file.as<File>()?.ownerId ?? addedBy.toCi(),
);
items.add(fileItem);
if (isCover) {
@ -272,7 +273,7 @@ class AlbumBuilder {
final String ownerId;
final items = <AlbumItem>[];
File? cover;
FileDescriptor? cover;
final shares = <AlbumShare>[];
}

View file

@ -76,14 +76,13 @@ Future<void> _addFile() async {
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5),
file: file,
).minimize(),
file: file.toDescriptor(),
ownerId: "admin".toCi(),
),
],
latestItemTime: DateTime.utc(2020, 1, 2, 3, 4, 5),
),
coverProvider: AlbumAutoCoverProvider(
coverFile: file,
),
coverProvider: AlbumAutoCoverProvider(coverFile: file.toDescriptor()),
sortProvider: const AlbumNullSortProvider(),
albumFile: albumFile,
),
@ -104,13 +103,13 @@ Future<void> _addExistingFile() async {
lastModified: DateTime.utc(2019, 1, 2, 3, 4, 5),
))
.build();
final oldFile = files[0].toDescriptor();
final album = (util.AlbumBuilder()
..addFileItem(
files[0],
oldFile,
addedAt: clock.now().toUtc(),
))
.build();
final oldFile = files[0];
final newFile = files[0].copyWith();
final albumFile = album.albumFile!;
final c = DiContainer(
@ -142,12 +141,15 @@ Future<void> _addExistingFile() async {
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5),
file: files[0],
file: files[0].toDescriptor(),
ownerId: "admin".toCi(),
),
],
latestItemTime: DateTime.utc(2019, 1, 2, 3, 4, 5),
),
coverProvider: AlbumAutoCoverProvider(coverFile: files[0]),
coverProvider: AlbumAutoCoverProvider(
coverFile: files[0].toDescriptor(),
),
sortProvider: const AlbumNullSortProvider(),
albumFile: albumFile,
),
@ -156,23 +158,27 @@ Future<void> _addExistingFile() async {
// when there's a conflict, it's guaranteed that the original file in the
// album is kept and the incoming file dropped
expect(
identical(
AlbumStaticProvider.of(c.albumMemoryRepo.albums[0])
.items
.whereType<AlbumFileItem>()
.first
.file,
oldFile),
true);
identical(
AlbumStaticProvider.of(c.albumMemoryRepo.albums[0])
.items
.whereType<AlbumFileItem>()
.first
.file,
oldFile,
),
true,
);
expect(
identical(
AlbumStaticProvider.of(c.albumMemoryRepo.albums[0])
.items
.whereType<AlbumFileItem>()
.first
.file,
newFile),
false);
identical(
AlbumStaticProvider.of(c.albumMemoryRepo.albums[0])
.items
.whereType<AlbumFileItem>()
.first
.file,
newFile,
),
false,
);
});
}
@ -189,7 +195,8 @@ Future<void> _addExistingSharedFile() async {
final user1Files = [
files[0].copyWith(path: "remote.php/dav/files/user1/test1.jpg")
];
final album = (util.AlbumBuilder()..addFileItem(files[0])).build();
final album =
(util.AlbumBuilder()..addFileItem(files[0].toDescriptor())).build();
final albumFile = album.albumFile!;
final c = DiContainer(
fileRepo: MockFileMemoryRepo(),
@ -222,12 +229,15 @@ Future<void> _addExistingSharedFile() async {
AlbumFileItem(
addedBy: "admin".toCi(),
addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5),
file: files[0],
file: files[0].toDescriptor(),
ownerId: "admin".toCi(),
),
],
latestItemTime: DateTime.utc(2020, 1, 2, 3, 4, 5),
),
coverProvider: AlbumAutoCoverProvider(coverFile: files[0]),
coverProvider: AlbumAutoCoverProvider(
coverFile: files[0].toDescriptor(),
),
sortProvider: const AlbumNullSortProvider(),
albumFile: albumFile,
),

View file

@ -6,6 +6,7 @@ import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/cover_provider.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/entity/album/sort_provider.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/use_case/album/remove_from_album.dart';
import 'package:np_common/or_null.dart';
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
@ -45,7 +46,8 @@ Future<void> _removeLastFile() async {
final account = util.buildAccount();
final files =
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
final album = (util.AlbumBuilder()..addFileItem(files[0])).build();
final album =
(util.AlbumBuilder()..addFileItem(files[0].toDescriptor())).build();
final file1 = files[0];
final fileItem1 = util.AlbumBuilder.fileItemsOf(album)[0];
final albumFile = album.albumFile!;
@ -94,9 +96,9 @@ Future<void> _remove1OfNFiles() async {
..addJpeg("admin/test3.jpg"))
.build();
final album = (util.AlbumBuilder()
..addFileItem(files[0])
..addFileItem(files[1])
..addFileItem(files[2]))
..addFileItem(files[0].toDescriptor())
..addFileItem(files[1].toDescriptor())
..addFileItem(files[2].toDescriptor()))
.build();
final fileItems = util.AlbumBuilder.fileItemsOf(album);
final albumFile = album.albumFile!;
@ -126,10 +128,12 @@ Future<void> _remove1OfNFiles() async {
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
name: "test",
provider: AlbumStaticProvider(
items: [fileItems[1].minimize(), fileItems[2].minimize()],
items: [fileItems[1], fileItems[2]],
latestItemTime: files[2].lastModified,
),
coverProvider: AlbumAutoCoverProvider(coverFile: files[2]),
coverProvider: AlbumAutoCoverProvider(
coverFile: files[2].toDescriptor(),
),
sortProvider: const AlbumNullSortProvider(),
albumFile: albumFile,
),
@ -151,9 +155,9 @@ Future<void> _removeLatestOfNFiles() async {
lastModified: DateTime.utc(2020, 1, 2, 3, 4, 6)))
.build();
final album = (util.AlbumBuilder()
..addFileItem(files[0])
..addFileItem(files[1])
..addFileItem(files[2]))
..addFileItem(files[0].toDescriptor())
..addFileItem(files[1].toDescriptor())
..addFileItem(files[2].toDescriptor()))
.build();
final fileItems = util.AlbumBuilder.fileItemsOf(album);
final albumFile = album.albumFile!;
@ -183,10 +187,12 @@ Future<void> _removeLatestOfNFiles() async {
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
name: "test",
provider: AlbumStaticProvider(
items: [fileItems[1].minimize(), fileItems[2].minimize()],
items: [fileItems[1], fileItems[2]],
latestItemTime: files[1].lastModified,
),
coverProvider: AlbumAutoCoverProvider(coverFile: files[1]),
coverProvider: AlbumAutoCoverProvider(
coverFile: files[1].toDescriptor(),
),
sortProvider: const AlbumNullSortProvider(),
albumFile: albumFile,
),
@ -205,9 +211,9 @@ Future<void> _removeManualCoverFile() async {
..addJpeg("admin/test3.jpg"))
.build();
final album = (util.AlbumBuilder()
..addFileItem(files[0], isCover: true)
..addFileItem(files[1])
..addFileItem(files[2]))
..addFileItem(files[0].toDescriptor(), isCover: true)
..addFileItem(files[1].toDescriptor())
..addFileItem(files[2].toDescriptor()))
.build();
final fileItems = util.AlbumBuilder.fileItemsOf(album);
final albumFile = album.albumFile!;
@ -237,10 +243,12 @@ Future<void> _removeManualCoverFile() async {
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
name: "test",
provider: AlbumStaticProvider(
items: [fileItems[1].minimize(), fileItems[2].minimize()],
items: [fileItems[1], fileItems[2]],
latestItemTime: files[2].lastModified,
),
coverProvider: AlbumAutoCoverProvider(coverFile: files[2]),
coverProvider: AlbumAutoCoverProvider(
coverFile: files[2].toDescriptor(),
),
sortProvider: const AlbumNullSortProvider(),
albumFile: albumFile,
),
@ -256,7 +264,7 @@ Future<void> _removeFromSharedAlbumOwned() async {
final files =
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
final album = (util.AlbumBuilder()
..addFileItem(files[0])
..addFileItem(files[0].toDescriptor())
..addShare("user1"))
.build();
final file1 = files[0];
@ -297,7 +305,7 @@ Future<void> _removeFromSharedAlbumOwnedWithOtherShare() async {
..addJpeg("user1/test1.jpg", ownerId: "user1"))
.build();
final album = (util.AlbumBuilder()
..addFileItem(files[0], addedBy: "user1")
..addFileItem(files[0].toDescriptor(), addedBy: "user1")
..addShare("user1")
..addShare("user2"))
.build();
@ -349,7 +357,7 @@ Future<void> _removeFromSharedAlbumOwnedLeaveExtraShare() async {
final files =
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
final album = (util.AlbumBuilder()
..addFileItem(files[0])
..addFileItem(files[0].toDescriptor())
..addShare("user1"))
.build();
final file1 = files[0];
@ -392,12 +400,12 @@ Future<void> _removeFromSharedAlbumOwnedFileInOtherAlbum() async {
final files =
(util.FilesBuilder(initialFileId: 2)..addJpeg("admin/test1.jpg")).build();
final album1 = (util.AlbumBuilder()
..addFileItem(files[0])
..addFileItem(files[0].toDescriptor())
..addShare("user1")
..addShare("user2"))
.build();
final album2 = (util.AlbumBuilder.ofId(albumId: 1)
..addFileItem(files[0])
..addFileItem(files[0].toDescriptor())
..addShare("user1"))
.build();
final album1fileItems = util.AlbumBuilder.fileItemsOf(album1);
@ -440,7 +448,7 @@ Future<void> _removeFromSharedAlbumNotOwned() async {
final files =
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
final album = (util.AlbumBuilder(ownerId: "user1")
..addFileItem(files[0])
..addFileItem(files[0].toDescriptor())
..addShare("admin")
..addShare("user2"))
.build();
@ -489,7 +497,7 @@ Future<void> _removeFromSharedAlbumNotOwnedWithOwnerShare() async {
final files =
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
final album = (util.AlbumBuilder(ownerId: "user1")
..addFileItem(files[0])
..addFileItem(files[0].toDescriptor())
..addShare("admin")
..addShare("user2"))
.build();

View file

@ -1,3 +1,4 @@
import 'package:clock/clock.dart';
import 'package:event_bus/event_bus.dart';
import 'package:kiwi/kiwi.dart';
import 'package:nc_photos/use_case/album/share_album_with_user.dart';
@ -97,37 +98,30 @@ Future<void> _shareWithFile() async {
/// Expect: share (admin -> user1) added to album's shares list;
/// new shares (admin -> user1) are created for the album json
Future<void> _shareWithFileOwnedByUser() async {
final account = util.buildAccount();
final files = (util.FilesBuilder(initialFileId: 1)
..addJpeg("admin/test1.jpg", ownerId: "user1"))
.build();
final album = (util.AlbumBuilder()..addFileItem(files[0])).build();
final albumFile = album.albumFile!;
final albumRepo = MockAlbumMemoryRepo([album]);
final shareRepo = MockShareMemoryRepo();
await withClock(Clock.fixed(DateTime.utc(2020, 1, 2, 3, 4, 5)), () async {
final account = util.buildAccount();
final files = (util.FilesBuilder(initialFileId: 1)
..addJpeg("admin/test1.jpg", ownerId: "user1"))
.build();
final album = (util.AlbumBuilder()..addFileItem(files[0])).build();
final albumFile = album.albumFile!;
final albumRepo = MockAlbumMemoryRepo([album]);
final shareRepo = MockShareMemoryRepo();
await ShareAlbumWithUser(shareRepo, albumRepo)(
account,
albumRepo.findAlbumByPath(albumFile.path),
util.buildSharee(shareWith: "user1".toCi()),
);
expect(
albumRepo
.findAlbumByPath(albumFile.path)
.shares
?.map((s) => s.copyWith(
// we need to set a known value to sharedAt
sharedAt: OrNull(DateTime.utc(2020, 1, 2, 3, 4, 5)),
))
.toList(),
[util.buildAlbumShare(userId: "user1")],
);
expect(
shareRepo.shares,
[
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
],
);
await ShareAlbumWithUser(shareRepo, albumRepo)(
account,
albumRepo.findAlbumByPath(albumFile.path),
util.buildSharee(shareWith: "user1".toCi()),
);
expect(
albumRepo.findAlbumByPath(albumFile.path).shares?.toList(),
[util.buildAlbumShare(userId: "user1")],
);
expect(
shareRepo.shares,
[util.buildShare(id: "0", file: albumFile, shareWith: "user1")],
);
});
}
/// Share a shared album (admin -> user1) with a user (user2)