feat(mobile): use efficient sync (#8842)

* feat(mobile): use efficient sync

review feedback

* adapt to changed  server endpoints

* formatting

* fix memory lane bug

* fix: bad merge

* fix call not returning correct number of asset

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Fynn Petersen-Frey
2024-05-14 17:35:37 +02:00
committed by GitHub
parent acc611a3d9
commit 116043b2b4
12 changed files with 185 additions and 125 deletions

View File

@@ -40,18 +40,20 @@ class SyncService {
/// Syncs remote assets owned by the logged-in user to the DB
/// Returns `true` if there were any changes
Future<bool> syncRemoteAssetsToDb(
User user,
Future<(List<Asset>? toUpsert, List<String>? toDelete)> Function(
User user,
Future<bool> syncRemoteAssetsToDb({
required List<User> users,
required Future<(List<Asset>? toUpsert, List<String>? toDelete)> Function(
List<User> users,
DateTime since,
) getChangedAssets,
FutureOr<List<Asset>?> Function(User user) loadAssets,
) =>
required FutureOr<List<Asset>?> Function(User user, DateTime until)
loadAssets,
required FutureOr<List<User>?> Function() refreshUsers,
}) =>
_lock.run(
() async =>
await _syncRemoteAssetChanges(user, getChangedAssets) ??
await _syncRemoteAssetsFull(user, loadAssets),
await _syncRemoteAssetChanges(users, getChangedAssets) ??
await _syncRemoteAssetsFull(refreshUsers, loadAssets),
);
/// Syncs remote albums to the database
@@ -111,7 +113,8 @@ class SyncService {
both: (User a, User b) {
if (!a.updatedAt.isAtSameMomentAs(b.updatedAt) ||
a.isPartnerSharedBy != b.isPartnerSharedBy ||
a.isPartnerSharedWith != b.isPartnerSharedWith) {
a.isPartnerSharedWith != b.isPartnerSharedWith ||
a.inTimeline != b.inTimeline) {
toUpsert.add(a);
return true;
}
@@ -149,17 +152,22 @@ class SyncService {
/// Efficiently syncs assets via changes. Returns `null` when a full sync is required.
Future<bool?> _syncRemoteAssetChanges(
User user,
List<User> users,
Future<(List<Asset>? toUpsert, List<String>? toDelete)> Function(
User user,
List<User> users,
DateTime since,
) getChangedAssets,
) async {
final DateTime? since = _db.eTags.getByIdSync(user.id)?.time?.toUtc();
final currentUser = Store.get(StoreKey.currentUser);
final DateTime? since =
_db.eTags.getSync(currentUser.isarId)?.time?.toUtc();
if (since == null) return null;
final DateTime now = DateTime.now();
final (toUpsert, toDelete) = await getChangedAssets(user, since);
if (toUpsert == null || toDelete == null) return null;
final (toUpsert, toDelete) = await getChangedAssets(users, since);
if (toUpsert == null || toDelete == null) {
await _clearUserAssetsETag(users);
return null;
}
try {
if (toDelete.isNotEmpty) {
await handleRemoteAssetRemoval(toDelete);
@@ -169,7 +177,7 @@ class SyncService {
await upsertAssetsWithExif(updated);
}
if (toUpsert.isNotEmpty || toDelete.isNotEmpty) {
await _updateUserAssetsETag(user, now);
await _updateUserAssetsETag(users, now);
return true;
}
return false;
@@ -203,11 +211,34 @@ class SyncService {
/// Syncs assets by loading and comparing all assets from the server.
Future<bool> _syncRemoteAssetsFull(
FutureOr<List<User>?> Function() refreshUsers,
FutureOr<List<Asset>?> Function(User user, DateTime until) loadAssets,
) async {
final serverUsers = await refreshUsers();
if (serverUsers == null) {
_log.warning("_syncRemoteAssetsFull aborted because user refresh failed");
return false;
}
await _syncUsersFromServer(serverUsers);
final List<User> users = await _db.users
.filter()
.isPartnerSharedWithEqualTo(true)
.or()
.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId)
.findAll();
bool changes = false;
for (User u in users) {
changes |= await _syncRemoteAssetsForUser(u, loadAssets);
}
return changes;
}
Future<bool> _syncRemoteAssetsForUser(
User user,
FutureOr<List<Asset>?> Function(User user) loadAssets,
FutureOr<List<Asset>?> Function(User user, DateTime until) loadAssets,
) async {
final DateTime now = DateTime.now().toUtc();
final List<Asset>? remote = await loadAssets(user);
final List<Asset>? remote = await loadAssets(user, now);
if (remote == null) {
return false;
}
@@ -225,7 +256,7 @@ class SyncService {
final (toAdd, toUpdate, toRemove) = _diffAssets(remote, inDb, remote: true);
if (toAdd.isEmpty && toUpdate.isEmpty && toRemove.isEmpty) {
await _updateUserAssetsETag(user, now);
await _updateUserAssetsETag([user], now);
return false;
}
final idsToDelete = toRemove.map((e) => e.id).toList();
@@ -235,12 +266,19 @@ class SyncService {
} on IsarError catch (e) {
_log.severe("Failed to sync remote assets to db", e);
}
await _updateUserAssetsETag(user, now);
await _updateUserAssetsETag([user], now);
return true;
}
Future<void> _updateUserAssetsETag(User user, DateTime time) =>
_db.writeTxn(() => _db.eTags.put(ETag(id: user.id, time: time)));
Future<void> _updateUserAssetsETag(List<User> users, DateTime time) {
final etags = users.map((u) => ETag(id: u.id, time: time)).toList();
return _db.writeTxn(() => _db.eTags.putAll(etags));
}
Future<void> _clearUserAssetsETag(List<User> users) {
final ids = users.map((u) => u.id).toList();
return _db.writeTxn(() => _db.eTags.deleteAllById(ids));
}
/// Syncs remote albums to the database
/// returns `true` if there were any changes