|
@@ -1,5 +1,3 @@
|
|
-// @dart=2.9
|
|
|
|
-
|
|
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:convert';
|
|
import 'dart:math';
|
|
import 'dart:math';
|
|
@@ -47,17 +45,18 @@ class CollectionsService {
|
|
|
|
|
|
final _logger = Logger("CollectionsService");
|
|
final _logger = Logger("CollectionsService");
|
|
|
|
|
|
- CollectionsDB _db;
|
|
|
|
- FilesDB _filesDB;
|
|
|
|
- Configuration _config;
|
|
|
|
- SharedPreferences _prefs;
|
|
|
|
- Future<List<File>> _cachedLatestFiles;
|
|
|
|
|
|
+ late CollectionsDB _db;
|
|
|
|
+ late FilesDB _filesDB;
|
|
|
|
+ late Configuration _config;
|
|
|
|
+ late SharedPreferences _prefs;
|
|
|
|
+
|
|
final _enteDio = Network.instance.enteDio;
|
|
final _enteDio = Network.instance.enteDio;
|
|
final _localPathToCollectionID = <String, int>{};
|
|
final _localPathToCollectionID = <String, int>{};
|
|
final _collectionIDToCollections = <int, Collection>{};
|
|
final _collectionIDToCollections = <int, Collection>{};
|
|
final _cachedKeys = <int, Uint8List>{};
|
|
final _cachedKeys = <int, Uint8List>{};
|
|
final _cachedUserIdToUser = <int, User>{};
|
|
final _cachedUserIdToUser = <int, User>{};
|
|
- Collection cachedDefaultHiddenCollection;
|
|
|
|
|
|
+ Collection? cachedDefaultHiddenCollection;
|
|
|
|
+ Future<List<File>>? _cachedLatestFiles;
|
|
|
|
|
|
CollectionsService._privateConstructor() {
|
|
CollectionsService._privateConstructor() {
|
|
_db = CollectionsDB.instance;
|
|
_db = CollectionsDB.instance;
|
|
@@ -117,7 +116,7 @@ class CollectionsService {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// remove reference for incoming collections when unshared/deleted
|
|
// remove reference for incoming collections when unshared/deleted
|
|
- if (collection.isDeleted && ownerID != collection?.owner?.id) {
|
|
|
|
|
|
+ if (collection.isDeleted && ownerID != collection.owner?.id) {
|
|
await _db.deleteCollection(collection.id);
|
|
await _db.deleteCollection(collection.id);
|
|
} else {
|
|
} else {
|
|
// keep entry for deletedCollection as collectionKey may be used during
|
|
// keep entry for deletedCollection as collectionKey may be used during
|
|
@@ -205,10 +204,10 @@ class CollectionsService {
|
|
|
|
|
|
Future<List<File>> getLatestCollectionFiles() {
|
|
Future<List<File>> getLatestCollectionFiles() {
|
|
_cachedLatestFiles ??= _filesDB.getLatestCollectionFiles();
|
|
_cachedLatestFiles ??= _filesDB.getLatestCollectionFiles();
|
|
- return _cachedLatestFiles;
|
|
|
|
|
|
+ return _cachedLatestFiles!;
|
|
}
|
|
}
|
|
|
|
|
|
- Future<void> setCollectionSyncTime(int collectionID, int time) async {
|
|
|
|
|
|
+ Future<bool> setCollectionSyncTime(int collectionID, int? time) async {
|
|
final key = _collectionSyncTimeKeyPrefix + collectionID.toString();
|
|
final key = _collectionSyncTimeKeyPrefix + collectionID.toString();
|
|
if (time == null) {
|
|
if (time == null) {
|
|
return _prefs.remove(key);
|
|
return _prefs.remove(key);
|
|
@@ -224,21 +223,21 @@ class CollectionsService {
|
|
.toList();
|
|
.toList();
|
|
}
|
|
}
|
|
|
|
|
|
- User getFileOwner(int userID, int collectionID) {
|
|
|
|
|
|
+ User getFileOwner(int userID, int? collectionID) {
|
|
if (_cachedUserIdToUser.containsKey(userID)) {
|
|
if (_cachedUserIdToUser.containsKey(userID)) {
|
|
- return _cachedUserIdToUser[userID];
|
|
|
|
|
|
+ return _cachedUserIdToUser[userID]!;
|
|
}
|
|
}
|
|
if (collectionID != null) {
|
|
if (collectionID != null) {
|
|
- final Collection collection = getCollectionByID(collectionID);
|
|
|
|
|
|
+ final Collection? collection = getCollectionByID(collectionID);
|
|
if (collection != null) {
|
|
if (collection != null) {
|
|
- if (collection.owner.id == userID) {
|
|
|
|
- _cachedUserIdToUser[userID] = collection.owner;
|
|
|
|
|
|
+ if (collection.owner?.id == userID) {
|
|
|
|
+ _cachedUserIdToUser[userID] = collection.owner!;
|
|
} else {
|
|
} else {
|
|
final matchingUser = collection.getSharees().firstWhereOrNull(
|
|
final matchingUser = collection.getSharees().firstWhereOrNull(
|
|
(u) => u.id == userID,
|
|
(u) => u.id == userID,
|
|
);
|
|
);
|
|
if (matchingUser != null) {
|
|
if (matchingUser != null) {
|
|
- _cachedUserIdToUser[userID] = collection.owner;
|
|
|
|
|
|
+ _cachedUserIdToUser[userID] = collection.owner!;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -265,20 +264,20 @@ class CollectionsService {
|
|
if (includeCollabCollections) {
|
|
if (includeCollabCollections) {
|
|
usersCollection.removeWhere(
|
|
usersCollection.removeWhere(
|
|
(c) =>
|
|
(c) =>
|
|
- (c.owner.id != userID) &&
|
|
|
|
|
|
+ (c.owner?.id != userID) &&
|
|
(c.getSharees().any((u) => (u.id ?? -1) == userID && u.isViewer)),
|
|
(c.getSharees().any((u) => (u.id ?? -1) == userID && u.isViewer)),
|
|
);
|
|
);
|
|
} else {
|
|
} else {
|
|
- usersCollection.removeWhere((c) => c.owner.id != userID);
|
|
|
|
|
|
+ usersCollection.removeWhere((c) => c.owner?.id != userID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
final latestCollectionFiles = await getLatestCollectionFiles();
|
|
final latestCollectionFiles = await getLatestCollectionFiles();
|
|
final Map<int, File> collectionToThumbnailMap = Map.fromEntries(
|
|
final Map<int, File> collectionToThumbnailMap = Map.fromEntries(
|
|
- latestCollectionFiles.map((e) => MapEntry(e.collectionID, e)),
|
|
|
|
|
|
+ latestCollectionFiles.map((e) => MapEntry(e.collectionID!, e)),
|
|
);
|
|
);
|
|
|
|
|
|
for (final c in usersCollection) {
|
|
for (final c in usersCollection) {
|
|
- final File thumbnail = collectionToThumbnailMap[c.id];
|
|
|
|
|
|
+ final File? thumbnail = collectionToThumbnailMap[c.id];
|
|
collectionsWithThumbnail.add(CollectionWithThumbnail(c, thumbnail));
|
|
collectionsWithThumbnail.add(CollectionWithThumbnail(c, thumbnail));
|
|
}
|
|
}
|
|
return collectionsWithThumbnail;
|
|
return collectionsWithThumbnail;
|
|
@@ -325,12 +324,12 @@ class CollectionsService {
|
|
sharees.add(User.fromMap(user));
|
|
sharees.add(User.fromMap(user));
|
|
}
|
|
}
|
|
_collectionIDToCollections[collectionID] =
|
|
_collectionIDToCollections[collectionID] =
|
|
- _collectionIDToCollections[collectionID].copyWith(sharees: sharees);
|
|
|
|
- unawaited(_db.insert([_collectionIDToCollections[collectionID]]));
|
|
|
|
|
|
+ _collectionIDToCollections[collectionID]!.copyWith(sharees: sharees);
|
|
|
|
+ unawaited(_db.insert([_collectionIDToCollections[collectionID]!]));
|
|
RemoteSyncService.instance.sync(silently: true).ignore();
|
|
RemoteSyncService.instance.sync(silently: true).ignore();
|
|
return sharees;
|
|
return sharees;
|
|
} on DioError catch (e) {
|
|
} on DioError catch (e) {
|
|
- if (e.response.statusCode == 402) {
|
|
|
|
|
|
+ if (e.response?.statusCode == 402) {
|
|
throw SharingNotPermittedForFreeAccountsError();
|
|
throw SharingNotPermittedForFreeAccountsError();
|
|
}
|
|
}
|
|
rethrow;
|
|
rethrow;
|
|
@@ -351,15 +350,14 @@ class CollectionsService {
|
|
sharees.add(User.fromMap(user));
|
|
sharees.add(User.fromMap(user));
|
|
}
|
|
}
|
|
_collectionIDToCollections[collectionID] =
|
|
_collectionIDToCollections[collectionID] =
|
|
- _collectionIDToCollections[collectionID].copyWith(sharees: sharees);
|
|
|
|
- unawaited(_db.insert([_collectionIDToCollections[collectionID]]));
|
|
|
|
|
|
+ _collectionIDToCollections[collectionID]!.copyWith(sharees: sharees);
|
|
|
|
+ unawaited(_db.insert([_collectionIDToCollections[collectionID]!]));
|
|
RemoteSyncService.instance.sync(silently: true).ignore();
|
|
RemoteSyncService.instance.sync(silently: true).ignore();
|
|
return sharees;
|
|
return sharees;
|
|
} catch (e) {
|
|
} catch (e) {
|
|
_logger.severe(e);
|
|
_logger.severe(e);
|
|
rethrow;
|
|
rethrow;
|
|
}
|
|
}
|
|
- RemoteSyncService.instance.sync(silently: true).ignore();
|
|
|
|
}
|
|
}
|
|
|
|
|
|
Future<void> trashCollection(
|
|
Future<void> trashCollection(
|
|
@@ -397,6 +395,29 @@ class CollectionsService {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ Future<void> trashEmptyCollection(Collection collection) async {
|
|
|
|
+ try {
|
|
|
|
+ // While trashing empty albums, we must pass keepFiles flag as True.
|
|
|
|
+ // The server will verify that the collection is actually empty before
|
|
|
|
+ // deleting the files. If keepFiles is set as False and the collection
|
|
|
|
+ // is not empty, then the files in the collections will be moved to trash.
|
|
|
|
+ await _enteDio.delete(
|
|
|
|
+ "/collections/v3/${collection.id}?keepFiles=True&collectionID=${collection.id}",
|
|
|
|
+ );
|
|
|
|
+ final deletedCollection = collection.copyWith(isDeleted: true);
|
|
|
|
+ _collectionIDToCollections[collection.id] = deletedCollection;
|
|
|
|
+ unawaited(_db.insert([deletedCollection]));
|
|
|
|
+ } on DioError catch (e) {
|
|
|
|
+ if (e.response != null) {
|
|
|
|
+ debugPrint("Error " + e.response!.toString());
|
|
|
|
+ }
|
|
|
|
+ rethrow;
|
|
|
|
+ } catch (e) {
|
|
|
|
+ _logger.severe('failed to trash empty collection', e);
|
|
|
|
+ rethrow;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
Future<void> _handleCollectionDeletion(Collection collection) async {
|
|
Future<void> _handleCollectionDeletion(Collection collection) async {
|
|
await _filesDB.deleteCollection(collection.id);
|
|
await _filesDB.deleteCollection(collection.id);
|
|
final deletedCollection = collection.copyWith(isDeleted: true);
|
|
final deletedCollection = collection.copyWith(isDeleted: true);
|
|
@@ -427,7 +448,7 @@ class CollectionsService {
|
|
_cachedKeys[collectionID] =
|
|
_cachedKeys[collectionID] =
|
|
_getAndCacheDecryptedKey(collection, source: "getCollectionKey");
|
|
_getAndCacheDecryptedKey(collection, source: "getCollectionKey");
|
|
}
|
|
}
|
|
- return _cachedKeys[collectionID];
|
|
|
|
|
|
+ return _cachedKeys[collectionID]!;
|
|
}
|
|
}
|
|
|
|
|
|
Uint8List _getAndCacheDecryptedKey(
|
|
Uint8List _getAndCacheDecryptedKey(
|
|
@@ -435,33 +456,31 @@ class CollectionsService {
|
|
String source = "",
|
|
String source = "",
|
|
}) {
|
|
}) {
|
|
if (_cachedKeys.containsKey(collection.id)) {
|
|
if (_cachedKeys.containsKey(collection.id)) {
|
|
- return _cachedKeys[collection.id];
|
|
|
|
|
|
+ return _cachedKeys[collection.id]!;
|
|
}
|
|
}
|
|
debugPrint(
|
|
debugPrint(
|
|
"Compute collection decryption key for ${collection.id} source"
|
|
"Compute collection decryption key for ${collection.id} source"
|
|
" $source",
|
|
" $source",
|
|
);
|
|
);
|
|
final encryptedKey = Sodium.base642bin(collection.encryptedKey);
|
|
final encryptedKey = Sodium.base642bin(collection.encryptedKey);
|
|
- Uint8List collectionKey;
|
|
|
|
- if (collection.owner.id == _config.getUserID()) {
|
|
|
|
|
|
+ Uint8List? collectionKey;
|
|
|
|
+ if (collection.owner?.id == _config.getUserID()) {
|
|
if (_config.getKey() == null) {
|
|
if (_config.getKey() == null) {
|
|
throw Exception("key can not be null");
|
|
throw Exception("key can not be null");
|
|
}
|
|
}
|
|
collectionKey = CryptoUtil.decryptSync(
|
|
collectionKey = CryptoUtil.decryptSync(
|
|
encryptedKey,
|
|
encryptedKey,
|
|
_config.getKey(),
|
|
_config.getKey(),
|
|
- Sodium.base642bin(collection.keyDecryptionNonce),
|
|
|
|
|
|
+ Sodium.base642bin(collection.keyDecryptionNonce!),
|
|
);
|
|
);
|
|
} else {
|
|
} else {
|
|
collectionKey = CryptoUtil.openSealSync(
|
|
collectionKey = CryptoUtil.openSealSync(
|
|
encryptedKey,
|
|
encryptedKey,
|
|
- Sodium.base642bin(_config.getKeyAttributes().publicKey),
|
|
|
|
- _config.getSecretKey(),
|
|
|
|
|
|
+ Sodium.base642bin(_config.getKeyAttributes()!.publicKey),
|
|
|
|
+ _config.getSecretKey()!,
|
|
);
|
|
);
|
|
}
|
|
}
|
|
- if (collectionKey != null) {
|
|
|
|
- _cachedKeys[collection.id] = collectionKey;
|
|
|
|
- }
|
|
|
|
|
|
+ _cachedKeys[collection.id] = collectionKey;
|
|
return collectionKey;
|
|
return collectionKey;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -473,15 +492,15 @@ class CollectionsService {
|
|
await updateMagicMetadata(collection, {"subType": 0});
|
|
await updateMagicMetadata(collection, {"subType": 0});
|
|
}
|
|
}
|
|
final encryptedName = CryptoUtil.encryptSync(
|
|
final encryptedName = CryptoUtil.encryptSync(
|
|
- utf8.encode(newName),
|
|
|
|
|
|
+ utf8.encode(newName) as Uint8List,
|
|
getCollectionKey(collection.id),
|
|
getCollectionKey(collection.id),
|
|
);
|
|
);
|
|
await _enteDio.post(
|
|
await _enteDio.post(
|
|
"/collections/rename",
|
|
"/collections/rename",
|
|
data: {
|
|
data: {
|
|
"collectionID": collection.id,
|
|
"collectionID": collection.id,
|
|
- "encryptedName": Sodium.bin2base64(encryptedName.encryptedData),
|
|
|
|
- "nameDecryptionNonce": Sodium.bin2base64(encryptedName.nonce)
|
|
|
|
|
|
+ "encryptedName": Sodium.bin2base64(encryptedName.encryptedData!),
|
|
|
|
+ "nameDecryptionNonce": Sodium.bin2base64(encryptedName.nonce!)
|
|
},
|
|
},
|
|
);
|
|
);
|
|
// trigger sync to fetch the latest name from server
|
|
// trigger sync to fetch the latest name from server
|
|
@@ -508,9 +527,9 @@ class CollectionsService {
|
|
Collection collection,
|
|
Collection collection,
|
|
Map<String, dynamic> newMetadataUpdate,
|
|
Map<String, dynamic> newMetadataUpdate,
|
|
) async {
|
|
) async {
|
|
- final int ownerID = Configuration.instance.getUserID();
|
|
|
|
|
|
+ final int ownerID = Configuration.instance.getUserID()!;
|
|
try {
|
|
try {
|
|
- if (collection.owner.id != ownerID) {
|
|
|
|
|
|
+ if (collection.owner?.id != ownerID) {
|
|
throw AssertionError("cannot modify albums not owned by you");
|
|
throw AssertionError("cannot modify albums not owned by you");
|
|
}
|
|
}
|
|
// read the existing magic metadata and apply new updates to existing data
|
|
// read the existing magic metadata and apply new updates to existing data
|
|
@@ -528,7 +547,7 @@ class CollectionsService {
|
|
|
|
|
|
final key = getCollectionKey(collection.id);
|
|
final key = getCollectionKey(collection.id);
|
|
final encryptedMMd = await CryptoUtil.encryptChaCha(
|
|
final encryptedMMd = await CryptoUtil.encryptChaCha(
|
|
- utf8.encode(jsonEncode(jsonToUpdate)),
|
|
|
|
|
|
+ utf8.encode(jsonEncode(jsonToUpdate)) as Uint8List,
|
|
key,
|
|
key,
|
|
);
|
|
);
|
|
// for required field, the json validator on golang doesn't treat 0 as valid
|
|
// for required field, the json validator on golang doesn't treat 0 as valid
|
|
@@ -539,8 +558,8 @@ class CollectionsService {
|
|
magicMetadata: MetadataRequest(
|
|
magicMetadata: MetadataRequest(
|
|
version: currentVersion,
|
|
version: currentVersion,
|
|
count: jsonToUpdate.length,
|
|
count: jsonToUpdate.length,
|
|
- data: Sodium.bin2base64(encryptedMMd.encryptedData),
|
|
|
|
- header: Sodium.bin2base64(encryptedMMd.header),
|
|
|
|
|
|
+ data: Sodium.bin2base64(encryptedMMd.encryptedData!),
|
|
|
|
+ header: Sodium.bin2base64(encryptedMMd.header!),
|
|
),
|
|
),
|
|
);
|
|
);
|
|
await _enteDio.put(
|
|
await _enteDio.put(
|
|
@@ -552,7 +571,7 @@ class CollectionsService {
|
|
// trigger sync to fetch the latest collection state from server
|
|
// trigger sync to fetch the latest collection state from server
|
|
sync().ignore();
|
|
sync().ignore();
|
|
} on DioError catch (e) {
|
|
} on DioError catch (e) {
|
|
- if (e.response != null && e.response.statusCode == 409) {
|
|
|
|
|
|
+ if (e.response != null && e.response?.statusCode == 409) {
|
|
_logger.severe('collection magic data out of sync');
|
|
_logger.severe('collection magic data out of sync');
|
|
sync().ignore();
|
|
sync().ignore();
|
|
}
|
|
}
|
|
@@ -578,7 +597,7 @@ class CollectionsService {
|
|
CollectionUpdatedEvent(collection.id, <File>[], "shareUrL"),
|
|
CollectionUpdatedEvent(collection.id, <File>[], "shareUrL"),
|
|
);
|
|
);
|
|
} on DioError catch (e) {
|
|
} on DioError catch (e) {
|
|
- if (e.response.statusCode == 402) {
|
|
|
|
|
|
+ if (e.response?.statusCode == 402) {
|
|
throw SharingNotPermittedForFreeAccountsError();
|
|
throw SharingNotPermittedForFreeAccountsError();
|
|
}
|
|
}
|
|
rethrow;
|
|
rethrow;
|
|
@@ -606,7 +625,7 @@ class CollectionsService {
|
|
Bus.instance
|
|
Bus.instance
|
|
.fire(CollectionUpdatedEvent(collection.id, <File>[], "updateUrl"));
|
|
.fire(CollectionUpdatedEvent(collection.id, <File>[], "updateUrl"));
|
|
} on DioError catch (e) {
|
|
} on DioError catch (e) {
|
|
- if (e.response.statusCode == 402) {
|
|
|
|
|
|
+ if (e.response?.statusCode == 402) {
|
|
throw SharingNotPermittedForFreeAccountsError();
|
|
throw SharingNotPermittedForFreeAccountsError();
|
|
}
|
|
}
|
|
rethrow;
|
|
rethrow;
|
|
@@ -621,7 +640,7 @@ class CollectionsService {
|
|
await _enteDio.delete(
|
|
await _enteDio.delete(
|
|
"/collections/share-url/" + collection.id.toString(),
|
|
"/collections/share-url/" + collection.id.toString(),
|
|
);
|
|
);
|
|
- collection.publicURLs.clear();
|
|
|
|
|
|
+ collection.publicURLs?.clear();
|
|
await _db.insert(List.from([collection]));
|
|
await _db.insert(List.from([collection]));
|
|
_cacheCollectionAttributes(collection);
|
|
_cacheCollectionAttributes(collection);
|
|
Bus.instance.fire(
|
|
Bus.instance.fire(
|
|
@@ -647,26 +666,24 @@ class CollectionsService {
|
|
},
|
|
},
|
|
);
|
|
);
|
|
final List<Collection> collections = [];
|
|
final List<Collection> collections = [];
|
|
- if (response != null) {
|
|
|
|
- final c = response.data["collections"];
|
|
|
|
- for (final collectionData in c) {
|
|
|
|
- final collection = Collection.fromMap(collectionData);
|
|
|
|
- if (collectionData['magicMetadata'] != null) {
|
|
|
|
- final decryptionKey =
|
|
|
|
- _getAndCacheDecryptedKey(collection, source: "fetchCollection");
|
|
|
|
- final utfEncodedMmd = await CryptoUtil.decryptChaCha(
|
|
|
|
- Sodium.base642bin(collectionData['magicMetadata']['data']),
|
|
|
|
- decryptionKey,
|
|
|
|
- Sodium.base642bin(collectionData['magicMetadata']['header']),
|
|
|
|
- );
|
|
|
|
- collection.mMdEncodedJson = utf8.decode(utfEncodedMmd);
|
|
|
|
- collection.mMdVersion = collectionData['magicMetadata']['version'];
|
|
|
|
- collection.magicMetadata = CollectionMagicMetadata.fromEncodedJson(
|
|
|
|
- collection.mMdEncodedJson,
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- collections.add(collection);
|
|
|
|
|
|
+ final c = response.data["collections"];
|
|
|
|
+ for (final collectionData in c) {
|
|
|
|
+ final collection = Collection.fromMap(collectionData);
|
|
|
|
+ if (collectionData['magicMetadata'] != null) {
|
|
|
|
+ final decryptionKey =
|
|
|
|
+ _getAndCacheDecryptedKey(collection, source: "fetchCollection");
|
|
|
|
+ final utfEncodedMmd = await CryptoUtil.decryptChaCha(
|
|
|
|
+ Sodium.base642bin(collectionData['magicMetadata']['data']),
|
|
|
|
+ decryptionKey,
|
|
|
|
+ Sodium.base642bin(collectionData['magicMetadata']['header']),
|
|
|
|
+ );
|
|
|
|
+ collection.mMdEncodedJson = utf8.decode(utfEncodedMmd);
|
|
|
|
+ collection.mMdVersion = collectionData['magicMetadata']['version'];
|
|
|
|
+ collection.magicMetadata = CollectionMagicMetadata.fromEncodedJson(
|
|
|
|
+ collection.mMdEncodedJson,
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
+ collections.add(collection);
|
|
}
|
|
}
|
|
return collections;
|
|
return collections;
|
|
} catch (e) {
|
|
} catch (e) {
|
|
@@ -677,28 +694,23 @@ class CollectionsService {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- Collection getCollectionByID(int collectionID) {
|
|
|
|
|
|
+ Collection? getCollectionByID(int collectionID) {
|
|
return _collectionIDToCollections[collectionID];
|
|
return _collectionIDToCollections[collectionID];
|
|
}
|
|
}
|
|
|
|
|
|
Future<Collection> createAlbum(String albumName) async {
|
|
Future<Collection> createAlbum(String albumName) async {
|
|
final key = CryptoUtil.generateKey();
|
|
final key = CryptoUtil.generateKey();
|
|
- final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey());
|
|
|
|
- final encryptedName = CryptoUtil.encryptSync(utf8.encode(albumName), key);
|
|
|
|
|
|
+ final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
|
|
|
|
+ final encryptedName =
|
|
|
|
+ CryptoUtil.encryptSync(utf8.encode(albumName) as Uint8List, key);
|
|
final collection = await createAndCacheCollection(
|
|
final collection = await createAndCacheCollection(
|
|
- Collection(
|
|
|
|
- null,
|
|
|
|
- null,
|
|
|
|
- Sodium.bin2base64(encryptedKeyData.encryptedData),
|
|
|
|
- Sodium.bin2base64(encryptedKeyData.nonce),
|
|
|
|
- null,
|
|
|
|
- Sodium.bin2base64(encryptedName.encryptedData),
|
|
|
|
- Sodium.bin2base64(encryptedName.nonce),
|
|
|
|
- CollectionType.album,
|
|
|
|
- CollectionAttributes(),
|
|
|
|
- null,
|
|
|
|
- null,
|
|
|
|
- null,
|
|
|
|
|
|
+ CreateRequest(
|
|
|
|
+ encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
|
|
|
+ keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
|
|
|
|
+ encryptedName: Sodium.bin2base64(encryptedName.encryptedData!),
|
|
|
|
+ nameDecryptionNonce: Sodium.bin2base64(encryptedName.nonce!),
|
|
|
|
+ type: CollectionType.album,
|
|
|
|
+ attributes: CollectionAttributes(),
|
|
),
|
|
),
|
|
);
|
|
);
|
|
return collection;
|
|
return collection;
|
|
@@ -710,7 +722,7 @@ class CollectionsService {
|
|
final response = await _enteDio.get(
|
|
final response = await _enteDio.get(
|
|
"/collections/$collectionID",
|
|
"/collections/$collectionID",
|
|
);
|
|
);
|
|
- assert(response != null && response.data != null);
|
|
|
|
|
|
+ assert(response.data != null);
|
|
final collectionData = response.data["collection"];
|
|
final collectionData = response.data["collection"];
|
|
final collection = Collection.fromMap(collectionData);
|
|
final collection = Collection.fromMap(collectionData);
|
|
if (collectionData['magicMetadata'] != null) {
|
|
if (collectionData['magicMetadata'] != null) {
|
|
@@ -742,44 +754,38 @@ class CollectionsService {
|
|
|
|
|
|
Future<Collection> getOrCreateForPath(String path) async {
|
|
Future<Collection> getOrCreateForPath(String path) async {
|
|
if (_localPathToCollectionID.containsKey(path)) {
|
|
if (_localPathToCollectionID.containsKey(path)) {
|
|
- final Collection cachedCollection =
|
|
|
|
|
|
+ final Collection? cachedCollection =
|
|
_collectionIDToCollections[_localPathToCollectionID[path]];
|
|
_collectionIDToCollections[_localPathToCollectionID[path]];
|
|
if (cachedCollection != null &&
|
|
if (cachedCollection != null &&
|
|
!cachedCollection.isDeleted &&
|
|
!cachedCollection.isDeleted &&
|
|
- cachedCollection.owner.id == _config.getUserID()) {
|
|
|
|
|
|
+ cachedCollection.owner?.id == _config.getUserID()) {
|
|
return cachedCollection;
|
|
return cachedCollection;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
final key = CryptoUtil.generateKey();
|
|
final key = CryptoUtil.generateKey();
|
|
- final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey());
|
|
|
|
- final encryptedPath = CryptoUtil.encryptSync(utf8.encode(path), key);
|
|
|
|
|
|
+ final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
|
|
|
|
+ final encryptedPath =
|
|
|
|
+ CryptoUtil.encryptSync(utf8.encode(path) as Uint8List, key);
|
|
final collection = await createAndCacheCollection(
|
|
final collection = await createAndCacheCollection(
|
|
- Collection(
|
|
|
|
- null,
|
|
|
|
- null,
|
|
|
|
- Sodium.bin2base64(encryptedKeyData.encryptedData),
|
|
|
|
- Sodium.bin2base64(encryptedKeyData.nonce),
|
|
|
|
- null,
|
|
|
|
- Sodium.bin2base64(encryptedPath.encryptedData),
|
|
|
|
- Sodium.bin2base64(encryptedPath.nonce),
|
|
|
|
- CollectionType.folder,
|
|
|
|
- CollectionAttributes(
|
|
|
|
- encryptedPath: Sodium.bin2base64(encryptedPath.encryptedData),
|
|
|
|
- pathDecryptionNonce: Sodium.bin2base64(encryptedPath.nonce),
|
|
|
|
|
|
+ CreateRequest(
|
|
|
|
+ encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
|
|
|
+ keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
|
|
|
|
+ encryptedName: Sodium.bin2base64(encryptedPath.encryptedData!),
|
|
|
|
+ nameDecryptionNonce: Sodium.bin2base64(encryptedPath.nonce!),
|
|
|
|
+ type: CollectionType.folder,
|
|
|
|
+ attributes: CollectionAttributes(
|
|
|
|
+ encryptedPath: Sodium.bin2base64(encryptedPath.encryptedData!),
|
|
|
|
+ pathDecryptionNonce: Sodium.bin2base64(encryptedPath.nonce!),
|
|
version: 1,
|
|
version: 1,
|
|
),
|
|
),
|
|
- null,
|
|
|
|
- null,
|
|
|
|
- null,
|
|
|
|
),
|
|
),
|
|
);
|
|
);
|
|
return collection;
|
|
return collection;
|
|
}
|
|
}
|
|
|
|
|
|
Future<void> addToCollection(int collectionID, List<File> files) async {
|
|
Future<void> addToCollection(int collectionID, List<File> files) async {
|
|
- final containsUploadedFile = files.firstWhere(
|
|
|
|
|
|
+ final containsUploadedFile = files.firstWhereOrNull(
|
|
(element) => element.uploadedFileID != null,
|
|
(element) => element.uploadedFileID != null,
|
|
- orElse: () => null,
|
|
|
|
) !=
|
|
) !=
|
|
null;
|
|
null;
|
|
if (containsUploadedFile) {
|
|
if (containsUploadedFile) {
|
|
@@ -808,13 +814,13 @@ class CollectionsService {
|
|
file.collectionID = collectionID;
|
|
file.collectionID = collectionID;
|
|
final encryptedKeyData =
|
|
final encryptedKeyData =
|
|
CryptoUtil.encryptSync(key, getCollectionKey(collectionID));
|
|
CryptoUtil.encryptSync(key, getCollectionKey(collectionID));
|
|
- file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData);
|
|
|
|
- file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce);
|
|
|
|
|
|
+ file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
|
|
|
+ file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce!);
|
|
params["files"].add(
|
|
params["files"].add(
|
|
CollectionFileItem(
|
|
CollectionFileItem(
|
|
- file.uploadedFileID,
|
|
|
|
- file.encryptedKey,
|
|
|
|
- file.keyDecryptionNonce,
|
|
|
|
|
|
+ file.uploadedFileID!,
|
|
|
|
+ file.encryptedKey!,
|
|
|
|
+ file.keyDecryptionNonce!,
|
|
).toMap(),
|
|
).toMap(),
|
|
);
|
|
);
|
|
}
|
|
}
|
|
@@ -834,13 +840,13 @@ class CollectionsService {
|
|
|
|
|
|
Future<File> linkLocalFileToExistingUploadedFileInAnotherCollection(
|
|
Future<File> linkLocalFileToExistingUploadedFileInAnotherCollection(
|
|
int destCollectionID, {
|
|
int destCollectionID, {
|
|
- @required File localFileToUpload,
|
|
|
|
- @required File existingUploadedFile,
|
|
|
|
|
|
+ required File localFileToUpload,
|
|
|
|
+ required File existingUploadedFile,
|
|
}) async {
|
|
}) async {
|
|
final params = <String, dynamic>{};
|
|
final params = <String, dynamic>{};
|
|
params["collectionID"] = destCollectionID;
|
|
params["collectionID"] = destCollectionID;
|
|
params["files"] = [];
|
|
params["files"] = [];
|
|
- final int uploadedFileID = existingUploadedFile.uploadedFileID;
|
|
|
|
|
|
+ final int uploadedFileID = existingUploadedFile.uploadedFileID!;
|
|
|
|
|
|
// encrypt the fileKey with destination collection's key
|
|
// encrypt the fileKey with destination collection's key
|
|
final fileKey = decryptFileKey(existingUploadedFile);
|
|
final fileKey = decryptFileKey(existingUploadedFile);
|
|
@@ -848,15 +854,15 @@ class CollectionsService {
|
|
CryptoUtil.encryptSync(fileKey, getCollectionKey(destCollectionID));
|
|
CryptoUtil.encryptSync(fileKey, getCollectionKey(destCollectionID));
|
|
|
|
|
|
localFileToUpload.encryptedKey =
|
|
localFileToUpload.encryptedKey =
|
|
- Sodium.bin2base64(encryptedKeyData.encryptedData);
|
|
|
|
|
|
+ Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
|
localFileToUpload.keyDecryptionNonce =
|
|
localFileToUpload.keyDecryptionNonce =
|
|
- Sodium.bin2base64(encryptedKeyData.nonce);
|
|
|
|
|
|
+ Sodium.bin2base64(encryptedKeyData.nonce!);
|
|
|
|
|
|
params["files"].add(
|
|
params["files"].add(
|
|
CollectionFileItem(
|
|
CollectionFileItem(
|
|
uploadedFileID,
|
|
uploadedFileID,
|
|
- localFileToUpload.encryptedKey,
|
|
|
|
- localFileToUpload.keyDecryptionNonce,
|
|
|
|
|
|
+ localFileToUpload.encryptedKey!,
|
|
|
|
+ localFileToUpload.keyDecryptionNonce!,
|
|
).toMap(),
|
|
).toMap(),
|
|
);
|
|
);
|
|
|
|
|
|
@@ -887,13 +893,13 @@ class CollectionsService {
|
|
null; // So that a new entry is created in the FilesDB
|
|
null; // So that a new entry is created in the FilesDB
|
|
file.collectionID = toCollectionID;
|
|
file.collectionID = toCollectionID;
|
|
final encryptedKeyData = CryptoUtil.encryptSync(key, toCollectionKey);
|
|
final encryptedKeyData = CryptoUtil.encryptSync(key, toCollectionKey);
|
|
- file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData);
|
|
|
|
- file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce);
|
|
|
|
|
|
+ file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
|
|
|
+ file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce!);
|
|
params["files"].add(
|
|
params["files"].add(
|
|
CollectionFileItem(
|
|
CollectionFileItem(
|
|
- file.uploadedFileID,
|
|
|
|
- file.encryptedKey,
|
|
|
|
- file.keyDecryptionNonce,
|
|
|
|
|
|
+ file.uploadedFileID!,
|
|
|
|
+ file.encryptedKey!,
|
|
|
|
+ file.keyDecryptionNonce!,
|
|
).toMap(),
|
|
).toMap(),
|
|
);
|
|
);
|
|
}
|
|
}
|
|
@@ -904,7 +910,7 @@ class CollectionsService {
|
|
);
|
|
);
|
|
await _filesDB.insertMultiple(batch);
|
|
await _filesDB.insertMultiple(batch);
|
|
await TrashDB.instance
|
|
await TrashDB.instance
|
|
- .delete(batch.map((e) => e.uploadedFileID).toList());
|
|
|
|
|
|
+ .delete(batch.map((e) => e.uploadedFileID!).toList());
|
|
Bus.instance.fire(
|
|
Bus.instance.fire(
|
|
CollectionUpdatedEvent(toCollectionID, batch, "restore"),
|
|
CollectionUpdatedEvent(toCollectionID, batch, "restore"),
|
|
);
|
|
);
|
|
@@ -914,7 +920,7 @@ class CollectionsService {
|
|
// but not uploaded automatically as it was trashed.
|
|
// but not uploaded automatically as it was trashed.
|
|
final localIDs = batch
|
|
final localIDs = batch
|
|
.where((e) => e.localID != null)
|
|
.where((e) => e.localID != null)
|
|
- .map((e) => e.localID)
|
|
|
|
|
|
+ .map((e) => e.localID!)
|
|
.toSet()
|
|
.toSet()
|
|
.toList();
|
|
.toList();
|
|
if (localIDs.isNotEmpty) {
|
|
if (localIDs.isNotEmpty) {
|
|
@@ -953,13 +959,13 @@ class CollectionsService {
|
|
file.collectionID = toCollectionID;
|
|
file.collectionID = toCollectionID;
|
|
final encryptedKeyData =
|
|
final encryptedKeyData =
|
|
CryptoUtil.encryptSync(fileKey, getCollectionKey(toCollectionID));
|
|
CryptoUtil.encryptSync(fileKey, getCollectionKey(toCollectionID));
|
|
- file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData);
|
|
|
|
- file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce);
|
|
|
|
|
|
+ file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
|
|
|
+ file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce!);
|
|
params["files"].add(
|
|
params["files"].add(
|
|
CollectionFileItem(
|
|
CollectionFileItem(
|
|
- file.uploadedFileID,
|
|
|
|
- file.encryptedKey,
|
|
|
|
- file.keyDecryptionNonce,
|
|
|
|
|
|
+ file.uploadedFileID!,
|
|
|
|
+ file.encryptedKey!,
|
|
|
|
+ file.keyDecryptionNonce!,
|
|
).toMap(),
|
|
).toMap(),
|
|
);
|
|
);
|
|
}
|
|
}
|
|
@@ -972,7 +978,7 @@ class CollectionsService {
|
|
// remove files from old collection
|
|
// remove files from old collection
|
|
await _filesDB.removeFromCollection(
|
|
await _filesDB.removeFromCollection(
|
|
fromCollectionID,
|
|
fromCollectionID,
|
|
- files.map((e) => e.uploadedFileID).toList(),
|
|
|
|
|
|
+ files.map((e) => e.uploadedFileID!).toList(),
|
|
);
|
|
);
|
|
Bus.instance.fire(
|
|
Bus.instance.fire(
|
|
CollectionUpdatedEvent(
|
|
CollectionUpdatedEvent(
|
|
@@ -1038,11 +1044,9 @@ class CollectionsService {
|
|
}
|
|
}
|
|
|
|
|
|
Future<Collection> createAndCacheCollection(
|
|
Future<Collection> createAndCacheCollection(
|
|
- Collection collection, {
|
|
|
|
CreateRequest createRequest,
|
|
CreateRequest createRequest,
|
|
- }) async {
|
|
|
|
- final dynamic payload =
|
|
|
|
- createRequest != null ? createRequest.toJson() : collection.toMap();
|
|
|
|
|
|
+ ) async {
|
|
|
|
+ final dynamic payload = createRequest.toJson();
|
|
return _enteDio
|
|
return _enteDio
|
|
.post(
|
|
.post(
|
|
"/collections",
|
|
"/collections",
|
|
@@ -1051,21 +1055,19 @@ class CollectionsService {
|
|
.then((response) async {
|
|
.then((response) async {
|
|
final collectionData = response.data["collection"];
|
|
final collectionData = response.data["collection"];
|
|
final collection = Collection.fromMap(collectionData);
|
|
final collection = Collection.fromMap(collectionData);
|
|
- if (createRequest != null) {
|
|
|
|
- if (collectionData['magicMetadata'] != null) {
|
|
|
|
- final decryptionKey =
|
|
|
|
- _getAndCacheDecryptedKey(collection, source: "create");
|
|
|
|
- final utfEncodedMmd = await CryptoUtil.decryptChaCha(
|
|
|
|
- Sodium.base642bin(collectionData['magicMetadata']['data']),
|
|
|
|
- decryptionKey,
|
|
|
|
- Sodium.base642bin(collectionData['magicMetadata']['header']),
|
|
|
|
- );
|
|
|
|
- collection.mMdEncodedJson = utf8.decode(utfEncodedMmd);
|
|
|
|
- collection.mMdVersion = collectionData['magicMetadata']['version'];
|
|
|
|
- collection.magicMetadata = CollectionMagicMetadata.fromEncodedJson(
|
|
|
|
- collection.mMdEncodedJson,
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
|
|
+ if (collectionData['magicMetadata'] != null) {
|
|
|
|
+ final decryptionKey =
|
|
|
|
+ _getAndCacheDecryptedKey(collection, source: "create");
|
|
|
|
+ final utfEncodedMmd = await CryptoUtil.decryptChaCha(
|
|
|
|
+ Sodium.base642bin(collectionData['magicMetadata']['data']),
|
|
|
|
+ decryptionKey,
|
|
|
|
+ Sodium.base642bin(collectionData['magicMetadata']['header']),
|
|
|
|
+ );
|
|
|
|
+ collection.mMdEncodedJson = utf8.decode(utfEncodedMmd);
|
|
|
|
+ collection.mMdVersion = collectionData['magicMetadata']['version'];
|
|
|
|
+ collection.magicMetadata = CollectionMagicMetadata.fromEncodedJson(
|
|
|
|
+ collection.mMdEncodedJson,
|
|
|
|
+ );
|
|
}
|
|
}
|
|
return _cacheCollectionAttributes(collection);
|
|
return _cacheCollectionAttributes(collection);
|
|
});
|
|
});
|
|
@@ -1076,7 +1078,7 @@ class CollectionsService {
|
|
_getCollectionWithDecryptedName(collection);
|
|
_getCollectionWithDecryptedName(collection);
|
|
if (collection.attributes.encryptedPath != null &&
|
|
if (collection.attributes.encryptedPath != null &&
|
|
!collection.isDeleted &&
|
|
!collection.isDeleted &&
|
|
- collection.owner.id == _config.getUserID()) {
|
|
|
|
|
|
+ collection.owner?.id == _config.getUserID()) {
|
|
_localPathToCollectionID[decryptCollectionPath(collection)] =
|
|
_localPathToCollectionID[decryptCollectionPath(collection)] =
|
|
collection.id;
|
|
collection.id;
|
|
}
|
|
}
|
|
@@ -1090,9 +1092,9 @@ class CollectionsService {
|
|
: _config.getKey();
|
|
: _config.getKey();
|
|
return utf8.decode(
|
|
return utf8.decode(
|
|
CryptoUtil.decryptSync(
|
|
CryptoUtil.decryptSync(
|
|
- Sodium.base642bin(collection.attributes.encryptedPath),
|
|
|
|
|
|
+ Sodium.base642bin(collection.attributes.encryptedPath!),
|
|
key,
|
|
key,
|
|
- Sodium.base642bin(collection.attributes.pathDecryptionNonce),
|
|
|
|
|
|
+ Sodium.base642bin(collection.attributes.pathDecryptionNonce!),
|
|
),
|
|
),
|
|
);
|
|
);
|
|
}
|
|
}
|
|
@@ -1106,7 +1108,7 @@ class CollectionsService {
|
|
return collection.copyWith(name: "Deleted Album");
|
|
return collection.copyWith(name: "Deleted Album");
|
|
}
|
|
}
|
|
if (collection.encryptedName != null &&
|
|
if (collection.encryptedName != null &&
|
|
- collection.encryptedName.isNotEmpty) {
|
|
|
|
|
|
+ collection.encryptedName!.isNotEmpty) {
|
|
String name;
|
|
String name;
|
|
try {
|
|
try {
|
|
final collectionKey = _getAndCacheDecryptedKey(
|
|
final collectionKey = _getAndCacheDecryptedKey(
|
|
@@ -1114,9 +1116,9 @@ class CollectionsService {
|
|
source: "Name",
|
|
source: "Name",
|
|
);
|
|
);
|
|
final result = CryptoUtil.decryptSync(
|
|
final result = CryptoUtil.decryptSync(
|
|
- Sodium.base642bin(collection.encryptedName),
|
|
|
|
|
|
+ Sodium.base642bin(collection.encryptedName!),
|
|
collectionKey,
|
|
collectionKey,
|
|
- Sodium.base642bin(collection.nameDecryptionNonce),
|
|
|
|
|
|
+ Sodium.base642bin(collection.nameDecryptionNonce!),
|
|
);
|
|
);
|
|
name = utf8.decode(result);
|
|
name = utf8.decode(result);
|
|
} catch (e, s) {
|
|
} catch (e, s) {
|