import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; import 'package:selfprivacy/logic/models/initialize_repository_input.dart'; import 'package:selfprivacy/logic/models/service.dart'; part 'backups_state.dart'; class BackupsCubit extends ServerInstallationDependendCubit { BackupsCubit(final ServerInstallationCubit serverInstallationCubit) : super( serverInstallationCubit, const BackupsState(preventActions: true), ); final ServerApi api = ServerApi(); final BackblazeApi backblaze = BackblazeApi(); @override Future load() async { if (serverInstallationCubit.state is ServerInstallationFinished) { final BackblazeBucket? bucket = getIt().backblazeBucket; final BackupConfiguration? backupConfig = await api.getBackupsConfiguration(); final List backups = await api.getBackups(); backups.sort((final a, final b) => b.time.compareTo(a.time)); emit( state.copyWith( backblazeBucket: bucket, isInitialized: backupConfig?.isInitialized, autobackupPeriod: backupConfig?.autobackupPeriod ?? Duration.zero, autobackupQuotas: backupConfig?.autobackupQuotas, backups: backups, preventActions: false, refreshing: false, ), ); } } Future initializeBackups() async { emit(state.copyWith(preventActions: true)); final String? encryptionKey = (await api.getBackupsConfiguration())?.encryptionKey; if (encryptionKey == null) { getIt() .showSnackBar("Couldn't get encryption key from your server."); emit(state.copyWith(preventActions: false)); return; } final BackblazeBucket bucket; if (state.backblazeBucket == null) { final String domain = serverInstallationCubit .state.serverDomain!.domainName .replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-'); final int serverId = serverInstallationCubit.state.serverDetails!.id; String bucketName = '${DateTime.now().millisecondsSinceEpoch}-$serverId-$domain'; if (bucketName.length > 49) { bucketName = bucketName.substring(0, 49); } final String bucketId = await backblaze.createBucket(bucketName); final BackblazeApplicationKey key = await backblaze.createKey(bucketId); bucket = BackblazeBucket( bucketId: bucketId, bucketName: bucketName, applicationKey: key.applicationKey, applicationKeyId: key.applicationKeyId, encryptionKey: encryptionKey, ); await getIt().storeBackblazeBucket(bucket); emit(state.copyWith(backblazeBucket: bucket)); } else { bucket = state.backblazeBucket!; } final GenericResult result = await api.initializeRepository( InitializeRepositoryInput( provider: BackupsProviderType.backblaze, locationId: bucket.bucketId, locationName: bucket.bucketName, login: bucket.applicationKeyId, password: bucket.applicationKey, ), ); if (result.success == false) { getIt() .showSnackBar(result.message ?? 'Unknown error'); emit(state.copyWith(preventActions: false)); return; } await updateBackups(); getIt().showSnackBar( 'Backups repository is now initializing. It may take a while.', ); emit(state.copyWith(preventActions: false)); } Future reuploadKey() async { emit(state.copyWith(preventActions: true)); BackblazeBucket? bucket = getIt().backblazeBucket; if (bucket == null) { emit(state.copyWith(isInitialized: false)); } else { String login = bucket.applicationKeyId; String password = bucket.applicationKey; if (login.isEmpty || password.isEmpty) { final BackblazeApplicationKey key = await backblaze.createKey(bucket.bucketId); login = key.applicationKeyId; password = key.applicationKey; bucket = BackblazeBucket( bucketId: bucket.bucketId, bucketName: bucket.bucketName, encryptionKey: bucket.encryptionKey, applicationKey: password, applicationKeyId: login, ); await getIt().storeBackblazeBucket(bucket); emit(state.copyWith(backblazeBucket: bucket)); } final GenericResult result = await api.initializeRepository( InitializeRepositoryInput( provider: BackupsProviderType.backblaze, locationId: bucket.bucketId, locationName: bucket.bucketName, login: login, password: password, ), ); if (result.success == false) { getIt() .showSnackBar(result.message ?? 'Unknown error'); emit(state.copyWith(preventActions: false)); return; } else { emit(state.copyWith(preventActions: false)); getIt().showSnackBar('backup.reuploaded_key'.tr()); await updateBackups(); } } } @Deprecated("we don't have states") Duration refreshTimeFromState() => const Duration(seconds: 60); Future updateBackups({final bool useTimer = false}) async { emit(state.copyWith(refreshing: true)); final backups = await api.getBackups(); backups.sort((final a, final b) => b.time.compareTo(a.time)); final backupConfig = await api.getBackupsConfiguration(); emit( state.copyWith( backups: backups, refreshTimer: refreshTimeFromState(), refreshing: false, isInitialized: backupConfig?.isInitialized ?? false, autobackupPeriod: backupConfig?.autobackupPeriod, autobackupQuotas: backupConfig?.autobackupQuotas, ), ); if (useTimer) { Timer(state.refreshTimer, () => updateBackups(useTimer: true)); } } Future forceUpdateBackups() async { emit(state.copyWith(preventActions: true)); getIt().showSnackBar('backup.refetching_list'.tr()); await api.forceBackupListReload(); emit(state.copyWith(preventActions: false)); } Future createMultipleBackups(final List services) async { emit(state.copyWith(preventActions: true)); for (final service in services) { await api.startBackup(service.id); } await updateBackups(); emit(state.copyWith(preventActions: false)); } Future createBackup(final String serviceId) async { emit(state.copyWith(preventActions: true)); await api.startBackup(serviceId); await updateBackups(); emit(state.copyWith(preventActions: false)); } Future restoreBackup( final String backupId, final BackupRestoreStrategy strategy, ) async { emit(state.copyWith(preventActions: true)); await api.restoreBackup(backupId, strategy); emit(state.copyWith(preventActions: false)); } Future setAutobackupPeriod(final Duration? period) async { emit(state.copyWith(preventActions: true)); final result = await api.setAutobackupPeriod(period: period?.inMinutes); if (result.success == false) { getIt() .showSnackBar(result.message ?? 'Unknown error'); emit(state.copyWith(preventActions: false)); } else { getIt() .showSnackBar('backup.autobackup_period_set'.tr()); emit( state.copyWith( preventActions: false, autobackupPeriod: period ?? Duration.zero, ), ); } await updateBackups(); } Future setAutobackupQuotas(final AutobackupQuotas quotas) async { emit(state.copyWith(preventActions: true)); final result = await api.setAutobackupQuotas(quotas); if (result.success == false) { getIt() .showSnackBar(result.message ?? 'Unknown error'); emit(state.copyWith(preventActions: false)); } else { getIt().showSnackBar('backup.quotas_set'.tr()); emit( state.copyWith( preventActions: false, autobackupQuotas: quotas, ), ); } await updateBackups(); } Future forgetSnapshot(final String snapshotId) async { final result = await api.forgetSnapshot(snapshotId); if (!result.success) { getIt().showSnackBar('jobs.generic_error'.tr()); return; } if (result.data == false) { getIt() .showSnackBar('backup.forget_snapshot_error'.tr()); } // Optimistic update final backups = state.backups; final index = backups.indexWhere((final snapshot) => snapshot.id == snapshotId); if (index != -1) { backups.removeAt(index); emit(state.copyWith(backups: backups)); } await updateBackups(); } @override void clear() async { emit(const BackupsState()); } }