Переглянути джерело

chore(mobile): use Record instead of custom pair+triple (#2483)

Fynn Petersen-Frey 2 роки тому
батько
коміт
dc7b0f75bb

+ 1 - 1
mobile/lib/modules/backup/views/backup_controller_page.dart

@@ -364,7 +364,7 @@ class BackupControllerPage extends HookConsumerWidget {
                   .read(backgroundServiceProvider)
                   .getIOSBackgroundAppRefreshEnabled(),
               builder: (context, snapshot) {
-                final enabled = snapshot.data as bool?;
+                final enabled = snapshot.data;
                 // If it's not enabled, show them some kind of alert that says
                 // background refresh is not enabled
                 if (enabled != null && !enabled) {}

+ 5 - 7
mobile/lib/shared/services/asset.service.dart

@@ -10,7 +10,6 @@ import 'package:immich_mobile/shared/providers/db.provider.dart';
 import 'package:immich_mobile/shared/services/api.service.dart';
 import 'package:immich_mobile/shared/services/sync.service.dart';
 import 'package:immich_mobile/utils/openapi_extensions.dart';
-import 'package:immich_mobile/utils/tuple.dart';
 import 'package:isar/isar.dart';
 import 'package:logging/logging.dart';
 import 'package:openapi/api.dart';
@@ -60,15 +59,14 @@ class AssetService {
   }) async {
     try {
       final etag = hasCache ? Store.tryGet(StoreKey.assetETag) : null;
-      final Pair<List<AssetResponseDto>, String?>? remote =
+      final (List<AssetResponseDto>? assets, String? newETag) =
           await _apiService.assetApi.getAllAssetsWithETag(eTag: etag);
-      if (remote == null) {
+      if (assets == null) {
         return null;
+      } else if (newETag != etag) {
+        Store.put(StoreKey.assetETag, newETag);
       }
-      if (remote.second != null && remote.second != etag) {
-        Store.put(StoreKey.assetETag, remote.second);
-      }
-      return remote.first;
+      return assets;
     } catch (e, stack) {
       log.severe('Error while getting remote assets', e, stack);
       return null;

+ 48 - 48
mobile/lib/shared/services/sync.service.dart

@@ -11,7 +11,6 @@ import 'package:immich_mobile/shared/providers/db.provider.dart';
 import 'package:immich_mobile/utils/async_mutex.dart';
 import 'package:immich_mobile/utils/builtin_extensions.dart';
 import 'package:immich_mobile/utils/diff.dart';
-import 'package:immich_mobile/utils/tuple.dart';
 import 'package:isar/isar.dart';
 import 'package:logging/logging.dart';
 import 'package:openapi/api.dart';
@@ -94,7 +93,7 @@ class SyncService {
     deleteCandidates.sort(Asset.compareById);
     existing.sort(Asset.compareById);
     return _diffAssets(existing, deleteCandidates, compare: Asset.compareById)
-        .third
+        .$3
         .map((e) => e.id)
         .toList();
   }
@@ -165,14 +164,14 @@ class SyncService {
         .thenByFileModifiedAt()
         .findAll();
     remote.sort(Asset.compareByOwnerDeviceLocalIdModified);
-    final diff = _diffAssets(remote, inDb, remote: true);
-    if (diff.first.isEmpty && diff.second.isEmpty && diff.third.isEmpty) {
+    final (toAdd, toUpdate, toRemove) = _diffAssets(remote, inDb, remote: true);
+    if (toAdd.isEmpty && toUpdate.isEmpty && toRemove.isEmpty) {
       return false;
     }
-    final idsToDelete = diff.third.map((e) => e.id).toList();
+    final idsToDelete = toRemove.map((e) => e.id).toList();
     try {
       await _db.writeTxn(() => _db.assets.deleteAll(idsToDelete));
-      await upsertAssetsWithExif(diff.first + diff.second);
+      await upsertAssetsWithExif(toAdd + toUpdate);
     } on IsarError catch (e) {
       _log.severe("Failed to sync remote assets to db: $e");
     }
@@ -252,8 +251,7 @@ class SyncService {
         .findAll();
     final List<Asset> assetsOnRemote = dto.getAssets();
     assetsOnRemote.sort(Asset.compareByOwnerDeviceLocalIdModified);
-    final d = _diffAssets(assetsOnRemote, assetsInDb);
-    final List<Asset> toAdd = d.first, toUpdate = d.second, toUnlink = d.third;
+    final (toAdd, toUpdate, toUnlink) = _diffAssets(assetsOnRemote, assetsInDb);
 
     // update shared users
     final List<User> sharedUsers = album.sharedUsers.toList(growable: false);
@@ -271,9 +269,9 @@ class SyncService {
     );
 
     // for shared album: put missing album assets into local DB
-    final resultPair = await _linkWithExistingFromDb(toAdd);
-    await upsertAssetsWithExif(resultPair.second);
-    final assetsToLink = resultPair.first + resultPair.second;
+    final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd);
+    await upsertAssetsWithExif(updated);
+    final assetsToLink = existingInDb + updated;
     final usersToLink = (await _db.users.getAllById(userIdsToAdd)).cast<User>();
 
     album.name = dto.albumName;
@@ -327,9 +325,10 @@ class SyncService {
     if (dto.assetCount == dto.assets.length) {
       // in case an album contains assets not yet present in local DB:
       // put missing album assets into local DB
-      final result = await _linkWithExistingFromDb(dto.getAssets());
-      existing.addAll(result.first);
-      await upsertAssetsWithExif(result.second);
+      final (existingInDb, updated) =
+          await _linkWithExistingFromDb(dto.getAssets());
+      existing.addAll(existingInDb);
+      await upsertAssetsWithExif(updated);
 
       final Album a = await Album.remote(dto);
       await _db.writeTxn(() => _db.albums.store(a));
@@ -393,18 +392,19 @@ class SyncService {
     _log.fine(
       "Syncing all local albums almost done. Collected ${deleteCandidates.length} asset candidates to delete",
     );
-    final pair = _handleAssetRemoval(deleteCandidates, existing, remote: false);
+    final (toDelete, toUpdate) =
+        _handleAssetRemoval(deleteCandidates, existing, remote: false);
     _log.fine(
-      "${pair.first.length} assets to delete, ${pair.second.length} to update",
+      "${toDelete.length} assets to delete, ${toUpdate.length} to update",
     );
-    if (pair.first.isNotEmpty || pair.second.isNotEmpty) {
+    if (toDelete.isNotEmpty || toUpdate.isNotEmpty) {
       await _db.writeTxn(() async {
-        await _db.assets.deleteAll(pair.first);
-        await _db.exifInfos.deleteAll(pair.first);
-        await _db.assets.putAll(pair.second);
+        await _db.assets.deleteAll(toDelete);
+        await _db.exifInfos.deleteAll(toDelete);
+        await _db.assets.putAll(toUpdate);
       });
       _log.info(
-        "Removed ${pair.first.length} and updated ${pair.second.length} local assets from DB",
+        "Removed ${toDelete.length} and updated ${toUpdate.length} local assets from DB",
       );
     }
     return anyChanges;
@@ -441,8 +441,8 @@ class SyncService {
     final List<Asset> onDevice =
         await ape.getAssets(excludedAssets: excludedAssets);
     onDevice.sort(Asset.compareByLocalId);
-    final d = _diffAssets(onDevice, inDb, compare: Asset.compareByLocalId);
-    final List<Asset> toAdd = d.first, toUpdate = d.second, toDelete = d.third;
+    final (toAdd, toUpdate, toDelete) =
+        _diffAssets(onDevice, inDb, compare: Asset.compareByLocalId);
     if (toAdd.isEmpty &&
         toUpdate.isEmpty &&
         toDelete.isEmpty &&
@@ -458,12 +458,12 @@ class SyncService {
     _log.fine(
       "Syncing local album ${ape.name}. ${toAdd.length} assets to add, ${toUpdate.length} to update, ${toDelete.length} to delete",
     );
-    final result = await _linkWithExistingFromDb(toAdd);
+    final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd);
     _log.fine(
-      "Linking assets to add with existing from db. ${result.first.length} existing, ${result.second.length} to update",
+      "Linking assets to add with existing from db. ${existingInDb.length} existing, ${updated.length} to update",
     );
     deleteCandidates.addAll(toDelete);
-    existing.addAll(result.first);
+    existing.addAll(existingInDb);
     album.name = ape.name;
     album.modifiedAt = ape.lastModified ?? DateTime.now();
     if (album.thumbnail.value != null &&
@@ -472,10 +472,10 @@ class SyncService {
     }
     try {
       await _db.writeTxn(() async {
-        await _db.assets.putAll(result.second);
+        await _db.assets.putAll(updated);
         await _db.assets.putAll(toUpdate);
         await album.assets
-            .update(link: result.first + result.second, unlink: toDelete);
+            .update(link: existingInDb + updated, unlink: toDelete);
         await _db.albums.put(album);
         album.thumbnail.value ??= await album.assets.filter().findFirst();
         await album.thumbnail.save();
@@ -510,11 +510,11 @@ class SyncService {
       return false;
     }
     album.modifiedAt = ape.lastModified ?? DateTime.now();
-    final result = await _linkWithExistingFromDb(newAssets);
+    final (existingInDb, updated) = await _linkWithExistingFromDb(newAssets);
     try {
       await _db.writeTxn(() async {
-        await _db.assets.putAll(result.second);
-        await album.assets.update(link: result.first + result.second);
+        await _db.assets.putAll(updated);
+        await album.assets.update(link: existingInDb + updated);
         await _db.albums.put(album);
       });
       _log.info("Fast synced local album ${ape.name} to DB");
@@ -536,15 +536,15 @@ class SyncService {
     _log.info("Syncing a new local album to DB: ${ape.name}");
     final Album a = Album.local(ape);
     final assets = await ape.getAssets(excludedAssets: excludedAssets);
-    final result = await _linkWithExistingFromDb(assets);
+    final (existingInDb, updated) = await _linkWithExistingFromDb(assets);
     _log.info(
-      "${result.first.length} assets already existed in DB, to upsert ${result.second.length}",
+      "${existingInDb.length} assets already existed in DB, to upsert ${updated.length}",
     );
-    await upsertAssetsWithExif(result.second);
-    existing.addAll(result.first);
-    a.assets.addAll(result.first);
-    a.assets.addAll(result.second);
-    final thumb = result.first.firstOrNull ?? result.second.firstOrNull;
+    await upsertAssetsWithExif(updated);
+    existing.addAll(existingInDb);
+    a.assets.addAll(existingInDb);
+    a.assets.addAll(updated);
+    final thumb = existingInDb.firstOrNull ?? updated.firstOrNull;
     a.thumbnail.value = thumb;
     try {
       await _db.writeTxn(() => _db.albums.store(a));
@@ -555,11 +555,11 @@ class SyncService {
   }
 
   /// Returns a tuple (existing, updated)
-  Future<Pair<List<Asset>, List<Asset>>> _linkWithExistingFromDb(
+  Future<(List<Asset> existing, List<Asset> updated)> _linkWithExistingFromDb(
     List<Asset> assets,
   ) async {
     if (assets.isEmpty) {
-      return const Pair([], []);
+      return ([].cast<Asset>(), [].cast<Asset>());
     }
     final List<Asset> inDb = await _db.assets
         .where()
@@ -596,7 +596,7 @@ class SyncService {
       ),
       onlySecond: (Asset b) => toUpsert.add(b),
     );
-    return Pair(existing, toUpsert);
+    return (existing, toUpsert);
   }
 
   /// Inserts or updates the assets in the database with their ExifInfo (if any)
@@ -623,7 +623,7 @@ class SyncService {
 }
 
 /// Returns a triple(toAdd, toUpdate, toRemove)
-Triple<List<Asset>, List<Asset>, List<Asset>> _diffAssets(
+(List<Asset> toAdd, List<Asset> toUpdate, List<Asset> toRemove) _diffAssets(
   List<Asset> assets,
   List<Asset> inDb, {
   bool? remote,
@@ -660,30 +660,30 @@ Triple<List<Asset>, List<Asset>, List<Asset>> _diffAssets(
     },
     onlySecond: (Asset b) => toAdd.add(b),
   );
-  return Triple(toAdd, toUpdate, toRemove);
+  return (toAdd, toUpdate, toRemove);
 }
 
 /// returns a tuple (toDelete toUpdate) when assets are to be deleted
-Pair<List<int>, List<Asset>> _handleAssetRemoval(
+(List<int> toDelete, List<Asset> toUpdate) _handleAssetRemoval(
   List<Asset> deleteCandidates,
   List<Asset> existing, {
   bool? remote,
 }) {
   if (deleteCandidates.isEmpty) {
-    return const Pair([], []);
+    return const ([], []);
   }
   deleteCandidates.sort(Asset.compareById);
   deleteCandidates.uniqueConsecutive((a) => a.id);
   existing.sort(Asset.compareById);
   existing.uniqueConsecutive((a) => a.id);
-  final triple = _diffAssets(
+  final (tooAdd, toUpdate, toRemove) = _diffAssets(
     existing,
     deleteCandidates,
     compare: Asset.compareById,
     remote: remote,
   );
-  assert(triple.first.isEmpty, "toAdd should be empty in _handleAssetRemoval");
-  return Pair(triple.third.map((e) => e.id).toList(), triple.second);
+  assert(tooAdd.isEmpty, "toAdd should be empty in _handleAssetRemoval");
+  return (toRemove.map((e) => e.id).toList(), toUpdate);
 }
 
 /// returns `true` if the albums differ on the surface

+ 3 - 5
mobile/lib/utils/openapi_extensions.dart

@@ -4,8 +4,6 @@ import 'dart:io';
 import 'package:http/http.dart';
 import 'package:openapi/api.dart';
 
-import 'tuple.dart';
-
 /// Extension methods to retrieve ETag together with the API call
 extension WithETag on AssetApi {
   /// Get all AssetEntity belong to the user
@@ -14,7 +12,7 @@ extension WithETag on AssetApi {
   ///
   /// * [String] eTag:
   ///   ETag of data already cached on the client
-  Future<Pair<List<AssetResponseDto>, String?>?> getAllAssetsWithETag({
+  Future<(List<AssetResponseDto>? assets, String? eTag)> getAllAssetsWithETag({
     String? eTag,
   }) async {
     final response = await getAllAssetsWithHttpInfo(
@@ -36,9 +34,9 @@ extension WithETag on AssetApi {
       ) as List)
           .cast<AssetResponseDto>()
           .toList();
-      return Pair(data, etag);
+      return (data, etag);
     }
-    return null;
+    return (null, null);
   }
 }
 

+ 0 - 18
mobile/lib/utils/tuple.dart

@@ -1,18 +0,0 @@
-/// An immutable pair or 2-tuple
-/// TODO replace with Record once Dart 2.19 is available
-class Pair<T1, T2> {
-  final T1 first;
-  final T2 second;
-
-  const Pair(this.first, this.second);
-}
-
-/// An immutable triple or 3-tuple
-/// TODO replace with Record once Dart 2.19 is available
-class Triple<T1, T2, T3> {
-  final T1 first;
-  final T2 second;
-  final T3 third;
-
-  const Triple(this.first, this.second, this.third);
-}

+ 1 - 1
mobile/pubspec.yaml

@@ -6,7 +6,7 @@ version: 1.56.2+79
 isar_version: &isar_version 3.0.5
 
 environment:
-  sdk: ">=2.17.0 <3.0.0"
+  sdk: ">=3.0.0-0 <4.0.0"
 
 dependencies:
   flutter: