feat(backups): Update the models

pull/228/head
Inex Code 2023-06-29 12:52:09 +03:00
parent 02cb4dbf8b
commit d2d8add10d
25 changed files with 669 additions and 589 deletions

View File

@ -21,6 +21,7 @@ class HiveConfig {
Hive.registerAdapter(UserTypeAdapter()); Hive.registerAdapter(UserTypeAdapter());
Hive.registerAdapter(DnsProviderTypeAdapter()); Hive.registerAdapter(DnsProviderTypeAdapter());
Hive.registerAdapter(ServerProviderTypeAdapter()); Hive.registerAdapter(ServerProviderTypeAdapter());
Hive.registerAdapter(BackupsProviderTypeAdapter());
await Hive.openBox(BNames.appSettingsBox); await Hive.openBox(BNames.appSettingsBox);

View File

@ -46,7 +46,7 @@ mutation ForceSnapshotsReload {
} }
} }
mutation StartBackup($serviceId: String = null) { mutation StartBackup($serviceId: String!) {
backup { backup {
startBackup(serviceId: $serviceId) { startBackup(serviceId: $serviceId) {
...basicMutationReturnFields ...basicMutationReturnFields

View File

@ -1,9 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'disk_volumes.graphql.dart';
import 'package:gql/ast.dart'; import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql; import 'package:graphql/client.dart' as graphql;
import 'package:selfprivacy/utils/scalars.dart'; import 'package:selfprivacy/utils/scalars.dart';
import 'schema.graphql.dart'; import 'schema.graphql.dart';
import 'services.graphql.dart';
class Fragment$genericBackupConfigReturn { class Fragment$genericBackupConfigReturn {
Fragment$genericBackupConfigReturn({ Fragment$genericBackupConfigReturn({
@ -2738,31 +2738,27 @@ class _CopyWithStubImpl$Mutation$ForceSnapshotsReload$backup$forceSnapshotsReloa
} }
class Variables$Mutation$StartBackup { class Variables$Mutation$StartBackup {
factory Variables$Mutation$StartBackup({String? serviceId}) => factory Variables$Mutation$StartBackup({required String serviceId}) =>
Variables$Mutation$StartBackup._({ Variables$Mutation$StartBackup._({
if (serviceId != null) r'serviceId': serviceId, r'serviceId': serviceId,
}); });
Variables$Mutation$StartBackup._(this._$data); Variables$Mutation$StartBackup._(this._$data);
factory Variables$Mutation$StartBackup.fromJson(Map<String, dynamic> data) { factory Variables$Mutation$StartBackup.fromJson(Map<String, dynamic> data) {
final result$data = <String, dynamic>{}; final result$data = <String, dynamic>{};
if (data.containsKey('serviceId')) { final l$serviceId = data['serviceId'];
final l$serviceId = data['serviceId']; result$data['serviceId'] = (l$serviceId as String);
result$data['serviceId'] = (l$serviceId as String?);
}
return Variables$Mutation$StartBackup._(result$data); return Variables$Mutation$StartBackup._(result$data);
} }
Map<String, dynamic> _$data; Map<String, dynamic> _$data;
String? get serviceId => (_$data['serviceId'] as String?); String get serviceId => (_$data['serviceId'] as String);
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final result$data = <String, dynamic>{}; final result$data = <String, dynamic>{};
if (_$data.containsKey('serviceId')) { final l$serviceId = serviceId;
final l$serviceId = serviceId; result$data['serviceId'] = l$serviceId;
result$data['serviceId'] = l$serviceId;
}
return result$data; return result$data;
} }
@ -2782,10 +2778,6 @@ class Variables$Mutation$StartBackup {
} }
final l$serviceId = serviceId; final l$serviceId = serviceId;
final lOther$serviceId = other.serviceId; final lOther$serviceId = other.serviceId;
if (_$data.containsKey('serviceId') !=
other._$data.containsKey('serviceId')) {
return false;
}
if (l$serviceId != lOther$serviceId) { if (l$serviceId != lOther$serviceId) {
return false; return false;
} }
@ -2795,8 +2787,7 @@ class Variables$Mutation$StartBackup {
@override @override
int get hashCode { int get hashCode {
final l$serviceId = serviceId; final l$serviceId = serviceId;
return Object.hashAll( return Object.hashAll([l$serviceId]);
[_$data.containsKey('serviceId') ? l$serviceId : const {}]);
} }
} }
@ -2828,7 +2819,8 @@ class _CopyWithImpl$Variables$Mutation$StartBackup<TRes>
TRes call({Object? serviceId = _undefined}) => TRes call({Object? serviceId = _undefined}) =>
_then(Variables$Mutation$StartBackup._({ _then(Variables$Mutation$StartBackup._({
..._instance._$data, ..._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')), variable: VariableNode(name: NameNode(value: 'serviceId')),
type: NamedTypeNode( type: NamedTypeNode(
name: NameNode(value: 'String'), name: NameNode(value: 'String'),
isNonNull: false, isNonNull: true,
), ),
defaultValue: DefaultValueNode(value: NullValueNode()), defaultValue: DefaultValueNode(value: null),
directives: [], directives: [],
) )
], ],
@ -3052,7 +3044,7 @@ class Options$Mutation$StartBackup
extends graphql.MutationOptions<Mutation$StartBackup> { extends graphql.MutationOptions<Mutation$StartBackup> {
Options$Mutation$StartBackup({ Options$Mutation$StartBackup({
String? operationName, String? operationName,
Variables$Mutation$StartBackup? variables, required Variables$Mutation$StartBackup variables,
graphql.FetchPolicy? fetchPolicy, graphql.FetchPolicy? fetchPolicy,
graphql.ErrorPolicy? errorPolicy, graphql.ErrorPolicy? errorPolicy,
graphql.CacheRereadPolicy? cacheRereadPolicy, graphql.CacheRereadPolicy? cacheRereadPolicy,
@ -3064,7 +3056,7 @@ class Options$Mutation$StartBackup
graphql.OnError? onError, graphql.OnError? onError,
}) : onCompletedWithParsed = onCompleted, }) : onCompletedWithParsed = onCompleted,
super( super(
variables: variables?.toJson() ?? {}, variables: variables.toJson(),
operationName: operationName, operationName: operationName,
fetchPolicy: fetchPolicy, fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy, errorPolicy: errorPolicy,
@ -3098,7 +3090,7 @@ class WatchOptions$Mutation$StartBackup
extends graphql.WatchQueryOptions<Mutation$StartBackup> { extends graphql.WatchQueryOptions<Mutation$StartBackup> {
WatchOptions$Mutation$StartBackup({ WatchOptions$Mutation$StartBackup({
String? operationName, String? operationName,
Variables$Mutation$StartBackup? variables, required Variables$Mutation$StartBackup variables,
graphql.FetchPolicy? fetchPolicy, graphql.FetchPolicy? fetchPolicy,
graphql.ErrorPolicy? errorPolicy, graphql.ErrorPolicy? errorPolicy,
graphql.CacheRereadPolicy? cacheRereadPolicy, graphql.CacheRereadPolicy? cacheRereadPolicy,
@ -3110,7 +3102,7 @@ class WatchOptions$Mutation$StartBackup
bool carryForwardDataOnException = true, bool carryForwardDataOnException = true,
bool fetchResults = false, bool fetchResults = false,
}) : super( }) : super(
variables: variables?.toJson() ?? {}, variables: variables.toJson(),
operationName: operationName, operationName: operationName,
fetchPolicy: fetchPolicy, fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy, errorPolicy: errorPolicy,
@ -3128,11 +3120,11 @@ class WatchOptions$Mutation$StartBackup
extension ClientExtension$Mutation$StartBackup on graphql.GraphQLClient { extension ClientExtension$Mutation$StartBackup on graphql.GraphQLClient {
Future<graphql.QueryResult<Mutation$StartBackup>> mutate$StartBackup( Future<graphql.QueryResult<Mutation$StartBackup>> mutate$StartBackup(
[Options$Mutation$StartBackup? options]) async => Options$Mutation$StartBackup options) async =>
await this.mutate(options ?? Options$Mutation$StartBackup()); await this.mutate(options);
graphql.ObservableQuery<Mutation$StartBackup> watchMutation$StartBackup( graphql.ObservableQuery<Mutation$StartBackup> watchMutation$StartBackup(
[WatchOptions$Mutation$StartBackup? options]) => WatchOptions$Mutation$StartBackup options) =>
this.watchMutation(options ?? WatchOptions$Mutation$StartBackup()); this.watchMutation(options);
} }
class Mutation$StartBackup$backup { class Mutation$StartBackup$backup {

View File

@ -3,7 +3,6 @@ import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql; import 'package:graphql/client.dart' as graphql;
import 'package:selfprivacy/utils/scalars.dart'; import 'package:selfprivacy/utils/scalars.dart';
import 'schema.graphql.dart'; import 'schema.graphql.dart';
import 'services.graphql.dart';
class Fragment$basicMutationReturnFields { class Fragment$basicMutationReturnFields {
Fragment$basicMutationReturnFields({ Fragment$basicMutationReturnFields({

View File

@ -19,6 +19,7 @@ type ApiDevice {
type ApiJob { type ApiJob {
uid: String! uid: String!
typeId: String!
name: String! name: String!
description: String! description: String!
status: String! status: String!

View File

@ -23,6 +23,7 @@ query GetApiJobs {
status status
statusText statusText
uid uid
typeId
updatedAt updatedAt
} }
} }

View File

@ -1,9 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'disk_volumes.graphql.dart';
import 'package:gql/ast.dart'; import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql; import 'package:graphql/client.dart' as graphql;
import 'package:selfprivacy/utils/scalars.dart'; import 'package:selfprivacy/utils/scalars.dart';
import 'schema.graphql.dart'; import 'schema.graphql.dart';
import 'services.graphql.dart';
class Fragment$basicMutationReturnFields { class Fragment$basicMutationReturnFields {
Fragment$basicMutationReturnFields({ Fragment$basicMutationReturnFields({
@ -2914,6 +2914,13 @@ const documentNodeQueryGetApiJobs = DocumentNode(definitions: [
directives: [], directives: [],
selectionSet: null, selectionSet: null,
), ),
FieldNode(
name: NameNode(value: 'typeId'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
FieldNode( FieldNode(
name: NameNode(value: 'updatedAt'), name: NameNode(value: 'updatedAt'),
alias: null, alias: null,
@ -3229,6 +3236,7 @@ class Query$GetApiJobs$jobs$getJobs {
required this.status, required this.status,
this.statusText, this.statusText,
required this.uid, required this.uid,
required this.typeId,
required this.updatedAt, required this.updatedAt,
this.$__typename = 'ApiJob', this.$__typename = 'ApiJob',
}); });
@ -3244,6 +3252,7 @@ class Query$GetApiJobs$jobs$getJobs {
final l$status = json['status']; final l$status = json['status'];
final l$statusText = json['statusText']; final l$statusText = json['statusText'];
final l$uid = json['uid']; final l$uid = json['uid'];
final l$typeId = json['typeId'];
final l$updatedAt = json['updatedAt']; final l$updatedAt = json['updatedAt'];
final l$$__typename = json['__typename']; final l$$__typename = json['__typename'];
return Query$GetApiJobs$jobs$getJobs( return Query$GetApiJobs$jobs$getJobs(
@ -3257,6 +3266,7 @@ class Query$GetApiJobs$jobs$getJobs {
status: (l$status as String), status: (l$status as String),
statusText: (l$statusText as String?), statusText: (l$statusText as String?),
uid: (l$uid as String), uid: (l$uid as String),
typeId: (l$typeId as String),
updatedAt: dateTimeFromJson(l$updatedAt), updatedAt: dateTimeFromJson(l$updatedAt),
$__typename: (l$$__typename as String), $__typename: (l$$__typename as String),
); );
@ -3282,6 +3292,8 @@ class Query$GetApiJobs$jobs$getJobs {
final String uid; final String uid;
final String typeId;
final DateTime updatedAt; final DateTime updatedAt;
final String $__typename; final String $__typename;
@ -3309,6 +3321,8 @@ class Query$GetApiJobs$jobs$getJobs {
_resultData['statusText'] = l$statusText; _resultData['statusText'] = l$statusText;
final l$uid = uid; final l$uid = uid;
_resultData['uid'] = l$uid; _resultData['uid'] = l$uid;
final l$typeId = typeId;
_resultData['typeId'] = l$typeId;
final l$updatedAt = updatedAt; final l$updatedAt = updatedAt;
_resultData['updatedAt'] = dateTimeToJson(l$updatedAt); _resultData['updatedAt'] = dateTimeToJson(l$updatedAt);
final l$$__typename = $__typename; final l$$__typename = $__typename;
@ -3328,6 +3342,7 @@ class Query$GetApiJobs$jobs$getJobs {
final l$status = status; final l$status = status;
final l$statusText = statusText; final l$statusText = statusText;
final l$uid = uid; final l$uid = uid;
final l$typeId = typeId;
final l$updatedAt = updatedAt; final l$updatedAt = updatedAt;
final l$$__typename = $__typename; final l$$__typename = $__typename;
return Object.hashAll([ return Object.hashAll([
@ -3341,6 +3356,7 @@ class Query$GetApiJobs$jobs$getJobs {
l$status, l$status,
l$statusText, l$statusText,
l$uid, l$uid,
l$typeId,
l$updatedAt, l$updatedAt,
l$$__typename, l$$__typename,
]); ]);
@ -3405,6 +3421,11 @@ class Query$GetApiJobs$jobs$getJobs {
if (l$uid != lOther$uid) { if (l$uid != lOther$uid) {
return false; return false;
} }
final l$typeId = typeId;
final lOther$typeId = other.typeId;
if (l$typeId != lOther$typeId) {
return false;
}
final l$updatedAt = updatedAt; final l$updatedAt = updatedAt;
final lOther$updatedAt = other.updatedAt; final lOther$updatedAt = other.updatedAt;
if (l$updatedAt != lOther$updatedAt) { if (l$updatedAt != lOther$updatedAt) {
@ -3448,6 +3469,7 @@ abstract class CopyWith$Query$GetApiJobs$jobs$getJobs<TRes> {
String? status, String? status,
String? statusText, String? statusText,
String? uid, String? uid,
String? typeId,
DateTime? updatedAt, DateTime? updatedAt,
String? $__typename, String? $__typename,
}); });
@ -3477,6 +3499,7 @@ class _CopyWithImpl$Query$GetApiJobs$jobs$getJobs<TRes>
Object? status = _undefined, Object? status = _undefined,
Object? statusText = _undefined, Object? statusText = _undefined,
Object? uid = _undefined, Object? uid = _undefined,
Object? typeId = _undefined,
Object? updatedAt = _undefined, Object? updatedAt = _undefined,
Object? $__typename = _undefined, Object? $__typename = _undefined,
}) => }) =>
@ -3504,6 +3527,9 @@ class _CopyWithImpl$Query$GetApiJobs$jobs$getJobs<TRes>
? _instance.statusText ? _instance.statusText
: (statusText as String?), : (statusText as String?),
uid: uid == _undefined || uid == null ? _instance.uid : (uid 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 updatedAt: updatedAt == _undefined || updatedAt == null
? _instance.updatedAt ? _instance.updatedAt
: (updatedAt as DateTime), : (updatedAt as DateTime),
@ -3530,6 +3556,7 @@ class _CopyWithStubImpl$Query$GetApiJobs$jobs$getJobs<TRes>
String? status, String? status,
String? statusText, String? statusText,
String? uid, String? uid,
String? typeId,
DateTime? updatedAt, DateTime? updatedAt,
String? $__typename, String? $__typename,
}) => }) =>

View File

@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'disk_volumes.graphql.dart';
import 'package:gql/ast.dart'; import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql; import 'package:graphql/client.dart' as graphql;
import 'schema.graphql.dart'; import 'schema.graphql.dart';
import 'services.graphql.dart';
class Fragment$basicMutationReturnFields { class Fragment$basicMutationReturnFields {
Fragment$basicMutationReturnFields({ Fragment$basicMutationReturnFields({

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'disk_volumes.graphql.dart';
import 'package:gql/ast.dart'; import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql; import 'package:graphql/client.dart' as graphql;
import 'package:selfprivacy/utils/scalars.dart'; import 'package:selfprivacy/utils/scalars.dart';

View File

@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'disk_volumes.graphql.dart';
import 'package:gql/ast.dart'; import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql; import 'package:graphql/client.dart' as graphql;
import 'schema.graphql.dart'; import 'schema.graphql.dart';
import 'services.graphql.dart';
class Fragment$basicMutationReturnFields { class Fragment$basicMutationReturnFields {
Fragment$basicMutationReturnFields({ Fragment$basicMutationReturnFields({

View File

@ -0,0 +1,207 @@
part of 'server_api.dart';
mixin BackupsApi on GraphQLApiMap {
Future<List<Backup>> getBackups() async {
List<Backup> backups;
QueryResult<Query$AllBackupSnapshots> 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<Backup> 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<BackupConfiguration?> getBackupsConfiguration() async {
BackupConfiguration? backupConfiguration;
QueryResult<Query$BackupConfiguration> 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<GenericResult> 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<GenericResult> startBackup(final String serviceId) async {
QueryResult<Mutation$StartBackup> 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<GenericResult> setAutobackupPeriod({final int? period}) async {
QueryResult<Mutation$SetAutobackupPeriod> 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<GenericResult> 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<GenericResult> initializeRepository(
final InitializeRepositoryInput input,
) async {
QueryResult<Mutation$InitializeRepository> 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;
}
}

View File

@ -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/hive/user.dart';
import 'package:selfprivacy/logic/models/initialize_repository_input.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/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/device_token.dart';
import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart';
import 'package:selfprivacy/logic/models/json/recovery_token_status.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 'services_api.dart';
part 'users_api.dart'; part 'users_api.dart';
part 'volume_api.dart'; part 'volume_api.dart';
part 'backups_api.dart';
class ServerApi extends GraphQLApiMap class ServerApi extends GraphQLApiMap
with VolumeApi, JobsApi, ServerActionsApi, ServicesApi, UsersApi { with VolumeApi, JobsApi, ServerActionsApi, ServicesApi, UsersApi, BackupsApi {
ServerApi({ ServerApi({
this.hasLogger = false, this.hasLogger = false,
this.isWithToken = true, this.isWithToken = true,
@ -512,202 +513,4 @@ class ServerApi extends GraphQLApiMap
return token; return token;
} }
Future<GenericResult<List<Backup>>> getBackups() async {
GenericResult<List<Backup>> backups;
QueryResult<Query$AllBackupSnapshots> response;
try {
final GraphQLClient client = await getClient();
response = await client.query$AllBackupSnapshots();
if (response.hasException) {
final message = response.exception.toString();
print(message);
backups = GenericResult<List<Backup>>(
success: false,
data: [],
message: message,
);
}
final List<Backup> parsed = response.parsedData!.backup.allSnapshots
.map(
(
final Query$AllBackupSnapshots$backup$allSnapshots snapshot,
) =>
Backup.fromGraphQL(snapshot),
)
.toList();
backups = GenericResult<List<Backup>>(
success: true,
data: parsed,
);
} catch (e) {
print(e);
backups = GenericResult<List<Backup>>(
success: false,
data: [],
message: e.toString(),
);
}
return backups;
}
Future<GenericResult> 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<GenericResult> startBackup({final String? serviceId}) async {
QueryResult<Mutation$StartBackup> 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<GenericResult> setAutobackupPeriod({final int? period}) async {
QueryResult<Mutation$SetAutobackupPeriod> 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<BackupStatus> getBackupStatus() async => BackupStatus(
progress: 0.0,
status: BackupStatusEnum.error,
errorMessage: null,
);
Future<GenericResult> 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<GenericResult> initializeRepository(
final InitializeRepositoryInput input,
) async {
QueryResult<Mutation$InitializeRepository> 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;
}
} }

View File

@ -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/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/cubit/app_config_dependent/authentication_dependend_cubit.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.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'; part 'backups_state.dart';
@ -24,107 +27,83 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
Future<void> load() async { Future<void> load() async {
if (serverInstallationCubit.state is ServerInstallationFinished) { if (serverInstallationCubit.state is ServerInstallationFinished) {
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket; final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
if (bucket == null) { final BackupConfiguration? backupConfig =
emit( await api.getBackupsConfiguration();
const BackupsState( final List<Backup> backups = await api.getBackups();
isInitialized: false, emit(
preventActions: false, state.copyWith(
refreshing: false, backblazeBucket: bucket,
), isInitialized: backupConfig?.isInitialized,
); autobackupPeriod: backupConfig?.autobackupPeriod,
} else { backups: backups,
final BackupStatus status = await api.getBackupStatus(); preventActions: false,
switch (status.status) { refreshing: false,
case BackupStatusEnum.noKey: ),
case BackupStatusEnum.notInitialized: );
emit( print(state);
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));
}
} }
} }
Future<void> createBucket() async { Future<void> initializeBackups() async {
emit(state.copyWith(preventActions: true)); emit(state.copyWith(preventActions: true));
final String domain = serverInstallationCubit.state.serverDomain!.domainName final String? encryptionKey =
.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-'); (await api.getBackupsConfiguration())?.encryptionKey;
final int serverId = serverInstallationCubit.state.serverDetails!.id; if (encryptionKey == null) {
String bucketName = 'selfprivacy-$domain-$serverId'; getIt<NavigationService>()
// If bucket name is too long, shorten it .showSnackBar("Couldn't get encryption key from your server.");
if (bucketName.length > 49) { emit(state.copyWith(preventActions: false));
bucketName = bucketName.substring(0, 49); return;
} }
final String bucketId = await backblaze.createBucket(bucketName);
final BackblazeApplicationKey key = await backblaze.createKey(bucketId); final BackblazeBucket bucket;
final BackblazeBucket bucket = BackblazeBucket(
bucketId: bucketId, if (state.backblazeBucket == null) {
bucketName: bucketName, final String domain = serverInstallationCubit
applicationKey: key.applicationKey, .state.serverDomain!.domainName
applicationKeyId: key.applicationKeyId, .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<ApiConfigModel>().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) {
await getIt<ApiConfigModel>().storeBackblazeBucket(bucket); getIt<NavigationService>()
//await api.uploadBackblazeConfig(bucket); .showSnackBar(result.message ?? 'Unknown error');
emit(state.copyWith(preventActions: false));
return;
}
await updateBackups(); await updateBackups();
getIt<NavigationService>().showSnackBar(
'Backups repository is now initializing. It may take a while.');
emit(state.copyWith(isInitialized: true, preventActions: false)); emit(state.copyWith(preventActions: false));
} }
Future<void> reuploadKey() async { Future<void> reuploadKey() async {
@ -132,42 +111,47 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket; final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
if (bucket == null) { if (bucket == null) {
emit(state.copyWith(isInitialized: false)); emit(state.copyWith(isInitialized: false));
print('bucket is null');
} else { } else {
//await api.uploadBackblazeConfig(bucket); print('bucket is not null');
emit(state.copyWith(isInitialized: true, preventActions: false)); final GenericResult result = await api.initializeRepository(
getIt<NavigationService>().showSnackBar('backup.reuploaded_key'); 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<NavigationService>()
.showSnackBar(result.message ?? 'Unknown error');
emit(state.copyWith(preventActions: false));
return;
} else {
emit(state.copyWith(preventActions: false));
getIt<NavigationService>().showSnackBar('backup.reuploaded_key');
await updateBackups();
}
} }
} }
Duration refreshTimeFromState(final BackupStatusEnum status) { @Deprecated("we don't have states")
switch (status) { Duration refreshTimeFromState() => const Duration(seconds: 60);
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);
}
}
Future<void> updateBackups({final bool useTimer = false}) async { Future<void> updateBackups({final bool useTimer = false}) async {
emit(state.copyWith(refreshing: true)); emit(state.copyWith(refreshing: true));
final result = await api.getBackups(); final backups = await api.getBackups();
if (!result.success || result.data.isEmpty) { final backupConfig = await api.getBackupsConfiguration();
return;
}
final List<Backup> backups = result.data;
final BackupStatus status = await api.getBackupStatus();
emit( emit(
state.copyWith( state.copyWith(
backups: backups, backups: backups,
progress: status.progress, refreshTimer: refreshTimeFromState(),
status: status.status,
error: status.errorMessage,
refreshTimer: refreshTimeFromState(status.status),
refreshing: false, refreshing: false,
isInitialized: backupConfig?.isInitialized ?? false,
autobackupPeriod: backupConfig?.autobackupPeriod,
), ),
); );
if (useTimer) { if (useTimer) {
@ -182,9 +166,18 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
emit(state.copyWith(preventActions: false)); emit(state.copyWith(preventActions: false));
} }
Future<void> createBackup() async { Future<void> createMultipleBackups(final List<Service> services) async {
emit(state.copyWith(preventActions: true)); 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<void> createBackup(final String serviceId) async {
emit(state.copyWith(preventActions: true));
await api.startBackup(serviceId);
await updateBackups(); await updateBackups();
emit(state.copyWith(preventActions: false)); emit(state.copyWith(preventActions: false));
} }

View File

@ -4,31 +4,26 @@ class BackupsState extends ServerInstallationDependendState {
const BackupsState({ const BackupsState({
this.isInitialized = false, this.isInitialized = false,
this.backups = const [], this.backups = const [],
this.progress = 0.0,
this.status = BackupStatusEnum.noKey,
this.preventActions = true, this.preventActions = true,
this.error = '',
this.refreshTimer = const Duration(seconds: 60), this.refreshTimer = const Duration(seconds: 60),
this.refreshing = true, this.refreshing = true,
this.autobackupPeriod,
this.backblazeBucket,
}); });
final bool isInitialized; final bool isInitialized;
final List<Backup> backups; final List<Backup> backups;
final double progress;
final BackupStatusEnum status;
final bool preventActions; final bool preventActions;
final String error;
final Duration refreshTimer; final Duration refreshTimer;
final bool refreshing; final bool refreshing;
final Duration? autobackupPeriod;
final BackblazeBucket? backblazeBucket;
@override @override
List<Object> get props => [ List<Object> get props => [
isInitialized, isInitialized,
backups, backups,
progress,
preventActions, preventActions,
status,
error,
refreshTimer, refreshTimer,
refreshing refreshing
]; ];
@ -36,21 +31,19 @@ class BackupsState extends ServerInstallationDependendState {
BackupsState copyWith({ BackupsState copyWith({
final bool? isInitialized, final bool? isInitialized,
final List<Backup>? backups, final List<Backup>? backups,
final double? progress,
final BackupStatusEnum? status,
final bool? preventActions, final bool? preventActions,
final String? error,
final Duration? refreshTimer, final Duration? refreshTimer,
final bool? refreshing, final bool? refreshing,
final Duration? autobackupPeriod,
final BackblazeBucket? backblazeBucket,
}) => }) =>
BackupsState( BackupsState(
isInitialized: isInitialized ?? this.isInitialized, isInitialized: isInitialized ?? this.isInitialized,
backups: backups ?? this.backups, backups: backups ?? this.backups,
progress: progress ?? this.progress,
status: status ?? this.status,
preventActions: preventActions ?? this.preventActions, preventActions: preventActions ?? this.preventActions,
error: error ?? this.error,
refreshTimer: refreshTimer ?? this.refreshTimer, refreshTimer: refreshTimer ?? this.refreshTimer,
refreshing: refreshing ?? this.refreshing, refreshing: refreshing ?? this.refreshing,
autobackupPeriod: autobackupPeriod ?? this.autobackupPeriod,
backblazeBucket: backblazeBucket ?? this.backblazeBucket,
); );
} }

View File

@ -197,7 +197,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
final BackupsCredential backblazeCredential = BackupsCredential( final BackupsCredential backblazeCredential = BackupsCredential(
keyId: keyId, keyId: keyId,
applicationKey: applicationKey, applicationKey: applicationKey,
provider: BackupsProvider.backblaze, provider: BackupsProviderType.backblaze,
); );
await repository.saveBackblazeKey(backblazeCredential); await repository.saveBackblazeKey(backblazeCredential);
if (state is ServerInstallationRecovery) { if (state is ServerInstallationRecovery) {
@ -699,7 +699,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
provider: dnsProviderType, provider: dnsProviderType,
), ),
); );
await repository.setDnsApiToken(token); // await repository.setDnsApiToken(token);
emit( emit(
dataState.copyWith( dataState.copyWith(
serverDomain: ServerDomain( serverDomain: ServerDomain(

View File

@ -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;
}

View File

@ -9,6 +9,7 @@ class BackblazeBucket {
required this.bucketName, required this.bucketName,
required this.applicationKeyId, required this.applicationKeyId,
required this.applicationKey, required this.applicationKey,
required this.encryptionKey,
}); });
@HiveField(0) @HiveField(0)
@ -23,6 +24,9 @@ class BackblazeBucket {
@HiveField(3) @HiveField(3)
final String bucketName; final String bucketName;
@HiveField(4)
final String encryptionKey;
@override @override
String toString() => bucketName; String toString() => bucketName;
} }

View File

@ -21,13 +21,14 @@ class BackblazeBucketAdapter extends TypeAdapter<BackblazeBucket> {
bucketName: fields[3] as String, bucketName: fields[3] as String,
applicationKeyId: fields[1] as String, applicationKeyId: fields[1] as String,
applicationKey: fields[2] as String, applicationKey: fields[2] as String,
encryptionKey: fields[4] as String,
); );
} }
@override @override
void write(BinaryWriter writer, BackblazeBucket obj) { void write(BinaryWriter writer, BackblazeBucket obj) {
writer writer
..writeByte(4) ..writeByte(5)
..writeByte(0) ..writeByte(0)
..write(obj.bucketId) ..write(obj.bucketId)
..writeByte(1) ..writeByte(1)
@ -35,7 +36,9 @@ class BackblazeBucketAdapter extends TypeAdapter<BackblazeBucket> {
..writeByte(2) ..writeByte(2)
..write(obj.applicationKey) ..write(obj.applicationKey)
..writeByte(3) ..writeByte(3)
..write(obj.bucketName); ..write(obj.bucketName)
..writeByte(4)
..write(obj.encryptionKey);
} }
@override @override

View File

@ -19,8 +19,8 @@ class BackupsCredential {
@HiveField(1) @HiveField(1)
final String applicationKey; final String applicationKey;
@HiveField(2, defaultValue: BackupsProvider.backblaze) @HiveField(2, defaultValue: BackupsProviderType.backblaze)
final BackupsProvider provider; final BackupsProviderType provider;
String get encodedApiKey => encodedBackblazeKey(keyId, applicationKey); String get encodedApiKey => encodedBackblazeKey(keyId, applicationKey);
@ -35,7 +35,7 @@ String encodedBackblazeKey(final String? keyId, final String? applicationKey) {
} }
@HiveType(typeId: 103) @HiveType(typeId: 103)
enum BackupsProvider { enum BackupsProviderType {
@HiveField(0) @HiveField(0)
none, none,
@HiveField(1) @HiveField(1)
@ -45,7 +45,7 @@ enum BackupsProvider {
@HiveField(3) @HiveField(3)
backblaze; backblaze;
factory BackupsProvider.fromGraphQL(final Enum$BackupProvider provider) => factory BackupsProviderType.fromGraphQL(final Enum$BackupProvider provider) =>
switch (provider) { switch (provider) {
Enum$BackupProvider.NONE => none, Enum$BackupProvider.NONE => none,
Enum$BackupProvider.MEMORY => memory, Enum$BackupProvider.MEMORY => memory,

View File

@ -20,8 +20,8 @@ class BackupsCredentialAdapter extends TypeAdapter<BackupsCredential> {
keyId: fields[0] as String, keyId: fields[0] as String,
applicationKey: fields[1] as String, applicationKey: fields[1] as String,
provider: fields[2] == null provider: fields[2] == null
? BackupsProvider.backblaze ? BackupsProviderType.backblaze
: fields[2] as BackupsProvider, : fields[2] as BackupsProviderType,
); );
} }
@ -48,39 +48,39 @@ class BackupsCredentialAdapter extends TypeAdapter<BackupsCredential> {
typeId == other.typeId; typeId == other.typeId;
} }
class BackupsProviderAdapter extends TypeAdapter<BackupsProvider> { class BackupsProviderTypeAdapter extends TypeAdapter<BackupsProviderType> {
@override @override
final int typeId = 103; final int typeId = 103;
@override @override
BackupsProvider read(BinaryReader reader) { BackupsProviderType read(BinaryReader reader) {
switch (reader.readByte()) { switch (reader.readByte()) {
case 0: case 0:
return BackupsProvider.none; return BackupsProviderType.none;
case 1: case 1:
return BackupsProvider.memory; return BackupsProviderType.memory;
case 2: case 2:
return BackupsProvider.file; return BackupsProviderType.file;
case 3: case 3:
return BackupsProvider.backblaze; return BackupsProviderType.backblaze;
default: default:
return BackupsProvider.none; return BackupsProviderType.none;
} }
} }
@override @override
void write(BinaryWriter writer, BackupsProvider obj) { void write(BinaryWriter writer, BackupsProviderType obj) {
switch (obj) { switch (obj) {
case BackupsProvider.none: case BackupsProviderType.none:
writer.writeByte(0); writer.writeByte(0);
break; break;
case BackupsProvider.memory: case BackupsProviderType.memory:
writer.writeByte(1); writer.writeByte(1);
break; break;
case BackupsProvider.file: case BackupsProviderType.file:
writer.writeByte(2); writer.writeByte(2);
break; break;
case BackupsProvider.backblaze: case BackupsProviderType.backblaze:
writer.writeByte(3); writer.writeByte(3);
break; break;
} }
@ -92,7 +92,7 @@ class BackupsProviderAdapter extends TypeAdapter<BackupsProvider> {
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
other is BackupsProviderAdapter && other is BackupsProviderTypeAdapter &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
typeId == other.typeId; typeId == other.typeId;
} }

View File

@ -8,7 +8,7 @@ class InitializeRepositoryInput {
required this.login, required this.login,
required this.password, required this.password,
}); });
final BackupsProvider provider; final BackupsProviderType provider;
final String locationId; final String locationId;
final String locationName; final String locationName;
final String login; final String login;

View File

@ -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<String, dynamic> 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<String, dynamic> 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;
}

View File

@ -1,44 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'backup.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Backup _$BackupFromJson(Map<String, dynamic> 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<String, dynamic> _$BackupToJson(Backup instance) => <String, dynamic>{
'time': instance.time.toIso8601String(),
'short_id': instance.id,
'serviceId': instance.serviceId,
'fallbackServiceName': instance.fallbackServiceName,
};
BackupStatus _$BackupStatusFromJson(Map<String, dynamic> json) => BackupStatus(
status: $enumDecode(_$BackupStatusEnumEnumMap, json['status']),
progress: (json['progress'] as num).toDouble(),
errorMessage: json['error_message'] as String?,
);
Map<String, dynamic> _$BackupStatusToJson(BackupStatus instance) =>
<String, dynamic>{
'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',
};

View File

@ -1,9 +1,14 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/cubit/backups/backups_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/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
import 'package:selfprivacy/ui/components/cards/outlined_card.dart'; import 'package:selfprivacy/ui/components/cards/outlined_card.dart';
@ -29,19 +34,15 @@ class _BackupDetailsPageState extends State<BackupDetailsPage>
is ServerInstallationFinished; is ServerInstallationFinished;
final bool isBackupInitialized = final bool isBackupInitialized =
context.watch<BackupsCubit>().state.isInitialized; context.watch<BackupsCubit>().state.isInitialized;
final BackupStatusEnum backupStatus =
context.watch<BackupsCubit>().state.status;
final StateType providerState = isReady && isBackupInitialized final StateType providerState = isReady && isBackupInitialized
? (backupStatus == BackupStatusEnum.error ? StateType.stable
? StateType.warning
: StateType.stable)
: StateType.uninitialized; : StateType.uninitialized;
final bool preventActions = final bool preventActions =
context.watch<BackupsCubit>().state.preventActions; context.watch<BackupsCubit>().state.preventActions;
final double backupProgress = context.watch<BackupsCubit>().state.progress;
final String backupError = context.watch<BackupsCubit>().state.error;
final List<Backup> backups = context.watch<BackupsCubit>().state.backups; final List<Backup> backups = context.watch<BackupsCubit>().state.backups;
final bool refreshing = context.watch<BackupsCubit>().state.refreshing; final bool refreshing = context.watch<BackupsCubit>().state.refreshing;
final List<Service> services =
context.watch<ServicesCubit>().state.services;
return BrandHeroScreen( return BrandHeroScreen(
heroIcon: BrandIcons.save, heroIcon: BrandIcons.save,
@ -53,81 +54,45 @@ class _BackupDetailsPageState extends State<BackupDetailsPage>
onPressed: preventActions onPressed: preventActions
? null ? null
: () async { : () async {
await context.read<BackupsCubit>().createBucket(); await context.read<BackupsCubit>().initializeBackups();
}, },
text: 'backup.initialize'.tr(), text: 'backup.initialize'.tr(),
), ),
if (backupStatus == BackupStatusEnum.initializing) ListTile(
Text( onTap: preventActions
'backup.waiting_for_rebuild'.tr(), ? null
style: Theme.of(context).textTheme.bodyMedium, : () {
), // await context.read<BackupsCubit>().createBackup();
if (backupStatus != BackupStatusEnum.initializing && showModalBottomSheet(
backupStatus != BackupStatusEnum.noKey) useRootNavigator: true,
OutlinedCard( context: context,
child: Column( isScrollControlled: true,
crossAxisAlignment: CrossAxisAlignment.start, builder: (final BuildContext context) =>
children: [ DraggableScrollableSheet(
if (backupStatus == BackupStatusEnum.initialized) expand: false,
ListTile( maxChildSize: 0.9,
onTap: preventActions minChildSize: 0.4,
? null initialChildSize: 0.6,
: () async { builder: (context, scrollController) =>
await context.read<BackupsCubit>().createBackup(); CreateBackupsModal(
}, services: services,
leading: const Icon( scrollController: scrollController,
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()],
), ),
style: Theme.of(context).textTheme.titleLarge,
), ),
subtitle: LinearProgressIndicator( );
value: backupProgress, },
backgroundColor: Colors.grey.withOpacity(0.2), leading: const Icon(
), Icons.add_circle_outline_rounded,
),
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,
),
),
],
),
), ),
title: Text(
'backup.create_new'.tr(),
),
),
const SizedBox(height: 16), const SizedBox(height: 16),
// Card with a list of existing backups // Card with a list of existing backups
// Each list item has a date // Each list item has a date
// When clicked, starts the restore action // When clicked, starts the restore action
if (backupStatus != BackupStatusEnum.initializing && if (isBackupInitialized)
backupStatus != BackupStatusEnum.noKey)
OutlinedCard( OutlinedCard(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -230,11 +195,151 @@ class _BackupDetailsPageState extends State<BackupDetailsPage>
], ],
), ),
), ),
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<Service> services;
final ScrollController scrollController;
@override
State<CreateBackupsModal> createState() => _CreateBackupsModalState();
}
class _CreateBackupsModalState extends State<CreateBackupsModal> {
// Store in state the selected services to backup
List<Service> selectedServices = [];
// Select all services on modal open
@override
void initState() {
super.initState();
final List<String> busyServices = context
.read<ServerJobsCubit>()
.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<String> busyServices = context
.watch<ServerJobsCubit>()
.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<BackupsCubit>()
.createMultipleBackups(selectedServices);
Navigator.of(context).pop();
},
child: Text(
'backup.create'.tr(),
),
),
], ],
); );
} }

View File

@ -97,16 +97,15 @@ class _ProvidersPageState extends State<ProvidersPage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// TODO: When backups are fixed, show this card // TODO: When backups are fixed, show this card
if (isBackupInitialized) _Card(
_Card( state: isBackupInitialized
state: isBackupInitialized ? StateType.stable
? StateType.stable : StateType.uninitialized,
: StateType.uninitialized, icon: BrandIcons.save,
icon: BrandIcons.save, title: 'backup.card_title'.tr(),
title: 'backup.card_title'.tr(), subtitle: isBackupInitialized ? 'backup.card_subtitle'.tr() : '',
subtitle: isBackupInitialized ? 'backup.card_subtitle'.tr() : '', onTap: () => context.pushRoute(const BackupDetailsRoute()),
onTap: () => context.pushRoute(const BackupDetailsRoute()), ),
),
], ],
), ),
); );