ente/lib/services/sync_service.dart

250 lines
7.8 KiB
Dart
Raw Normal View History

2020-03-30 14:28:46 +00:00
import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
2020-05-02 16:28:54 +00:00
import 'package:logging/logging.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/constants.dart';
2021-02-26 09:21:47 +00:00
import 'package:photos/core/errors.dart';
import 'package:photos/core/event_bus.dart';
2023-02-03 07:39:04 +00:00
import 'package:photos/core/network/network.dart';
import 'package:photos/db/device_files_db.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';
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';
2023-08-25 04:39:30 +00:00
import 'package:photos/models/file/file_type.dart';
import "package:photos/services/files_service.dart";
import 'package:photos/services/local_sync_service.dart';
import 'package:photos/services/notification_service.dart';
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 {
final _logger = Logger("SyncService");
final _localSyncService = LocalSyncService.instance;
final _remoteSyncService = RemoteSyncService.instance;
2023-02-03 07:39:04 +00:00
final _enteDio = NetworkClient.instance.enteDio;
2020-10-21 16:20:41 +00:00
final _uploader = FileUploader.instance;
bool _syncStopRequested = false;
2022-12-30 07:59:48 +00:00
Completer<bool>? _existingSync;
late SharedPreferences _prefs;
SyncStatusUpdate? _lastSyncStatusEvent;
2020-04-27 13:02: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()) {
sync();
}
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
Future<void> init(SharedPreferences preferences) async {
_prefs = preferences;
if (Platform.isIOS) {
_logger.info("Clearing file cache");
await PhotoManager.clearFileCache();
_logger.info("Cleared file cache");
}
}
2022-12-30 07:59:48 +00:00
// Note: Do not use this future for anything except log out.
// This is prone to bugs due to any potential race conditions
2021-03-17 21:07:17 +00:00
Future<bool> existingSync() async {
2022-12-30 07:59:48 +00:00
return _existingSync?.future ?? Future.value(true);
2021-03-17 21:07:17 +00:00
}
Future<bool> sync() async {
_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.");
2022-12-30 07:59:48 +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 {
await _doSync();
2021-03-25 19:06:01 +00:00
if (_lastSyncStatusEvent != null &&
2022-12-30 07:59:48 +00:00
_lastSyncStatusEvent!.status !=
2022-07-03 09:49:33 +00:00
SyncStatus.completedFirstGalleryImport &&
2022-12-30 07:59:48 +00:00
_lastSyncStatusEvent!.status != SyncStatus.completedBackup) {
2022-07-03 07:47:15 +00:00
Bus.instance.fire(SyncStatusUpdate(SyncStatus.completedBackup));
}
2021-03-01 23:37:24 +00:00
successful = true;
} on WiFiUnavailableError {
_logger.warning("Not uploading over mobile data");
Bus.instance.fire(
2022-11-21 09:29:17 +00:00
SyncStatusUpdate(SyncStatus.paused, reason: "Waiting for WiFi..."),
2022-06-11 08:23:52 +00:00
);
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 {
_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,
2022-11-21 09:29:17 +00:00
reason: "Waiting for network...",
2022-06-11 08:23:52 +00:00
),
);
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 {
2022-12-30 07:59:48 +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-04-30 15:09:41 +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
}
2022-12-30 07:59:48 +00:00
SyncStatusUpdate? getLastSyncStatusEvent() {
2020-11-16 16:35:16 +00:00
return _lastSyncStatusEvent;
}
Future<void> onPermissionGranted(PermissionState state) async {
2021-07-01 09:29:25 +00:00
_logger.info("Permission granted " + state.toString());
await _localSyncService.onPermissionGranted(state);
2021-05-29 17:01:59 +00:00
Bus.instance.fire(PermissionGrantedEvent());
2023-01-11 07:05:40 +00:00
_doSync().ignore();
2021-03-12 08:40:36 +00:00
}
void onDeviceCollectionSet(Set<int> collectionIDs) {
_uploader.removeFromQueueWhere(
(file) {
return !collectionIDs.contains(file.collectionID);
},
UserCancelledUploadError(),
);
}
void onVideoBackupPaused() {
2022-06-11 08:23:52 +00:00
_uploader.removeFromQueueWhere(
(file) {
return file.fileType == FileType.video;
},
UserCancelledUploadError(),
);
}
2022-12-30 07:59:48 +00:00
Future<BackupStatus> getBackupStatus({String? pathID}) async {
BackedUpFileIDs ids;
final bool hasMigratedSize = await FilesService.instance.hasMigratedSizes();
if (pathID == null) {
ids = await FilesDB.instance.getBackedUpIDs();
} else {
ids = await FilesDB.instance.getBackedUpForDeviceCollection(
pathID,
2022-12-30 07:59:48 +00:00
Configuration.instance.getUserID()!,
);
}
late int size;
if (hasMigratedSize) {
size = ids.localSize;
} else {
size = await _getFileSize(ids.uploadedIDs);
}
2021-06-28 06:40:19 +00:00
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
}
}
void _showStorageLimitExceededNotification() async {
final lastNotificationShownTime =
_prefs.getInt(kLastStorageLimitExceededNotificationPushTime) ?? 0;
final now = DateTime.now().microsecondsSinceEpoch;
if ((now - lastNotificationShownTime) > microSecondsInDay) {
await _prefs.setInt(kLastStorageLimitExceededNotificationPushTime, now);
NotificationService.instance.showNotification(
2022-11-14 08:50:41 +00:00
"Storage limit exceeded",
"Sorry, we had to pause your backups",
2022-06-11 08:23:52 +00:00
);
}
}
2020-03-24 19:59:36 +00:00
}