nc-photos/app/lib/entity/album.dart

323 lines
9.6 KiB
Dart
Raw Normal View History

2023-03-13 12:55:39 +01:00
import 'package:clock/clock.dart';
2021-04-15 20:44:25 +02:00
import 'package:equatable/equatable.dart';
2021-04-10 06:28:12 +02:00
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
2021-06-26 13:51:13 +02:00
import 'package:nc_photos/entity/album/cover_provider.dart';
2021-06-24 18:26:56 +02:00
import 'package:nc_photos/entity/album/provider.dart';
2021-07-07 20:40:43 +02:00
import 'package:nc_photos/entity/album/sort_provider.dart';
2021-06-24 16:54:41 +02:00
import 'package:nc_photos/entity/album/upgrader.dart';
2021-04-10 06:28:12 +02:00
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/iterable_extension.dart';
import 'package:nc_photos/object_extension.dart';
2021-07-22 08:15:41 +02:00
import 'package:nc_photos/or_null.dart';
2022-12-16 16:01:04 +01:00
import 'package:np_codegen/np_codegen.dart';
import 'package:np_common/ci_string.dart';
import 'package:np_common/type.dart';
2022-12-08 16:39:13 +01:00
import 'package:to_string/to_string.dart';
part 'album.g.dart';
2021-04-10 06:28:12 +02:00
/// Immutable object that represents an album
2022-12-16 16:01:04 +01:00
@npLog
2021-04-15 20:44:25 +02:00
class Album with EquatableMixin {
/// Create a new album
///
/// If [lastUpdated] is null, the current time will be used.
///
/// [savedVersion] should be null when creating a new album, such that it'll
/// be filled with the current version number automatically. You should only
/// pass this argument when reading album from storage
2021-04-10 06:28:12 +02:00
Album({
2021-07-23 22:05:57 +02:00
DateTime? lastUpdated,
required this.name,
required this.provider,
required this.coverProvider,
required this.sortProvider,
this.shares,
2021-04-10 06:28:12 +02:00
this.albumFile,
int? savedVersion,
2023-03-13 12:55:39 +01:00
}) : lastUpdated = (lastUpdated ?? clock.now()).toUtc(),
savedVersion = savedVersion ?? version;
2021-04-10 06:28:12 +02:00
2021-07-23 22:05:57 +02:00
static Album? fromJson(
2021-08-06 19:11:00 +02:00
JsonObj json, {
required AlbumUpgraderFactory? upgraderFactory,
2021-04-10 06:28:12 +02:00
}) {
2021-06-24 16:54:41 +02:00
final jsonVersion = json["version"];
2021-08-06 19:11:00 +02:00
JsonObj? result = json;
2021-06-24 16:54:41 +02:00
if (jsonVersion < 2) {
result = upgraderFactory?.buildV1()?.doJson(result);
2021-07-23 22:05:57 +02:00
if (result == null) {
2021-06-24 16:54:41 +02:00
_log.info("[fromJson] Version $jsonVersion not compatible");
return null;
}
}
2021-06-24 18:26:56 +02:00
if (jsonVersion < 3) {
result = upgraderFactory?.buildV2()?.doJson(result);
2021-07-23 22:05:57 +02:00
if (result == null) {
2021-06-24 18:26:56 +02:00
_log.info("[fromJson] Version $jsonVersion not compatible");
return null;
}
}
2021-07-07 20:40:43 +02:00
if (jsonVersion < 4) {
result = upgraderFactory?.buildV3()?.doJson(result);
2021-07-23 22:05:57 +02:00
if (result == null) {
2021-07-07 20:40:43 +02:00
_log.info("[fromJson] Version $jsonVersion not compatible");
return null;
}
}
if (jsonVersion < 5) {
result = upgraderFactory?.buildV4()?.doJson(result);
if (result == null) {
_log.info("[fromJson] Version $jsonVersion not compatible");
return null;
}
}
if (jsonVersion < 6) {
result = upgraderFactory?.buildV5()?.doJson(result);
if (result == null) {
_log.info("[fromJson] Version $jsonVersion not compatible");
return null;
}
}
if (jsonVersion < 7) {
result = upgraderFactory?.buildV6()?.doJson(result);
if (result == null) {
_log.info("[fromJson] Version $jsonVersion not compatible");
return null;
}
}
2022-05-26 06:00:55 +02:00
if (jsonVersion < 8) {
result = upgraderFactory?.buildV7()?.doJson(result);
2022-05-26 06:00:55 +02:00
if (result == null) {
_log.info("[fromJson] Version $jsonVersion not compatible");
return null;
}
}
2023-04-17 18:15:29 +02:00
if (jsonVersion < 9) {
result = upgraderFactory?.buildV8()?.doJson(result);
2023-04-17 18:15:29 +02:00
if (result == null) {
_log.info("[fromJson] Version $jsonVersion not compatible");
return null;
}
}
if (jsonVersion > version) {
_log.warning(
"[fromJson] Reading album with newer version: $jsonVersion > $version");
}
2021-06-24 16:54:41 +02:00
return Album(
2021-07-23 22:05:57 +02:00
lastUpdated: result["lastUpdated"] == null
2021-04-10 06:28:12 +02:00
? null
2021-07-23 22:05:57 +02:00
: DateTime.parse(result["lastUpdated"]),
name: result["name"],
2021-06-24 18:26:56 +02:00
provider:
2021-07-23 22:05:57 +02:00
AlbumProvider.fromJson(result["provider"].cast<String, dynamic>()),
2021-06-26 13:51:13 +02:00
coverProvider: AlbumCoverProvider.fromJson(
2021-07-23 22:05:57 +02:00
result["coverProvider"].cast<String, dynamic>()),
2021-07-07 20:40:43 +02:00
sortProvider: AlbumSortProvider.fromJson(
2021-07-23 22:05:57 +02:00
result["sortProvider"].cast<String, dynamic>()),
shares: (result["shares"] as List?)
?.map((e) => AlbumShare.fromJson(e.cast<String, dynamic>()))
.toList(),
2021-07-23 22:05:57 +02:00
albumFile: result["albumFile"] == null
2021-04-10 06:28:12 +02:00
? null
2021-07-23 22:05:57 +02:00
: File.fromJson(result["albumFile"].cast<String, dynamic>()),
savedVersion: result["version"],
2021-04-10 06:28:12 +02:00
);
}
@override
2021-06-12 17:18:41 +02:00
toString({bool isDeep = false}) {
2021-04-10 06:28:12 +02:00
return "$runtimeType {"
"lastUpdated: $lastUpdated, "
"name: $name, "
2021-06-24 18:26:56 +02:00
"provider: ${provider.toString(isDeep: isDeep)}, "
2021-06-26 13:51:13 +02:00
"coverProvider: $coverProvider, "
2021-07-07 20:40:43 +02:00
"sortProvider: $sortProvider, "
"shares: ${shares?.toReadableString()}, "
2021-04-10 06:28:12 +02:00
"albumFile: $albumFile, "
"}";
}
/// Return a copy with specified field modified
///
2021-07-22 08:15:41 +02:00
/// [lastUpdated] is handled differently where if not set, the current time
/// will be used. In order to keep [lastUpdated], you must explicitly assign
/// it with value from this or a null value
2021-04-10 06:28:12 +02:00
Album copyWith({
2021-07-23 22:05:57 +02:00
OrNull<DateTime>? lastUpdated,
String? name,
AlbumProvider? provider,
AlbumCoverProvider? coverProvider,
AlbumSortProvider? sortProvider,
OrNull<List<AlbumShare>>? shares,
2021-10-12 12:15:58 +02:00
OrNull<File>? albumFile,
2021-04-10 06:28:12 +02:00
}) {
return Album(
2021-07-22 08:15:41 +02:00
lastUpdated:
lastUpdated == null ? null : (lastUpdated.obj ?? this.lastUpdated),
2021-04-10 06:28:12 +02:00
name: name ?? this.name,
2021-06-24 18:26:56 +02:00
provider: provider ?? this.provider,
2021-06-26 13:51:13 +02:00
coverProvider: coverProvider ?? this.coverProvider,
2021-07-18 19:22:45 +02:00
sortProvider: sortProvider ?? this.sortProvider,
shares:
shares == null ? this.shares?.run((obj) => List.of(obj)) : shares.obj,
2021-10-12 12:15:58 +02:00
albumFile: albumFile == null ? this.albumFile : albumFile.obj,
savedVersion: savedVersion,
2021-04-10 06:28:12 +02:00
);
}
2021-08-06 19:11:00 +02:00
JsonObj toRemoteJson() {
2021-04-10 06:28:12 +02:00
return {
"version": version,
"lastUpdated": lastUpdated.toIso8601String(),
"name": name,
2021-06-24 18:26:56 +02:00
"provider": provider.toJson(),
2021-06-26 13:51:13 +02:00
"coverProvider": coverProvider.toJson(),
2021-07-07 20:40:43 +02:00
"sortProvider": sortProvider.toJson(),
if (shares != null) "shares": shares!.map((e) => e.toJson()).toList(),
2021-04-10 06:28:12 +02:00
// ignore albumFile
};
}
2021-08-06 19:11:00 +02:00
JsonObj toAppDbJson() {
2021-04-10 06:28:12 +02:00
return {
"version": version,
"lastUpdated": lastUpdated.toIso8601String(),
"name": name,
2021-06-24 18:26:56 +02:00
"provider": provider.toJson(),
2021-06-26 13:51:13 +02:00
"coverProvider": coverProvider.toJson(),
2021-07-07 20:40:43 +02:00
"sortProvider": sortProvider.toJson(),
if (shares != null) "shares": shares!.map((e) => e.toJson()).toList(),
2021-07-23 22:05:57 +02:00
if (albumFile != null) "albumFile": albumFile!.toJson(),
2021-04-10 06:28:12 +02:00
};
}
2021-04-15 20:44:25 +02:00
@override
get props => [
lastUpdated,
name,
2021-06-24 18:26:56 +02:00
provider,
2021-06-26 13:51:13 +02:00
coverProvider,
2021-07-07 20:40:43 +02:00
sortProvider,
shares,
2021-04-15 20:44:25 +02:00
albumFile,
savedVersion,
2021-04-15 20:44:25 +02:00
];
2021-04-10 06:28:12 +02:00
final DateTime lastUpdated;
final String name;
2021-06-24 18:26:56 +02:00
final AlbumProvider provider;
2021-06-26 13:51:13 +02:00
final AlbumCoverProvider coverProvider;
2021-07-07 20:40:43 +02:00
final AlbumSortProvider sortProvider;
final List<AlbumShare>? shares;
2021-04-10 06:28:12 +02:00
/// How is this album stored on server
///
/// This field is typically only meaningful when returned by [AlbumRepo.get]
2021-07-23 22:05:57 +02:00
final File? albumFile;
2021-04-10 06:28:12 +02:00
/// The original version of this class when saved
///
/// This field only exists in runtime and are not persisted
final int savedVersion;
2021-04-10 06:28:12 +02:00
/// versioning of this class, use to upgrade old persisted album
2023-04-17 18:15:29 +02:00
static const version = 9;
2022-12-20 17:49:14 +01:00
static final _log = _$AlbumNpLog.log;
2021-04-10 06:28:12 +02:00
}
2022-12-08 16:39:13 +01:00
@toString
2021-11-12 22:13:02 +01:00
class AlbumShare with EquatableMixin {
AlbumShare({
required this.userId,
this.displayName,
DateTime? sharedAt,
2023-03-13 12:55:39 +01:00
}) : sharedAt = (sharedAt ?? clock.now()).toUtc();
factory AlbumShare.fromJson(JsonObj json) {
return AlbumShare(
2021-11-12 22:13:02 +01:00
userId: CiString(json["userId"]),
displayName: json["displayName"],
sharedAt: DateTime.parse(json["sharedAt"]),
);
}
JsonObj toJson() {
return {
2021-11-12 22:13:02 +01:00
"userId": userId.toString(),
if (displayName != null) "displayName": displayName,
"sharedAt": sharedAt.toIso8601String(),
};
}
/// Return a copy with specified field modified
///
/// [sharedAt] is handled differently where if not set, the current time will
/// be used. In order to keep [sharedAt], you must explicitly assign it with
/// value from this or a null value
AlbumShare copyWith({
CiString? userId,
OrNull<String>? displayName,
OrNull<DateTime>? sharedAt,
}) {
return AlbumShare(
userId: userId ?? this.userId,
displayName: displayName == null ? this.displayName : displayName.obj,
sharedAt: sharedAt == null ? null : (sharedAt.obj ?? this.sharedAt),
);
}
@override
2022-12-08 16:39:13 +01:00
String toString() => _$toString();
2021-11-12 22:13:02 +01:00
@override
get props => [
userId,
sharedAt,
2021-11-12 22:13:02 +01:00
];
/// User ID or username, case insensitive
2021-11-12 22:13:02 +01:00
final CiString userId;
final String? displayName;
final DateTime sharedAt;
}
2021-04-10 06:28:12 +02:00
class AlbumRepo {
AlbumRepo(this.dataSrc);
/// See [AlbumDataSource.get]
Future<Album> get(Account account, File albumFile) =>
2021-09-15 08:58:06 +02:00
dataSrc.get(account, albumFile);
2021-04-10 06:28:12 +02:00
/// See [AlbumDataSource.getAll]
Stream<dynamic> getAll(Account account, List<File> albumFiles) =>
dataSrc.getAll(account, albumFiles);
2021-04-10 06:28:12 +02:00
/// See [AlbumDataSource.create]
Future<Album> create(Account account, Album album) =>
2021-09-15 08:58:06 +02:00
dataSrc.create(account, album);
2021-04-10 06:28:12 +02:00
/// See [AlbumDataSource.update]
Future<void> update(Account account, Album album) =>
2021-09-15 08:58:06 +02:00
dataSrc.update(account, album);
2021-04-10 06:28:12 +02:00
final AlbumDataSource dataSrc;
}
abstract class AlbumDataSource {
/// Return the album defined by [albumFile]
Future<Album> get(Account account, File albumFile);
/// Emit albums defined by [albumFiles] or ExceptionEvent
Stream<dynamic> getAll(Account account, List<File> albumFiles);
2021-04-10 06:28:12 +02:00
// Create a new album
Future<Album> create(Account account, Album album);
/// Update an album
Future<void> update(Account account, Album album);
}