diff --git a/mobile/lib/db/collections_db.dart b/mobile/lib/db/collections_db.dart index c73b9a8e35..6c57410962 100644 --- a/mobile/lib/db/collections_db.dart +++ b/mobile/lib/db/collections_db.dart @@ -6,6 +6,7 @@ import 'package:path_provider/path_provider.dart'; import "package:photos/models/api/collection/public_url.dart"; import "package:photos/models/api/collection/user.dart"; import 'package:photos/models/collection/collection.dart'; +import "package:photos/models/collection/collection_old.dart"; import 'package:sqflite/sqflite.dart'; import 'package:sqflite_migration/sqflite_migration.dart'; @@ -192,6 +193,10 @@ class CollectionsDB { } Future insert(List collections) async { + throw UnimplementedError(); + } + + Future insertOld(List collections) async { final db = await instance.database; var batch = db.batch(); int batchCounter = 0; @@ -211,10 +216,10 @@ class CollectionsDB { await batch.commit(noResult: true); } - Future> getAllCollections() async { + Future> getAllCollections() async { final db = await instance.database; final rows = await db.query(table); - final collections = []; + final collections = []; for (final row in rows) { collections.add(_convertToCollection(row)); } @@ -248,7 +253,7 @@ class CollectionsDB { ); } - Map _getRowForCollection(Collection collection) { + Map _getRowForCollection(CollectionV2 collection) { final row = {}; row[columnID] = collection.id; row[columnOwner] = collection.owner.toJson(); @@ -281,8 +286,8 @@ class CollectionsDB { return row; } - Collection _convertToCollection(Map row) { - final Collection result = Collection( + CollectionV2 _convertToCollection(Map row) { + final CollectionV2 result = CollectionV2( row[columnID], User.fromJson(row[columnOwner]), row[columnEncryptedKey], diff --git a/mobile/lib/models/collection/collection.dart b/mobile/lib/models/collection/collection.dart index c2186a199b..1bae6ee81e 100644 --- a/mobile/lib/models/collection/collection.dart +++ b/mobile/lib/models/collection/collection.dart @@ -3,6 +3,7 @@ import 'dart:core'; import 'package:flutter/foundation.dart'; import "package:photos/models/api/collection/public_url.dart"; import "package:photos/models/api/collection/user.dart"; +import "package:photos/models/collection/collection_old.dart"; import "package:photos/models/metadata/collection_magic.dart"; import "package:photos/models/metadata/common_keys.dart"; @@ -10,33 +11,17 @@ class Collection { final int id; final User owner; final String encryptedKey; - final String? keyDecryptionNonce; - @Deprecated("Use collectionName instead") + final String keyDecryptionNonce; String? name; - - // encryptedName & nameDecryptionNonce will be null for collections - // created before we started encrypting collection name - final String? encryptedName; - final String? nameDecryptionNonce; final CollectionType type; - final CollectionAttributes attributes; final List sharees; final List publicURLs; final int updationTime; final bool isDeleted; - - // In early days before public launch, we used to store collection name - // un-encrypted. decryptName will be value either decrypted value for - // encryptedName or name itself. - String? decryptedName; - - // decryptedPath will be null for collections now owned by user, deleted - // collections, && collections which don't have a path. The path is used - // to map local on-device album on mobile to remote collection on ente. - String? decryptedPath; - String? mMdEncodedJson; - String? mMdPubEncodedJson; - String? sharedMmdJson; + final String? localPath; + String mMdEncodedJson; + String mMdPubEncodedJson; + String sharedMmdJson; int mMdVersion = 0; int mMbPubVersion = 0; int sharedMmdVersion = 0; @@ -45,14 +30,13 @@ class Collection { ShareeMagicMetadata? _sharedMmd; CollectionMagicMetadata get magicMetadata => - _mmd ?? CollectionMagicMetadata.fromEncodedJson(mMdEncodedJson ?? '{}'); + _mmd ?? CollectionMagicMetadata.fromEncodedJson(mMdEncodedJson); CollectionPubMagicMetadata get pubMagicMetadata => - _pubMmd ?? - CollectionPubMagicMetadata.fromEncodedJson(mMdPubEncodedJson ?? '{}'); + _pubMmd ?? CollectionPubMagicMetadata.fromEncodedJson(mMdPubEncodedJson); ShareeMagicMetadata get sharedMagicMetadata => - _sharedMmd ?? ShareeMagicMetadata.fromEncodedJson(sharedMmdJson ?? '{}'); + _sharedMmd ?? ShareeMagicMetadata.fromEncodedJson(sharedMmdJson); set magicMetadata(CollectionMagicMetadata? val) => _mmd = val; @@ -60,30 +44,55 @@ class Collection { set sharedMagicMetadata(ShareeMagicMetadata? val) => _sharedMmd = val; - String get displayName => decryptedName ?? name ?? "Unnamed Album"; + String get displayName => + name ?? (isDeleted == true ? 'Delete album' : "Unnamed Album"); - // set the value for both name and decryptedName till we finish migration void setName(String newName) { name = newName; - decryptedName = newName; } - Collection( - this.id, - this.owner, - this.encryptedKey, - this.keyDecryptionNonce, - this.name, - this.encryptedName, - this.nameDecryptionNonce, - this.type, - this.attributes, - this.sharees, - this.publicURLs, - this.updationTime, { + Collection({ + required this.id, + required this.owner, + required this.encryptedKey, + required this.keyDecryptionNonce, + required this.name, + required this.type, + required this.sharees, + required this.publicURLs, + required this.updationTime, + required this.localPath, this.isDeleted = false, + this.mMdEncodedJson = '{}', + this.mMdPubEncodedJson = '{}', + this.sharedMmdJson = '{}', + this.mMdVersion = 0, + this.mMbPubVersion = 0, + this.sharedMmdVersion = 0, }); + factory Collection.fromOldCollection(CollectionV2 collection) { + return Collection( + id: collection.id, + owner: collection.owner, + encryptedKey: collection.encryptedKey, + keyDecryptionNonce: collection.keyDecryptionNonce!, + name: collection.displayName, + type: collection.type, + sharees: collection.sharees, + publicURLs: collection.publicURLs, + updationTime: collection.updationTime, + localPath: collection.decryptedPath, + isDeleted: collection.isDeleted, + mMbPubVersion: collection.mMbPubVersion, + mMdPubEncodedJson: collection.mMdPubEncodedJson ?? '{}', + mMdVersion: collection.mMdVersion, + mMdEncodedJson: collection.mMdEncodedJson ?? '{}', + sharedMmdJson: collection.sharedMmdJson ?? '{}', + sharedMmdVersion: collection.sharedMmdVersion, + ); + } + bool isArchived() { return mMdVersion > 0 && magicMetadata.visibility == archiveVisibility; } @@ -172,7 +181,10 @@ class Collection { // device album based on path. The path is nothing but the name of the device // album. bool canLinkToDevicePath(int userID) { - return isOwner(userID) && !isDeleted && attributes.encryptedPath != null; + return isOwner(userID) && + !isDeleted && + localPath != null && + localPath != ''; } void updateSharees(List newSharees) { @@ -186,47 +198,42 @@ class Collection { String? encryptedKey, String? keyDecryptionNonce, String? name, - String? encryptedName, - String? nameDecryptionNonce, CollectionType? type, - CollectionAttributes? attributes, List? sharees, List? publicURLs, int? updationTime, bool? isDeleted, + String? localPath, String? mMdEncodedJson, int? mMdVersion, - String? decryptedName, - String? decryptedPath, + String? mMdPubEncodedJson, + int? mMbPubVersion, + String? sharedMmdJson, + int? sharedMmdVersion, }) { final Collection result = Collection( - id ?? this.id, - owner ?? this.owner, - encryptedKey ?? this.encryptedKey, - keyDecryptionNonce ?? this.keyDecryptionNonce, - name ?? this.name, - encryptedName ?? this.encryptedName, - nameDecryptionNonce ?? this.nameDecryptionNonce, - type ?? this.type, - attributes ?? this.attributes, - sharees ?? this.sharees, - publicURLs ?? this.publicURLs, - updationTime ?? this.updationTime, + id: id ?? this.id, + owner: owner ?? this.owner, + encryptedKey: encryptedKey ?? this.encryptedKey, + keyDecryptionNonce: keyDecryptionNonce ?? this.keyDecryptionNonce, + name: name ?? this.name, + type: type ?? this.type, + sharees: sharees ?? this.sharees, + publicURLs: publicURLs ?? this.publicURLs, + updationTime: updationTime ?? this.updationTime, + localPath: localPath ?? this.localPath, isDeleted: isDeleted ?? this.isDeleted, + mMdEncodedJson: mMdEncodedJson ?? this.mMdEncodedJson, + mMdVersion: mMdVersion ?? this.mMdVersion, + mMdPubEncodedJson: mMdPubEncodedJson ?? this.mMdPubEncodedJson, + mMbPubVersion: mMbPubVersion ?? this.mMbPubVersion, + sharedMmdJson: sharedMmdJson ?? this.sharedMmdJson, + sharedMmdVersion: sharedMmdVersion ?? this.sharedMmdVersion, ); - result.mMdVersion = mMdVersion ?? this.mMdVersion; - result.mMdEncodedJson = mMdEncodedJson ?? this.mMdEncodedJson; - result.decryptedName = decryptedName ?? this.decryptedName; - result.decryptedPath = decryptedPath ?? this.decryptedPath; - result.mMbPubVersion = mMbPubVersion; - result.mMdPubEncodedJson = mMdPubEncodedJson; - result.sharedMmdVersion = sharedMmdVersion; - result.sharedMmdJson = sharedMmdJson; return result; } static Collection fromMap(Map map) { - final sharees = (map['sharees'] == null || map['sharees'].length == 0) ? [] : List.from(map['sharees'].map((x) => User.fromMap(x))); @@ -237,19 +244,23 @@ class Collection { map['publicURLs'].map((x) => PublicURL.fromMap(x)), ); return Collection( - map['id'], - User.fromMap(map['owner']), - map['encryptedKey'], - map['keyDecryptionNonce'], - map['name'], - map['encryptedName'], - map['nameDecryptionNonce'], - typeFromString(map['type']), - CollectionAttributes.fromMap(map['attributes']), - sharees, - publicURLs, - map['updationTime'], + id: map['id'], + owner: User.fromMap(map['owner']), + encryptedKey: map['encryptedKey'], + keyDecryptionNonce: map['keyDecryptionNonce'], + name: map['name'], + type: typeFromString(map['type']), + sharees: sharees, + publicURLs: publicURLs, + updationTime: map['updationTime'], + localPath: map['localPath'], isDeleted: map['isDeleted'] ?? false, + mMdEncodedJson: map['mMdEncodedJson'] ?? '{}', + mMdPubEncodedJson: map['mMdPubEncodedJson'] ?? '{}', + sharedMmdJson: map['sharedMmdJson'] ?? '{}', + mMdVersion: map['mMdVersion'] ?? 0, + mMbPubVersion: map['mMbPubVersion'] ?? 0, + sharedMmdVersion: map['sharedMmdVersion'] ?? 0, ); } } diff --git a/mobile/lib/models/collection/collection_old.dart b/mobile/lib/models/collection/collection_old.dart new file mode 100644 index 0000000000..24d3ed36d7 --- /dev/null +++ b/mobile/lib/models/collection/collection_old.dart @@ -0,0 +1,252 @@ +import "package:photos/models/api/collection/public_url.dart"; +import "package:photos/models/api/collection/user.dart"; +import "package:photos/models/collection/collection.dart"; +import "package:photos/models/metadata/collection_magic.dart"; +import "package:photos/models/metadata/common_keys.dart"; + +class CollectionV2 { + final int id; + final User owner; + final String encryptedKey; + final String? keyDecryptionNonce; + @Deprecated("Use collectionName instead") + String? name; + + // encryptedName & nameDecryptionNonce will be null for collections + // created before we started encrypting collection name + final String? encryptedName; + final String? nameDecryptionNonce; + final CollectionType type; + final CollectionAttributes attributes; + final List sharees; + final List publicURLs; + final int updationTime; + final bool isDeleted; + + // In early days before public launch, we used to store collection name + // un-encrypted. decryptName will be value either decrypted value for + // encryptedName or name itself. + String? decryptedName; + + // decryptedPath will be null for collections now owned by user, deleted + // collections, && collections which don't have a path. The path is used + // to map local on-device album on mobile to remote collection on ente. + String? decryptedPath; + String? mMdEncodedJson; + String? mMdPubEncodedJson; + String? sharedMmdJson; + int mMdVersion = 0; + int mMbPubVersion = 0; + int sharedMmdVersion = 0; + CollectionMagicMetadata? _mmd; + CollectionPubMagicMetadata? _pubMmd; + ShareeMagicMetadata? _sharedMmd; + + CollectionMagicMetadata get magicMetadata => + _mmd ?? CollectionMagicMetadata.fromEncodedJson(mMdEncodedJson ?? '{}'); + + CollectionPubMagicMetadata get pubMagicMetadata => + _pubMmd ?? + CollectionPubMagicMetadata.fromEncodedJson(mMdPubEncodedJson ?? '{}'); + + ShareeMagicMetadata get sharedMagicMetadata => + _sharedMmd ?? ShareeMagicMetadata.fromEncodedJson(sharedMmdJson ?? '{}'); + + set magicMetadata(CollectionMagicMetadata? val) => _mmd = val; + + set pubMagicMetadata(CollectionPubMagicMetadata? val) => _pubMmd = val; + + set sharedMagicMetadata(ShareeMagicMetadata? val) => _sharedMmd = val; + + String get displayName => decryptedName ?? name ?? "Unnamed Album"; + + // set the value for both name and decryptedName till we finish migration + void setName(String newName) { + name = newName; + decryptedName = newName; + } + + CollectionV2( + this.id, + this.owner, + this.encryptedKey, + this.keyDecryptionNonce, + this.name, + this.encryptedName, + this.nameDecryptionNonce, + this.type, + this.attributes, + this.sharees, + this.publicURLs, + this.updationTime, { + this.isDeleted = false, + }); + + bool isArchived() { + return mMdVersion > 0 && magicMetadata.visibility == archiveVisibility; + } + + bool hasShareeArchived() { + return sharedMmdVersion > 0 && + sharedMagicMetadata.visibility == archiveVisibility; + } + + // hasLink returns true if there's any link attached to the collection + // including expired links + bool get hasLink => publicURLs.isNotEmpty; + + bool get hasCover => (pubMagicMetadata.coverID ?? 0) > 0; + + // hasSharees returns true if the collection is shared with other ente users + bool get hasSharees => sharees.isNotEmpty; + + bool get isPinned => (magicMetadata.order ?? 0) != 0; + + bool isHidden() { + if (isDefaultHidden()) { + return true; + } + return mMdVersion > 0 && (magicMetadata.visibility == hiddenVisibility); + } + + bool isDefaultHidden() { + return (magicMetadata.subType ?? 0) == subTypeDefaultHidden; + } + + bool isQuickLinkCollection() { + return (magicMetadata.subType ?? 0) == subTypeSharedFilesCollection && + !hasSharees; + } + + List getSharees() { + return sharees; + } + + bool isOwner(int userID) { + return (owner.id ?? -100) == userID; + } + + bool isDownloadEnabledForPublicLink() { + if (publicURLs.isEmpty) { + return false; + } + return publicURLs.first.enableDownload; + } + + bool isCollectEnabledForPublicLink() { + if (publicURLs.isEmpty) { + return false; + } + return publicURLs.first.enableCollect; + } + + bool get isJoinEnabled { + if (publicURLs.isEmpty) { + return false; + } + return publicURLs.first.enableJoin; + } + + CollectionParticipantRole getRole(int userID) { + if (isOwner(userID)) { + return CollectionParticipantRole.owner; + } + if (sharees.isEmpty) { + return CollectionParticipantRole.unknown; + } + for (final User u in sharees) { + if (u.id == userID) { + if (u.isViewer) { + return CollectionParticipantRole.viewer; + } else if (u.isCollaborator) { + return CollectionParticipantRole.collaborator; + } + } + } + return CollectionParticipantRole.unknown; + } + + // canLinkToDevicePath returns true if the collection can be linked to local + // device album based on path. The path is nothing but the name of the device + // album. + bool canLinkToDevicePath(int userID) { + return isOwner(userID) && !isDeleted && attributes.encryptedPath != null; + } + + void updateSharees(List newSharees) { + sharees.clear(); + sharees.addAll(newSharees); + } + + CollectionV2 copyWith({ + int? id, + User? owner, + String? encryptedKey, + String? keyDecryptionNonce, + String? name, + String? encryptedName, + String? nameDecryptionNonce, + CollectionType? type, + CollectionAttributes? attributes, + List? sharees, + List? publicURLs, + int? updationTime, + bool? isDeleted, + String? mMdEncodedJson, + int? mMdVersion, + String? decryptedName, + String? decryptedPath, + }) { + final CollectionV2 result = CollectionV2( + id ?? this.id, + owner ?? this.owner, + encryptedKey ?? this.encryptedKey, + keyDecryptionNonce ?? this.keyDecryptionNonce, + name ?? this.name, + encryptedName ?? this.encryptedName, + nameDecryptionNonce ?? this.nameDecryptionNonce, + type ?? this.type, + attributes ?? this.attributes, + sharees ?? this.sharees, + publicURLs ?? this.publicURLs, + updationTime ?? this.updationTime, + isDeleted: isDeleted ?? this.isDeleted, + ); + result.mMdVersion = mMdVersion ?? this.mMdVersion; + result.mMdEncodedJson = mMdEncodedJson ?? this.mMdEncodedJson; + result.decryptedName = decryptedName ?? this.decryptedName; + result.decryptedPath = decryptedPath ?? this.decryptedPath; + result.mMbPubVersion = mMbPubVersion; + result.mMdPubEncodedJson = mMdPubEncodedJson; + result.sharedMmdVersion = sharedMmdVersion; + result.sharedMmdJson = sharedMmdJson; + return result; + } + + static CollectionV2 fromMap(Map map) { + final sharees = (map['sharees'] == null || map['sharees'].length == 0) + ? [] + : List.from(map['sharees'].map((x) => User.fromMap(x))); + final publicURLs = + (map['publicURLs'] == null || map['publicURLs'].length == 0) + ? [] + : List.from( + map['publicURLs'].map((x) => PublicURL.fromMap(x)), + ); + return CollectionV2( + map['id'], + User.fromMap(map['owner']), + map['encryptedKey'], + map['keyDecryptionNonce'], + map['name'], + map['encryptedName'], + map['nameDecryptionNonce'], + typeFromString(map['type']), + CollectionAttributes.fromMap(map['attributes']), + sharees, + publicURLs, + map['updationTime'], + isDeleted: map['isDeleted'] ?? false, + ); + } +} diff --git a/mobile/lib/services/collections_service.dart b/mobile/lib/services/collections_service.dart index fd4df26e6f..7528dda73d 100644 --- a/mobile/lib/services/collections_service.dart +++ b/mobile/lib/services/collections_service.dart @@ -30,6 +30,7 @@ import "package:photos/models/api/collection/user.dart"; import 'package:photos/models/collection/collection.dart'; import 'package:photos/models/collection/collection_file_item.dart'; import 'package:photos/models/collection/collection_items.dart'; +import "package:photos/models/collection/collection_old.dart"; import 'package:photos/models/file/file.dart'; import "package:photos/models/files_split.dart"; import "package:photos/models/metadata/collection_magic.dart"; @@ -679,26 +680,34 @@ class CollectionsService { fetchCollectionByID(collectionID); throw AssertionError('collectionID $collectionID is not cached'); } - _cachedKeys[collectionID] = - _getAndCacheDecryptedKey(collection, source: "getCollectionKey"); + _cachedKeys[collectionID] = _getAndCacheDecryptedKey( + collection.id, + collection.isOwner(_config.getUserID()!), + encKey: collection.encryptedKey, + encKeyNonce: collection.keyDecryptionNonce, + source: "getCollectionKey", + ); } return _cachedKeys[collectionID]!; } Uint8List _getAndCacheDecryptedKey( - Collection collection, { + int id, + bool isOwner, { + required String encKey, + required String encKeyNonce, String source = "", }) { - if (_cachedKeys.containsKey(collection.id)) { - return _cachedKeys[collection.id]!; + if (_cachedKeys.containsKey(id)) { + return _cachedKeys[id]!; } debugPrint( - "Compute collection decryption key for ${collection.id} source" + "Compute collection decryption key for $id source" " $source", ); - final encryptedKey = CryptoUtil.base642bin(collection.encryptedKey); + final encryptedKey = CryptoUtil.base642bin(encKey); Uint8List? collectionKey; - if (collection.owner.id == _config.getUserID()) { + if (isOwner) { // If the collection is owned by the user, decrypt with the master key if (_config.getKey() == null) { // Possible during AppStore account migration, where SecureStorage @@ -708,7 +717,7 @@ class CollectionsService { collectionKey = CryptoUtil.decryptSync( encryptedKey, _config.getKey()!, - CryptoUtil.base642bin(collection.keyDecryptionNonce!), + CryptoUtil.base642bin(encKeyNonce), ); } else { // If owned by a different user, decrypt with the public key @@ -718,7 +727,7 @@ class CollectionsService { _config.getSecretKey()!, ); } - _cachedKeys[collection.id] = collectionKey; + _cachedKeys[id] = collectionKey; return collectionKey; } @@ -730,7 +739,7 @@ class CollectionsService { await updateMagicMetadata(collection, {"subType": 0}); } final encryptedName = CryptoUtil.encryptSync( - utf8.encode(newName) as Uint8List, + utf8.encode(newName), getCollectionKey(collection.id), ); await _enteDio.post( @@ -1060,7 +1069,7 @@ class CollectionsService { ); final collectionData = response.data["collection"]; - final Collection collection = Collection.fromMap(collectionData); + final CollectionV2 collection = CollectionV2.fromMap(collectionData); final Uint8List collectionKey = Uint8List.fromList(Base58Decode(albumKey)); @@ -1087,7 +1096,22 @@ class CollectionsService { } collection.setName(_getDecryptedCollectionName(collection)); - return collection; + final Collection result = Collection( + id: collection.id, + owner: collection.owner, + name: collection.displayName, + type: collection.type, + updationTime: collection.updationTime, + encryptedKey: collection.encryptedKey, + keyDecryptionNonce: collection.keyDecryptionNonce!, + sharees: collection.sharees, + publicURLs: collection.publicURLs, + localPath: null, + isDeleted: collection.isDeleted, + mMbPubVersion: collection.mMbPubVersion, + mMdPubEncodedJson: collection.mMdPubEncodedJson ?? '{}', + ); + return result; } catch (e, s) { _logger.warning(e, s); _logger.severe("Failed to fetch public collection"); @@ -1204,10 +1228,15 @@ class CollectionsService { Future _fromRemoteCollection( Map collectionData, ) async { - final Collection collection = Collection.fromMap(collectionData); + final CollectionV2 collection = CollectionV2.fromMap(collectionData); if (!collection.isDeleted) { - final collectionKey = - _getAndCacheDecryptedKey(collection, source: "fetchDecryptMeta"); + final collectionKey = _getAndCacheDecryptedKey( + collection.id, + collection.isOwner(_config.getUserID()!), + encKey: collection.encryptedKey, + encKeyNonce: collection.keyDecryptionNonce!, + source: "fetchDecryptMeta", + ); if (collectionData['magicMetadata'] != null) { final utfEncodedMmd = await CryptoUtil.decryptChaCha( CryptoUtil.base642bin(collectionData['magicMetadata']['data']), @@ -1259,7 +1288,7 @@ class CollectionsService { if (collection.canLinkToDevicePath(_config.getUserID()!)) { collection.decryptedPath = (_decryptCollectionPath(collection)); } - return collection; + return Collection.fromOldCollection(collection); } Collection? getCollectionByID(int collectionID) { @@ -1874,31 +1903,31 @@ class CollectionsService { } @Deprecated("Use _cacheLocalPathAndCollection instead") - Collection _cacheCollectionAttributes(Collection collection) { + CollectionV2 _cacheCollectionAttributes(CollectionV2 collection) { final String decryptedName = _getDecryptedCollectionName(collection); collection.setName(decryptedName); if (collection.canLinkToDevicePath(_config.getUserID()!)) { - _localPathToCollectionID[_decryptCollectionPath(collection)] = - collection.id; + collection.decryptedPath = _decryptCollectionPath(collection); + _localPathToCollectionID[collection.decryptedPath!] = collection.id; } - _collectionIDToCollections[collection.id] = collection; + final Collection c = Collection.fromOldCollection(collection); + _collectionIDToCollections[collection.id] = c; return collection; } Collection _cacheLocalPathAndCollection(Collection collection) { assert( - collection.decryptedName != null, + collection.name != null, "decryptedName should be already set", ); - if (collection.canLinkToDevicePath(_config.getUserID()!) && - (collection.decryptedPath ?? '').isNotEmpty) { - _localPathToCollectionID[collection.decryptedPath!] = collection.id; + if (collection.canLinkToDevicePath(_config.getUserID()!)) { + _localPathToCollectionID[collection.localPath!] = collection.id; } _collectionIDToCollections[collection.id] = collection; return collection; } - String _decryptCollectionPath(Collection collection) { + String _decryptCollectionPath(CollectionV2 collection) { final existingPath = collection.decryptedPath; if (existingPath != null && existingPath.isNotEmpty) { debugPrint("Using cached decrypted path for collection ${collection.id}"); @@ -1925,7 +1954,7 @@ class CollectionsService { return _prefs.containsKey(_collectionsSyncTimeKey); } - String _getDecryptedCollectionName(Collection collection) { + String _getDecryptedCollectionName(CollectionV2 collection) { if (collection.isDeleted) { return "Deleted Album"; } @@ -1933,7 +1962,10 @@ class CollectionsService { collection.encryptedName!.isNotEmpty) { try { final collectionKey = _getAndCacheDecryptedKey( - collection, + collection.id, + collection.isOwner(_config.getUserID()!), + encKey: collection.encryptedKey, + encKeyNonce: collection.keyDecryptionNonce!, source: "Name", ); final result = CryptoUtil.decryptSync(