2022-09-07 08:30:09 +00:00
|
|
|
// @dart=2.9
|
|
|
|
|
2020-03-30 14:28:46 +00:00
|
|
|
import 'dart:async';
|
2020-11-30 10:20:08 +00:00
|
|
|
import 'dart:io';
|
2021-06-14 16:24:15 +00:00
|
|
|
|
2020-11-16 16:35:16 +00:00
|
|
|
import 'package:connectivity/connectivity.dart';
|
2021-06-14 16:24:15 +00:00
|
|
|
import 'package:dio/dio.dart';
|
2020-05-02 16:28:54 +00:00
|
|
|
import 'package:logging/logging.dart';
|
2021-06-14 16:24:15 +00:00
|
|
|
import 'package:photo_manager/photo_manager.dart';
|
|
|
|
import 'package:photos/core/configuration.dart';
|
2021-05-20 23:23:29 +00:00
|
|
|
import 'package:photos/core/constants.dart';
|
2021-02-26 09:21:47 +00:00
|
|
|
import 'package:photos/core/errors.dart';
|
2020-05-04 20:08:20 +00:00
|
|
|
import 'package:photos/core/event_bus.dart';
|
2020-11-19 18:22:30 +00:00
|
|
|
import 'package:photos/core/network.dart';
|
2021-06-28 06:40:19 +00:00
|
|
|
import 'package:photos/db/files_db.dart';
|
2021-05-29 17:01:59 +00:00
|
|
|
import 'package:photos/events/permission_granted_event.dart';
|
2021-02-02 16:35:38 +00:00
|
|
|
import 'package:photos/events/subscription_purchased_event.dart';
|
2021-06-14 16:24:15 +00:00
|
|
|
import 'package:photos/events/sync_status_update_event.dart';
|
2021-03-17 22:08:13 +00:00
|
|
|
import 'package:photos/events/trigger_logout_event.dart';
|
2021-06-28 06:40:19 +00:00
|
|
|
import 'package:photos/models/backup_status.dart';
|
2021-07-12 11:54:43 +00:00
|
|
|
import 'package:photos/models/file_type.dart';
|
2021-06-14 14:58:36 +00:00
|
|
|
import 'package:photos/services/local_sync_service.dart';
|
2021-05-20 23:23:29 +00:00
|
|
|
import 'package:photos/services/notification_service.dart';
|
2021-06-14 16:24:15 +00:00
|
|
|
import 'package:photos/services/remote_sync_service.dart';
|
2020-10-03 17:58:26 +00:00
|
|
|
import 'package:photos/utils/file_uploader.dart';
|
2020-03-24 19:59:36 +00:00
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
2020-04-30 15:09:41 +00:00
|
|
|
|
2020-10-03 17:58:26 +00:00
|
|
|
class SyncService {
|
2020-11-01 11:00:50 +00:00
|
|
|
final _logger = Logger("SyncService");
|
2021-06-14 14:58:36 +00:00
|
|
|
final _localSyncService = LocalSyncService.instance;
|
2021-06-14 16:24:15 +00:00
|
|
|
final _remoteSyncService = RemoteSyncService.instance;
|
2022-10-14 15:03:55 +00:00
|
|
|
final _enteDio = Network.instance.enteDio;
|
2020-10-21 16:20:41 +00:00
|
|
|
final _uploader = FileUploader.instance;
|
2020-09-17 19:40:08 +00:00
|
|
|
bool _syncStopRequested = false;
|
2021-03-17 21:07:17 +00:00
|
|
|
Completer<bool> _existingSync;
|
2020-07-15 17:09:52 +00:00
|
|
|
SharedPreferences _prefs;
|
2020-11-16 16:35:16 +00:00
|
|
|
SyncStatusUpdate _lastSyncStatusEvent;
|
2020-04-27 13:02:29 +00:00
|
|
|
|
2021-05-20 23:23:29 +00:00
|
|
|
static const kLastStorageLimitExceededNotificationPushTime =
|
|
|
|
"last_storage_limit_exceeded_notification_push_time";
|
2020-03-24 19:59:36 +00:00
|
|
|
|
2020-10-03 17:58:26 +00:00
|
|
|
SyncService._privateConstructor() {
|
2021-02-02 16:35:38 +00:00
|
|
|
Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
|
2021-03-02 07:20:21 +00:00
|
|
|
_uploader.clearQueue(SilentlyCancelUploadsError());
|
2020-04-30 15:09:41 +00:00
|
|
|
sync();
|
|
|
|
});
|
2020-11-16 16:35:16 +00:00
|
|
|
|
|
|
|
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
|
|
|
|
_logger.info("Connectivity change detected " + result.toString());
|
2021-05-19 15:49:04 +00:00
|
|
|
if (Configuration.instance.hasConfiguredAccount()) {
|
2021-03-03 16:03:03 +00:00
|
|
|
sync();
|
2021-01-13 19:41:32 +00:00
|
|
|
}
|
2020-11-16 16:35:16 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
Bus.instance.on<SyncStatusUpdate>().listen((event) {
|
2021-03-22 07:34:59 +00:00
|
|
|
_logger.info("Sync status received " + event.toString());
|
2020-11-16 16:35:16 +00:00
|
|
|
_lastSyncStatusEvent = event;
|
|
|
|
});
|
2020-04-30 15:09:41 +00:00
|
|
|
}
|
|
|
|
|
2020-10-03 17:58:26 +00:00
|
|
|
static final SyncService instance = SyncService._privateConstructor();
|
2020-03-28 13:56:06 +00:00
|
|
|
|
2021-06-14 14:58:36 +00:00
|
|
|
Future<void> init() async {
|
2020-07-15 17:09:52 +00:00
|
|
|
_prefs = await SharedPreferences.getInstance();
|
2020-11-30 10:20:08 +00:00
|
|
|
if (Platform.isIOS) {
|
|
|
|
_logger.info("Clearing file cache");
|
|
|
|
await PhotoManager.clearFileCache();
|
|
|
|
_logger.info("Cleared file cache");
|
|
|
|
}
|
2020-07-15 17:09:52 +00:00
|
|
|
}
|
|
|
|
|
2021-03-17 21:07:17 +00:00
|
|
|
Future<bool> existingSync() async {
|
|
|
|
return _existingSync.future;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool> sync() async {
|
2020-09-17 19:40:08 +00:00
|
|
|
_syncStopRequested = false;
|
2021-03-01 23:37:24 +00:00
|
|
|
if (_existingSync != null) {
|
2020-05-02 16:28:54 +00:00
|
|
|
_logger.warning("Sync already in progress, skipping.");
|
2021-03-01 23:37:24 +00:00
|
|
|
return _existingSync.future;
|
2020-04-27 13:02:29 +00:00
|
|
|
}
|
2021-03-17 21:07:17 +00:00
|
|
|
_existingSync = Completer<bool>();
|
2021-03-01 23:37:24 +00:00
|
|
|
bool successful = false;
|
|
|
|
try {
|
2021-03-03 16:03:03 +00:00
|
|
|
await _doSync();
|
2021-03-25 19:06:01 +00:00
|
|
|
if (_lastSyncStatusEvent != null &&
|
2022-07-03 09:49:33 +00:00
|
|
|
_lastSyncStatusEvent.status !=
|
|
|
|
SyncStatus.completedFirstGalleryImport &&
|
2022-07-03 07:47:15 +00:00
|
|
|
_lastSyncStatusEvent.status != SyncStatus.completedBackup) {
|
|
|
|
Bus.instance.fire(SyncStatusUpdate(SyncStatus.completedBackup));
|
2020-06-15 18:42:25 +00:00
|
|
|
}
|
2021-03-01 23:37:24 +00:00
|
|
|
successful = true;
|
|
|
|
} on WiFiUnavailableError {
|
|
|
|
_logger.warning("Not uploading over mobile data");
|
|
|
|
Bus.instance.fire(
|
2022-06-11 08:23:52 +00:00
|
|
|
SyncStatusUpdate(SyncStatus.paused, reason: "waiting for WiFi..."),
|
|
|
|
);
|
2021-03-01 23:37:24 +00:00
|
|
|
} on SyncStopRequestedError {
|
|
|
|
_syncStopRequested = false;
|
2021-04-19 10:58:07 +00:00
|
|
|
Bus.instance.fire(
|
2022-07-03 07:47:15 +00:00
|
|
|
SyncStatusUpdate(SyncStatus.completedBackup, wasStopped: true),
|
2022-06-11 08:23:52 +00:00
|
|
|
);
|
2021-03-01 23:37:24 +00:00
|
|
|
} on NoActiveSubscriptionError {
|
2022-06-11 08:23:52 +00:00
|
|
|
Bus.instance.fire(
|
|
|
|
SyncStatusUpdate(
|
|
|
|
SyncStatus.error,
|
|
|
|
error: NoActiveSubscriptionError(),
|
|
|
|
),
|
|
|
|
);
|
2021-03-01 23:37:24 +00:00
|
|
|
} on StorageLimitExceededError {
|
2021-05-20 23:23:29 +00:00
|
|
|
_showStorageLimitExceededNotification();
|
2022-06-11 08:23:52 +00:00
|
|
|
Bus.instance.fire(
|
|
|
|
SyncStatusUpdate(
|
|
|
|
SyncStatus.error,
|
|
|
|
error: StorageLimitExceededError(),
|
|
|
|
),
|
|
|
|
);
|
2021-03-17 22:08:13 +00:00
|
|
|
} on UnauthorizedError {
|
|
|
|
_logger.info("Logging user out");
|
|
|
|
Bus.instance.fire(TriggerLogoutEvent());
|
2022-07-03 10:09:01 +00:00
|
|
|
} catch (e) {
|
2021-05-02 16:05:36 +00:00
|
|
|
if (e is DioError) {
|
|
|
|
if (e.type == DioErrorType.connectTimeout ||
|
|
|
|
e.type == DioErrorType.sendTimeout ||
|
|
|
|
e.type == DioErrorType.receiveTimeout ||
|
|
|
|
e.type == DioErrorType.other) {
|
2022-06-11 08:23:52 +00:00
|
|
|
Bus.instance.fire(
|
|
|
|
SyncStatusUpdate(
|
|
|
|
SyncStatus.paused,
|
|
|
|
reason: "waiting for network...",
|
|
|
|
),
|
|
|
|
);
|
2021-11-15 14:01:04 +00:00
|
|
|
_logger.severe("unable to connect", e, StackTrace.current);
|
2021-03-17 21:07:17 +00:00
|
|
|
return false;
|
2021-03-07 05:25:07 +00:00
|
|
|
}
|
2021-03-02 07:20:21 +00:00
|
|
|
}
|
2021-11-15 14:01:04 +00:00
|
|
|
_logger.severe("backup failed", e, StackTrace.current);
|
2021-06-12 10:33:48 +00:00
|
|
|
Bus.instance.fire(SyncStatusUpdate(SyncStatus.error));
|
2021-07-22 18:41:58 +00:00
|
|
|
rethrow;
|
2021-03-01 23:37:24 +00:00
|
|
|
} finally {
|
2021-03-17 21:07:17 +00:00
|
|
|
_existingSync.complete(successful);
|
2021-03-01 23:37:24 +00:00
|
|
|
_existingSync = null;
|
2021-03-25 19:06:01 +00:00
|
|
|
_lastSyncStatusEvent = null;
|
2021-03-01 23:37:24 +00:00
|
|
|
_logger.info("Syncing completed");
|
|
|
|
}
|
2021-03-17 21:07:17 +00:00
|
|
|
return successful;
|
2020-06-15 18:42:25 +00:00
|
|
|
}
|
2020-04-30 15:09:41 +00:00
|
|
|
|
2020-09-17 19:40:08 +00:00
|
|
|
void stopSync() {
|
|
|
|
_logger.info("Sync stop requested");
|
|
|
|
_syncStopRequested = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool shouldStopSync() {
|
|
|
|
return _syncStopRequested;
|
|
|
|
}
|
|
|
|
|
2020-11-12 16:32:10 +00:00
|
|
|
bool isSyncInProgress() {
|
2021-03-01 23:37:24 +00:00
|
|
|
return _existingSync != null;
|
2020-11-12 16:32:10 +00:00
|
|
|
}
|
|
|
|
|
2020-11-16 16:35:16 +00:00
|
|
|
SyncStatusUpdate getLastSyncStatusEvent() {
|
|
|
|
return _lastSyncStatusEvent;
|
|
|
|
}
|
|
|
|
|
2021-07-01 07:44:33 +00:00
|
|
|
Future<void> onPermissionGranted(PermissionState state) async {
|
2021-07-01 09:29:25 +00:00
|
|
|
_logger.info("Permission granted " + state.toString());
|
2021-07-01 07:44:33 +00:00
|
|
|
await _localSyncService.onPermissionGranted(state);
|
2021-05-29 17:01:59 +00:00
|
|
|
Bus.instance.fire(PermissionGrantedEvent());
|
2021-03-12 08:40:36 +00:00
|
|
|
_doSync();
|
|
|
|
}
|
|
|
|
|
2021-07-23 12:28:02 +00:00
|
|
|
void onFoldersSet(Set<String> paths) {
|
2022-06-11 08:23:52 +00:00
|
|
|
_uploader.removeFromQueueWhere(
|
|
|
|
(file) {
|
|
|
|
return !paths.contains(file.deviceFolder);
|
|
|
|
},
|
|
|
|
UserCancelledUploadError(),
|
|
|
|
);
|
2021-03-22 06:29:53 +00:00
|
|
|
}
|
2022-09-09 09:34:09 +00:00
|
|
|
|
|
|
|
void onDeviceCollectionSet(Set<int> collectionIDs) {
|
|
|
|
_uploader.removeFromQueueWhere(
|
|
|
|
(file) {
|
|
|
|
return !collectionIDs.contains(file.collectionID);
|
|
|
|
},
|
|
|
|
UserCancelledUploadError(),
|
|
|
|
);
|
|
|
|
}
|
2021-03-22 06:29:53 +00:00
|
|
|
|
2021-07-12 11:54:43 +00:00
|
|
|
void onVideoBackupPaused() {
|
2022-06-11 08:23:52 +00:00
|
|
|
_uploader.removeFromQueueWhere(
|
|
|
|
(file) {
|
|
|
|
return file.fileType == FileType.video;
|
|
|
|
},
|
|
|
|
UserCancelledUploadError(),
|
|
|
|
);
|
2021-07-12 11:54:43 +00:00
|
|
|
}
|
|
|
|
|
2021-04-27 20:49:00 +00:00
|
|
|
Future<void> deleteFilesOnServer(List<int> fileIDs) async {
|
2022-10-14 15:03:55 +00:00
|
|
|
return await _enteDio.post(
|
|
|
|
"/files/delete",
|
2021-06-14 19:10:28 +00:00
|
|
|
data: {
|
|
|
|
"fileIDs": fileIDs,
|
|
|
|
},
|
|
|
|
);
|
2020-03-29 14:04:26 +00:00
|
|
|
}
|
2021-05-20 23:23:29 +00:00
|
|
|
|
2021-06-28 06:40:19 +00:00
|
|
|
Future<BackupStatus> getBackupStatus() async {
|
|
|
|
final ids = await FilesDB.instance.getBackedUpIDs();
|
|
|
|
final size = await _getFileSize(ids.uploadedIDs);
|
|
|
|
return BackupStatus(ids.localIDs, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<int> _getFileSize(List<int> fileIDs) async {
|
|
|
|
try {
|
2022-10-14 15:03:55 +00:00
|
|
|
final response = await _enteDio.post(
|
|
|
|
"/files/size",
|
|
|
|
data: {"fileIDs": fileIDs},
|
2021-06-28 06:40:19 +00:00
|
|
|
);
|
|
|
|
return response.data["size"];
|
|
|
|
} catch (e) {
|
|
|
|
_logger.severe(e);
|
2021-07-22 18:41:58 +00:00
|
|
|
rethrow;
|
2021-06-28 06:40:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-14 16:27:50 +00:00
|
|
|
Future<void> _doSync() async {
|
|
|
|
await _localSyncService.sync();
|
|
|
|
if (_localSyncService.hasCompletedFirstImport()) {
|
|
|
|
await _remoteSyncService.sync();
|
2021-06-15 11:57:33 +00:00
|
|
|
final shouldSync = await _localSyncService.syncAll();
|
|
|
|
if (shouldSync) {
|
|
|
|
await _remoteSyncService.sync();
|
|
|
|
}
|
2021-06-14 16:27:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-20 23:23:29 +00:00
|
|
|
void _showStorageLimitExceededNotification() async {
|
|
|
|
final lastNotificationShownTime =
|
|
|
|
_prefs.getInt(kLastStorageLimitExceededNotificationPushTime) ?? 0;
|
|
|
|
final now = DateTime.now().microsecondsSinceEpoch;
|
2022-09-20 11:53:32 +00:00
|
|
|
if ((now - lastNotificationShownTime) > microSecondsInDay) {
|
2021-05-20 23:23:29 +00:00
|
|
|
await _prefs.setInt(kLastStorageLimitExceededNotificationPushTime, now);
|
|
|
|
NotificationService.instance.showNotification(
|
2022-06-11 08:23:52 +00:00
|
|
|
"storage limit exceeded",
|
|
|
|
"sorry, we had to pause your backups",
|
|
|
|
);
|
2021-05-20 23:23:29 +00:00
|
|
|
}
|
|
|
|
}
|
2020-03-24 19:59:36 +00:00
|
|
|
}
|