2023-04-19 19:22:30 +02:00
|
|
|
import 'dart:convert';
|
|
|
|
|
2023-04-17 18:15:29 +02:00
|
|
|
import 'package:clock/clock.dart';
|
2021-12-26 20:11:31 +01:00
|
|
|
import 'package:collection/collection.dart';
|
2021-06-24 16:54:41 +02:00
|
|
|
import 'package:logging/logging.dart';
|
2021-10-23 20:13:06 +02:00
|
|
|
import 'package:nc_photos/account.dart';
|
2021-09-25 18:22:19 +02:00
|
|
|
import 'package:nc_photos/entity/exif.dart';
|
2021-10-31 14:07:05 +01:00
|
|
|
import 'package:nc_photos/entity/file.dart';
|
2023-04-19 19:22:30 +02:00
|
|
|
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
2023-04-17 18:15:29 +02:00
|
|
|
import 'package:nc_photos/object_extension.dart';
|
2022-12-16 16:01:04 +01:00
|
|
|
import 'package:np_codegen/np_codegen.dart';
|
2023-02-23 15:49:17 +01:00
|
|
|
import 'package:np_common/ci_string.dart';
|
|
|
|
import 'package:np_common/type.dart';
|
2021-09-25 18:22:19 +02:00
|
|
|
import 'package:tuple/tuple.dart';
|
2021-06-24 16:54:41 +02:00
|
|
|
|
2022-12-16 16:01:04 +01:00
|
|
|
part 'upgrader.g.dart';
|
|
|
|
|
2021-06-24 16:54:41 +02:00
|
|
|
abstract class AlbumUpgrader {
|
2023-04-19 19:22:30 +02:00
|
|
|
JsonObj? doJson(JsonObj json);
|
|
|
|
sql.Album? doDb(sql.Album dbObj);
|
2021-06-24 16:54:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Upgrade v1 Album to v2
|
2022-12-16 16:01:04 +01:00
|
|
|
@npLog
|
2021-06-24 16:54:41 +02:00
|
|
|
class AlbumUpgraderV1 implements AlbumUpgrader {
|
|
|
|
AlbumUpgraderV1({
|
|
|
|
this.logFilePath,
|
|
|
|
});
|
|
|
|
|
2021-07-23 22:05:57 +02:00
|
|
|
@override
|
2023-04-19 19:22:30 +02:00
|
|
|
doJson(JsonObj json) {
|
2021-06-24 16:54:41 +02:00
|
|
|
// v1 album items are corrupted in one of the updates, drop it
|
2023-04-19 19:22:30 +02:00
|
|
|
_log.fine("[doJson] Upgrade v1 Album for file: $logFilePath");
|
2021-08-06 19:11:00 +02:00
|
|
|
final result = JsonObj.from(json);
|
2021-06-24 16:54:41 +02:00
|
|
|
result["items"] = [];
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-04-19 19:22:30 +02:00
|
|
|
@override
|
|
|
|
sql.Album? doDb(sql.Album dbObj) => null;
|
|
|
|
|
2021-06-24 16:54:41 +02:00
|
|
|
/// File path for logging only
|
2021-07-23 22:05:57 +02:00
|
|
|
final String? logFilePath;
|
2021-06-24 16:54:41 +02:00
|
|
|
}
|
2021-06-24 18:26:56 +02:00
|
|
|
|
|
|
|
/// Upgrade v2 Album to v3
|
2022-12-16 16:01:04 +01:00
|
|
|
@npLog
|
2021-06-24 18:26:56 +02:00
|
|
|
class AlbumUpgraderV2 implements AlbumUpgrader {
|
|
|
|
AlbumUpgraderV2({
|
|
|
|
this.logFilePath,
|
|
|
|
});
|
|
|
|
|
2021-07-23 22:05:57 +02:00
|
|
|
@override
|
2023-04-19 19:22:30 +02:00
|
|
|
doJson(JsonObj json) {
|
2021-06-24 18:26:56 +02:00
|
|
|
// move v2 items to v3 provider
|
2023-04-19 19:22:30 +02:00
|
|
|
_log.fine("[doJson] Upgrade v2 Album for file: $logFilePath");
|
2021-08-06 19:11:00 +02:00
|
|
|
final result = JsonObj.from(json);
|
2021-06-24 18:26:56 +02:00
|
|
|
result["provider"] = <String, dynamic>{
|
|
|
|
"type": "static",
|
|
|
|
"content": <String, dynamic>{
|
|
|
|
"items": result["items"],
|
|
|
|
}
|
|
|
|
};
|
|
|
|
result.remove("items");
|
2021-06-26 13:51:13 +02:00
|
|
|
|
|
|
|
// add the auto cover provider
|
|
|
|
result["coverProvider"] = <String, dynamic>{
|
|
|
|
"type": "auto",
|
|
|
|
"content": {},
|
|
|
|
};
|
2021-06-24 18:26:56 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-04-19 19:22:30 +02:00
|
|
|
@override
|
|
|
|
sql.Album? doDb(sql.Album dbObj) => null;
|
|
|
|
|
2021-06-24 18:26:56 +02:00
|
|
|
/// File path for logging only
|
2021-07-23 22:05:57 +02:00
|
|
|
final String? logFilePath;
|
2021-06-24 18:26:56 +02:00
|
|
|
}
|
2021-07-07 20:40:43 +02:00
|
|
|
|
|
|
|
/// Upgrade v3 Album to v4
|
2022-12-16 16:01:04 +01:00
|
|
|
@npLog
|
2021-07-07 20:40:43 +02:00
|
|
|
class AlbumUpgraderV3 implements AlbumUpgrader {
|
|
|
|
AlbumUpgraderV3({
|
|
|
|
this.logFilePath,
|
|
|
|
});
|
|
|
|
|
2021-07-23 22:05:57 +02:00
|
|
|
@override
|
2023-04-19 19:22:30 +02:00
|
|
|
doJson(JsonObj json) {
|
2021-07-07 20:40:43 +02:00
|
|
|
// move v3 items to v4 provider
|
2023-04-19 19:22:30 +02:00
|
|
|
_log.fine("[doJson] Upgrade v3 Album for file: $logFilePath");
|
2021-08-06 19:11:00 +02:00
|
|
|
final result = JsonObj.from(json);
|
2021-07-07 20:40:43 +02:00
|
|
|
// add the descending time sort provider
|
|
|
|
result["sortProvider"] = <String, dynamic>{
|
|
|
|
"type": "time",
|
|
|
|
"content": {
|
|
|
|
"isAscending": false,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-04-19 19:22:30 +02:00
|
|
|
@override
|
|
|
|
sql.Album? doDb(sql.Album dbObj) => null;
|
|
|
|
|
2021-07-07 20:40:43 +02:00
|
|
|
/// File path for logging only
|
2021-07-23 22:05:57 +02:00
|
|
|
final String? logFilePath;
|
2021-07-07 20:40:43 +02:00
|
|
|
}
|
2021-09-25 18:22:19 +02:00
|
|
|
|
|
|
|
/// Upgrade v4 Album to v5
|
2022-12-16 16:01:04 +01:00
|
|
|
@npLog
|
2021-09-25 18:22:19 +02:00
|
|
|
class AlbumUpgraderV4 implements AlbumUpgrader {
|
|
|
|
AlbumUpgraderV4({
|
|
|
|
this.logFilePath,
|
|
|
|
});
|
|
|
|
|
|
|
|
@override
|
2023-04-19 19:22:30 +02:00
|
|
|
doJson(JsonObj json) {
|
|
|
|
_log.fine("[doJson] Upgrade v4 Album for file: $logFilePath");
|
2021-09-25 18:22:19 +02:00
|
|
|
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
|
2023-04-19 19:22:30 +02:00
|
|
|
_log.shout("[doJson] Failed while upgrade", e, stackTrace);
|
2021-09-25 18:22:19 +02:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-04-19 19:22:30 +02:00
|
|
|
@override
|
|
|
|
sql.Album? doDb(sql.Album dbObj) => null;
|
|
|
|
|
2021-09-25 18:22:19 +02:00
|
|
|
/// File path for logging only
|
|
|
|
final String? logFilePath;
|
|
|
|
}
|
2021-10-23 20:13:06 +02:00
|
|
|
|
|
|
|
/// Upgrade v5 Album to v6
|
2022-12-16 16:01:04 +01:00
|
|
|
@npLog
|
2021-10-23 20:13:06 +02:00
|
|
|
class AlbumUpgraderV5 implements AlbumUpgrader {
|
|
|
|
const AlbumUpgraderV5(
|
|
|
|
this.account, {
|
2021-10-31 14:07:05 +01:00
|
|
|
this.albumFile,
|
2021-10-23 20:13:06 +02:00
|
|
|
this.logFilePath,
|
|
|
|
});
|
|
|
|
|
|
|
|
@override
|
2023-04-19 19:22:30 +02:00
|
|
|
doJson(JsonObj json) {
|
|
|
|
_log.fine("[doJson] Upgrade v5 Album for file: $logFilePath");
|
2021-10-23 20:13:06 +02:00
|
|
|
final result = JsonObj.from(json);
|
|
|
|
try {
|
|
|
|
if (result["provider"]["type"] != "static") {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
for (final item in (result["provider"]["content"]["items"] as List)) {
|
2021-11-12 22:13:02 +01:00
|
|
|
final CiString addedBy;
|
2021-10-31 14:07:05 +01:00
|
|
|
if (result.containsKey("albumFile")) {
|
2021-11-12 22:13:02 +01:00
|
|
|
addedBy = result["albumFile"]["ownerId"] == null
|
2022-07-11 20:14:42 +02:00
|
|
|
? account.userId
|
2021-11-12 22:13:02 +01:00
|
|
|
: CiString(result["albumFile"]["ownerId"]);
|
2021-10-31 14:07:05 +01:00
|
|
|
} else {
|
2022-07-11 20:14:42 +02:00
|
|
|
addedBy = albumFile?.ownerId ?? account.userId;
|
2021-10-31 14:07:05 +01:00
|
|
|
}
|
2021-11-12 22:13:02 +01:00
|
|
|
item["addedBy"] = addedBy.toString();
|
2021-10-23 20:13:06 +02:00
|
|
|
item["addedAt"] = result["lastUpdated"];
|
|
|
|
}
|
|
|
|
} 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
|
2023-04-19 19:22:30 +02:00
|
|
|
_log.shout("[doJson] Failed while upgrade", e, stackTrace);
|
2021-10-23 20:13:06 +02:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-04-19 19:22:30 +02:00
|
|
|
@override
|
|
|
|
sql.Album? doDb(sql.Album dbObj) => null;
|
|
|
|
|
2021-10-23 20:13:06 +02:00
|
|
|
final Account account;
|
2021-10-31 14:07:05 +01:00
|
|
|
final File? albumFile;
|
2021-10-23 20:13:06 +02:00
|
|
|
|
|
|
|
/// File path for logging only
|
|
|
|
final String? logFilePath;
|
|
|
|
}
|
2021-11-18 09:43:14 +01:00
|
|
|
|
2021-11-18 10:02:16 +01:00
|
|
|
/// Upgrade v6 Album to v7
|
2022-12-16 16:01:04 +01:00
|
|
|
@npLog
|
2021-11-18 10:02:16 +01:00
|
|
|
class AlbumUpgraderV6 implements AlbumUpgrader {
|
|
|
|
const AlbumUpgraderV6({
|
|
|
|
this.logFilePath,
|
|
|
|
});
|
|
|
|
|
|
|
|
@override
|
2023-04-19 19:22:30 +02:00
|
|
|
doJson(JsonObj json) {
|
|
|
|
_log.fine("[doJson] Upgrade v6 Album for file: $logFilePath");
|
2021-11-18 10:02:16 +01:00
|
|
|
return json;
|
|
|
|
}
|
|
|
|
|
2023-04-19 19:22:30 +02:00
|
|
|
@override
|
|
|
|
sql.Album? doDb(sql.Album dbObj) => null;
|
|
|
|
|
2021-11-18 10:02:16 +01:00
|
|
|
/// File path for logging only
|
|
|
|
final String? logFilePath;
|
|
|
|
}
|
|
|
|
|
2022-05-26 06:00:55 +02:00
|
|
|
/// Upgrade v7 Album to v8
|
2022-12-16 16:01:04 +01:00
|
|
|
@npLog
|
2022-05-26 06:00:55 +02:00
|
|
|
class AlbumUpgraderV7 implements AlbumUpgrader {
|
|
|
|
const AlbumUpgraderV7({
|
|
|
|
this.logFilePath,
|
|
|
|
});
|
|
|
|
|
|
|
|
@override
|
2023-04-19 19:22:30 +02:00
|
|
|
doJson(JsonObj json) {
|
|
|
|
_log.fine("[doJson] Upgrade v7 Album for file: $logFilePath");
|
2022-05-26 06:00:55 +02:00
|
|
|
return json;
|
|
|
|
}
|
|
|
|
|
2023-04-19 19:22:30 +02:00
|
|
|
@override
|
|
|
|
sql.Album? doDb(sql.Album dbObj) => null;
|
|
|
|
|
2022-05-26 06:00:55 +02:00
|
|
|
/// File path for logging only
|
|
|
|
final String? logFilePath;
|
|
|
|
}
|
|
|
|
|
2023-04-17 18:15:29 +02:00
|
|
|
/// Upgrade v8 Album to v9
|
|
|
|
@npLog
|
|
|
|
class AlbumUpgraderV8 implements AlbumUpgrader {
|
|
|
|
const AlbumUpgraderV8({
|
|
|
|
this.logFilePath,
|
|
|
|
});
|
|
|
|
|
|
|
|
@override
|
2023-04-19 19:22:30 +02:00
|
|
|
JsonObj? doJson(JsonObj json) {
|
|
|
|
_log.fine("[doJson] Upgrade v8 Album for file: $logFilePath");
|
2023-04-17 18:15:29 +02:00
|
|
|
final result = JsonObj.from(json);
|
2023-04-19 19:22:30 +02:00
|
|
|
if (result["coverProvider"]["type"] == "manual") {
|
|
|
|
final content = (result["coverProvider"]["content"]["coverFile"] as Map)
|
|
|
|
.cast<String, dynamic>();
|
|
|
|
final fd = _fileJsonToFileDescriptorJson(content);
|
|
|
|
result["coverProvider"]["content"]["coverFile"] = fd;
|
|
|
|
} else if (result["coverProvider"]["type"] == "auto") {
|
|
|
|
final content = (result["coverProvider"]["content"]["coverFile"] as Map?)
|
|
|
|
?.cast<String, dynamic>();
|
|
|
|
if (content != null) {
|
2023-04-17 18:58:09 +02:00
|
|
|
final fd = _fileJsonToFileDescriptorJson(content);
|
|
|
|
result["coverProvider"]["content"]["coverFile"] = fd;
|
2023-04-17 18:15:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-04-19 19:22:30 +02:00
|
|
|
@override
|
|
|
|
sql.Album? doDb(sql.Album dbObj) {
|
|
|
|
_log.fine("[doDb] Upgrade v8 Album for file: $logFilePath");
|
|
|
|
if (dbObj.coverProviderType == "manual") {
|
|
|
|
final content = (jsonDecode(dbObj.coverProviderContent) as Map)
|
|
|
|
.cast<String, dynamic>();
|
|
|
|
final converted = _fileJsonToFileDescriptorJson(
|
|
|
|
(content["coverFile"] as Map).cast<String, dynamic>());
|
|
|
|
return dbObj.copyWith(
|
|
|
|
coverProviderContent: jsonEncode({"coverFile": converted}),
|
|
|
|
);
|
|
|
|
} else if (dbObj.coverProviderType == "auto") {
|
|
|
|
final content = (jsonDecode(dbObj.coverProviderContent) as Map)
|
|
|
|
.cast<String, dynamic>();
|
|
|
|
if (content["coverFile"] != null) {
|
|
|
|
final converted = _fileJsonToFileDescriptorJson(
|
|
|
|
(content["coverFile"] as Map).cast<String, dynamic>());
|
|
|
|
return dbObj.copyWith(
|
|
|
|
coverProviderContent: jsonEncode({"coverFile": converted}),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return dbObj;
|
|
|
|
}
|
|
|
|
|
2023-04-17 18:58:09 +02:00
|
|
|
static JsonObj _fileJsonToFileDescriptorJson(JsonObj json) {
|
|
|
|
return {
|
|
|
|
"fdPath": json["path"],
|
|
|
|
"fdId": json["fileId"],
|
|
|
|
"fdMime": json["contentType"],
|
|
|
|
"fdIsArchived": json["isArchived"] ?? false,
|
|
|
|
"fdIsFavorite": json["isFavorite"] ?? false,
|
|
|
|
"fdDateTime": json["overrideDateTime"] ??
|
|
|
|
(json["metadata"]?["exif"]?["DateTimeOriginal"] as String?)?.run(
|
|
|
|
(d) => Exif.dateTimeFormat.parse(d).toUtc().toIso8601String()) ??
|
|
|
|
json["lastModified"] ??
|
|
|
|
clock.now().toUtc().toIso8601String(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-04-17 18:15:29 +02:00
|
|
|
/// File path for logging only
|
|
|
|
final String? logFilePath;
|
|
|
|
}
|
|
|
|
|
2021-11-18 09:43:14 +01:00
|
|
|
abstract class AlbumUpgraderFactory {
|
|
|
|
const AlbumUpgraderFactory();
|
|
|
|
|
|
|
|
AlbumUpgraderV1? buildV1();
|
|
|
|
AlbumUpgraderV2? buildV2();
|
|
|
|
AlbumUpgraderV3? buildV3();
|
|
|
|
AlbumUpgraderV4? buildV4();
|
|
|
|
AlbumUpgraderV5? buildV5();
|
2021-11-18 10:02:16 +01:00
|
|
|
AlbumUpgraderV6? buildV6();
|
2022-05-26 06:00:55 +02:00
|
|
|
AlbumUpgraderV7? buildV7();
|
2023-04-17 18:15:29 +02:00
|
|
|
AlbumUpgraderV8? buildV8();
|
2021-11-18 09:43:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class DefaultAlbumUpgraderFactory extends AlbumUpgraderFactory {
|
|
|
|
const DefaultAlbumUpgraderFactory({
|
|
|
|
required this.account,
|
|
|
|
this.albumFile,
|
|
|
|
this.logFilePath,
|
|
|
|
});
|
|
|
|
|
|
|
|
@override
|
|
|
|
buildV1() => AlbumUpgraderV1(logFilePath: logFilePath);
|
|
|
|
|
|
|
|
@override
|
|
|
|
buildV2() => AlbumUpgraderV2(logFilePath: logFilePath);
|
|
|
|
|
|
|
|
@override
|
|
|
|
buildV3() => AlbumUpgraderV3(logFilePath: logFilePath);
|
|
|
|
|
|
|
|
@override
|
|
|
|
buildV4() => AlbumUpgraderV4(logFilePath: logFilePath);
|
|
|
|
|
|
|
|
@override
|
|
|
|
buildV5() => AlbumUpgraderV5(
|
|
|
|
account,
|
|
|
|
albumFile: albumFile,
|
|
|
|
logFilePath: logFilePath,
|
|
|
|
);
|
|
|
|
|
2021-11-18 10:02:16 +01:00
|
|
|
@override
|
|
|
|
buildV6() => AlbumUpgraderV6(logFilePath: logFilePath);
|
|
|
|
|
2022-05-26 06:00:55 +02:00
|
|
|
@override
|
|
|
|
buildV7() => AlbumUpgraderV7(logFilePath: logFilePath);
|
|
|
|
|
2023-04-17 18:15:29 +02:00
|
|
|
@override
|
|
|
|
AlbumUpgraderV8? buildV8() => AlbumUpgraderV8(logFilePath: logFilePath);
|
|
|
|
|
2021-11-18 09:43:14 +01:00
|
|
|
final Account account;
|
|
|
|
final File? albumFile;
|
|
|
|
|
|
|
|
/// File path for logging only
|
|
|
|
final String? logFilePath;
|
|
|
|
}
|