Disallow updating album with a newer version

This commit is contained in:
Ming Ming 2021-11-18 18:50:51 +08:00
parent 3527e96f7a
commit 50599f2ba5
6 changed files with 65 additions and 7 deletions

View file

@ -28,6 +28,13 @@ import 'package:tuple/tuple.dart';
/// Immutable object that represents an album /// Immutable object that represents an album
class Album with EquatableMixin { 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
Album({ Album({
DateTime? lastUpdated, DateTime? lastUpdated,
required this.name, required this.name,
@ -36,7 +43,9 @@ class Album with EquatableMixin {
required this.sortProvider, required this.sortProvider,
this.shares, this.shares,
this.albumFile, this.albumFile,
}) : lastUpdated = (lastUpdated ?? DateTime.now()).toUtc(); int? savedVersion,
}) : lastUpdated = (lastUpdated ?? DateTime.now()).toUtc(),
savedVersion = savedVersion ?? version;
static Album? fromJson( static Album? fromJson(
JsonObj json, { JsonObj json, {
@ -79,6 +88,17 @@ class Album with EquatableMixin {
return null; return null;
} }
} }
if (jsonVersion < 7) {
result = upgraderFactory?.buildV6()?.call(result);
if (result == null) {
_log.info("[fromJson] Version $jsonVersion not compatible");
return null;
}
}
if (jsonVersion > version) {
_log.warning(
"[fromJson] Reading album with newer version: $jsonVersion > $version");
}
return Album( return Album(
lastUpdated: result["lastUpdated"] == null lastUpdated: result["lastUpdated"] == null
? null ? null
@ -96,6 +116,7 @@ class Album with EquatableMixin {
albumFile: result["albumFile"] == null albumFile: result["albumFile"] == null
? null ? null
: File.fromJson(result["albumFile"].cast<String, dynamic>()), : File.fromJson(result["albumFile"].cast<String, dynamic>()),
savedVersion: result["version"],
); );
} }
@ -135,6 +156,7 @@ class Album with EquatableMixin {
sortProvider: sortProvider ?? this.sortProvider, sortProvider: sortProvider ?? this.sortProvider,
shares: shares == null ? this.shares : shares.obj, shares: shares == null ? this.shares : shares.obj,
albumFile: albumFile == null ? this.albumFile : albumFile.obj, albumFile: albumFile == null ? this.albumFile : albumFile.obj,
savedVersion: savedVersion,
); );
} }
@ -173,6 +195,7 @@ class Album with EquatableMixin {
sortProvider, sortProvider,
shares, shares,
albumFile, albumFile,
savedVersion,
]; ];
final DateTime lastUpdated; final DateTime lastUpdated;
@ -188,6 +211,11 @@ class Album with EquatableMixin {
/// This field is typically only meaningful when returned by [AlbumRepo.get] /// This field is typically only meaningful when returned by [AlbumRepo.get]
final File? albumFile; final File? albumFile;
/// The original version of this class when saved
///
/// This field only exists in runtime and are not persisted
final int savedVersion;
/// versioning of this class, use to upgrade old persisted album /// versioning of this class, use to upgrade old persisted album
static const version = 7; static const version = 7;
} }

View file

@ -89,3 +89,15 @@ class JobCanceledException implements Exception {
final dynamic message; final dynamic message;
} }
/// Trying to downgrade an Album
class AlbumDowngradeException implements Exception {
const AlbumDowngradeException([this.message]);
@override
toString() {
return "AlbumDowngradeException: $message";
}
final dynamic message;
}

View file

@ -21,6 +21,8 @@ String toUserString(dynamic exception) {
return L10n.global().errorDisconnected; return L10n.global().errorDisconnected;
} else if (exception is InvalidBaseUrlException) { } else if (exception is InvalidBaseUrlException) {
return L10n.global().errorInvalidBaseUrl; return L10n.global().errorInvalidBaseUrl;
} else if (exception is AlbumDowngradeException) {
return L10n.global().errorAlbumDowngrade;
} }
return exception.toString(); return exception.toString();
} }

View file

@ -1099,5 +1099,9 @@
"errorServerError": "Server error. Please make sure the server is setup correctly", "errorServerError": "Server error. Please make sure the server is setup correctly",
"@errorServerError": { "@errorServerError": {
"description": "HTTP 500" "description": "HTTP 500"
},
"errorAlbumDowngrade": "Can't modify this album as it was created by a later version of this app. Please update the app and try again",
"@errorAlbumDowngrade": {
"description": "Album files are versioned. Overwriting a newer version is disallowed as it will lead to unexpected data loss"
} }
} }

View file

@ -48,7 +48,8 @@
"fixAllTooltip", "fixAllTooltip",
"missingShareDescription", "missingShareDescription",
"extraShareDescription", "extraShareDescription",
"defaultButtonLabel" "defaultButtonLabel",
"errorAlbumDowngrade"
], ],
"de": [ "de": [
@ -114,7 +115,8 @@
"fixAllTooltip", "fixAllTooltip",
"missingShareDescription", "missingShareDescription",
"extraShareDescription", "extraShareDescription",
"defaultButtonLabel" "defaultButtonLabel",
"errorAlbumDowngrade"
], ],
"el": [ "el": [
@ -235,11 +237,13 @@
"fixAllTooltip", "fixAllTooltip",
"missingShareDescription", "missingShareDescription",
"extraShareDescription", "extraShareDescription",
"defaultButtonLabel" "defaultButtonLabel",
"errorAlbumDowngrade"
], ],
"es": [ "es": [
"settingsMapProviderTitle" "settingsMapProviderTitle",
"errorAlbumDowngrade"
], ],
"fr": [ "fr": [
@ -340,7 +344,8 @@
"fixAllTooltip", "fixAllTooltip",
"missingShareDescription", "missingShareDescription",
"extraShareDescription", "extraShareDescription",
"defaultButtonLabel" "defaultButtonLabel",
"errorAlbumDowngrade"
], ],
"ru": [ "ru": [
@ -414,6 +419,7 @@
"fixAllTooltip", "fixAllTooltip",
"missingShareDescription", "missingShareDescription",
"extraShareDescription", "extraShareDescription",
"defaultButtonLabel" "defaultButtonLabel",
"errorAlbumDowngrade"
] ]
} }

View file

@ -5,11 +5,17 @@ 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/event/event.dart'; import 'package:nc_photos/event/event.dart';
import 'package:nc_photos/exception.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 {
if (album.savedVersion > Album.version) {
// the album is created by a newer version of this app
throw AlbumDowngradeException(
"Not allowed to downgrade album '${album.name}'");
}
final provider = album.provider; final provider = album.provider;
if (provider is AlbumStaticProvider) { if (provider is AlbumStaticProvider) {
await albumRepo.update( await albumRepo.update(