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(),
|
||||
upgraderV2: AlbumUpgraderV2(),
|
||||
upgraderV3: AlbumUpgraderV3(),
|
||||
upgraderV4: AlbumUpgraderV4(),
|
||||
)!,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ class Album with EquatableMixin {
|
|||
required AlbumUpgraderV1? upgraderV1,
|
||||
required AlbumUpgraderV2? upgraderV2,
|
||||
required AlbumUpgraderV3? upgraderV3,
|
||||
required AlbumUpgraderV4? upgraderV4,
|
||||
}) {
|
||||
final jsonVersion = json["version"];
|
||||
JsonObj? result = json;
|
||||
|
@ -68,6 +69,13 @@ class Album with EquatableMixin {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
if (jsonVersion < 5) {
|
||||
result = upgraderV4?.call(result);
|
||||
if (result == null) {
|
||||
_log.info("[fromJson] Version $jsonVersion not compatible");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return Album(
|
||||
lastUpdated: result["lastUpdated"] == null
|
||||
? null
|
||||
|
@ -168,7 +176,7 @@ class Album with EquatableMixin {
|
|||
final File? albumFile;
|
||||
|
||||
/// versioning of this class, use to upgrade old persisted album
|
||||
static const version = 4;
|
||||
static const version = 5;
|
||||
}
|
||||
|
||||
class AlbumRepo {
|
||||
|
@ -224,6 +232,7 @@ class AlbumRemoteDataSource implements AlbumDataSource {
|
|||
upgraderV1: AlbumUpgraderV1(),
|
||||
upgraderV2: AlbumUpgraderV2(),
|
||||
upgraderV3: AlbumUpgraderV3(),
|
||||
upgraderV4: AlbumUpgraderV4(),
|
||||
)!
|
||||
.copyWith(
|
||||
lastUpdated: OrNull(null),
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:equatable/equatable.dart';
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/list_extension.dart';
|
||||
import 'package:nc_photos/or_null.dart';
|
||||
import 'package:nc_photos/type.dart';
|
||||
|
||||
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
|
||||
get props => [
|
||||
// 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/item.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/type.dart';
|
||||
|
||||
|
@ -58,13 +57,54 @@ abstract class AlbumProvider with EquatableMixin {
|
|||
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({
|
||||
DateTime? latestItemTime,
|
||||
required List<AlbumItem> items,
|
||||
}) : items = UnmodifiableListView(items);
|
||||
}) : items = UnmodifiableListView(items),
|
||||
super(
|
||||
latestItemTime: latestItemTime,
|
||||
);
|
||||
|
||||
factory AlbumStaticProvider.fromJson(JsonObj json) {
|
||||
return AlbumStaticProvider(
|
||||
latestItemTime: json["latestItemTime"] == null
|
||||
? null
|
||||
: DateTime.parse(json["latestItemTime"]),
|
||||
items: (json["items"] as List)
|
||||
.map((e) => AlbumItem.fromJson(e.cast<String, dynamic>()))
|
||||
.toList(),
|
||||
|
@ -79,6 +119,7 @@ class AlbumStaticProvider extends AlbumProvider {
|
|||
final itemsStr =
|
||||
isDeep ? items.toReadableString() : "List {length: ${items.length}}";
|
||||
return "$runtimeType {"
|
||||
"super: ${super.toString(isDeep: isDeep)}, "
|
||||
"items: $itemsStr, "
|
||||
"}";
|
||||
}
|
||||
|
@ -86,36 +127,25 @@ class AlbumStaticProvider extends AlbumProvider {
|
|||
@override
|
||||
toContentJson() {
|
||||
return {
|
||||
...super.toContentJson(),
|
||||
"items": items.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
AlbumStaticProvider copyWith({
|
||||
DateTime? latestItemTime,
|
||||
List<AlbumItem>? items,
|
||||
}) {
|
||||
return AlbumStaticProvider(
|
||||
latestItemTime: latestItemTime ?? this.latestItemTime,
|
||||
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
|
||||
get props => [
|
||||
...super.props,
|
||||
items,
|
||||
];
|
||||
|
||||
|
@ -125,55 +155,28 @@ class AlbumStaticProvider extends AlbumProvider {
|
|||
static const _type = "static";
|
||||
}
|
||||
|
||||
abstract class AlbumDynamicProvider extends AlbumProvider {
|
||||
AlbumDynamicProvider({
|
||||
abstract class AlbumDynamicProvider extends AlbumProviderBase {
|
||||
const AlbumDynamicProvider({
|
||||
DateTime? latestItemTime,
|
||||
}) : _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;
|
||||
}) : super(latestItemTime: latestItemTime);
|
||||
}
|
||||
|
||||
class AlbumDirProvider extends AlbumDynamicProvider {
|
||||
AlbumDirProvider({
|
||||
const AlbumDirProvider({
|
||||
required this.dirs,
|
||||
DateTime? latestItemTime,
|
||||
}) : super(latestItemTime: latestItemTime);
|
||||
}) : super(
|
||||
latestItemTime: latestItemTime,
|
||||
);
|
||||
|
||||
factory AlbumDirProvider.fromJson(JsonObj json) {
|
||||
return AlbumDirProvider(
|
||||
dirs: (json["dirs"] as List)
|
||||
.map((e) => File.fromJson(e.cast<String, dynamic>()))
|
||||
.toList(),
|
||||
latestItemTime: json["latestItemTime"] == null
|
||||
? null
|
||||
: 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
|
||||
AlbumDirProvider copyWith({
|
||||
List<File>? dirs,
|
||||
DateTime? latestItemTime,
|
||||
List<File>? dirs,
|
||||
}) {
|
||||
return AlbumDirProvider(
|
||||
dirs: dirs ?? this.dirs,
|
||||
latestItemTime: latestItemTime ?? this.latestItemTime,
|
||||
dirs: dirs ?? this.dirs,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
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:tuple/tuple.dart';
|
||||
|
||||
abstract class AlbumUpgrader {
|
||||
JsonObj? call(JsonObj json);
|
||||
|
@ -85,3 +88,72 @@ class AlbumUpgraderV3 implements AlbumUpgrader {
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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/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/resync_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 {
|
||||
AddToAlbum(this.albumRepo);
|
||||
|
||||
/// Add a list of AlbumItems to [album]
|
||||
Future<void> call(Account account, Album album, List<AlbumItem> items) =>
|
||||
UpdateAlbum(albumRepo)(
|
||||
account,
|
||||
album.copyWith(
|
||||
provider: AlbumStaticProvider.of(album).copyWith(
|
||||
items: makeDistinctAlbumItems([
|
||||
...items,
|
||||
...AlbumStaticProvider.of(album).items,
|
||||
]),
|
||||
),
|
||||
));
|
||||
Future<Album> call(
|
||||
Account account, Album album, List<AlbumItem> items) async {
|
||||
_log.info("[call] Add ${items.length} items to album '${album.name}'");
|
||||
assert(album.provider is AlbumStaticProvider);
|
||||
// resync is needed to work out album cover and latest item
|
||||
final oldItems = await ResyncAlbum()(account, album);
|
||||
final newItems = makeDistinctAlbumItems([
|
||||
...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;
|
||||
|
||||
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/item.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/use_case/resync_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 {
|
||||
RemoveFromAlbum(this.albumRepo);
|
||||
|
@ -26,6 +29,18 @@ class RemoveFromAlbum {
|
|||
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);
|
||||
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
|
||||
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) {
|
||||
_log.warning(
|
||||
"[call] Resync only make sense for static albums: ${album.name}");
|
||||
return album;
|
||||
throw ArgumentError(
|
||||
"Resync only make sense for static albums: ${album.name}");
|
||||
}
|
||||
return await AppDb.use((db) async {
|
||||
final transaction =
|
||||
|
@ -38,7 +38,7 @@ class ResyncAlbum {
|
|||
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: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/event/event.dart';
|
||||
|
||||
class UpdateAlbum {
|
||||
UpdateAlbum(this.albumRepo);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
List<AlbumItem> _minimizeItems(List<AlbumItem> items) {
|
||||
return items.map((e) => e is AlbumFileItem ? e.minimize() : e).toList();
|
||||
}
|
||||
|
||||
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/event/event.dart';
|
||||
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/list_extension.dart';
|
||||
import 'package:nc_photos/or_null.dart';
|
||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||
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/resync_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/draggable_item_list_mixin.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/simple_input_dialog.dart';
|
||||
import 'package:nc_photos/widget/viewer.dart';
|
||||
import 'package:quiver/iterables.dart';
|
||||
|
||||
class AlbumBrowserArguments {
|
||||
AlbumBrowserArguments(this.account, this.album);
|
||||
|
@ -169,20 +169,10 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
|||
}
|
||||
}
|
||||
|
||||
void _initAlbum() {
|
||||
assert(widget.album.provider is AlbumStaticProvider);
|
||||
ResyncAlbum()(widget.account, widget.album).then((album) {
|
||||
if (_shouldPropagateResyncedAlbum(album)) {
|
||||
_propagateResyncedAlbum(album);
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_album = album;
|
||||
_transformItems();
|
||||
initCover(widget.account, album);
|
||||
});
|
||||
}
|
||||
});
|
||||
Future<void> _initAlbum() async {
|
||||
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
||||
final album = await albumRepo.get(widget.account, widget.album.albumFile!);
|
||||
await _setAlbum(album);
|
||||
}
|
||||
|
||||
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) {
|
||||
setState(() {
|
||||
_album = ev.album;
|
||||
_transformItems();
|
||||
initCover(widget.account, ev.album);
|
||||
});
|
||||
await _setAlbum(ev.album);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -600,64 +586,29 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
|||
draggableItemList = items;
|
||||
}
|
||||
|
||||
void _propagateResyncedAlbum(Album album) {
|
||||
final propagateItems =
|
||||
zip([_getAlbumItemsOf(album), _getAlbumItemsOf(widget.album)]).map((e) {
|
||||
if (e[0] is AlbumFileItem) {
|
||||
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(
|
||||
Future<void> _setAlbum(Album album) async {
|
||||
assert(album.provider is AlbumStaticProvider);
|
||||
final items = await ResyncAlbum()(widget.account, album);
|
||||
album = album.copyWith(
|
||||
provider: AlbumStaticProvider.of(album).copyWith(
|
||||
items: propagateItems,
|
||||
items: items,
|
||||
),
|
||||
);
|
||||
UpdateAlbum(AlbumRepo(AlbumCachedDataSource()))(
|
||||
widget.account, propagateAlbum)
|
||||
.catchError((e, stacktrace) {
|
||||
_log.shout("[_propagateResyncedAlbum] Failed while updating album", e,
|
||||
stacktrace);
|
||||
});
|
||||
album = await _updateAlbumPostResync(album, items);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_album = album;
|
||||
_transformItems();
|
||||
initCover(widget.account, album);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool _shouldPropagateResyncedAlbum(Album album) {
|
||||
final origItems = _getAlbumItemsOf(widget.album);
|
||||
final resyncItems = _getAlbumItemsOf(album);
|
||||
if (origItems.length != resyncItems.length) {
|
||||
_log.info(
|
||||
"[_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;
|
||||
Future<Album> _updateAlbumPostResync(
|
||||
Album album, List<AlbumItem> items) async {
|
||||
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
||||
return await UpdateAlbumWithActualItems(albumRepo)(
|
||||
widget.account, album, items);
|
||||
}
|
||||
|
||||
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/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/album/sort_provider.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||
import 'package:nc_photos/iterable_extension.dart';
|
||||
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/use_case/create_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_dynamic_album_time.dart';
|
||||
import 'package:nc_photos/use_case/update_album_with_actual_items.dart';
|
||||
import 'package:nc_photos/widget/processing_dialog.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
|
@ -239,15 +236,8 @@ class _AlbumImporterState extends State<AlbumImporter> {
|
|||
_log.info("[_createAllAlbums] Creating dir album: $album");
|
||||
|
||||
final items = await PopulateAlbum()(widget.account, album);
|
||||
final sortedFiles = items
|
||||
.whereType<AlbumFileItem>()
|
||||
.map((e) => e.file)
|
||||
.where((element) => file_util.isSupportedFormat(element))
|
||||
.sorted(compareFileDateTimeDescending);
|
||||
album =
|
||||
UpdateDynamicAlbumCover().updateWithSortedFiles(album, sortedFiles);
|
||||
album =
|
||||
UpdateDynamicAlbumTime().updateWithSortedFiles(album, sortedFiles);
|
||||
album = await UpdateAlbumWithActualItems(null)(
|
||||
widget.account, album, items);
|
||||
|
||||
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
||||
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/remove.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_dynamic_album_time.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/fancy_option_picker.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);
|
||||
PopulateAlbum()(widget.account, widget.album).then((items) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_album = widget.album;
|
||||
_transformItems(items);
|
||||
initCover(widget.account, widget.album);
|
||||
_updateAlbumPostPopulate(items);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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!);
|
||||
final items = await PopulateAlbum()(widget.account, widget.album);
|
||||
final album = await _updateAlbumPostPopulate(widget.album, items);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_album = album;
|
||||
_transformItems(items);
|
||||
initCover(widget.account, widget.album);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,6 +333,7 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
|||
widget.account,
|
||||
_album!.copyWith(
|
||||
provider: AlbumStaticProvider(
|
||||
latestItemTime: _album!.provider.latestItemTime,
|
||||
items: _sortedItems,
|
||||
),
|
||||
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) {
|
||||
final album = await _updateAlbumPostPopulate(ev.album, _sortedItems);
|
||||
setState(() {
|
||||
_album = ev.album;
|
||||
initCover(widget.account, ev.album);
|
||||
_updateAlbumPostPopulate(_sortedItems);
|
||||
_album = album;
|
||||
initCover(widget.account, album);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -572,6 +530,13 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
|||
.toList();
|
||||
}
|
||||
|
||||
Future<Album> _updateAlbumPostPopulate(
|
||||
Album album, List<AlbumItem> items) async {
|
||||
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
||||
return await UpdateAlbumWithActualItems(albumRepo)(
|
||||
widget.account, album, items);
|
||||
}
|
||||
|
||||
Album? _album;
|
||||
var _sortedItems = <AlbumItem>[];
|
||||
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/cover_provider.dart';
|
||||
import 'package:nc_photos/entity/album/item.dart';
|
||||
|
@ -31,8 +32,13 @@ void main() {
|
|||
},
|
||||
};
|
||||
expect(
|
||||
Album.fromJson(json,
|
||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
||||
Album.fromJson(
|
||||
json,
|
||||
upgraderV1: null,
|
||||
upgraderV2: null,
|
||||
upgraderV3: null,
|
||||
upgraderV4: null,
|
||||
),
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
|
@ -65,8 +71,13 @@ void main() {
|
|||
},
|
||||
};
|
||||
expect(
|
||||
Album.fromJson(json,
|
||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
||||
Album.fromJson(
|
||||
json,
|
||||
upgraderV1: null,
|
||||
upgraderV2: null,
|
||||
upgraderV3: null,
|
||||
upgraderV4: null,
|
||||
),
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "album",
|
||||
|
@ -117,8 +128,13 @@ void main() {
|
|||
},
|
||||
};
|
||||
expect(
|
||||
Album.fromJson(json,
|
||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
||||
Album.fromJson(
|
||||
json,
|
||||
upgraderV1: null,
|
||||
upgraderV2: null,
|
||||
upgraderV3: null,
|
||||
upgraderV4: null,
|
||||
),
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
|
@ -165,8 +181,13 @@ void main() {
|
|||
},
|
||||
};
|
||||
expect(
|
||||
Album.fromJson(json,
|
||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
||||
Album.fromJson(
|
||||
json,
|
||||
upgraderV1: null,
|
||||
upgraderV2: null,
|
||||
upgraderV3: null,
|
||||
upgraderV4: null,
|
||||
),
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
|
@ -208,8 +229,13 @@ void main() {
|
|||
},
|
||||
};
|
||||
expect(
|
||||
Album.fromJson(json,
|
||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
||||
Album.fromJson(
|
||||
json,
|
||||
upgraderV1: null,
|
||||
upgraderV2: null,
|
||||
upgraderV3: null,
|
||||
upgraderV4: null,
|
||||
),
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
|
@ -248,8 +274,13 @@ void main() {
|
|||
},
|
||||
};
|
||||
expect(
|
||||
Album.fromJson(json,
|
||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
||||
Album.fromJson(
|
||||
json,
|
||||
upgraderV1: null,
|
||||
upgraderV2: null,
|
||||
upgraderV3: null,
|
||||
upgraderV4: null,
|
||||
),
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
|
@ -287,8 +318,13 @@ void main() {
|
|||
},
|
||||
};
|
||||
expect(
|
||||
Album.fromJson(json,
|
||||
upgraderV1: null, upgraderV2: null, upgraderV3: null),
|
||||
Album.fromJson(
|
||||
json,
|
||||
upgraderV1: null,
|
||||
upgraderV2: null,
|
||||
upgraderV3: null,
|
||||
upgraderV4: null,
|
||||
),
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
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