From d2d8add10d98b9930cf0eb1b85305821eb714ae6 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Thu, 29 Jun 2023 12:52:09 +0300 Subject: [PATCH] feat(backups): Update the models --- lib/config/hive_config.dart | 1 + .../graphql_maps/schema/backups.graphql | 2 +- .../graphql_maps/schema/backups.graphql.dart | 50 ++-- .../schema/disk_volumes.graphql.dart | 1 - .../graphql_maps/schema/schema.graphql | 1 + .../graphql_maps/schema/server_api.graphql | 1 + .../schema/server_api.graphql.dart | 29 +- .../schema/server_settings.graphql.dart | 2 +- .../graphql_maps/schema/services.graphql.dart | 1 + .../graphql_maps/schema/users.graphql.dart | 2 +- .../graphql_maps/server_api/backups_api.dart | 207 ++++++++++++++ .../graphql_maps/server_api/server_api.dart | 203 +------------- lib/logic/cubit/backups/backups_cubit.dart | 231 ++++++++-------- lib/logic/cubit/backups/backups_state.dart | 23 +- .../server_installation_cubit.dart | 4 +- lib/logic/models/backup.dart | 60 ++++ lib/logic/models/hive/backblaze_bucket.dart | 4 + lib/logic/models/hive/backblaze_bucket.g.dart | 7 +- lib/logic/models/hive/backups_credential.dart | 8 +- .../models/hive/backups_credential.g.dart | 30 +- .../models/initialize_repository_input.dart | 2 +- lib/logic/models/json/backup.dart | 65 ----- lib/logic/models/json/backup.g.dart | 44 --- .../pages/backup_details/backup_details.dart | 261 ++++++++++++------ lib/ui/pages/providers/providers.dart | 19 +- 25 files changed, 669 insertions(+), 589 deletions(-) create mode 100644 lib/logic/api_maps/graphql_maps/server_api/backups_api.dart create mode 100644 lib/logic/models/backup.dart delete mode 100644 lib/logic/models/json/backup.dart delete mode 100644 lib/logic/models/json/backup.g.dart diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart index 0bf1ca7c..25dee2c8 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -21,6 +21,7 @@ class HiveConfig { Hive.registerAdapter(UserTypeAdapter()); Hive.registerAdapter(DnsProviderTypeAdapter()); Hive.registerAdapter(ServerProviderTypeAdapter()); + Hive.registerAdapter(BackupsProviderTypeAdapter()); await Hive.openBox(BNames.appSettingsBox); diff --git a/lib/logic/api_maps/graphql_maps/schema/backups.graphql b/lib/logic/api_maps/graphql_maps/schema/backups.graphql index 0ee5aa6c..c0341b19 100644 --- a/lib/logic/api_maps/graphql_maps/schema/backups.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/backups.graphql @@ -46,7 +46,7 @@ mutation ForceSnapshotsReload { } } -mutation StartBackup($serviceId: String = null) { +mutation StartBackup($serviceId: String!) { backup { startBackup(serviceId: $serviceId) { ...basicMutationReturnFields diff --git a/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart index 7ebfa705..2ed668b1 100644 --- a/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart @@ -1,9 +1,9 @@ import 'dart:async'; +import 'disk_volumes.graphql.dart'; import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'package:selfprivacy/utils/scalars.dart'; import 'schema.graphql.dart'; -import 'services.graphql.dart'; class Fragment$genericBackupConfigReturn { Fragment$genericBackupConfigReturn({ @@ -2738,31 +2738,27 @@ class _CopyWithStubImpl$Mutation$ForceSnapshotsReload$backup$forceSnapshotsReloa } class Variables$Mutation$StartBackup { - factory Variables$Mutation$StartBackup({String? serviceId}) => + factory Variables$Mutation$StartBackup({required String serviceId}) => Variables$Mutation$StartBackup._({ - if (serviceId != null) r'serviceId': serviceId, + r'serviceId': serviceId, }); Variables$Mutation$StartBackup._(this._$data); factory Variables$Mutation$StartBackup.fromJson(Map data) { final result$data = {}; - if (data.containsKey('serviceId')) { - final l$serviceId = data['serviceId']; - result$data['serviceId'] = (l$serviceId as String?); - } + final l$serviceId = data['serviceId']; + result$data['serviceId'] = (l$serviceId as String); return Variables$Mutation$StartBackup._(result$data); } Map _$data; - String? get serviceId => (_$data['serviceId'] as String?); + String get serviceId => (_$data['serviceId'] as String); Map toJson() { final result$data = {}; - if (_$data.containsKey('serviceId')) { - final l$serviceId = serviceId; - result$data['serviceId'] = l$serviceId; - } + final l$serviceId = serviceId; + result$data['serviceId'] = l$serviceId; return result$data; } @@ -2782,10 +2778,6 @@ class Variables$Mutation$StartBackup { } final l$serviceId = serviceId; final lOther$serviceId = other.serviceId; - if (_$data.containsKey('serviceId') != - other._$data.containsKey('serviceId')) { - return false; - } if (l$serviceId != lOther$serviceId) { return false; } @@ -2795,8 +2787,7 @@ class Variables$Mutation$StartBackup { @override int get hashCode { final l$serviceId = serviceId; - return Object.hashAll( - [_$data.containsKey('serviceId') ? l$serviceId : const {}]); + return Object.hashAll([l$serviceId]); } } @@ -2828,7 +2819,8 @@ class _CopyWithImpl$Variables$Mutation$StartBackup TRes call({Object? serviceId = _undefined}) => _then(Variables$Mutation$StartBackup._({ ..._instance._$data, - if (serviceId != _undefined) 'serviceId': (serviceId as String?), + if (serviceId != _undefined && serviceId != null) + 'serviceId': (serviceId as String), })); } @@ -2982,9 +2974,9 @@ const documentNodeMutationStartBackup = DocumentNode(definitions: [ variable: VariableNode(name: NameNode(value: 'serviceId')), type: NamedTypeNode( name: NameNode(value: 'String'), - isNonNull: false, + isNonNull: true, ), - defaultValue: DefaultValueNode(value: NullValueNode()), + defaultValue: DefaultValueNode(value: null), directives: [], ) ], @@ -3052,7 +3044,7 @@ class Options$Mutation$StartBackup extends graphql.MutationOptions { Options$Mutation$StartBackup({ String? operationName, - Variables$Mutation$StartBackup? variables, + required Variables$Mutation$StartBackup variables, graphql.FetchPolicy? fetchPolicy, graphql.ErrorPolicy? errorPolicy, graphql.CacheRereadPolicy? cacheRereadPolicy, @@ -3064,7 +3056,7 @@ class Options$Mutation$StartBackup graphql.OnError? onError, }) : onCompletedWithParsed = onCompleted, super( - variables: variables?.toJson() ?? {}, + variables: variables.toJson(), operationName: operationName, fetchPolicy: fetchPolicy, errorPolicy: errorPolicy, @@ -3098,7 +3090,7 @@ class WatchOptions$Mutation$StartBackup extends graphql.WatchQueryOptions { WatchOptions$Mutation$StartBackup({ String? operationName, - Variables$Mutation$StartBackup? variables, + required Variables$Mutation$StartBackup variables, graphql.FetchPolicy? fetchPolicy, graphql.ErrorPolicy? errorPolicy, graphql.CacheRereadPolicy? cacheRereadPolicy, @@ -3110,7 +3102,7 @@ class WatchOptions$Mutation$StartBackup bool carryForwardDataOnException = true, bool fetchResults = false, }) : super( - variables: variables?.toJson() ?? {}, + variables: variables.toJson(), operationName: operationName, fetchPolicy: fetchPolicy, errorPolicy: errorPolicy, @@ -3128,11 +3120,11 @@ class WatchOptions$Mutation$StartBackup extension ClientExtension$Mutation$StartBackup on graphql.GraphQLClient { Future> mutate$StartBackup( - [Options$Mutation$StartBackup? options]) async => - await this.mutate(options ?? Options$Mutation$StartBackup()); + Options$Mutation$StartBackup options) async => + await this.mutate(options); graphql.ObservableQuery watchMutation$StartBackup( - [WatchOptions$Mutation$StartBackup? options]) => - this.watchMutation(options ?? WatchOptions$Mutation$StartBackup()); + WatchOptions$Mutation$StartBackup options) => + this.watchMutation(options); } class Mutation$StartBackup$backup { diff --git a/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart index e9e91eb8..c900dcc7 100644 --- a/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart @@ -3,7 +3,6 @@ import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'package:selfprivacy/utils/scalars.dart'; import 'schema.graphql.dart'; -import 'services.graphql.dart'; class Fragment$basicMutationReturnFields { Fragment$basicMutationReturnFields({ diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql b/lib/logic/api_maps/graphql_maps/schema/schema.graphql index 15ae463d..d53da52e 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql @@ -19,6 +19,7 @@ type ApiDevice { type ApiJob { uid: String! + typeId: String! name: String! description: String! status: String! diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql index f1012815..9797e81f 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql @@ -23,6 +23,7 @@ query GetApiJobs { status statusText uid + typeId updatedAt } } diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart index 2e950aa1..7c09fbb7 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart @@ -1,9 +1,9 @@ import 'dart:async'; +import 'disk_volumes.graphql.dart'; import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'package:selfprivacy/utils/scalars.dart'; import 'schema.graphql.dart'; -import 'services.graphql.dart'; class Fragment$basicMutationReturnFields { Fragment$basicMutationReturnFields({ @@ -2914,6 +2914,13 @@ const documentNodeQueryGetApiJobs = DocumentNode(definitions: [ directives: [], selectionSet: null, ), + FieldNode( + name: NameNode(value: 'typeId'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), FieldNode( name: NameNode(value: 'updatedAt'), alias: null, @@ -3229,6 +3236,7 @@ class Query$GetApiJobs$jobs$getJobs { required this.status, this.statusText, required this.uid, + required this.typeId, required this.updatedAt, this.$__typename = 'ApiJob', }); @@ -3244,6 +3252,7 @@ class Query$GetApiJobs$jobs$getJobs { final l$status = json['status']; final l$statusText = json['statusText']; final l$uid = json['uid']; + final l$typeId = json['typeId']; final l$updatedAt = json['updatedAt']; final l$$__typename = json['__typename']; return Query$GetApiJobs$jobs$getJobs( @@ -3257,6 +3266,7 @@ class Query$GetApiJobs$jobs$getJobs { status: (l$status as String), statusText: (l$statusText as String?), uid: (l$uid as String), + typeId: (l$typeId as String), updatedAt: dateTimeFromJson(l$updatedAt), $__typename: (l$$__typename as String), ); @@ -3282,6 +3292,8 @@ class Query$GetApiJobs$jobs$getJobs { final String uid; + final String typeId; + final DateTime updatedAt; final String $__typename; @@ -3309,6 +3321,8 @@ class Query$GetApiJobs$jobs$getJobs { _resultData['statusText'] = l$statusText; final l$uid = uid; _resultData['uid'] = l$uid; + final l$typeId = typeId; + _resultData['typeId'] = l$typeId; final l$updatedAt = updatedAt; _resultData['updatedAt'] = dateTimeToJson(l$updatedAt); final l$$__typename = $__typename; @@ -3328,6 +3342,7 @@ class Query$GetApiJobs$jobs$getJobs { final l$status = status; final l$statusText = statusText; final l$uid = uid; + final l$typeId = typeId; final l$updatedAt = updatedAt; final l$$__typename = $__typename; return Object.hashAll([ @@ -3341,6 +3356,7 @@ class Query$GetApiJobs$jobs$getJobs { l$status, l$statusText, l$uid, + l$typeId, l$updatedAt, l$$__typename, ]); @@ -3405,6 +3421,11 @@ class Query$GetApiJobs$jobs$getJobs { if (l$uid != lOther$uid) { return false; } + final l$typeId = typeId; + final lOther$typeId = other.typeId; + if (l$typeId != lOther$typeId) { + return false; + } final l$updatedAt = updatedAt; final lOther$updatedAt = other.updatedAt; if (l$updatedAt != lOther$updatedAt) { @@ -3448,6 +3469,7 @@ abstract class CopyWith$Query$GetApiJobs$jobs$getJobs { String? status, String? statusText, String? uid, + String? typeId, DateTime? updatedAt, String? $__typename, }); @@ -3477,6 +3499,7 @@ class _CopyWithImpl$Query$GetApiJobs$jobs$getJobs Object? status = _undefined, Object? statusText = _undefined, Object? uid = _undefined, + Object? typeId = _undefined, Object? updatedAt = _undefined, Object? $__typename = _undefined, }) => @@ -3504,6 +3527,9 @@ class _CopyWithImpl$Query$GetApiJobs$jobs$getJobs ? _instance.statusText : (statusText as String?), uid: uid == _undefined || uid == null ? _instance.uid : (uid as String), + typeId: typeId == _undefined || typeId == null + ? _instance.typeId + : (typeId as String), updatedAt: updatedAt == _undefined || updatedAt == null ? _instance.updatedAt : (updatedAt as DateTime), @@ -3530,6 +3556,7 @@ class _CopyWithStubImpl$Query$GetApiJobs$jobs$getJobs String? status, String? statusText, String? uid, + String? typeId, DateTime? updatedAt, String? $__typename, }) => diff --git a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart index abbba037..ae8aabcb 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart @@ -1,8 +1,8 @@ import 'dart:async'; +import 'disk_volumes.graphql.dart'; import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'schema.graphql.dart'; -import 'services.graphql.dart'; class Fragment$basicMutationReturnFields { Fragment$basicMutationReturnFields({ diff --git a/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart index 64720f3e..2fbf6c3d 100644 --- a/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'disk_volumes.graphql.dart'; import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'package:selfprivacy/utils/scalars.dart'; diff --git a/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart index 4df77d6f..d17316df 100644 --- a/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart @@ -1,8 +1,8 @@ import 'dart:async'; +import 'disk_volumes.graphql.dart'; import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'schema.graphql.dart'; -import 'services.graphql.dart'; class Fragment$basicMutationReturnFields { Fragment$basicMutationReturnFields({ diff --git a/lib/logic/api_maps/graphql_maps/server_api/backups_api.dart b/lib/logic/api_maps/graphql_maps/server_api/backups_api.dart new file mode 100644 index 00000000..18fea675 --- /dev/null +++ b/lib/logic/api_maps/graphql_maps/server_api/backups_api.dart @@ -0,0 +1,207 @@ +part of 'server_api.dart'; + +mixin BackupsApi on GraphQLApiMap { + Future> getBackups() async { + List backups; + QueryResult response; + + try { + final GraphQLClient client = await getClient(); + response = await client.query$AllBackupSnapshots(); + if (response.hasException) { + final message = response.exception.toString(); + print(message); + backups = []; + } + final List parsed = response.parsedData!.backup.allSnapshots + .map( + ( + final Query$AllBackupSnapshots$backup$allSnapshots snapshot, + ) => + Backup.fromGraphQL(snapshot), + ) + .toList(); + backups = parsed; + } catch (e) { + print(e); + backups = []; + } + + return backups; + } + + Future getBackupsConfiguration() async { + BackupConfiguration? backupConfiguration; + QueryResult response; + try { + final GraphQLClient client = await getClient(); + response = await client.query$BackupConfiguration(); + if (response.hasException) { + final message = response.exception.toString(); + print(message); + backupConfiguration = null; + } + final BackupConfiguration parsed = BackupConfiguration.fromGraphQL( + response.parsedData!.backup.configuration, + ); + backupConfiguration = parsed; + } catch (e) { + print(e); + backupConfiguration = null; + } + + return backupConfiguration; + } + + Future forceBackupListReload() async { + try { + final GraphQLClient client = await getClient(); + await client.mutate$ForceSnapshotsReload(); + } catch (e) { + print(e); + return GenericResult( + success: false, + data: null, + message: e.toString(), + ); + } + + return GenericResult( + success: true, + data: null, + ); + } + + Future startBackup(final String serviceId) async { + QueryResult response; + GenericResult? result; + + try { + final GraphQLClient client = await getClient(); + final variables = Variables$Mutation$StartBackup(serviceId: serviceId); + final options = Options$Mutation$StartBackup(variables: variables); + response = await client.mutate$StartBackup(options); + if (response.hasException) { + final message = response.exception.toString(); + print(message); + result = GenericResult( + success: false, + data: null, + message: message, + ); + } + result = GenericResult( + success: true, + data: null, + ); + } catch (e) { + print(e); + result = GenericResult( + success: false, + data: null, + message: e.toString(), + ); + } + + return result; + } + + Future setAutobackupPeriod({final int? period}) async { + QueryResult response; + GenericResult? result; + + try { + final GraphQLClient client = await getClient(); + final variables = Variables$Mutation$SetAutobackupPeriod(period: period); + final options = + Options$Mutation$SetAutobackupPeriod(variables: variables); + response = await client.mutate$SetAutobackupPeriod(options); + if (response.hasException) { + final message = response.exception.toString(); + print(message); + result = GenericResult( + success: false, + data: null, + message: message, + ); + } + result = GenericResult( + success: true, + data: null, + ); + } catch (e) { + print(e); + result = GenericResult( + success: false, + data: null, + message: e.toString(), + ); + } + + return result; + } + + Future removeRepository() async { + try { + final GraphQLClient client = await getClient(); + await client.mutate$RemoveRepository(); + } catch (e) { + print(e); + return GenericResult( + success: false, + data: null, + message: e.toString(), + ); + } + + return GenericResult( + success: true, + data: null, + ); + } + + Future initializeRepository( + final InitializeRepositoryInput input, + ) async { + QueryResult response; + GenericResult? result; + + try { + final GraphQLClient client = await getClient(); + final variables = Variables$Mutation$InitializeRepository( + repository: Input$InitializeRepositoryInput( + locationId: input.locationId, + locationName: input.locationName, + login: input.login, + password: input.password, + provider: input.provider.toGraphQL(), + ), + ); + final options = + Options$Mutation$InitializeRepository(variables: variables); + response = await client.mutate$InitializeRepository(options); + if (response.hasException) { + final message = response.exception.toString(); + print(message); + result = GenericResult( + success: false, + data: null, + message: message, + ); + } + result = GenericResult( + success: true, + data: null, + ); + } catch (e) { + print(e); + result = GenericResult( + success: false, + data: null, + message: e.toString(), + ); + } + + return result; + } +} diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart index bbcbb461..91694945 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart @@ -15,7 +15,7 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/initialize_repository_input.dart'; import 'package:selfprivacy/logic/models/json/api_token.dart'; -import 'package:selfprivacy/logic/models/json/backup.dart'; +import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/json/device_token.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; @@ -32,9 +32,10 @@ part 'server_actions_api.dart'; part 'services_api.dart'; part 'users_api.dart'; part 'volume_api.dart'; +part 'backups_api.dart'; class ServerApi extends GraphQLApiMap - with VolumeApi, JobsApi, ServerActionsApi, ServicesApi, UsersApi { + with VolumeApi, JobsApi, ServerActionsApi, ServicesApi, UsersApi, BackupsApi { ServerApi({ this.hasLogger = false, this.isWithToken = true, @@ -512,202 +513,4 @@ class ServerApi extends GraphQLApiMap return token; } - - Future>> getBackups() async { - GenericResult> backups; - QueryResult response; - - try { - final GraphQLClient client = await getClient(); - response = await client.query$AllBackupSnapshots(); - if (response.hasException) { - final message = response.exception.toString(); - print(message); - backups = GenericResult>( - success: false, - data: [], - message: message, - ); - } - final List parsed = response.parsedData!.backup.allSnapshots - .map( - ( - final Query$AllBackupSnapshots$backup$allSnapshots snapshot, - ) => - Backup.fromGraphQL(snapshot), - ) - .toList(); - backups = GenericResult>( - success: true, - data: parsed, - ); - } catch (e) { - print(e); - backups = GenericResult>( - success: false, - data: [], - message: e.toString(), - ); - } - - return backups; - } - - Future forceBackupListReload() async { - try { - final GraphQLClient client = await getClient(); - await client.mutate$ForceSnapshotsReload(); - } catch (e) { - print(e); - return GenericResult( - success: false, - data: null, - message: e.toString(), - ); - } - - return GenericResult( - success: true, - data: null, - ); - } - - Future startBackup({final String? serviceId}) async { - QueryResult response; - GenericResult? result; - - try { - final GraphQLClient client = await getClient(); - final variables = Variables$Mutation$StartBackup(serviceId: serviceId); - final options = Options$Mutation$StartBackup(variables: variables); - response = await client.mutate$StartBackup(options); - if (response.hasException) { - final message = response.exception.toString(); - print(message); - result = GenericResult( - success: false, - data: null, - message: message, - ); - } - result = GenericResult( - success: true, - data: null, - ); - } catch (e) { - print(e); - result = GenericResult( - success: false, - data: null, - message: e.toString(), - ); - } - - return result; - } - - Future setAutobackupPeriod({final int? period}) async { - QueryResult response; - GenericResult? result; - - try { - final GraphQLClient client = await getClient(); - final variables = Variables$Mutation$SetAutobackupPeriod(period: period); - final options = - Options$Mutation$SetAutobackupPeriod(variables: variables); - response = await client.mutate$SetAutobackupPeriod(options); - if (response.hasException) { - final message = response.exception.toString(); - print(message); - result = GenericResult( - success: false, - data: null, - message: message, - ); - } - result = GenericResult( - success: true, - data: null, - ); - } catch (e) { - print(e); - result = GenericResult( - success: false, - data: null, - message: e.toString(), - ); - } - - return result; - } - - Future getBackupStatus() async => BackupStatus( - progress: 0.0, - status: BackupStatusEnum.error, - errorMessage: null, - ); - - Future removeRepository() async { - try { - final GraphQLClient client = await getClient(); - await client.mutate$RemoveRepository(); - } catch (e) { - print(e); - return GenericResult( - success: false, - data: null, - message: e.toString(), - ); - } - - return GenericResult( - success: true, - data: null, - ); - } - - Future initializeRepository( - final InitializeRepositoryInput input, - ) async { - QueryResult response; - GenericResult? result; - - try { - final GraphQLClient client = await getClient(); - final variables = Variables$Mutation$InitializeRepository( - repository: Input$InitializeRepositoryInput( - locationId: input.locationId, - locationName: input.locationName, - login: input.login, - password: input.password, - provider: input.provider.toGraphQL(), - ), - ); - final options = - Options$Mutation$InitializeRepository(variables: variables); - response = await client.mutate$InitializeRepository(options); - if (response.hasException) { - final message = response.exception.toString(); - print(message); - result = GenericResult( - success: false, - data: null, - message: message, - ); - } - result = GenericResult( - success: true, - data: null, - ); - } catch (e) { - print(e); - result = GenericResult( - success: false, - data: null, - message: e.toString(), - ); - } - - return result; - } } diff --git a/lib/logic/cubit/backups/backups_cubit.dart b/lib/logic/cubit/backups/backups_cubit.dart index cb743161..4a1c980b 100644 --- a/lib/logic/cubit/backups/backups_cubit.dart +++ b/lib/logic/cubit/backups/backups_cubit.dart @@ -6,7 +6,10 @@ 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/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; -import 'package:selfprivacy/logic/models/json/backup.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'; @@ -24,107 +27,83 @@ class BackupsCubit extends ServerInstallationDependendCubit { Future load() async { if (serverInstallationCubit.state is ServerInstallationFinished) { final BackblazeBucket? bucket = getIt().backblazeBucket; - if (bucket == null) { - emit( - const BackupsState( - isInitialized: false, - preventActions: false, - refreshing: false, - ), - ); - } else { - final BackupStatus status = await api.getBackupStatus(); - switch (status.status) { - case BackupStatusEnum.noKey: - case BackupStatusEnum.notInitialized: - emit( - BackupsState( - backups: const [], - isInitialized: true, - preventActions: false, - progress: 0, - status: status.status, - refreshing: false, - ), - ); - break; - case BackupStatusEnum.initializing: - emit( - BackupsState( - backups: const [], - isInitialized: true, - preventActions: false, - progress: 0, - status: status.status, - refreshTimer: const Duration(seconds: 10), - refreshing: false, - ), - ); - break; - case BackupStatusEnum.initialized: - case BackupStatusEnum.error: - final result = await api.getBackups(); - emit( - BackupsState( - backups: result.data, - isInitialized: true, - preventActions: false, - progress: status.progress, - status: status.status, - error: status.errorMessage ?? '', - refreshing: false, - ), - ); - break; - case BackupStatusEnum.backingUp: - case BackupStatusEnum.restoring: - final result = await api.getBackups(); - emit( - BackupsState( - backups: result.data, - isInitialized: true, - preventActions: true, - progress: status.progress, - status: status.status, - error: status.errorMessage ?? '', - refreshTimer: const Duration(seconds: 5), - refreshing: false, - ), - ); - break; - default: - emit(const BackupsState()); - } - Timer(state.refreshTimer, () => updateBackups(useTimer: true)); - } + final BackupConfiguration? backupConfig = + await api.getBackupsConfiguration(); + final List backups = await api.getBackups(); + emit( + state.copyWith( + backblazeBucket: bucket, + isInitialized: backupConfig?.isInitialized, + autobackupPeriod: backupConfig?.autobackupPeriod, + backups: backups, + preventActions: false, + refreshing: false, + ), + ); + print(state); } } - Future createBucket() async { + Future initializeBackups() async { emit(state.copyWith(preventActions: true)); - final String domain = serverInstallationCubit.state.serverDomain!.domainName - .replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-'); - final int serverId = serverInstallationCubit.state.serverDetails!.id; - String bucketName = 'selfprivacy-$domain-$serverId'; - // If bucket name is too long, shorten it - if (bucketName.length > 49) { - bucketName = bucketName.substring(0, 49); + 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 String bucketId = await backblaze.createBucket(bucketName); - final BackblazeApplicationKey key = await backblaze.createKey(bucketId); - final BackblazeBucket bucket = BackblazeBucket( - bucketId: bucketId, - bucketName: bucketName, - applicationKey: key.applicationKey, - applicationKeyId: key.applicationKeyId, + 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 = 'selfprivacy-$domain-$serverId'; + // If bucket name is too long, shorten it + 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, + ), ); - - await getIt().storeBackblazeBucket(bucket); - //await api.uploadBackblazeConfig(bucket); + 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(isInitialized: true, preventActions: false)); + emit(state.copyWith(preventActions: false)); } Future reuploadKey() async { @@ -132,42 +111,47 @@ class BackupsCubit extends ServerInstallationDependendCubit { final BackblazeBucket? bucket = getIt().backblazeBucket; if (bucket == null) { emit(state.copyWith(isInitialized: false)); + print('bucket is null'); } else { - //await api.uploadBackblazeConfig(bucket); - emit(state.copyWith(isInitialized: true, preventActions: false)); - getIt().showSnackBar('backup.reuploaded_key'); + print('bucket is not null'); + final GenericResult result = await api.initializeRepository( + InitializeRepositoryInput( + provider: BackupsProviderType.backblaze, + locationId: bucket.bucketId, + locationName: bucket.bucketName, + login: bucket.applicationKeyId, + password: bucket.applicationKey, + ), + ); + print('result is $result'); + 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'); + await updateBackups(); + } } } - Duration refreshTimeFromState(final BackupStatusEnum status) { - switch (status) { - case BackupStatusEnum.backingUp: - case BackupStatusEnum.restoring: - return const Duration(seconds: 5); - case BackupStatusEnum.initializing: - return const Duration(seconds: 10); - default: - return const Duration(seconds: 60); - } - } + @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 result = await api.getBackups(); - if (!result.success || result.data.isEmpty) { - return; - } + final backups = await api.getBackups(); + final backupConfig = await api.getBackupsConfiguration(); - final List backups = result.data; - final BackupStatus status = await api.getBackupStatus(); emit( state.copyWith( backups: backups, - progress: status.progress, - status: status.status, - error: status.errorMessage, - refreshTimer: refreshTimeFromState(status.status), + refreshTimer: refreshTimeFromState(), refreshing: false, + isInitialized: backupConfig?.isInitialized ?? false, + autobackupPeriod: backupConfig?.autobackupPeriod, ), ); if (useTimer) { @@ -182,9 +166,18 @@ class BackupsCubit extends ServerInstallationDependendCubit { emit(state.copyWith(preventActions: false)); } - Future createBackup() async { + Future createMultipleBackups(final List services) async { emit(state.copyWith(preventActions: true)); - await api.startBackup(); + 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)); } diff --git a/lib/logic/cubit/backups/backups_state.dart b/lib/logic/cubit/backups/backups_state.dart index 33ec52c8..988f669b 100644 --- a/lib/logic/cubit/backups/backups_state.dart +++ b/lib/logic/cubit/backups/backups_state.dart @@ -4,31 +4,26 @@ class BackupsState extends ServerInstallationDependendState { const BackupsState({ this.isInitialized = false, this.backups = const [], - this.progress = 0.0, - this.status = BackupStatusEnum.noKey, this.preventActions = true, - this.error = '', this.refreshTimer = const Duration(seconds: 60), this.refreshing = true, + this.autobackupPeriod, + this.backblazeBucket, }); final bool isInitialized; final List backups; - final double progress; - final BackupStatusEnum status; final bool preventActions; - final String error; final Duration refreshTimer; final bool refreshing; + final Duration? autobackupPeriod; + final BackblazeBucket? backblazeBucket; @override List get props => [ isInitialized, backups, - progress, preventActions, - status, - error, refreshTimer, refreshing ]; @@ -36,21 +31,19 @@ class BackupsState extends ServerInstallationDependendState { BackupsState copyWith({ final bool? isInitialized, final List? backups, - final double? progress, - final BackupStatusEnum? status, final bool? preventActions, - final String? error, final Duration? refreshTimer, final bool? refreshing, + final Duration? autobackupPeriod, + final BackblazeBucket? backblazeBucket, }) => BackupsState( isInitialized: isInitialized ?? this.isInitialized, backups: backups ?? this.backups, - progress: progress ?? this.progress, - status: status ?? this.status, preventActions: preventActions ?? this.preventActions, - error: error ?? this.error, refreshTimer: refreshTimer ?? this.refreshTimer, refreshing: refreshing ?? this.refreshing, + autobackupPeriod: autobackupPeriod ?? this.autobackupPeriod, + backblazeBucket: backblazeBucket ?? this.backblazeBucket, ); } diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index b2252e8e..3f00a5b5 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -197,7 +197,7 @@ class ServerInstallationCubit extends Cubit { final BackupsCredential backblazeCredential = BackupsCredential( keyId: keyId, applicationKey: applicationKey, - provider: BackupsProvider.backblaze, + provider: BackupsProviderType.backblaze, ); await repository.saveBackblazeKey(backblazeCredential); if (state is ServerInstallationRecovery) { @@ -699,7 +699,7 @@ class ServerInstallationCubit extends Cubit { provider: dnsProviderType, ), ); - await repository.setDnsApiToken(token); + // await repository.setDnsApiToken(token); emit( dataState.copyWith( serverDomain: ServerDomain( diff --git a/lib/logic/models/backup.dart b/lib/logic/models/backup.dart new file mode 100644 index 00000000..fda68375 --- /dev/null +++ b/lib/logic/models/backup.dart @@ -0,0 +1,60 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/backups.graphql.dart'; +import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; + +class Backup { + Backup.fromGraphQL( + final Query$AllBackupSnapshots$backup$allSnapshots snapshot, + ) : this( + id: snapshot.id, + time: snapshot.createdAt, + serviceId: snapshot.service.id, + fallbackServiceName: snapshot.service.displayName, + ); + + Backup({ + required this.time, + required this.id, + required this.serviceId, + required this.fallbackServiceName, + }); + + // Time of the backup + final DateTime time; + @JsonKey(name: 'short_id') + final String id; + final String serviceId; + final String fallbackServiceName; +} + +class BackupConfiguration { + BackupConfiguration.fromGraphQL( + final Query$BackupConfiguration$backup$configuration configuration, + ) : this( + // Provided by API as int of minutes + autobackupPeriod: configuration.autobackupPeriod != null + ? Duration(minutes: configuration.autobackupPeriod!) + : null, + encryptionKey: configuration.encryptionKey, + isInitialized: configuration.isInitialized, + locationId: configuration.locationId, + locationName: configuration.locationName, + provider: BackupsProviderType.fromGraphQL(configuration.provider), + ); + + BackupConfiguration({ + required this.autobackupPeriod, + required this.encryptionKey, + required this.isInitialized, + required this.locationId, + required this.locationName, + required this.provider, + }); + + final Duration? autobackupPeriod; + final String encryptionKey; + final bool isInitialized; + final String? locationId; + final String? locationName; + final BackupsProviderType provider; +} diff --git a/lib/logic/models/hive/backblaze_bucket.dart b/lib/logic/models/hive/backblaze_bucket.dart index 39b98cf5..6c4bbeea 100644 --- a/lib/logic/models/hive/backblaze_bucket.dart +++ b/lib/logic/models/hive/backblaze_bucket.dart @@ -9,6 +9,7 @@ class BackblazeBucket { required this.bucketName, required this.applicationKeyId, required this.applicationKey, + required this.encryptionKey, }); @HiveField(0) @@ -23,6 +24,9 @@ class BackblazeBucket { @HiveField(3) final String bucketName; + @HiveField(4) + final String encryptionKey; + @override String toString() => bucketName; } diff --git a/lib/logic/models/hive/backblaze_bucket.g.dart b/lib/logic/models/hive/backblaze_bucket.g.dart index 18802bc0..129905a5 100644 --- a/lib/logic/models/hive/backblaze_bucket.g.dart +++ b/lib/logic/models/hive/backblaze_bucket.g.dart @@ -21,13 +21,14 @@ class BackblazeBucketAdapter extends TypeAdapter { bucketName: fields[3] as String, applicationKeyId: fields[1] as String, applicationKey: fields[2] as String, + encryptionKey: fields[4] as String, ); } @override void write(BinaryWriter writer, BackblazeBucket obj) { writer - ..writeByte(4) + ..writeByte(5) ..writeByte(0) ..write(obj.bucketId) ..writeByte(1) @@ -35,7 +36,9 @@ class BackblazeBucketAdapter extends TypeAdapter { ..writeByte(2) ..write(obj.applicationKey) ..writeByte(3) - ..write(obj.bucketName); + ..write(obj.bucketName) + ..writeByte(4) + ..write(obj.encryptionKey); } @override diff --git a/lib/logic/models/hive/backups_credential.dart b/lib/logic/models/hive/backups_credential.dart index 9b14f067..0c0cf48d 100644 --- a/lib/logic/models/hive/backups_credential.dart +++ b/lib/logic/models/hive/backups_credential.dart @@ -19,8 +19,8 @@ class BackupsCredential { @HiveField(1) final String applicationKey; - @HiveField(2, defaultValue: BackupsProvider.backblaze) - final BackupsProvider provider; + @HiveField(2, defaultValue: BackupsProviderType.backblaze) + final BackupsProviderType provider; String get encodedApiKey => encodedBackblazeKey(keyId, applicationKey); @@ -35,7 +35,7 @@ String encodedBackblazeKey(final String? keyId, final String? applicationKey) { } @HiveType(typeId: 103) -enum BackupsProvider { +enum BackupsProviderType { @HiveField(0) none, @HiveField(1) @@ -45,7 +45,7 @@ enum BackupsProvider { @HiveField(3) backblaze; - factory BackupsProvider.fromGraphQL(final Enum$BackupProvider provider) => + factory BackupsProviderType.fromGraphQL(final Enum$BackupProvider provider) => switch (provider) { Enum$BackupProvider.NONE => none, Enum$BackupProvider.MEMORY => memory, diff --git a/lib/logic/models/hive/backups_credential.g.dart b/lib/logic/models/hive/backups_credential.g.dart index 2cf023df..13166d31 100644 --- a/lib/logic/models/hive/backups_credential.g.dart +++ b/lib/logic/models/hive/backups_credential.g.dart @@ -20,8 +20,8 @@ class BackupsCredentialAdapter extends TypeAdapter { keyId: fields[0] as String, applicationKey: fields[1] as String, provider: fields[2] == null - ? BackupsProvider.backblaze - : fields[2] as BackupsProvider, + ? BackupsProviderType.backblaze + : fields[2] as BackupsProviderType, ); } @@ -48,39 +48,39 @@ class BackupsCredentialAdapter extends TypeAdapter { typeId == other.typeId; } -class BackupsProviderAdapter extends TypeAdapter { +class BackupsProviderTypeAdapter extends TypeAdapter { @override final int typeId = 103; @override - BackupsProvider read(BinaryReader reader) { + BackupsProviderType read(BinaryReader reader) { switch (reader.readByte()) { case 0: - return BackupsProvider.none; + return BackupsProviderType.none; case 1: - return BackupsProvider.memory; + return BackupsProviderType.memory; case 2: - return BackupsProvider.file; + return BackupsProviderType.file; case 3: - return BackupsProvider.backblaze; + return BackupsProviderType.backblaze; default: - return BackupsProvider.none; + return BackupsProviderType.none; } } @override - void write(BinaryWriter writer, BackupsProvider obj) { + void write(BinaryWriter writer, BackupsProviderType obj) { switch (obj) { - case BackupsProvider.none: + case BackupsProviderType.none: writer.writeByte(0); break; - case BackupsProvider.memory: + case BackupsProviderType.memory: writer.writeByte(1); break; - case BackupsProvider.file: + case BackupsProviderType.file: writer.writeByte(2); break; - case BackupsProvider.backblaze: + case BackupsProviderType.backblaze: writer.writeByte(3); break; } @@ -92,7 +92,7 @@ class BackupsProviderAdapter extends TypeAdapter { @override bool operator ==(Object other) => identical(this, other) || - other is BackupsProviderAdapter && + other is BackupsProviderTypeAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } diff --git a/lib/logic/models/initialize_repository_input.dart b/lib/logic/models/initialize_repository_input.dart index fcb137b0..7ccaa410 100644 --- a/lib/logic/models/initialize_repository_input.dart +++ b/lib/logic/models/initialize_repository_input.dart @@ -8,7 +8,7 @@ class InitializeRepositoryInput { required this.login, required this.password, }); - final BackupsProvider provider; + final BackupsProviderType provider; final String locationId; final String locationName; final String login; diff --git a/lib/logic/models/json/backup.dart b/lib/logic/models/json/backup.dart deleted file mode 100644 index a806d00b..00000000 --- a/lib/logic/models/json/backup.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/backups.graphql.dart'; - -part 'backup.g.dart'; - -@JsonSerializable() -class Backup { - factory Backup.fromJson(final Map json) => - _$BackupFromJson(json); - Backup.fromGraphQL( - final Query$AllBackupSnapshots$backup$allSnapshots snapshot, - ) : this( - id: snapshot.id, - time: snapshot.createdAt, - serviceId: snapshot.service.id, - fallbackServiceName: snapshot.service.displayName, - ); - - Backup({ - required this.time, - required this.id, - required this.serviceId, - required this.fallbackServiceName, - }); - - // Time of the backup - final DateTime time; - @JsonKey(name: 'short_id') - final String id; - final String serviceId; - final String fallbackServiceName; -} - -enum BackupStatusEnum { - @JsonValue('NO_KEY') - noKey, - @JsonValue('NOT_INITIALIZED') - notInitialized, - @JsonValue('INITIALIZED') - initialized, - @JsonValue('BACKING_UP') - backingUp, - @JsonValue('RESTORING') - restoring, - @JsonValue('ERROR') - error, - @JsonValue('INITIALIZING') - initializing, -} - -@JsonSerializable() -class BackupStatus { - factory BackupStatus.fromJson(final Map json) => - _$BackupStatusFromJson(json); - BackupStatus({ - required this.status, - required this.progress, - required this.errorMessage, - }); - - final BackupStatusEnum status; - final double progress; - @JsonKey(name: 'error_message') - final String? errorMessage; -} diff --git a/lib/logic/models/json/backup.g.dart b/lib/logic/models/json/backup.g.dart deleted file mode 100644 index 35148517..00000000 --- a/lib/logic/models/json/backup.g.dart +++ /dev/null @@ -1,44 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'backup.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Backup _$BackupFromJson(Map json) => Backup( - time: DateTime.parse(json['time'] as String), - id: json['short_id'] as String, - serviceId: json['serviceId'] as String, - fallbackServiceName: json['fallbackServiceName'] as String, - ); - -Map _$BackupToJson(Backup instance) => { - 'time': instance.time.toIso8601String(), - 'short_id': instance.id, - 'serviceId': instance.serviceId, - 'fallbackServiceName': instance.fallbackServiceName, - }; - -BackupStatus _$BackupStatusFromJson(Map json) => BackupStatus( - status: $enumDecode(_$BackupStatusEnumEnumMap, json['status']), - progress: (json['progress'] as num).toDouble(), - errorMessage: json['error_message'] as String?, - ); - -Map _$BackupStatusToJson(BackupStatus instance) => - { - 'status': _$BackupStatusEnumEnumMap[instance.status]!, - 'progress': instance.progress, - 'error_message': instance.errorMessage, - }; - -const _$BackupStatusEnumEnumMap = { - BackupStatusEnum.noKey: 'NO_KEY', - BackupStatusEnum.notInitialized: 'NOT_INITIALIZED', - BackupStatusEnum.initialized: 'INITIALIZED', - BackupStatusEnum.backingUp: 'BACKING_UP', - BackupStatusEnum.restoring: 'RESTORING', - BackupStatusEnum.error: 'ERROR', - BackupStatusEnum.initializing: 'INITIALIZING', -}; diff --git a/lib/ui/pages/backup_details/backup_details.dart b/lib/ui/pages/backup_details/backup_details.dart index f909e481..2dac9a0b 100644 --- a/lib/ui/pages/backup_details/backup_details.dart +++ b/lib/ui/pages/backup_details/backup_details.dart @@ -1,9 +1,14 @@ 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/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/models/json/backup.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.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'; +import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/cards/outlined_card.dart'; @@ -29,19 +34,15 @@ class _BackupDetailsPageState extends State is ServerInstallationFinished; final bool isBackupInitialized = context.watch().state.isInitialized; - final BackupStatusEnum backupStatus = - context.watch().state.status; final StateType providerState = isReady && isBackupInitialized - ? (backupStatus == BackupStatusEnum.error - ? StateType.warning - : StateType.stable) + ? StateType.stable : StateType.uninitialized; final bool preventActions = context.watch().state.preventActions; - final double backupProgress = context.watch().state.progress; - final String backupError = context.watch().state.error; final List backups = context.watch().state.backups; final bool refreshing = context.watch().state.refreshing; + final List services = + context.watch().state.services; return BrandHeroScreen( heroIcon: BrandIcons.save, @@ -53,81 +54,45 @@ class _BackupDetailsPageState extends State onPressed: preventActions ? null : () async { - await context.read().createBucket(); + await context.read().initializeBackups(); }, text: 'backup.initialize'.tr(), ), - if (backupStatus == BackupStatusEnum.initializing) - Text( - 'backup.waiting_for_rebuild'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - if (backupStatus != BackupStatusEnum.initializing && - backupStatus != BackupStatusEnum.noKey) - OutlinedCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (backupStatus == BackupStatusEnum.initialized) - ListTile( - onTap: preventActions - ? null - : () async { - await context.read().createBackup(); - }, - leading: const Icon( - Icons.add_circle_outline_rounded, - ), - title: Text( - 'backup.create_new'.tr(), - style: Theme.of(context).textTheme.titleLarge, - ), - ), - if (backupStatus == BackupStatusEnum.backingUp) - ListTile( - title: Text( - 'backup.creating'.tr( - args: [(backupProgress * 100).round().toString()], + ListTile( + onTap: preventActions + ? null + : () { + // await context.read().createBackup(); + showModalBottomSheet( + useRootNavigator: true, + context: context, + isScrollControlled: true, + builder: (final BuildContext context) => + DraggableScrollableSheet( + expand: false, + maxChildSize: 0.9, + minChildSize: 0.4, + initialChildSize: 0.6, + builder: (context, scrollController) => + CreateBackupsModal( + services: services, + scrollController: scrollController, ), - style: Theme.of(context).textTheme.titleLarge, ), - subtitle: LinearProgressIndicator( - value: backupProgress, - backgroundColor: Colors.grey.withOpacity(0.2), - ), - ), - if (backupStatus == BackupStatusEnum.restoring) - ListTile( - title: Text( - 'backup.restoring'.tr( - args: [(backupProgress * 100).round().toString()], - ), - style: Theme.of(context).textTheme.titleLarge, - ), - subtitle: LinearProgressIndicator( - backgroundColor: Colors.grey.withOpacity(0.2), - ), - ), - if (backupStatus == BackupStatusEnum.error) - ListTile( - leading: Icon( - Icons.error_outline, - color: Theme.of(context).colorScheme.error, - ), - title: Text( - 'backup.error_pending'.tr(), - style: Theme.of(context).textTheme.titleLarge, - ), - ), - ], - ), + ); + }, + leading: const Icon( + Icons.add_circle_outline_rounded, ), + title: Text( + 'backup.create_new'.tr(), + ), + ), const SizedBox(height: 16), // Card with a list of existing backups // Each list item has a date // When clicked, starts the restore action - if (backupStatus != BackupStatusEnum.initializing && - backupStatus != BackupStatusEnum.noKey) + if (isBackupInitialized) OutlinedCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -230,11 +195,151 @@ class _BackupDetailsPageState extends State ], ), ), - if (backupStatus == BackupStatusEnum.error) - Text( - backupError.toString(), - style: Theme.of(context).textTheme.bodyMedium, - ), + ], + ); + } +} + +class CreateBackupsModal extends StatefulWidget { + const CreateBackupsModal({ + super.key, + required this.services, + required this.scrollController, + }); + + final List services; + final ScrollController scrollController; + + @override + State createState() => _CreateBackupsModalState(); +} + +class _CreateBackupsModalState extends State { + // Store in state the selected services to backup + List selectedServices = []; + + // Select all services on modal open + @override + void initState() { + super.initState(); + final List busyServices = context + .read() + .state + .backupJobList + .where((final ServerJob job) => + job.status == JobStatusEnum.running || + job.status == JobStatusEnum.created) + .map((final ServerJob job) => job.typeId.split('.')[1]) + .toList(); + selectedServices.addAll(widget.services + .where((final Service service) => !busyServices.contains(service.id))); + } + + @override + Widget build(final BuildContext context) { + final List busyServices = context + .watch() + .state + .backupJobList + .where((final ServerJob job) => + job.status == JobStatusEnum.running || + job.status == JobStatusEnum.created) + .map((final ServerJob job) => job.typeId.split('.')[1]) + .toList(); + + return ListView( + controller: widget.scrollController, + padding: const EdgeInsets.all(16), + children: [ + const SizedBox(height: 16), + Text( + 'backup.create_new_select_headline'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + // Select all services tile + CheckboxListTile( + onChanged: (final bool? value) { + setState(() { + if (value ?? true) { + setState(() { + selectedServices.clear(); + selectedServices.addAll(widget.services.where( + (final service) => !busyServices.contains(service.id))); + }); + } else { + selectedServices.clear(); + } + }); + }, + title: Text( + 'backup.select_all'.tr(), + ), + secondary: const Icon( + Icons.checklist_outlined, + ), + value: selectedServices.length >= + widget.services.length - busyServices.length, + ), + const Divider( + height: 1.0, + ), + ...widget.services.map( + (final Service service) { + final bool busy = busyServices.contains(service.id); + return CheckboxListTile( + onChanged: !busy + ? (final bool? value) { + setState(() { + if (value ?? true) { + setState(() { + selectedServices.add(service); + }); + } else { + setState(() { + selectedServices.remove(service); + }); + } + }); + } + : null, + title: Text( + service.displayName, + ), + subtitle: Text( + busy ? 'backup.service_busy'.tr() : service.description, + ), + secondary: SvgPicture.string( + service.svgIcon, + height: 24, + width: 24, + colorFilter: ColorFilter.mode( + busy + ? Theme.of(context).colorScheme.outlineVariant + : Theme.of(context).colorScheme.onBackground, + BlendMode.srcIn, + ), + ), + value: selectedServices.contains(service), + ); + }, + ), + const SizedBox(height: 16), + // Create backup button + FilledButton( + onPressed: selectedServices.isEmpty + ? null + : () { + context + .read() + .createMultipleBackups(selectedServices); + Navigator.of(context).pop(); + }, + child: Text( + 'backup.create'.tr(), + ), + ), ], ); } diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index 25533a43..bdae6e07 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -97,16 +97,15 @@ class _ProvidersPageState extends State { ), const SizedBox(height: 16), // TODO: When backups are fixed, show this card - if (isBackupInitialized) - _Card( - state: isBackupInitialized - ? StateType.stable - : StateType.uninitialized, - icon: BrandIcons.save, - title: 'backup.card_title'.tr(), - subtitle: isBackupInitialized ? 'backup.card_subtitle'.tr() : '', - onTap: () => context.pushRoute(const BackupDetailsRoute()), - ), + _Card( + state: isBackupInitialized + ? StateType.stable + : StateType.uninitialized, + icon: BrandIcons.save, + title: 'backup.card_title'.tr(), + subtitle: isBackupInitialized ? 'backup.card_subtitle'.tr() : '', + onTap: () => context.pushRoute(const BackupDetailsRoute()), + ), ], ), );