ente/lib/services/sync_service.dart

286 lines
9.5 KiB
Dart
Raw Normal View History

2020-03-30 14:28:46 +00:00
import 'dart:async';
2020-11-02 10:57:55 +00:00
import 'package:flutter/foundation.dart';
2020-05-02 16:28:54 +00:00
import 'package:logging/logging.dart';
import 'package:photos/core/event_bus.dart';
2020-07-20 11:03:09 +00:00
import 'package:photos/db/files_db.dart';
import 'package:photos/events/collection_updated_event.dart';
import 'package:photos/events/photo_upload_event.dart';
import 'package:photos/events/user_authenticated_event.dart';
2020-11-02 10:57:55 +00:00
import 'package:photos/models/file_type.dart';
import 'package:photos/services/collections_service.dart';
import 'package:photos/utils/date_time_util.dart';
2020-10-03 17:58:26 +00:00
import 'package:photos/utils/file_downloader.dart';
import 'package:photos/repositories/file_repository.dart';
2020-03-24 19:59:36 +00:00
import 'package:photo_manager/photo_manager.dart';
import 'package:photos/utils/file_sync_util.dart';
2020-10-03 17:58:26 +00:00
import 'package:photos/utils/file_uploader.dart';
import 'package:photos/utils/file_name_util.dart';
2020-03-24 19:59:36 +00:00
import 'package:shared_preferences/shared_preferences.dart';
import 'package:dio/dio.dart';
2020-06-19 23:03:26 +00:00
import 'package:photos/models/file.dart';
2020-03-26 14:39:31 +00:00
import 'package:photos/core/configuration.dart';
2020-04-30 15:09:41 +00:00
2020-10-03 17:58:26 +00:00
class SyncService {
final _logger = Logger("SyncService");
2020-03-28 13:56:06 +00:00
final _dio = Dio();
2020-07-20 11:03:09 +00:00
final _db = FilesDB.instance;
2020-10-21 16:20:41 +00:00
final _uploader = FileUploader.instance;
2020-10-28 15:45:05 +00:00
final _collectionsService = CollectionsService.instance;
final _downloader = DiffFetcher();
2020-04-30 15:09:41 +00:00
bool _isSyncInProgress = false;
bool _syncStopRequested = false;
Future<void> _existingSync;
SharedPreferences _prefs;
2020-04-27 13:02:29 +00:00
2020-10-28 15:45:05 +00:00
static final _collectionSyncTimeKeyPrefix = "collection_sync_time_";
2020-08-11 23:04:16 +00:00
static final _dbUpdationTimeKey = "db_updation_time";
2020-05-17 10:18:09 +00:00
static final _diffLimit = 100;
2020-03-24 19:59:36 +00:00
2020-10-03 17:58:26 +00:00
SyncService._privateConstructor() {
2020-04-30 15:09:41 +00:00
Bus.instance.on<UserAuthenticatedEvent>().listen((event) {
sync();
});
}
2020-10-03 17:58:26 +00:00
static final SyncService instance = SyncService._privateConstructor();
2020-03-28 13:56:06 +00:00
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
2020-04-30 15:09:41 +00:00
Future<void> sync() async {
_syncStopRequested = false;
2020-04-30 15:09:41 +00:00
if (_isSyncInProgress) {
2020-05-02 16:28:54 +00:00
_logger.warning("Sync already in progress, skipping.");
return _existingSync;
2020-04-27 13:02:29 +00:00
}
2020-04-30 15:09:41 +00:00
_isSyncInProgress = true;
_existingSync = Future<void>(() async {
_logger.info("Syncing...");
try {
await _doSync();
2020-10-28 15:25:32 +00:00
} catch (e, s) {
_logger.severe(e, s);
} finally {
_isSyncInProgress = false;
}
});
return _existingSync;
}
2020-04-30 15:09:41 +00:00
void stopSync() {
_logger.info("Sync stop requested");
_syncStopRequested = true;
}
bool shouldStopSync() {
return _syncStopRequested;
}
bool hasScannedDisk() {
2020-08-11 23:04:16 +00:00
return _prefs.containsKey(_dbUpdationTimeKey);
2020-06-15 19:57:48 +00:00
}
Future<void> _doSync() async {
2020-07-27 21:07:56 +00:00
final result = await PhotoManager.requestPermission();
if (!result) {
_logger.severe("Did not get permission");
}
final syncStartTime = DateTime.now().microsecondsSinceEpoch;
final lastDBUpdationTime = _prefs.getInt(_dbUpdationTimeKey);
if (lastDBUpdationTime != null && lastDBUpdationTime != 0) {
await _loadAndStorePhotos(lastDBUpdationTime, syncStartTime);
} else {
// Load from 0 - 01.01.2010
var startTime = 0;
var toYear = 2010;
var toTime = DateTime(toYear).microsecondsSinceEpoch;
while (toTime < syncStartTime) {
await _loadAndStorePhotos(startTime, toTime);
startTime = toTime;
toYear++;
toTime = DateTime(toYear).microsecondsSinceEpoch;
2020-03-30 14:28:46 +00:00
}
await _loadAndStorePhotos(startTime, syncStartTime);
2020-10-30 21:07:20 +00:00
}
2020-10-30 20:37:21 +00:00
await syncWithRemote();
2020-04-24 12:40:24 +00:00
}
Future<void> _loadAndStorePhotos(int fromTime, int toTime) async {
_logger.info("Loading photos from " +
getMonthAndYear(DateTime.fromMicrosecondsSinceEpoch(fromTime)) +
" to " +
getMonthAndYear(DateTime.fromMicrosecondsSinceEpoch(toTime)));
final files = await getDeviceFiles(fromTime, toTime);
if (files.isNotEmpty) {
_logger.info("Fetched " + files.length.toString() + " files.");
await _db.insertMultiple(files);
_logger.info("Inserted " + files.length.toString() + " files.");
await _prefs.setInt(_dbUpdationTimeKey, toTime);
await FileRepository.instance.reloadFiles();
}
}
2020-10-30 20:37:21 +00:00
Future<void> syncWithRemote() async {
if (!Configuration.instance.hasConfiguredAccount()) {
return Future.error("Account not configured yet");
}
2020-10-28 15:45:05 +00:00
await _collectionsService.sync();
final collections = _collectionsService.getCollections();
for (final collection in collections) {
await _fetchEncryptedFilesDiff(collection.id);
}
await deleteFilesOnServer();
2020-11-09 12:28:43 +00:00
await _uploadDiff();
}
2020-10-28 15:45:05 +00:00
Future<void> _fetchEncryptedFilesDiff(int collectionID) async {
final diff = await _downloader.getEncryptedFilesDiff(
2020-10-28 15:45:05 +00:00
collectionID,
_getCollectionSyncTime(collectionID),
_diffLimit,
);
2020-09-03 16:50:26 +00:00
if (diff.isNotEmpty) {
2020-10-28 15:45:05 +00:00
await _storeDiff(diff, collectionID);
2020-08-11 23:04:16 +00:00
FileRepository.instance.reloadFiles();
Bus.instance.fire(CollectionUpdatedEvent(collectionID: collectionID));
2020-08-11 23:04:16 +00:00
if (diff.length == _diffLimit) {
2020-10-28 15:45:05 +00:00
return await _fetchEncryptedFilesDiff(collectionID);
2020-08-11 23:04:16 +00:00
}
}
}
2020-10-28 15:45:05 +00:00
int _getCollectionSyncTime(int collectionID) {
var syncTime =
_prefs.getInt(_collectionSyncTimeKeyPrefix + collectionID.toString());
2020-08-11 23:04:16 +00:00
if (syncTime == null) {
syncTime = 0;
}
return syncTime;
}
2020-10-28 15:45:05 +00:00
Future<void> _setCollectionSyncTime(int collectionID, int time) async {
return _prefs.setInt(
_collectionSyncTimeKeyPrefix + collectionID.toString(), time);
}
Future<void> _uploadDiff() async {
final foldersToBackUp = Configuration.instance.getPathsToBackUp();
2020-09-17 18:48:25 +00:00
List<File> filesToBeUploaded =
await _db.getFilesToBeUploadedWithinFolders(foldersToBackUp);
2020-11-09 12:28:43 +00:00
final futures = List<Future>();
2020-09-17 18:48:25 +00:00
for (int i = 0; i < filesToBeUploaded.length; i++) {
if (_syncStopRequested) {
_syncStopRequested = false;
Bus.instance.fire(PhotoUploadEvent(wasStopped: true));
return;
}
2020-09-17 18:48:25 +00:00
File file = filesToBeUploaded[i];
2020-11-02 10:57:55 +00:00
if (kDebugMode) {
if (file.fileType == FileType.video) {
continue;
}
}
2020-05-17 12:39:38 +00:00
try {
2020-10-21 16:20:41 +00:00
file.collectionID = (await CollectionsService.instance
.getOrCreateForPath(file.deviceFolder))
.id;
2020-11-09 12:28:43 +00:00
final currentFile = await _db.getFile(file.generatedID);
Future<void> future;
if (currentFile.uploadedFileID != null) {
// The file was uploaded outside this loop
// Eg: Addition to an album or favorites
2020-11-09 12:28:43 +00:00
future = CollectionsService.instance
.addToCollection(file.collectionID, [currentFile]);
2020-08-10 23:47:22 +00:00
} else {
2020-11-09 12:28:43 +00:00
if (_uploader.getCurrentUploadStatus(file) != null) {
// The file is currently being uploaded outside this loop
// Eg: Addition to an album or favorites
future = _uploader
.getCurrentUploadStatus(file)
.then((uploadedFile) async {
await CollectionsService.instance
.addToCollection(file.collectionID, [uploadedFile]);
});
} else {
future = _uploader.addToQueue(file).then((uploadedFile) async {
await _db.update(uploadedFile);
});
}
2020-08-10 23:47:22 +00:00
}
2020-11-09 12:28:43 +00:00
futures.add(future.then((value) {
Bus.instance
.fire(CollectionUpdatedEvent(collectionID: file.collectionID));
Bus.instance.fire(PhotoUploadEvent(
completed: i + 1, total: filesToBeUploaded.length));
}));
2020-05-17 12:39:38 +00:00
} catch (e) {
Bus.instance.fire(PhotoUploadEvent(hasError: true));
throw e;
2020-04-13 15:01:27 +00:00
}
2020-03-26 14:39:31 +00:00
}
2020-11-09 12:28:43 +00:00
await Future.wait(futures);
2020-03-26 14:39:31 +00:00
}
2020-10-28 15:45:05 +00:00
Future _storeDiff(List<File> diff, int collectionID) async {
2020-06-19 23:03:26 +00:00
for (File file in diff) {
final existingFiles = await _db.getMatchingFiles(file.title,
file.deviceFolder, file.creationTime, file.modificationTime,
alternateTitle: getHEICFileNameForJPG(file));
if (existingFiles == null) {
// File uploaded from a different device
file.localID = null;
await _db.insert(file);
} else {
// File exists on device
bool wasUploadedOnAPreviousInstallation =
existingFiles.length == 1 && existingFiles[0].collectionID == null;
file.localID = existingFiles[0]
.localID; // File should ideally have the same localID
if (wasUploadedOnAPreviousInstallation) {
file.generatedID = existingFiles[0].generatedID;
await _db.update(file);
} else {
bool wasUpdatedInExistingCollection = false;
for (final existingFile in existingFiles) {
if (file.collectionID == existingFile.collectionID) {
file.generatedID = existingFile.generatedID;
wasUpdatedInExistingCollection = true;
break;
}
}
if (wasUpdatedInExistingCollection) {
await _db.update(file);
} else {
// Added to a new collection
await _db.insert(file);
}
}
}
2020-10-28 15:45:05 +00:00
await _setCollectionSyncTime(collectionID, file.updationTime);
2020-03-28 13:56:06 +00:00
}
}
2020-03-26 14:39:31 +00:00
Future<void> deleteFilesOnServer() async {
2020-10-30 23:25:28 +00:00
return _db.getDeletedFileIDs().then((ids) async {
for (int id in ids) {
await _deleteFileOnServer(id);
await _db.delete(id);
2020-04-12 12:38:49 +00:00
}
});
}
2020-10-30 23:25:28 +00:00
Future<void> _deleteFileOnServer(int fileID) async {
2020-05-17 10:18:09 +00:00
return _dio
.delete(
Configuration.instance.getHttpEndpoint() +
"/files/" +
2020-10-30 23:25:28 +00:00
fileID.toString(),
2020-05-17 10:18:09 +00:00
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
)
.catchError((e) => _logger.severe(e));
2020-03-29 14:04:26 +00:00
}
2020-03-24 19:59:36 +00:00
}