From a5e7725733b2ea488b68da9b393ce75fb8efaa46 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Mon, 29 Jan 2024 17:54:09 +0400 Subject: [PATCH] refactor: Rewrite backups cubit to bloc, using ApiRepo streams --- lib/config/bloc_config.dart | 9 +- lib/config/get_it_config.dart | 1 + lib/logic/bloc/backups/backups_bloc.dart | 396 ++++++++++++++++++ lib/logic/bloc/backups/backups_event.dart | 89 ++++ lib/logic/bloc/backups/backups_state.dart | 168 ++++++++ .../server_jobs/server_jobs_bloc.dart | 1 - .../server_jobs/server_jobs_event.dart | 0 .../server_jobs/server_jobs_state.dart | 0 lib/logic/cubit/backups/backups_cubit.dart | 278 ------------ lib/logic/cubit/backups/backups_state.dart | 61 --- .../connection_status_bloc.dart | 1 - .../cubit/dns_records/dns_records_cubit.dart | 1 - .../server_connection_dependent_cubit.dart | 1 - .../server_installation_cubit.dart | 3 +- .../server_installation_repository.dart | 16 +- lib/logic/cubit/users/users_cubit.dart | 1 - lib/logic/get_it/api_config.dart | 18 +- .../get_it/api_connection_repository.dart | 88 +++- lib/logic/models/backup.dart | 48 ++- lib/logic/models/hive/backblaze_bucket.dart | 15 + .../components/jobs_content/jobs_content.dart | 2 +- lib/ui/pages/backups/backup_details.dart | 43 +- lib/ui/pages/backups/backups_list.dart | 14 +- lib/ui/pages/backups/change_period_modal.dart | 12 +- .../backups/change_rotation_quotas_modal.dart | 14 +- .../backups/copy_encryption_key_modal.dart | 6 +- .../pages/backups/create_backups_modal.dart | 8 +- lib/ui/pages/backups/snapshot_modal.dart | 10 +- .../more/app_settings/developer_settings.dart | 1 - lib/ui/pages/providers/providers.dart | 4 +- .../binds_migration/services_migration.dart | 2 +- 31 files changed, 859 insertions(+), 452 deletions(-) create mode 100644 lib/logic/bloc/backups/backups_bloc.dart create mode 100644 lib/logic/bloc/backups/backups_event.dart create mode 100644 lib/logic/bloc/backups/backups_state.dart rename lib/logic/{cubit => bloc}/server_jobs/server_jobs_bloc.dart (96%) rename lib/logic/{cubit => bloc}/server_jobs/server_jobs_event.dart (100%) rename lib/logic/{cubit => bloc}/server_jobs/server_jobs_state.dart (100%) delete mode 100644 lib/logic/cubit/backups/backups_cubit.dart delete mode 100644 lib/logic/cubit/backups/backups_state.dart diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 34b880dc..b7d87f2e 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/connection_status/connection_status_bloc.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; @@ -29,7 +29,7 @@ class BlocAndProviderConfig extends StatelessWidget { final supportSystemCubit = SupportSystemCubit(); final usersCubit = UsersCubit(serverInstallationCubit); final servicesCubit = ServicesCubit(serverInstallationCubit); - final backupsCubit = BackupsCubit(serverInstallationCubit); + final backupsBloc = BackupsBloc(); final dnsRecordsCubit = DnsRecordsCubit(serverInstallationCubit); final recoveryKeyCubit = RecoveryKeyCubit(serverInstallationCubit); final apiDevicesCubit = ApiDevicesCubit(serverInstallationCubit); @@ -65,8 +65,7 @@ class BlocAndProviderConfig extends StatelessWidget { lazy: false, ), BlocProvider( - create: (final _) => backupsCubit, - lazy: false, + create: (final _) => backupsBloc, ), BlocProvider( create: (final _) => dnsRecordsCubit, diff --git a/lib/config/get_it_config.dart b/lib/config/get_it_config.dart index c67c1e32..b8ac1866 100644 --- a/lib/config/get_it_config.dart +++ b/lib/config/get_it_config.dart @@ -7,6 +7,7 @@ import 'package:selfprivacy/logic/get_it/navigation.dart'; export 'package:selfprivacy/logic/get_it/api_config.dart'; export 'package:selfprivacy/logic/get_it/console.dart'; export 'package:selfprivacy/logic/get_it/navigation.dart'; +export 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; final GetIt getIt = GetIt.instance; diff --git a/lib/logic/bloc/backups/backups_bloc.dart b/lib/logic/bloc/backups/backups_bloc.dart new file mode 100644 index 00000000..55d4753a --- /dev/null +++ b/lib/logic/bloc/backups/backups_bloc.dart @@ -0,0 +1,396 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.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/json/server_job.dart'; +import 'package:selfprivacy/logic/models/service.dart'; + +part 'backups_event.dart'; +part 'backups_state.dart'; + +class BackupsBloc extends Bloc { + BackupsBloc() : super(BackupsInitial()) { + final connectionRepository = getIt(); + + _apiStatusSubscription = connectionRepository.connectionStatusStream + .listen((final ConnectionStatus connectionStatus) { + switch (connectionStatus) { + case ConnectionStatus.nonexistent: + add(const BackupsServerReset()); + isLoaded = false; + break; + case ConnectionStatus.connected: + if (!isLoaded) { + add(const BackupsServerLoaded()); + isLoaded = true; + } + break; + default: + break; + } + }); + + _apiDataSubscription = connectionRepository.dataStream.listen( + (final ApiData apiData) { + if (apiData.backups.data == null || apiData.backupConfig.data == null) { + add(const BackupsServerReset()); + isLoaded = false; + } else { + add( + BackupsStateChanged( + apiData.backups.data!, + apiData.backupConfig.data, + ), + ); + isLoaded = true; + } + }, + ); + + if (connectionRepository.connectionStatus == ConnectionStatus.connected) { + add(const BackupsServerLoaded()); + isLoaded = true; + } + + on( + _loadState, + ); + on( + _resetState, + ); + on( + _updateState, + ); + on( + _initializeRepository, + ); + on( + _forceSnapshotListUpdate, + ); + on( + _createBackups, + ); + on( + _restoreBackup, + ); + on( + _setAutobackupPeriod, + ); + on( + _setAutobackupQuotas, + ); + on( + _forgetSnapshot, + ); + } + + final BackblazeApi backblaze = BackblazeApi(); + + Future _loadState( + final BackupsServerLoaded event, + final Emitter emit, + ) async { + BackblazeBucket? bucket = getIt().backblazeBucket; + final backups = getIt().apiData.backups; + final backupConfig = getIt().apiData.backupConfig; + if (backupConfig.data == null || backups.data == null) { + emit(BackupsLoading()); + return; + } + if (bucket != null && + backupConfig.data!.encryptionKey != bucket.encryptionKey) { + bucket = bucket.copyWith( + encryptionKey: backupConfig.data!.encryptionKey, + ); + await getIt().setBackblazeBucket(bucket); + } + if (backupConfig.data!.isInitialized) { + emit( + BackupsInitialized( + backblazeBucket: bucket, + backupConfig: backupConfig.data, + backups: backups.data ?? [], + ), + ); + } else { + emit(BackupsUnititialized()); + } + } + + Future _resetState( + final BackupsServerReset event, + final Emitter emit, + ) async { + emit(BackupsInitial()); + } + + Future _initializeRepository( + final InitializeBackupsRepository event, + final Emitter emit, + ) async { + if (state is! BackupsUnititialized) { + return; + } + emit(BackupsInitializing()); + final String? encryptionKey = getIt() + .apiData + .backupConfig + .data + ?.encryptionKey; + if (encryptionKey == null) { + emit(BackupsUnititialized()); + getIt() + .showSnackBar("Couldn't get encryption key from your server."); + return; + } + + final BackblazeBucket bucket; + + if (state.backblazeBucket == null) { + final String domain = getIt() + .serverDomain! + .domainName + .replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-'); + final int serverId = getIt().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().setBackblazeBucket(bucket); + emit(state.copyWith(backblazeBucket: bucket)); + } else { + bucket = state.backblazeBucket!; + } + + final GenericResult result = + await getIt().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 ?? "Couldn't initialize repository on your server."); + emit(BackupsUnititialized()); + return; + } + getIt().apiData.backupConfig.invalidate(); + getIt().apiData.backups.invalidate(); + await getIt().reload(null); + + getIt().showSnackBar( + 'Backups repository is now initializing. It may take a while.', + ); + } + + Future _updateState( + final BackupsStateChanged event, + final Emitter emit, + ) async { + if (event.backupConfiguration == null || + event.backupConfiguration!.isInitialized == false) { + emit(BackupsUnititialized()); + return; + } + final BackblazeBucket? bucket = getIt().backblazeBucket; + emit( + BackupsInitialized( + backblazeBucket: bucket, + backupConfig: event.backupConfiguration, + backups: event.backups, + ), + ); + } + + Future _forceSnapshotListUpdate( + final ForceSnapshotListUpdate event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + getIt().showSnackBar('backup.refetching_list'.tr()); + await getIt().api.forceBackupListReload(); + getIt().apiData.backups.invalidate(); + emit(currentState); + } + } + + Future _createBackups( + final CreateBackups event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + for (final service in event.services) { + final GenericResult result = + await getIt().api.startBackup( + service.id, + ); + if (result.success == false) { + getIt() + .showSnackBar(result.message ?? 'Unknown error'); + } + if (result.data != null) { + getIt() + .apiData + .serverJobs + .data + ?.add(result.data!); + } + } + emit(currentState); + getIt().emitData(); + } + } + + Future _restoreBackup( + final RestoreBackup event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + final GenericResult result = + await getIt().api.restoreBackup( + event.backupId, + event.restoreStrategy, + ); + if (result.success == false) { + getIt() + .showSnackBar(result.message ?? 'Unknown error'); + } + emit(currentState); + } + } + + Future _setAutobackupPeriod( + final SetAutobackupPeriod event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + final GenericResult result = + await getIt().api.setAutobackupPeriod( + period: event.period?.inMinutes, + ); + if (result.success == false) { + getIt() + .showSnackBar(result.message ?? 'Unknown error'); + } + if (result.success == true) { + getIt().apiData.backupConfig.data = + getIt() + .apiData + .backupConfig + .data + ?.copyWith( + autobackupPeriod: event.period, + ); + } + emit(currentState); + getIt().emitData(); + } + } + + Future _setAutobackupQuotas( + final SetAutobackupQuotas event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + final GenericResult result = + await getIt().api.setAutobackupQuotas( + event.quotas, + ); + if (result.success == false) { + getIt() + .showSnackBar(result.message ?? 'Unknown error'); + } + if (result.success == true) { + getIt().apiData.backupConfig.data = + getIt() + .apiData + .backupConfig + .data + ?.copyWith( + autobackupQuotas: event.quotas, + ); + } + emit(currentState); + getIt().emitData(); + } + } + + Future _forgetSnapshot( + final ForgetSnapshot event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + final GenericResult result = + await getIt().api.forgetSnapshot( + event.backupId, + ); + if (result.success == false) { + getIt() + .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); + } else if (result.data == false) { + getIt() + .showSnackBar('backup.forget_snapshot_error'.tr()); + } else { + final backups = getIt().apiData.backups.data; + if (backups != null) { + getIt().apiData.backups.data = backups + .where((final Backup backup) => backup.id != event.backupId) + .toList(); + } + } + emit(currentState); + getIt().emitData(); + } + } + + @override + Future close() { + _apiStatusSubscription.cancel(); + _apiDataSubscription.cancel(); + return super.close(); + } + + @override + void onChange(final Change change) { + super.onChange(change); + } + + late StreamSubscription _apiStatusSubscription; + late StreamSubscription _apiDataSubscription; + bool isLoaded = false; +} diff --git a/lib/logic/bloc/backups/backups_event.dart b/lib/logic/bloc/backups/backups_event.dart new file mode 100644 index 00000000..f2625fd8 --- /dev/null +++ b/lib/logic/bloc/backups/backups_event.dart @@ -0,0 +1,89 @@ +part of 'backups_bloc.dart'; + +sealed class BackupsEvent extends Equatable { + const BackupsEvent(); +} + +class BackupsServerLoaded extends BackupsEvent { + const BackupsServerLoaded(); + + @override + List get props => []; +} + +class BackupsServerReset extends BackupsEvent { + const BackupsServerReset(); + + @override + List get props => []; +} + +class InitializeBackupsRepository extends BackupsEvent { + const InitializeBackupsRepository(); + + @override + List get props => []; +} + +class BackupsStateChanged extends BackupsEvent { + const BackupsStateChanged(this.backups, this.backupConfiguration); + + final List backups; + final BackupConfiguration? backupConfiguration; + + @override + List get props => [backups, backupConfiguration]; +} + +class ForceSnapshotListUpdate extends BackupsEvent { + const ForceSnapshotListUpdate(); + + @override + List get props => []; +} + +class CreateBackups extends BackupsEvent { + const CreateBackups(this.services); + + final List services; + + @override + List get props => [services]; +} + +class RestoreBackup extends BackupsEvent { + const RestoreBackup(this.backupId, this.restoreStrategy); + + final String backupId; + final BackupRestoreStrategy restoreStrategy; + + @override + List get props => [backupId, restoreStrategy]; +} + +class SetAutobackupPeriod extends BackupsEvent { + const SetAutobackupPeriod(this.period); + + final Duration? period; + + @override + List get props => [period]; +} + +class SetAutobackupQuotas extends BackupsEvent { + const SetAutobackupQuotas(this.quotas); + + final AutobackupQuotas quotas; + + @override + List get props => [quotas]; +} + +class ForgetSnapshot extends BackupsEvent { + const ForgetSnapshot(this.backupId); + + final String backupId; + + @override + List get props => [backupId]; +} diff --git a/lib/logic/bloc/backups/backups_state.dart b/lib/logic/bloc/backups/backups_state.dart new file mode 100644 index 00000000..153b1e4e --- /dev/null +++ b/lib/logic/bloc/backups/backups_state.dart @@ -0,0 +1,168 @@ +part of 'backups_bloc.dart'; + +sealed class BackupsState extends Equatable { + BackupsState({ + this.backblazeBucket, + }); + final apiConnectionRepository = getIt(); + final BackblazeBucket? backblazeBucket; + + @Deprecated('Infer the initializations status from state') + bool get isInitialized => false; + + @Deprecated('Infer the loading status from state') + bool get refreshing => false; + + @Deprecated('Infer the prevent actions status from state') + bool get preventActions => true; + + List get backups => []; + + List serviceBackups(final String serviceId) => []; + + Duration? get autobackupPeriod => null; + + AutobackupQuotas? get autobackupQuotas => null; + + BackupsState copyWith({required final BackblazeBucket backblazeBucket}); +} + +class BackupsInitial extends BackupsState { + BackupsInitial({ + super.backblazeBucket, + }); + @override + List get props => []; + + @override + BackupsInitial copyWith({ + final BackblazeBucket? backblazeBucket, + }) => + BackupsInitial(backblazeBucket: backblazeBucket ?? this.backblazeBucket); +} + +class BackupsLoading extends BackupsState { + BackupsLoading({ + super.backblazeBucket, + }); + @override + List get props => []; + + @override + @Deprecated('Infer the loading status from state') + bool get refreshing => true; + + @override + BackupsLoading copyWith({ + final BackblazeBucket? backblazeBucket, + }) => + BackupsLoading(backblazeBucket: backblazeBucket ?? this.backblazeBucket); +} + +class BackupsUnititialized extends BackupsState { + BackupsUnititialized({ + super.backblazeBucket, + }); + @override + List get props => []; + + @override + BackupsUnititialized copyWith({ + final BackblazeBucket? backblazeBucket, + }) => + BackupsUnititialized( + backblazeBucket: backblazeBucket ?? this.backblazeBucket); +} + +class BackupsInitializing extends BackupsState { + BackupsInitializing({ + super.backblazeBucket, + }); + @override + List get props => []; + + @override + BackupsInitializing copyWith({ + final BackblazeBucket? backblazeBucket, + }) => + BackupsInitializing( + backblazeBucket: backblazeBucket ?? this.backblazeBucket); +} + +class BackupsInitialized extends BackupsState { + BackupsInitialized({ + final List backups = const [], + final BackupConfiguration? backupConfig, + super.backblazeBucket, + }) : _backupsHashCode = Object.hashAll(backups), + _backupConfigHashCode = Object.hashAll([backupConfig]); + + final int _backupsHashCode; + final int _backupConfigHashCode; + + List get _backupList => + apiConnectionRepository.apiData.backups.data ?? []; + + BackupConfiguration? get _backupConfig => + apiConnectionRepository.apiData.backupConfig.data; + + @override + AutobackupQuotas? get autobackupQuotas => _backupConfig?.autobackupQuotas; + + @override + Duration? get autobackupPeriod => + _backupConfig?.autobackupPeriod?.inMinutes == 0 + ? null + : _backupConfig?.autobackupPeriod; + + @override + @Deprecated('Infer the initializations status from state') + bool get isInitialized => true; + + @override + @Deprecated('Infer the prevent actions status from state') + bool get preventActions => false; + + @override + List get backups { + try { + final List list = _backupList; + list.sort((final a, final b) => b.time.compareTo(a.time)); + return list; + } on UnsupportedError { + return _backupList; + } + } + + @override + List serviceBackups(final String serviceId) => backups + .where((final backup) => backup.serviceId == serviceId) + .toList(growable: false); + + @override + List get props => [_backupsHashCode, _backupConfigHashCode]; + + @override + BackupsState copyWith({required final BackblazeBucket backblazeBucket}) => + BackupsInitialized( + backups: backups, + backupConfig: _backupConfig, + backblazeBucket: backblazeBucket, + ); +} + +class BackupsBusy extends BackupsInitialized { + BackupsBusy.fromState(final BackupsInitialized state) + : super( + backups: state.backups, + backupConfig: state._backupConfig, + backblazeBucket: state.backblazeBucket, + ); + + @override + @Deprecated('Infer the prevent actions status from state') + bool get preventActions => true; + + @override + List get props => []; +} diff --git a/lib/logic/cubit/server_jobs/server_jobs_bloc.dart b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart similarity index 96% rename from lib/logic/cubit/server_jobs/server_jobs_bloc.dart rename to lib/logic/bloc/server_jobs/server_jobs_bloc.dart index 3b82ddd8..bf428d23 100644 --- a/lib/logic/cubit/server_jobs/server_jobs_bloc.dart +++ b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; export 'package:provider/provider.dart'; diff --git a/lib/logic/cubit/server_jobs/server_jobs_event.dart b/lib/logic/bloc/server_jobs/server_jobs_event.dart similarity index 100% rename from lib/logic/cubit/server_jobs/server_jobs_event.dart rename to lib/logic/bloc/server_jobs/server_jobs_event.dart diff --git a/lib/logic/cubit/server_jobs/server_jobs_state.dart b/lib/logic/bloc/server_jobs/server_jobs_state.dart similarity index 100% rename from lib/logic/cubit/server_jobs/server_jobs_state.dart rename to lib/logic/bloc/server_jobs/server_jobs_state.dart diff --git a/lib/logic/cubit/backups/backups_cubit.dart b/lib/logic/cubit/backups/backups_cubit.dart deleted file mode 100644 index 1b358f4b..00000000 --- a/lib/logic/cubit/backups/backups_cubit.dart +++ /dev/null @@ -1,278 +0,0 @@ -import 'dart:async'; - -import 'package:easy_localization/easy_localization.dart'; -import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; -import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; -import 'package:selfprivacy/logic/models/backup.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 ServerConnectionDependentCubit { - BackupsCubit(final ServerInstallationCubit serverInstallationCubit) - : super( - const BackupsState(preventActions: true), - ); - - final ServerApi api = ServerApi(); - final BackblazeApi backblaze = BackblazeApi(); - - @override - Future load() async { - 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 = getIt() - .serverDomain! - .domainName - .replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-'); - final int serverId = getIt().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()); - } -} diff --git a/lib/logic/cubit/backups/backups_state.dart b/lib/logic/cubit/backups/backups_state.dart deleted file mode 100644 index 887396d7..00000000 --- a/lib/logic/cubit/backups/backups_state.dart +++ /dev/null @@ -1,61 +0,0 @@ -part of 'backups_cubit.dart'; - -class BackupsState extends ServerInstallationDependendState { - const BackupsState({ - this.isInitialized = false, - this.backups = const [], - this.preventActions = true, - this.refreshTimer = const Duration(seconds: 60), - this.refreshing = true, - this.autobackupPeriod, - this.backblazeBucket, - this.autobackupQuotas, - }); - - final bool isInitialized; - final List backups; - final bool preventActions; - final Duration refreshTimer; - final bool refreshing; - final Duration? autobackupPeriod; - final BackblazeBucket? backblazeBucket; - final AutobackupQuotas? autobackupQuotas; - - List serviceBackups(final String serviceId) => backups - .where((final backup) => backup.serviceId == serviceId) - .toList(growable: false); - - @override - List get props => [ - isInitialized, - backups, - preventActions, - refreshTimer, - refreshing, - ]; - - BackupsState copyWith({ - final bool? isInitialized, - final List? backups, - final bool? preventActions, - final Duration? refreshTimer, - final bool? refreshing, - final Duration? autobackupPeriod, - final BackblazeBucket? backblazeBucket, - final AutobackupQuotas? autobackupQuotas, - }) => - BackupsState( - isInitialized: isInitialized ?? this.isInitialized, - backups: backups ?? this.backups, - preventActions: preventActions ?? this.preventActions, - refreshTimer: refreshTimer ?? this.refreshTimer, - refreshing: refreshing ?? this.refreshing, - // The autobackupPeriod might be null, so if the duration is set to 0, we - // set it to null. - autobackupPeriod: autobackupPeriod?.inSeconds == 0 - ? null - : autobackupPeriod ?? this.autobackupPeriod, - backblazeBucket: backblazeBucket ?? this.backblazeBucket, - autobackupQuotas: autobackupQuotas ?? this.autobackupQuotas, - ); -} diff --git a/lib/logic/cubit/connection_status/connection_status_bloc.dart b/lib/logic/cubit/connection_status/connection_status_bloc.dart index df3ebf97..f44288ed 100644 --- a/lib/logic/cubit/connection_status/connection_status_bloc.dart +++ b/lib/logic/cubit/connection_status/connection_status_bloc.dart @@ -1,7 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; part 'connection_status_event.dart'; part 'connection_status_state.dart'; diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index 0f14a8cc..6e928b63 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -2,7 +2,6 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart'; import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; diff --git a/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart b/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart index 61afa203..f46c7ef9 100644 --- a/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart +++ b/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; export 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 709bc0b4..01f46aa4 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -7,7 +7,6 @@ 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/api_maps/tls_options.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; @@ -234,7 +233,7 @@ class ServerInstallationCubit extends Cubit { try { bucket = await BackblazeApi() .fetchBucket(backblazeCredential, configuration); - await getIt().storeBackblazeBucket(bucket!); + await getIt().setBackblazeBucket(bucket!); } catch (e) { print(e); } diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 36cb8743..0e6c0dcd 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -470,7 +470,7 @@ class ServerInstallationRepository { Future saveServerDetails( final ServerHostingDetails serverDetails, ) async { - await getIt().storeServerDetails(serverDetails); + await getIt().setServerDetails(serverDetails); } Future deleteServerDetails() async { @@ -483,18 +483,18 @@ class ServerInstallationRepository { } Future saveDnsProviderType(final DnsProviderType type) async { - await getIt().storeDnsProviderType(type); + await getIt().setDnsProviderType(type); } Future saveServerProviderKey(final String key) async { - await getIt().storeServerProviderKey(key); + await getIt().setServerProviderKey(key); } Future saveServerType(final ServerType serverType) async { - await getIt().storeServerTypeIdentifier( + await getIt().setServerTypeIdentifier( serverType.identifier, ); - await getIt().storeServerLocation( + await getIt().setServerLocation( serverType.location.identifier, ); } @@ -507,7 +507,7 @@ class ServerInstallationRepository { Future saveBackblazeKey( final BackupsCredential backblazeCredential, ) async { - await getIt().storeBackblazeCredential(backblazeCredential); + await getIt().setBackblazeCredential(backblazeCredential); } Future deleteBackblazeKey() async { @@ -516,7 +516,7 @@ class ServerInstallationRepository { } Future setDnsApiToken(final String key) async { - await getIt().storeDnsProviderKey(key); + await getIt().setDnsProviderKey(key); } Future deleteDnsProviderKey() async { @@ -525,7 +525,7 @@ class ServerInstallationRepository { } Future saveDomain(final ServerDomain serverDomain) async { - await getIt().storeServerDomain(serverDomain); + await getIt().setServerDomain(serverDomain); } Future deleteDomain() async { diff --git a/lib/logic/cubit/users/users_cubit.dart b/lib/logic/cubit/users/users_cubit.dart index 037b7c39..858a7b4f 100644 --- a/lib/logic/cubit/users/users_cubit.dart +++ b/lib/logic/cubit/users/users_cubit.dart @@ -6,7 +6,6 @@ import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; export 'package:provider/provider.dart'; diff --git a/lib/logic/get_it/api_config.dart b/lib/logic/get_it/api_config.dart index ac889dcc..b32cd995 100644 --- a/lib/logic/get_it/api_config.dart +++ b/lib/logic/get_it/api_config.dart @@ -42,47 +42,47 @@ class ApiConfigModel { _serverProvider = value; } - Future storeDnsProviderType(final DnsProviderType value) async { + Future setDnsProviderType(final DnsProviderType value) async { await _box.put(BNames.dnsProvider, value); _dnsProvider = value; } - Future storeServerProviderKey(final String value) async { + Future setServerProviderKey(final String value) async { await _box.put(BNames.hetznerKey, value); _serverProviderKey = value; } - Future storeDnsProviderKey(final String value) async { + Future setDnsProviderKey(final String value) async { await _box.put(BNames.cloudFlareKey, value); _dnsProviderKey = value; } - Future storeServerTypeIdentifier(final String typeIdentifier) async { + Future setServerTypeIdentifier(final String typeIdentifier) async { await _box.put(BNames.serverTypeIdentifier, typeIdentifier); _serverType = typeIdentifier; } - Future storeServerLocation(final String serverLocation) async { + Future setServerLocation(final String serverLocation) async { await _box.put(BNames.serverLocation, serverLocation); _serverLocation = serverLocation; } - Future storeBackblazeCredential(final BackupsCredential value) async { + Future setBackblazeCredential(final BackupsCredential value) async { await _box.put(BNames.backblazeCredential, value); _backblazeCredential = value; } - Future storeServerDomain(final ServerDomain value) async { + Future setServerDomain(final ServerDomain value) async { await _box.put(BNames.serverDomain, value); _serverDomain = value; } - Future storeServerDetails(final ServerHostingDetails value) async { + Future setServerDetails(final ServerHostingDetails value) async { await _box.put(BNames.serverDetails, value); _serverDetails = value; } - Future storeBackblazeBucket(final BackblazeBucket value) async { + Future setBackblazeBucket(final BackblazeBucket value) async { await _box.put(BNames.backblazeBucket, value); _backblazeBucket = value; } diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index d46f0010..72e9fb65 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -5,6 +5,7 @@ import 'package:pub_semver/pub_semver.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; +import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -15,7 +16,7 @@ class ApiConnectionRepository { Box box = Hive.box(BNames.serverInstallationBox); final ServerApi api = ServerApi(); - final ApiData _apiData = ApiData(); + final ApiData _apiData = ApiData(ServerApi()); ApiData get apiData => _apiData; @@ -66,15 +67,18 @@ class ApiConnectionRepository { _connectionStatusStream.add(connectionStatus); return; } else { - connectionStatus = ConnectionStatus.connected; - _connectionStatusStream.add(connectionStatus); _apiData.apiVersion.data = apiVersion; _dataStream.add(_apiData); } _apiData.serverJobs.data = await api.getServerJobs(); + _apiData.backupConfig.data = await api.getBackupsConfiguration(); + _apiData.backups.data = await api.getBackups(); _dataStream.add(_apiData); + connectionStatus = ConnectionStatus.connected; + _connectionStatusStream.add(connectionStatus); + // Use timer to periodically check for new jobs _timer = Timer.periodic( const Duration(seconds: 10), @@ -82,7 +86,7 @@ class ApiConnectionRepository { ); } - void reload(final Timer timer) async { + Future reload(final Timer? timer) async { final serverDetails = getIt().serverDetails; if (serverDetails == null) { return; @@ -98,26 +102,42 @@ class ApiConnectionRepository { _connectionStatusStream.add(connectionStatus); _apiData.apiVersion.data = apiVersion; } + final Version version = Version.parse(apiVersion); + await _apiData.serverJobs + .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.backups + .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.backupConfig + .refetchData(version, () => _dataStream.add(_apiData)); + } - if (VersionConstraint.parse(_apiData.apiVersion.requiredApiVersion) - .allows(Version.parse(apiVersion))) { - final jobs = await api.getServerJobs(); - if (Object.hashAll(_apiData.serverJobs.data ?? []) != - Object.hashAll(jobs)) { - _apiData.serverJobs.data = [...jobs]; - _dataStream.add(_apiData); - } - } + void emitData() { + _dataStream.add(_apiData); } } class ApiData { - ApiData() - : serverJobs = ApiDataElement>(null), - apiVersion = ApiDataElement(null); + ApiData(final ServerApi api) + : apiVersion = ApiDataElement( + fetchData: () async => api.getApiVersion(), + ), + serverJobs = ApiDataElement>( + fetchData: () async => api.getServerJobs(), + ttl: 10, + ), + backupConfig = ApiDataElement( + fetchData: () async => api.getBackupsConfiguration(), + requiredApiVersion: '>=2.4.2', + ), + backups = ApiDataElement>( + fetchData: () async => api.getBackups(), + requiredApiVersion: '>=2.4.2', + ); ApiDataElement> serverJobs; ApiDataElement apiVersion; + ApiDataElement backupConfig; + ApiDataElement> backups; } enum ConnectionStatus { @@ -129,8 +149,9 @@ enum ConnectionStatus { } class ApiDataElement { - ApiDataElement( - final T? data, { + ApiDataElement({ + required this.fetchData, + final T? data, this.requiredApiVersion = '>=2.3.0', this.ttl = 60, }) : _data = data, @@ -139,9 +160,40 @@ class ApiDataElement { T? _data; final String requiredApiVersion; + final Future Function() fetchData; + + Future refetchData( + final Version version, final Function callback) async { + if (VersionConstraint.parse(requiredApiVersion).allows(version)) { + print('Fetching data for $runtimeType'); + if (isExpired) { + print('Data is expired'); + final newData = await fetchData(); + print(newData); + if (T is List) { + if (Object.hashAll(newData as Iterable) != + Object.hashAll(_data as Iterable)) { + _data = [...newData] as T?; + } + } else { + if (newData.hashCode != _data.hashCode) { + _data = newData; + } + } + callback(); + } + } + } + /// TTL of the data in seconds final int ttl; + Type get type => T; + + void invalidate() { + _lastUpdated = DateTime.fromMillisecondsSinceEpoch(0); + } + /// Timestamp of when the data was last updated DateTime _lastUpdated; diff --git a/lib/logic/models/backup.dart b/lib/logic/models/backup.dart index 1dcf9129..43d50987 100644 --- a/lib/logic/models/backup.dart +++ b/lib/logic/models/backup.dart @@ -1,3 +1,4 @@ +import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/backups.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart'; @@ -40,7 +41,7 @@ extension BackupReasonExtension on Enum$BackupReason { }; } -class BackupConfiguration { +class BackupConfiguration extends Equatable { BackupConfiguration.fromGraphQL( final Query$BackupConfiguration$backup$configuration configuration, ) : this( @@ -58,7 +59,7 @@ class BackupConfiguration { ), ); - BackupConfiguration({ + const BackupConfiguration({ required this.autobackupPeriod, required this.encryptionKey, required this.isInitialized, @@ -75,9 +76,39 @@ class BackupConfiguration { final String? locationName; final BackupsProviderType provider; final AutobackupQuotas autobackupQuotas; + + @override + List get props => [ + autobackupPeriod, + encryptionKey, + isInitialized, + locationId, + locationName, + provider, + autobackupQuotas, + ]; + + BackupConfiguration copyWith({ + final Duration? autobackupPeriod, + final String? encryptionKey, + final bool? isInitialized, + final String? locationId, + final String? locationName, + final BackupsProviderType? provider, + final AutobackupQuotas? autobackupQuotas, + }) => + BackupConfiguration( + autobackupPeriod: autobackupPeriod ?? this.autobackupPeriod, + encryptionKey: encryptionKey ?? this.encryptionKey, + isInitialized: isInitialized ?? this.isInitialized, + locationId: locationId ?? this.locationId, + locationName: locationName ?? this.locationName, + provider: provider ?? this.provider, + autobackupQuotas: autobackupQuotas ?? this.autobackupQuotas, + ); } -class AutobackupQuotas { +class AutobackupQuotas extends Equatable { AutobackupQuotas.fromGraphQL( final Query$BackupConfiguration$backup$configuration$autobackupQuotas autobackupQuotas, @@ -89,7 +120,7 @@ class AutobackupQuotas { yearly: autobackupQuotas.yearly, ); - AutobackupQuotas({ + const AutobackupQuotas({ required this.last, required this.daily, required this.weekly, @@ -117,6 +148,15 @@ class AutobackupQuotas { monthly: monthly ?? this.monthly, yearly: yearly ?? this.yearly, ); + + @override + List get props => [ + last, + daily, + weekly, + monthly, + yearly, + ]; } enum BackupRestoreStrategy { diff --git a/lib/logic/models/hive/backblaze_bucket.dart b/lib/logic/models/hive/backblaze_bucket.dart index 6c4bbeea..de898dff 100644 --- a/lib/logic/models/hive/backblaze_bucket.dart +++ b/lib/logic/models/hive/backblaze_bucket.dart @@ -29,4 +29,19 @@ class BackblazeBucket { @override String toString() => bucketName; + + BackblazeBucket copyWith({ + final String? bucketId, + final String? applicationKeyId, + final String? applicationKey, + final String? bucketName, + final String? encryptionKey, + }) => + BackblazeBucket( + bucketId: bucketId ?? this.bucketId, + applicationKeyId: applicationKeyId ?? this.applicationKeyId, + applicationKey: applicationKey ?? this.applicationKey, + bucketName: bucketName ?? this.bucketName, + encryptionKey: encryptionKey ?? this.encryptionKey, + ); } diff --git a/lib/ui/components/jobs_content/jobs_content.dart b/lib/ui/components/jobs_content/jobs_content.dart index d71636ee..62bdbc14 100644 --- a/lib/ui/components/jobs_content/jobs_content.dart +++ b/lib/ui/components/jobs_content/jobs_content.dart @@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index c965a2cc..a99d6385 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -2,9 +2,9 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -31,14 +31,13 @@ class BackupDetailsPage extends StatelessWidget { Widget build(final BuildContext context) { final bool isReady = context.watch().state is ServerInstallationFinished; - final BackupsState backupsState = context.watch().state; + final BackupsState backupsState = context.watch().state; final bool isBackupInitialized = backupsState.isInitialized; final StateType providerState = isReady && isBackupInitialized ? StateType.stable : StateType.uninitialized; final bool preventActions = backupsState.preventActions; final List backups = backupsState.backups; - final bool refreshing = backupsState.refreshing; final List services = context.watch().state.servicesThatCanBeBackedUp; final Duration? autobackupPeriod = backupsState.autobackupPeriod; @@ -75,8 +74,10 @@ class BackupDetailsPage extends StatelessWidget { BrandButton.rised( onPressed: preventActions ? null - : () async { - await context.read().initializeBackups(); + : () { + context + .read() + .add(const InitializeBackupsRepository()); }, text: 'backup.initialize'.tr(), ), @@ -335,8 +336,10 @@ class BackupDetailsPage extends StatelessWidget { actionButtonTitle: 'backup.forget_snapshot'.tr(), actionButtonOnPressed: () => { - context.read().forgetSnapshot( - backup.id, + context.read().add( + ForgetSnapshot( + backup.id, + ), ), }, ); @@ -391,18 +394,6 @@ class BackupDetailsPage extends StatelessWidget { const SizedBox(height: 8), const Divider(), const SizedBox(height: 8), - ListTile( - title: Text( - 'backup.refresh'.tr(), - ), - onTap: refreshing - ? null - : () => {context.read().updateBackups()}, - enabled: !refreshing, - leading: const Icon( - Icons.refresh_outlined, - ), - ), if (providerState != StateType.uninitialized) Column( children: [ @@ -425,7 +416,11 @@ class BackupDetailsPage extends StatelessWidget { ), onTap: preventActions ? null - : () => {context.read().forceUpdateBackups()}, + : () => { + context + .read() + .add(const ForceSnapshotListUpdate()) + }, ), const SizedBox(height: 8), const Divider(), @@ -447,9 +442,9 @@ class BackupDetailsPage extends StatelessWidget { Icons.warning_amber_outlined, color: overrideColor, ), - onTap: preventActions - ? null - : () => {context.read().reuploadKey()}, + // onTap: preventActions + // ? null + // : () => {context.read().reuploadKey()}, ), ], ), diff --git a/lib/ui/pages/backups/backups_list.dart b/lib/ui/pages/backups/backups_list.dart index 128c8ed6..79eb7a95 100644 --- a/lib/ui/pages/backups/backups_list.dart +++ b/lib/ui/pages/backups/backups_list.dart @@ -3,7 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -25,10 +25,10 @@ class BackupsListPage extends StatelessWidget { // If the service is null, get all backups from state. If not null, call the // serviceBackups(serviceId) on the backups state. final List backups = service == null - ? context.watch().state.backups - : context.watch().state.serviceBackups(service!.id); + ? context.watch().state.backups + : context.watch().state.serviceBackups(service!.id); final bool preventActions = - context.watch().state.preventActions; + context.watch().state.preventActions; return BrandHeroScreen( heroTitle: 'backup.snapshots_title'.tr(), hasFlashButton: true, @@ -76,9 +76,9 @@ class BackupsListPage extends StatelessWidget { description: 'backup.forget_snapshot_alert'.tr(), actionButtonTitle: 'backup.forget_snapshot'.tr(), actionButtonOnPressed: () => { - context.read().forgetSnapshot( - backup.id, - ), + context + .read() + .add(ForgetSnapshot(backup.id)), }, ); }, diff --git a/lib/ui/pages/backups/change_period_modal.dart b/lib/ui/pages/backups/change_period_modal.dart index a02b0c85..87c9fe2d 100644 --- a/lib/ui/pages/backups/change_period_modal.dart +++ b/lib/ui/pages/backups/change_period_modal.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/utils/extensions/duration.dart'; class ChangeAutobackupsPeriodModal extends StatefulWidget { @@ -34,13 +34,13 @@ class _ChangeAutobackupsPeriodModalState @override void initState() { super.initState(); - selectedPeriod = context.read().state.autobackupPeriod; + selectedPeriod = context.read().state.autobackupPeriod; } @override Widget build(final BuildContext context) { final Duration? initialAutobackupPeriod = - context.watch().state.autobackupPeriod; + context.watch().state.autobackupPeriod; return ListView( controller: widget.scrollController, padding: const EdgeInsets.all(16), @@ -91,8 +91,8 @@ class _ChangeAutobackupsPeriodModalState ? null : () { context - .read() - .setAutobackupPeriod(selectedPeriod); + .read() + .add(SetAutobackupPeriod(selectedPeriod)); Navigator.of(context).pop(); }, child: Text( diff --git a/lib/ui/pages/backups/change_rotation_quotas_modal.dart b/lib/ui/pages/backups/change_rotation_quotas_modal.dart index dab54d02..f3972387 100644 --- a/lib/ui/pages/backups/change_rotation_quotas_modal.dart +++ b/lib/ui/pages/backups/change_rotation_quotas_modal.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/models/backup.dart'; class ChangeRotationQuotasModal extends StatefulWidget { @@ -27,7 +27,7 @@ enum QuotaUnits { } class _ChangeRotationQuotasModalState extends State { - AutobackupQuotas selectedQuotas = AutobackupQuotas( + AutobackupQuotas selectedQuotas = const AutobackupQuotas( last: 3, daily: 7, weekly: 4, @@ -40,7 +40,7 @@ class _ChangeRotationQuotasModalState extends State { void initState() { super.initState(); selectedQuotas = - context.read().state.autobackupQuotas ?? selectedQuotas; + context.read().state.autobackupQuotas ?? selectedQuotas; } String generateSubtitle(final int value, final QuotaUnits unit) { @@ -83,7 +83,7 @@ class _ChangeRotationQuotasModalState extends State { @override Widget build(final BuildContext context) { final AutobackupQuotas? initialAutobackupQuotas = - context.watch().state.autobackupQuotas; + context.watch().state.autobackupQuotas; return ListView( controller: widget.scrollController, padding: const EdgeInsets.all(16), @@ -190,8 +190,8 @@ class _ChangeRotationQuotasModalState extends State { ? null : () { context - .read() - .setAutobackupQuotas(selectedQuotas); + .read() + .add(SetAutobackupQuotas(selectedQuotas)); Navigator.of(context).pop(); }, child: Text( diff --git a/lib/ui/pages/backups/copy_encryption_key_modal.dart b/lib/ui/pages/backups/copy_encryption_key_modal.dart index 96d64ce1..19c98727 100644 --- a/lib/ui/pages/backups/copy_encryption_key_modal.dart +++ b/lib/ui/pages/backups/copy_encryption_key_modal.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; import 'package:selfprivacy/utils/platform_adapter.dart'; @@ -34,7 +34,7 @@ class _CopyEncryptionKeyModalState extends State { @override Widget build(final BuildContext context) { final String? encryptionKey = - context.watch().state.backblazeBucket?.encryptionKey; + context.watch().state.backblazeBucket?.encryptionKey; if (encryptionKey == null) { return ListView( controller: widget.scrollController, diff --git a/lib/ui/pages/backups/create_backups_modal.dart b/lib/ui/pages/backups/create_backups_modal.dart index 891f26cf..acf6ac6f 100644 --- a/lib/ui/pages/backups/create_backups_modal.dart +++ b/lib/ui/pages/backups/create_backups_modal.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -147,8 +147,8 @@ class _CreateBackupsModalState extends State { ? null : () { context - .read() - .createMultipleBackups(selectedServices); + .read() + .add(CreateBackups(selectedServices)); Navigator.of(context).pop(); }, child: Text( diff --git a/lib/ui/pages/backups/snapshot_modal.dart b/lib/ui/pages/backups/snapshot_modal.dart index 411da72a..3236ddc1 100644 --- a/lib/ui/pages/backups/snapshot_modal.dart +++ b/lib/ui/pages/backups/snapshot_modal.dart @@ -2,8 +2,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -153,10 +153,8 @@ class _SnapshotModalState extends State { onPressed: isServiceBusy ? null : () { - context.read().restoreBackup( - widget.snapshot.id, - selectedStrategy, - ); + context.read().add(RestoreBackup( + widget.snapshot.id, selectedStrategy)); Navigator.of(context).pop(); getIt() .showSnackBar('backup.restore_started'.tr()); diff --git a/lib/ui/pages/more/app_settings/developer_settings.dart b/lib/ui/pages/more/app_settings/developer_settings.dart index e7881510..21f69665 100644 --- a/lib/ui/pages/more/app_settings/developer_settings.dart +++ b/lib/ui/pages/more/app_settings/developer_settings.dart @@ -5,7 +5,6 @@ import 'package:selfprivacy/logic/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:easy_localization/easy_localization.dart'; diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index 5d9d4fcc..e65098d0 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; @@ -30,7 +30,7 @@ class _ProvidersPageState extends State { final bool isReady = context.watch().state is ServerInstallationFinished; final bool isBackupInitialized = - context.watch().state.isInitialized; + context.watch().state.isInitialized; final DnsRecordsStatus dnsStatus = context.watch().state.dnsState; diff --git a/lib/ui/pages/server_storage/binds_migration/services_migration.dart b/lib/ui/pages/server_storage/binds_migration/services_migration.dart index 3d8acbc6..453e7067 100644 --- a/lib/ui/pages/server_storage/binds_migration/services_migration.dart +++ b/lib/ui/pages/server_storage/binds_migration/services_migration.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/service.dart';