// @dart=2.9 import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_sodium/flutter_sodium.dart'; import 'package:photos/core/configuration.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/db/files_db.dart'; import 'package:photos/events/collection_updated_event.dart'; import 'package:photos/events/files_updated_event.dart'; import 'package:photos/models/collection.dart'; import 'package:photos/models/file.dart'; import 'package:photos/services/collections_service.dart'; import 'package:photos/services/remote_sync_service.dart'; import 'package:photos/utils/crypto_util.dart'; class FavoritesService { Configuration _config; CollectionsService _collectionsService; FilesDB _filesDB; int _cachedFavoritesCollectionID; final Set _cachedFavUploadedIDs = {}; final Set _cachedPendingLocalIDs = {}; StreamSubscription _collectionUpdatesSubscription; FavoritesService._privateConstructor() { _config = Configuration.instance; _collectionsService = CollectionsService.instance; _filesDB = FilesDB.instance; _collectionUpdatesSubscription = Bus.instance.on().listen((event) { if (event.collectionID != null && _cachedFavoritesCollectionID != null && _cachedFavoritesCollectionID == event.collectionID) { if (event.type == EventType.addedOrUpdated) { _updateFavoriteFilesCache(event.updatedFiles, favFlag: true); } else if (event.type == EventType.deletedFromEverywhere || event.type == EventType.deletedFromRemote) { _updateFavoriteFilesCache(event.updatedFiles, favFlag: false); } } }); } Future initFav() async { await _warmUpCache(); } void dispose() { _collectionUpdatesSubscription.cancel(); } Future _warmUpCache() async { final favCollection = await _getFavoritesCollection(); if (favCollection != null) { final uploadedIDs = await FilesDB.instance.getUploadedFileIDs(favCollection.id); _cachedFavUploadedIDs.addAll(uploadedIDs); } } static FavoritesService instance = FavoritesService._privateConstructor(); void clearCache() { _cachedFavoritesCollectionID = null; } bool isFavoriteCache(File file, {bool checkOnlyAlbum = false}) { if (file.collectionID != null && _cachedFavoritesCollectionID != null && file.collectionID == _cachedFavoritesCollectionID) { return true; } if (checkOnlyAlbum) { return false; } if (file.uploadedFileID != null) { return _cachedFavUploadedIDs.contains(file.uploadedFileID); } else if (file.localID != null) { return _cachedPendingLocalIDs.contains(file.localID); } return false; } Future isFavorite(File file) async { final collection = await _getFavoritesCollection(); if (collection == null) { return false; } return _filesDB.doesFileExistInCollection( file.uploadedFileID, collection.id, ); } void _updateFavoriteFilesCache(List files, {@required bool favFlag}) { final Set updatedIDs = {}; final Set localIDs = {}; for (var file in files) { if (file.uploadedFileID != null) { updatedIDs.add(file.uploadedFileID); } else if (file.localID != null || file.localID != "") { /* Note: Favorite un-uploaded files For such files, as we don't have uploaded IDs yet, we will cache cache the local ID for showing the fav icon in the gallery */ localIDs.add(file.localID); } } if (favFlag) { _cachedFavUploadedIDs.addAll(updatedIDs); } else { _cachedFavUploadedIDs.removeAll(updatedIDs); } } Future addToFavorites(File file) async { final collectionID = await _getOrCreateFavoriteCollectionID(); final List files = [file]; if (file.uploadedFileID == null) { file.collectionID = collectionID; await _filesDB.insert(file); Bus.instance.fire(CollectionUpdatedEvent(collectionID, files, "addTFav")); } else { await _collectionsService.addToCollection(collectionID, files); } _updateFavoriteFilesCache(files, favFlag: true); RemoteSyncService.instance.sync(silently: true); } Future updateFavorites(List files, bool favFlag) async { final int currentUserID = Configuration.instance.getUserID(); if (files.any((f) => f.uploadedFileID == null)) { throw AssertionError("Can only favorite uploaded items"); } if (files.any((f) => f.ownerID != currentUserID)) { throw AssertionError("Can not favortie files owned by others"); } final collectionID = await _getOrCreateFavoriteCollectionID(); if (favFlag) { await _collectionsService.addToCollection(collectionID, files); } else { await _collectionsService.removeFromCollection(collectionID, files); } _updateFavoriteFilesCache(files, favFlag: false); } Future removeFromFavorites(File file) async { final collectionID = await _getOrCreateFavoriteCollectionID(); final fileID = file.uploadedFileID; if (fileID == null) { // Do nothing, ignore } else { await _collectionsService.removeFromCollection(collectionID, [file]); } _updateFavoriteFilesCache([file], favFlag: false); } Future _getFavoritesCollection() async { if (_cachedFavoritesCollectionID == null) { final collections = _collectionsService.getActiveCollections(); for (final collection in collections) { if (collection.owner.id == _config.getUserID() && collection.type == CollectionType.favorites) { _cachedFavoritesCollectionID = collection.id; return collection; } } return null; } return _collectionsService.getCollectionByID(_cachedFavoritesCollectionID); } Future _getOrCreateFavoriteCollectionID() async { if (_cachedFavoritesCollectionID != null) { return _cachedFavoritesCollectionID; } final key = CryptoUtil.generateKey(); final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()); final encryptedName = CryptoUtil.encryptSync(utf8.encode("Favorites"), key); final collection = await _collectionsService.createAndCacheCollection( Collection( null, null, Sodium.bin2base64(encryptedKeyData.encryptedData), Sodium.bin2base64(encryptedKeyData.nonce), null, Sodium.bin2base64(encryptedName.encryptedData), Sodium.bin2base64(encryptedName.nonce), CollectionType.favorites, CollectionAttributes(), null, null, null, ), ); _cachedFavoritesCollectionID = collection.id; return collection.id; } }