mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-24 18:38:48 +01:00
Remove metadata from album files
This help reduce the file size
This commit is contained in:
parent
e2152a7ebe
commit
f01c64a155
19 changed files with 856 additions and 349 deletions
|
@ -140,6 +140,7 @@ class AppDbAlbumEntry {
|
||||||
upgraderV1: AlbumUpgraderV1(),
|
upgraderV1: AlbumUpgraderV1(),
|
||||||
upgraderV2: AlbumUpgraderV2(),
|
upgraderV2: AlbumUpgraderV2(),
|
||||||
upgraderV3: AlbumUpgraderV3(),
|
upgraderV3: AlbumUpgraderV3(),
|
||||||
|
upgraderV4: AlbumUpgraderV4(),
|
||||||
)!,
|
)!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ class Album with EquatableMixin {
|
||||||
required AlbumUpgraderV1? upgraderV1,
|
required AlbumUpgraderV1? upgraderV1,
|
||||||
required AlbumUpgraderV2? upgraderV2,
|
required AlbumUpgraderV2? upgraderV2,
|
||||||
required AlbumUpgraderV3? upgraderV3,
|
required AlbumUpgraderV3? upgraderV3,
|
||||||
|
required AlbumUpgraderV4? upgraderV4,
|
||||||
}) {
|
}) {
|
||||||
final jsonVersion = json["version"];
|
final jsonVersion = json["version"];
|
||||||
JsonObj? result = json;
|
JsonObj? result = json;
|
||||||
|
@ -68,6 +69,13 @@ class Album with EquatableMixin {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (jsonVersion < 5) {
|
||||||
|
result = upgraderV4?.call(result);
|
||||||
|
if (result == null) {
|
||||||
|
_log.info("[fromJson] Version $jsonVersion not compatible");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
return Album(
|
return Album(
|
||||||
lastUpdated: result["lastUpdated"] == null
|
lastUpdated: result["lastUpdated"] == null
|
||||||
? null
|
? null
|
||||||
|
@ -168,7 +176,7 @@ class Album with EquatableMixin {
|
||||||
final File? albumFile;
|
final File? albumFile;
|
||||||
|
|
||||||
/// versioning of this class, use to upgrade old persisted album
|
/// versioning of this class, use to upgrade old persisted album
|
||||||
static const version = 4;
|
static const version = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumRepo {
|
class AlbumRepo {
|
||||||
|
@ -224,6 +232,7 @@ class AlbumRemoteDataSource implements AlbumDataSource {
|
||||||
upgraderV1: AlbumUpgraderV1(),
|
upgraderV1: AlbumUpgraderV1(),
|
||||||
upgraderV2: AlbumUpgraderV2(),
|
upgraderV2: AlbumUpgraderV2(),
|
||||||
upgraderV3: AlbumUpgraderV3(),
|
upgraderV3: AlbumUpgraderV3(),
|
||||||
|
upgraderV4: AlbumUpgraderV4(),
|
||||||
)!
|
)!
|
||||||
.copyWith(
|
.copyWith(
|
||||||
lastUpdated: OrNull(null),
|
lastUpdated: OrNull(null),
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:equatable/equatable.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/list_extension.dart';
|
import 'package:nc_photos/list_extension.dart';
|
||||||
|
import 'package:nc_photos/or_null.dart';
|
||||||
import 'package:nc_photos/type.dart';
|
import 'package:nc_photos/type.dart';
|
||||||
|
|
||||||
List<AlbumItem> makeDistinctAlbumItems(List<AlbumItem> items) =>
|
List<AlbumItem> makeDistinctAlbumItems(List<AlbumItem> items) =>
|
||||||
|
@ -95,6 +96,10 @@ class AlbumFileItem extends AlbumItem with EquatableMixin {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AlbumFileItem minimize() => AlbumFileItem(
|
||||||
|
file: file.copyWith(metadata: OrNull(null)),
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get props => [
|
get props => [
|
||||||
// file is handled separately, see [equals]
|
// file is handled separately, see [equals]
|
||||||
|
|
|
@ -6,7 +6,6 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/type.dart';
|
import 'package:nc_photos/type.dart';
|
||||||
|
|
||||||
|
@ -58,13 +57,54 @@ abstract class AlbumProvider with EquatableMixin {
|
||||||
static final _log = Logger("entity.album.provider.AlbumProvider");
|
static final _log = Logger("entity.album.provider.AlbumProvider");
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumStaticProvider extends AlbumProvider {
|
abstract class AlbumProviderBase extends AlbumProvider {
|
||||||
|
const AlbumProviderBase({
|
||||||
|
this.latestItemTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString({bool isDeep = false}) {
|
||||||
|
return "$runtimeType {"
|
||||||
|
"latestItemTime: $latestItemTime, "
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
toContentJson() {
|
||||||
|
return {
|
||||||
|
if (latestItemTime != null)
|
||||||
|
"latestItemTime": latestItemTime!.toUtc().toIso8601String(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AlbumProviderBase copyWith({
|
||||||
|
DateTime? latestItemTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
get props => [
|
||||||
|
latestItemTime,
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
final DateTime? latestItemTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlbumStaticProvider extends AlbumProviderBase {
|
||||||
AlbumStaticProvider({
|
AlbumStaticProvider({
|
||||||
|
DateTime? latestItemTime,
|
||||||
required List<AlbumItem> items,
|
required List<AlbumItem> items,
|
||||||
}) : items = UnmodifiableListView(items);
|
}) : items = UnmodifiableListView(items),
|
||||||
|
super(
|
||||||
|
latestItemTime: latestItemTime,
|
||||||
|
);
|
||||||
|
|
||||||
factory AlbumStaticProvider.fromJson(JsonObj json) {
|
factory AlbumStaticProvider.fromJson(JsonObj json) {
|
||||||
return AlbumStaticProvider(
|
return AlbumStaticProvider(
|
||||||
|
latestItemTime: json["latestItemTime"] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json["latestItemTime"]),
|
||||||
items: (json["items"] as List)
|
items: (json["items"] as List)
|
||||||
.map((e) => AlbumItem.fromJson(e.cast<String, dynamic>()))
|
.map((e) => AlbumItem.fromJson(e.cast<String, dynamic>()))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -79,6 +119,7 @@ class AlbumStaticProvider extends AlbumProvider {
|
||||||
final itemsStr =
|
final itemsStr =
|
||||||
isDeep ? items.toReadableString() : "List {length: ${items.length}}";
|
isDeep ? items.toReadableString() : "List {length: ${items.length}}";
|
||||||
return "$runtimeType {"
|
return "$runtimeType {"
|
||||||
|
"super: ${super.toString(isDeep: isDeep)}, "
|
||||||
"items: $itemsStr, "
|
"items: $itemsStr, "
|
||||||
"}";
|
"}";
|
||||||
}
|
}
|
||||||
|
@ -86,36 +127,25 @@ class AlbumStaticProvider extends AlbumProvider {
|
||||||
@override
|
@override
|
||||||
toContentJson() {
|
toContentJson() {
|
||||||
return {
|
return {
|
||||||
|
...super.toContentJson(),
|
||||||
"items": items.map((e) => e.toJson()).toList(),
|
"items": items.map((e) => e.toJson()).toList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AlbumStaticProvider copyWith({
|
AlbumStaticProvider copyWith({
|
||||||
|
DateTime? latestItemTime,
|
||||||
List<AlbumItem>? items,
|
List<AlbumItem>? items,
|
||||||
}) {
|
}) {
|
||||||
return AlbumStaticProvider(
|
return AlbumStaticProvider(
|
||||||
|
latestItemTime: latestItemTime ?? this.latestItemTime,
|
||||||
items: items ?? this.items,
|
items: items ?? this.items,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
get latestItemTime {
|
|
||||||
try {
|
|
||||||
return items
|
|
||||||
.whereType<AlbumFileItem>()
|
|
||||||
.map((e) => e.file)
|
|
||||||
.where((element) => file_util.isSupportedFormat(element))
|
|
||||||
.sorted(compareFileDateTimeDescending)
|
|
||||||
.first
|
|
||||||
.bestDateTime;
|
|
||||||
} catch (_) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get props => [
|
get props => [
|
||||||
|
...super.props,
|
||||||
items,
|
items,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -125,55 +155,28 @@ class AlbumStaticProvider extends AlbumProvider {
|
||||||
static const _type = "static";
|
static const _type = "static";
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AlbumDynamicProvider extends AlbumProvider {
|
abstract class AlbumDynamicProvider extends AlbumProviderBase {
|
||||||
AlbumDynamicProvider({
|
const AlbumDynamicProvider({
|
||||||
DateTime? latestItemTime,
|
DateTime? latestItemTime,
|
||||||
}) : _latestItemTime = latestItemTime;
|
}) : super(latestItemTime: latestItemTime);
|
||||||
|
|
||||||
@override
|
|
||||||
toString({bool isDeep = false}) {
|
|
||||||
return "$runtimeType {"
|
|
||||||
"latestItemTime: $_latestItemTime, "
|
|
||||||
"}";
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
toContentJson() {
|
|
||||||
return {
|
|
||||||
"latestItemTime": _latestItemTime?.toUtc().toIso8601String(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
AlbumDynamicProvider copyWith({
|
|
||||||
DateTime? latestItemTime,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
get latestItemTime => _latestItemTime;
|
|
||||||
|
|
||||||
@override
|
|
||||||
get props => [
|
|
||||||
_latestItemTime,
|
|
||||||
];
|
|
||||||
|
|
||||||
final DateTime? _latestItemTime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumDirProvider extends AlbumDynamicProvider {
|
class AlbumDirProvider extends AlbumDynamicProvider {
|
||||||
AlbumDirProvider({
|
const AlbumDirProvider({
|
||||||
required this.dirs,
|
required this.dirs,
|
||||||
DateTime? latestItemTime,
|
DateTime? latestItemTime,
|
||||||
}) : super(latestItemTime: latestItemTime);
|
}) : super(
|
||||||
|
latestItemTime: latestItemTime,
|
||||||
|
);
|
||||||
|
|
||||||
factory AlbumDirProvider.fromJson(JsonObj json) {
|
factory AlbumDirProvider.fromJson(JsonObj json) {
|
||||||
return AlbumDirProvider(
|
return AlbumDirProvider(
|
||||||
dirs: (json["dirs"] as List)
|
|
||||||
.map((e) => File.fromJson(e.cast<String, dynamic>()))
|
|
||||||
.toList(),
|
|
||||||
latestItemTime: json["latestItemTime"] == null
|
latestItemTime: json["latestItemTime"] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json["latestItemTime"]),
|
: DateTime.parse(json["latestItemTime"]),
|
||||||
|
dirs: (json["dirs"] as List)
|
||||||
|
.map((e) => File.fromJson(e.cast<String, dynamic>()))
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,12 +198,12 @@ class AlbumDirProvider extends AlbumDynamicProvider {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AlbumDirProvider copyWith({
|
AlbumDirProvider copyWith({
|
||||||
List<File>? dirs,
|
|
||||||
DateTime? latestItemTime,
|
DateTime? latestItemTime,
|
||||||
|
List<File>? dirs,
|
||||||
}) {
|
}) {
|
||||||
return AlbumDirProvider(
|
return AlbumDirProvider(
|
||||||
dirs: dirs ?? this.dirs,
|
|
||||||
latestItemTime: latestItemTime ?? this.latestItemTime,
|
latestItemTime: latestItemTime ?? this.latestItemTime,
|
||||||
|
dirs: dirs ?? this.dirs,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/type.dart';
|
import 'package:nc_photos/type.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
abstract class AlbumUpgrader {
|
abstract class AlbumUpgrader {
|
||||||
JsonObj? call(JsonObj json);
|
JsonObj? call(JsonObj json);
|
||||||
|
@ -85,3 +88,72 @@ class AlbumUpgraderV3 implements AlbumUpgrader {
|
||||||
|
|
||||||
static final _log = Logger("entity.album.upgrader.AlbumUpgraderV3");
|
static final _log = Logger("entity.album.upgrader.AlbumUpgraderV3");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Upgrade v4 Album to v5
|
||||||
|
class AlbumUpgraderV4 implements AlbumUpgrader {
|
||||||
|
AlbumUpgraderV4({
|
||||||
|
this.logFilePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
call(JsonObj json) {
|
||||||
|
_log.fine("[call] Upgrade v4 Album for file: $logFilePath");
|
||||||
|
final result = JsonObj.from(json);
|
||||||
|
try {
|
||||||
|
if (result["provider"]["type"] != "static") {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
final latestItem = (result["provider"]["content"]["items"] as List)
|
||||||
|
.map((e) => e.cast<String, dynamic>())
|
||||||
|
.where((e) => e["type"] == "file")
|
||||||
|
.map((e) => e["content"]["file"] as JsonObj)
|
||||||
|
.map((e) {
|
||||||
|
final overrideDateTime = e["overrideDateTime"] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(e["overrideDateTime"]);
|
||||||
|
final String? dateTimeOriginalStr =
|
||||||
|
e["metadata"]?["exif"]?["DateTimeOriginal"];
|
||||||
|
final dateTimeOriginal =
|
||||||
|
dateTimeOriginalStr == null || dateTimeOriginalStr.isEmpty
|
||||||
|
? null
|
||||||
|
: Exif.dateTimeFormat.parse(dateTimeOriginalStr).toUtc();
|
||||||
|
final lastModified = e["lastModified"] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(e["lastModified"]);
|
||||||
|
final latestItemTime =
|
||||||
|
overrideDateTime ?? dateTimeOriginal ?? lastModified;
|
||||||
|
|
||||||
|
// remove metadata
|
||||||
|
e.remove("metadata");
|
||||||
|
if (latestItemTime != null) {
|
||||||
|
return Tuple2(latestItemTime, e);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.whereType<Tuple2<DateTime, JsonObj>>()
|
||||||
|
.sorted((a, b) => a.item1.compareTo(b.item1))
|
||||||
|
.lastOrNull;
|
||||||
|
if (latestItem != null) {
|
||||||
|
// save the latest item time
|
||||||
|
result["provider"]["content"]["latestItemTime"] =
|
||||||
|
latestItem.item1.toIso8601String();
|
||||||
|
if (result["coverProvider"]["type"] == "auto") {
|
||||||
|
// save the cover
|
||||||
|
result["coverProvider"]["content"]["coverFile"] =
|
||||||
|
Map.of(latestItem.item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
// this upgrade is not a must, if it failed then just leave it and it'll
|
||||||
|
// be upgraded the next time the album is saved
|
||||||
|
_log.shout("[call] Failed while upgrade", e, stackTrace);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// File path for logging only
|
||||||
|
final String? logFilePath;
|
||||||
|
|
||||||
|
static final _log = Logger("entity.album.upgrader.AlbumUpgraderV4");
|
||||||
|
}
|
||||||
|
|
|
@ -49,4 +49,12 @@ extension IterableExtension<T> on Iterable<T> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T? get lastOrNull {
|
||||||
|
try {
|
||||||
|
return last;
|
||||||
|
} on StateError catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,43 @@
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
|
import 'package:nc_photos/use_case/resync_album.dart';
|
||||||
import 'package:nc_photos/use_case/update_album.dart';
|
import 'package:nc_photos/use_case/update_album.dart';
|
||||||
|
import 'package:nc_photos/use_case/update_album_with_actual_items.dart';
|
||||||
|
|
||||||
class AddToAlbum {
|
class AddToAlbum {
|
||||||
AddToAlbum(this.albumRepo);
|
AddToAlbum(this.albumRepo);
|
||||||
|
|
||||||
/// Add a list of AlbumItems to [album]
|
/// Add a list of AlbumItems to [album]
|
||||||
Future<void> call(Account account, Album album, List<AlbumItem> items) =>
|
Future<Album> call(
|
||||||
UpdateAlbum(albumRepo)(
|
Account account, Album album, List<AlbumItem> items) async {
|
||||||
account,
|
_log.info("[call] Add ${items.length} items to album '${album.name}'");
|
||||||
album.copyWith(
|
assert(album.provider is AlbumStaticProvider);
|
||||||
provider: AlbumStaticProvider.of(album).copyWith(
|
// resync is needed to work out album cover and latest item
|
||||||
items: makeDistinctAlbumItems([
|
final oldItems = await ResyncAlbum()(account, album);
|
||||||
...items,
|
final newItems = makeDistinctAlbumItems([
|
||||||
...AlbumStaticProvider.of(album).items,
|
...items,
|
||||||
]),
|
...oldItems,
|
||||||
),
|
]);
|
||||||
));
|
var newAlbum = album.copyWith(
|
||||||
|
provider: AlbumStaticProvider.of(album).copyWith(
|
||||||
|
items: newItems,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// UpdateAlbumWithActualItems only persists when there are changes to
|
||||||
|
// several properties, so we can't rely on it
|
||||||
|
newAlbum = await UpdateAlbumWithActualItems(null)(
|
||||||
|
account,
|
||||||
|
newAlbum,
|
||||||
|
newItems,
|
||||||
|
);
|
||||||
|
await UpdateAlbum(albumRepo)(account, newAlbum);
|
||||||
|
return newAlbum;
|
||||||
|
}
|
||||||
|
|
||||||
final AlbumRepo albumRepo;
|
final AlbumRepo albumRepo;
|
||||||
|
|
||||||
|
static final _log = Logger("use_case.add_to_album.AddToAlbum");
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,11 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
|
import 'package:nc_photos/use_case/resync_album.dart';
|
||||||
import 'package:nc_photos/use_case/update_album.dart';
|
import 'package:nc_photos/use_case/update_album.dart';
|
||||||
|
import 'package:nc_photos/use_case/update_album_with_actual_items.dart';
|
||||||
|
|
||||||
class RemoveFromAlbum {
|
class RemoveFromAlbum {
|
||||||
RemoveFromAlbum(this.albumRepo);
|
RemoveFromAlbum(this.albumRepo);
|
||||||
|
@ -26,6 +29,18 @@ class RemoveFromAlbum {
|
||||||
items: newItems,
|
items: newItems,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
// check if any of the removed items was the latest item
|
||||||
|
if (items.whereType<AlbumFileItem>().any((element) =>
|
||||||
|
element.file.bestDateTime == album.provider.latestItemTime)) {
|
||||||
|
_log.info("[call] Resync as latest item is being removed");
|
||||||
|
// need to update the album properties
|
||||||
|
final newItemsSynced = await ResyncAlbum()(account, newAlbum);
|
||||||
|
newAlbum = await UpdateAlbumWithActualItems(null)(
|
||||||
|
account,
|
||||||
|
newAlbum,
|
||||||
|
newItemsSynced,
|
||||||
|
);
|
||||||
|
}
|
||||||
await UpdateAlbum(albumRepo)(account, newAlbum);
|
await UpdateAlbum(albumRepo)(account, newAlbum);
|
||||||
return newAlbum;
|
return newAlbum;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,11 @@ import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
|
|
||||||
/// Resync files inside an album with the file db
|
/// Resync files inside an album with the file db
|
||||||
class ResyncAlbum {
|
class ResyncAlbum {
|
||||||
Future<Album> call(Account account, Album album) async {
|
Future<List<AlbumItem>> call(Account account, Album album) async {
|
||||||
|
_log.info("[call] Resync album: ${album.name}");
|
||||||
if (album.provider is! AlbumStaticProvider) {
|
if (album.provider is! AlbumStaticProvider) {
|
||||||
_log.warning(
|
throw ArgumentError(
|
||||||
"[call] Resync only make sense for static albums: ${album.name}");
|
"Resync only make sense for static albums: ${album.name}");
|
||||||
return album;
|
|
||||||
}
|
}
|
||||||
return await AppDb.use((db) async {
|
return await AppDb.use((db) async {
|
||||||
final transaction =
|
final transaction =
|
||||||
|
@ -38,7 +38,7 @@ class ResyncAlbum {
|
||||||
newItems.add(item);
|
newItems.add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return album.copyWith(provider: AlbumStaticProvider(items: newItems));
|
return newItems;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,33 @@ import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/entity/album.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/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
|
|
||||||
class UpdateAlbum {
|
class UpdateAlbum {
|
||||||
UpdateAlbum(this.albumRepo);
|
UpdateAlbum(this.albumRepo);
|
||||||
|
|
||||||
Future<void> call(Account account, Album album) async {
|
Future<void> call(Account account, Album album) async {
|
||||||
await albumRepo.update(account, album);
|
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);
|
||||||
|
}
|
||||||
KiwiContainer().resolve<EventBus>().fire(AlbumUpdatedEvent(account, album));
|
KiwiContainer().resolve<EventBus>().fire(AlbumUpdatedEvent(account, album));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<AlbumItem> _minimizeItems(List<AlbumItem> items) {
|
||||||
|
return items.map((e) => e is AlbumFileItem ? e.minimize() : e).toList();
|
||||||
|
}
|
||||||
|
|
||||||
final AlbumRepo albumRepo;
|
final AlbumRepo albumRepo;
|
||||||
}
|
}
|
||||||
|
|
56
lib/use_case/update_album_time.dart
Normal file
56
lib/use_case/update_album_time.dart
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
class UpdateAlbumTime {
|
||||||
|
/// Update the latest item time of an album with unsorted items
|
||||||
|
///
|
||||||
|
/// If no updates are needed, return the same object
|
||||||
|
Album call(Album album, List<AlbumItem> items) {
|
||||||
|
if (album.provider is! AlbumProviderBase) {
|
||||||
|
return album;
|
||||||
|
} else {
|
||||||
|
final sortedItems =
|
||||||
|
const AlbumTimeSortProvider(isAscending: false).sort(items);
|
||||||
|
return _updateWithSortedItems(album, sortedItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the latest item time of an album with pre-sorted files
|
||||||
|
///
|
||||||
|
/// The album items are expected to be sorted by [AlbumTimeSortProvider] with
|
||||||
|
/// isAscending = false, otherwise please call the unsorted version. If no
|
||||||
|
/// updates are needed, return the same object
|
||||||
|
Album updateWithSortedItems(Album album, List<AlbumItem> sortedItems) {
|
||||||
|
if (album.provider is! AlbumProviderBase) {
|
||||||
|
return album;
|
||||||
|
} else {
|
||||||
|
return _updateWithSortedItems(album, sortedItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Album _updateWithSortedItems(Album album, List<AlbumItem> sortedItems) {
|
||||||
|
DateTime? latestItemTime;
|
||||||
|
try {
|
||||||
|
final latestFile = sortedItems
|
||||||
|
.whereType<AlbumFileItem>()
|
||||||
|
.map((e) => e.file)
|
||||||
|
.where((element) => file_util.isSupportedFormat(element))
|
||||||
|
.first;
|
||||||
|
latestItemTime = latestFile.bestDateTime;
|
||||||
|
} catch (_) {
|
||||||
|
latestItemTime = null;
|
||||||
|
}
|
||||||
|
if (latestItemTime != album.provider.latestItemTime) {
|
||||||
|
return album.copyWith(
|
||||||
|
provider: (album.provider as AlbumProviderBase).copyWith(
|
||||||
|
latestItemTime: latestItemTime,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return album;
|
||||||
|
}
|
||||||
|
}
|
50
lib/use_case/update_album_with_actual_items.dart
Normal file
50
lib/use_case/update_album_with_actual_items.dart
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
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/sort_provider.dart';
|
||||||
|
import 'package:nc_photos/use_case/update_album.dart';
|
||||||
|
import 'package:nc_photos/use_case/update_album_time.dart';
|
||||||
|
import 'package:nc_photos/use_case/update_auto_album_cover.dart';
|
||||||
|
|
||||||
|
class UpdateAlbumWithActualItems {
|
||||||
|
UpdateAlbumWithActualItems(this.albumRepo);
|
||||||
|
|
||||||
|
/// Update, if necessary, [album] after resynced/populated with actual items
|
||||||
|
///
|
||||||
|
/// If [albumRepo] is null, the modified album will not be saved
|
||||||
|
Future<Album> call(
|
||||||
|
Account account, Album album, List<AlbumItem> items) async {
|
||||||
|
final sortedItems =
|
||||||
|
const AlbumTimeSortProvider(isAscending: false).sort(items);
|
||||||
|
|
||||||
|
bool shouldUpdate = false;
|
||||||
|
final albumUpdatedCover =
|
||||||
|
UpdateAutoAlbumCover().updateWithSortedItems(album, sortedItems);
|
||||||
|
if (!identical(albumUpdatedCover, album)) {
|
||||||
|
_log.info("[call] Update album cover");
|
||||||
|
shouldUpdate = true;
|
||||||
|
}
|
||||||
|
album = albumUpdatedCover;
|
||||||
|
|
||||||
|
final albumUpdatedTime =
|
||||||
|
UpdateAlbumTime().updateWithSortedItems(album, sortedItems);
|
||||||
|
if (!identical(albumUpdatedTime, album)) {
|
||||||
|
_log.info(
|
||||||
|
"[call] Update album time: ${album.provider.latestItemTime} -> ${albumUpdatedTime.provider.latestItemTime}");
|
||||||
|
shouldUpdate = true;
|
||||||
|
}
|
||||||
|
album = albumUpdatedTime;
|
||||||
|
|
||||||
|
if (albumRepo != null && shouldUpdate) {
|
||||||
|
_log.info("[call] Persist album");
|
||||||
|
await UpdateAlbum(albumRepo!)(account, album);
|
||||||
|
}
|
||||||
|
return album;
|
||||||
|
}
|
||||||
|
|
||||||
|
final AlbumRepo? albumRepo;
|
||||||
|
|
||||||
|
static final _log = Logger(
|
||||||
|
"use_case.update_album_with_actual_items.UpdateAlbumWithActualItems");
|
||||||
|
}
|
55
lib/use_case/update_auto_album_cover.dart
Normal file
55
lib/use_case/update_auto_album_cover.dart
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import 'package:nc_photos/entity/album.dart';
|
||||||
|
import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
|
|
||||||
|
class UpdateAutoAlbumCover {
|
||||||
|
/// Update the AlbumAutoCoverProvider of an album with unsorted items
|
||||||
|
///
|
||||||
|
/// If no updates are needed, return the same object
|
||||||
|
Album call(Album album, List<AlbumItem> items) {
|
||||||
|
if (album.coverProvider is! AlbumAutoCoverProvider) {
|
||||||
|
return album;
|
||||||
|
} else {
|
||||||
|
final sortedItems =
|
||||||
|
const AlbumTimeSortProvider(isAscending: false).sort(items);
|
||||||
|
return _updateWithSortedItems(album, sortedItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the AlbumAutoCoverProvider of an album with pre-sorted files
|
||||||
|
///
|
||||||
|
/// The album items are expected to be sorted by [AlbumTimeSortProvider] with
|
||||||
|
/// isAscending = false, otherwise please call the unsorted version. If no
|
||||||
|
/// updates are needed, return the same object
|
||||||
|
Album updateWithSortedItems(Album album, List<AlbumItem> sortedItems) {
|
||||||
|
if (album.coverProvider is! AlbumAutoCoverProvider) {
|
||||||
|
return album;
|
||||||
|
} else {
|
||||||
|
return _updateWithSortedItems(album, sortedItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Album _updateWithSortedItems(Album album, List<AlbumItem> sortedItems) {
|
||||||
|
try {
|
||||||
|
final coverFile = sortedItems
|
||||||
|
.whereType<AlbumFileItem>()
|
||||||
|
.map((e) => e.file)
|
||||||
|
.where((element) => file_util.isSupportedFormat(element))
|
||||||
|
.firstWhere((element) => element.hasPreview ?? false);
|
||||||
|
// cache the result for later use
|
||||||
|
if (coverFile.path !=
|
||||||
|
(album.coverProvider as AlbumAutoCoverProvider).coverFile?.path) {
|
||||||
|
return album.copyWith(
|
||||||
|
coverProvider: AlbumAutoCoverProvider(
|
||||||
|
coverFile: coverFile,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} on StateError catch (_) {
|
||||||
|
// no files
|
||||||
|
}
|
||||||
|
return album;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,60 +0,0 @@
|
||||||
import 'package:nc_photos/entity/album.dart';
|
|
||||||
import 'package:nc_photos/entity/album/cover_provider.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_util.dart' as file_util;
|
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
|
||||||
|
|
||||||
class UpdateDynamicAlbumCover {
|
|
||||||
/// Update the cover of a dynamic album with unsorted items
|
|
||||||
///
|
|
||||||
/// If no updates are needed, return the same object
|
|
||||||
Album call(Album album, List<AlbumItem> populatedItems) {
|
|
||||||
if (album.provider is! AlbumDynamicProvider ||
|
|
||||||
album.coverProvider is! AlbumAutoCoverProvider) {
|
|
||||||
return album;
|
|
||||||
} else {
|
|
||||||
return _updateWithSortedFiles(
|
|
||||||
album,
|
|
||||||
populatedItems
|
|
||||||
.whereType<AlbumFileItem>()
|
|
||||||
.map((e) => e.file)
|
|
||||||
.where((element) => file_util.isSupportedFormat(element))
|
|
||||||
.sorted(compareFileDateTimeDescending));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the cover of a dynamic album with pre-sorted files
|
|
||||||
///
|
|
||||||
/// The album items are expected to be sorted by
|
|
||||||
/// [compareFileDateTimeDescending], otherwise please call the unsorted
|
|
||||||
/// version. If no updates are needed, return the same object
|
|
||||||
Album updateWithSortedFiles(Album album, List<File> sortedFiles) {
|
|
||||||
if (album.provider is! AlbumDynamicProvider ||
|
|
||||||
album.coverProvider is! AlbumAutoCoverProvider) {
|
|
||||||
return album;
|
|
||||||
} else {
|
|
||||||
return _updateWithSortedFiles(album, sortedFiles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Album _updateWithSortedFiles(Album album, List<File> sortedFiles) {
|
|
||||||
try {
|
|
||||||
final coverFile =
|
|
||||||
sortedFiles.firstWhere((element) => element.hasPreview ?? false);
|
|
||||||
// cache the result for later use
|
|
||||||
if (coverFile.path !=
|
|
||||||
(album.coverProvider as AlbumAutoCoverProvider).coverFile?.path) {
|
|
||||||
return album.copyWith(
|
|
||||||
coverProvider: AlbumAutoCoverProvider(
|
|
||||||
coverFile: coverFile,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} on StateError catch (_) {
|
|
||||||
// no files
|
|
||||||
}
|
|
||||||
return album;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
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_util.dart' as file_util;
|
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
|
||||||
|
|
||||||
class UpdateDynamicAlbumTime {
|
|
||||||
/// Update the latest item time of a dynamic album with unsorted items
|
|
||||||
///
|
|
||||||
/// If no updates are needed, return the same object
|
|
||||||
Album call(Album album, List<AlbumItem> populatedItems) {
|
|
||||||
if (album.provider is! AlbumDynamicProvider) {
|
|
||||||
return album;
|
|
||||||
} else {
|
|
||||||
return _updateWithSortedFiles(
|
|
||||||
album,
|
|
||||||
populatedItems
|
|
||||||
.whereType<AlbumFileItem>()
|
|
||||||
.map((e) => e.file)
|
|
||||||
.where((element) => file_util.isSupportedFormat(element))
|
|
||||||
.sorted(compareFileDateTimeDescending));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the latest item time of a dynamic album with pre-sorted files
|
|
||||||
///
|
|
||||||
/// The album items are expected to be sorted by
|
|
||||||
/// [compareFileDateTimeDescending], otherwise please call the unsorted
|
|
||||||
/// version. If no updates are needed, return the same object
|
|
||||||
Album updateWithSortedFiles(Album album, List<File> sortedFiles) {
|
|
||||||
if (album.provider is! AlbumDynamicProvider) {
|
|
||||||
return album;
|
|
||||||
} else {
|
|
||||||
return _updateWithSortedFiles(album, sortedFiles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Album _updateWithSortedFiles(Album album, List<File> sortedFiles) {
|
|
||||||
DateTime? latestItemTime;
|
|
||||||
try {
|
|
||||||
latestItemTime = sortedFiles.first.bestDateTime;
|
|
||||||
} catch (_) {
|
|
||||||
latestItemTime = null;
|
|
||||||
}
|
|
||||||
if (latestItemTime != album.provider.latestItemTime) {
|
|
||||||
return album.copyWith(
|
|
||||||
provider: (album.provider as AlbumDynamicProvider).copyWith(
|
|
||||||
latestItemTime: latestItemTime,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return album;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,8 +14,8 @@ import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
import 'package:nc_photos/list_extension.dart';
|
||||||
import 'package:nc_photos/or_null.dart';
|
import 'package:nc_photos/or_null.dart';
|
||||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||||
import 'package:nc_photos/session_storage.dart';
|
import 'package:nc_photos/session_storage.dart';
|
||||||
|
@ -25,6 +25,7 @@ import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/use_case/remove_from_album.dart';
|
import 'package:nc_photos/use_case/remove_from_album.dart';
|
||||||
import 'package:nc_photos/use_case/resync_album.dart';
|
import 'package:nc_photos/use_case/resync_album.dart';
|
||||||
import 'package:nc_photos/use_case/update_album.dart';
|
import 'package:nc_photos/use_case/update_album.dart';
|
||||||
|
import 'package:nc_photos/use_case/update_album_with_actual_items.dart';
|
||||||
import 'package:nc_photos/widget/album_browser_mixin.dart';
|
import 'package:nc_photos/widget/album_browser_mixin.dart';
|
||||||
import 'package:nc_photos/widget/draggable_item_list_mixin.dart';
|
import 'package:nc_photos/widget/draggable_item_list_mixin.dart';
|
||||||
import 'package:nc_photos/widget/fancy_option_picker.dart';
|
import 'package:nc_photos/widget/fancy_option_picker.dart';
|
||||||
|
@ -32,7 +33,6 @@ import 'package:nc_photos/widget/photo_list_item.dart';
|
||||||
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
|
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
|
||||||
import 'package:nc_photos/widget/simple_input_dialog.dart';
|
import 'package:nc_photos/widget/simple_input_dialog.dart';
|
||||||
import 'package:nc_photos/widget/viewer.dart';
|
import 'package:nc_photos/widget/viewer.dart';
|
||||||
import 'package:quiver/iterables.dart';
|
|
||||||
|
|
||||||
class AlbumBrowserArguments {
|
class AlbumBrowserArguments {
|
||||||
AlbumBrowserArguments(this.account, this.album);
|
AlbumBrowserArguments(this.account, this.album);
|
||||||
|
@ -169,20 +169,10 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initAlbum() {
|
Future<void> _initAlbum() async {
|
||||||
assert(widget.album.provider is AlbumStaticProvider);
|
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
||||||
ResyncAlbum()(widget.account, widget.album).then((album) {
|
final album = await albumRepo.get(widget.account, widget.album.albumFile!);
|
||||||
if (_shouldPropagateResyncedAlbum(album)) {
|
await _setAlbum(album);
|
||||||
_propagateResyncedAlbum(album);
|
|
||||||
}
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_album = album;
|
|
||||||
_transformItems();
|
|
||||||
initCover(widget.account, album);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context) {
|
Widget _buildContent(BuildContext context) {
|
||||||
|
@ -495,13 +485,9 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAlbumUpdatedEvent(AlbumUpdatedEvent ev) {
|
Future<void> _onAlbumUpdatedEvent(AlbumUpdatedEvent ev) async {
|
||||||
if (ev.album.albumFile!.path == _album?.albumFile?.path) {
|
if (ev.album.albumFile!.path == _album?.albumFile?.path) {
|
||||||
setState(() {
|
await _setAlbum(ev.album);
|
||||||
_album = ev.album;
|
|
||||||
_transformItems();
|
|
||||||
initCover(widget.account, ev.album);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,64 +586,29 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
draggableItemList = items;
|
draggableItemList = items;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _propagateResyncedAlbum(Album album) {
|
Future<void> _setAlbum(Album album) async {
|
||||||
final propagateItems =
|
assert(album.provider is AlbumStaticProvider);
|
||||||
zip([_getAlbumItemsOf(album), _getAlbumItemsOf(widget.album)]).map((e) {
|
final items = await ResyncAlbum()(widget.account, album);
|
||||||
if (e[0] is AlbumFileItem) {
|
album = album.copyWith(
|
||||||
final item = e[0] as AlbumFileItem;
|
|
||||||
if (!item.file.isOwned(widget.account.username)) {
|
|
||||||
// don't propagate shared file not owned by this user, this is to
|
|
||||||
// prevent multiple user having different properties to keep
|
|
||||||
// overriding each others
|
|
||||||
_log.info(
|
|
||||||
"[_propagateResyncedAlbum] Skip shared file: ${item.file.path}");
|
|
||||||
return e[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return e[0];
|
|
||||||
}).toList();
|
|
||||||
final propagateAlbum = album.copyWith(
|
|
||||||
provider: AlbumStaticProvider.of(album).copyWith(
|
provider: AlbumStaticProvider.of(album).copyWith(
|
||||||
items: propagateItems,
|
items: items,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
UpdateAlbum(AlbumRepo(AlbumCachedDataSource()))(
|
album = await _updateAlbumPostResync(album, items);
|
||||||
widget.account, propagateAlbum)
|
if (mounted) {
|
||||||
.catchError((e, stacktrace) {
|
setState(() {
|
||||||
_log.shout("[_propagateResyncedAlbum] Failed while updating album", e,
|
_album = album;
|
||||||
stacktrace);
|
_transformItems();
|
||||||
});
|
initCover(widget.account, album);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldPropagateResyncedAlbum(Album album) {
|
Future<Album> _updateAlbumPostResync(
|
||||||
final origItems = _getAlbumItemsOf(widget.album);
|
Album album, List<AlbumItem> items) async {
|
||||||
final resyncItems = _getAlbumItemsOf(album);
|
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
||||||
if (origItems.length != resyncItems.length) {
|
return await UpdateAlbumWithActualItems(albumRepo)(
|
||||||
_log.info(
|
widget.account, album, items);
|
||||||
"[_shouldPropagateResyncedAlbum] Item length differ: ${origItems.length}, ${resyncItems.length}");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (final z in zip([origItems, resyncItems])) {
|
|
||||||
final a = z[0], b = z[1];
|
|
||||||
bool isEqual;
|
|
||||||
if (a is AlbumFileItem && b is AlbumFileItem) {
|
|
||||||
if (!a.file.isOwned(widget.account.username)) {
|
|
||||||
// ignore shared files
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// faster compare
|
|
||||||
isEqual = a.equals(b, isDeep: false);
|
|
||||||
} else {
|
|
||||||
isEqual = a == b;
|
|
||||||
}
|
|
||||||
if (!isEqual) {
|
|
||||||
_log.info(
|
|
||||||
"[_shouldPropagateResyncedAlbum] Item differ:\nOriginal: ${z[0]}\nResynced: ${z[1]}");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_log.info("[_shouldPropagateResyncedAlbum] false");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<AlbumItem> _getAlbumItemsOf(Album a) =>
|
static List<AlbumItem> _getAlbumItemsOf(Album a) =>
|
||||||
|
|
|
@ -8,11 +8,9 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/bloc/list_importable_album.dart';
|
import 'package:nc_photos/bloc/list_importable_album.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/cover_provider.dart';
|
import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
@ -20,8 +18,7 @@ import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/use_case/create_album.dart';
|
import 'package:nc_photos/use_case/create_album.dart';
|
||||||
import 'package:nc_photos/use_case/populate_album.dart';
|
import 'package:nc_photos/use_case/populate_album.dart';
|
||||||
import 'package:nc_photos/use_case/update_dynamic_album_cover.dart';
|
import 'package:nc_photos/use_case/update_album_with_actual_items.dart';
|
||||||
import 'package:nc_photos/use_case/update_dynamic_album_time.dart';
|
|
||||||
import 'package:nc_photos/widget/processing_dialog.dart';
|
import 'package:nc_photos/widget/processing_dialog.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
@ -239,15 +236,8 @@ class _AlbumImporterState extends State<AlbumImporter> {
|
||||||
_log.info("[_createAllAlbums] Creating dir album: $album");
|
_log.info("[_createAllAlbums] Creating dir album: $album");
|
||||||
|
|
||||||
final items = await PopulateAlbum()(widget.account, album);
|
final items = await PopulateAlbum()(widget.account, album);
|
||||||
final sortedFiles = items
|
album = await UpdateAlbumWithActualItems(null)(
|
||||||
.whereType<AlbumFileItem>()
|
widget.account, album, items);
|
||||||
.map((e) => e.file)
|
|
||||||
.where((element) => file_util.isSupportedFormat(element))
|
|
||||||
.sorted(compareFileDateTimeDescending);
|
|
||||||
album =
|
|
||||||
UpdateDynamicAlbumCover().updateWithSortedFiles(album, sortedFiles);
|
|
||||||
album =
|
|
||||||
UpdateDynamicAlbumTime().updateWithSortedFiles(album, sortedFiles);
|
|
||||||
|
|
||||||
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
||||||
await CreateAlbum(albumRepo)(widget.account, album);
|
await CreateAlbum(albumRepo)(widget.account, album);
|
||||||
|
|
|
@ -27,8 +27,7 @@ import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/use_case/populate_album.dart';
|
import 'package:nc_photos/use_case/populate_album.dart';
|
||||||
import 'package:nc_photos/use_case/remove.dart';
|
import 'package:nc_photos/use_case/remove.dart';
|
||||||
import 'package:nc_photos/use_case/update_album.dart';
|
import 'package:nc_photos/use_case/update_album.dart';
|
||||||
import 'package:nc_photos/use_case/update_dynamic_album_cover.dart';
|
import 'package:nc_photos/use_case/update_album_with_actual_items.dart';
|
||||||
import 'package:nc_photos/use_case/update_dynamic_album_time.dart';
|
|
||||||
import 'package:nc_photos/widget/album_browser_mixin.dart';
|
import 'package:nc_photos/widget/album_browser_mixin.dart';
|
||||||
import 'package:nc_photos/widget/fancy_option_picker.dart';
|
import 'package:nc_photos/widget/fancy_option_picker.dart';
|
||||||
import 'package:nc_photos/widget/photo_list_item.dart';
|
import 'package:nc_photos/widget/photo_list_item.dart';
|
||||||
|
@ -159,58 +158,16 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initAlbum() {
|
Future<void> _initAlbum() async {
|
||||||
assert(widget.album.provider is AlbumDynamicProvider);
|
assert(widget.album.provider is AlbumDynamicProvider);
|
||||||
PopulateAlbum()(widget.account, widget.album).then((items) {
|
final items = await PopulateAlbum()(widget.account, widget.album);
|
||||||
if (mounted) {
|
final album = await _updateAlbumPostPopulate(widget.album, items);
|
||||||
setState(() {
|
if (mounted) {
|
||||||
_album = widget.album;
|
setState(() {
|
||||||
_transformItems(items);
|
_album = album;
|
||||||
initCover(widget.account, widget.album);
|
_transformItems(items);
|
||||||
_updateAlbumPostPopulate(items);
|
initCover(widget.account, widget.album);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _updateAlbumPostPopulate(List<AlbumItem> items) async {
|
|
||||||
final List<File> timeDescSortedFiles;
|
|
||||||
if (widget.album.sortProvider is AlbumTimeSortProvider) {
|
|
||||||
if ((widget.album.sortProvider as AlbumTimeSortProvider).isAscending) {
|
|
||||||
timeDescSortedFiles = _backingFiles.reversed.toList();
|
|
||||||
} else {
|
|
||||||
timeDescSortedFiles = _backingFiles;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
timeDescSortedFiles = const AlbumTimeSortProvider(isAscending: false)
|
|
||||||
.sort(items)
|
|
||||||
.whereType<AlbumFileItem>()
|
|
||||||
.map((e) => e.file)
|
|
||||||
.where((element) => file_util.isSupportedFormat(element))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool shouldUpdate = false;
|
|
||||||
final albumUpdatedCover = UpdateDynamicAlbumCover()
|
|
||||||
.updateWithSortedFiles(_album!, timeDescSortedFiles);
|
|
||||||
if (!identical(albumUpdatedCover, _album)) {
|
|
||||||
_log.info("[_updateAlbumPostPopulate] Update album cover");
|
|
||||||
shouldUpdate = true;
|
|
||||||
}
|
|
||||||
_album = albumUpdatedCover;
|
|
||||||
|
|
||||||
final albumUpdatedTime = UpdateDynamicAlbumTime()
|
|
||||||
.updateWithSortedFiles(_album!, timeDescSortedFiles);
|
|
||||||
if (!identical(albumUpdatedTime, _album)) {
|
|
||||||
_log.info(
|
|
||||||
"[_updateAlbumPostPopulate] Update album time: ${albumUpdatedTime.provider.latestItemTime}");
|
|
||||||
shouldUpdate = true;
|
|
||||||
}
|
|
||||||
_album = albumUpdatedTime;
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
await UpdateAlbum(AlbumRepo(AlbumCachedDataSource()))(
|
|
||||||
widget.account, _album!);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,6 +333,7 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
widget.account,
|
widget.account,
|
||||||
_album!.copyWith(
|
_album!.copyWith(
|
||||||
provider: AlbumStaticProvider(
|
provider: AlbumStaticProvider(
|
||||||
|
latestItemTime: _album!.provider.latestItemTime,
|
||||||
items: _sortedItems,
|
items: _sortedItems,
|
||||||
),
|
),
|
||||||
coverProvider: AlbumAutoCoverProvider(),
|
coverProvider: AlbumAutoCoverProvider(),
|
||||||
|
@ -513,12 +471,12 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAlbumUpdatedEvent(AlbumUpdatedEvent ev) {
|
Future<void> _onAlbumUpdatedEvent(AlbumUpdatedEvent ev) async {
|
||||||
if (ev.album.albumFile!.path == _album?.albumFile?.path) {
|
if (ev.album.albumFile!.path == _album?.albumFile?.path) {
|
||||||
|
final album = await _updateAlbumPostPopulate(ev.album, _sortedItems);
|
||||||
setState(() {
|
setState(() {
|
||||||
_album = ev.album;
|
_album = album;
|
||||||
initCover(widget.account, ev.album);
|
initCover(widget.account, album);
|
||||||
_updateAlbumPostPopulate(_sortedItems);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -572,6 +530,13 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Album> _updateAlbumPostPopulate(
|
||||||
|
Album album, List<AlbumItem> items) async {
|
||||||
|
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
||||||
|
return await UpdateAlbumWithActualItems(albumRepo)(
|
||||||
|
widget.account, album, items);
|
||||||
|
}
|
||||||
|
|
||||||
Album? _album;
|
Album? _album;
|
||||||
var _sortedItems = <AlbumItem>[];
|
var _sortedItems = <AlbumItem>[];
|
||||||
var _backingFiles = <File>[];
|
var _backingFiles = <File>[];
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/cover_provider.dart';
|
import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
|
@ -31,8 +32,13 @@ void main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
Album.fromJson(json,
|
Album.fromJson(
|
||||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
json,
|
||||||
|
upgraderV1: null,
|
||||||
|
upgraderV2: null,
|
||||||
|
upgraderV3: null,
|
||||||
|
upgraderV4: null,
|
||||||
|
),
|
||||||
Album(
|
Album(
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -65,8 +71,13 @@ void main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
Album.fromJson(json,
|
Album.fromJson(
|
||||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
json,
|
||||||
|
upgraderV1: null,
|
||||||
|
upgraderV2: null,
|
||||||
|
upgraderV3: null,
|
||||||
|
upgraderV4: null,
|
||||||
|
),
|
||||||
Album(
|
Album(
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||||
name: "album",
|
name: "album",
|
||||||
|
@ -117,8 +128,13 @@ void main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
Album.fromJson(json,
|
Album.fromJson(
|
||||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
json,
|
||||||
|
upgraderV1: null,
|
||||||
|
upgraderV2: null,
|
||||||
|
upgraderV3: null,
|
||||||
|
upgraderV4: null,
|
||||||
|
),
|
||||||
Album(
|
Album(
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -165,8 +181,13 @@ void main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
Album.fromJson(json,
|
Album.fromJson(
|
||||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
json,
|
||||||
|
upgraderV1: null,
|
||||||
|
upgraderV2: null,
|
||||||
|
upgraderV3: null,
|
||||||
|
upgraderV4: null,
|
||||||
|
),
|
||||||
Album(
|
Album(
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -208,8 +229,13 @@ void main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
Album.fromJson(json,
|
Album.fromJson(
|
||||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
json,
|
||||||
|
upgraderV1: null,
|
||||||
|
upgraderV2: null,
|
||||||
|
upgraderV3: null,
|
||||||
|
upgraderV4: null,
|
||||||
|
),
|
||||||
Album(
|
Album(
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -248,8 +274,13 @@ void main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
Album.fromJson(json,
|
Album.fromJson(
|
||||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
json,
|
||||||
|
upgraderV1: null,
|
||||||
|
upgraderV2: null,
|
||||||
|
upgraderV3: null,
|
||||||
|
upgraderV4: null,
|
||||||
|
),
|
||||||
Album(
|
Album(
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -287,8 +318,13 @@ void main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
Album.fromJson(json,
|
Album.fromJson(
|
||||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
json,
|
||||||
|
upgraderV1: null,
|
||||||
|
upgraderV2: null,
|
||||||
|
upgraderV3: null,
|
||||||
|
upgraderV4: null,
|
||||||
|
),
|
||||||
Album(
|
Album(
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -942,5 +978,374 @@ void main() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group("AlbumUpgraderV4", () {
|
||||||
|
test("Non AlbumFileItem", () {
|
||||||
|
final json = <String, dynamic>{
|
||||||
|
"version": 4,
|
||||||
|
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||||
|
"provider": <String, dynamic>{
|
||||||
|
"type": "static",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"items": [
|
||||||
|
<String, dynamic>{
|
||||||
|
"type": "label",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"text": "123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"coverProvider": <String, dynamic>{
|
||||||
|
"type": "auto",
|
||||||
|
"content": <String, dynamic>{},
|
||||||
|
},
|
||||||
|
"sortProvider": <String, dynamic>{
|
||||||
|
"type": "time",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"isAscending": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"albumFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(AlbumUpgraderV4()(json), <String, dynamic>{
|
||||||
|
"version": 4,
|
||||||
|
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||||
|
"provider": <String, dynamic>{
|
||||||
|
"type": "static",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"items": [
|
||||||
|
<String, dynamic>{
|
||||||
|
"type": "label",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"text": "123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"coverProvider": <String, dynamic>{
|
||||||
|
"type": "auto",
|
||||||
|
"content": <String, dynamic>{},
|
||||||
|
},
|
||||||
|
"sortProvider": <String, dynamic>{
|
||||||
|
"type": "time",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"isAscending": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"albumFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group("AlbumFileItem", () {
|
||||||
|
test("drop metadata", () {
|
||||||
|
final json = <String, dynamic>{
|
||||||
|
"version": 4,
|
||||||
|
"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",
|
||||||
|
"metadata": <String, dynamic>{
|
||||||
|
"Make": "Super",
|
||||||
|
"Model": "A123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"coverProvider": <String, dynamic>{
|
||||||
|
"type": "auto",
|
||||||
|
"content": <String, dynamic>{},
|
||||||
|
},
|
||||||
|
"sortProvider": <String, dynamic>{
|
||||||
|
"type": "time",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"isAscending": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"albumFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(AlbumUpgraderV4()(json), <String, dynamic>{
|
||||||
|
"version": 4,
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"coverProvider": <String, dynamic>{
|
||||||
|
"type": "auto",
|
||||||
|
"content": <String, dynamic>{},
|
||||||
|
},
|
||||||
|
"sortProvider": <String, dynamic>{
|
||||||
|
"type": "time",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"isAscending": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"albumFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("lastModified as latestItemTime", () {
|
||||||
|
final json = <String, dynamic>{
|
||||||
|
"version": 4,
|
||||||
|
"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",
|
||||||
|
"lastModified": "2020-01-02T03:04:05.678901Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"coverProvider": <String, dynamic>{
|
||||||
|
"type": "auto",
|
||||||
|
"content": <String, dynamic>{},
|
||||||
|
},
|
||||||
|
"sortProvider": <String, dynamic>{
|
||||||
|
"type": "time",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"isAscending": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"albumFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(AlbumUpgraderV4()(json), <String, dynamic>{
|
||||||
|
"version": 4,
|
||||||
|
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||||
|
"provider": <String, dynamic>{
|
||||||
|
"type": "static",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"latestItemTime": "2020-01-02T03:04:05.678901Z",
|
||||||
|
"items": [
|
||||||
|
<String, dynamic>{
|
||||||
|
"type": "file",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"file": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||||
|
"lastModified": "2020-01-02T03:04:05.678901Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"coverProvider": <String, dynamic>{
|
||||||
|
"type": "auto",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"coverFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||||
|
"lastModified": "2020-01-02T03:04:05.678901Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sortProvider": <String, dynamic>{
|
||||||
|
"type": "time",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"isAscending": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"albumFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("dateTimeOriginal as latestItemTime", () {
|
||||||
|
final json = <String, dynamic>{
|
||||||
|
"version": 4,
|
||||||
|
"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",
|
||||||
|
"metadata": <String, dynamic>{
|
||||||
|
"exif": <String, dynamic>{
|
||||||
|
// convert 2020-01-02T03:04:05Z to local time
|
||||||
|
"DateTimeOriginal":
|
||||||
|
DateFormat("yyyy:MM:dd HH:mm:ss").format(
|
||||||
|
DateTime.utc(2020, 1, 2, 3, 4, 5)
|
||||||
|
.toLocal()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"coverProvider": <String, dynamic>{
|
||||||
|
"type": "auto",
|
||||||
|
"content": <String, dynamic>{},
|
||||||
|
},
|
||||||
|
"sortProvider": <String, dynamic>{
|
||||||
|
"type": "time",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"isAscending": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"albumFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(AlbumUpgraderV4()(json), <String, dynamic>{
|
||||||
|
"version": 4,
|
||||||
|
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||||
|
"provider": <String, dynamic>{
|
||||||
|
"type": "static",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"latestItemTime": "2020-01-02T03:04:05.000Z",
|
||||||
|
"items": [
|
||||||
|
<String, dynamic>{
|
||||||
|
"type": "file",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"file": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"coverProvider": <String, dynamic>{
|
||||||
|
"type": "auto",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"coverFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sortProvider": <String, dynamic>{
|
||||||
|
"type": "time",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"isAscending": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"albumFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("overrideDateTime as latestItemTime", () {
|
||||||
|
final json = <String, dynamic>{
|
||||||
|
"version": 4,
|
||||||
|
"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",
|
||||||
|
"overrideDateTime": "2020-01-02T03:04:05.678901Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"coverProvider": <String, dynamic>{
|
||||||
|
"type": "auto",
|
||||||
|
"content": <String, dynamic>{},
|
||||||
|
},
|
||||||
|
"sortProvider": <String, dynamic>{
|
||||||
|
"type": "time",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"isAscending": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"albumFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(AlbumUpgraderV4()(json), <String, dynamic>{
|
||||||
|
"version": 4,
|
||||||
|
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||||
|
"provider": <String, dynamic>{
|
||||||
|
"type": "static",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"latestItemTime": "2020-01-02T03:04:05.678901Z",
|
||||||
|
"items": [
|
||||||
|
<String, dynamic>{
|
||||||
|
"type": "file",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"file": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||||
|
"overrideDateTime": "2020-01-02T03:04:05.678901Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"coverProvider": <String, dynamic>{
|
||||||
|
"type": "auto",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"coverFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||||
|
"overrideDateTime": "2020-01-02T03:04:05.678901Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sortProvider": <String, dynamic>{
|
||||||
|
"type": "time",
|
||||||
|
"content": <String, dynamic>{
|
||||||
|
"isAscending": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"albumFile": <String, dynamic>{
|
||||||
|
"path": "remote.php/dav/files/admin/test1.json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue