diff --git a/lib/db/db_helper.dart b/lib/db/db_helper.dart index 7efb2b0b5..dfe0e923e 100644 --- a/lib/db/db_helper.dart +++ b/lib/db/db_helper.dart @@ -108,10 +108,27 @@ class DatabaseHelper { return _convertToPhotos(results); } - Future updatePhoto( - int generatedId, String remotePath, int syncTimestamp) async { + Future getMatchingPhoto(String localId, String title, + String deviceFolder, int createTimestamp) async { + final db = await instance.database; + final rows = await db.query( + table, + where: + '$columnLocalId=? AND $columnTitle=? AND $columnDeviceFolder=? AND $columnCreateTimestamp=?', + whereArgs: [localId, title, deviceFolder, createTimestamp], + ); + if (rows.isNotEmpty) { + return _getPhotoFromRow(rows[0]); + } else { + throw ("No matching photo found"); + } + } + + Future updatePhoto(int generatedId, int uploadedId, String remotePath, + int syncTimestamp) async { final db = await instance.database; final values = new Map(); + values[columnUploadedFileId] = uploadedId; values[columnRemotePath] = remotePath; values[columnSyncTimestamp] = syncTimestamp; return await db.update( @@ -130,12 +147,13 @@ class DatabaseHelper { whereArgs: [path], ); if (rows.isNotEmpty) { - return _getPhotofromRow(rows[0]); + return _getPhotoFromRow(rows[0]); } else { throw ("No cached photo"); } } + // TODO: Remove deleted photos on remote Future markPhotoForDeletion(Photo photo) async { final db = await instance.database; var values = new Map(); @@ -181,7 +199,7 @@ class DatabaseHelper { limit: 1, ); if (rows.isNotEmpty) { - return _getPhotofromRow(rows[0]); + return _getPhotoFromRow(rows[0]); } else { throw ("No photo found in path"); } @@ -197,7 +215,7 @@ class DatabaseHelper { limit: 1, ); if (rows.isNotEmpty) { - return _getPhotofromRow(rows[0]); + return _getPhotoFromRow(rows[0]); } else { throw ("No photo found with ids " + generatedIds.join(", ").toString()); } @@ -206,7 +224,7 @@ class DatabaseHelper { List _convertToPhotos(List> results) { var photos = List(); for (var result in results) { - photos.add(_getPhotofromRow(result)); + photos.add(_getPhotoFromRow(result)); } return photos; } @@ -224,7 +242,7 @@ class DatabaseHelper { return row; } - Photo _getPhotofromRow(Map row) { + Photo _getPhotoFromRow(Map row) { Photo photo = Photo(); photo.generatedId = row[columnGeneratedId]; photo.localId = row[columnLocalId]; diff --git a/lib/models/photo.dart b/lib/models/photo.dart index e2e80a081..332725cb1 100644 --- a/lib/models/photo.dart +++ b/lib/models/photo.dart @@ -17,9 +17,10 @@ class Photo { Photo(); Photo.fromJson(Map json) - : uploadedFileId = json["fileId"], - title = json["title"], + : uploadedFileId = json["fileID"], + localId = json["deviceFileID"], deviceFolder = json["deviceFolder"], + title = json["title"], remotePath = json["path"], createTimestamp = json["createTimestamp"], syncTimestamp = json["syncTimestamp"]; diff --git a/lib/photo_sync_manager.dart b/lib/photo_sync_manager.dart index d6a723161..36ade276d 100644 --- a/lib/photo_sync_manager.dart +++ b/lib/photo_sync_manager.dart @@ -20,6 +20,7 @@ import 'package:photos/events/remote_sync_event.dart'; class PhotoSyncManager { final _logger = Logger("PhotoSyncManager"); final _dio = Dio(); + final _db = DatabaseHelper.instance; bool _isSyncInProgress = false; static final _lastSyncTimestampKey = "last_sync_timestamp_0"; @@ -72,16 +73,16 @@ class PhotoSyncManager { } if (photos.isEmpty) { _isSyncInProgress = false; - _syncPhotos().then((_) { - _deletePhotos(); + _syncPhotosWithServer().then((_) { + _deletePhotosOnServer(); }); } else { photos.sort((first, second) => first.createTimestamp.compareTo(second.createTimestamp)); _updateDatabase(photos, prefs, lastDBUpdateTimestamp).then((_) { _isSyncInProgress = false; - _syncPhotos().then((_) { - _deletePhotos(); + _syncPhotosWithServer().then((_) { + _deletePhotosOnServer(); }); }); } @@ -99,37 +100,44 @@ class PhotoSyncManager { photosToBeAdded, prefs, DateTime.now().microsecondsSinceEpoch); } - _syncPhotos() async { + _syncPhotosWithServer() async { SharedPreferences prefs = await SharedPreferences.getInstance(); - var lastSyncTimestamp = prefs.getInt(_lastSyncTimestampKey); - if (lastSyncTimestamp == null) { - lastSyncTimestamp = 0; - } - _logger.info("Last sync timestamp: " + lastSyncTimestamp.toString()); - await _getDiff(lastSyncTimestamp, _diffLimit).then((diff) async { - if (diff != null) { - await _storeDiff(diff, prefs).then((_) { - // TODO: Recursively store diff - _uploadDiff(prefs); - }); + var shouldFetchDiff = true; + while (shouldFetchDiff) { + int lastSyncTimestamp = _getLastSyncTimestamp(prefs); + var diff = await _getDiff(lastSyncTimestamp, _diffLimit); + if (diff != null && diff.isNotEmpty) { + await _storeDiff(diff, prefs); + PhotoRepository.instance.reloadPhotos(); } - }); + if (diff.length < _diffLimit) { + shouldFetchDiff = false; + } + } + _uploadDiff(prefs); // TODO: Fix race conditions triggered due to concurrent syncs. // Add device_id/last_sync_timestamp to the upload request? } + int _getLastSyncTimestamp(SharedPreferences prefs) { + var lastSyncTimestamp = prefs.getInt(_lastSyncTimestampKey); + if (lastSyncTimestamp == null) { + lastSyncTimestamp = 0; + } + return lastSyncTimestamp; + } + Future _uploadDiff(SharedPreferences prefs) async { - List photosToBeUploaded = - await DatabaseHelper.instance.getPhotosToBeUploaded(); + List photosToBeUploaded = await _db.getPhotosToBeUploaded(); for (Photo photo in photosToBeUploaded) { try { var uploadedPhoto = await _uploadFile(photo); if (uploadedPhoto == null) { return; } - await DatabaseHelper.instance.updatePhoto(photo.generatedId, + await _db.updatePhoto(photo.generatedId, uploadedPhoto.uploadedFileId, uploadedPhoto.remotePath, uploadedPhoto.syncTimestamp); prefs.setInt(_lastSyncTimestampKey, uploadedPhoto.syncTimestamp); } catch (e) { @@ -140,10 +148,16 @@ class PhotoSyncManager { Future _storeDiff(List diff, SharedPreferences prefs) async { for (Photo photo in diff) { - await DatabaseHelper.instance.insertPhoto(photo); + try { + var existingPhoto = await _db.getMatchingPhoto(photo.localId, + photo.title, photo.deviceFolder, photo.createTimestamp); + await _db.updatePhoto(existingPhoto.generatedId, photo.uploadedFileId, + photo.remotePath, photo.syncTimestamp); + } catch (e) { + await _db.insertPhoto(photo); + } await prefs.setInt(_lastSyncTimestampKey, photo.syncTimestamp); } - PhotoRepository.instance.reloadPhotos(); } Future> _getDiff(int lastSyncTimestamp, int limit) async { @@ -190,11 +204,11 @@ class PhotoSyncManager { }); } - Future _deletePhotos() async { - DatabaseHelper.instance.getAllDeletedPhotos().then((deletedPhotos) { + Future _deletePhotosOnServer() async { + _db.getAllDeletedPhotos().then((deletedPhotos) { for (Photo deletedPhoto in deletedPhotos) { _deletePhotoOnServer(deletedPhoto) - .then((value) => DatabaseHelper.instance.deletePhoto(deletedPhoto)); + .then((value) => _db.deletePhoto(deletedPhoto)); } }); } @@ -219,7 +233,7 @@ class PhotoSyncManager { Future _insertPhotosToDB( List photos, SharedPreferences prefs, int timestamp) async { - await DatabaseHelper.instance.insertPhotos(photos); + await _db.insertPhotos(photos); _logger.info("Inserted " + photos.length.toString() + " photos."); PhotoRepository.instance.reloadPhotos(); return await prefs.setInt(_lastDBUpdateTimestampKey, timestamp);