From 50599f2ba57ededc8ed13cf75c9f7015df25666f Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Thu, 18 Nov 2021 18:50:51 +0800 Subject: [PATCH] Disallow updating album with a newer version --- lib/entity/album.dart | 30 +++++++++++++++++++++++++++++- lib/exception.dart | 12 ++++++++++++ lib/exception_util.dart | 2 ++ lib/l10n/app_en.arb | 4 ++++ lib/l10n/untranslated-messages.txt | 18 ++++++++++++------ lib/use_case/update_album.dart | 6 ++++++ 6 files changed, 65 insertions(+), 7 deletions(-) diff --git a/lib/entity/album.dart b/lib/entity/album.dart index a7a89765..bd5b5b92 100644 --- a/lib/entity/album.dart +++ b/lib/entity/album.dart @@ -28,6 +28,13 @@ import 'package:tuple/tuple.dart'; /// Immutable object that represents an album 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({ DateTime? lastUpdated, required this.name, @@ -36,7 +43,9 @@ class Album with EquatableMixin { required this.sortProvider, this.shares, this.albumFile, - }) : lastUpdated = (lastUpdated ?? DateTime.now()).toUtc(); + int? savedVersion, + }) : lastUpdated = (lastUpdated ?? DateTime.now()).toUtc(), + savedVersion = savedVersion ?? version; static Album? fromJson( JsonObj json, { @@ -79,6 +88,17 @@ class Album with EquatableMixin { 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( lastUpdated: result["lastUpdated"] == null ? null @@ -96,6 +116,7 @@ class Album with EquatableMixin { albumFile: result["albumFile"] == null ? null : File.fromJson(result["albumFile"].cast()), + savedVersion: result["version"], ); } @@ -135,6 +156,7 @@ class Album with EquatableMixin { sortProvider: sortProvider ?? this.sortProvider, shares: shares == null ? this.shares : shares.obj, albumFile: albumFile == null ? this.albumFile : albumFile.obj, + savedVersion: savedVersion, ); } @@ -173,6 +195,7 @@ class Album with EquatableMixin { sortProvider, shares, albumFile, + savedVersion, ]; final DateTime lastUpdated; @@ -188,6 +211,11 @@ class Album with EquatableMixin { /// This field is typically only meaningful when returned by [AlbumRepo.get] 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 static const version = 7; } diff --git a/lib/exception.dart b/lib/exception.dart index d8bea1c0..36287348 100644 --- a/lib/exception.dart +++ b/lib/exception.dart @@ -89,3 +89,15 @@ class JobCanceledException implements Exception { final dynamic message; } + +/// Trying to downgrade an Album +class AlbumDowngradeException implements Exception { + const AlbumDowngradeException([this.message]); + + @override + toString() { + return "AlbumDowngradeException: $message"; + } + + final dynamic message; +} diff --git a/lib/exception_util.dart b/lib/exception_util.dart index 7ae9b078..b5372566 100644 --- a/lib/exception_util.dart +++ b/lib/exception_util.dart @@ -21,6 +21,8 @@ String toUserString(dynamic exception) { return L10n.global().errorDisconnected; } else if (exception is InvalidBaseUrlException) { return L10n.global().errorInvalidBaseUrl; + } else if (exception is AlbumDowngradeException) { + return L10n.global().errorAlbumDowngrade; } return exception.toString(); } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9b4e27b2..b61d19bc 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1099,5 +1099,9 @@ "errorServerError": "Server error. Please make sure the server is setup correctly", "@errorServerError": { "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" } } \ No newline at end of file diff --git a/lib/l10n/untranslated-messages.txt b/lib/l10n/untranslated-messages.txt index a873507b..670855a6 100644 --- a/lib/l10n/untranslated-messages.txt +++ b/lib/l10n/untranslated-messages.txt @@ -48,7 +48,8 @@ "fixAllTooltip", "missingShareDescription", "extraShareDescription", - "defaultButtonLabel" + "defaultButtonLabel", + "errorAlbumDowngrade" ], "de": [ @@ -114,7 +115,8 @@ "fixAllTooltip", "missingShareDescription", "extraShareDescription", - "defaultButtonLabel" + "defaultButtonLabel", + "errorAlbumDowngrade" ], "el": [ @@ -235,11 +237,13 @@ "fixAllTooltip", "missingShareDescription", "extraShareDescription", - "defaultButtonLabel" + "defaultButtonLabel", + "errorAlbumDowngrade" ], "es": [ - "settingsMapProviderTitle" + "settingsMapProviderTitle", + "errorAlbumDowngrade" ], "fr": [ @@ -340,7 +344,8 @@ "fixAllTooltip", "missingShareDescription", "extraShareDescription", - "defaultButtonLabel" + "defaultButtonLabel", + "errorAlbumDowngrade" ], "ru": [ @@ -414,6 +419,7 @@ "fixAllTooltip", "missingShareDescription", "extraShareDescription", - "defaultButtonLabel" + "defaultButtonLabel", + "errorAlbumDowngrade" ] } diff --git a/lib/use_case/update_album.dart b/lib/use_case/update_album.dart index 687513ea..3ebe202b 100644 --- a/lib/use_case/update_album.dart +++ b/lib/use_case/update_album.dart @@ -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/provider.dart'; import 'package:nc_photos/event/event.dart'; +import 'package:nc_photos/exception.dart'; class UpdateAlbum { UpdateAlbum(this.albumRepo); Future 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; if (provider is AlbumStaticProvider) { await albumRepo.update(