diff --git a/lib/db/files_db.dart b/lib/db/files_db.dart index 46e609b2a..c3794b079 100644 --- a/lib/db/files_db.dart +++ b/lib/db/files_db.dart @@ -273,6 +273,79 @@ class FilesDB { return _convertToFiles(results); } + Future> getUploadedFileIDsToBeUpdated() async { + final db = await instance.database; + final rows = await db.query( + table, + columns: [columnUploadedFileID], + where: + '($columnUploadedFileID IS NOT NULL AND $columnUpdationTime IS NULL AND $columnIsDeleted = 0)', + orderBy: '$columnCreationTime DESC', + distinct: true, + ); + final uploadedFileIDs = List(); + for (final row in rows) { + uploadedFileIDs.add(row[columnUploadedFileID]); + } + return uploadedFileIDs; + } + + Future getUploadedFileInAnyCollection(int uploadedFileID) async { + final db = await instance.database; + final results = await db.query( + table, + where: '$columnUploadedFileID = ?', + whereArgs: [ + uploadedFileID, + ], + limit: 1, + ); + if (results.isEmpty) { + return null; + } + return _convertToFiles(results)[0]; + } + + Future> getUploadedLocalFileIDs() async { + final db = await instance.database; + final rows = await db.query( + table, + columns: [columnLocalID], + distinct: true, + where: + '$columnLocalID IS NOT NULL AND $columnUploadedFileID IS NOT NULL AND $columnIsDeleted = 0', + ); + final result = Set(); + for (final row in rows) { + result.add(row[columnLocalID]); + } + return result; + } + + Future updateUploadedFile( + String localID, + String title, + Location location, + int creationTime, + int modificationTime, + int updationTime, + ) async { + final db = await instance.database; + return await db.update( + table, + { + columnTitle: title, + columnLatitude: location.latitude, + columnLongitude: location.longitude, + columnCreationTime: creationTime, + columnModificationTime: modificationTime, + columnUpdationTime: updationTime, + }, + where: '$columnLocalID = ?', + whereArgs: [localID], + ); + } + Future> getLastCreatedFilesInCollections( List collectionIDs) async { final db = await instance.database; @@ -398,6 +471,16 @@ class FilesDB { ); } + Future updateUploadedFileAcrossCollections(File file) async { + final db = await instance.database; + return await db.update( + table, + _getRowForFileWithoutCollection(file), + where: '$columnUploadedFileID = ?', + whereArgs: [file.uploadedFileID], + ); + } + Future markForDeletion(int uploadedFileID) async { final db = await instance.database; final values = new Map(); @@ -573,6 +656,37 @@ class FilesDB { return row; } + Map _getRowForFileWithoutCollection(File file) { + final row = new Map(); + row[columnLocalID] = file.localID; + row[columnUploadedFileID] = file.uploadedFileID; + row[columnOwnerID] = file.ownerID; + row[columnTitle] = file.title; + row[columnDeviceFolder] = file.deviceFolder; + if (file.location != null) { + row[columnLatitude] = file.location.latitude; + row[columnLongitude] = file.location.longitude; + } + switch (file.fileType) { + case FileType.image: + row[columnFileType] = 0; + break; + case FileType.video: + row[columnFileType] = 1; + break; + default: + row[columnFileType] = -1; + } + row[columnIsEncrypted] = file.isEncrypted ? 1 : 0; + row[columnCreationTime] = file.creationTime; + row[columnModificationTime] = file.modificationTime; + row[columnUpdationTime] = file.updationTime; + row[columnFileDecryptionHeader] = file.fileDecryptionHeader; + row[columnThumbnailDecryptionHeader] = file.thumbnailDecryptionHeader; + row[columnMetadataDecryptionHeader] = file.metadataDecryptionHeader; + return row; + } + File _getFileFromRow(Map row) { final file = File(); file.generatedID = row[columnGeneratedID]; diff --git a/lib/services/sync_service.dart b/lib/services/sync_service.dart index 78efa80d8..052d165a2 100644 --- a/lib/services/sync_service.dart +++ b/lib/services/sync_service.dart @@ -30,6 +30,7 @@ class SyncService { final _uploader = FileUploader.instance; final _collectionsService = CollectionsService.instance; final _downloader = DiffFetcher(); + final _uploadedLocalFileIDs = Set(); bool _isSyncInProgress = false; bool _syncStopRequested = false; Future _existingSync; @@ -67,6 +68,7 @@ class SyncService { } Future sync() async { + _uploadedLocalFileIDs.addAll(await _db.getUploadedLocalFileIDs()); _syncStopRequested = false; if (_isSyncInProgress) { _logger.warning("Sync already in progress, skipping."); @@ -147,7 +149,20 @@ class SyncService { final files = await getDeviceFiles(fromTime, toTime); if (files.isNotEmpty) { _logger.info("Fetched " + files.length.toString() + " files."); - // Deal with file updates + final updatedFiles = + files.where((file) => _uploadedLocalFileIDs.contains(file.localID)); + _logger.info(updatedFiles.length.toString() + " files were updated."); + for (final file in updatedFiles) { + await _db.updateUploadedFile( + file.localID, + file.title, + file.location, + file.creationTime, + file.modificationTime, + null, + ); + } + files.removeWhere((file) => _uploadedLocalFileIDs.contains(file.localID)); await _db.insertMultiple(files); _logger.info("Inserted " + files.length.toString() + " files."); await _prefs.setInt(_dbUpdationTimeKey, toTime); @@ -201,30 +216,53 @@ class SyncService { Future _uploadDiff() async { final foldersToBackUp = Configuration.instance.getPathsToBackUp(); - List filesToBeUploaded = + final filesToBeUploaded = await _db.getFilesToBeUploadedWithinFolders(foldersToBackUp); if (kDebugMode) { - filesToBeUploaded = filesToBeUploaded - .where((element) => element.fileType != FileType.video) - .toList(); + filesToBeUploaded + .removeWhere((element) => element.fileType == FileType.video); } final futures = List(); - for (int i = 0; i < filesToBeUploaded.length; i++) { + + final updatedFileIDs = await _db.getUploadedFileIDsToBeUpdated(); + + int uploadCounter = 0; + final totalUploads = filesToBeUploaded.length + updatedFileIDs.length; + + for (final uploadedFileID in updatedFileIDs) { + if (_syncStopRequested) { + _syncStopRequested = false; + Bus.instance + .fire(SyncStatusUpdate(SyncStatus.completed, wasStopped: true)); + return; + } + final file = await _db.getUploadedFileInAnyCollection(uploadedFileID); + final future = _uploader.upload(file, file.collectionID).then((value) { + uploadCounter++; + Bus.instance + .fire(CollectionUpdatedEvent(collectionID: file.collectionID)); + Bus.instance.fire(SyncStatusUpdate(SyncStatus.in_progress, + completed: uploadCounter, total: totalUploads)); + }); + futures.add(future); + } + + for (final file in filesToBeUploaded) { if (_syncStopRequested) { _syncStopRequested = false; Bus.instance .fire(SyncStatusUpdate(SyncStatus.completed, wasStopped: true)); return; } - File file = filesToBeUploaded[i]; final collectionID = (await CollectionsService.instance .getOrCreateForPath(file.deviceFolder)) .id; final future = _uploader.upload(file, collectionID).then((value) { + uploadCounter++; Bus.instance .fire(CollectionUpdatedEvent(collectionID: file.collectionID)); Bus.instance.fire(SyncStatusUpdate(SyncStatus.in_progress, - completed: i + 1, total: filesToBeUploaded.length)); + completed: uploadCounter, total: totalUploads)); }); futures.add(future); } diff --git a/lib/utils/file_uploader.dart b/lib/utils/file_uploader.dart index a89d6e13d..1ed531d6c 100644 --- a/lib/utils/file_uploader.dart +++ b/lib/utils/file_uploader.dart @@ -125,7 +125,6 @@ class FileUploader { _currentlyUploading++; try { final uploadedFile = await _tryToUpload(file, collectionID, forcedUpload); - await FilesDB.instance.update(uploadedFile); _queue.remove(file.generatedID).completer.complete(uploadedFile); } catch (e) { _queue.remove(file.generatedID).completer.completeError(e); @@ -222,7 +221,7 @@ class FileUploader { final metadataDecryptionHeader = Sodium.bin2base64(encryptedMetadataData.header); if (isAlreadyUploadedFile) { - return await _updateFile( + final updatedFile = await _updateFile( file, fileObjectKey, fileDecryptionHeader, @@ -231,8 +230,11 @@ class FileUploader { encryptedMetadata, metadataDecryptionHeader, ); + // Update across all collections + await FilesDB.instance.updateUploadedFileAcrossCollections(updatedFile); + return updatedFile; } else { - return await _uploadFile( + final uploadedFile = await _uploadFile( file, collectionID, fileAttributes, @@ -243,6 +245,8 @@ class FileUploader { encryptedMetadata, metadataDecryptionHeader, ); + await FilesDB.instance.update(uploadedFile); + return uploadedFile; } } catch (e, s) { _logger.severe(