fix: optimize things (1)
This commit is contained in:
@@ -15,12 +15,12 @@ import "package:photos/events/memories_changed_event.dart";
|
||||
import "package:photos/events/people_changed_event.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/l10n/l10n.dart";
|
||||
import "package:photos/models/collection/smart_album_config.dart";
|
||||
import "package:photos/service_locator.dart";
|
||||
import 'package:photos/services/app_lifecycle_service.dart';
|
||||
import "package:photos/services/home_widget_service.dart";
|
||||
import "package:photos/services/memory_home_widget_service.dart";
|
||||
import "package:photos/services/people_home_widget_service.dart";
|
||||
import "package:photos/services/smart_albums_service.dart";
|
||||
import 'package:photos/services/sync/sync_service.dart';
|
||||
import 'package:photos/ui/tabs/home_widget.dart';
|
||||
import "package:photos/ui/viewer/actions/file_viewer.dart";
|
||||
@@ -77,7 +77,7 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
|
||||
_changeCallbackDebouncer.run(
|
||||
() async {
|
||||
unawaited(PeopleHomeWidgetService.instance.checkPeopleChanged());
|
||||
unawaited(syncSmartAlbums());
|
||||
unawaited(SmartAlbumsService.instance.syncSmartAlbums());
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -128,4 +128,21 @@ extension EntitiesDB on FilesDB {
|
||||
}
|
||||
return maps.values.first as String?;
|
||||
}
|
||||
|
||||
Future<Map<String, int>> getUpdatedAts(
|
||||
EntityType type,
|
||||
List<String> ids,
|
||||
) async {
|
||||
final db = await sqliteAsyncDB;
|
||||
final List<Map<String, dynamic>> maps = await db.getAll(
|
||||
'SELECT id, updatedAt FROM entities WHERE type = ? AND id IN (${List.filled(ids.length, '?').join(',')})',
|
||||
[type.name, ...ids],
|
||||
);
|
||||
return Map<String, int>.fromEntries(
|
||||
List.generate(
|
||||
maps.length,
|
||||
(i) => MapEntry(maps[i]['id'] as String, maps[i]['updatedAt'] as int),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
import "package:photos/models/api/entity/type.dart";
|
||||
import "package:photos/service_locator.dart" show entityService, prefs;
|
||||
import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/services/search_service.dart";
|
||||
import "package:photos/ui/actions/collection/collection_file_actions.dart";
|
||||
import "package:photos/ui/actions/collection/collection_sharing_actions.dart";
|
||||
import "package:shared_preferences/shared_preferences.dart";
|
||||
import "package:synchronized/synchronized.dart";
|
||||
|
||||
class SmartAlbumConfig {
|
||||
final int collectionId;
|
||||
// person ids
|
||||
@@ -20,9 +11,6 @@ class SmartAlbumConfig {
|
||||
required this.addedFiles,
|
||||
});
|
||||
|
||||
static const _personIdsKey = "smart_album_person_ids";
|
||||
static const _addedFilesKey = "smart_album_added_files";
|
||||
|
||||
Future<SmartAlbumConfig> getUpdatedConfig(Set<String> newPersonsIds) async {
|
||||
final toAdd = newPersonsIds.difference(personIDs);
|
||||
final toRemove = personIDs.difference(newPersonsIds);
|
||||
@@ -65,101 +53,4 @@ class SmartAlbumConfig {
|
||||
addedFiles: newFiles,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> saveConfig() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
await prefs.setStringList(
|
||||
"${_personIdsKey}_$collectionId",
|
||||
personIDs.toList(),
|
||||
);
|
||||
|
||||
await prefs.setString(
|
||||
"${_addedFilesKey}_$collectionId",
|
||||
addedFiles.entries
|
||||
.map((e) => "${e.key}:${e.value.$1}|${e.value.$2.join(',')}")
|
||||
.join(';'),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<SmartAlbumConfig> loadConfig(int collectionId) async {
|
||||
final personIDs =
|
||||
prefs.getStringList("${_personIdsKey}_$collectionId") ?? [];
|
||||
final addedFilesString =
|
||||
prefs.getString("${_addedFilesKey}_$collectionId") ?? "";
|
||||
|
||||
final addedFiles = <String, (int, Set<int>)>{};
|
||||
if (addedFilesString.isNotEmpty) {
|
||||
for (final entry in addedFilesString.split(';')) {
|
||||
final parts = entry.split(':');
|
||||
if (parts.length == 2) {
|
||||
addedFiles[parts[0]] = (
|
||||
int.parse(parts[1].split('|')[0]),
|
||||
parts[1].split('|')[1].split(',').map(int.parse).toSet(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SmartAlbumConfig(
|
||||
collectionId: collectionId,
|
||||
personIDs: personIDs.toSet(),
|
||||
addedFiles: addedFiles,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final _lock = Lock();
|
||||
|
||||
Future<void> syncSmartAlbums() async {
|
||||
await _lock.synchronized(() async {
|
||||
// get all collections
|
||||
final collections = CollectionsService.instance.nonHiddenOwnedCollections();
|
||||
for (final collectionId in collections) {
|
||||
final config = await SmartAlbumConfig.loadConfig(collectionId);
|
||||
|
||||
if (config.personIDs.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (final personId in config.personIDs) {
|
||||
final person =
|
||||
await entityService.getEntity(EntityType.cgroup, personId);
|
||||
|
||||
if (person == null ||
|
||||
config.addedFiles[personId]?.$1 == null ||
|
||||
(person.updatedAt <= config.addedFiles[personId]!.$1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final files =
|
||||
(await SearchService.instance.getClusterFilesForPersonID(personId))
|
||||
.entries
|
||||
.expand((e) => e.value)
|
||||
.toSet();
|
||||
|
||||
final toBeSynced =
|
||||
files.difference(config.addedFiles[personId]?.$2 ?? {});
|
||||
|
||||
if (toBeSynced.isNotEmpty) {
|
||||
final CollectionActions collectionActions =
|
||||
CollectionActions(CollectionsService.instance);
|
||||
final result = await collectionActions.addToCollection(
|
||||
null,
|
||||
collectionId,
|
||||
false,
|
||||
selectedFiles: toBeSynced.toList(),
|
||||
);
|
||||
if (result) {
|
||||
final newConfig = await config.addFiles(
|
||||
personId,
|
||||
person.updatedAt,
|
||||
toBeSynced.map((e) => e.uploadedFileID!).toSet(),
|
||||
);
|
||||
await newConfig.saveConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,10 +45,6 @@ class ServiceLocator {
|
||||
}
|
||||
}
|
||||
|
||||
SharedPreferences get prefs {
|
||||
return ServiceLocator.instance.prefs;
|
||||
}
|
||||
|
||||
FlagService? _flagService;
|
||||
|
||||
FlagService get flagService {
|
||||
|
||||
@@ -249,4 +249,11 @@ class EntityService {
|
||||
final hash = md5.convert(utf8.encode(preHash)).toString().substring(0, 10);
|
||||
return hash;
|
||||
}
|
||||
|
||||
Future<Map<String, int>> getUpdatedAts(
|
||||
EntityType type,
|
||||
List<String> personIds,
|
||||
) async {
|
||||
return await _db.getUpdatedAts(type, personIds);
|
||||
}
|
||||
}
|
||||
|
||||
178
mobile/apps/photos/lib/services/smart_albums_service.dart
Normal file
178
mobile/apps/photos/lib/services/smart_albums_service.dart
Normal file
@@ -0,0 +1,178 @@
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/models/api/entity/type.dart";
|
||||
import "package:photos/models/collection/smart_album_config.dart";
|
||||
import "package:photos/service_locator.dart" show entityService, ServiceLocator;
|
||||
import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/services/search_service.dart";
|
||||
import "package:photos/ui/actions/collection/collection_file_actions.dart";
|
||||
import "package:photos/ui/actions/collection/collection_sharing_actions.dart";
|
||||
import "package:shared_preferences/shared_preferences.dart";
|
||||
import "package:synchronized/synchronized.dart";
|
||||
|
||||
class SmartAlbumsService {
|
||||
SmartAlbumsService._();
|
||||
|
||||
static final SmartAlbumsService instance = SmartAlbumsService._();
|
||||
|
||||
final _lock = Lock();
|
||||
final _logger = Logger((SmartAlbumsService).toString());
|
||||
|
||||
final Map<int, SmartAlbumConfig> _cachedConfigs = {};
|
||||
bool isInitialized = false;
|
||||
|
||||
void init() {
|
||||
_logger.info("SmartAlbumsService initialized");
|
||||
refresh().ignore();
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
await _lock.synchronized(() async {
|
||||
if (isInitialized) return;
|
||||
|
||||
_logger.info("Refreshing SmartAlbumsService");
|
||||
|
||||
final collections =
|
||||
CollectionsService.instance.nonHiddenOwnedCollections();
|
||||
|
||||
for (final collectionId in collections) {
|
||||
try {
|
||||
final config = await loadConfig(collectionId);
|
||||
_cachedConfigs[collectionId] = config;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
isInitialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
void updateCachedCollection(SmartAlbumConfig config) =>
|
||||
_cachedConfigs[config.collectionId] = config;
|
||||
|
||||
Future<void> syncSmartAlbums() async {
|
||||
await _lock.synchronized(() async {
|
||||
if (!isInitialized) await refresh();
|
||||
|
||||
for (final entry in _cachedConfigs.entries) {
|
||||
final collectionId = entry.key;
|
||||
final config = entry.value;
|
||||
|
||||
final addedFiles = config.addedFiles;
|
||||
|
||||
// Person Id key mapped to updatedAt value
|
||||
final updatedAtMap = await entityService.getUpdatedAts(
|
||||
EntityType.cgroup,
|
||||
config.personIDs.toList(),
|
||||
);
|
||||
|
||||
for (final personId in config.personIDs) {
|
||||
// compares current updateAt with last added file's updatedAt
|
||||
if (updatedAtMap[personId] == null ||
|
||||
addedFiles[personId] == null ||
|
||||
(updatedAtMap[personId]! <= addedFiles[personId]!.$1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final files = (await SearchService.instance
|
||||
.getClusterFilesForPersonID(personId))
|
||||
.entries
|
||||
.expand((e) => e.value)
|
||||
.toList();
|
||||
|
||||
// TODO: Can we optimize it more?
|
||||
final toBeSynced = files
|
||||
..removeWhere(
|
||||
(e) =>
|
||||
e.uploadedFileID == null ||
|
||||
config.addedFiles[personId]!.$2.contains(e.uploadedFileID),
|
||||
);
|
||||
|
||||
if (toBeSynced.isNotEmpty) {
|
||||
final CollectionActions collectionActions =
|
||||
CollectionActions(CollectionsService.instance);
|
||||
|
||||
final result = await collectionActions.addToCollection(
|
||||
null,
|
||||
collectionId,
|
||||
false,
|
||||
selectedFiles: toBeSynced,
|
||||
);
|
||||
|
||||
if (result) {
|
||||
final newConfig = await config.addFiles(
|
||||
personId,
|
||||
updatedAtMap[personId]!,
|
||||
toBeSynced.map((e) => e.uploadedFileID!).toSet(),
|
||||
);
|
||||
await saveConfig(newConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static const _personIdsKey = "smart_album_person_ids";
|
||||
static const _addedFilesKey = "smart_album_added_files";
|
||||
|
||||
Future<void> saveConfig(SmartAlbumConfig config) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
final collectionId = config.collectionId;
|
||||
final personIDs = config.personIDs;
|
||||
final addedFiles = config.addedFiles;
|
||||
|
||||
await prefs.setStringList(
|
||||
"${_personIdsKey}_$collectionId",
|
||||
personIDs.toList(),
|
||||
);
|
||||
|
||||
await prefs.setString(
|
||||
"${_addedFilesKey}_$collectionId",
|
||||
addedFiles.entries
|
||||
.map((e) => "${e.key}:${e.value.$1}|${e.value.$2.join(',')}")
|
||||
.join(';'),
|
||||
);
|
||||
updateCachedCollection(config);
|
||||
}
|
||||
|
||||
Future<SmartAlbumConfig> getConfig(int collectionId) async {
|
||||
if (isInitialized && _cachedConfigs.containsKey(collectionId)) {
|
||||
return _cachedConfigs[collectionId]!;
|
||||
}
|
||||
|
||||
refresh().ignore();
|
||||
return await loadConfig(collectionId);
|
||||
}
|
||||
|
||||
Future<SmartAlbumConfig> loadConfig(int collectionId) async {
|
||||
final personIDs = ServiceLocator.instance.prefs
|
||||
.getStringList("${_personIdsKey}_$collectionId");
|
||||
if (personIDs == null || personIDs.isEmpty) {
|
||||
throw Exception(
|
||||
"No person IDs found for collection $collectionId",
|
||||
);
|
||||
}
|
||||
final addedFilesString = ServiceLocator.instance.prefs
|
||||
.getString("${_addedFilesKey}_$collectionId") ??
|
||||
"";
|
||||
|
||||
final addedFiles = <String, (int, Set<int>)>{};
|
||||
if (addedFilesString.isNotEmpty) {
|
||||
for (final entry in addedFilesString.split(';')) {
|
||||
final parts = entry.split(':');
|
||||
if (parts.length == 2) {
|
||||
addedFiles[parts[0]] = (
|
||||
int.parse(parts[1].split('|')[0]),
|
||||
parts[1].split('|')[1].split(',').map(int.parse).toSet(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SmartAlbumConfig(
|
||||
collectionId: collectionId,
|
||||
personIDs: personIDs.toSet(),
|
||||
addedFiles: addedFiles,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@ import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/events/subscription_purchased_event.dart';
|
||||
import 'package:photos/events/sync_status_update_event.dart';
|
||||
import 'package:photos/events/trigger_logout_event.dart';
|
||||
import "package:photos/models/collection/smart_album_config.dart";
|
||||
import 'package:photos/models/file/file_type.dart';
|
||||
import "package:photos/services/language_service.dart";
|
||||
import 'package:photos/services/notification_service.dart';
|
||||
import "package:photos/services/smart_albums_service.dart";
|
||||
import 'package:photos/services/sync/local_sync_service.dart';
|
||||
import 'package:photos/services/sync/remote_sync_service.dart';
|
||||
import 'package:photos/utils/file_uploader.dart';
|
||||
@@ -200,7 +200,7 @@ class SyncService {
|
||||
if (shouldSync) {
|
||||
await _remoteSyncService.sync();
|
||||
}
|
||||
await syncSmartAlbums();
|
||||
await SmartAlbumsService.instance.syncSmartAlbums();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/collection/smart_album_config.dart";
|
||||
import "package:photos/models/selected_people.dart";
|
||||
import "package:photos/services/smart_albums_service.dart";
|
||||
import "package:photos/ui/components/buttons/button_widget.dart";
|
||||
import 'package:photos/ui/components/buttons/icon_button_widget.dart';
|
||||
import "package:photos/ui/components/models/button_type.dart";
|
||||
@@ -26,7 +27,7 @@ class SmartAlbumPeople extends StatefulWidget {
|
||||
|
||||
class _SmartAlbumPeopleState extends State<SmartAlbumPeople> {
|
||||
final _selectedPeople = SelectedPeople();
|
||||
late SmartAlbumConfig currentConfig;
|
||||
SmartAlbumConfig? currentConfig;
|
||||
bool isLoading = false;
|
||||
|
||||
@override
|
||||
@@ -36,9 +37,12 @@ class _SmartAlbumPeopleState extends State<SmartAlbumPeople> {
|
||||
}
|
||||
|
||||
Future<void> getSelections() async {
|
||||
currentConfig = await SmartAlbumConfig.loadConfig(widget.collectionId);
|
||||
currentConfig =
|
||||
await SmartAlbumsService.instance.getConfig(widget.collectionId);
|
||||
|
||||
_selectedPeople.select(currentConfig.personIDs);
|
||||
if (currentConfig == null) {
|
||||
_selectedPeople.select(currentConfig!.personIDs);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -65,12 +69,32 @@ class _SmartAlbumPeopleState extends State<SmartAlbumPeople> {
|
||||
: () async {
|
||||
isLoading = true;
|
||||
if (mounted) setState(() {});
|
||||
|
||||
try {
|
||||
final newConfig = await currentConfig.getUpdatedConfig(
|
||||
_selectedPeople.personIds,
|
||||
);
|
||||
await newConfig.saveConfig();
|
||||
syncSmartAlbums().ignore();
|
||||
SmartAlbumConfig newConfig;
|
||||
|
||||
if (currentConfig == null) {
|
||||
final newFiles = <String, (int, Set<int>)>{};
|
||||
|
||||
// Add files which are needed
|
||||
for (final personId in _selectedPeople.personIds) {
|
||||
newFiles[personId] = (0, {});
|
||||
}
|
||||
|
||||
newConfig = SmartAlbumConfig(
|
||||
collectionId: widget.collectionId,
|
||||
personIDs: _selectedPeople.personIds,
|
||||
addedFiles: newFiles,
|
||||
);
|
||||
} else {
|
||||
newConfig = await currentConfig!.getUpdatedConfig(
|
||||
_selectedPeople.personIds,
|
||||
);
|
||||
}
|
||||
|
||||
await SmartAlbumsService.instance.saveConfig(newConfig);
|
||||
SmartAlbumsService.instance.syncSmartAlbums().ignore();
|
||||
|
||||
Navigator.pop(context);
|
||||
} catch (_) {
|
||||
isLoading = false;
|
||||
|
||||
Reference in New Issue
Block a user