Browse Source

Handle cancellation for uploads when folder is unmarked for backup

Neeraj Gupta 2 năm trước cách đây
mục cha
commit
01c29037dd

+ 18 - 0
lib/db/device_files_db.dart

@@ -236,6 +236,24 @@ extension DeviceFiles on FilesDB {
     }
   }
 
+  // getDeviceSyncCollectionIDs returns the collectionIDs for the
+  // deviceCollections which are marked for auto-backup
+  Future<Set<int>> getDeviceSyncCollectionIDs() async {
+    final Database db = await database;
+    final rows = await db.rawQuery(
+      '''
+      SELECT collection_id FROM device_collections where should_backup = 
+      $_sqlBoolTrue 
+      and collection_id != -1;
+      ''',
+    );
+    final Set<int> result = <int>{};
+    for (final row in rows) {
+      result.add(row['collection_id']);
+    }
+    return result;
+  }
+
   Future<void> updateDevicePathSyncStatus(Map<String, bool> syncStatus) async {
     final db = await database;
     var batch = db.batch();

+ 47 - 0
lib/db/files_db.dart

@@ -1064,6 +1064,17 @@ class FilesDB {
     );
   }
 
+  Future<int> deleteMultipleByGeneratedIDs(List<int> generatedIDs) async {
+    if (generatedIDs.isEmpty) {
+      return 0;
+    }
+    final db = await instance.database;
+    return await db.delete(
+      filesTable,
+      where: '$columnGeneratedID IN (${generatedIDs.join(', ')})',
+    );
+  }
+
   Future<int> deleteLocalFile(File file) async {
     final db = await instance.database;
     if (file.localID != null) {
@@ -1187,6 +1198,42 @@ class FilesDB {
     );
   }
 
+  Future<List<File>> getPendingUploadForCollection(int collectionID) async {
+    final db = await instance.database;
+    final results = await db.query(
+      filesTable,
+      where: '$columnCollectionID = ? AND ($columnUploadedFileID IS NULL OR '
+          '$columnUploadedFileID = -1)',
+      whereArgs: [collectionID],
+    );
+    return convertToFiles(results);
+  }
+
+  Future<Set<String>> getLocalIDsPresentInEntries(
+    List<File> existingFiles,
+    int collectionID,
+  ) async {
+    String inParam = "";
+    for (final existingFile in existingFiles) {
+      inParam += "'" + existingFile.localID + "',";
+    }
+    inParam = inParam.substring(0, inParam.length - 1);
+    final db = await instance.database;
+    final rows = await db.rawQuery(
+      '''
+      SELECT $columnLocalID
+      FROM $filesTable
+      WHERE $columnLocalID IN ($inParam) AND $columnCollectionID != 
+      $collectionID AND $columnLocalID IS NOT NULL;
+    ''',
+    );
+    final result = <String>{};
+    for (final row in rows) {
+      result.add(row[columnLocalID]);
+    }
+    return result;
+  }
+
   Future<List<File>> getLatestLocalFiles() async {
     final db = await instance.database;
     final rows = await db.rawQuery(

+ 100 - 46
lib/services/remote_sync_service.dart

@@ -5,6 +5,7 @@ import 'dart:io';
 import 'dart:math';
 
 import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
 import 'package:logging/logging.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/errors.dart';
@@ -26,6 +27,7 @@ import 'package:photos/services/collections_service.dart';
 import 'package:photos/services/ignored_files_service.dart';
 import 'package:photos/services/local_file_update_service.dart';
 import 'package:photos/services/local_sync_service.dart';
+import 'package:photos/services/sync_service.dart';
 import 'package:photos/services/trash_sync_service.dart';
 import 'package:photos/utils/diff_fetcher.dart';
 import 'package:photos/utils/file_uploader.dart';
@@ -254,59 +256,111 @@ class RemoteSyncService {
             await filesDB.getLocalIDsMarkedForOrAlreadyUploaded(ownerID);
         localIDsToSync.removeAll(alreadyClaimedLocalIDs);
       }
-      if (localIDsToSync.isNotEmpty && deviceCollection.collectionID != -1) {
-        await filesDB.setCollectionIDForUnMappedLocalFiles(
-          deviceCollection.collectionID,
-          localIDsToSync,
+
+      if (localIDsToSync.isEmpty || deviceCollection.collectionID == -1) {
+        continue;
+      }
+
+      await filesDB.setCollectionIDForUnMappedLocalFiles(
+        deviceCollection.collectionID,
+        localIDsToSync,
+      );
+
+      // mark IDs as already synced if corresponding entry is present in
+      // the collection. This can happen when a user has marked a folder
+      // for sync, then un-synced it and again tries to mark if for sync.
+      final Set<String> existingMapping = await filesDB
+          .getLocalFileIDsForCollection(deviceCollection.collectionID);
+      final Set<String> commonElements =
+          localIDsToSync.intersection(existingMapping);
+      if (commonElements.isNotEmpty) {
+        debugPrint(
+          "${commonElements.length} files already existing in "
+          "collection ${deviceCollection.collectionID} for ${deviceCollection.name}",
         );
+        localIDsToSync.removeAll(commonElements);
+      }
 
-        // mark IDs as already synced if corresponding entry is present in
-        // the collection. This can happen when a user has marked a folder
-        // for sync, then un-synced it and again tries to mark if for sync.
-        final Set<String> existingMapping = await filesDB
-            .getLocalFileIDsForCollection(deviceCollection.collectionID);
-        final Set<String> commonElements =
-            localIDsToSync.intersection(existingMapping);
-        if (commonElements.isNotEmpty) {
-          debugPrint(
-            "${commonElements.length} files already existing in "
-            "collection ${deviceCollection.collectionID} for ${deviceCollection.name}",
+      // At this point, the remaining localIDsToSync will need to create
+      // new file entries, where we can store mapping for localID and
+      // corresponding collection ID
+      if (localIDsToSync.isNotEmpty) {
+        debugPrint(
+          'Adding new entries for ${localIDsToSync.length} files'
+          ' for ${deviceCollection.name}',
+        );
+        final filesWithCollectionID =
+            await filesDB.getLocalFiles(localIDsToSync.toList());
+        final List<File> newFilesToInsert = [];
+        final Set<String> fileFoundForLocalIDs = {};
+        for (var existingFile in filesWithCollectionID) {
+          final String localID = existingFile.localID;
+          if (!fileFoundForLocalIDs.contains(localID)) {
+            existingFile.generatedID = null;
+            existingFile.collectionID = deviceCollection.collectionID;
+            existingFile.uploadedFileID = null;
+            existingFile.ownerID = null;
+            newFilesToInsert.add(existingFile);
+            fileFoundForLocalIDs.add(localID);
+          }
+        }
+        await filesDB.insertMultiple(newFilesToInsert);
+        if (fileFoundForLocalIDs.length != localIDsToSync.length) {
+          _logger.warning(
+            "mismatch in num of filesToSync ${localIDsToSync.length} to "
+            "fileSynced ${fileFoundForLocalIDs.length}",
           );
-          localIDsToSync.removeAll(commonElements);
         }
+      }
+    }
+  }
 
-        // At this point, the remaining localIDsToSync will need to create
-        // new file entries, where we can store mapping for localID and
-        // corresponding collection ID
-        if (localIDsToSync.isNotEmpty) {
-          debugPrint(
-            'Adding new entries for ${localIDsToSync.length} files'
-            ' for ${deviceCollection.name}',
-          );
-          final filesWithCollectionID =
-              await filesDB.getLocalFiles(localIDsToSync.toList());
-          final List<File> newFilesToInsert = [];
-          final Set<String> fileFoundForLocalIDs = {};
-          for (var existingFile in filesWithCollectionID) {
-            final String localID = existingFile.localID;
-            if (!fileFoundForLocalIDs.contains(localID)) {
-              existingFile.generatedID = null;
-              existingFile.collectionID = deviceCollection.collectionID;
-              existingFile.uploadedFileID = null;
-              existingFile.ownerID = null;
-              newFilesToInsert.add(existingFile);
-              fileFoundForLocalIDs.add(localID);
-            }
-          }
-          await filesDB.insertMultiple(newFilesToInsert);
-          if (fileFoundForLocalIDs.length != localIDsToSync.length) {
-            _logger.warning(
-              "mismatch in num of filesToSync ${localIDsToSync.length} to "
-              "fileSynced ${fileFoundForLocalIDs.length}",
-            );
-          }
+  Future<void> updateDeviceFolderSyncStatus(
+    Map<String, bool> syncStatusUpdate,
+  ) async {
+    final Set<int> oldCollectionIDsForAutoSync =
+        await _db.getDeviceSyncCollectionIDs();
+    await _db.updateDevicePathSyncStatus(syncStatusUpdate);
+    final Set<int> newCollectionIDsForAutoSync =
+        await _db.getDeviceSyncCollectionIDs();
+    SyncService.instance.onDeviceCollectionSet(newCollectionIDsForAutoSync);
+    // remove all collectionIDs which are still marked for backup
+    oldCollectionIDsForAutoSync.removeAll(newCollectionIDsForAutoSync);
+    await removeFilesQueuedForUpload(oldCollectionIDsForAutoSync.toList());
+    Bus.instance.fire(LocalPhotosUpdatedEvent(<File>[]));
+  }
+
+  Future<void> removeFilesQueuedForUpload(List<int> collectionIDs) async {
+    /*
+      For each collection, perform following action
+      1) Get List of all files not uploaded yet
+      2) Delete files who localIDs is also present in other collections.
+      3) For Remaining files, set the collectionID as -1
+     */
+    debugPrint("Removing files for collections $collectionIDs");
+    for (int collectionID in collectionIDs) {
+      final List<File> pendingUploads =
+          await _db.getPendingUploadForCollection(collectionID);
+      if (pendingUploads.isEmpty) {
+        continue;
+      }
+      final Set<String> localIDsInOtherFileEntries =
+          await _db.getLocalIDsPresentInEntries(
+        pendingUploads,
+        collectionID,
+      );
+      final List<File> entriesToUpdate = [];
+      final List<int> entriesToDelete = [];
+      for (File pendingUpload in pendingUploads) {
+        if (localIDsInOtherFileEntries.contains(pendingUpload.localID)) {
+          entriesToDelete.add(pendingUpload.generatedID);
+        } else {
+          pendingUpload.collectionID = -1;
+          entriesToUpdate.add(pendingUpload);
         }
       }
+      await _db.deleteMultipleByGeneratedIDs(entriesToDelete);
+      await _db.insertMultiple(entriesToUpdate);
     }
   }
 

+ 9 - 0
lib/services/sync_service.dart

@@ -179,6 +179,15 @@ class SyncService {
     );
   }
 
+  void onDeviceCollectionSet(Set<int> collectionIDs) {
+    _uploader.removeFromQueueWhere(
+      (file) {
+        return !collectionIDs.contains(file.collectionID);
+      },
+      UserCancelledUploadError(),
+    );
+  }
+
   void onVideoBackupPaused() {
     _uploader.removeFromQueueWhere(
       (file) {

+ 3 - 2
lib/ui/backup_folder_selection_page.dart

@@ -16,6 +16,7 @@ import 'package:photos/ente_theme_data.dart';
 import 'package:photos/events/backup_folders_updated_event.dart';
 import 'package:photos/models/device_collection.dart';
 import 'package:photos/models/file.dart';
+import 'package:photos/services/remote_sync_service.dart';
 import 'package:photos/ui/common/loading_widget.dart';
 import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
 
@@ -181,8 +182,8 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
                               syncStatus[pathID] =
                                   _selectedDevicePathIDs.contains(pathID);
                             }
-                            await FilesDB.instance
-                                .updateDevicePathSyncStatus(syncStatus);
+                            await RemoteSyncService.instance
+                                .updateDeviceFolderSyncStatus(syncStatus);
                             await Configuration.instance
                                 .setSelectAllFoldersForBackup(
                               _allDevicePathIDs.length ==

+ 2 - 1
lib/ui/viewer/gallery/device_folder_page.dart

@@ -12,6 +12,7 @@ import 'package:photos/events/local_photos_updated_event.dart';
 import 'package:photos/models/device_collection.dart';
 import 'package:photos/models/gallery_type.dart';
 import 'package:photos/models/selected_files.dart';
+import 'package:photos/services/remote_sync_service.dart';
 import 'package:photos/ui/viewer/gallery/gallery.dart';
 import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart';
 import 'package:photos/ui/viewer/gallery/gallery_overlay_widget.dart';
@@ -114,7 +115,7 @@ class _BackupConfigurationHeaderWidgetState
           Switch(
             value: _isBackedUp,
             onChanged: (value) async {
-              await FilesDB.instance.updateDevicePathSyncStatus(
+              await RemoteSyncService.instance.updateDeviceFolderSyncStatus(
                 {widget.deviceCollection.id: value},
               );
               _isBackedUp = value;