refactor: Introduce the API connection repository #440
|
@ -36,7 +36,8 @@
|
|||
"continue": "Continue",
|
||||
"alert": "Alert",
|
||||
"copied_to_clipboard": "Copied to clipboard!",
|
||||
"please_connect": "Please connect your server, domain and DNS provider to dive in!"
|
||||
"please_connect": "Please connect your server, domain and DNS provider to dive in!",
|
||||
"network_error": "Network error"
|
||||
},
|
||||
"more_page": {
|
||||
"configuration_wizard": "Setup wizard",
|
||||
|
@ -305,6 +306,10 @@
|
|||
"extending_volume_description": "Resizing volume will allow you to store more data on your server without extending the server itself. Volume can only be extended: shrinking is not possible.",
|
||||
"extending_volume_price_info": "Price includes VAT and is estimated from pricing data provided by your server provider. Server will be rebooted after resizing.",
|
||||
"extending_volume_error": "Couldn't initialize volume extending.",
|
||||
"extending_volume_started": "Volume extending started",
|
||||
"extending_volume_provider_waiting": "Provider volume resized, waiting 10 seconds…",
|
||||
"extending_volume_server_waiting": "Server volume resized, waiting 20 seconds…",
|
||||
"extending_volume_rebooting": "Rebooting server…",
|
||||
"extending_volume_modal_description": "Upgrade to {} for {} plan per month.",
|
||||
"size": "Size",
|
||||
"price": "Price",
|
||||
|
@ -390,7 +395,8 @@
|
|||
"could_not_add_ssh_key": "Couldn't add SSH key",
|
||||
"username_rule": "Username must contain only lowercase latin letters, digits and underscores, should not start with a digit",
|
||||
"email_login": "Email login",
|
||||
"no_ssh_notice": "Only email and SSH accounts are created for this user. Single Sign On for all services is coming soon."
|
||||
"no_ssh_notice": "Only email and SSH accounts are created for this user. Single Sign On for all services is coming soon.",
|
||||
"user_already_exists": "User with such username already exists"
|
||||
},
|
||||
"initializing": {
|
||||
"server_provider_description": "A place where your data and SelfPrivacy services will reside:",
|
||||
|
@ -590,6 +596,8 @@
|
|||
"service_turn_off": "Turn off",
|
||||
"service_turn_on": "Turn on",
|
||||
"job_added": "Job added",
|
||||
"job_postponed": "Job added, but you will be able to launch it after current jobs are finished",
|
||||
"job_removed": "Job removed",
|
||||
"run_jobs": "Run jobs",
|
||||
"reboot_success": "Server is rebooting",
|
||||
"reboot_failed": "Couldn't reboot the server. Check the app logs.",
|
||||
|
@ -602,7 +610,11 @@
|
|||
"delete_ssh_key": "Delete SSH key for {}",
|
||||
"server_jobs": "Jobs on the server",
|
||||
"reset_user_password": "Reset password of user",
|
||||
"generic_error": "Couldn't connect to the server!"
|
||||
"generic_error": "Couldn't connect to the server!",
|
||||
"rebuild_system": "Rebuild system",
|
||||
"start_server_upgrade": "Start the server upgrade",
|
||||
"change_auto_upgrade_settings": "Change auto-upgrade settings",
|
||||
"change_server_timezone": "Change server timezone"
|
||||
},
|
||||
"validations": {
|
||||
"required": "Required",
|
||||
|
|
|
@ -1,43 +1,64 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/connection_status/connection_status_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||
|
||||
class BlocAndProviderConfig extends StatelessWidget {
|
||||
class BlocAndProviderConfig extends StatefulWidget {
|
||||
const BlocAndProviderConfig({super.key, this.child});
|
||||
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
BlocAndProviderConfigState createState() => BlocAndProviderConfigState();
|
||||
}
|
||||
|
||||
class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||
late final ServerInstallationCubit serverInstallationCubit;
|
||||
late final SupportSystemCubit supportSystemCubit;
|
||||
late final UsersBloc usersBloc;
|
||||
late final ServicesBloc servicesBloc;
|
||||
late final BackupsBloc backupsBloc;
|
||||
late final DnsRecordsCubit dnsRecordsCubit;
|
||||
late final RecoveryKeyBloc recoveryKeyBloc;
|
||||
late final DevicesBloc devicesBloc;
|
||||
late final ServerJobsBloc serverJobsBloc;
|
||||
late final ConnectionStatusBloc connectionStatusBloc;
|
||||
late final ServerDetailsCubit serverDetailsCubit;
|
||||
late final VolumesBloc volumesBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
serverInstallationCubit = ServerInstallationCubit()..load();
|
||||
supportSystemCubit = SupportSystemCubit();
|
||||
usersBloc = UsersBloc();
|
||||
servicesBloc = ServicesBloc();
|
||||
backupsBloc = BackupsBloc();
|
||||
dnsRecordsCubit = DnsRecordsCubit();
|
||||
recoveryKeyBloc = RecoveryKeyBloc();
|
||||
devicesBloc = DevicesBloc();
|
||||
serverJobsBloc = ServerJobsBloc();
|
||||
connectionStatusBloc = ConnectionStatusBloc();
|
||||
serverDetailsCubit = ServerDetailsCubit();
|
||||
volumesBloc = VolumesBloc();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
const isDark = false;
|
||||
const isAutoDark = true;
|
||||
final serverInstallationCubit = ServerInstallationCubit()..load();
|
||||
final supportSystemCubit = SupportSystemCubit();
|
||||
final usersCubit = UsersCubit(serverInstallationCubit);
|
||||
final servicesCubit = ServicesCubit(serverInstallationCubit);
|
||||
final backupsCubit = BackupsCubit(serverInstallationCubit);
|
||||
final dnsRecordsCubit = DnsRecordsCubit(serverInstallationCubit);
|
||||
final recoveryKeyCubit = RecoveryKeyCubit(serverInstallationCubit);
|
||||
final apiDevicesCubit = ApiDevicesCubit(serverInstallationCubit);
|
||||
final apiVolumesCubit = ApiProviderVolumeCubit(serverInstallationCubit);
|
||||
final apiServerVolumesCubit =
|
||||
ApiServerVolumeCubit(serverInstallationCubit, apiVolumesCubit);
|
||||
final serverJobsCubit = ServerJobsCubit(serverInstallationCubit);
|
||||
final serverDetailsCubit = ServerDetailsCubit(serverInstallationCubit);
|
||||
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
|
@ -56,49 +77,37 @@ class BlocAndProviderConfig extends StatelessWidget {
|
|||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => ProvidersCubit(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => usersCubit..load(),
|
||||
create: (final _) => usersBloc,
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => servicesCubit..load(),
|
||||
lazy: false,
|
||||
create: (final _) => servicesBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => backupsCubit..load(),
|
||||
lazy: false,
|
||||
create: (final _) => backupsBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => dnsRecordsCubit..load(),
|
||||
create: (final _) => dnsRecordsCubit,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => recoveryKeyCubit..load(),
|
||||
create: (final _) => recoveryKeyBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => apiDevicesCubit..load(),
|
||||
create: (final _) => devicesBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => apiVolumesCubit..load(),
|
||||
create: (final _) => serverJobsBloc,
|
||||
),
|
||||
BlocProvider(create: (final _) => connectionStatusBloc),
|
||||
BlocProvider(
|
||||
create: (final _) => apiServerVolumesCubit..load(),
|
||||
create: (final _) => serverDetailsCubit,
|
||||
),
|
||||
BlocProvider(create: (final _) => volumesBloc),
|
||||
BlocProvider(
|
||||
create: (final _) => serverJobsCubit..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => serverDetailsCubit..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => JobsCubit(
|
||||
usersCubit: usersCubit,
|
||||
servicesCubit: servicesCubit,
|
||||
),
|
||||
create: (final _) => JobsCubit(),
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import 'package:get_it/get_it.dart';
|
||||
import 'package:selfprivacy/logic/get_it/api_config.dart';
|
||||
import 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
||||
import 'package:selfprivacy/logic/get_it/console.dart';
|
||||
import 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||
import 'package:selfprivacy/logic/get_it/timer.dart';
|
||||
|
||||
export 'package:selfprivacy/logic/get_it/api_config.dart';
|
||||
export 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
||||
export 'package:selfprivacy/logic/get_it/console.dart';
|
||||
export 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||
export 'package:selfprivacy/logic/get_it/timer.dart';
|
||||
|
||||
final GetIt getIt = GetIt.instance;
|
||||
|
||||
|
@ -15,8 +15,11 @@ Future<void> getItSetup() async {
|
|||
getIt.registerSingleton<NavigationService>(NavigationService());
|
||||
|
||||
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
||||
getIt.registerSingleton<TimerModel>(TimerModel());
|
||||
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
|
||||
|
||||
getIt.registerSingleton<ApiConnectionRepository>(
|
||||
ApiConnectionRepository()..init(),
|
||||
);
|
||||
|
||||
await getIt.allReady();
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ class HiveConfig {
|
|||
Hive.registerAdapter(ServerDomainAdapter());
|
||||
Hive.registerAdapter(BackupsCredentialAdapter());
|
||||
Hive.registerAdapter(BackblazeBucketAdapter());
|
||||
Hive.registerAdapter(ServerVolumeAdapter());
|
||||
Hive.registerAdapter(ServerProviderVolumeAdapter());
|
||||
Hive.registerAdapter(UserTypeAdapter());
|
||||
Hive.registerAdapter(DnsProviderTypeAdapter());
|
||||
Hive.registerAdapter(ServerProviderTypeAdapter());
|
||||
|
|
|
@ -150,9 +150,9 @@ type DnsRecord {
|
|||
recordType: String!
|
||||
name: String!
|
||||
content: String!
|
||||
displayName: String!
|
||||
ttl: Int!
|
||||
priority: Int
|
||||
displayName: String!
|
||||
}
|
||||
|
||||
type GenericBackupConfigReturn implements MutationReturnInterface {
|
||||
|
@ -272,6 +272,19 @@ enum RestoreStrategy {
|
|||
DOWNLOAD_VERIFY_OVERWRITE
|
||||
}
|
||||
|
||||
input SSHSettingsInput {
|
||||
enable: Boolean!
|
||||
passwordAuthentication: Boolean!
|
||||
}
|
||||
|
||||
type SSHSettingsMutationReturn implements MutationReturnInterface {
|
||||
success: Boolean!
|
||||
message: String!
|
||||
code: Int!
|
||||
enable: Boolean!
|
||||
passwordAuthentication: Boolean!
|
||||
}
|
||||
|
||||
enum ServerProvider {
|
||||
HETZNER
|
||||
DIGITALOCEAN
|
||||
|
@ -424,9 +437,10 @@ type SystemInfo {
|
|||
type SystemMutations {
|
||||
changeTimezone(timezone: String!): TimezoneMutationReturn!
|
||||
changeAutoUpgradeSettings(settings: AutoUpgradeSettingsInput!): AutoUpgradeSettingsMutationReturn!
|
||||
runSystemRebuild: GenericMutationReturn!
|
||||
changeSshSettings(settings: SSHSettingsInput!): SSHSettingsMutationReturn!
|
||||
runSystemRebuild: GenericJobMutationReturn!
|
||||
runSystemRollback: GenericMutationReturn!
|
||||
runSystemUpgrade: GenericMutationReturn!
|
||||
runSystemUpgrade: GenericJobMutationReturn!
|
||||
rebootSystem: GenericMutationReturn!
|
||||
pullRepositoryChanges: GenericMutationReturn!
|
||||
}
|
||||
|
|
|
@ -982,6 +982,135 @@ class _CopyWithStubImpl$Input$RecoveryKeyLimitsInput<TRes>
|
|||
_res;
|
||||
}
|
||||
|
||||
class Input$SSHSettingsInput {
|
||||
factory Input$SSHSettingsInput({
|
||||
required bool enable,
|
||||
required bool passwordAuthentication,
|
||||
}) =>
|
||||
Input$SSHSettingsInput._({
|
||||
r'enable': enable,
|
||||
r'passwordAuthentication': passwordAuthentication,
|
||||
});
|
||||
|
||||
Input$SSHSettingsInput._(this._$data);
|
||||
|
||||
factory Input$SSHSettingsInput.fromJson(Map<String, dynamic> data) {
|
||||
final result$data = <String, dynamic>{};
|
||||
final l$enable = data['enable'];
|
||||
result$data['enable'] = (l$enable as bool);
|
||||
final l$passwordAuthentication = data['passwordAuthentication'];
|
||||
result$data['passwordAuthentication'] = (l$passwordAuthentication as bool);
|
||||
return Input$SSHSettingsInput._(result$data);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$data;
|
||||
|
||||
bool get enable => (_$data['enable'] as bool);
|
||||
|
||||
bool get passwordAuthentication => (_$data['passwordAuthentication'] as bool);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final result$data = <String, dynamic>{};
|
||||
final l$enable = enable;
|
||||
result$data['enable'] = l$enable;
|
||||
final l$passwordAuthentication = passwordAuthentication;
|
||||
result$data['passwordAuthentication'] = l$passwordAuthentication;
|
||||
return result$data;
|
||||
}
|
||||
|
||||
CopyWith$Input$SSHSettingsInput<Input$SSHSettingsInput> get copyWith =>
|
||||
CopyWith$Input$SSHSettingsInput(
|
||||
this,
|
||||
(i) => i,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (!(other is Input$SSHSettingsInput) ||
|
||||
runtimeType != other.runtimeType) {
|
||||
return false;
|
||||
}
|
||||
final l$enable = enable;
|
||||
final lOther$enable = other.enable;
|
||||
if (l$enable != lOther$enable) {
|
||||
return false;
|
||||
}
|
||||
final l$passwordAuthentication = passwordAuthentication;
|
||||
final lOther$passwordAuthentication = other.passwordAuthentication;
|
||||
if (l$passwordAuthentication != lOther$passwordAuthentication) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
final l$enable = enable;
|
||||
final l$passwordAuthentication = passwordAuthentication;
|
||||
return Object.hashAll([
|
||||
l$enable,
|
||||
l$passwordAuthentication,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CopyWith$Input$SSHSettingsInput<TRes> {
|
||||
factory CopyWith$Input$SSHSettingsInput(
|
||||
Input$SSHSettingsInput instance,
|
||||
TRes Function(Input$SSHSettingsInput) then,
|
||||
) = _CopyWithImpl$Input$SSHSettingsInput;
|
||||
|
||||
factory CopyWith$Input$SSHSettingsInput.stub(TRes res) =
|
||||
_CopyWithStubImpl$Input$SSHSettingsInput;
|
||||
|
||||
TRes call({
|
||||
bool? enable,
|
||||
bool? passwordAuthentication,
|
||||
});
|
||||
}
|
||||
|
||||
class _CopyWithImpl$Input$SSHSettingsInput<TRes>
|
||||
implements CopyWith$Input$SSHSettingsInput<TRes> {
|
||||
_CopyWithImpl$Input$SSHSettingsInput(
|
||||
this._instance,
|
||||
this._then,
|
||||
);
|
||||
|
||||
final Input$SSHSettingsInput _instance;
|
||||
|
||||
final TRes Function(Input$SSHSettingsInput) _then;
|
||||
|
||||
static const _undefined = <dynamic, dynamic>{};
|
||||
|
||||
TRes call({
|
||||
Object? enable = _undefined,
|
||||
Object? passwordAuthentication = _undefined,
|
||||
}) =>
|
||||
_then(Input$SSHSettingsInput._({
|
||||
..._instance._$data,
|
||||
if (enable != _undefined && enable != null) 'enable': (enable as bool),
|
||||
if (passwordAuthentication != _undefined &&
|
||||
passwordAuthentication != null)
|
||||
'passwordAuthentication': (passwordAuthentication as bool),
|
||||
}));
|
||||
}
|
||||
|
||||
class _CopyWithStubImpl$Input$SSHSettingsInput<TRes>
|
||||
implements CopyWith$Input$SSHSettingsInput<TRes> {
|
||||
_CopyWithStubImpl$Input$SSHSettingsInput(this._res);
|
||||
|
||||
TRes _res;
|
||||
|
||||
call({
|
||||
bool? enable,
|
||||
bool? passwordAuthentication,
|
||||
}) =>
|
||||
_res;
|
||||
}
|
||||
|
||||
class Input$SshMutationInput {
|
||||
factory Input$SshMutationInput({
|
||||
required String username,
|
||||
|
@ -1928,6 +2057,7 @@ const possibleTypesMap = <String, Set<String>>{
|
|||
'GenericBackupConfigReturn',
|
||||
'GenericJobMutationReturn',
|
||||
'GenericMutationReturn',
|
||||
'SSHSettingsMutationReturn',
|
||||
'ServiceJobMutationReturn',
|
||||
'ServiceMutationReturn',
|
||||
'TimezoneMutationReturn',
|
||||
|
|
|
@ -42,6 +42,17 @@ mutation RemoveJob($jobId: String!) {
|
|||
}
|
||||
|
||||
mutation RunSystemRebuild {
|
||||
system {
|
||||
runSystemRebuild {
|
||||
...basicMutationReturnFields
|
||||
job {
|
||||
...basicApiJobsFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation RunSystemRebuildFallback {
|
||||
system {
|
||||
runSystemRebuild {
|
||||
...basicMutationReturnFields
|
||||
|
@ -58,6 +69,17 @@ mutation RunSystemRollback {
|
|||
}
|
||||
|
||||
mutation RunSystemUpgrade {
|
||||
system {
|
||||
runSystemUpgrade {
|
||||
...basicMutationReturnFields
|
||||
job {
|
||||
...basicApiJobsFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation RunSystemUpgradeFallback {
|
||||
system {
|
||||
runSystemUpgrade {
|
||||
...basicMutationReturnFields
|
||||
|
|
|
@ -29,7 +29,11 @@ mixin ServerActionsApi on GraphQLApiMap {
|
|||
print(response.exception.toString());
|
||||
}
|
||||
if (response.parsedData!.system.rebootSystem.success) {
|
||||
time = DateTime.now().toUtc();
|
||||
return GenericResult(
|
||||
data: time,
|
||||
success: true,
|
||||
message: response.parsedData!.system.rebootSystem.message,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
|
@ -50,23 +54,94 @@ mixin ServerActionsApi on GraphQLApiMap {
|
|||
}
|
||||
}
|
||||
|
||||
Future<bool> upgrade() async {
|
||||
Future<GenericResult<ServerJob?>> upgrade() async {
|
||||
try {
|
||||
final GraphQLClient client = await getClient();
|
||||
return _commonBoolRequest(
|
||||
() async => client.mutate$RunSystemUpgrade(),
|
||||
final result = await client.mutate$RunSystemUpgrade();
|
||||
if (result.hasException) {
|
||||
final fallbackResult = await client.mutate$RunSystemUpgradeFallback();
|
||||
if (fallbackResult.parsedData!.system.runSystemUpgrade.success) {
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: null,
|
||||
message: fallbackResult.parsedData!.system.runSystemUpgrade.message,
|
||||
);
|
||||
} else {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
message: fallbackResult.parsedData!.system.runSystemUpgrade.message,
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
} else if (result.parsedData!.system.runSystemUpgrade.success &&
|
||||
result.parsedData!.system.runSystemUpgrade.job != null) {
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: ServerJob.fromGraphQL(
|
||||
result.parsedData!.system.runSystemUpgrade.job!,
|
||||
),
|
||||
message: result.parsedData!.system.runSystemUpgrade.message,
|
||||
);
|
||||
} else {
|
||||
|
||||
return GenericResult(
|
||||
success: false,
|
||||
message: result.parsedData!.system.runSystemUpgrade.message,
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
return GenericResult(
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> apply() async {
|
||||
Future<GenericResult<ServerJob?>> apply() async {
|
||||
try {
|
||||
final GraphQLClient client = await getClient();
|
||||
await client.mutate$RunSystemRebuild();
|
||||
final result = await client.mutate$RunSystemRebuild();
|
||||
if (result.hasException) {
|
||||
final fallbackResult = await client.mutate$RunSystemRebuildFallback();
|
||||
if (fallbackResult.parsedData!.system.runSystemRebuild.success) {
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: null,
|
||||
message: fallbackResult.parsedData!.system.runSystemRebuild.message,
|
||||
);
|
||||
} else {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
message: fallbackResult.parsedData!.system.runSystemRebuild.message,
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (result.parsedData!.system.runSystemRebuild.success &&
|
||||
result.parsedData!.system.runSystemRebuild.job != null) {
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: ServerJob.fromGraphQL(
|
||||
result.parsedData!.system.runSystemRebuild.job!,
|
||||
),
|
||||
message: result.parsedData!.system.runSystemRebuild.message,
|
||||
);
|
||||
} else {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
message: result.parsedData!.system.runSystemRebuild.message,
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return GenericResult(
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,24 +132,55 @@ class ServerApi extends GraphQLApiMap
|
|||
return usesBinds;
|
||||
}
|
||||
|
||||
Future<void> switchService(final String uid, final bool needTurnOn) async {
|
||||
Future<GenericResult> switchService(
|
||||
final String uid,
|
||||
final bool needTurnOn,
|
||||
) async {
|
||||
try {
|
||||
final GraphQLClient client = await getClient();
|
||||
if (needTurnOn) {
|
||||
final variables = Variables$Mutation$EnableService(serviceId: uid);
|
||||
final mutation = Options$Mutation$EnableService(variables: variables);
|
||||
await client.mutate$EnableService(mutation);
|
||||
final result = await client.mutate$EnableService(mutation);
|
||||
if (result.hasException) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
message: result.exception.toString(),
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
return GenericResult(
|
||||
success: result.parsedData?.services.enableService.success ?? false,
|
||||
message: result.parsedData?.services.enableService.message,
|
||||
data: null,
|
||||
);
|
||||
} else {
|
||||
final variables = Variables$Mutation$DisableService(serviceId: uid);
|
||||
final mutation = Options$Mutation$DisableService(variables: variables);
|
||||
await client.mutate$DisableService(mutation);
|
||||
final result = await client.mutate$DisableService(mutation);
|
||||
if (result.hasException) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
message: result.exception.toString(),
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
return GenericResult(
|
||||
success: result.parsedData?.services.disableService.success ?? false,
|
||||
message: result.parsedData?.services.disableService.message,
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return GenericResult(
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setAutoUpgradeSettings(
|
||||
Future<GenericResult<AutoUpgradeSettings?>> setAutoUpgradeSettings(
|
||||
final AutoUpgradeSettings settings,
|
||||
) async {
|
||||
try {
|
||||
|
@ -164,13 +195,38 @@ class ServerApi extends GraphQLApiMap
|
|||
final mutation = Options$Mutation$ChangeAutoUpgradeSettings(
|
||||
variables: variables,
|
||||
);
|
||||
await client.mutate$ChangeAutoUpgradeSettings(mutation);
|
||||
final result = await client.mutate$ChangeAutoUpgradeSettings(mutation);
|
||||
if (result.hasException) {
|
||||
return GenericResult<AutoUpgradeSettings?>(
|
||||
success: false,
|
||||
message: result.exception.toString(),
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
return GenericResult<AutoUpgradeSettings?>(
|
||||
success: result.parsedData?.system.changeAutoUpgradeSettings.success ??
|
||||
false,
|
||||
message: result.parsedData?.system.changeAutoUpgradeSettings.message,
|
||||
data: result.parsedData == null
|
||||
? null
|
||||
: AutoUpgradeSettings(
|
||||
allowReboot: result
|
||||
.parsedData!.system.changeAutoUpgradeSettings.allowReboot,
|
||||
enable: result.parsedData!.system.changeAutoUpgradeSettings
|
||||
.enableAutoUpgrade,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return GenericResult<AutoUpgradeSettings?>(
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setTimezone(final String timezone) async {
|
||||
Future<GenericResult<String?>> setTimezone(final String timezone) async {
|
||||
try {
|
||||
final GraphQLClient client = await getClient();
|
||||
final variables = Variables$Mutation$ChangeTimezone(
|
||||
|
@ -179,9 +235,26 @@ class ServerApi extends GraphQLApiMap
|
|||
final mutation = Options$Mutation$ChangeTimezone(
|
||||
variables: variables,
|
||||
);
|
||||
await client.mutate$ChangeTimezone(mutation);
|
||||
final result = await client.mutate$ChangeTimezone(mutation);
|
||||
if (result.hasException) {
|
||||
return GenericResult<String>(
|
||||
success: false,
|
||||
message: result.exception.toString(),
|
||||
data: '',
|
||||
);
|
||||
}
|
||||
return GenericResult<String?>(
|
||||
success: result.parsedData?.system.changeTimezone.success ?? false,
|
||||
message: result.parsedData?.system.changeTimezone.message,
|
||||
data: result.parsedData?.system.changeTimezone.timezone,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return GenericResult<String?>(
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
data: '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ mixin VolumeApi on GraphQLApiMap {
|
|||
if (response.hasException) {
|
||||
print(response.exception.toString());
|
||||
}
|
||||
// TODO: Rewrite to use fromGraphQL
|
||||
volumes = response.data!['storage']['volumes']
|
||||
.map<ServerDiskVolume>((final e) => ServerDiskVolume.fromJson(e))
|
||||
.toList();
|
||||
|
|
|
@ -0,0 +1,408 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
|
||||
import 'package:selfprivacy/logic/models/backup.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
|
||||
import 'package:selfprivacy/logic/models/initialize_repository_input.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
|
||||
part 'backups_event.dart';
|
||||
part 'backups_state.dart';
|
||||
|
||||
class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
|
||||
BackupsBloc() : super(BackupsInitial()) {
|
||||
on<BackupsServerLoaded>(
|
||||
_loadState,
|
||||
transformer: droppable(),
|
||||
);
|
||||
on<BackupsServerReset>(
|
||||
_resetState,
|
||||
transformer: droppable(),
|
||||
);
|
||||
on<BackupsStateChanged>(
|
||||
_updateState,
|
||||
transformer: droppable(),
|
||||
);
|
||||
on<InitializeBackupsRepository>(
|
||||
_initializeRepository,
|
||||
transformer: droppable(),
|
||||
);
|
||||
on<ForceSnapshotListUpdate>(
|
||||
_forceSnapshotListUpdate,
|
||||
transformer: droppable(),
|
||||
);
|
||||
on<CreateBackups>(
|
||||
_createBackups,
|
||||
transformer: sequential(),
|
||||
);
|
||||
on<RestoreBackup>(
|
||||
_restoreBackup,
|
||||
transformer: sequential(),
|
||||
);
|
||||
on<SetAutobackupPeriod>(
|
||||
_setAutobackupPeriod,
|
||||
transformer: restartable(),
|
||||
);
|
||||
on<SetAutobackupQuotas>(
|
||||
_setAutobackupQuotas,
|
||||
transformer: restartable(),
|
||||
);
|
||||
on<ForgetSnapshot>(
|
||||
_forgetSnapshot,
|
||||
transformer: sequential(),
|
||||
);
|
||||
|
||||
final connectionRepository = getIt<ApiConnectionRepository>();
|
||||
|
||||
_apiStatusSubscription = connectionRepository.connectionStatusStream
|
||||
.listen((final ConnectionStatus connectionStatus) {
|
||||
switch (connectionStatus) {
|
||||
case ConnectionStatus.nonexistent:
|
||||
add(const BackupsServerReset());
|
||||
isLoaded = false;
|
||||
break;
|
||||
case ConnectionStatus.connected:
|
||||
if (!isLoaded) {
|
||||
add(const BackupsServerLoaded());
|
||||
isLoaded = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
_apiDataSubscription = connectionRepository.dataStream.listen(
|
||||
(final ApiData apiData) {
|
||||
if (apiData.backups.data == null || apiData.backupConfig.data == null) {
|
||||
add(const BackupsServerReset());
|
||||
isLoaded = false;
|
||||
} else {
|
||||
add(
|
||||
BackupsStateChanged(
|
||||
apiData.backups.data!,
|
||||
apiData.backupConfig.data,
|
||||
),
|
||||
);
|
||||
isLoaded = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (connectionRepository.connectionStatus == ConnectionStatus.connected) {
|
||||
add(const BackupsServerLoaded());
|
||||
isLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
final BackblazeApi backblaze = BackblazeApi();
|
||||
|
||||
Future<void> _loadState(
|
||||
final BackupsServerLoaded event,
|
||||
final Emitter<BackupsState> emit,
|
||||
) async {
|
||||
BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
|
||||
final backups = getIt<ApiConnectionRepository>().apiData.backups;
|
||||
final backupConfig = getIt<ApiConnectionRepository>().apiData.backupConfig;
|
||||
if (backupConfig.data == null || backups.data == null) {
|
||||
emit(BackupsLoading());
|
||||
return;
|
||||
}
|
||||
if (bucket != null &&
|
||||
backupConfig.data!.encryptionKey != bucket.encryptionKey) {
|
||||
bucket = bucket.copyWith(
|
||||
encryptionKey: backupConfig.data!.encryptionKey,
|
||||
);
|
||||
await getIt<ApiConfigModel>().setBackblazeBucket(bucket);
|
||||
}
|
||||
if (backupConfig.data!.isInitialized) {
|
||||
emit(
|
||||
BackupsInitialized(
|
||||
backblazeBucket: bucket,
|
||||
backupConfig: backupConfig.data,
|
||||
backups: backups.data ?? [],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(BackupsUnititialized());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _resetState(
|
||||
final BackupsServerReset event,
|
||||
final Emitter<BackupsState> emit,
|
||||
) async {
|
||||
emit(BackupsInitial());
|
||||
}
|
||||
|
||||
Future<void> _initializeRepository(
|
||||
final InitializeBackupsRepository event,
|
||||
final Emitter<BackupsState> emit,
|
||||
) async {
|
||||
if (state is! BackupsUnititialized) {
|
||||
return;
|
||||
NaiJi
commented
Review
This is quite extreme, is this impossible to trigger? Do we need a notification for it? This is quite extreme, is this impossible to trigger? Do we need a notification for it?
inex
commented
Review
Better safe than sorry. Events now trigger these functions, and not the direct calls. Better safe than sorry. Events now trigger these functions, and not the direct calls.
|
||||
}
|
||||
emit(BackupsInitializing());
|
||||
final String? encryptionKey = getIt<ApiConnectionRepository>()
|
||||
.apiData
|
||||
.backupConfig
|
||||
.data
|
||||
?.encryptionKey;
|
||||
if (encryptionKey == null) {
|
||||
emit(BackupsUnititialized());
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar("Couldn't get encryption key from your server.");
|
||||
return;
|
||||
}
|
||||
|
||||
final BackblazeBucket bucket;
|
||||
|
||||
if (state.backblazeBucket == null) {
|
||||
final String domain = getIt<ApiConnectionRepository>()
|
||||
.serverDomain!
|
||||
.domainName
|
||||
.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
|
||||
final int serverId = getIt<ApiConnectionRepository>().serverDetails!.id;
|
||||
String bucketName =
|
||||
'${DateTime.now().millisecondsSinceEpoch}-$serverId-$domain';
|
||||
if (bucketName.length > 49) {
|
||||
bucketName = bucketName.substring(0, 49);
|
||||
}
|
||||
final String bucketId = await backblaze.createBucket(bucketName);
|
||||
|
||||
final BackblazeApplicationKey key = await backblaze.createKey(bucketId);
|
||||
bucket = BackblazeBucket(
|
||||
bucketId: bucketId,
|
||||
bucketName: bucketName,
|
||||
applicationKey: key.applicationKey,
|
||||
applicationKeyId: key.applicationKeyId,
|
||||
encryptionKey: encryptionKey,
|
||||
);
|
||||
|
||||
await getIt<ApiConfigModel>().setBackblazeBucket(bucket);
|
||||
emit(state.copyWith(backblazeBucket: bucket));
|
||||
} else {
|
||||
bucket = state.backblazeBucket!;
|
||||
}
|
||||
|
||||
final GenericResult result =
|
||||
await getIt<ApiConnectionRepository>().api.initializeRepository(
|
||||
InitializeRepositoryInput(
|
||||
provider: BackupsProviderType.backblaze,
|
||||
locationId: bucket.bucketId,
|
||||
locationName: bucket.bucketName,
|
||||
login: bucket.applicationKeyId,
|
||||
password: bucket.applicationKey,
|
||||
),
|
||||
);
|
||||
if (result.success == false) {
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
result.message ?? "Couldn't initialize repository on your server.",
|
||||
);
|
||||
emit(BackupsUnititialized());
|
||||
return;
|
||||
}
|
||||
getIt<ApiConnectionRepository>().apiData.backupConfig.invalidate();
|
||||
getIt<ApiConnectionRepository>().apiData.backups.invalidate();
|
||||
await getIt<ApiConnectionRepository>().reload(null);
|
||||
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'Backups repository is now initializing. It may take a while.',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateState(
|
||||
final BackupsStateChanged event,
|
||||
final Emitter<BackupsState> emit,
|
||||
) async {
|
||||
if (event.backupConfiguration == null ||
|
||||
event.backupConfiguration!.isInitialized == false) {
|
||||
emit(BackupsUnititialized());
|
||||
return;
|
||||
}
|
||||
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
|
||||
emit(
|
||||
BackupsInitialized(
|
||||
backblazeBucket: bucket,
|
||||
backupConfig: event.backupConfiguration,
|
||||
backups: event.backups,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _forceSnapshotListUpdate(
|
||||
final ForceSnapshotListUpdate event,
|
||||
final Emitter<BackupsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is BackupsInitialized) {
|
||||
emit(BackupsBusy.fromState(currentState));
|
||||
getIt<NavigationService>().showSnackBar('backup.refetching_list'.tr());
|
||||
await getIt<ApiConnectionRepository>().api.forceBackupListReload();
|
||||
getIt<ApiConnectionRepository>().apiData.backups.invalidate();
|
||||
emit(currentState);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createBackups(
|
||||
final CreateBackups event,
|
||||
final Emitter<BackupsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is BackupsInitialized) {
|
||||
emit(BackupsBusy.fromState(currentState));
|
||||
for (final service in event.services) {
|
||||
final GenericResult<ServerJob?> result =
|
||||
await getIt<ApiConnectionRepository>().api.startBackup(
|
||||
service.id,
|
||||
);
|
||||
if (result.success == false) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'Unknown error');
|
||||
}
|
||||
if (result.data != null) {
|
||||
getIt<ApiConnectionRepository>()
|
||||
.apiData
|
||||
.serverJobs
|
||||
.data
|
||||
?.add(result.data!);
|
||||
}
|
||||
}
|
||||
emit(currentState);
|
||||
getIt<ApiConnectionRepository>().emitData();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _restoreBackup(
|
||||
final RestoreBackup event,
|
||||
final Emitter<BackupsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is BackupsInitialized) {
|
||||
emit(BackupsBusy.fromState(currentState));
|
||||
final GenericResult result =
|
||||
await getIt<ApiConnectionRepository>().api.restoreBackup(
|
||||
event.backupId,
|
||||
event.restoreStrategy,
|
||||
);
|
||||
if (result.success == false) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'Unknown error');
|
||||
}
|
||||
emit(currentState);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setAutobackupPeriod(
|
||||
final SetAutobackupPeriod event,
|
||||
final Emitter<BackupsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is BackupsInitialized) {
|
||||
emit(BackupsBusy.fromState(currentState));
|
||||
final GenericResult result =
|
||||
await getIt<ApiConnectionRepository>().api.setAutobackupPeriod(
|
||||
period: event.period?.inMinutes,
|
||||
);
|
||||
if (result.success == false) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'Unknown error');
|
||||
}
|
||||
if (result.success == true) {
|
||||
getIt<ApiConnectionRepository>().apiData.backupConfig.data =
|
||||
getIt<ApiConnectionRepository>()
|
||||
.apiData
|
||||
.backupConfig
|
||||
.data
|
||||
?.copyWith(
|
||||
autobackupPeriod: event.period,
|
||||
);
|
||||
}
|
||||
emit(currentState);
|
||||
getIt<ApiConnectionRepository>().emitData();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setAutobackupQuotas(
|
||||
final SetAutobackupQuotas event,
|
||||
final Emitter<BackupsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is BackupsInitialized) {
|
||||
emit(BackupsBusy.fromState(currentState));
|
||||
final GenericResult result =
|
||||
await getIt<ApiConnectionRepository>().api.setAutobackupQuotas(
|
||||
event.quotas,
|
||||
);
|
||||
if (result.success == false) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'Unknown error');
|
||||
}
|
||||
if (result.success == true) {
|
||||
getIt<ApiConnectionRepository>().apiData.backupConfig.data =
|
||||
getIt<ApiConnectionRepository>()
|
||||
.apiData
|
||||
.backupConfig
|
||||
.data
|
||||
?.copyWith(
|
||||
autobackupQuotas: event.quotas,
|
||||
);
|
||||
}
|
||||
emit(currentState);
|
||||
getIt<ApiConnectionRepository>().emitData();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _forgetSnapshot(
|
||||
final ForgetSnapshot event,
|
||||
final Emitter<BackupsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is BackupsInitialized) {
|
||||
// Optimistically remove the snapshot from the list
|
||||
getIt<ApiConnectionRepository>().apiData.backups.data =
|
||||
getIt<ApiConnectionRepository>()
|
||||
.apiData
|
||||
.backups
|
||||
.data
|
||||
?.where((final Backup backup) => backup.id != event.backupId)
|
||||
.toList();
|
||||
emit(BackupsBusy.fromState(currentState));
|
||||
final GenericResult result =
|
||||
await getIt<ApiConnectionRepository>().api.forgetSnapshot(
|
||||
event.backupId,
|
||||
);
|
||||
if (result.success == false) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
||||
} else if (result.data == false) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('backup.forget_snapshot_error'.tr());
|
||||
}
|
||||
emit(currentState);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_apiStatusSubscription.cancel();
|
||||
_apiDataSubscription.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(final Change<BackupsState> change) {
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
late StreamSubscription _apiStatusSubscription;
|
||||
late StreamSubscription _apiDataSubscription;
|
||||
bool isLoaded = false;
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
part of 'backups_bloc.dart';
|
||||
|
||||
sealed class BackupsEvent extends Equatable {
|
||||
const BackupsEvent();
|
||||
}
|
||||
|
||||
class BackupsServerLoaded extends BackupsEvent {
|
||||
const BackupsServerLoaded();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class BackupsServerReset extends BackupsEvent {
|
||||
const BackupsServerReset();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class InitializeBackupsRepository extends BackupsEvent {
|
||||
const InitializeBackupsRepository();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class BackupsStateChanged extends BackupsEvent {
|
||||
const BackupsStateChanged(this.backups, this.backupConfiguration);
|
||||
|
||||
final List<Backup> backups;
|
||||
final BackupConfiguration? backupConfiguration;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [backups, backupConfiguration];
|
||||
}
|
||||
|
||||
class ForceSnapshotListUpdate extends BackupsEvent {
|
||||
const ForceSnapshotListUpdate();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class CreateBackups extends BackupsEvent {
|
||||
const CreateBackups(this.services);
|
||||
|
||||
final List<Service> services;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [services];
|
||||
}
|
||||
|
||||
class RestoreBackup extends BackupsEvent {
|
||||
const RestoreBackup(this.backupId, this.restoreStrategy);
|
||||
|
||||
final String backupId;
|
||||
final BackupRestoreStrategy restoreStrategy;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [backupId, restoreStrategy];
|
||||
}
|
||||
|
||||
class SetAutobackupPeriod extends BackupsEvent {
|
||||
const SetAutobackupPeriod(this.period);
|
||||
|
||||
final Duration? period;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [period];
|
||||
}
|
||||
|
||||
class SetAutobackupQuotas extends BackupsEvent {
|
||||
const SetAutobackupQuotas(this.quotas);
|
||||
|
||||
final AutobackupQuotas quotas;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [quotas];
|
||||
}
|
||||
|
||||
class ForgetSnapshot extends BackupsEvent {
|
||||
const ForgetSnapshot(this.backupId);
|
||||
|
||||
final String backupId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [backupId];
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
part of 'backups_bloc.dart';
|
||||
|
||||
sealed class BackupsState extends Equatable {
|
||||
BackupsState({
|
||||
this.backblazeBucket,
|
||||
});
|
||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
final BackblazeBucket? backblazeBucket;
|
||||
|
||||
@Deprecated('Infer the initializations status from state')
|
||||
bool get isInitialized => false;
|
||||
|
||||
@Deprecated('Infer the loading status from state')
|
||||
bool get refreshing => false;
|
||||
|
||||
@Deprecated('Infer the prevent actions status from state')
|
||||
bool get preventActions => true;
|
||||
|
||||
List<Backup> get backups => [];
|
||||
|
||||
List<Backup> serviceBackups(final String serviceId) => [];
|
||||
|
||||
Duration? get autobackupPeriod => null;
|
||||
|
||||
AutobackupQuotas? get autobackupQuotas => null;
|
||||
|
||||
BackupsState copyWith({required final BackblazeBucket backblazeBucket});
|
||||
}
|
||||
|
||||
class BackupsInitial extends BackupsState {
|
||||
BackupsInitial({
|
||||
super.backblazeBucket,
|
||||
});
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
|
||||
@override
|
||||
BackupsInitial copyWith({
|
||||
final BackblazeBucket? backblazeBucket,
|
||||
}) =>
|
||||
BackupsInitial(backblazeBucket: backblazeBucket ?? this.backblazeBucket);
|
||||
}
|
||||
|
||||
class BackupsLoading extends BackupsState {
|
||||
BackupsLoading({
|
||||
super.backblazeBucket,
|
||||
});
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
|
||||
@override
|
||||
@Deprecated('Infer the loading status from state')
|
||||
bool get refreshing => true;
|
||||
|
||||
@override
|
||||
BackupsLoading copyWith({
|
||||
final BackblazeBucket? backblazeBucket,
|
||||
}) =>
|
||||
BackupsLoading(backblazeBucket: backblazeBucket ?? this.backblazeBucket);
|
||||
}
|
||||
|
||||
class BackupsUnititialized extends BackupsState {
|
||||
BackupsUnititialized({
|
||||
super.backblazeBucket,
|
||||
});
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
|
||||
@override
|
||||
BackupsUnititialized copyWith({
|
||||
final BackblazeBucket? backblazeBucket,
|
||||
}) =>
|
||||
BackupsUnititialized(
|
||||
backblazeBucket: backblazeBucket ?? this.backblazeBucket,
|
||||
);
|
||||
}
|
||||
|
||||
class BackupsInitializing extends BackupsState {
|
||||
BackupsInitializing({
|
||||
super.backblazeBucket,
|
||||
});
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
|
||||
@override
|
||||
BackupsInitializing copyWith({
|
||||
final BackblazeBucket? backblazeBucket,
|
||||
}) =>
|
||||
BackupsInitializing(
|
||||
backblazeBucket: backblazeBucket ?? this.backblazeBucket,
|
||||
);
|
||||
}
|
||||
|
||||
class BackupsInitialized extends BackupsState {
|
||||
BackupsInitialized({
|
||||
final List<Backup> backups = const [],
|
||||
final BackupConfiguration? backupConfig,
|
||||
super.backblazeBucket,
|
||||
}) : _backupsHashCode = Object.hashAll(backups),
|
||||
_backupConfigHashCode = Object.hashAll([backupConfig]);
|
||||
|
||||
final int _backupsHashCode;
|
||||
final int _backupConfigHashCode;
|
||||
|
||||
List<Backup> get _backupList =>
|
||||
apiConnectionRepository.apiData.backups.data ?? [];
|
||||
|
||||
BackupConfiguration? get _backupConfig =>
|
||||
apiConnectionRepository.apiData.backupConfig.data;
|
||||
|
||||
@override
|
||||
AutobackupQuotas? get autobackupQuotas => _backupConfig?.autobackupQuotas;
|
||||
|
||||
@override
|
||||
Duration? get autobackupPeriod =>
|
||||
_backupConfig?.autobackupPeriod?.inMinutes == 0
|
||||
? null
|
||||
: _backupConfig?.autobackupPeriod;
|
||||
|
||||
@override
|
||||
@Deprecated('Infer the initializations status from state')
|
||||
bool get isInitialized => true;
|
||||
|
||||
@override
|
||||
@Deprecated('Infer the prevent actions status from state')
|
||||
bool get preventActions => false;
|
||||
|
||||
@override
|
||||
List<Backup> get backups {
|
||||
try {
|
||||
final List<Backup> list = _backupList;
|
||||
list.sort((final a, final b) => b.time.compareTo(a.time));
|
||||
return list;
|
||||
} catch (_) {
|
||||
return _backupList;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<Backup> serviceBackups(final String serviceId) => backups
|
||||
.where((final backup) => backup.serviceId == serviceId)
|
||||
.toList(growable: false);
|
||||
|
||||
@override
|
||||
List<Object> get props => [_backupsHashCode, _backupConfigHashCode];
|
||||
|
||||
@override
|
||||
BackupsState copyWith({required final BackblazeBucket backblazeBucket}) =>
|
||||
BackupsInitialized(
|
||||
backups: backups,
|
||||
backupConfig: _backupConfig,
|
||||
backblazeBucket: backblazeBucket,
|
||||
);
|
||||
}
|
||||
|
||||
class BackupsBusy extends BackupsInitialized {
|
||||
BackupsBusy.fromState(final BackupsInitialized state)
|
||||
: super(
|
||||
backups: state.backups,
|
||||
backupConfig: state._backupConfig,
|
||||
backblazeBucket: state.backblazeBucket,
|
||||
);
|
||||
|
||||
@override
|
||||
@Deprecated('Infer the prevent actions status from state')
|
||||
bool get preventActions => true;
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
|
||||
part 'connection_status_event.dart';
|
||||
part 'connection_status_state.dart';
|
||||
|
||||
class ConnectionStatusBloc
|
||||
extends Bloc<ConnectionStatusEvent, ConnectionStatusState> {
|
||||
ConnectionStatusBloc()
|
||||
: super(
|
||||
const ConnectionStatusState(
|
||||
connectionStatus: ConnectionStatus.nonexistent,
|
||||
),
|
||||
) {
|
||||
on<ConnectionStatusChanged>((final event, final emit) {
|
||||
emit(ConnectionStatusState(connectionStatus: event.connectionStatus));
|
||||
});
|
||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
_apiConnectionStatusSubscription =
|
||||
apiConnectionRepository.connectionStatusStream.listen(
|
||||
(final ConnectionStatus connectionStatus) {
|
||||
add(
|
||||
ConnectionStatusChanged(connectionStatus),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
StreamSubscription? _apiConnectionStatusSubscription;
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_apiConnectionStatusSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
part of 'connection_status_bloc.dart';
|
||||
|
||||
sealed class ConnectionStatusEvent extends Equatable {
|
||||
const ConnectionStatusEvent();
|
||||
}
|
||||
|
||||
class ConnectionStatusChanged extends ConnectionStatusEvent {
|
||||
const ConnectionStatusChanged(this.connectionStatus);
|
||||
|
||||
final ConnectionStatus connectionStatus;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [connectionStatus];
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
part of 'connection_status_bloc.dart';
|
||||
|
||||
class ConnectionStatusState extends Equatable {
|
||||
const ConnectionStatusState({
|
||||
required this.connectionStatus,
|
||||
});
|
||||
|
||||
final ConnectionStatus connectionStatus;
|
||||
|
||||
@override
|
||||
List<Object> get props => [connectionStatus];
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
|
||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||
|
||||
part 'devices_event.dart';
|
||||
part 'devices_state.dart';
|
||||
|
||||
class DevicesBloc extends Bloc<DevicesEvent, DevicesState> {
|
||||
DevicesBloc() : super(DevicesInitial()) {
|
||||
on<DevicesListChanged>(
|
||||
_mapDevicesListChangedToState,
|
||||
transformer: sequential(),
|
||||
);
|
||||
on<DeleteDevice>(
|
||||
_mapDeleteDeviceToState,
|
||||
transformer: sequential(),
|
||||
);
|
||||
|
||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||
(final ApiData apiData) {
|
||||
add(
|
||||
DevicesListChanged(apiData.devices.data),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
StreamSubscription? _apiDataSubscription;
|
||||
|
||||
Future<void> _mapDevicesListChangedToState(
|
||||
final DevicesListChanged event,
|
||||
final Emitter<DevicesState> emit,
|
||||
) async {
|
||||
if (state is DevicesDeleting) {
|
||||
NaiJi
commented
Review
This really scares me. I suspect it's in all other blocs like this... what do you think? This really scares me. I suspect it's in all other blocs like this... what do you think?
|
||||
return;
|
||||
}
|
||||
if (event.devices == null) {
|
||||
emit(DevicesError());
|
||||
return;
|
||||
}
|
||||
emit(DevicesLoaded(devices: event.devices!));
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
getIt<ApiConnectionRepository>().apiData.devices.invalidate();
|
||||
await getIt<ApiConnectionRepository>().reload(null);
|
||||
}
|
||||
|
||||
Future<void> _mapDeleteDeviceToState(
|
||||
final DeleteDevice event,
|
||||
final Emitter<DevicesState> emit,
|
||||
) async {
|
||||
// Optimistically remove the device from the list
|
||||
emit(
|
||||
DevicesDeleting(
|
||||
devices: state.devices
|
||||
.where((final d) => d.name != event.device.name)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
|
||||
final GenericResult<void> response = await getIt<ApiConnectionRepository>()
|
||||
.api
|
||||
.deleteApiToken(event.device.name);
|
||||
if (response.success) {
|
||||
getIt<ApiConnectionRepository>().apiData.devices.invalidate();
|
||||
emit(
|
||||
DevicesLoaded(
|
||||
devices: state.devices
|
||||
.where((final d) => d.name != event.device.name)
|
||||
NaiJi
commented
Review
Duplication with line 62? Duplication with line 62?
inex
commented
Review
There are different states emited There are different states emited
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(response.message ?? 'Error deleting device');
|
||||
emit(DevicesLoaded(devices: state.devices));
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getNewDeviceKey() async {
|
||||
final GenericResult<String> response =
|
||||
await getIt<ApiConnectionRepository>().api.createDeviceToken();
|
||||
if (response.success) {
|
||||
return response.data;
|
||||
} else {
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
response.message ?? 'Error getting new device key',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(final Change<DevicesState> change) {
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_apiDataSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
part of 'devices_bloc.dart';
|
||||
|
||||
sealed class DevicesEvent extends Equatable {
|
||||
const DevicesEvent();
|
||||
}
|
||||
|
||||
class DevicesListChanged extends DevicesEvent {
|
||||
const DevicesListChanged(this.devices);
|
||||
|
||||
final List<ApiToken>? devices;
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class DeleteDevice extends DevicesEvent {
|
||||
const DeleteDevice(this.device);
|
||||
|
||||
final ApiToken device;
|
||||
|
||||
@override
|
||||
List<Object> get props => [device];
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
part of 'devices_bloc.dart';
|
||||
|
||||
sealed class DevicesState extends Equatable {
|
||||
DevicesState({
|
||||
required final List<ApiToken> devices,
|
||||
}) : _hashCode = Object.hashAll(devices);
|
||||
|
||||
final int _hashCode;
|
||||
|
||||
List<ApiToken> get _devices =>
|
||||
getIt<ApiConnectionRepository>().apiData.devices.data ?? const [];
|
||||
|
||||
List<ApiToken> get devices => _devices;
|
||||
ApiToken get thisDevice => _devices.firstWhere(
|
||||
(final device) => device.isCaller,
|
||||
orElse: () => ApiToken(
|
||||
name: 'Error fetching device',
|
||||
isCaller: true,
|
||||
date: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
List<ApiToken> get otherDevices =>
|
||||
_devices.where((final device) => !device.isCaller).toList();
|
||||
}
|
||||
|
||||
class DevicesInitial extends DevicesState {
|
||||
DevicesInitial() : super(devices: const []);
|
||||
|
||||
@override
|
||||
List<Object> get props => [_hashCode];
|
||||
}
|
||||
|
||||
class DevicesLoaded extends DevicesState {
|
||||
DevicesLoaded({required super.devices});
|
||||
|
||||
@override
|
||||
List<Object> get props => [_hashCode];
|
||||
}
|
||||
|
||||
class DevicesError extends DevicesState {
|
||||
DevicesError() : super(devices: const []);
|
||||
|
||||
@override
|
||||
List<Object> get props => [_hashCode];
|
||||
}
|
||||
|
||||
class DevicesDeleting extends DevicesState {
|
||||
DevicesDeleting({required super.devices});
|
||||
|
||||
@override
|
||||
List<Object> get props => [_hashCode];
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
|
||||
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
||||
|
||||
part 'recovery_key_event.dart';
|
||||
part 'recovery_key_state.dart';
|
||||
|
||||
class RecoveryKeyBloc extends Bloc<RecoveryKeyEvent, RecoveryKeyState> {
|
||||
RecoveryKeyBloc() : super(RecoveryKeyInitial()) {
|
||||
on<RecoveryKeyStatusChanged>(
|
||||
_mapRecoveryKeyStatusChangedToState,
|
||||
transformer: sequential(),
|
||||
);
|
||||
on<RecoveryKeyStatusRefresh>(
|
||||
_mapRecoveryKeyStatusRefreshToState,
|
||||
transformer: droppable(),
|
||||
);
|
||||
|
||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||
(final ApiData apiData) {
|
||||
add(
|
||||
RecoveryKeyStatusChanged(apiData.recoveryKeyStatus.data),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
StreamSubscription? _apiDataSubscription;
|
||||
|
||||
Future<void> _mapRecoveryKeyStatusChangedToState(
|
||||
final RecoveryKeyStatusChanged event,
|
||||
final Emitter<RecoveryKeyState> emit,
|
||||
) async {
|
||||
if (event.recoveryKeyStatus == null) {
|
||||
emit(RecoveryKeyError());
|
||||
return;
|
||||
}
|
||||
emit(RecoveryKeyLoaded(keyStatus: event.recoveryKeyStatus));
|
||||
}
|
||||
|
||||
Future<String> generateRecoveryKey({
|
||||
final DateTime? expirationDate,
|
||||
final int? numberOfUses,
|
||||
}) async {
|
||||
final GenericResult<String> response =
|
||||
await getIt<ApiConnectionRepository>()
|
||||
.api
|
||||
.generateRecoveryToken(expirationDate, numberOfUses);
|
||||
if (response.success) {
|
||||
getIt<ApiConnectionRepository>().apiData.recoveryKeyStatus.invalidate();
|
||||
unawaited(getIt<ApiConnectionRepository>().reload(null));
|
||||
return response.data;
|
||||
} else {
|
||||
throw GenerationError(response.message ?? 'Unknown error');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _mapRecoveryKeyStatusRefreshToState(
|
||||
final RecoveryKeyEvent event,
|
||||
final Emitter<RecoveryKeyState> emit,
|
||||
) async {
|
||||
emit(RecoveryKeyRefreshing(keyStatus: state._status));
|
||||
getIt<ApiConnectionRepository>().apiData.recoveryKeyStatus.invalidate();
|
||||
await getIt<ApiConnectionRepository>().reload(null);
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(final Change<RecoveryKeyState> change) {
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_apiDataSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
class GenerationError extends Error {
|
||||
GenerationError(this.message);
|
||||
final String message;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
part of 'recovery_key_bloc.dart';
|
||||
|
||||
sealed class RecoveryKeyEvent extends Equatable {
|
||||
const RecoveryKeyEvent();
|
||||
}
|
||||
|
||||
class RecoveryKeyStatusChanged extends RecoveryKeyEvent {
|
||||
const RecoveryKeyStatusChanged(this.recoveryKeyStatus);
|
||||
|
||||
final RecoveryKeyStatus? recoveryKeyStatus;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [recoveryKeyStatus];
|
||||
}
|
||||
|
||||
class RecoveryKeyStatusRefresh extends RecoveryKeyEvent {
|
||||
const RecoveryKeyStatusRefresh();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
part of 'recovery_key_bloc.dart';
|
||||
|
||||
sealed class RecoveryKeyState extends Equatable {
|
||||
RecoveryKeyState({
|
||||
required final RecoveryKeyStatus? keyStatus,
|
||||
}) : _hashCode = keyStatus.hashCode;
|
||||
|
||||
final int _hashCode;
|
||||
|
||||
RecoveryKeyStatus get _status =>
|
||||
getIt<ApiConnectionRepository>().apiData.recoveryKeyStatus.data ??
|
||||
const RecoveryKeyStatus(exists: false, valid: false);
|
||||
|
||||
bool get exists => _status.exists;
|
||||
bool get isValid => _status.valid;
|
||||
DateTime? get generatedAt => _status.date;
|
||||
DateTime? get expiresAt => _status.expiration;
|
||||
int? get usesLeft => _status.usesLeft;
|
||||
|
||||
bool get isInvalidBecauseExpired =>
|
||||
_status.expiration != null &&
|
||||
_status.expiration!.isBefore(DateTime.now());
|
||||
|
||||
bool get isInvalidBecauseUsed =>
|
||||
_status.usesLeft != null && _status.usesLeft == 0;
|
||||
}
|
||||
|
||||
class RecoveryKeyInitial extends RecoveryKeyState {
|
||||
RecoveryKeyInitial()
|
||||
: super(keyStatus: const RecoveryKeyStatus(exists: false, valid: false));
|
||||
|
||||
@override
|
||||
List<Object> get props => [_hashCode];
|
||||
}
|
||||
|
||||
class RecoveryKeyRefreshing extends RecoveryKeyState {
|
||||
RecoveryKeyRefreshing({required super.keyStatus});
|
||||
|
||||
@override
|
||||
List<Object> get props => [_hashCode];
|
||||
}
|
||||
|
||||
class RecoveryKeyLoaded extends RecoveryKeyState {
|
||||
RecoveryKeyLoaded({required super.keyStatus});
|
||||
|
||||
@override
|
||||
List<Object> get props => [_hashCode];
|
||||
}
|
||||
|
||||
class RecoveryKeyError extends RecoveryKeyState {
|
||||
RecoveryKeyError()
|
||||
: super(keyStatus: const RecoveryKeyStatus(exists: false, valid: false));
|
||||
|
||||
@override
|
||||
List<Object> get props => [_hashCode];
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
part 'server_jobs_state.dart';
|
||||
part 'server_jobs_event.dart';
|
||||
|
||||
class ServerJobsBloc extends Bloc<ServerJobsEvent, ServerJobsState> {
|
||||
ServerJobsBloc()
|
||||
: super(
|
||||
ServerJobsInitialState(),
|
||||
) {
|
||||
on<ServerJobsListChanged>(
|
||||
_mapServerJobsListChangedToState,
|
||||
transformer: sequential(),
|
||||
);
|
||||
on<RemoveServerJob>(
|
||||
_mapRemoveServerJobToState,
|
||||
transformer: sequential(),
|
||||
);
|
||||
on<RemoveAllFinishedJobs>(
|
||||
_mapRemoveAllFinishedJobsToState,
|
||||
transformer: droppable(),
|
||||
);
|
||||
|
||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||
(final ApiData apiData) {
|
||||
add(
|
||||
ServerJobsListChanged([...apiData.serverJobs.data ?? []]),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
StreamSubscription? _apiDataSubscription;
|
||||
|
||||
Future<void> _mapServerJobsListChangedToState(
|
||||
final ServerJobsListChanged event,
|
||||
final Emitter<ServerJobsState> emit,
|
||||
) async {
|
||||
if (event.serverJobList.isEmpty) {
|
||||
emit(ServerJobsListEmptyState());
|
||||
return;
|
||||
}
|
||||
final newState =
|
||||
ServerJobsListWithJobsState(serverJobList: event.serverJobList);
|
||||
emit(newState);
|
||||
}
|
||||
|
||||
Future<void> _mapRemoveServerJobToState(
|
||||
final RemoveServerJob event,
|
||||
final Emitter<ServerJobsState> emit,
|
||||
) async {
|
||||
await getIt<ApiConnectionRepository>().removeServerJob(event.uid);
|
||||
}
|
||||
|
||||
Future<void> _mapRemoveAllFinishedJobsToState(
|
||||
final RemoveAllFinishedJobs event,
|
||||
final Emitter<ServerJobsState> emit,
|
||||
) async {
|
||||
await getIt<ApiConnectionRepository>().removeAllFinishedServerJobs();
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(final Change<ServerJobsState> change) {
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_apiDataSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
part of 'server_jobs_bloc.dart';
|
||||
|
||||
sealed class ServerJobsEvent extends Equatable {
|
||||
const ServerJobsEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class ServerJobsListChanged extends ServerJobsEvent {
|
||||
const ServerJobsListChanged(this.serverJobList);
|
||||
|
||||
final List<ServerJob> serverJobList;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [serverJobList];
|
||||
}
|
||||
|
||||
class RemoveServerJob extends ServerJobsEvent {
|
||||
const RemoveServerJob(this.uid);
|
||||
|
||||
final String uid;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [uid];
|
||||
}
|
||||
|
||||
class RemoveAllFinishedJobs extends ServerJobsEvent {}
|
|
@ -0,0 +1,55 @@
|
|||
part of 'server_jobs_bloc.dart';
|
||||
|
||||
sealed class ServerJobsState extends Equatable {
|
||||
ServerJobsState({
|
||||
final int? hashCode,
|
||||
}) : _hashCode = hashCode ?? Object.hashAll([]);
|
||||
|
||||
final int? _hashCode;
|
||||
|
||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
|
||||
List<ServerJob> get _serverJobList =>
|
||||
apiConnectionRepository.apiData.serverJobs.data ?? [];
|
||||
|
||||
List<ServerJob> get serverJobList {
|
||||
try {
|
||||
final List<ServerJob> list = _serverJobList;
|
||||
list.sort((final a, final b) => b.createdAt.compareTo(a.createdAt));
|
||||
return list;
|
||||
} on UnsupportedError {
|
||||
return _serverJobList;
|
||||
}
|
||||
}
|
||||
|
||||
List<ServerJob> get backupJobList => serverJobList
|
||||
.where(
|
||||
// The backup jobs has the format of 'service.<service_id>.backup'
|
||||
(final job) =>
|
||||
job.typeId.contains('backup') || job.typeId.contains('restore'),
|
||||
)
|
||||
.toList();
|
||||
|
||||
bool get hasRemovableJobs => serverJobList.any(
|
||||
(final job) =>
|
||||
job.status == JobStatusEnum.finished ||
|
||||
job.status == JobStatusEnum.error,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [_hashCode];
|
||||
}
|
||||
|
||||
class ServerJobsInitialState extends ServerJobsState {
|
||||
ServerJobsInitialState() : super(hashCode: Object.hashAll([]));
|
||||
}
|
||||
|
||||
class ServerJobsListEmptyState extends ServerJobsState {
|
||||
ServerJobsListEmptyState() : super(hashCode: Object.hashAll([]));
|
||||
}
|
||||
|
||||
class ServerJobsListWithJobsState extends ServerJobsState {
|
||||
ServerJobsListWithJobsState({
|
||||
required final List<ServerJob> serverJobList,
|
||||
}) : super(hashCode: Object.hashAll([...serverJobList]));
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
|
||||
part 'services_event.dart';
|
||||
part 'services_state.dart';
|
||||
|
||||
class ServicesBloc extends Bloc<ServicesEvent, ServicesState> {
|
||||
ServicesBloc() : super(ServicesInitial()) {
|
||||
on<ServicesListUpdate>(
|
||||
_updateList,
|
||||
transformer: sequential(),
|
||||
);
|
||||
on<ServicesReload>(
|
||||
_reload,
|
||||
transformer: droppable(),
|
||||
);
|
||||
on<ServiceRestart>(
|
||||
_restart,
|
||||
transformer: sequential(),
|
||||
);
|
||||
on<ServiceMove>(
|
||||
_move,
|
||||
transformer: sequential(),
|
||||
);
|
||||
|
||||
final connectionRepository = getIt<ApiConnectionRepository>();
|
||||
inex marked this conversation as resolved
misterfourtytwo
commented
Review
imho handler registration should preceed event subscription. not sure how it works for bloc default state now, but it could easily drop event from 26 line, because handler was not yet registered. imho handler registration should preceed event subscription. not sure how it works for bloc default state now, but it could easily drop event from 26 line, because handler was not yet registered.
|
||||
|
||||
_apiDataSubscription = connectionRepository.dataStream.listen(
|
||||
(final ApiData apiData) {
|
||||
add(
|
||||
ServicesListUpdate([...apiData.services.data ?? []]),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (connectionRepository.connectionStatus == ConnectionStatus.connected) {
|
||||
add(
|
||||
ServicesListUpdate(
|
||||
[...connectionRepository.apiData.services.data ?? []],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateList(
|
||||
final ServicesListUpdate event,
|
||||
final Emitter<ServicesState> emit,
|
||||
) async {
|
||||
if (event.services.isEmpty) {
|
||||
emit(ServicesInitial());
|
||||
return;
|
||||
}
|
||||
final newState = ServicesLoaded(
|
||||
services: event.services,
|
||||
lockedServices: state._lockedServices,
|
||||
);
|
||||
emit(newState);
|
||||
}
|
||||
|
||||
Future<void> _reload(
|
||||
final ServicesReload event,
|
||||
final Emitter<ServicesState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is ServicesLoaded) {
|
||||
emit(ServicesReloading.fromState(currentState));
|
||||
getIt<ApiConnectionRepository>().apiData.services.invalidate();
|
||||
await getIt<ApiConnectionRepository>().reload(null);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> awaitReload() async {
|
||||
final currentState = state;
|
||||
if (currentState is ServicesLoaded) {
|
||||
getIt<ApiConnectionRepository>().apiData.services.invalidate();
|
||||
await getIt<ApiConnectionRepository>().reload(null);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _restart(
|
||||
final ServiceRestart event,
|
||||
final Emitter<ServicesState> emit,
|
||||
) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
lockedServices: [
|
||||
...state._lockedServices,
|
||||
ServiceLock(
|
||||
serviceId: event.service.id,
|
||||
lockDuration: const Duration(seconds: 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
final result = await getIt<ApiConnectionRepository>()
|
||||
.api
|
||||
.restartService(event.service.id);
|
||||
if (!result.success) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.generic_error'.tr());
|
||||
return;
|
||||
}
|
||||
if (!result.data) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _move(
|
||||
final ServiceMove event,
|
||||
final Emitter<ServicesState> emit,
|
||||
) async {
|
||||
final migrationJob = await getIt<ApiConnectionRepository>()
|
||||
.api
|
||||
.moveService(event.service.id, event.destination);
|
||||
if (!migrationJob.success) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(migrationJob.message ?? 'jobs.generic_error'.tr());
|
||||
}
|
||||
if (migrationJob.data != null) {
|
||||
getIt<ApiConnectionRepository>()
|
||||
.apiData
|
||||
.serverJobs
|
||||
.data
|
||||
?.add(migrationJob.data!);
|
||||
getIt<ApiConnectionRepository>().emitData();
|
||||
}
|
||||
}
|
||||
|
||||
late StreamSubscription _apiDataSubscription;
|
||||
|
||||
@override
|
||||
void onChange(final Change<ServicesState> change) {
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_apiDataSubscription.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
part of 'services_bloc.dart';
|
||||
|
||||
sealed class ServicesEvent extends Equatable {
|
||||
const ServicesEvent();
|
||||
}
|
||||
|
||||
class ServicesListUpdate extends ServicesEvent {
|
||||
const ServicesListUpdate(this.services);
|
||||
|
||||
final List<Service> services;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [services];
|
||||
}
|
||||
|
||||
class ServicesReload extends ServicesEvent {
|
||||
const ServicesReload();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class ServiceRestart extends ServicesEvent {
|
||||
const ServiceRestart(this.service);
|
||||
|
||||
final Service service;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [service];
|
||||
}
|
||||
|
||||
class ServiceMove extends ServicesEvent {
|
||||
const ServiceMove(this.service, this.destination);
|
||||
|
||||
final Service service;
|
||||
final String destination;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [service, destination];
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
part of 'services_bloc.dart';
|
||||
|
||||
sealed class ServicesState extends Equatable {
|
||||
ServicesState({final List<ServiceLock> lockedServices = const []})
|
||||
: _lockedServices =
|
||||
lockedServices.where((final lock) => lock.isLocked).toList();
|
||||
final List<ServiceLock> _lockedServices;
|
||||
List<Service> get services;
|
||||
List<String> get lockedServices => _lockedServices
|
||||
.where((final lock) => lock.isLocked)
|
||||
.map((final lock) => lock.serviceId)
|
||||
.toList();
|
||||
|
||||
List<Service> get servicesThatCanBeBackedUp => services
|
||||
.where(
|
||||
(final service) => service.canBeBackedUp,
|
||||
)
|
||||
.toList();
|
||||
|
||||
bool isServiceLocked(final String serviceId) =>
|
||||
lockedServices.contains(serviceId);
|
||||
|
||||
Service? getServiceById(final String id) {
|
||||
final service = services.firstWhere(
|
||||
(final service) => service.id == id,
|
||||
orElse: () => Service.empty,
|
||||
);
|
||||
if (service.id == 'empty') {
|
||||
return null;
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
ServicesState copyWith({
|
||||
final List<Service>? services,
|
||||
final List<ServiceLock>? lockedServices,
|
||||
});
|
||||
}
|
||||
|
||||
class ServiceLock extends Equatable {
|
||||
ServiceLock({
|
||||
required this.serviceId,
|
||||
required this.lockDuration,
|
||||
}) : lockTime = DateTime.now();
|
||||
|
||||
final String serviceId;
|
||||
final Duration lockDuration;
|
||||
final DateTime lockTime;
|
||||
|
||||
bool get isLocked => DateTime.now().isBefore(lockTime.add(lockDuration));
|
||||
|
||||
@override
|
||||
List<Object?> get props => [serviceId, lockDuration, lockTime];
|
||||
}
|
||||
|
||||
class ServicesInitial extends ServicesState {
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
|
||||
@override
|
||||
List<Service> get services => [];
|
||||
|
||||
@override
|
||||
ServicesState copyWith({
|
||||
final List<Service>? services,
|
||||
final List<ServiceLock>? lockedServices,
|
||||
}) =>
|
||||
ServicesInitial();
|
||||
}
|
||||
|
||||
class ServicesLoaded extends ServicesState {
|
||||
ServicesLoaded({
|
||||
required final List<Service> services,
|
||||
required super.lockedServices,
|
||||
}) : _servicesHachCode = Object.hashAll([...services]);
|
||||
|
||||
final int _servicesHachCode;
|
||||
|
||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
|
||||
List<Service> get _services =>
|
||||
apiConnectionRepository.apiData.services.data ?? [];
|
||||
|
||||
@override
|
||||
List<Service> get services => _services;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [_servicesHachCode, _lockedServices];
|
||||
|
||||
@override
|
||||
ServicesLoaded copyWith({
|
||||
final List<Service>? services,
|
||||
final List<ServiceLock>? lockedServices,
|
||||
}) =>
|
||||
ServicesLoaded(
|
||||
services: services ?? this.services,
|
||||
lockedServices: lockedServices ?? _lockedServices,
|
||||
);
|
||||
}
|
||||
|
||||
class ServicesReloading extends ServicesLoaded {
|
||||
ServicesReloading({
|
||||
required super.services,
|
||||
required super.lockedServices,
|
||||
});
|
||||
|
||||
ServicesReloading.fromState(final ServicesLoaded state)
|
||||
: super(
|
||||
services: state.services,
|
||||
lockedServices: state._lockedServices,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [services, lockedServices];
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||
|
||||
part 'users_event.dart';
|
||||
part 'users_state.dart';
|
||||
|
||||
class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
||||
UsersBloc() : super(UsersInitial()) {
|
||||
on<UsersListChanged>(
|
||||
_updateList,
|
||||
transformer: sequential(),
|
||||
);
|
||||
on<UsersListRefresh>(
|
||||
_reload,
|
||||
transformer: droppable(),
|
||||
);
|
||||
on<UsersConnectionStatusChanged>(
|
||||
_mapConnectionStatusChangedToState,
|
||||
transformer: sequential(),
|
||||
);
|
||||
|
||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
_apiConnectionStatusSubscription =
|
||||
apiConnectionRepository.connectionStatusStream.listen(
|
||||
(final ConnectionStatus connectionStatus) {
|
||||
add(
|
||||
UsersConnectionStatusChanged(connectionStatus),
|
||||
);
|
||||
},
|
||||
);
|
||||
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||
(final ApiData apiData) {
|
||||
add(
|
||||
UsersListChanged(apiData.users.data ?? []),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateList(
|
||||
final UsersListChanged event,
|
||||
final Emitter<UsersState> emit,
|
||||
) async {
|
||||
if (event.users.isEmpty) {
|
||||
emit(UsersInitial());
|
||||
return;
|
||||
}
|
||||
final newState = UsersLoaded(
|
||||
users: event.users,
|
||||
);
|
||||
emit(newState);
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
getIt<ApiConnectionRepository>().apiData.users.invalidate();
|
||||
await getIt<ApiConnectionRepository>().reload(null);
|
||||
}
|
||||
|
||||
Future<void> _reload(
|
||||
final UsersListRefresh event,
|
||||
final Emitter<UsersState> emit,
|
||||
) async {
|
||||
emit(UsersRefreshing(users: state.users));
|
||||
await refresh();
|
||||
}
|
||||
|
||||
Future<void> _mapConnectionStatusChangedToState(
|
||||
final UsersConnectionStatusChanged event,
|
||||
final Emitter<UsersState> emit,
|
||||
) async {
|
||||
switch (event.connectionStatus) {
|
||||
case ConnectionStatus.nonexistent:
|
||||
emit(UsersInitial());
|
||||
break;
|
||||
case ConnectionStatus.connected:
|
||||
if (state is! UsersLoaded) {
|
||||
emit(UsersRefreshing(users: state.users));
|
||||
}
|
||||
case ConnectionStatus.reconnecting:
|
||||
case ConnectionStatus.offline:
|
||||
case ConnectionStatus.unauthorized:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
StreamSubscription? _apiDataSubscription;
|
||||
StreamSubscription? _apiConnectionStatusSubscription;
|
||||
|
||||
@override
|
||||
void onChange(final Change<UsersState> change) {
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_apiDataSubscription?.cancel();
|
||||
_apiConnectionStatusSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
part of 'users_bloc.dart';
|
||||
|
||||
sealed class UsersEvent extends Equatable {
|
||||
const UsersEvent();
|
||||
}
|
||||
|
||||
class UsersListChanged extends UsersEvent {
|
||||
const UsersListChanged(this.users);
|
||||
|
||||
final List<User> users;
|
||||
|
||||
@override
|
||||
List<Object> get props => [users];
|
||||
}
|
||||
|
||||
class UsersListRefresh extends UsersEvent {
|
||||
const UsersListRefresh();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class UsersConnectionStatusChanged extends UsersEvent {
|
||||
const UsersConnectionStatusChanged(this.connectionStatus);
|
||||
|
||||
final ConnectionStatus connectionStatus;
|
||||
|
||||
@override
|
||||
List<Object> get props => [connectionStatus];
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
part of 'users_cubit.dart';
|
||||
part of 'users_bloc.dart';
|
||||
|
||||
class UsersState extends ServerInstallationDependendState {
|
||||
const UsersState(this.users, this.isLoading);
|
||||
sealed class UsersState extends Equatable {
|
||||
UsersState({
|
||||
required final List<User> users,
|
||||
}) : _hashCode = Object.hashAll(users);
|
||||
|
||||
final List<User> users;
|
||||
final bool isLoading;
|
||||
final int _hashCode;
|
||||
|
||||
List<User> get users =>
|
||||
getIt<ApiConnectionRepository>().apiData.users.data ?? const [];
|
||||
|
||||
User get rootUser =>
|
||||
users.firstWhere((final user) => user.type == UserType.root);
|
||||
|
@ -15,9 +19,6 @@ class UsersState extends ServerInstallationDependendState {
|
|||
List<User> get normalUsers =>
|
||||
users.where((final user) => user.type == UserType.normal).toList();
|
||||
|
||||
@override
|
||||
List<Object> get props => [users, isLoading];
|
||||
|
||||
/// Makes a copy of existing users list, but places 'primary'
|
||||
/// to the beginning and sorts the rest alphabetically
|
||||
///
|
||||
|
@ -44,17 +45,29 @@ class UsersState extends ServerInstallationDependendState {
|
|||
return primaryUser == null ? normalUsers : [primaryUser] + normalUsers;
|
||||
}
|
||||
|
||||
UsersState copyWith({
|
||||
final List<User>? users,
|
||||
final bool? isLoading,
|
||||
}) =>
|
||||
UsersState(
|
||||
users ?? this.users,
|
||||
isLoading ?? this.isLoading,
|
||||
);
|
||||
|
||||
bool isLoginRegistered(final String login) =>
|
||||
users.any((final User user) => user.login == login);
|
||||
|
||||
bool get isEmpty => users.isEmpty;
|
||||
}
|
||||
|
||||
class UsersInitial extends UsersState {
|
||||
UsersInitial() : super(users: const []);
|
||||
|
||||
@override
|
||||
List<Object> get props => [_hashCode];
|
||||
}
|
||||
|
||||
class UsersRefreshing extends UsersState {
|
||||
UsersRefreshing({required super.users});
|
||||
|
||||
@override
|
||||
List<Object> get props => [_hashCode];
|
||||
}
|
||||
|
||||
class UsersLoaded extends UsersState {
|
||||
UsersLoaded({required super.users});
|
||||
|
||||
@override
|
||||
List<Object> get props => [_hashCode];
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_disk_volume.dart';
|
||||
import 'package:selfprivacy/logic/models/price.dart';
|
||||
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
||||
|
||||
part 'volumes_event.dart';
|
||||
part 'volumes_state.dart';
|
||||
|
||||
class VolumesBloc extends Bloc<VolumesEvent, VolumesState> {
|
||||
VolumesBloc() : super(VolumesInitial()) {
|
||||
on<VolumesServerLoaded>(
|
||||
_loadState,
|
||||
transformer: droppable(),
|
||||
);
|
||||
on<VolumesServerReset>(
|
||||
_resetState,
|
||||
transformer: droppable(),
|
||||
);
|
||||
on<VolumesServerStateChanged>(
|
||||
_updateState,
|
||||
transformer: droppable(),
|
||||
);
|
||||
on<VolumeResize>(
|
||||
_resizeVolume,
|
||||
transformer: droppable(),
|
||||
);
|
||||
|
||||
final connectionRepository = getIt<ApiConnectionRepository>();
|
||||
|
||||
_apiStatusSubscription = connectionRepository.connectionStatusStream
|
||||
.listen((final ConnectionStatus connectionStatus) {
|
||||
switch (connectionStatus) {
|
||||
case ConnectionStatus.nonexistent:
|
||||
add(const VolumesServerReset());
|
||||
isLoaded = false;
|
||||
break;
|
||||
case ConnectionStatus.connected:
|
||||
if (!isLoaded) {
|
||||
add(const VolumesServerLoaded());
|
||||
isLoaded = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
_apiDataSubscription = connectionRepository.dataStream.listen(
|
||||
(final ApiData apiData) {
|
||||
if (apiData.volumes.data == null) {
|
||||
add(const VolumesServerReset());
|
||||
} else {
|
||||
add(
|
||||
VolumesServerStateChanged(
|
||||
apiData.volumes.data!,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
late StreamSubscription _apiStatusSubscription;
|
||||
late StreamSubscription _apiDataSubscription;
|
||||
bool isLoaded = false;
|
||||
|
||||
Future<Price?> getPricePerGb() async {
|
||||
if (ProvidersController.currentServerProvider == null) {
|
||||
return null;
|
||||
}
|
||||
Price? price;
|
||||
final pricingResult =
|
||||
await ProvidersController.currentServerProvider!.getAdditionalPricing();
|
||||
if (pricingResult.data == null || !pricingResult.success) {
|
||||
getIt<NavigationService>().showSnackBar('server.pricing_error'.tr());
|
||||
return price;
|
||||
}
|
||||
price = pricingResult.data!.perVolumeGb;
|
||||
return price;
|
||||
}
|
||||
|
||||
Future<void> _loadState(
|
||||
final VolumesServerLoaded event,
|
||||
final Emitter<VolumesState> emit,
|
||||
) async {
|
||||
if (ProvidersController.currentServerProvider == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit(VolumesLoading());
|
||||
|
||||
final volumesResult =
|
||||
await ProvidersController.currentServerProvider!.getVolumes();
|
||||
|
||||
if (!volumesResult.success || volumesResult.data.isEmpty) {
|
||||
emit(VolumesInitial());
|
||||
return;
|
||||
}
|
||||
|
||||
final serverVolumes = getIt<ApiConnectionRepository>().apiData.volumes.data;
|
||||
|
||||
if (serverVolumes == null) {
|
||||
emit(VolumesLoading(providerVolumes: volumesResult.data));
|
||||
return;
|
||||
} else {
|
||||
emit(
|
||||
VolumesLoaded(
|
||||
diskStatus: DiskStatus.fromVolumes(
|
||||
serverVolumes,
|
||||
volumesResult.data,
|
||||
),
|
||||
providerVolumes: volumesResult.data,
|
||||
serverVolumesHashCode: Object.hashAll(serverVolumes),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _resetState(
|
||||
final VolumesServerReset event,
|
||||
final Emitter<VolumesState> emit,
|
||||
) async {
|
||||
emit(VolumesInitial());
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(final Change<VolumesState> change) {
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _apiStatusSubscription.cancel();
|
||||
await _apiDataSubscription.cancel();
|
||||
await super.close();
|
||||
}
|
||||
|
||||
Future<void> invalidateCache() async {
|
||||
getIt<ApiConnectionRepository>().apiData.volumes.invalidate();
|
||||
}
|
||||
|
||||
Future<void> _updateState(
|
||||
final VolumesServerStateChanged event,
|
||||
final Emitter<VolumesState> emit,
|
||||
) async {
|
||||
final serverVolumes = event.volumes;
|
||||
final providerVolumes = state.providerVolumes;
|
||||
if (state is VolumesLoading) {
|
||||
emit(
|
||||
VolumesLoaded(
|
||||
diskStatus: DiskStatus.fromVolumes(
|
||||
serverVolumes,
|
||||
providerVolumes,
|
||||
),
|
||||
providerVolumes: providerVolumes,
|
||||
serverVolumesHashCode: Object.hashAll(serverVolumes),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
emit(
|
||||
state.copyWith(
|
||||
diskStatus: DiskStatus.fromVolumes(
|
||||
serverVolumes,
|
||||
providerVolumes,
|
||||
),
|
||||
providerVolumes: providerVolumes,
|
||||
serverVolumesHashCode: Object.hashAll(serverVolumes),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _resizeVolume(
|
||||
final VolumeResize event,
|
||||
final Emitter<VolumesState> emit,
|
||||
) async {
|
||||
if (state is! VolumesLoaded) {
|
||||
return;
|
||||
}
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'storage.extending_volume_started'.tr(),
|
||||
);
|
||||
emit(
|
||||
VolumesResizing(
|
||||
serverVolumesHashCode: state._serverVolumesHashCode,
|
||||
diskStatus: state.diskStatus,
|
||||
providerVolumes: state.providerVolumes,
|
||||
),
|
||||
);
|
||||
|
||||
final resizedResult =
|
||||
await ProvidersController.currentServerProvider!.resizeVolume(
|
||||
event.volume.providerVolume!,
|
||||
event.newSize,
|
||||
);
|
||||
|
||||
if (!resizedResult.success || !resizedResult.data) {
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'storage.extending_volume_error'.tr(),
|
||||
);
|
||||
emit(
|
||||
VolumesLoaded(
|
||||
serverVolumesHashCode: state._serverVolumesHashCode,
|
||||
diskStatus: state.diskStatus,
|
||||
providerVolumes: state.providerVolumes,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'storage.extending_volume_waiting'.tr(),
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(seconds: 10));
|
||||
|
||||
await getIt<ApiConnectionRepository>().api.resizeVolume(event.volume.name);
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'storage.extending_volume_server_waiting'.tr(),
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(seconds: 20));
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'storage.extending_volume_rebooting'.tr(),
|
||||
);
|
||||
|
||||
emit(
|
||||
VolumesLoaded(
|
||||
serverVolumesHashCode: state._serverVolumesHashCode,
|
||||
diskStatus: state.diskStatus,
|
||||
providerVolumes: state.providerVolumes,
|
||||
),
|
||||
);
|
||||
|
||||
await getIt<ApiConnectionRepository>().api.reboot();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
part of 'volumes_bloc.dart';
|
||||
|
||||
sealed class VolumesEvent extends Equatable {
|
||||
const VolumesEvent();
|
||||
}
|
||||
|
||||
class VolumesServerLoaded extends VolumesEvent {
|
||||
const VolumesServerLoaded();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class VolumesServerReset extends VolumesEvent {
|
||||
const VolumesServerReset();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class VolumesServerStateChanged extends VolumesEvent {
|
||||
const VolumesServerStateChanged(
|
||||
this.volumes,
|
||||
);
|
||||
|
||||
final List<ServerDiskVolume> volumes;
|
||||
|
||||
@override
|
||||
List<Object> get props => [volumes];
|
||||
}
|
||||
|
||||
class VolumeResize extends VolumesEvent {
|
||||
const VolumeResize(
|
||||
this.volume,
|
||||
this.newSize,
|
||||
);
|
||||
|
||||
final DiskVolume volume;
|
||||
final DiskSize newSize;
|
||||
|
||||
@override
|
||||
List<Object> get props => [volume, newSize];
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
part of 'volumes_bloc.dart';
|
||||
|
||||
sealed class VolumesState extends Equatable {
|
||||
const VolumesState({
|
||||
required this.diskStatus,
|
||||
required final serverVolumesHashCode,
|
||||
this.providerVolumes = const [],
|
||||
}) : _serverVolumesHashCode = serverVolumesHashCode;
|
||||
|
||||
final DiskStatus diskStatus;
|
||||
final List<ServerProviderVolume> providerVolumes;
|
||||
List<DiskVolume> get volumes => diskStatus.diskVolumes;
|
||||
final int? _serverVolumesHashCode;
|
||||
|
||||
DiskVolume getVolume(final String volumeName) => volumes.firstWhere(
|
||||
(final volume) => volume.name == volumeName,
|
||||
orElse: () => DiskVolume(),
|
||||
);
|
||||
|
||||
bool get isProviderVolumesLoaded => providerVolumes.isNotEmpty;
|
||||
|
||||
VolumesState copyWith({
|
||||
required final int? serverVolumesHashCode,
|
||||
final DiskStatus? diskStatus,
|
||||
final List<ServerProviderVolume>? providerVolumes,
|
||||
});
|
||||
}
|
||||
|
||||
class VolumesInitial extends VolumesState {
|
||||
VolumesInitial()
|
||||
: super(
|
||||
diskStatus: DiskStatus(),
|
||||
serverVolumesHashCode: null,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [providerVolumes, _serverVolumesHashCode];
|
||||
|
||||
@override
|
||||
VolumesInitial copyWith({
|
||||
required final int? serverVolumesHashCode,
|
||||
final DiskStatus? diskStatus,
|
||||
final List<ServerProviderVolume>? providerVolumes,
|
||||
}) =>
|
||||
VolumesInitial();
|
||||
}
|
||||
|
||||
class VolumesLoading extends VolumesState {
|
||||
VolumesLoading({
|
||||
super.serverVolumesHashCode,
|
||||
final DiskStatus? diskStatus,
|
||||
final List<ServerProviderVolume>? providerVolumes,
|
||||
}) : super(
|
||||
diskStatus: diskStatus ?? DiskStatus(),
|
||||
providerVolumes: providerVolumes ?? const [],
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [providerVolumes, _serverVolumesHashCode];
|
||||
|
||||
@override
|
||||
VolumesLoading copyWith({
|
||||
required final int? serverVolumesHashCode,
|
||||
final DiskStatus? diskStatus,
|
||||
final List<ServerProviderVolume>? providerVolumes,
|
||||
}) =>
|
||||
VolumesLoading(
|
||||
diskStatus: diskStatus ?? this.diskStatus,
|
||||
providerVolumes: providerVolumes ?? this.providerVolumes,
|
||||
serverVolumesHashCode: serverVolumesHashCode ?? _serverVolumesHashCode!,
|
||||
);
|
||||
}
|
||||
|
||||
class VolumesLoaded extends VolumesState {
|
||||
const VolumesLoaded({
|
||||
required super.serverVolumesHashCode,
|
||||
required super.diskStatus,
|
||||
final List<ServerProviderVolume>? providerVolumes,
|
||||
}) : super(
|
||||
providerVolumes: providerVolumes ?? const [],
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [providerVolumes, _serverVolumesHashCode];
|
||||
|
||||
@override
|
||||
VolumesLoaded copyWith({
|
||||
final DiskStatus? diskStatus,
|
||||
final List<ServerProviderVolume>? providerVolumes,
|
||||
final int? serverVolumesHashCode,
|
||||
}) =>
|
||||
VolumesLoaded(
|
||||
diskStatus: diskStatus ?? this.diskStatus,
|
||||
providerVolumes: providerVolumes ?? this.providerVolumes,
|
||||
serverVolumesHashCode: serverVolumesHashCode ?? _serverVolumesHashCode!,
|
||||
);
|
||||
}
|
||||
|
||||
class VolumesResizing extends VolumesState {
|
||||
const VolumesResizing({
|
||||
required super.serverVolumesHashCode,
|
||||
required super.diskStatus,
|
||||
final List<ServerProviderVolume>? providerVolumes,
|
||||
}) : super(
|
||||
providerVolumes: providerVolumes ?? const [],
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [providerVolumes, _serverVolumesHashCode];
|
||||
|
||||
@override
|
||||
VolumesResizing copyWith({
|
||||
final DiskStatus? diskStatus,
|
||||
final List<ServerProviderVolume>? providerVolumes,
|
||||
final int? serverVolumesHashCode,
|
||||
}) =>
|
||||
VolumesResizing(
|
||||
diskStatus: diskStatus ?? this.diskStatus,
|
||||
providerVolumes: providerVolumes ?? this.providerVolumes,
|
||||
serverVolumesHashCode: serverVolumesHashCode ?? _serverVolumesHashCode!,
|
||||
);
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
|
||||
export 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
|
||||
part 'authentication_dependend_state.dart';
|
||||
|
||||
abstract class ServerInstallationDependendCubit<
|
||||
T extends ServerInstallationDependendState> extends Cubit<T> {
|
||||
ServerInstallationDependendCubit(
|
||||
this.serverInstallationCubit,
|
||||
final T initState,
|
||||
) : super(initState) {
|
||||
authCubitSubscription =
|
||||
serverInstallationCubit.stream.listen(checkAuthStatus);
|
||||
checkAuthStatus(serverInstallationCubit.state);
|
||||
}
|
||||
|
||||
void checkAuthStatus(final ServerInstallationState state) {
|
||||
if (state is ServerInstallationFinished) {
|
||||
load();
|
||||
} else if (state is ServerInstallationEmpty) {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
late StreamSubscription authCubitSubscription;
|
||||
final ServerInstallationCubit serverInstallationCubit;
|
||||
|
||||
void load();
|
||||
void clear();
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
authCubitSubscription.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
|
@ -1,279 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/backup.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
|
||||
import 'package:selfprivacy/logic/models/initialize_repository_input.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
|
||||
part 'backups_state.dart';
|
||||
|
||||
class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
|
||||
BackupsCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(
|
||||
serverInstallationCubit,
|
||||
const BackupsState(preventActions: true),
|
||||
);
|
||||
|
||||
final ServerApi api = ServerApi();
|
||||
final BackblazeApi backblaze = BackblazeApi();
|
||||
|
||||
@override
|
||||
Future<void> load() async {
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
|
||||
final BackupConfiguration? backupConfig =
|
||||
await api.getBackupsConfiguration();
|
||||
final List<Backup> backups = await api.getBackups();
|
||||
backups.sort((final a, final b) => b.time.compareTo(a.time));
|
||||
emit(
|
||||
state.copyWith(
|
||||
backblazeBucket: bucket,
|
||||
isInitialized: backupConfig?.isInitialized,
|
||||
autobackupPeriod: backupConfig?.autobackupPeriod ?? Duration.zero,
|
||||
autobackupQuotas: backupConfig?.autobackupQuotas,
|
||||
backups: backups,
|
||||
preventActions: false,
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initializeBackups() async {
|
||||
emit(state.copyWith(preventActions: true));
|
||||
final String? encryptionKey =
|
||||
(await api.getBackupsConfiguration())?.encryptionKey;
|
||||
if (encryptionKey == null) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar("Couldn't get encryption key from your server.");
|
||||
emit(state.copyWith(preventActions: false));
|
||||
return;
|
||||
}
|
||||
|
||||
final BackblazeBucket bucket;
|
||||
|
||||
if (state.backblazeBucket == null) {
|
||||
final String domain = serverInstallationCubit
|
||||
.state.serverDomain!.domainName
|
||||
.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
|
||||
final int serverId = serverInstallationCubit.state.serverDetails!.id;
|
||||
String bucketName =
|
||||
'${DateTime.now().millisecondsSinceEpoch}-$serverId-$domain';
|
||||
if (bucketName.length > 49) {
|
||||
bucketName = bucketName.substring(0, 49);
|
||||
}
|
||||
final String bucketId = await backblaze.createBucket(bucketName);
|
||||
|
||||
final BackblazeApplicationKey key = await backblaze.createKey(bucketId);
|
||||
bucket = BackblazeBucket(
|
||||
bucketId: bucketId,
|
||||
bucketName: bucketName,
|
||||
applicationKey: key.applicationKey,
|
||||
applicationKeyId: key.applicationKeyId,
|
||||
encryptionKey: encryptionKey,
|
||||
);
|
||||
|
||||
await getIt<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) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'Unknown error');
|
||||
emit(state.copyWith(preventActions: false));
|
||||
return;
|
||||
}
|
||||
await updateBackups();
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'Backups repository is now initializing. It may take a while.',
|
||||
);
|
||||
|
||||
emit(state.copyWith(preventActions: false));
|
||||
}
|
||||
|
||||
Future<void> reuploadKey() async {
|
||||
emit(state.copyWith(preventActions: true));
|
||||
BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
|
||||
if (bucket == null) {
|
||||
emit(state.copyWith(isInitialized: false));
|
||||
} else {
|
||||
String login = bucket.applicationKeyId;
|
||||
String password = bucket.applicationKey;
|
||||
if (login.isEmpty || password.isEmpty) {
|
||||
final BackblazeApplicationKey key =
|
||||
await backblaze.createKey(bucket.bucketId);
|
||||
login = key.applicationKeyId;
|
||||
password = key.applicationKey;
|
||||
bucket = BackblazeBucket(
|
||||
bucketId: bucket.bucketId,
|
||||
bucketName: bucket.bucketName,
|
||||
encryptionKey: bucket.encryptionKey,
|
||||
applicationKey: password,
|
||||
applicationKeyId: login,
|
||||
);
|
||||
await getIt<ApiConfigModel>().storeBackblazeBucket(bucket);
|
||||
emit(state.copyWith(backblazeBucket: bucket));
|
||||
}
|
||||
final GenericResult result = await api.initializeRepository(
|
||||
InitializeRepositoryInput(
|
||||
provider: BackupsProviderType.backblaze,
|
||||
locationId: bucket.bucketId,
|
||||
locationName: bucket.bucketName,
|
||||
login: login,
|
||||
password: password,
|
||||
),
|
||||
);
|
||||
if (result.success == false) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'Unknown error');
|
||||
emit(state.copyWith(preventActions: false));
|
||||
return;
|
||||
} else {
|
||||
emit(state.copyWith(preventActions: false));
|
||||
getIt<NavigationService>().showSnackBar('backup.reuploaded_key'.tr());
|
||||
await updateBackups();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("we don't have states")
|
||||
Duration refreshTimeFromState() => const Duration(seconds: 60);
|
||||
|
||||
Future<void> updateBackups({final bool useTimer = false}) async {
|
||||
emit(state.copyWith(refreshing: true));
|
||||
final backups = await api.getBackups();
|
||||
backups.sort((final a, final b) => b.time.compareTo(a.time));
|
||||
final backupConfig = await api.getBackupsConfiguration();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
backups: backups,
|
||||
refreshTimer: refreshTimeFromState(),
|
||||
refreshing: false,
|
||||
isInitialized: backupConfig?.isInitialized ?? false,
|
||||
autobackupPeriod: backupConfig?.autobackupPeriod,
|
||||
autobackupQuotas: backupConfig?.autobackupQuotas,
|
||||
),
|
||||
);
|
||||
if (useTimer) {
|
||||
Timer(state.refreshTimer, () => updateBackups(useTimer: true));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> forceUpdateBackups() async {
|
||||
emit(state.copyWith(preventActions: true));
|
||||
getIt<NavigationService>().showSnackBar('backup.refetching_list'.tr());
|
||||
await api.forceBackupListReload();
|
||||
emit(state.copyWith(preventActions: false));
|
||||
}
|
||||
|
||||
Future<void> createMultipleBackups(final List<Service> services) async {
|
||||
emit(state.copyWith(preventActions: true));
|
||||
for (final service in services) {
|
||||
await api.startBackup(service.id);
|
||||
}
|
||||
await updateBackups();
|
||||
emit(state.copyWith(preventActions: false));
|
||||
}
|
||||
|
||||
Future<void> createBackup(final String serviceId) async {
|
||||
emit(state.copyWith(preventActions: true));
|
||||
await api.startBackup(serviceId);
|
||||
await updateBackups();
|
||||
emit(state.copyWith(preventActions: false));
|
||||
}
|
||||
|
||||
Future<void> restoreBackup(
|
||||
final String backupId,
|
||||
final BackupRestoreStrategy strategy,
|
||||
) async {
|
||||
emit(state.copyWith(preventActions: true));
|
||||
await api.restoreBackup(backupId, strategy);
|
||||
emit(state.copyWith(preventActions: false));
|
||||
}
|
||||
|
||||
Future<void> setAutobackupPeriod(final Duration? period) async {
|
||||
emit(state.copyWith(preventActions: true));
|
||||
final result = await api.setAutobackupPeriod(period: period?.inMinutes);
|
||||
if (result.success == false) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'Unknown error');
|
||||
emit(state.copyWith(preventActions: false));
|
||||
} else {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('backup.autobackup_period_set'.tr());
|
||||
emit(
|
||||
state.copyWith(
|
||||
preventActions: false,
|
||||
autobackupPeriod: period ?? Duration.zero,
|
||||
),
|
||||
);
|
||||
}
|
||||
await updateBackups();
|
||||
}
|
||||
|
||||
Future<void> setAutobackupQuotas(final AutobackupQuotas quotas) async {
|
||||
emit(state.copyWith(preventActions: true));
|
||||
final result = await api.setAutobackupQuotas(quotas);
|
||||
if (result.success == false) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'Unknown error');
|
||||
emit(state.copyWith(preventActions: false));
|
||||
} else {
|
||||
getIt<NavigationService>().showSnackBar('backup.quotas_set'.tr());
|
||||
emit(
|
||||
state.copyWith(
|
||||
preventActions: false,
|
||||
autobackupQuotas: quotas,
|
||||
),
|
||||
);
|
||||
}
|
||||
await updateBackups();
|
||||
}
|
||||
|
||||
Future<void> forgetSnapshot(final String snapshotId) async {
|
||||
final result = await api.forgetSnapshot(snapshotId);
|
||||
if (!result.success) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.generic_error'.tr());
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.data == false) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('backup.forget_snapshot_error'.tr());
|
||||
}
|
||||
|
||||
// Optimistic update
|
||||
final backups = state.backups;
|
||||
final index =
|
||||
backups.indexWhere((final snapshot) => snapshot.id == snapshotId);
|
||||
if (index != -1) {
|
||||
backups.removeAt(index);
|
||||
emit(state.copyWith(backups: backups));
|
||||
}
|
||||
|
||||
await updateBackups();
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() async {
|
||||
emit(const BackupsState());
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
part of 'backups_cubit.dart';
|
||||
|
||||
class BackupsState extends ServerInstallationDependendState {
|
||||
const BackupsState({
|
||||
this.isInitialized = false,
|
||||
this.backups = const [],
|
||||
this.preventActions = true,
|
||||
this.refreshTimer = const Duration(seconds: 60),
|
||||
this.refreshing = true,
|
||||
this.autobackupPeriod,
|
||||
this.backblazeBucket,
|
||||
this.autobackupQuotas,
|
||||
});
|
||||
|
||||
final bool isInitialized;
|
||||
final List<Backup> backups;
|
||||
final bool preventActions;
|
||||
final Duration refreshTimer;
|
||||
final bool refreshing;
|
||||
final Duration? autobackupPeriod;
|
||||
final BackblazeBucket? backblazeBucket;
|
||||
final AutobackupQuotas? autobackupQuotas;
|
||||
|
||||
List<Backup> serviceBackups(final String serviceId) => backups
|
||||
.where((final backup) => backup.serviceId == serviceId)
|
||||
.toList(growable: false);
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
isInitialized,
|
||||
backups,
|
||||
preventActions,
|
||||
refreshTimer,
|
||||
refreshing,
|
||||
];
|
||||
|
||||
BackupsState copyWith({
|
||||
final bool? isInitialized,
|
||||
final List<Backup>? backups,
|
||||
final bool? preventActions,
|
||||
final Duration? refreshTimer,
|
||||
final bool? refreshing,
|
||||
final Duration? autobackupPeriod,
|
||||
final BackblazeBucket? backblazeBucket,
|
||||
final AutobackupQuotas? autobackupQuotas,
|
||||
}) =>
|
||||
BackupsState(
|
||||
isInitialized: isInitialized ?? this.isInitialized,
|
||||
backups: backups ?? this.backups,
|
||||
preventActions: preventActions ?? this.preventActions,
|
||||
refreshTimer: refreshTimer ?? this.refreshTimer,
|
||||
refreshing: refreshing ?? this.refreshing,
|
||||
// The autobackupPeriod might be null, so if the duration is set to 0, we
|
||||
// set it to null.
|
||||
autobackupPeriod: autobackupPeriod?.inSeconds == 0
|
||||
? null
|
||||
: autobackupPeriod ?? this.autobackupPeriod,
|
||||
backblazeBucket: backblazeBucket ?? this.backblazeBucket,
|
||||
autobackupQuotas: autobackupQuotas ?? this.autobackupQuotas,
|
||||
);
|
||||
}
|
|
@ -1,36 +1,52 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/job.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
part 'client_jobs_state.dart';
|
||||
|
||||
class JobsCubit extends Cubit<JobsState> {
|
||||
JobsCubit({
|
||||
required this.usersCubit,
|
||||
required this.servicesCubit,
|
||||
}) : super(JobsStateEmpty());
|
||||
|
||||
final ServerApi api = ServerApi();
|
||||
final UsersCubit usersCubit;
|
||||
final ServicesCubit servicesCubit;
|
||||
|
||||
void addJob(final ClientJob job) {
|
||||
final jobs = currentJobList;
|
||||
if (job.canAddTo(jobs)) {
|
||||
_updateJobsState([
|
||||
...jobs,
|
||||
...[job],
|
||||
]);
|
||||
JobsCubit() : super(JobsStateEmpty()) {
|
||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||
(final ApiData apiData) {
|
||||
if (apiData.serverJobs.data != null &&
|
||||
apiData.serverJobs.data!.isNotEmpty) {
|
||||
_handleServerJobs(apiData.serverJobs.data!);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
StreamSubscription? _apiDataSubscription;
|
||||
|
||||
void _handleServerJobs(final List<ServerJob> jobs) {
|
||||
if (state is! JobsStateLoading) {
|
||||
return;
|
||||
}
|
||||
if (state.rebuildJobUid == null) {
|
||||
return;
|
||||
}
|
||||
// Find a job with the uid of the rebuild job
|
||||
final ServerJob? rebuildJob = jobs.firstWhereOrNull(
|
||||
(final job) => job.uid == state.rebuildJobUid,
|
||||
);
|
||||
if (rebuildJob == null ||
|
||||
rebuildJob.status == JobStatusEnum.error ||
|
||||
rebuildJob.status == JobStatusEnum.finished) {
|
||||
emit((state as JobsStateLoading).finished());
|
||||
}
|
||||
}
|
||||
|
||||
void addJob(final ClientJob job) async {
|
||||
emit(state.addJob(job));
|
||||
}
|
||||
|
||||
void removeJob(final String id) {
|
||||
|
@ -38,61 +54,145 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
emit(newState);
|
||||
}
|
||||
|
||||
List<ClientJob> get currentJobList {
|
||||
final List<ClientJob> jobs = <ClientJob>[];
|
||||
if (state is JobsStateWithJobs) {
|
||||
jobs.addAll((state as JobsStateWithJobs).clientJobList);
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
void _updateJobsState(final List<ClientJob> newJobs) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
emit(JobsStateWithJobs(newJobs));
|
||||
}
|
||||
|
||||
Future<void> rebootServer() async {
|
||||
emit(JobsStateLoading());
|
||||
final rebootResult = await api.reboot();
|
||||
if (state is JobsStateEmpty) {
|
||||
emit(
|
||||
JobsStateLoading(
|
||||
[RebootServerJob(status: JobStatusEnum.running)],
|
||||
null,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
final rebootResult = await getIt<ApiConnectionRepository>().api.reboot();
|
||||
if (rebootResult.success && rebootResult.data != null) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.reboot_success'.tr());
|
||||
emit(
|
||||
JobsStateFinished(
|
||||
[
|
||||
RebootServerJob(
|
||||
status: JobStatusEnum.finished,
|
||||
message: rebootResult.message,
|
||||
),
|
||||
],
|
||||
null,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
getIt<NavigationService>().showSnackBar('jobs.reboot_failed'.tr());
|
||||
emit(
|
||||
JobsStateFinished(
|
||||
[RebootServerJob(status: JobStatusEnum.error)],
|
||||
null,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
emit(JobsStateEmpty());
|
||||
}
|
||||
|
||||
Future<void> upgradeServer() async {
|
||||
emit(JobsStateLoading());
|
||||
final bool isPullSuccessful = await api.pullConfigurationUpdate();
|
||||
final bool isSuccessful = await api.upgrade();
|
||||
if (isSuccessful) {
|
||||
if (!isPullSuccessful) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.config_pull_failed'.tr());
|
||||
if (state is JobsStateEmpty) {
|
||||
emit(
|
||||
JobsStateLoading(
|
||||
[UpgradeServerJob(status: JobStatusEnum.running)],
|
||||
null,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
final result = await getIt<ApiConnectionRepository>().api.upgrade();
|
||||
if (result.success && result.data != null) {
|
||||
emit(
|
||||
JobsStateLoading(
|
||||
[UpgradeServerJob(status: JobStatusEnum.finished)],
|
||||
result.data!.uid,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
getIt<NavigationService>().showSnackBar('jobs.upgrade_success'.tr());
|
||||
emit(
|
||||
JobsStateFinished(
|
||||
[UpgradeServerJob(status: JobStatusEnum.error)],
|
||||
null,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
getIt<NavigationService>().showSnackBar('jobs.upgrade_failed'.tr());
|
||||
}
|
||||
emit(JobsStateEmpty());
|
||||
}
|
||||
|
||||
Future<void> applyAll() async {
|
||||
if (state is JobsStateWithJobs) {
|
||||
final List<ClientJob> jobs = (state as JobsStateWithJobs).clientJobList;
|
||||
emit(JobsStateLoading());
|
||||
emit(JobsStateLoading(jobs, null, const []));
|
||||
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
final rebuildRequired = jobs.any((final job) => job.requiresRebuild);
|
||||
|
||||
for (final ClientJob job in jobs) {
|
||||
job.execute(this);
|
||||
emit(
|
||||
(state as JobsStateLoading)
|
||||
.updateJobStatus(job.id, JobStatusEnum.running),
|
||||
);
|
||||
final (result, message) = await job.execute();
|
||||
if (result) {
|
||||
emit(
|
||||
(state as JobsStateLoading).updateJobStatus(
|
||||
job.id,
|
||||
JobStatusEnum.finished,
|
||||
message: message,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
(state as JobsStateLoading)
|
||||
.updateJobStatus(job.id, JobStatusEnum.error, message: message),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await api.pullConfigurationUpdate();
|
||||
await api.apply();
|
||||
await servicesCubit.load();
|
||||
if (!rebuildRequired) {
|
||||
emit((state as JobsStateLoading).finished());
|
||||
return;
|
||||
}
|
||||
final rebuildResult = await getIt<ApiConnectionRepository>().api.apply();
|
||||
if (rebuildResult.success) {
|
||||
if (rebuildResult.data != null) {
|
||||
emit(
|
||||
(state as JobsStateLoading)
|
||||
.copyWith(rebuildJobUid: rebuildResult.data!.uid),
|
||||
);
|
||||
} else {
|
||||
emit((state as JobsStateLoading).finished());
|
||||
}
|
||||
} else {
|
||||
emit((state as JobsStateLoading).finished());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> acknowledgeFinished() async {
|
||||
if (state is! JobsStateFinished) {
|
||||
return;
|
||||
}
|
||||
final rebuildJobUid = state.rebuildJobUid;
|
||||
if ((state as JobsStateFinished).postponedJobs.isNotEmpty) {
|
||||
emit(JobsStateWithJobs((state as JobsStateFinished).postponedJobs));
|
||||
} else {
|
||||
emit(JobsStateEmpty());
|
||||
}
|
||||
if (rebuildJobUid != null) {
|
||||
await getIt<ApiConnectionRepository>().removeServerJob(rebuildJobUid);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(final Change<JobsState> change) {
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_apiDataSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,32 @@
|
|||
part of 'client_jobs_cubit.dart';
|
||||
|
||||
abstract class JobsState extends Equatable {
|
||||
sealed class JobsState extends Equatable {
|
||||
String? get rebuildJobUid => null;
|
||||
|
||||
JobsState addJob(final ClientJob job);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class JobsStateLoading extends JobsState {}
|
||||
class JobsStateEmpty extends JobsState {
|
||||
@override
|
||||
JobsStateWithJobs addJob(final ClientJob job) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
return JobsStateWithJobs([job]);
|
||||
}
|
||||
|
||||
class JobsStateEmpty extends JobsState {}
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class JobsStateWithJobs extends JobsState {
|
||||
JobsStateWithJobs(this.clientJobList);
|
||||
final List<ClientJob> clientJobList;
|
||||
|
||||
bool get rebuildRequired =>
|
||||
clientJobList.any((final job) => job.requiresRebuild);
|
||||
|
||||
JobsState removeById(final String id) {
|
||||
final List<ClientJob> newJobsList =
|
||||
clientJobList.where((final element) => element.id != id).toList();
|
||||
|
@ -22,5 +37,135 @@ class JobsStateWithJobs extends JobsState {
|
|||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => clientJobList;
|
||||
List<Object?> get props => [clientJobList];
|
||||
|
||||
@override
|
||||
JobsState addJob(final ClientJob job) {
|
||||
if (job is ReplaceableJob) {
|
||||
final List<ClientJob> newJobsList = clientJobList
|
||||
.where((final element) => element.runtimeType != job.runtimeType)
|
||||
.toList();
|
||||
if (job.shouldRemoveInsteadOfAdd(clientJobList)) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_removed'.tr());
|
||||
} else {
|
||||
newJobsList.add(job);
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
}
|
||||
if (newJobsList.isEmpty) {
|
||||
return JobsStateEmpty();
|
||||
}
|
||||
return JobsStateWithJobs(newJobsList);
|
||||
}
|
||||
if (job.canAddTo(clientJobList)) {
|
||||
final List<ClientJob> newJobsList = [...clientJobList, job];
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
return JobsStateWithJobs(newJobsList);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class JobsStateLoading extends JobsState {
|
||||
JobsStateLoading(this.clientJobList, this.rebuildJobUid, this.postponedJobs);
|
||||
final List<ClientJob> clientJobList;
|
||||
@override
|
||||
final String? rebuildJobUid;
|
||||
|
||||
bool get rebuildRequired =>
|
||||
clientJobList.any((final job) => job.requiresRebuild);
|
||||
|
||||
final List<ClientJob> postponedJobs;
|
||||
|
||||
JobsStateLoading updateJobStatus(
|
||||
final String id,
|
||||
final JobStatusEnum status, {
|
||||
final String? message,
|
||||
}) {
|
||||
final List<ClientJob> newJobsList = clientJobList.map((final job) {
|
||||
if (job.id == id) {
|
||||
return job.copyWithNewStatus(status: status, message: message);
|
||||
}
|
||||
return job;
|
||||
}).toList();
|
||||
return JobsStateLoading(newJobsList, rebuildJobUid, postponedJobs);
|
||||
}
|
||||
|
||||
JobsStateLoading copyWith({
|
||||
final List<ClientJob>? clientJobList,
|
||||
final String? rebuildJobUid,
|
||||
final List<ClientJob>? postponedJobs,
|
||||
}) =>
|
||||
JobsStateLoading(
|
||||
clientJobList ?? this.clientJobList,
|
||||
rebuildJobUid ?? this.rebuildJobUid,
|
||||
postponedJobs ?? this.postponedJobs,
|
||||
);
|
||||
|
||||
JobsStateFinished finished() =>
|
||||
JobsStateFinished(clientJobList, rebuildJobUid, postponedJobs);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [clientJobList, rebuildJobUid, postponedJobs];
|
||||
|
||||
@override
|
||||
JobsState addJob(final ClientJob job) {
|
||||
if (job is ReplaceableJob) {
|
||||
final List<ClientJob> newPostponedJobs = postponedJobs
|
||||
.where((final element) => element.runtimeType != job.runtimeType)
|
||||
.toList();
|
||||
if (job.shouldRemoveInsteadOfAdd(postponedJobs)) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_removed'.tr());
|
||||
} else {
|
||||
newPostponedJobs.add(job);
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_postponed'.tr());
|
||||
}
|
||||
return JobsStateLoading(clientJobList, rebuildJobUid, newPostponedJobs);
|
||||
}
|
||||
if (job.canAddTo(postponedJobs)) {
|
||||
final List<ClientJob> newPostponedJobs = [...postponedJobs, job];
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_postponed'.tr());
|
||||
return JobsStateLoading(clientJobList, rebuildJobUid, newPostponedJobs);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class JobsStateFinished extends JobsState {
|
||||
JobsStateFinished(this.clientJobList, this.rebuildJobUid, this.postponedJobs);
|
||||
final List<ClientJob> clientJobList;
|
||||
@override
|
||||
final String? rebuildJobUid;
|
||||
|
||||
bool get rebuildRequired =>
|
||||
clientJobList.any((final job) => job.requiresRebuild);
|
||||
|
||||
final List<ClientJob> postponedJobs;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [clientJobList, rebuildJobUid, postponedJobs];
|
||||
|
||||
@override
|
||||
JobsState addJob(final ClientJob job) {
|
||||
if (job is ReplaceableJob) {
|
||||
final List<ClientJob> newPostponedJobs = postponedJobs
|
||||
.where((final element) => element.runtimeType != job.runtimeType)
|
||||
.toList();
|
||||
if (job.shouldRemoveInsteadOfAdd(postponedJobs)) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_removed'.tr());
|
||||
} else {
|
||||
newPostponedJobs.add(job);
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
}
|
||||
if (newPostponedJobs.isEmpty) {
|
||||
return JobsStateEmpty();
|
||||
}
|
||||
return JobsStateWithJobs(newPostponedJobs);
|
||||
}
|
||||
if (job.canAddTo(postponedJobs)) {
|
||||
final List<ClientJob> newPostponedJobs = [...postponedJobs, job];
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
return JobsStateWithJobs(newPostponedJobs);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||
|
||||
part 'devices_state.dart';
|
||||
|
||||
class ApiDevicesCubit
|
||||
extends ServerInstallationDependendCubit<ApiDevicesState> {
|
||||
ApiDevicesCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(serverInstallationCubit, const ApiDevicesState.initial());
|
||||
|
||||
final ServerApi api = ServerApi();
|
||||
|
||||
@override
|
||||
void load() async {
|
||||
// if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
_refetch();
|
||||
// }
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
emit(ApiDevicesState([state.thisDevice], LoadingStatus.refreshing));
|
||||
_refetch();
|
||||
}
|
||||
|
||||
void _refetch() async {
|
||||
final List<ApiToken>? devices = await _getApiTokens();
|
||||
if (devices != null) {
|
||||
emit(ApiDevicesState(devices, LoadingStatus.success));
|
||||
} else {
|
||||
emit(const ApiDevicesState([], LoadingStatus.error));
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ApiToken>?> _getApiTokens() async {
|
||||
final GenericResult<List<ApiToken>> response = await api.getApiTokens();
|
||||
if (response.success) {
|
||||
return response.data;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteDevice(final ApiToken device) async {
|
||||
final GenericResult<void> response = await api.deleteApiToken(device.name);
|
||||
if (response.success) {
|
||||
emit(
|
||||
ApiDevicesState(
|
||||
state.devices.where((final d) => d.name != device.name).toList(),
|
||||
LoadingStatus.success,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(response.message ?? 'Error deleting device');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getNewDeviceKey() async {
|
||||
final GenericResult<String> response = await api.createDeviceToken();
|
||||
if (response.success) {
|
||||
return response.data;
|
||||
} else {
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
response.message ?? 'Error getting new device key',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
emit(const ApiDevicesState.initial());
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
part of 'devices_cubit.dart';
|
||||
|
||||
class ApiDevicesState extends ServerInstallationDependendState {
|
||||
const ApiDevicesState(this._devices, this.status);
|
||||
|
||||
const ApiDevicesState.initial() : this(const [], LoadingStatus.uninitialized);
|
||||
final List<ApiToken> _devices;
|
||||
final LoadingStatus status;
|
||||
|
||||
List<ApiToken> get devices => _devices;
|
||||
ApiToken get thisDevice => _devices.firstWhere(
|
||||
(final device) => device.isCaller,
|
||||
orElse: () => ApiToken(
|
||||
name: 'Error fetching device',
|
||||
isCaller: true,
|
||||
date: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
List<ApiToken> get otherDevices =>
|
||||
_devices.where((final device) => !device.isCaller).toList();
|
||||
|
||||
ApiDevicesState copyWith({
|
||||
final List<ApiToken>? devices,
|
||||
final LoadingStatus? status,
|
||||
}) =>
|
||||
ApiDevicesState(
|
||||
devices ?? _devices,
|
||||
status ?? this.status,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [_devices];
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
||||
|
@ -11,11 +10,9 @@ import 'package:selfprivacy/utils/network_utils.dart';
|
|||
|
||||
part 'dns_records_state.dart';
|
||||
|
||||
class DnsRecordsCubit
|
||||
extends ServerInstallationDependendCubit<DnsRecordsState> {
|
||||
DnsRecordsCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
class DnsRecordsCubit extends ServerConnectionDependentCubit<DnsRecordsState> {
|
||||
DnsRecordsCubit()
|
||||
: super(
|
||||
serverInstallationCubit,
|
||||
const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing),
|
||||
);
|
||||
|
||||
|
@ -30,10 +27,9 @@ class DnsRecordsCubit
|
|||
),
|
||||
);
|
||||
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
||||
final ServerDomain? domain = getIt<ApiConnectionRepository>().serverDomain;
|
||||
final String? ipAddress =
|
||||
serverInstallationCubit.state.serverDetails?.ip4;
|
||||
getIt<ApiConnectionRepository>().serverDetails?.ip4;
|
||||
|
||||
if (domain == null || ipAddress == null) {
|
||||
emit(const DnsRecordsState());
|
||||
|
@ -46,11 +42,10 @@ class DnsRecordsCubit
|
|||
domain,
|
||||
extractDkimRecord(allDnsRecords)?.content ?? '',
|
||||
allDnsRecords,
|
||||
ipAddress,
|
||||
);
|
||||
|
||||
if (!foundRecords.success || foundRecords.data.isEmpty) {
|
||||
emit(const DnsRecordsState(dnsState: DnsRecordsStatus.error));
|
||||
emit(const DnsRecordsState());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -63,7 +58,6 @@ class DnsRecordsCubit
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to check whether all known DNS records on the domain by ip4
|
||||
/// match expectations of SelfPrivacy in order to launch.
|
||||
|
@ -74,19 +68,7 @@ class DnsRecordsCubit
|
|||
final ServerDomain domain,
|
||||
final String dkimPublicKey,
|
||||
final List<DnsRecord> pendingDnsRecords,
|
||||
final String ip4,
|
||||
) async {
|
||||
final matchMap = await validateDnsMatch(domain.domainName, ['api'], ip4);
|
||||
if (matchMap.values.any((final status) => status != DnsRecordStatus.ok)) {
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'domain.domain_validation_failure'.tr(),
|
||||
);
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: [],
|
||||
);
|
||||
}
|
||||
|
||||
final result = await ProvidersController.currentDnsProvider!
|
||||
.getDnsRecords(domain: domain);
|
||||
if (result.data.isEmpty || !result.success) {
|
||||
|
@ -185,7 +167,7 @@ class DnsRecordsCubit
|
|||
final List<DnsRecord> records = await api.getDnsRecords();
|
||||
|
||||
/// TODO: Error handling?
|
||||
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
||||
final ServerDomain? domain = getIt<ApiConnectionRepository>().serverDomain;
|
||||
await ProvidersController.currentDnsProvider!.removeDomainRecords(
|
||||
records: records,
|
||||
domain: domain!,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||
|
||||
class FieldCubitFactory {
|
||||
FieldCubitFactory(this.context);
|
||||
|
@ -27,7 +27,7 @@ class FieldCubitFactory {
|
|||
),
|
||||
ValidationModel(
|
||||
(final String login) =>
|
||||
context.read<UsersCubit>().state.isLoginRegistered(login),
|
||||
context.read<UsersBloc>().state.isLoginRegistered(login),
|
||||
'validations.already_exist'.tr(),
|
||||
),
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/price.dart';
|
||||
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
||||
|
||||
part 'provider_volume_state.dart';
|
||||
|
||||
class ApiProviderVolumeCubit
|
||||
extends ServerInstallationDependendCubit<ApiProviderVolumeState> {
|
||||
ApiProviderVolumeCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(serverInstallationCubit, const ApiProviderVolumeState.initial());
|
||||
final ServerApi serverApi = ServerApi();
|
||||
|
||||
@override
|
||||
Future<void> load() async {
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
unawaited(_refetch());
|
||||
}
|
||||
}
|
||||
|
||||
Future<Price?> getPricePerGb() async {
|
||||
Price? price;
|
||||
final pricingResult =
|
||||
await ProvidersController.currentServerProvider!.getAdditionalPricing();
|
||||
if (pricingResult.data == null || !pricingResult.success) {
|
||||
getIt<NavigationService>().showSnackBar('server.pricing_error'.tr());
|
||||
return price;
|
||||
}
|
||||
price = pricingResult.data!.perVolumeGb;
|
||||
return price;
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false));
|
||||
unawaited(_refetch());
|
||||
}
|
||||
|
||||
Future<void> _refetch() async {
|
||||
if (ProvidersController.currentServerProvider == null) {
|
||||
return emit(const ApiProviderVolumeState([], LoadingStatus.error, false));
|
||||
}
|
||||
|
||||
final volumesResult =
|
||||
await ProvidersController.currentServerProvider!.getVolumes();
|
||||
|
||||
if (!volumesResult.success || volumesResult.data.isEmpty) {
|
||||
return emit(const ApiProviderVolumeState([], LoadingStatus.error, false));
|
||||
}
|
||||
|
||||
emit(
|
||||
ApiProviderVolumeState(
|
||||
volumesResult.data,
|
||||
LoadingStatus.success,
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> attachVolume(final DiskVolume volume) async {
|
||||
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
|
||||
await ProvidersController.currentServerProvider!
|
||||
.attachVolume(volume.providerVolume!, server.id);
|
||||
unawaited(refresh());
|
||||
}
|
||||
|
||||
Future<void> detachVolume(final DiskVolume volume) async {
|
||||
await ProvidersController.currentServerProvider!
|
||||
.detachVolume(volume.providerVolume!);
|
||||
unawaited(refresh());
|
||||
}
|
||||
|
||||
Future<bool> resizeVolume(
|
||||
final DiskVolume volume,
|
||||
final DiskSize newSize,
|
||||
final Function() callback,
|
||||
) async {
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'Starting resize',
|
||||
);
|
||||
emit(state.copyWith(isResizing: true));
|
||||
final resizedResult =
|
||||
await ProvidersController.currentServerProvider!.resizeVolume(
|
||||
volume.providerVolume!,
|
||||
newSize,
|
||||
);
|
||||
|
||||
if (!resizedResult.success || !resizedResult.data) {
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'storage.extending_volume_error'.tr(),
|
||||
);
|
||||
emit(state.copyWith(isResizing: false));
|
||||
return false;
|
||||
}
|
||||
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'Provider volume resized, waiting 10 seconds',
|
||||
);
|
||||
await Future.delayed(const Duration(seconds: 10));
|
||||
|
||||
await ServerApi().resizeVolume(volume.name);
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'Server volume resized, waiting 20 seconds',
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(seconds: 20));
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'Restarting server',
|
||||
);
|
||||
|
||||
await refresh();
|
||||
emit(state.copyWith(isResizing: false));
|
||||
await callback();
|
||||
await serverApi.reboot();
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> createVolume(final DiskSize size) async {
|
||||
final ServerVolume? volume = (await ProvidersController
|
||||
.currentServerProvider!
|
||||
.createVolume(size.gibibyte.toInt()))
|
||||
.data;
|
||||
|
||||
final diskVolume = DiskVolume(providerVolume: volume);
|
||||
await attachVolume(diskVolume);
|
||||
|
||||
await Future.delayed(const Duration(seconds: 10));
|
||||
|
||||
await ServerApi().mountVolume(volume!.name);
|
||||
unawaited(refresh());
|
||||
}
|
||||
|
||||
Future<void> deleteVolume(final DiskVolume volume) async {
|
||||
await ProvidersController.currentServerProvider!
|
||||
.deleteVolume(volume.providerVolume!);
|
||||
unawaited(refresh());
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
emit(const ApiProviderVolumeState.initial());
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
part of 'provider_volume_cubit.dart';
|
||||
|
||||
class ApiProviderVolumeState extends ServerInstallationDependendState {
|
||||
const ApiProviderVolumeState(this._volumes, this.status, this.isResizing);
|
||||
|
||||
const ApiProviderVolumeState.initial()
|
||||
: this(const [], LoadingStatus.uninitialized, false);
|
||||
final List<ServerVolume> _volumes;
|
||||
final LoadingStatus status;
|
||||
final bool isResizing;
|
||||
|
||||
List<ServerVolume> get volumes => _volumes;
|
||||
|
||||
ApiProviderVolumeState copyWith({
|
||||
final List<ServerVolume>? volumes,
|
||||
final LoadingStatus? status,
|
||||
final bool? isResizing,
|
||||
}) =>
|
||||
ApiProviderVolumeState(
|
||||
volumes ?? _volumes,
|
||||
status ?? this.status,
|
||||
isResizing ?? this.isResizing,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [_volumes, status, isResizing];
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/logic/models/provider.dart';
|
||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
export 'package:selfprivacy/logic/models/state_types.dart';
|
||||
|
||||
part 'providers_state.dart';
|
||||
|
||||
class ProvidersCubit extends Cubit<ProvidersState> {
|
||||
ProvidersCubit() : super(InitialProviderState());
|
||||
|
||||
void connect(final ProviderModel provider) {
|
||||
final ProvidersState newState =
|
||||
state.updateElement(provider, StateType.stable);
|
||||
emit(newState);
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
part of 'providers_cubit.dart';
|
||||
|
||||
class ProvidersState extends Equatable {
|
||||
const ProvidersState(this.all);
|
||||
|
||||
final List<ProviderModel> all;
|
||||
|
||||
ProvidersState updateElement(
|
||||
final ProviderModel provider,
|
||||
final StateType newState,
|
||||
) {
|
||||
final List<ProviderModel> newList = [...all];
|
||||
final int index = newList.indexOf(provider);
|
||||
newList[index] = provider.updateState(newState);
|
||||
return ProvidersState(newList);
|
||||
}
|
||||
|
||||
List<ProviderModel> get connected => all
|
||||
.where((final service) => service.state != StateType.uninitialized)
|
||||
.toList();
|
||||
|
||||
List<ProviderModel> get uninitialized => all
|
||||
.where((final service) => service.state == StateType.uninitialized)
|
||||
.toList();
|
||||
|
||||
bool get isFullyInitialized => uninitialized.isEmpty;
|
||||
|
||||
@override
|
||||
List<Object> get props => all;
|
||||
}
|
||||
|
||||
class InitialProviderState extends ProvidersState {
|
||||
InitialProviderState()
|
||||
: super(
|
||||
ProviderType.values
|
||||
.map(
|
||||
(final type) => ProviderModel(
|
||||
state: StateType.uninitialized,
|
||||
type: type,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
||||
|
||||
part 'recovery_key_state.dart';
|
||||
|
||||
class RecoveryKeyCubit
|
||||
extends ServerInstallationDependendCubit<RecoveryKeyState> {
|
||||
RecoveryKeyCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(serverInstallationCubit, const RecoveryKeyState.initial());
|
||||
|
||||
final ServerApi api = ServerApi();
|
||||
|
||||
@override
|
||||
void load() async {
|
||||
// if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final RecoveryKeyStatus? status = await _getRecoveryKeyStatus();
|
||||
if (status == null) {
|
||||
emit(state.copyWith(loadingStatus: LoadingStatus.error));
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: status,
|
||||
loadingStatus: LoadingStatus.success,
|
||||
),
|
||||
);
|
||||
}
|
||||
// } else {
|
||||
// emit(state.copyWith(loadingStatus: LoadingStatus.uninitialized));
|
||||
// }
|
||||
}
|
||||
|
||||
Future<RecoveryKeyStatus?> _getRecoveryKeyStatus() async {
|
||||
final GenericResult<RecoveryKeyStatus?> response =
|
||||
await api.getRecoveryTokenStatus();
|
||||
if (response.success) {
|
||||
return response.data;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
emit(state.copyWith(loadingStatus: LoadingStatus.refreshing));
|
||||
final RecoveryKeyStatus? status = await _getRecoveryKeyStatus();
|
||||
if (status == null) {
|
||||
emit(state.copyWith(loadingStatus: LoadingStatus.error));
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(status: status, loadingStatus: LoadingStatus.success),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> generateRecoveryKey({
|
||||
final DateTime? expirationDate,
|
||||
final int? numberOfUses,
|
||||
}) async {
|
||||
final GenericResult<String> response =
|
||||
await api.generateRecoveryToken(expirationDate, numberOfUses);
|
||||
if (response.success) {
|
||||
unawaited(refresh());
|
||||
return response.data;
|
||||
} else {
|
||||
throw GenerationError(response.message ?? 'Unknown error');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
emit(state.copyWith(loadingStatus: LoadingStatus.uninitialized));
|
||||
}
|
||||
}
|
||||
|
||||
class GenerationError extends Error {
|
||||
GenerationError(this.message);
|
||||
final String message;
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
part of 'recovery_key_cubit.dart';
|
||||
|
||||
class RecoveryKeyState extends ServerInstallationDependendState {
|
||||
const RecoveryKeyState(this._status, this.loadingStatus);
|
||||
|
||||
const RecoveryKeyState.initial()
|
||||
: this(
|
||||
const RecoveryKeyStatus(exists: false, valid: false),
|
||||
LoadingStatus.refreshing,
|
||||
);
|
||||
|
||||
final RecoveryKeyStatus _status;
|
||||
final LoadingStatus loadingStatus;
|
||||
|
||||
bool get exists => _status.exists;
|
||||
bool get isValid => _status.valid;
|
||||
DateTime? get generatedAt => _status.date;
|
||||
DateTime? get expiresAt => _status.expiration;
|
||||
int? get usesLeft => _status.usesLeft;
|
||||
|
||||
bool get isInvalidBecauseExpired =>
|
||||
_status.expiration != null &&
|
||||
_status.expiration!.isBefore(DateTime.now());
|
||||
|
||||
bool get isInvalidBecauseUsed =>
|
||||
_status.usesLeft != null && _status.usesLeft == 0;
|
||||
|
||||
@override
|
||||
List<Object> get props => [_status, loadingStatus];
|
||||
|
||||
RecoveryKeyState copyWith({
|
||||
final RecoveryKeyStatus? status,
|
||||
final LoadingStatus? loadingStatus,
|
||||
}) =>
|
||||
RecoveryKeyState(
|
||||
status ?? _status,
|
||||
loadingStatus ?? this.loadingStatus,
|
||||
);
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
|
||||
export 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
|
||||
part 'server_connection_dependent_state.dart';
|
||||
|
||||
abstract class ServerConnectionDependentCubit<
|
||||
T extends ServerInstallationDependendState> extends Cubit<T> {
|
||||
ServerConnectionDependentCubit(
|
||||
super.initState,
|
||||
) {
|
||||
final connectionRepository = getIt<ApiConnectionRepository>();
|
||||
|
||||
apiStatusSubscription =
|
||||
connectionRepository.connectionStatusStream.listen(checkAuthStatus);
|
||||
checkAuthStatus(connectionRepository.connectionStatus);
|
||||
}
|
||||
|
||||
void checkAuthStatus(final ConnectionStatus state) {
|
||||
switch (state) {
|
||||
case ConnectionStatus.nonexistent:
|
||||
clear();
|
||||
isLoaded = false;
|
||||
break;
|
||||
case ConnectionStatus.connected:
|
||||
if (!isLoaded) {
|
||||
load();
|
||||
isLoaded = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
late StreamSubscription apiStatusSubscription;
|
||||
bool isLoaded = false;
|
||||
|
||||
void load();
|
||||
void clear();
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
apiStatusSubscription.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
part of 'authentication_dependend_cubit.dart';
|
||||
part of 'server_connection_dependent_cubit.dart';
|
||||
|
||||
abstract class ServerInstallationDependendState extends Equatable {
|
||||
const ServerInstallationDependendState();
|
|
@ -1,35 +1,75 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_repository.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
||||
import 'package:selfprivacy/logic/models/server_metadata.dart';
|
||||
import 'package:selfprivacy/logic/models/system_settings.dart';
|
||||
import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
||||
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
||||
|
||||
part 'server_detailed_info_state.dart';
|
||||
|
||||
class ServerDetailsCubit
|
||||
extends ServerInstallationDependendCubit<ServerDetailsState> {
|
||||
ServerDetailsCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(serverInstallationCubit, ServerDetailsInitial());
|
||||
extends ServerConnectionDependentCubit<ServerDetailsState> {
|
||||
ServerDetailsCubit() : super(const ServerDetailsInitial()) {
|
||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||
(final ApiData apiData) {
|
||||
if (apiData.settings.data != null) {
|
||||
_handleServerSettings(apiData.settings.data!);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
ServerDetailsRepository repository = ServerDetailsRepository();
|
||||
StreamSubscription? _apiDataSubscription;
|
||||
|
||||
void _handleServerSettings(final SystemSettings settings) {
|
||||
emit(
|
||||
Loaded(
|
||||
metadata: state.metadata,
|
||||
serverTimezone: TimeZoneSettings.fromString(settings.timezone),
|
||||
autoUpgradeSettings: settings.autoUpgradeSettings,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<ServerMetadataEntity>> get _metadata async {
|
||||
List<ServerMetadataEntity> data = [];
|
||||
|
||||
final serverProviderApi = ProvidersController.currentServerProvider;
|
||||
final dnsProviderApi = ProvidersController.currentDnsProvider;
|
||||
if (serverProviderApi != null && dnsProviderApi != null) {
|
||||
final serverId = getIt<ApiConfigModel>().serverDetails?.id ?? 0;
|
||||
final metadataResult = await serverProviderApi.getMetadata(serverId);
|
||||
metadataResult.data.add(
|
||||
ServerMetadataEntity(
|
||||
trId: 'server.dns_provider',
|
||||
value: dnsProviderApi.type.displayName,
|
||||
type: MetadataType.other,
|
||||
),
|
||||
);
|
||||
|
||||
data = metadataResult.data;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void check() async {
|
||||
final bool isReadyToCheck = getIt<ApiConfigModel>().serverDetails != null;
|
||||
try {
|
||||
if (isReadyToCheck) {
|
||||
emit(ServerDetailsLoading());
|
||||
final ServerDetailsRepositoryDto data = await repository.load();
|
||||
emit(const ServerDetailsLoading());
|
||||
final List<ServerMetadataEntity> metadata = await _metadata;
|
||||
emit(
|
||||
Loaded(
|
||||
metadata: data.metadata,
|
||||
autoUpgradeSettings: data.autoUpgradeSettings,
|
||||
serverTimezone: data.serverTimezone,
|
||||
checkTime: DateTime.now(),
|
||||
state.copyWith(
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(ServerDetailsNotReady());
|
||||
emit(const ServerDetailsNotReady());
|
||||
}
|
||||
} on StateError {
|
||||
print('Tried to emit server info state when cubit is closed');
|
||||
|
@ -38,11 +78,17 @@ class ServerDetailsCubit
|
|||
|
||||
@override
|
||||
void clear() {
|
||||
emit(ServerDetailsNotReady());
|
||||
emit(const ServerDetailsNotReady());
|
||||
}
|
||||
|
||||
@override
|
||||
void load() async {
|
||||
check();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_apiDataSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
||||
import 'package:selfprivacy/logic/models/server_metadata.dart';
|
||||
import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
||||
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
||||
|
||||
class ServerDetailsRepository {
|
||||
ServerApi server = ServerApi();
|
||||
|
||||
Future<ServerDetailsRepositoryDto> load() async {
|
||||
final settings = await server.getSystemSettings();
|
||||
return ServerDetailsRepositoryDto(
|
||||
autoUpgradeSettings: settings.autoUpgradeSettings,
|
||||
metadata: await metadata,
|
||||
serverTimezone: TimeZoneSettings.fromString(
|
||||
settings.timezone,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<ServerMetadataEntity>> get metadata async {
|
||||
List<ServerMetadataEntity> data = [];
|
||||
|
||||
final serverProviderApi = ProvidersController.currentServerProvider;
|
||||
final dnsProviderApi = ProvidersController.currentDnsProvider;
|
||||
if (serverProviderApi != null && dnsProviderApi != null) {
|
||||
final serverId = getIt<ApiConfigModel>().serverDetails?.id ?? 0;
|
||||
final metadataResult = await serverProviderApi.getMetadata(serverId);
|
||||
metadataResult.data.add(
|
||||
ServerMetadataEntity(
|
||||
trId: 'server.dns_provider',
|
||||
value: dnsProviderApi.type.displayName,
|
||||
type: MetadataType.other,
|
||||
),
|
||||
);
|
||||
|
||||
data = metadataResult.data;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<void> setAutoUpgradeSettings(
|
||||
final AutoUpgradeSettings settings,
|
||||
) async {
|
||||
await server.setAutoUpgradeSettings(settings);
|
||||
}
|
||||
|
||||
Future<void> setTimezone(
|
||||
final String timezone,
|
||||
) async {
|
||||
if (timezone.isNotEmpty) {
|
||||
await server.setTimezone(timezone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ServerDetailsRepositoryDto {
|
||||
ServerDetailsRepositoryDto({
|
||||
required this.metadata,
|
||||
required this.serverTimezone,
|
||||
required this.autoUpgradeSettings,
|
||||
});
|
||||
final List<ServerMetadataEntity> metadata;
|
||||
final TimeZoneSettings serverTimezone;
|
||||
final AutoUpgradeSettings autoUpgradeSettings;
|
||||
}
|
|
@ -1,37 +1,78 @@
|
|||
part of 'server_detailed_info_cubit.dart';
|
||||
|
||||
abstract class ServerDetailsState extends ServerInstallationDependendState {
|
||||
const ServerDetailsState();
|
||||
const ServerDetailsState({
|
||||
required this.metadata,
|
||||
});
|
||||
|
||||
final List<ServerMetadataEntity> metadata;
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
List<Object> get props => [metadata];
|
||||
|
||||
ServerDetailsState copyWith({
|
||||
final List<ServerMetadataEntity>? metadata,
|
||||
});
|
||||
}
|
||||
|
||||
class ServerDetailsInitial extends ServerDetailsState {}
|
||||
class ServerDetailsInitial extends ServerDetailsState {
|
||||
const ServerDetailsInitial({super.metadata = const []});
|
||||
|
||||
class ServerDetailsLoading extends ServerDetailsState {}
|
||||
@override
|
||||
ServerDetailsInitial copyWith({final List<ServerMetadataEntity>? metadata}) =>
|
||||
ServerDetailsInitial(
|
||||
metadata: metadata ?? this.metadata,
|
||||
);
|
||||
}
|
||||
|
||||
class ServerDetailsNotReady extends ServerDetailsState {}
|
||||
class ServerDetailsLoading extends ServerDetailsState {
|
||||
const ServerDetailsLoading({super.metadata = const []});
|
||||
|
||||
class Loading extends ServerDetailsState {}
|
||||
@override
|
||||
ServerDetailsLoading copyWith({final List<ServerMetadataEntity>? metadata}) =>
|
||||
ServerDetailsLoading(
|
||||
metadata: metadata ?? this.metadata,
|
||||
);
|
||||
}
|
||||
|
||||
class ServerDetailsNotReady extends ServerDetailsState {
|
||||
const ServerDetailsNotReady({super.metadata = const []});
|
||||
|
||||
@override
|
||||
ServerDetailsNotReady copyWith({
|
||||
final List<ServerMetadataEntity>? metadata,
|
||||
}) =>
|
||||
ServerDetailsNotReady(
|
||||
metadata: metadata ?? this.metadata,
|
||||
);
|
||||
}
|
||||
|
||||
class Loaded extends ServerDetailsState {
|
||||
const Loaded({
|
||||
required this.metadata,
|
||||
required super.metadata,
|
||||
required this.serverTimezone,
|
||||
required this.autoUpgradeSettings,
|
||||
required this.checkTime,
|
||||
});
|
||||
final List<ServerMetadataEntity> metadata;
|
||||
final TimeZoneSettings serverTimezone;
|
||||
final AutoUpgradeSettings autoUpgradeSettings;
|
||||
final DateTime checkTime;
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
metadata,
|
||||
serverTimezone,
|
||||
autoUpgradeSettings,
|
||||
checkTime,
|
||||
];
|
||||
|
||||
@override
|
||||
Loaded copyWith({
|
||||
final List<ServerMetadataEntity>? metadata,
|
||||
final TimeZoneSettings? serverTimezone,
|
||||
final AutoUpgradeSettings? autoUpgradeSettings,
|
||||
final DateTime? checkTime,
|
||||
}) =>
|
||||
Loaded(
|
||||
metadata: metadata ?? this.metadata,
|
||||
serverTimezone: serverTimezone ?? this.serverTimezone,
|
||||
autoUpgradeSettings: autoUpgradeSettings ?? this.autoUpgradeSettings,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -233,7 +233,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
|||
try {
|
||||
bucket = await BackblazeApi()
|
||||
.fetchBucket(backblazeCredential, configuration);
|
||||
await getIt<ApiConfigModel>().storeBackblazeBucket(bucket!);
|
||||
await getIt<ApiConfigModel>().setBackblazeBucket(bucket!);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
@ -484,6 +484,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
|||
if (dkimCreated) {
|
||||
await repository.saveHasFinalChecked(true);
|
||||
emit(dataState.finish());
|
||||
getIt<ApiConnectionRepository>().init();
|
||||
} else {
|
||||
runDelayed(
|
||||
finishCheckIfServerIsOkay,
|
||||
|
@ -724,7 +725,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
|||
ip4: server.ip,
|
||||
id: server.id,
|
||||
createTime: server.created,
|
||||
volume: ServerVolume(
|
||||
volume: ServerProviderVolume(
|
||||
id: 0,
|
||||
name: 'recovered_volume',
|
||||
sizeByte: 0,
|
||||
|
@ -802,6 +803,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
|||
serverTypeIdentificator: serverType.data!.identifier,
|
||||
);
|
||||
emit(updatedState.finish());
|
||||
getIt<ApiConnectionRepository>().init();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -313,7 +313,7 @@ class ServerInstallationRepository {
|
|||
if (result.success) {
|
||||
return ServerHostingDetails(
|
||||
apiToken: result.data,
|
||||
volume: ServerVolume(
|
||||
volume: ServerProviderVolume(
|
||||
id: 0,
|
||||
name: '',
|
||||
sizeByte: 0,
|
||||
|
@ -350,7 +350,7 @@ class ServerInstallationRepository {
|
|||
if (result.success) {
|
||||
return ServerHostingDetails(
|
||||
apiToken: result.data,
|
||||
volume: ServerVolume(
|
||||
volume: ServerProviderVolume(
|
||||
id: 0,
|
||||
name: '',
|
||||
sizeByte: 0,
|
||||
|
@ -385,7 +385,7 @@ class ServerInstallationRepository {
|
|||
if (await serverApi.isHttpServerWorking()) {
|
||||
return ServerHostingDetails(
|
||||
apiToken: apiToken,
|
||||
volume: ServerVolume(
|
||||
volume: ServerProviderVolume(
|
||||
id: 0,
|
||||
name: '',
|
||||
serverId: 0,
|
||||
|
@ -416,7 +416,7 @@ class ServerInstallationRepository {
|
|||
if (result.success) {
|
||||
return ServerHostingDetails(
|
||||
apiToken: result.data,
|
||||
volume: ServerVolume(
|
||||
volume: ServerProviderVolume(
|
||||
id: 0,
|
||||
name: '',
|
||||
sizeByte: 0,
|
||||
|
@ -470,7 +470,7 @@ class ServerInstallationRepository {
|
|||
Future<void> saveServerDetails(
|
||||
final ServerHostingDetails serverDetails,
|
||||
) async {
|
||||
await getIt<ApiConfigModel>().storeServerDetails(serverDetails);
|
||||
await getIt<ApiConfigModel>().setServerDetails(serverDetails);
|
||||
}
|
||||
|
||||
Future<void> deleteServerDetails() async {
|
||||
|
@ -483,18 +483,18 @@ class ServerInstallationRepository {
|
|||
}
|
||||
|
||||
Future<void> saveDnsProviderType(final DnsProviderType type) async {
|
||||
await getIt<ApiConfigModel>().storeDnsProviderType(type);
|
||||
await getIt<ApiConfigModel>().setDnsProviderType(type);
|
||||
}
|
||||
|
||||
Future<void> saveServerProviderKey(final String key) async {
|
||||
await getIt<ApiConfigModel>().storeServerProviderKey(key);
|
||||
await getIt<ApiConfigModel>().setServerProviderKey(key);
|
||||
}
|
||||
|
||||
Future<void> saveServerType(final ServerType serverType) async {
|
||||
await getIt<ApiConfigModel>().storeServerTypeIdentifier(
|
||||
await getIt<ApiConfigModel>().setServerTypeIdentifier(
|
||||
serverType.identifier,
|
||||
);
|
||||
await getIt<ApiConfigModel>().storeServerLocation(
|
||||
await getIt<ApiConfigModel>().setServerLocation(
|
||||
serverType.location.identifier,
|
||||
);
|
||||
}
|
||||
|
@ -507,7 +507,7 @@ class ServerInstallationRepository {
|
|||
Future<void> saveBackblazeKey(
|
||||
final BackupsCredential backblazeCredential,
|
||||
) async {
|
||||
await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential);
|
||||
await getIt<ApiConfigModel>().setBackblazeCredential(backblazeCredential);
|
||||
}
|
||||
|
||||
Future<void> deleteBackblazeKey() async {
|
||||
|
@ -516,7 +516,7 @@ class ServerInstallationRepository {
|
|||
}
|
||||
|
||||
Future<void> setDnsApiToken(final String key) async {
|
||||
await getIt<ApiConfigModel>().storeDnsProviderKey(key);
|
||||
await getIt<ApiConfigModel>().setDnsProviderKey(key);
|
||||
}
|
||||
|
||||
Future<void> deleteDnsProviderKey() async {
|
||||
|
@ -525,7 +525,7 @@ class ServerInstallationRepository {
|
|||
}
|
||||
|
||||
Future<void> saveDomain(final ServerDomain serverDomain) async {
|
||||
await getIt<ApiConfigModel>().storeServerDomain(serverDomain);
|
||||
await getIt<ApiConfigModel>().setServerDomain(serverDomain);
|
||||
}
|
||||
|
||||
Future<void> deleteDomain() async {
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
part 'server_jobs_state.dart';
|
||||
|
||||
class ServerJobsCubit
|
||||
extends ServerInstallationDependendCubit<ServerJobsState> {
|
||||
ServerJobsCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(
|
||||
serverInstallationCubit,
|
||||
ServerJobsState(),
|
||||
);
|
||||
|
||||
Timer? timer;
|
||||
final ServerApi api = ServerApi();
|
||||
|
||||
@override
|
||||
void clear() async {
|
||||
emit(
|
||||
ServerJobsState(),
|
||||
);
|
||||
if (timer != null && timer!.isActive) {
|
||||
timer!.cancel();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void load() async {
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final List<ServerJob> jobs = await api.getServerJobs();
|
||||
emit(
|
||||
ServerJobsState(
|
||||
serverJobList: jobs,
|
||||
),
|
||||
);
|
||||
timer = Timer(const Duration(seconds: 5), () => reload(useTimer: true));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> migrateToBinds(final Map<String, String> serviceToDisk) async {
|
||||
final result = await api.migrateToBinds(serviceToDisk);
|
||||
if (result.data == null) {
|
||||
getIt<NavigationService>().showSnackBar(result.message!);
|
||||
return;
|
||||
}
|
||||
|
||||
emit(
|
||||
ServerJobsState(
|
||||
migrationJobUid: result.data,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ServerJob? getServerJobByUid(final String uid) {
|
||||
ServerJob? job;
|
||||
|
||||
try {
|
||||
job = state.serverJobList.firstWhere(
|
||||
(final ServerJob job) => job.uid == uid,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
Future<void> removeServerJob(final String uid) async {
|
||||
final result = await api.removeApiJob(uid);
|
||||
if (!result.success) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.generic_error'.tr());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
||||
return;
|
||||
}
|
||||
|
||||
emit(
|
||||
ServerJobsState(
|
||||
serverJobList: [
|
||||
for (final ServerJob job in state.serverJobList)
|
||||
if (job.uid != uid) job,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeAllFinishedJobs() async {
|
||||
final List<ServerJob> finishedJobs = state.serverJobList
|
||||
.where(
|
||||
(final ServerJob job) =>
|
||||
job.status == JobStatusEnum.finished ||
|
||||
job.status == JobStatusEnum.error,
|
||||
)
|
||||
.toList();
|
||||
for (final ServerJob job in finishedJobs) {
|
||||
await removeServerJob(job.uid);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reload({final bool useTimer = false}) async {
|
||||
final List<ServerJob> jobs = await api.getServerJobs();
|
||||
emit(
|
||||
ServerJobsState(
|
||||
serverJobList: jobs,
|
||||
),
|
||||
);
|
||||
if (useTimer) {
|
||||
timer = Timer(const Duration(seconds: 5), () => reload(useTimer: true));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
part of 'server_jobs_cubit.dart';
|
||||
|
||||
class ServerJobsState extends ServerInstallationDependendState {
|
||||
ServerJobsState({
|
||||
final serverJobList = const <ServerJob>[],
|
||||
this.migrationJobUid,
|
||||
}) {
|
||||
_serverJobList = serverJobList;
|
||||
}
|
||||
|
||||
late final List<ServerJob> _serverJobList;
|
||||
final String? migrationJobUid;
|
||||
|
||||
List<ServerJob> get serverJobList {
|
||||
try {
|
||||
final List<ServerJob> list = _serverJobList;
|
||||
list.sort((final a, final b) => b.createdAt.compareTo(a.createdAt));
|
||||
return list;
|
||||
} on UnsupportedError {
|
||||
return _serverJobList;
|
||||
}
|
||||
}
|
||||
|
||||
List<ServerJob> get backupJobList => serverJobList
|
||||
.where(
|
||||
// The backup jobs has the format of 'service.<service_id>.backup'
|
||||
(final job) =>
|
||||
job.typeId.contains('backup') || job.typeId.contains('restore'),
|
||||
)
|
||||
.toList();
|
||||
|
||||
bool get hasRemovableJobs => serverJobList.any(
|
||||
(final job) =>
|
||||
job.status == JobStatusEnum.finished ||
|
||||
job.status == JobStatusEnum.error,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [migrationJobUid, _serverJobList];
|
||||
|
||||
ServerJobsState copyWith({
|
||||
final List<ServerJob>? serverJobList,
|
||||
final String? migrationJobUid,
|
||||
}) =>
|
||||
ServerJobsState(
|
||||
serverJobList: serverJobList ?? _serverJobList,
|
||||
migrationJobUid: migrationJobUid ?? this.migrationJobUid,
|
||||
);
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_disk_volume.dart';
|
||||
|
||||
part 'server_volume_state.dart';
|
||||
|
||||
class ApiServerVolumeCubit
|
||||
extends ServerInstallationDependendCubit<ApiServerVolumeState> {
|
||||
ApiServerVolumeCubit(
|
||||
final ServerInstallationCubit serverInstallationCubit,
|
||||
this.providerVolumeCubit,
|
||||
) : super(serverInstallationCubit, ApiServerVolumeState.initial()) {
|
||||
_providerVolumeSubscription =
|
||||
providerVolumeCubit.stream.listen(checkProviderVolumes);
|
||||
}
|
||||
|
||||
final ServerApi serverApi = ServerApi();
|
||||
|
||||
@override
|
||||
Future<void> load() async {
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
unawaited(reload());
|
||||
}
|
||||
}
|
||||
|
||||
late StreamSubscription<ApiProviderVolumeState> _providerVolumeSubscription;
|
||||
final ApiProviderVolumeCubit providerVolumeCubit;
|
||||
|
||||
void checkProviderVolumes(final ApiProviderVolumeState state) {
|
||||
emit(
|
||||
ApiServerVolumeState(
|
||||
this.state._volumes,
|
||||
this.state.status,
|
||||
this.state.usesBinds,
|
||||
DiskStatus.fromVolumes(this.state._volumes, state.volumes),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> reload() async {
|
||||
final volumes = await serverApi.getServerDiskVolumes();
|
||||
final usesBinds = await serverApi.isUsingBinds();
|
||||
var status = LoadingStatus.error;
|
||||
|
||||
if (volumes.isNotEmpty) {
|
||||
status = LoadingStatus.success;
|
||||
}
|
||||
|
||||
emit(
|
||||
ApiServerVolumeState(
|
||||
volumes,
|
||||
status,
|
||||
usesBinds,
|
||||
DiskStatus.fromVolumes(
|
||||
volumes,
|
||||
providerVolumeCubit.state.volumes,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
emit(ApiServerVolumeState.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_providerVolumeSubscription.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
part of 'server_volume_cubit.dart';
|
||||
|
||||
class ApiServerVolumeState extends ServerInstallationDependendState {
|
||||
const ApiServerVolumeState(
|
||||
this._volumes,
|
||||
this.status,
|
||||
this.usesBinds,
|
||||
this._diskStatus,
|
||||
);
|
||||
|
||||
ApiServerVolumeState.initial()
|
||||
: this(const [], LoadingStatus.uninitialized, null, DiskStatus());
|
||||
|
||||
final List<ServerDiskVolume> _volumes;
|
||||
final DiskStatus _diskStatus;
|
||||
final bool? usesBinds;
|
||||
final LoadingStatus status;
|
||||
|
||||
List<DiskVolume> get volumes => _diskStatus.diskVolumes;
|
||||
DiskStatus get diskStatus => _diskStatus;
|
||||
|
||||
DiskVolume getVolume(final String volumeName) => volumes.firstWhere(
|
||||
(final volume) => volume.name == volumeName,
|
||||
orElse: () => DiskVolume(),
|
||||
);
|
||||
|
||||
ApiServerVolumeState copyWith({
|
||||
final List<ServerDiskVolume>? volumes,
|
||||
final LoadingStatus? status,
|
||||
final bool? usesBinds,
|
||||
final DiskStatus? diskStatus,
|
||||
}) =>
|
||||
ApiServerVolumeState(
|
||||
volumes ?? _volumes,
|
||||
status ?? this.status,
|
||||
usesBinds ?? this.usesBinds,
|
||||
diskStatus ?? _diskStatus,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [_volumes, status, usesBinds];
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
|
||||
part 'services_state.dart';
|
||||
|
||||
class ServicesCubit extends ServerInstallationDependendCubit<ServicesState> {
|
||||
ServicesCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(serverInstallationCubit, const ServicesState.empty());
|
||||
final ServerApi api = ServerApi();
|
||||
Timer? timer;
|
||||
@override
|
||||
Future<void> load() async {
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final List<Service> services = await api.getAllServices();
|
||||
emit(
|
||||
ServicesState(
|
||||
services: services,
|
||||
lockedServices: const [],
|
||||
),
|
||||
);
|
||||
timer = Timer(const Duration(seconds: 10), () => reload(useTimer: true));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reload({final bool useTimer = false}) async {
|
||||
final List<Service> services = await api.getAllServices();
|
||||
emit(
|
||||
state.copyWith(
|
||||
services: services,
|
||||
),
|
||||
);
|
||||
if (useTimer) {
|
||||
timer = Timer(const Duration(seconds: 60), () => reload(useTimer: true));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> restart(final String serviceId) async {
|
||||
emit(state.copyWith(lockedServices: [...state.lockedServices, serviceId]));
|
||||
final result = await api.restartService(serviceId);
|
||||
if (!result.success) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.generic_error'.tr());
|
||||
return;
|
||||
}
|
||||
if (!result.data) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
||||
return;
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
unawaited(reload());
|
||||
await Future.delayed(const Duration(seconds: 10));
|
||||
emit(
|
||||
state.copyWith(
|
||||
lockedServices: state.lockedServices
|
||||
.where((final element) => element != serviceId)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
unawaited(reload());
|
||||
}
|
||||
|
||||
Future<void> moveService(
|
||||
final String serviceId,
|
||||
final String destination,
|
||||
) async {
|
||||
await api.moveService(serviceId, destination);
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() async {
|
||||
emit(const ServicesState.empty());
|
||||
if (timer != null && timer!.isActive) {
|
||||
timer!.cancel();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
part of 'services_cubit.dart';
|
||||
|
||||
class ServicesState extends ServerInstallationDependendState {
|
||||
const ServicesState({
|
||||
required this.services,
|
||||
required this.lockedServices,
|
||||
});
|
||||
|
||||
const ServicesState.empty()
|
||||
: this(services: const [], lockedServices: const []);
|
||||
|
||||
final List<Service> services;
|
||||
final List<String> lockedServices;
|
||||
|
||||
List<Service> get servicesThatCanBeBackedUp => services
|
||||
.where(
|
||||
(final service) => service.canBeBackedUp,
|
||||
)
|
||||
.toList();
|
||||
|
||||
bool isServiceLocked(final String serviceId) =>
|
||||
lockedServices.contains(serviceId);
|
||||
|
||||
Service? getServiceById(final String id) {
|
||||
final service = services.firstWhere(
|
||||
(final service) => service.id == id,
|
||||
orElse: () => Service.empty,
|
||||
);
|
||||
if (service.id == 'empty') {
|
||||
return null;
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
services,
|
||||
lockedServices,
|
||||
];
|
||||
|
||||
ServicesState copyWith({
|
||||
final List<Service>? services,
|
||||
final List<String>? lockedServices,
|
||||
}) =>
|
||||
ServicesState(
|
||||
services: services ?? this.services,
|
||||
lockedServices: lockedServices ?? this.lockedServices,
|
||||
);
|
||||
}
|
|
@ -1,186 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
part 'users_state.dart';
|
||||
|
||||
class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
||||
UsersCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(
|
||||
serverInstallationCubit,
|
||||
const UsersState(
|
||||
<User>[],
|
||||
false,
|
||||
),
|
||||
);
|
||||
Box<User> box = Hive.box<User>(BNames.usersBox);
|
||||
Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
|
||||
|
||||
final ServerApi api = ServerApi();
|
||||
|
||||
@override
|
||||
Future<void> load() async {
|
||||
if (serverInstallationCubit.state is! ServerInstallationFinished) {
|
||||
return;
|
||||
}
|
||||
final List<User> loadedUsers = box.values.toList();
|
||||
if (loadedUsers.isNotEmpty) {
|
||||
emit(
|
||||
UsersState(
|
||||
loadedUsers,
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
unawaited(refresh());
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
if (serverInstallationCubit.state is! ServerInstallationFinished) {
|
||||
return;
|
||||
}
|
||||
emit(state.copyWith(isLoading: true));
|
||||
final List<User> usersFromServer = await api.getAllUsers();
|
||||
if (usersFromServer.isNotEmpty) {
|
||||
emit(
|
||||
UsersState(
|
||||
usersFromServer,
|
||||
false,
|
||||
),
|
||||
);
|
||||
// Update the users it the box
|
||||
await box.clear();
|
||||
await box.addAll(usersFromServer);
|
||||
} else {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('users.could_not_fetch_users'.tr());
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createUser(final User user) async {
|
||||
// If user exists on server, do nothing
|
||||
if (state.users
|
||||
.any((final User u) => u.login == user.login && u.isFoundOnServer)) {
|
||||
return;
|
||||
}
|
||||
final String? password = user.password;
|
||||
if (password == null) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('users.could_not_create_user'.tr());
|
||||
return;
|
||||
}
|
||||
// If API returned error, do nothing
|
||||
final GenericResult<User?> result =
|
||||
await api.createUser(user.login, password);
|
||||
if (result.data == null) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'users.could_not_create_user'.tr());
|
||||
return;
|
||||
}
|
||||
|
||||
final List<User> loadedUsers = List<User>.from(state.users);
|
||||
loadedUsers.add(result.data!);
|
||||
await box.clear();
|
||||
await box.addAll(loadedUsers);
|
||||
emit(state.copyWith(users: loadedUsers));
|
||||
}
|
||||
|
||||
Future<void> deleteUser(final User user) async {
|
||||
// If user is primary or root, don't delete
|
||||
if (user.type != UserType.normal) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('users.could_not_delete_user'.tr());
|
||||
return;
|
||||
}
|
||||
final List<User> loadedUsers = List<User>.from(state.users);
|
||||
final GenericResult result = await api.deleteUser(user.login);
|
||||
if (result.success && result.data) {
|
||||
loadedUsers.removeWhere((final User u) => u.login == user.login);
|
||||
await box.clear();
|
||||
await box.addAll(loadedUsers);
|
||||
emit(state.copyWith(users: loadedUsers));
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.generic_error'.tr());
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> changeUserPassword(
|
||||
final User user,
|
||||
final String newPassword,
|
||||
) async {
|
||||
if (user.type == UserType.root) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('users.could_not_change_password'.tr());
|
||||
return;
|
||||
}
|
||||
final GenericResult<User?> result =
|
||||
await api.updateUser(user.login, newPassword);
|
||||
if (result.data == null) {
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
result.message ?? 'users.could_not_change_password'.tr(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addSshKey(final User user, final String publicKey) async {
|
||||
final GenericResult<User?> result =
|
||||
await api.addSshKey(user.login, publicKey);
|
||||
if (result.data != null) {
|
||||
final User updatedUser = result.data!;
|
||||
final int index =
|
||||
state.users.indexWhere((final User u) => u.login == user.login);
|
||||
await box.putAt(index, updatedUser);
|
||||
emit(
|
||||
state.copyWith(
|
||||
users: box.values.toList(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'users.could_not_add_ssh_key'.tr());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteSshKey(final User user, final String publicKey) async {
|
||||
final GenericResult<User?> result =
|
||||
await api.removeSshKey(user.login, publicKey);
|
||||
if (result.data != null) {
|
||||
final User updatedUser = result.data!;
|
||||
final int index =
|
||||
state.users.indexWhere((final User u) => u.login == user.login);
|
||||
await box.putAt(index, updatedUser);
|
||||
emit(
|
||||
state.copyWith(
|
||||
users: box.values.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() async {
|
||||
emit(
|
||||
const UsersState(
|
||||
<User>[],
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -42,47 +42,47 @@ class ApiConfigModel {
|
|||
_serverProvider = value;
|
||||
}
|
||||
|
||||
Future<void> storeDnsProviderType(final DnsProviderType value) async {
|
||||
Future<void> setDnsProviderType(final DnsProviderType value) async {
|
||||
await _box.put(BNames.dnsProvider, value);
|
||||
_dnsProvider = value;
|
||||
}
|
||||
|
||||
Future<void> storeServerProviderKey(final String value) async {
|
||||
Future<void> setServerProviderKey(final String value) async {
|
||||
await _box.put(BNames.hetznerKey, value);
|
||||
_serverProviderKey = value;
|
||||
}
|
||||
|
||||
Future<void> storeDnsProviderKey(final String value) async {
|
||||
Future<void> setDnsProviderKey(final String value) async {
|
||||
await _box.put(BNames.cloudFlareKey, value);
|
||||
_dnsProviderKey = value;
|
||||
}
|
||||
|
||||
Future<void> storeServerTypeIdentifier(final String typeIdentifier) async {
|
||||
Future<void> setServerTypeIdentifier(final String typeIdentifier) async {
|
||||
await _box.put(BNames.serverTypeIdentifier, typeIdentifier);
|
||||
_serverType = typeIdentifier;
|
||||
}
|
||||
|
||||
Future<void> storeServerLocation(final String serverLocation) async {
|
||||
Future<void> setServerLocation(final String serverLocation) async {
|
||||
await _box.put(BNames.serverLocation, serverLocation);
|
||||
_serverLocation = serverLocation;
|
||||
}
|
||||
|
||||
Future<void> storeBackblazeCredential(final BackupsCredential value) async {
|
||||
Future<void> setBackblazeCredential(final BackupsCredential value) async {
|
||||
await _box.put(BNames.backblazeCredential, value);
|
||||
_backblazeCredential = value;
|
||||
}
|
||||
|
||||
Future<void> storeServerDomain(final ServerDomain value) async {
|
||||
Future<void> setServerDomain(final ServerDomain value) async {
|
||||
await _box.put(BNames.serverDomain, value);
|
||||
_serverDomain = value;
|
||||
}
|
||||
|
||||
Future<void> storeServerDetails(final ServerHostingDetails value) async {
|
||||
Future<void> setServerDetails(final ServerHostingDetails value) async {
|
||||
await _box.put(BNames.serverDetails, value);
|
||||
_serverDetails = value;
|
||||
}
|
||||
|
||||
Future<void> storeBackblazeBucket(final BackblazeBucket value) async {
|
||||
Future<void> setBackblazeBucket(final BackblazeBucket value) async {
|
||||
await _box.put(BNames.backblazeBucket, value);
|
||||
_backblazeBucket = value;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,435 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
||||
import 'package:selfprivacy/logic/models/backup.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_disk_volume.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
import 'package:selfprivacy/logic/models/system_settings.dart';
|
||||
|
||||
/// Repository for all API calls
|
||||
/// Stores the current state of all data from API and exposes it to Blocs.
|
||||
class ApiConnectionRepository {
|
||||
Box box = Hive.box(BNames.serverInstallationBox);
|
||||
final ServerApi api = ServerApi();
|
||||
|
||||
final ApiData _apiData = ApiData(ServerApi());
|
||||
|
||||
ApiData get apiData => _apiData;
|
||||
|
||||
ConnectionStatus connectionStatus = ConnectionStatus.nonexistent;
|
||||
|
||||
final _dataStream = StreamController<ApiData>.broadcast();
|
||||
final _connectionStatusStream =
|
||||
StreamController<ConnectionStatus>.broadcast();
|
||||
|
||||
Stream<ApiData> get dataStream => _dataStream.stream;
|
||||
Stream<ConnectionStatus> get connectionStatusStream =>
|
||||
_connectionStatusStream.stream;
|
||||
|
||||
ConnectionStatus get currentConnectionStatus => connectionStatus;
|
||||
|
||||
Timer? _timer;
|
||||
|
||||
Future<void> removeServerJob(final String uid) async {
|
||||
await api.removeApiJob(uid);
|
||||
_apiData.serverJobs.data
|
||||
?.removeWhere((final ServerJob element) => element.uid == uid);
|
||||
_dataStream.add(_apiData);
|
||||
}
|
||||
|
||||
Future<void> removeAllFinishedServerJobs() async {
|
||||
final List<ServerJob> finishedJobs = _apiData.serverJobs.data
|
||||
?.where(
|
||||
(final ServerJob element) =>
|
||||
element.status == JobStatusEnum.finished ||
|
||||
element.status == JobStatusEnum.error,
|
||||
)
|
||||
.toList() ??
|
||||
[];
|
||||
// Optimistically remove the jobs from the list
|
||||
_apiData.serverJobs.data?.removeWhere(
|
||||
(final ServerJob element) =>
|
||||
element.status == JobStatusEnum.finished ||
|
||||
element.status == JobStatusEnum.error,
|
||||
);
|
||||
_dataStream.add(_apiData);
|
||||
|
||||
await Future.forEach<ServerJob>(
|
||||
finishedJobs,
|
||||
(final ServerJob job) async => removeServerJob(job.uid),
|
||||
);
|
||||
}
|
||||
|
||||
Future<(bool, String)> createUser(final User user) async {
|
||||
final List<User>? loadedUsers = _apiData.users.data;
|
||||
if (loadedUsers == null) {
|
||||
return (false, 'basis.network_error'.tr());
|
||||
}
|
||||
// If user exists on server, do nothing
|
||||
if (loadedUsers
|
||||
.any((final User u) => u.login == user.login && u.isFoundOnServer)) {
|
||||
return (false, 'users.user_already_exists'.tr());
|
||||
}
|
||||
final String? password = user.password;
|
||||
if (password == null) {
|
||||
return (false, 'users.could_not_create_user'.tr());
|
||||
}
|
||||
// If API returned error, do nothing
|
||||
final GenericResult<User?> result =
|
||||
await api.createUser(user.login, password);
|
||||
if (result.data == null) {
|
||||
return (false, result.message ?? 'users.could_not_create_user'.tr());
|
||||
}
|
||||
|
||||
_apiData.users.data?.add(result.data!);
|
||||
_apiData.users.invalidate();
|
||||
|
||||
return (true, result.message ?? 'basis.done'.tr());
|
||||
}
|
||||
|
||||
Future<(bool, String)> deleteUser(final User user) async {
|
||||
final List<User>? loadedUsers = _apiData.users.data;
|
||||
if (loadedUsers == null) {
|
||||
return (false, 'basis.network_error'.tr());
|
||||
}
|
||||
// If user is primary or root, don't delete
|
||||
if (user.type != UserType.normal) {
|
||||
return (false, 'users.could_not_delete_user'.tr());
|
||||
}
|
||||
final GenericResult result = await api.deleteUser(user.login);
|
||||
if (result.success && result.data) {
|
||||
_apiData.users.data?.removeWhere((final User u) => u.login == user.login);
|
||||
_apiData.users.invalidate();
|
||||
}
|
||||
|
||||
if (!result.success || !result.data) {
|
||||
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||
}
|
||||
|
||||
return (true, result.message ?? 'basis.done'.tr());
|
||||
}
|
||||
|
||||
Future<(bool, String)> changeUserPassword(
|
||||
final User user,
|
||||
final String newPassword,
|
||||
) async {
|
||||
if (user.type == UserType.root) {
|
||||
return (false, 'users.could_not_change_password'.tr());
|
||||
}
|
||||
final GenericResult<User?> result = await api.updateUser(
|
||||
user.login,
|
||||
newPassword,
|
||||
);
|
||||
if (result.data == null) {
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
result.message ?? 'users.could_not_change_password'.tr(),
|
||||
);
|
||||
return (
|
||||
false,
|
||||
result.message ?? 'users.could_not_change_password'.tr(),
|
||||
);
|
||||
}
|
||||
return (true, result.message ?? 'basis.done'.tr());
|
||||
}
|
||||
|
||||
Future<(bool, String)> addSshKey(
|
||||
final User user,
|
||||
final String publicKey,
|
||||
) async {
|
||||
final List<User>? loadedUsers = _apiData.users.data;
|
||||
if (loadedUsers == null) {
|
||||
return (false, 'basis.network_error'.tr());
|
||||
}
|
||||
final GenericResult<User?> result =
|
||||
await api.addSshKey(user.login, publicKey);
|
||||
if (result.data != null) {
|
||||
final User updatedUser = result.data!;
|
||||
final int index =
|
||||
loadedUsers.indexWhere((final User u) => u.login == user.login);
|
||||
loadedUsers[index] = updatedUser;
|
||||
_apiData.users.invalidate();
|
||||
} else {
|
||||
return (false, result.message ?? 'users.could_not_add_ssh_key'.tr());
|
||||
}
|
||||
|
||||
return (true, result.message ?? 'basis.done'.tr());
|
||||
}
|
||||
|
||||
Future<(bool, String)> deleteSshKey(
|
||||
final User user,
|
||||
final String publicKey,
|
||||
) async {
|
||||
final List<User>? loadedUsers = _apiData.users.data;
|
||||
if (loadedUsers == null) {
|
||||
return (false, 'basis.network_error'.tr());
|
||||
}
|
||||
final GenericResult<User?> result =
|
||||
await api.removeSshKey(user.login, publicKey);
|
||||
if (result.data != null) {
|
||||
final User updatedUser = result.data!;
|
||||
final int index =
|
||||
loadedUsers.indexWhere((final User u) => u.login == user.login);
|
||||
loadedUsers[index] = updatedUser;
|
||||
_apiData.users.invalidate();
|
||||
} else {
|
||||
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||
}
|
||||
return (true, result.message ?? 'basis.done'.tr());
|
||||
}
|
||||
|
||||
Future<(bool, String)> setAutoUpgradeSettings(
|
||||
final bool enable,
|
||||
final bool allowReboot,
|
||||
) async {
|
||||
final GenericResult<AutoUpgradeSettings?> result =
|
||||
await api.setAutoUpgradeSettings(
|
||||
AutoUpgradeSettings(
|
||||
enable: enable,
|
||||
allowReboot: allowReboot,
|
||||
),
|
||||
);
|
||||
_apiData.settings.invalidate();
|
||||
if (result.data != null) {
|
||||
return (true, result.message ?? 'basis.done'.tr());
|
||||
} else {
|
||||
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||
}
|
||||
}
|
||||
|
||||
Future<(bool, String)> setServerTimezone(
|
||||
final String timezone,
|
||||
) async {
|
||||
final GenericResult result = await api.setTimezone(timezone);
|
||||
_apiData.settings.invalidate();
|
||||
if (result.success) {
|
||||
return (true, result.message ?? 'basis.done'.tr());
|
||||
} else {
|
||||
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_dataStream.close();
|
||||
_connectionStatusStream.close();
|
||||
_timer?.cancel();
|
||||
}
|
||||
|
||||
ServerHostingDetails? get serverDetails =>
|
||||
getIt<ApiConfigModel>().serverDetails;
|
||||
ServerDomain? get serverDomain => getIt<ApiConfigModel>().serverDomain;
|
||||
|
||||
void init() async {
|
||||
final serverDetails = getIt<ApiConfigModel>().serverDetails;
|
||||
final hasFinalChecked =
|
||||
box.get(BNames.hasFinalChecked, defaultValue: false);
|
||||
if (serverDetails == null || !hasFinalChecked) {
|
||||
return;
|
||||
}
|
||||
connectionStatus = ConnectionStatus.reconnecting;
|
||||
_connectionStatusStream.add(connectionStatus);
|
||||
|
||||
final String? apiVersion = await api.getApiVersion();
|
||||
if (apiVersion == null) {
|
||||
connectionStatus = ConnectionStatus.offline;
|
||||
_connectionStatusStream.add(connectionStatus);
|
||||
return;
|
||||
} else {
|
||||
_apiData.apiVersion.data = apiVersion;
|
||||
_dataStream.add(_apiData);
|
||||
}
|
||||
|
||||
await _refetchEverything(Version.parse(apiVersion));
|
||||
|
||||
connectionStatus = ConnectionStatus.connected;
|
||||
_connectionStatusStream.add(connectionStatus);
|
||||
|
||||
// Use timer to periodically check for new jobs
|
||||
_timer = Timer.periodic(
|
||||
const Duration(seconds: 10),
|
||||
reload,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _refetchEverything(final Version version) async {
|
||||
await _apiData.serverJobs
|
||||
.refetchData(version, () => _dataStream.add(_apiData));
|
||||
await _apiData.backups
|
||||
.refetchData(version, () => _dataStream.add(_apiData));
|
||||
await _apiData.backupConfig
|
||||
.refetchData(version, () => _dataStream.add(_apiData));
|
||||
await _apiData.services
|
||||
.refetchData(version, () => _dataStream.add(_apiData));
|
||||
await _apiData.volumes
|
||||
.refetchData(version, () => _dataStream.add(_apiData));
|
||||
await _apiData.recoveryKeyStatus
|
||||
.refetchData(version, () => _dataStream.add(_apiData));
|
||||
await _apiData.devices
|
||||
.refetchData(version, () => _dataStream.add(_apiData));
|
||||
await _apiData.users.refetchData(version, () => _dataStream.add(_apiData));
|
||||
await _apiData.settings
|
||||
.refetchData(version, () => _dataStream.add(_apiData));
|
||||
}
|
||||
|
||||
Future<void> reload(final Timer? timer) async {
|
||||
final serverDetails = getIt<ApiConfigModel>().serverDetails;
|
||||
if (serverDetails == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String? apiVersion = await api.getApiVersion();
|
||||
if (apiVersion == null) {
|
||||
connectionStatus = ConnectionStatus.offline;
|
||||
_connectionStatusStream.add(connectionStatus);
|
||||
return;
|
||||
} else {
|
||||
connectionStatus = ConnectionStatus.connected;
|
||||
_connectionStatusStream.add(connectionStatus);
|
||||
_apiData.apiVersion.data = apiVersion;
|
||||
}
|
||||
final Version version = Version.parse(apiVersion);
|
||||
await _refetchEverything(version);
|
||||
}
|
||||
|
||||
void emitData() {
|
||||
_dataStream.add(_apiData);
|
||||
}
|
||||
}
|
||||
|
||||
class ApiData {
|
||||
ApiData(final ServerApi api)
|
||||
: apiVersion = ApiDataElement<String>(
|
||||
fetchData: () async => api.getApiVersion(),
|
||||
),
|
||||
serverJobs = ApiDataElement<List<ServerJob>>(
|
||||
fetchData: () async => api.getServerJobs(),
|
||||
ttl: 10,
|
||||
),
|
||||
backupConfig = ApiDataElement<BackupConfiguration>(
|
||||
fetchData: () async => api.getBackupsConfiguration(),
|
||||
requiredApiVersion: '>=2.4.2',
|
||||
ttl: 120,
|
||||
),
|
||||
backups = ApiDataElement<List<Backup>>(
|
||||
fetchData: () async => api.getBackups(),
|
||||
requiredApiVersion: '>=2.4.2',
|
||||
ttl: 120,
|
||||
),
|
||||
services = ApiDataElement<List<Service>>(
|
||||
fetchData: () async => api.getAllServices(),
|
||||
requiredApiVersion: '>=2.4.3',
|
||||
),
|
||||
volumes = ApiDataElement<List<ServerDiskVolume>>(
|
||||
fetchData: () async => api.getServerDiskVolumes(),
|
||||
),
|
||||
recoveryKeyStatus = ApiDataElement<RecoveryKeyStatus>(
|
||||
fetchData: () async => (await api.getRecoveryTokenStatus()).data,
|
||||
ttl: 300,
|
||||
),
|
||||
devices = ApiDataElement<List<ApiToken>>(
|
||||
fetchData: () async => (await api.getApiTokens()).data,
|
||||
),
|
||||
users = ApiDataElement<List<User>>(
|
||||
fetchData: () async => api.getAllUsers(),
|
||||
),
|
||||
settings = ApiDataElement<SystemSettings>(
|
||||
fetchData: () async => api.getSystemSettings(),
|
||||
ttl: 600,
|
||||
);
|
||||
|
||||
ApiDataElement<List<ServerJob>> serverJobs;
|
||||
ApiDataElement<String> apiVersion;
|
||||
ApiDataElement<BackupConfiguration> backupConfig;
|
||||
ApiDataElement<List<Backup>> backups;
|
||||
ApiDataElement<List<Service>> services;
|
||||
ApiDataElement<List<ServerDiskVolume>> volumes;
|
||||
ApiDataElement<RecoveryKeyStatus> recoveryKeyStatus;
|
||||
ApiDataElement<List<ApiToken>> devices;
|
||||
ApiDataElement<List<User>> users;
|
||||
ApiDataElement<SystemSettings> settings;
|
||||
}
|
||||
|
||||
enum ConnectionStatus {
|
||||
nonexistent,
|
||||
connected,
|
||||
reconnecting,
|
||||
offline,
|
||||
unauthorized,
|
||||
}
|
||||
|
||||
class ApiDataElement<T> {
|
||||
ApiDataElement({
|
||||
required this.fetchData,
|
||||
final T? data,
|
||||
this.requiredApiVersion = '>=2.3.0',
|
||||
this.ttl = 60,
|
||||
}) : _data = data,
|
||||
_lastUpdated = DateTime.now();
|
||||
|
||||
T? _data;
|
||||
final String requiredApiVersion;
|
||||
|
||||
final Future<T?> Function() fetchData;
|
||||
|
||||
Future<void> refetchData(
|
||||
final Version version,
|
||||
final Function callback,
|
||||
) async {
|
||||
if (VersionConstraint.parse(requiredApiVersion).allows(version)) {
|
||||
if (isExpired || _data == null) {
|
||||
final newData = await fetchData();
|
||||
if (T is List) {
|
||||
if (Object.hashAll(newData as Iterable<Object?>) !=
|
||||
Object.hashAll(_data as Iterable<Object?>)) {
|
||||
_data = [...newData] as T?;
|
||||
}
|
||||
} else {
|
||||
if (newData.hashCode != _data.hashCode) {
|
||||
_data = newData;
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TTL of the data in seconds
|
||||
final int ttl;
|
||||
|
||||
Type get type => T;
|
||||
|
||||
void invalidate() {
|
||||
_lastUpdated = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
}
|
||||
|
||||
/// Timestamp of when the data was last updated
|
||||
DateTime _lastUpdated;
|
||||
|
||||
bool get isExpired {
|
||||
final now = DateTime.now();
|
||||
final difference = now.difference(_lastUpdated);
|
||||
return difference.inSeconds > ttl;
|
||||
}
|
||||
|
||||
T? get data => _data;
|
||||
|
||||
/// Sets the data and updates the lastUpdated timestamp
|
||||
set data(final T? data) {
|
||||
_data = data;
|
||||
_lastUpdated = DateTime.now();
|
||||
}
|
||||
|
||||
/// Returns the last time the data was updated
|
||||
DateTime get lastUpdated => _lastUpdated;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class TimerModel extends ChangeNotifier {
|
||||
DateTime _time = DateTime.now();
|
||||
|
||||
DateTime get time => _time;
|
||||
|
||||
void restart() {
|
||||
_time = DateTime.now();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/backups.graphql.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
|
||||
|
@ -40,7 +41,7 @@ extension BackupReasonExtension on Enum$BackupReason {
|
|||
};
|
||||
}
|
||||
|
||||
class BackupConfiguration {
|
||||
class BackupConfiguration extends Equatable {
|
||||
BackupConfiguration.fromGraphQL(
|
||||
final Query$BackupConfiguration$backup$configuration configuration,
|
||||
) : this(
|
||||
|
@ -58,7 +59,7 @@ class BackupConfiguration {
|
|||
),
|
||||
);
|
||||
|
||||
BackupConfiguration({
|
||||
const BackupConfiguration({
|
||||
required this.autobackupPeriod,
|
||||
required this.encryptionKey,
|
||||
required this.isInitialized,
|
||||
|
@ -75,9 +76,39 @@ class BackupConfiguration {
|
|||
final String? locationName;
|
||||
final BackupsProviderType provider;
|
||||
final AutobackupQuotas autobackupQuotas;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
autobackupPeriod,
|
||||
encryptionKey,
|
||||
isInitialized,
|
||||
locationId,
|
||||
locationName,
|
||||
provider,
|
||||
autobackupQuotas,
|
||||
];
|
||||
|
||||
BackupConfiguration copyWith({
|
||||
final Duration? autobackupPeriod,
|
||||
final String? encryptionKey,
|
||||
final bool? isInitialized,
|
||||
final String? locationId,
|
||||
final String? locationName,
|
||||
final BackupsProviderType? provider,
|
||||
final AutobackupQuotas? autobackupQuotas,
|
||||
}) =>
|
||||
BackupConfiguration(
|
||||
autobackupPeriod: autobackupPeriod ?? this.autobackupPeriod,
|
||||
encryptionKey: encryptionKey ?? this.encryptionKey,
|
||||
isInitialized: isInitialized ?? this.isInitialized,
|
||||
locationId: locationId ?? this.locationId,
|
||||
locationName: locationName ?? this.locationName,
|
||||
provider: provider ?? this.provider,
|
||||
autobackupQuotas: autobackupQuotas ?? this.autobackupQuotas,
|
||||
);
|
||||
}
|
||||
|
||||
class AutobackupQuotas {
|
||||
class AutobackupQuotas extends Equatable {
|
||||
AutobackupQuotas.fromGraphQL(
|
||||
final Query$BackupConfiguration$backup$configuration$autobackupQuotas
|
||||
autobackupQuotas,
|
||||
|
@ -89,7 +120,7 @@ class AutobackupQuotas {
|
|||
yearly: autobackupQuotas.yearly,
|
||||
);
|
||||
|
||||
AutobackupQuotas({
|
||||
const AutobackupQuotas({
|
||||
required this.last,
|
||||
required this.daily,
|
||||
required this.weekly,
|
||||
|
@ -117,6 +148,15 @@ class AutobackupQuotas {
|
|||
monthly: monthly ?? this.monthly,
|
||||
yearly: yearly ?? this.yearly,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
last,
|
||||
daily,
|
||||
weekly,
|
||||
monthly,
|
||||
yearly,
|
||||
];
|
||||
}
|
||||
|
||||
enum BackupRestoreStrategy {
|
||||
|
|
|
@ -9,13 +9,12 @@ class DiskVolume {
|
|||
this.sizeUsed = const DiskSize(byte: 0),
|
||||
this.root = false,
|
||||
this.isResizable = false,
|
||||
this.serverDiskVolume,
|
||||
this.providerVolume,
|
||||
});
|
||||
|
||||
DiskVolume.fromServerDiscVolume(
|
||||
final ServerDiskVolume volume,
|
||||
final ServerVolume? providerVolume,
|
||||
final ServerProviderVolume? providerVolume,
|
||||
) : this(
|
||||
name: volume.name,
|
||||
sizeTotal: DiskSize(
|
||||
|
@ -27,7 +26,6 @@ class DiskVolume {
|
|||
),
|
||||
root: volume.root,
|
||||
isResizable: providerVolume != null,
|
||||
serverDiskVolume: volume,
|
||||
providerVolume: providerVolume,
|
||||
);
|
||||
|
||||
|
@ -51,8 +49,7 @@ class DiskVolume {
|
|||
String name;
|
||||
bool root;
|
||||
bool isResizable;
|
||||
ServerDiskVolume? serverDiskVolume;
|
||||
ServerVolume? providerVolume;
|
||||
ServerProviderVolume? providerVolume;
|
||||
|
||||
/// from 0.0 to 1.0
|
||||
double get percentage =>
|
||||
|
@ -67,7 +64,7 @@ class DiskVolume {
|
|||
final bool? root,
|
||||
final bool? isResizable,
|
||||
final ServerDiskVolume? serverDiskVolume,
|
||||
final ServerVolume? providerVolume,
|
||||
final ServerProviderVolume? providerVolume,
|
||||
}) =>
|
||||
DiskVolume(
|
||||
sizeUsed: sizeUsed ?? this.sizeUsed,
|
||||
|
@ -75,7 +72,6 @@ class DiskVolume {
|
|||
name: name ?? this.name,
|
||||
root: root ?? this.root,
|
||||
isResizable: isResizable ?? this.isResizable,
|
||||
serverDiskVolume: serverDiskVolume ?? this.serverDiskVolume,
|
||||
providerVolume: providerVolume ?? this.providerVolume,
|
||||
);
|
||||
}
|
||||
|
@ -83,14 +79,15 @@ class DiskVolume {
|
|||
class DiskStatus {
|
||||
DiskStatus.fromVolumes(
|
||||
final List<ServerDiskVolume> serverVolumes,
|
||||
final List<ServerVolume> providerVolumes,
|
||||
final List<ServerProviderVolume> providerVolumes,
|
||||
) {
|
||||
diskVolumes = serverVolumes.map((
|
||||
final ServerDiskVolume volume,
|
||||
) {
|
||||
ServerVolume? providerVolume;
|
||||
ServerProviderVolume? providerVolume;
|
||||
|
||||
for (final ServerVolume iterableProviderVolume in providerVolumes) {
|
||||
for (final ServerProviderVolume iterableProviderVolume
|
||||
in providerVolumes) {
|
||||
if (iterableProviderVolume.linuxDevice == null ||
|
||||
volume.model == null ||
|
||||
volume.serial == null) {
|
||||
|
|
|
@ -29,4 +29,19 @@ class BackblazeBucket {
|
|||
|
||||
@override
|
||||
String toString() => bucketName;
|
||||
|
||||
BackblazeBucket copyWith({
|
||||
final String? bucketId,
|
||||
final String? applicationKeyId,
|
||||
final String? applicationKey,
|
||||
final String? bucketName,
|
||||
final String? encryptionKey,
|
||||
}) =>
|
||||
BackblazeBucket(
|
||||
bucketId: bucketId ?? this.bucketId,
|
||||
applicationKeyId: applicationKeyId ?? this.applicationKeyId,
|
||||
applicationKey: applicationKey ?? this.applicationKey,
|
||||
bucketName: bucketName ?? this.bucketName,
|
||||
encryptionKey: encryptionKey ?? this.encryptionKey,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -27,8 +27,9 @@ class ServerHostingDetails {
|
|||
@HiveField(2)
|
||||
final DateTime? startTime;
|
||||
|
||||
// TODO: Check if it is still needed
|
||||
NaiJi
commented
Review
I am 99% sure it isn't 🙏 will do some cleaning evenetually I am 99% sure it isn't 🙏 will do some cleaning evenetually
|
||||
@HiveField(4)
|
||||
final ServerVolume volume;
|
||||
final ServerProviderVolume volume;
|
||||
|
||||
@HiveField(5)
|
||||
final String apiToken;
|
||||
|
@ -52,8 +53,8 @@ class ServerHostingDetails {
|
|||
}
|
||||
|
||||
@HiveType(typeId: 5)
|
||||
class ServerVolume {
|
||||
ServerVolume({
|
||||
class ServerProviderVolume {
|
||||
ServerProviderVolume({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.sizeByte,
|
||||
|
|
|
@ -20,7 +20,7 @@ class ServerHostingDetailsAdapter extends TypeAdapter<ServerHostingDetails> {
|
|||
ip4: fields[0] as String,
|
||||
id: fields[1] as int,
|
||||
createTime: fields[3] as DateTime?,
|
||||
volume: fields[4] as ServerVolume,
|
||||
volume: fields[4] as ServerProviderVolume,
|
||||
apiToken: fields[5] as String,
|
||||
provider: fields[6] == null
|
||||
? ServerProviderType.hetzner
|
||||
|
@ -60,17 +60,17 @@ class ServerHostingDetailsAdapter extends TypeAdapter<ServerHostingDetails> {
|
|||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class ServerVolumeAdapter extends TypeAdapter<ServerVolume> {
|
||||
class ServerProviderVolumeAdapter extends TypeAdapter<ServerProviderVolume> {
|
||||
@override
|
||||
final int typeId = 5;
|
||||
|
||||
@override
|
||||
ServerVolume read(BinaryReader reader) {
|
||||
ServerProviderVolume read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return ServerVolume(
|
||||
return ServerProviderVolume(
|
||||
id: fields[1] as int,
|
||||
name: fields[2] as String,
|
||||
sizeByte: fields[3] == null ? 10737418240 : fields[3] as int,
|
||||
|
@ -81,7 +81,7 @@ class ServerVolumeAdapter extends TypeAdapter<ServerVolume> {
|
|||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ServerVolume obj) {
|
||||
void write(BinaryWriter writer, ServerProviderVolume obj) {
|
||||
writer
|
||||
..writeByte(6)
|
||||
..writeByte(1)
|
||||
|
@ -104,7 +104,7 @@ class ServerVolumeAdapter extends TypeAdapter<ServerVolume> {
|
|||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ServerVolumeAdapter &&
|
||||
other is ServerProviderVolumeAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
import 'package:selfprivacy/utils/password_generator.dart';
|
||||
|
||||
|
@ -11,69 +12,148 @@ abstract class ClientJob extends Equatable {
|
|||
ClientJob({
|
||||
required this.title,
|
||||
final String? id,
|
||||
this.requiresRebuild = true,
|
||||
this.status = JobStatusEnum.created,
|
||||
this.message,
|
||||
}) : id = id ?? StringGenerators.simpleId();
|
||||
|
||||
final String title;
|
||||
final String id;
|
||||
final bool requiresRebuild;
|
||||
|
||||
final JobStatusEnum status;
|
||||
final String? message;
|
||||
|
||||
bool canAddTo(final List<ClientJob> jobs) => true;
|
||||
void execute(final JobsCubit cubit);
|
||||
Future<(bool, String)> execute();
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title];
|
||||
List<Object> get props => [id, title, status];
|
||||
|
||||
ClientJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
});
|
||||
}
|
||||
|
||||
class RebuildServerJob extends ClientJob {
|
||||
RebuildServerJob({
|
||||
required super.title,
|
||||
class UpgradeServerJob extends ClientJob {
|
||||
UpgradeServerJob({
|
||||
super.status,
|
||||
super.message,
|
||||
super.id,
|
||||
});
|
||||
}) : super(title: 'jobs.start_server_upgrade'.tr());
|
||||
|
||||
@override
|
||||
bool canAddTo(final List<ClientJob> jobs) =>
|
||||
!jobs.any((final job) => job is RebuildServerJob);
|
||||
!jobs.any((final job) => job is UpgradeServerJob);
|
||||
|
||||
@override
|
||||
void execute(final JobsCubit cubit) async {
|
||||
await cubit.upgradeServer();
|
||||
Future<(bool, String)> execute() async => (false, 'unimplemented');
|
||||
|
||||
@override
|
||||
UpgradeServerJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
UpgradeServerJob(
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
||||
class RebootServerJob extends ClientJob {
|
||||
RebootServerJob({
|
||||
super.status,
|
||||
super.message,
|
||||
super.id,
|
||||
}) : super(title: 'jobs.reboot_server'.tr(), requiresRebuild: false);
|
||||
|
||||
@override
|
||||
bool canAddTo(final List<ClientJob> jobs) =>
|
||||
!jobs.any((final job) => job is RebootServerJob);
|
||||
|
||||
@override
|
||||
Future<(bool, String)> execute() async => (false, 'unimplemented');
|
||||
|
||||
@override
|
||||
RebootServerJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
RebootServerJob(
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
||||
class CreateUserJob extends ClientJob {
|
||||
CreateUserJob({
|
||||
required this.user,
|
||||
super.status,
|
||||
super.message,
|
||||
super.id,
|
||||
}) : super(title: '${"jobs.create_user".tr()} ${user.login}');
|
||||
|
||||
final User user;
|
||||
|
||||
@override
|
||||
void execute(final JobsCubit cubit) async {
|
||||
await cubit.usersCubit.createUser(user);
|
||||
}
|
||||
Future<(bool, String)> execute() async =>
|
||||
getIt<ApiConnectionRepository>().createUser(user);
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title, user];
|
||||
List<Object> get props => [...super.props, user];
|
||||
|
||||
@override
|
||||
CreateUserJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
CreateUserJob(
|
||||
user: user,
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
||||
class ResetUserPasswordJob extends ClientJob {
|
||||
ResetUserPasswordJob({
|
||||
required this.user,
|
||||
super.status,
|
||||
super.message,
|
||||
super.id,
|
||||
}) : super(title: '${"jobs.reset_user_password".tr()} ${user.login}');
|
||||
|
||||
final User user;
|
||||
|
||||
@override
|
||||
void execute(final JobsCubit cubit) async {
|
||||
await cubit.usersCubit.changeUserPassword(user, user.password!);
|
||||
}
|
||||
Future<(bool, String)> execute() async =>
|
||||
getIt<ApiConnectionRepository>().changeUserPassword(user, user.password!);
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title, user];
|
||||
List<Object> get props => [...super.props, user];
|
||||
|
||||
@override
|
||||
ResetUserPasswordJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
ResetUserPasswordJob(
|
||||
user: user,
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
||||
class DeleteUserJob extends ClientJob {
|
||||
DeleteUserJob({
|
||||
required this.user,
|
||||
super.status,
|
||||
super.message,
|
||||
super.id,
|
||||
}) : super(title: '${"jobs.delete_user".tr()} ${user.login}');
|
||||
|
||||
final User user;
|
||||
|
@ -84,18 +164,32 @@ class DeleteUserJob extends ClientJob {
|
|||
);
|
||||
|
||||
@override
|
||||
void execute(final JobsCubit cubit) async {
|
||||
await cubit.usersCubit.deleteUser(user);
|
||||
}
|
||||
Future<(bool, String)> execute() async =>
|
||||
getIt<ApiConnectionRepository>().deleteUser(user);
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title, user];
|
||||
List<Object> get props => [...super.props, user];
|
||||
|
||||
@override
|
||||
DeleteUserJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
DeleteUserJob(
|
||||
user: user,
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
||||
class ServiceToggleJob extends ClientJob {
|
||||
ServiceToggleJob({
|
||||
required this.service,
|
||||
required this.needToTurnOn,
|
||||
super.status,
|
||||
super.message,
|
||||
super.id,
|
||||
}) : super(
|
||||
title:
|
||||
'${needToTurnOn ? "jobs.service_turn_on".tr() : "jobs.service_turn_off".tr()} ${service.displayName}',
|
||||
|
@ -110,36 +204,70 @@ class ServiceToggleJob extends ClientJob {
|
|||
);
|
||||
|
||||
@override
|
||||
void execute(final JobsCubit cubit) async {
|
||||
await cubit.api.switchService(service.id, needToTurnOn);
|
||||
Future<(bool, String)> execute() async {
|
||||
final result = await getIt<ApiConnectionRepository>()
|
||||
.api
|
||||
.switchService(service.id, needToTurnOn);
|
||||
return (result.success, result.message ?? 'jobs.generic_error'.tr());
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [...super.props, service];
|
||||
|
||||
@override
|
||||
ServiceToggleJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
ServiceToggleJob(
|
||||
service: service,
|
||||
needToTurnOn: needToTurnOn,
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
||||
class CreateSSHKeyJob extends ClientJob {
|
||||
CreateSSHKeyJob({
|
||||
required this.user,
|
||||
required this.publicKey,
|
||||
super.status,
|
||||
super.message,
|
||||
super.id,
|
||||
}) : super(title: 'jobs.create_ssh_key'.tr(args: [user.login]));
|
||||
|
||||
final User user;
|
||||
final String publicKey;
|
||||
|
||||
@override
|
||||
void execute(final JobsCubit cubit) async {
|
||||
await cubit.usersCubit.addSshKey(user, publicKey);
|
||||
}
|
||||
Future<(bool, String)> execute() async =>
|
||||
getIt<ApiConnectionRepository>().addSshKey(user, publicKey);
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title, user, publicKey];
|
||||
List<Object> get props => [...super.props, user, publicKey];
|
||||
|
||||
@override
|
||||
CreateSSHKeyJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
CreateSSHKeyJob(
|
||||
user: user,
|
||||
publicKey: publicKey,
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
||||
class DeleteSSHKeyJob extends ClientJob {
|
||||
DeleteSSHKeyJob({
|
||||
required this.user,
|
||||
required this.publicKey,
|
||||
super.status,
|
||||
super.message,
|
||||
super.id,
|
||||
}) : super(title: 'jobs.delete_ssh_key'.tr(args: [user.login]));
|
||||
|
||||
final User user;
|
||||
|
@ -154,10 +282,120 @@ class DeleteSSHKeyJob extends ClientJob {
|
|||
);
|
||||
|
||||
@override
|
||||
void execute(final JobsCubit cubit) async {
|
||||
await cubit.usersCubit.deleteSshKey(user, publicKey);
|
||||
Future<(bool, String)> execute() async =>
|
||||
getIt<ApiConnectionRepository>().deleteSshKey(user, publicKey);
|
||||
|
||||
@override
|
||||
List<Object> get props => [...super.props, user, publicKey];
|
||||
|
||||
@override
|
||||
DeleteSSHKeyJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
DeleteSSHKeyJob(
|
||||
user: user,
|
||||
publicKey: publicKey,
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
||||
abstract class ReplaceableJob extends ClientJob {
|
||||
ReplaceableJob({
|
||||
required super.title,
|
||||
super.id,
|
||||
super.status,
|
||||
super.message,
|
||||
});
|
||||
|
||||
bool shouldRemoveInsteadOfAdd(final List<ClientJob> jobs) => false;
|
||||
}
|
||||
|
||||
class ChangeAutoUpgradeSettingsJob extends ReplaceableJob {
|
||||
ChangeAutoUpgradeSettingsJob({
|
||||
required this.enable,
|
||||
required this.allowReboot,
|
||||
super.status,
|
||||
super.message,
|
||||
super.id,
|
||||
}) : super(title: 'jobs.change_auto_upgrade_settings'.tr());
|
||||
|
||||
final bool enable;
|
||||
final bool allowReboot;
|
||||
|
||||
@override
|
||||
Future<(bool, String)> execute() async => getIt<ApiConnectionRepository>()
|
||||
.setAutoUpgradeSettings(enable, allowReboot);
|
||||
|
||||
@override
|
||||
bool shouldRemoveInsteadOfAdd(final List<ClientJob> jobs) {
|
||||
final currentSettings = getIt<ApiConnectionRepository>()
|
||||
.apiData
|
||||
.settings
|
||||
.data
|
||||
?.autoUpgradeSettings;
|
||||
if (currentSettings == null) {
|
||||
return false;
|
||||
}
|
||||
return currentSettings.enable == enable &&
|
||||
currentSettings.allowReboot == allowReboot;
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title, user, publicKey];
|
||||
List<Object> get props => [...super.props, enable, allowReboot];
|
||||
|
||||
@override
|
||||
ChangeAutoUpgradeSettingsJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
ChangeAutoUpgradeSettingsJob(
|
||||
enable: enable,
|
||||
allowReboot: allowReboot,
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
||||
class ChangeServerTimezoneJob extends ReplaceableJob {
|
||||
ChangeServerTimezoneJob({
|
||||
required this.timezone,
|
||||
super.status,
|
||||
super.message,
|
||||
super.id,
|
||||
}) : super(title: 'jobs.change_server_timezone'.tr());
|
||||
|
||||
final String timezone;
|
||||
|
||||
@override
|
||||
Future<(bool, String)> execute() async =>
|
||||
NaiJi
commented
Review
Can't we use Can't we use `GenericResult` everywhere? Is it overkill?
|
||||
getIt<ApiConnectionRepository>().setServerTimezone(timezone);
|
||||
|
||||
@override
|
||||
bool shouldRemoveInsteadOfAdd(final List<ClientJob> jobs) {
|
||||
final currentSettings =
|
||||
getIt<ApiConnectionRepository>().apiData.settings.data?.timezone;
|
||||
if (currentSettings == null) {
|
||||
return false;
|
||||
}
|
||||
return currentSettings == timezone;
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [...super.props, timezone];
|
||||
|
||||
@override
|
||||
ChangeServerTimezoneJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
ChangeServerTimezoneJob(
|
||||
timezone: timezone,
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
|
||||
|
||||
part 'api_token.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ApiToken {
|
||||
class ApiToken extends Equatable {
|
||||
factory ApiToken.fromJson(final Map<String, dynamic> json) =>
|
||||
_$ApiTokenFromJson(json);
|
||||
ApiToken({
|
||||
const ApiToken({
|
||||
required this.name,
|
||||
required this.date,
|
||||
required this.isCaller,
|
||||
|
@ -25,4 +26,7 @@ class ApiToken {
|
|||
final DateTime date;
|
||||
@JsonKey(name: 'is_caller')
|
||||
final bool isCaller;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, date, isCaller];
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'server_disk_volume.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ServerDiskVolume {
|
||||
class ServerDiskVolume extends Equatable {
|
||||
factory ServerDiskVolume.fromJson(final Map<String, dynamic> json) =>
|
||||
_$ServerDiskVolumeFromJson(json);
|
||||
ServerDiskVolume({
|
||||
const ServerDiskVolume({
|
||||
required this.freeSpace,
|
||||
required this.model,
|
||||
required this.name,
|
||||
|
@ -25,4 +26,16 @@ class ServerDiskVolume {
|
|||
final String totalSpace;
|
||||
final String type;
|
||||
final String usedSpace;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
freeSpace,
|
||||
model,
|
||||
name,
|
||||
root,
|
||||
serial,
|
||||
totalSpace,
|
||||
type,
|
||||
usedSpace,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
|
||||
|
||||
part 'server_job.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ServerJob {
|
||||
class ServerJob extends Equatable {
|
||||
factory ServerJob.fromJson(final Map<String, dynamic> json) =>
|
||||
_$ServerJobFromJson(json);
|
||||
ServerJob({
|
||||
const ServerJob({
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.status,
|
||||
|
@ -50,6 +51,22 @@ class ServerJob {
|
|||
final String? result;
|
||||
final String? statusText;
|
||||
final DateTime? finishedAt;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
uid,
|
||||
typeId,
|
||||
updatedAt,
|
||||
createdAt,
|
||||
error,
|
||||
progress,
|
||||
result,
|
||||
statusText,
|
||||
finishedAt,
|
||||
];
|
||||
}
|
||||
|
||||
enum JobStatusEnum {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart';
|
||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||
|
||||
class Service {
|
||||
class Service extends Equatable {
|
||||
Service.fromGraphQL(final Query$AllServices$services$allServices service)
|
||||
: this(
|
||||
id: service.id,
|
||||
|
@ -36,7 +37,7 @@ class Service {
|
|||
[],
|
||||
url: service.url,
|
||||
);
|
||||
Service({
|
||||
const Service({
|
||||
required this.id,
|
||||
required this.displayName,
|
||||
required this.description,
|
||||
|
@ -71,7 +72,7 @@ class Service {
|
|||
return '';
|
||||
}
|
||||
|
||||
static Service empty = Service(
|
||||
static Service empty = const Service(
|
||||
id: 'empty',
|
||||
displayName: '',
|
||||
description: '',
|
||||
|
@ -82,7 +83,7 @@ class Service {
|
|||
backupDescription: '',
|
||||
status: ServiceStatus.off,
|
||||
storageUsage: ServiceStorageUsage(
|
||||
used: const DiskSize(byte: 0),
|
||||
used: DiskSize(byte: 0),
|
||||
volume: '',
|
||||
),
|
||||
svgIcon: '',
|
||||
|
@ -103,16 +104,36 @@ class Service {
|
|||
final String svgIcon;
|
||||
final String? url;
|
||||
final List<DnsRecord> dnsRecords;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
displayName,
|
||||
description,
|
||||
isEnabled,
|
||||
isRequired,
|
||||
isMovable,
|
||||
canBeBackedUp,
|
||||
backupDescription,
|
||||
status,
|
||||
storageUsage,
|
||||
svgIcon,
|
||||
dnsRecords,
|
||||
url,
|
||||
];
|
||||
}
|
||||
|
||||
class ServiceStorageUsage {
|
||||
ServiceStorageUsage({
|
||||
class ServiceStorageUsage extends Equatable {
|
||||
const ServiceStorageUsage({
|
||||
required this.used,
|
||||
required this.volume,
|
||||
});
|
||||
|
||||
final DiskSize used;
|
||||
final String? volume;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [used, volume];
|
||||
}
|
||||
|
||||
enum ServiceStatus {
|
||||
|
|
|
@ -336,7 +336,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
}
|
||||
|
||||
final volumes = await getVolumes();
|
||||
final ServerVolume volumeToRemove;
|
||||
final ServerProviderVolume volumeToRemove;
|
||||
volumeToRemove = volumes.data.firstWhere(
|
||||
(final el) => el.serverId == foundServer!.id,
|
||||
);
|
||||
|
@ -548,10 +548,10 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerVolume>>> getVolumes({
|
||||
Future<GenericResult<List<ServerProviderVolume>>> getVolumes({
|
||||
final String? status,
|
||||
}) async {
|
||||
final List<ServerVolume> volumes = [];
|
||||
final List<ServerProviderVolume> volumes = [];
|
||||
|
||||
final result = await _adapter.api().getVolumes();
|
||||
|
||||
|
@ -568,7 +568,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
int id = 0;
|
||||
for (final rawVolume in result.data) {
|
||||
final String volumeName = rawVolume.name;
|
||||
final volume = ServerVolume(
|
||||
final volume = ServerProviderVolume(
|
||||
id: id++,
|
||||
name: volumeName,
|
||||
sizeByte: rawVolume.sizeGigabytes * 1024 * 1024 * 1024,
|
||||
|
@ -597,8 +597,10 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<ServerVolume?>> createVolume(final int gb) async {
|
||||
ServerVolume? volume;
|
||||
Future<GenericResult<ServerProviderVolume?>> createVolume(
|
||||
final int gb,
|
||||
) async {
|
||||
ServerProviderVolume? volume;
|
||||
|
||||
final result = await _adapter.api().createVolume(gb);
|
||||
|
||||
|
@ -623,7 +625,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
}
|
||||
|
||||
final String volumeName = result.data!.name;
|
||||
volume = ServerVolume(
|
||||
volume = ServerProviderVolume(
|
||||
id: getVolumesResult.data.length,
|
||||
name: volumeName,
|
||||
sizeByte: result.data!.sizeGigabytes,
|
||||
|
@ -638,10 +640,10 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
);
|
||||
}
|
||||
|
||||
Future<GenericResult<ServerVolume?>> getVolume(
|
||||
Future<GenericResult<ServerProviderVolume?>> getVolume(
|
||||
final String volumeUuid,
|
||||
) async {
|
||||
ServerVolume? requestedVolume;
|
||||
ServerProviderVolume? requestedVolume;
|
||||
|
||||
final result = await getVolumes();
|
||||
|
||||
|
@ -668,7 +670,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
|
||||
@override
|
||||
Future<GenericResult<bool>> attachVolume(
|
||||
final ServerVolume volume,
|
||||
final ServerProviderVolume volume,
|
||||
final int serverId,
|
||||
) async =>
|
||||
_adapter.api().attachVolume(
|
||||
|
@ -678,7 +680,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
|
||||
@override
|
||||
Future<GenericResult<bool>> detachVolume(
|
||||
final ServerVolume volume,
|
||||
final ServerProviderVolume volume,
|
||||
) async =>
|
||||
_adapter.api().detachVolume(
|
||||
volume.name,
|
||||
|
@ -687,7 +689,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
|
||||
@override
|
||||
Future<GenericResult<void>> deleteVolume(
|
||||
final ServerVolume volume,
|
||||
final ServerProviderVolume volume,
|
||||
) async =>
|
||||
_adapter.api().deleteVolume(
|
||||
volume.uuid!,
|
||||
|
@ -695,7 +697,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
|
||||
@override
|
||||
Future<GenericResult<bool>> resizeVolume(
|
||||
final ServerVolume volume,
|
||||
final ServerProviderVolume volume,
|
||||
final DiskSize size,
|
||||
) async =>
|
||||
_adapter.api().resizeVolume(
|
||||
|
|
|
@ -280,7 +280,7 @@ class HetznerServerProvider extends ServerProvider {
|
|||
id: serverResult.data!.id,
|
||||
ip4: serverResult.data!.publicNet.ipv4!.ip,
|
||||
createTime: DateTime.now(),
|
||||
volume: ServerVolume(
|
||||
volume: ServerProviderVolume(
|
||||
id: volume.id,
|
||||
name: volume.name,
|
||||
sizeByte: volume.size * 1024 * 1024 * 1024,
|
||||
|
@ -580,10 +580,10 @@ class HetznerServerProvider extends ServerProvider {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerVolume>>> getVolumes({
|
||||
Future<GenericResult<List<ServerProviderVolume>>> getVolumes({
|
||||
final String? status,
|
||||
}) async {
|
||||
final List<ServerVolume> volumes = [];
|
||||
final List<ServerProviderVolume> volumes = [];
|
||||
|
||||
final result = await _adapter.api().getVolumes();
|
||||
|
||||
|
@ -603,7 +603,7 @@ class HetznerServerProvider extends ServerProvider {
|
|||
final volumeServer = rawVolume.serverId;
|
||||
final String volumeName = rawVolume.name;
|
||||
final volumeDevice = rawVolume.linuxDevice;
|
||||
final volume = ServerVolume(
|
||||
final volume = ServerProviderVolume(
|
||||
id: volumeId,
|
||||
name: volumeName,
|
||||
sizeByte: volumeSize,
|
||||
|
@ -629,8 +629,10 @@ class HetznerServerProvider extends ServerProvider {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<ServerVolume?>> createVolume(final int gb) async {
|
||||
ServerVolume? volume;
|
||||
Future<GenericResult<ServerProviderVolume?>> createVolume(
|
||||
final int gb,
|
||||
) async {
|
||||
ServerProviderVolume? volume;
|
||||
|
||||
final result = await _adapter.api().createVolume(gb);
|
||||
|
||||
|
@ -644,7 +646,7 @@ class HetznerServerProvider extends ServerProvider {
|
|||
}
|
||||
|
||||
try {
|
||||
volume = ServerVolume(
|
||||
volume = ServerProviderVolume(
|
||||
id: result.data!.id,
|
||||
name: result.data!.name,
|
||||
sizeByte: result.data!.size * 1024 * 1024 * 1024,
|
||||
|
@ -669,12 +671,14 @@ class HetznerServerProvider extends ServerProvider {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<void>> deleteVolume(final ServerVolume volume) async =>
|
||||
Future<GenericResult<void>> deleteVolume(
|
||||
final ServerProviderVolume volume,
|
||||
) async =>
|
||||
_adapter.api().deleteVolume(volume.id);
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> resizeVolume(
|
||||
final ServerVolume volume,
|
||||
final ServerProviderVolume volume,
|
||||
final DiskSize size,
|
||||
) async =>
|
||||
_adapter.api().resizeVolume(
|
||||
|
@ -690,7 +694,7 @@ class HetznerServerProvider extends ServerProvider {
|
|||
|
||||
@override
|
||||
Future<GenericResult<bool>> attachVolume(
|
||||
final ServerVolume volume,
|
||||
final ServerProviderVolume volume,
|
||||
final int serverId,
|
||||
) async =>
|
||||
_adapter.api().attachVolume(
|
||||
|
@ -706,7 +710,7 @@ class HetznerServerProvider extends ServerProvider {
|
|||
|
||||
@override
|
||||
Future<GenericResult<bool>> detachVolume(
|
||||
final ServerVolume volume,
|
||||
final ServerProviderVolume volume,
|
||||
) async =>
|
||||
_adapter.api().detachVolume(
|
||||
volume.id,
|
||||
|
|
|
@ -94,35 +94,37 @@ abstract class ServerProvider {
|
|||
/// main server type pricing
|
||||
Future<GenericResult<AdditionalPricing?>> getAdditionalPricing();
|
||||
|
||||
/// Returns [ServerVolume] of all available volumes
|
||||
/// Returns [ServerProviderVolume] of all available volumes
|
||||
/// assigned to the authorized user and attached to active machine.
|
||||
Future<GenericResult<List<ServerVolume>>> getVolumes({final String? status});
|
||||
Future<GenericResult<List<ServerProviderVolume>>> getVolumes({
|
||||
final String? status,
|
||||
});
|
||||
|
||||
/// Tries to create an empty unattached [ServerVolume].
|
||||
/// Tries to create an empty unattached [ServerProviderVolume].
|
||||
///
|
||||
/// If success, returns this volume information.
|
||||
Future<GenericResult<ServerVolume?>> createVolume(final int gb);
|
||||
Future<GenericResult<ServerProviderVolume?>> createVolume(final int gb);
|
||||
|
||||
/// Tries to delete the requested accessible [ServerVolume].
|
||||
Future<GenericResult<void>> deleteVolume(final ServerVolume volume);
|
||||
/// Tries to delete the requested accessible [ServerProviderVolume].
|
||||
Future<GenericResult<void>> deleteVolume(final ServerProviderVolume volume);
|
||||
|
||||
/// Tries to resize the requested accessible [ServerVolume]
|
||||
/// Tries to resize the requested accessible [ServerProviderVolume]
|
||||
/// to the provided size **(not by!)**, must be greater than current size.
|
||||
Future<GenericResult<bool>> resizeVolume(
|
||||
final ServerVolume volume,
|
||||
final ServerProviderVolume volume,
|
||||
final DiskSize size,
|
||||
);
|
||||
|
||||
/// Tries to attach the requested accessible [ServerVolume]
|
||||
/// Tries to attach the requested accessible [ServerProviderVolume]
|
||||
/// to an accessible machine by the provided identificator.
|
||||
Future<GenericResult<bool>> attachVolume(
|
||||
final ServerVolume volume,
|
||||
final ServerProviderVolume volume,
|
||||
final int serverId,
|
||||
);
|
||||
|
||||
/// Tries to attach the requested accessible [ServerVolume]
|
||||
/// Tries to attach the requested accessible [ServerProviderVolume]
|
||||
/// from any machine.
|
||||
Future<GenericResult<bool>> detachVolume(final ServerVolume volume);
|
||||
Future<GenericResult<bool>> detachVolume(final ServerProviderVolume volume);
|
||||
|
||||
/// Returns metedata of an accessible machine by the provided identificator
|
||||
/// to show on ServerDetailsScreen.
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/brand_theme.dart';
|
||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart';
|
||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||
|
@ -19,13 +19,39 @@ class JobsContent extends StatelessWidget {
|
|||
|
||||
final ScrollController controller;
|
||||
|
||||
IconData _getIcon(final JobStatusEnum status) {
|
||||
switch (status) {
|
||||
case JobStatusEnum.created:
|
||||
return Icons.query_builder_outlined;
|
||||
case JobStatusEnum.running:
|
||||
return Icons.pending_outlined;
|
||||
case JobStatusEnum.finished:
|
||||
return Icons.check_circle_outline;
|
||||
case JobStatusEnum.error:
|
||||
return Icons.error_outline;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getColor(final JobStatusEnum status, final BuildContext context) {
|
||||
switch (status) {
|
||||
case JobStatusEnum.created:
|
||||
return Theme.of(context).colorScheme.secondary;
|
||||
case JobStatusEnum.running:
|
||||
return Theme.of(context).colorScheme.tertiary;
|
||||
case JobStatusEnum.finished:
|
||||
return Theme.of(context).colorScheme.primary;
|
||||
case JobStatusEnum.error:
|
||||
return Theme.of(context).colorScheme.error;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final List<ServerJob> serverJobs =
|
||||
context.watch<ServerJobsCubit>().state.serverJobList;
|
||||
context.watch<ServerJobsBloc>().state.serverJobList;
|
||||
|
||||
final bool hasRemovableJobs =
|
||||
context.watch<ServerJobsCubit>().state.hasRemovableJobs;
|
||||
context.watch<ServerJobsBloc>().state.hasRemovableJobs;
|
||||
|
||||
return BlocBuilder<JobsCubit, JobsState>(
|
||||
builder: (final context, final state) {
|
||||
|
@ -68,8 +94,274 @@ class JobsContent extends StatelessWidget {
|
|||
}
|
||||
} else if (state is JobsStateLoading) {
|
||||
widgets = [
|
||||
const SizedBox(height: 80),
|
||||
BrandLoader.horizontal(),
|
||||
...state.clientJobList.map(
|
||||
(final j) => Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
_getIcon(j.status),
|
||||
color: _getColor(j.status, context),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Card(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
j.title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
if (j.message != null)
|
||||
Text(
|
||||
j.message!,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.rebuildRequired)
|
||||
Builder(
|
||||
builder: (final context) {
|
||||
final rebuildJob = serverJobs.firstWhereOrNull(
|
||||
(final job) => job.uid == state.rebuildJobUid,
|
||||
);
|
||||
return Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
_getIcon(rebuildJob?.status ?? JobStatusEnum.created),
|
||||
color: _getColor(
|
||||
rebuildJob?.status ?? JobStatusEnum.created,
|
||||
context,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Card(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
rebuildJob?.name ??
|
||||
'jobs.rebuild_system'.tr(),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
if (rebuildJob?.description != null)
|
||||
Text(
|
||||
rebuildJob!.description,
|
||||
style:
|
||||
Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LinearProgressIndicator(
|
||||
value: rebuildJob?.progress == null
|
||||
? 0.0
|
||||
: ((rebuildJob!.progress ?? 0) < 1)
|
||||
? null
|
||||
: rebuildJob.progress! / 100.0,
|
||||
color: _getColor(
|
||||
rebuildJob?.status ?? JobStatusEnum.created,
|
||||
context,
|
||||
),
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant,
|
||||
minHeight: 7.0,
|
||||
borderRadius: BorderRadius.circular(7.0),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (rebuildJob?.error != null ||
|
||||
rebuildJob?.result != null ||
|
||||
rebuildJob?.statusText != null)
|
||||
Text(
|
||||
rebuildJob?.error ??
|
||||
rebuildJob?.result ??
|
||||
rebuildJob?.statusText ??
|
||||
'',
|
||||
style:
|
||||
Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
NaiJi
commented
Review
it's so over it's so over
|
||||
];
|
||||
} else if (state is JobsStateFinished) {
|
||||
widgets = [
|
||||
...state.clientJobList.map(
|
||||
(final j) => Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
_getIcon(j.status),
|
||||
color: _getColor(j.status, context),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Card(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
j.title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
if (j.message != null)
|
||||
Text(
|
||||
j.message!,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.rebuildRequired)
|
||||
Builder(
|
||||
builder: (final context) {
|
||||
final rebuildJob = serverJobs.firstWhereOrNull(
|
||||
(final job) => job.uid == state.rebuildJobUid,
|
||||
);
|
||||
if (rebuildJob == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
_getIcon(rebuildJob.status),
|
||||
color: _getColor(
|
||||
rebuildJob.status,
|
||||
context,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Card(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
rebuildJob.name,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
rebuildJob.description,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LinearProgressIndicator(
|
||||
value: rebuildJob.progress == null
|
||||
? 0.0
|
||||
: ((rebuildJob.progress ?? 0) < 1)
|
||||
? null
|
||||
: rebuildJob.progress! / 100.0,
|
||||
NaiJi
commented
Review
I can already see how we create tickets to refactor all that I can already see how we create tickets to refactor all that
inex
commented
Review
Welp... Server API does not return the progress value yet, but might return later. When we pass Welp...
Server API does not return the progress value yet, but might return later.
When we pass `null`, it does the cute loading animation.
|
||||
color: _getColor(
|
||||
rebuildJob.status,
|
||||
context,
|
||||
),
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant,
|
||||
minHeight: 7.0,
|
||||
borderRadius: BorderRadius.circular(7.0),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (rebuildJob.error != null ||
|
||||
rebuildJob.result != null ||
|
||||
rebuildJob.statusText != null)
|
||||
Text(
|
||||
rebuildJob.error ??
|
||||
rebuildJob.result ??
|
||||
rebuildJob.statusText ??
|
||||
'',
|
||||
style:
|
||||
Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.rised(
|
||||
onPressed: () => context.read<JobsCubit>().acknowledgeFinished(),
|
||||
text: 'basis.done'.tr(),
|
||||
),
|
||||
];
|
||||
} else if (state is JobsStateWithJobs) {
|
||||
widgets = [
|
||||
|
@ -84,19 +376,31 @@ class JobsContent extends StatelessWidget {
|
|||
horizontal: 15,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Text(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
j.title,
|
||||
style:
|
||||
Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
if (j.message != null)
|
||||
Text(
|
||||
j.message!,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
|
@ -116,7 +420,7 @@ class JobsContent extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.rised(
|
||||
onPressed: () => context.read<JobsCubit>().applyAll(),
|
||||
text: 'jobs.start'.tr(),
|
||||
|
@ -152,8 +456,8 @@ class JobsContent extends StatelessWidget {
|
|||
IconButton(
|
||||
onPressed: hasRemovableJobs
|
||||
? () => context
|
||||
.read<ServerJobsCubit>()
|
||||
.removeAllFinishedJobs()
|
||||
.read<ServerJobsBloc>()
|
||||
.add(RemoveAllFinishedJobs())
|
||||
: null,
|
||||
icon: const Icon(Icons.clear_all),
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
|
@ -161,7 +465,9 @@ class JobsContent extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
...serverJobs.map(
|
||||
...serverJobs
|
||||
.whereNot((final job) => job.uid == state.rebuildJobUid)
|
||||
.map(
|
||||
(final job) => Dismissible(
|
||||
key: ValueKey(job.uid),
|
||||
direction: job.status == JobStatusEnum.finished ||
|
||||
|
@ -172,7 +478,9 @@ class JobsContent extends StatelessWidget {
|
|||
serverJob: job,
|
||||
),
|
||||
onDismissed: (final direction) {
|
||||
context.read<ServerJobsCubit>().removeServerJob(job.uid);
|
||||
context.read<ServerJobsBloc>().add(
|
||||
RemoveServerJob(job.uid),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -2,10 +2,10 @@ 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/backups/backups_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.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';
|
||||
|
@ -31,19 +31,18 @@ class BackupDetailsPage extends StatelessWidget {
|
|||
Widget build(final BuildContext context) {
|
||||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
final BackupsState backupsState = context.watch<BackupsCubit>().state;
|
||||
final BackupsState backupsState = context.watch<BackupsBloc>().state;
|
||||
final bool isBackupInitialized = backupsState.isInitialized;
|
||||
final StateType providerState = isReady && isBackupInitialized
|
||||
? StateType.stable
|
||||
: StateType.uninitialized;
|
||||
final bool preventActions = backupsState.preventActions;
|
||||
final List<Backup> backups = backupsState.backups;
|
||||
final bool refreshing = backupsState.refreshing;
|
||||
final List<Service> services =
|
||||
context.watch<ServicesCubit>().state.servicesThatCanBeBackedUp;
|
||||
context.watch<ServicesBloc>().state.servicesThatCanBeBackedUp;
|
||||
final Duration? autobackupPeriod = backupsState.autobackupPeriod;
|
||||
final List<ServerJob> backupJobs = context
|
||||
.watch<ServerJobsCubit>()
|
||||
.watch<ServerJobsBloc>()
|
||||
.state
|
||||
.backupJobList
|
||||
.where((final job) => job.status != JobStatusEnum.finished)
|
||||
|
@ -75,8 +74,10 @@ class BackupDetailsPage extends StatelessWidget {
|
|||
BrandButton.rised(
|
||||
onPressed: preventActions
|
||||
? null
|
||||
: () async {
|
||||
await context.read<BackupsCubit>().initializeBackups();
|
||||
: () {
|
||||
context
|
||||
.read<BackupsBloc>()
|
||||
.add(const InitializeBackupsRepository());
|
||||
},
|
||||
text: 'backup.initialize'.tr(),
|
||||
),
|
||||
|
@ -297,7 +298,7 @@ class BackupDetailsPage extends StatelessWidget {
|
|||
children: backups.take(15).map(
|
||||
(final Backup backup) {
|
||||
final service = context
|
||||
.read<ServicesCubit>()
|
||||
.read<ServicesBloc>()
|
||||
.state
|
||||
.getServiceById(backup.serviceId);
|
||||
return ListTile(
|
||||
|
@ -334,11 +335,12 @@ class BackupDetailsPage extends StatelessWidget {
|
|||
'backup.forget_snapshot_alert'.tr(),
|
||||
actionButtonTitle:
|
||||
'backup.forget_snapshot'.tr(),
|
||||
actionButtonOnPressed: () => {
|
||||
context.read<BackupsCubit>().forgetSnapshot(
|
||||
actionButtonOnPressed: () =>
|
||||
context.read<BackupsBloc>().add(
|
||||
ForgetSnapshot(
|
||||
backup.id,
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
title: Text(
|
||||
|
@ -391,18 +393,6 @@ class BackupDetailsPage extends StatelessWidget {
|
|||
const SizedBox(height: 8),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'backup.refresh'.tr(),
|
||||
),
|
||||
onTap: refreshing
|
||||
? null
|
||||
: () => {context.read<BackupsCubit>().updateBackups()},
|
||||
enabled: !refreshing,
|
||||
leading: const Icon(
|
||||
Icons.refresh_outlined,
|
||||
),
|
||||
),
|
||||
if (providerState != StateType.uninitialized)
|
||||
Column(
|
||||
children: [
|
||||
|
@ -425,32 +415,11 @@ class BackupDetailsPage extends StatelessWidget {
|
|||
),
|
||||
onTap: preventActions
|
||||
? null
|
||||
: () => {context.read<BackupsCubit>().forceUpdateBackups()},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'backup.reupload_key'.tr(),
|
||||
style: TextStyle(
|
||||
color: overrideColor,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'backup.reupload_key_subtitle'.tr(),
|
||||
style: TextStyle(
|
||||
color: overrideColor,
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.warning_amber_outlined,
|
||||
color: overrideColor,
|
||||
),
|
||||
onTap: preventActions
|
||||
? null
|
||||
: () => {context.read<BackupsCubit>().reuploadKey()},
|
||||
: () => context
|
||||
.read<BackupsBloc>()
|
||||
.add(const ForceSnapshotListUpdate()),
|
||||
),
|
||||
// TODO: Return reupload key button in some form
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
@ -3,8 +3,8 @@ import 'package:easy_localization/easy_localization.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||
import 'package:selfprivacy/logic/models/backup.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||
|
@ -25,10 +25,10 @@ class BackupsListPage extends StatelessWidget {
|
|||
// If the service is null, get all backups from state. If not null, call the
|
||||
// serviceBackups(serviceId) on the backups state.
|
||||
final List<Backup> backups = service == null
|
||||
? context.watch<BackupsCubit>().state.backups
|
||||
: context.watch<BackupsCubit>().state.serviceBackups(service!.id);
|
||||
? context.watch<BackupsBloc>().state.backups
|
||||
: context.watch<BackupsBloc>().state.serviceBackups(service!.id);
|
||||
final bool preventActions =
|
||||
context.watch<BackupsCubit>().state.preventActions;
|
||||
context.watch<BackupsBloc>().state.preventActions;
|
||||
return BrandHeroScreen(
|
||||
heroTitle: 'backup.snapshots_title'.tr(),
|
||||
hasFlashButton: true,
|
||||
|
@ -43,7 +43,7 @@ class BackupsListPage extends StatelessWidget {
|
|||
...backups.map(
|
||||
(final Backup backup) {
|
||||
final service = context
|
||||
.read<ServicesCubit>()
|
||||
.read<ServicesBloc>()
|
||||
.state
|
||||
.getServiceById(backup.serviceId);
|
||||
return ListTile(
|
||||
|
@ -75,11 +75,9 @@ class BackupsListPage extends StatelessWidget {
|
|||
alertTitle: 'backup.forget_snapshot'.tr(),
|
||||
description: 'backup.forget_snapshot_alert'.tr(),
|
||||
actionButtonTitle: 'backup.forget_snapshot'.tr(),
|
||||
actionButtonOnPressed: () => {
|
||||
context.read<BackupsCubit>().forgetSnapshot(
|
||||
backup.id,
|
||||
),
|
||||
},
|
||||
actionButtonOnPressed: () => context
|
||||
.read<BackupsBloc>()
|
||||
.add(ForgetSnapshot(backup.id)),
|
||||
);
|
||||
},
|
||||
title: Text(
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/utils/extensions/duration.dart';
|
||||
|
||||
class ChangeAutobackupsPeriodModal extends StatefulWidget {
|
||||
|
@ -34,13 +34,13 @@ class _ChangeAutobackupsPeriodModalState
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedPeriod = context.read<BackupsCubit>().state.autobackupPeriod;
|
||||
selectedPeriod = context.read<BackupsBloc>().state.autobackupPeriod;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final Duration? initialAutobackupPeriod =
|
||||
context.watch<BackupsCubit>().state.autobackupPeriod;
|
||||
context.watch<BackupsBloc>().state.autobackupPeriod;
|
||||
return ListView(
|
||||
controller: widget.scrollController,
|
||||
padding: const EdgeInsets.all(16),
|
||||
|
@ -91,8 +91,8 @@ class _ChangeAutobackupsPeriodModalState
|
|||
? null
|
||||
: () {
|
||||
context
|
||||
.read<BackupsCubit>()
|
||||
.setAutobackupPeriod(selectedPeriod);
|
||||
.read<BackupsBloc>()
|
||||
.add(SetAutobackupPeriod(selectedPeriod));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/backup.dart';
|
||||
|
||||
class ChangeRotationQuotasModal extends StatefulWidget {
|
||||
|
@ -27,7 +27,7 @@ enum QuotaUnits {
|
|||
}
|
||||
|
||||
class _ChangeRotationQuotasModalState extends State<ChangeRotationQuotasModal> {
|
||||
AutobackupQuotas selectedQuotas = AutobackupQuotas(
|
||||
AutobackupQuotas selectedQuotas = const AutobackupQuotas(
|
||||
last: 3,
|
||||
daily: 7,
|
||||
weekly: 4,
|
||||
|
@ -40,7 +40,7 @@ class _ChangeRotationQuotasModalState extends State<ChangeRotationQuotasModal> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
selectedQuotas =
|
||||
context.read<BackupsCubit>().state.autobackupQuotas ?? selectedQuotas;
|
||||
context.read<BackupsBloc>().state.autobackupQuotas ?? selectedQuotas;
|
||||
}
|
||||
|
||||
String generateSubtitle(final int value, final QuotaUnits unit) {
|
||||
|
@ -83,7 +83,7 @@ class _ChangeRotationQuotasModalState extends State<ChangeRotationQuotasModal> {
|
|||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final AutobackupQuotas? initialAutobackupQuotas =
|
||||
context.watch<BackupsCubit>().state.autobackupQuotas;
|
||||
context.watch<BackupsBloc>().state.autobackupQuotas;
|
||||
return ListView(
|
||||
controller: widget.scrollController,
|
||||
padding: const EdgeInsets.all(16),
|
||||
|
@ -190,8 +190,8 @@ class _ChangeRotationQuotasModalState extends State<ChangeRotationQuotasModal> {
|
|||
? null
|
||||
: () {
|
||||
context
|
||||
.read<BackupsCubit>()
|
||||
.setAutobackupQuotas(selectedQuotas);
|
||||
.read<BackupsBloc>()
|
||||
.add(SetAutobackupQuotas(selectedQuotas));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
|
|
|
@ -2,9 +2,9 @@ import 'dart:async';
|
|||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||
import 'package:selfprivacy/utils/platform_adapter.dart';
|
||||
|
||||
|
@ -34,7 +34,7 @@ class _CopyEncryptionKeyModalState extends State<CopyEncryptionKeyModal> {
|
|||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final String? encryptionKey =
|
||||
context.watch<BackupsCubit>().state.backblazeBucket?.encryptionKey;
|
||||
context.watch<BackupsBloc>().state.backblazeBucket?.encryptionKey;
|
||||
if (encryptionKey == null) {
|
||||
return ListView(
|
||||
controller: widget.scrollController,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
|
||||
|
@ -29,7 +29,7 @@ class _CreateBackupsModalState extends State<CreateBackupsModal> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
final List<String> busyServices = context
|
||||
.read<ServerJobsCubit>()
|
||||
.read<ServerJobsBloc>()
|
||||
.state
|
||||
.backupJobList
|
||||
.where(
|
||||
|
@ -48,7 +48,7 @@ class _CreateBackupsModalState extends State<CreateBackupsModal> {
|
|||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final List<String> busyServices = context
|
||||
.watch<ServerJobsCubit>()
|
||||
.watch<ServerJobsBloc>()
|
||||
.state
|
||||
.backupJobList
|
||||
.where(
|
||||
|
@ -147,8 +147,8 @@ class _CreateBackupsModalState extends State<CreateBackupsModal> {
|
|||
? null
|
||||
: () {
|
||||
context
|
||||
.read<BackupsCubit>()
|
||||
.createMultipleBackups(selectedServices);
|
||||
.read<BackupsBloc>()
|
||||
.add(CreateBackups(selectedServices));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
|
|
|
@ -2,9 +2,9 @@ import 'package:easy_localization/easy_localization.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||
import 'package:selfprivacy/logic/models/backup.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
|
@ -34,7 +34,7 @@ class _SnapshotModalState extends State<SnapshotModal> {
|
|||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final List<String> busyServices = context
|
||||
.watch<ServerJobsCubit>()
|
||||
.watch<ServerJobsBloc>()
|
||||
.state
|
||||
.backupJobList
|
||||
.where(
|
||||
|
@ -48,7 +48,7 @@ class _SnapshotModalState extends State<SnapshotModal> {
|
|||
final bool isServiceBusy = busyServices.contains(widget.snapshot.serviceId);
|
||||
|
||||
final Service? service = context
|
||||
.read<ServicesCubit>()
|
||||
.read<ServicesBloc>()
|
||||
.state
|
||||
.getServiceById(widget.snapshot.serviceId);
|
||||
|
||||
|
@ -153,9 +153,11 @@ class _SnapshotModalState extends State<SnapshotModal> {
|
|||
onPressed: isServiceBusy
|
||||
? null
|
||||
: () {
|
||||
context.read<BackupsCubit>().restoreBackup(
|
||||
context.read<BackupsBloc>().add(
|
||||
RestoreBackup(
|
||||
widget.snapshot.id,
|
||||
selectedStrategy,
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
getIt<NavigationService>()
|
||||
|
|
|
@ -2,8 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||
|
@ -22,12 +21,11 @@ class DevicesScreen extends StatefulWidget {
|
|||
class _DevicesScreenState extends State<DevicesScreen> {
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final ApiDevicesState devicesStatus =
|
||||
context.watch<ApiDevicesCubit>().state;
|
||||
final DevicesState devicesStatus = context.watch<DevicesBloc>().state;
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await context.read<ApiDevicesCubit>().refresh();
|
||||
await context.read<DevicesBloc>().refresh();
|
||||
},
|
||||
child: BrandHeroScreen(
|
||||
heroTitle: 'devices.main_screen.header'.tr(),
|
||||
|
@ -35,13 +33,13 @@ class _DevicesScreenState extends State<DevicesScreen> {
|
|||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
children: [
|
||||
if (devicesStatus.status == LoadingStatus.uninitialized) ...[
|
||||
if (devicesStatus is DevicesInitial) ...[
|
||||
const Center(
|
||||
heightFactor: 8,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
],
|
||||
if (devicesStatus.status != LoadingStatus.uninitialized) ...[
|
||||
if (devicesStatus is! DevicesInitial) ...[
|
||||
_DevicesInfo(
|
||||
devicesStatus: devicesStatus,
|
||||
),
|
||||
|
@ -70,7 +68,7 @@ class _DevicesInfo extends StatelessWidget {
|
|||
required this.devicesStatus,
|
||||
});
|
||||
|
||||
final ApiDevicesState devicesStatus;
|
||||
final DevicesState devicesStatus;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Column(
|
||||
|
@ -82,7 +80,9 @@ class _DevicesInfo extends StatelessWidget {
|
|||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
_DeviceTile(device: devicesStatus.thisDevice),
|
||||
_DeviceTile(
|
||||
device: devicesStatus.thisDevice,
|
||||
),
|
||||
const Divider(height: 1),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
|
@ -91,14 +91,18 @@ class _DevicesInfo extends StatelessWidget {
|
|||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
if (devicesStatus.status == LoadingStatus.refreshing) ...[
|
||||
if (devicesStatus is DevicesDeleting) ...[
|
||||
const Center(
|
||||
heightFactor: 4,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
],
|
||||
...devicesStatus.otherDevices
|
||||
.map((final device) => _DeviceTile(device: device)),
|
||||
if (devicesStatus is! DevicesDeleting)
|
||||
...devicesStatus.otherDevices.map(
|
||||
(final device) => _DeviceTile(
|
||||
device: device,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -110,7 +114,7 @@ class _DeviceTile extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(device.name),
|
||||
subtitle: Text(
|
||||
'devices.main_screen.access_granted_on'
|
||||
|
@ -161,7 +165,7 @@ class _DeviceTile extends StatelessWidget {
|
|||
TextButton(
|
||||
child: Text('devices.revoke_device_alert.yes'.tr()),
|
||||
onPressed: () {
|
||||
context.read<ApiDevicesCubit>().deleteDevice(device);
|
||||
context.read<DevicesBloc>().add(DeleteDevice(device));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
|
@ -17,7 +17,7 @@ class NewDeviceScreen extends StatelessWidget {
|
|||
hasFlashButton: false,
|
||||
children: [
|
||||
FutureBuilder(
|
||||
future: context.read<ApiDevicesCubit>().getNewDeviceKey(),
|
||||
future: context.read<DevicesBloc>().getNewDeviceKey(),
|
||||
builder: (
|
||||
final BuildContext context,
|
||||
final AsyncSnapshot<Object?> snapshot,
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
|
||||
@RoutePage()
|
||||
|
@ -89,15 +88,11 @@ class _DeveloperSettingsPageState extends State<DeveloperSettingsPage> {
|
|||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('ApiDevicesCubit'),
|
||||
title: const Text('ApiConnectionRepository status'),
|
||||
subtitle: Text(
|
||||
context.watch<ApiDevicesCubit>().state.status.toString(),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('RecoveryKeyCubit'),
|
||||
subtitle: Text(
|
||||
context.watch<RecoveryKeyCubit>().state.loadingStatus.toString(),
|
||||
getIt<ApiConnectionRepository>()
|
||||
.currentConnectionStatus
|
||||
.toString(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -4,8 +4,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:ionicons/ionicons.dart';
|
||||
import 'package:selfprivacy/config/brand_theme.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||
|
@ -21,9 +19,6 @@ class MorePage extends StatelessWidget {
|
|||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
|
||||
final bool? usesBinds =
|
||||
context.watch<ApiServerVolumeCubit>().state.usesBinds;
|
||||
|
||||
return Scaffold(
|
||||
appBar: Breakpoints.small.isActive(context)
|
||||
? PreferredSize(
|
||||
|
@ -39,31 +34,6 @@ class MorePage extends StatelessWidget {
|
|||
padding: paddingH15V0,
|
||||
child: Column(
|
||||
children: [
|
||||
if (isReady && usesBinds != null && !usesBinds)
|
||||
_MoreMenuItem(
|
||||
title: 'storage.start_migration_button'.tr(),
|
||||
iconData: Icons.drive_file_move_outline,
|
||||
goTo: () => ServicesMigrationRoute(
|
||||
diskStatus:
|
||||
context.read<ApiServerVolumeCubit>().state.diskStatus,
|
||||
services: context
|
||||
.read<ServicesCubit>()
|
||||
.state
|
||||
.services
|
||||
.where(
|
||||
(final service) =>
|
||||
service.id == 'bitwarden' ||
|
||||
service.id == 'gitea' ||
|
||||
service.id == 'pleroma' ||
|
||||
service.id == 'mailserver' ||
|
||||
service.id == 'nextcloud',
|
||||
)
|
||||
.toList(),
|
||||
isMigration: true,
|
||||
),
|
||||
subtitle: 'storage.data_migration_notice'.tr(),
|
||||
accent: true,
|
||||
),
|
||||
if (!isReady)
|
||||
_MoreMenuItem(
|
||||
title: 'more_page.configuration_wizard'.tr(),
|
||||
|
|
|
@ -2,11 +2,11 @@ import 'package:auto_route/auto_route.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/brand_theme.dart';
|
||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
||||
|
@ -30,11 +30,11 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
|||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
final bool isBackupInitialized =
|
||||
context.watch<BackupsCubit>().state.isInitialized;
|
||||
context.watch<BackupsBloc>().state.isInitialized;
|
||||
final DnsRecordsStatus dnsStatus =
|
||||
context.watch<DnsRecordsCubit>().state.dnsState;
|
||||
|
||||
final diskStatus = context.watch<ApiServerVolumeCubit>().state.diskStatus;
|
||||
final diskStatus = context.watch<VolumesBloc>().state.diskStatus;
|
||||
|
||||
final ServerInstallationState appConfig =
|
||||
context.watch<ServerInstallationCubit>().state;
|
||||
|
|
|
@ -3,8 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||
|
@ -21,34 +20,28 @@ class RecoveryKeyPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
context.read<RecoveryKeyCubit>().load();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final RecoveryKeyState keyStatus = context.watch<RecoveryKeyCubit>().state;
|
||||
final RecoveryKeyState keyStatus = context.watch<RecoveryKeyBloc>().state;
|
||||
|
||||
final List<Widget> widgets;
|
||||
String? subtitle =
|
||||
keyStatus.exists ? null : 'recovery_key.key_main_description'.tr();
|
||||
|
||||
switch (keyStatus.loadingStatus) {
|
||||
case LoadingStatus.refreshing:
|
||||
switch (keyStatus) {
|
||||
case RecoveryKeyRefreshing():
|
||||
subtitle = 'recovery_key.key_synchronizing'.tr();
|
||||
widgets = [
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
];
|
||||
break;
|
||||
case LoadingStatus.success:
|
||||
case RecoveryKeyLoaded():
|
||||
widgets = [
|
||||
const RecoveryKeyContent(),
|
||||
];
|
||||
break;
|
||||
case LoadingStatus.uninitialized:
|
||||
case LoadingStatus.error:
|
||||
case RecoveryKeyInitial():
|
||||
case RecoveryKeyError():
|
||||
subtitle = 'recovery_key.key_connection_error'.tr();
|
||||
widgets = [
|
||||
const Icon(Icons.sentiment_dissatisfied_outlined),
|
||||
|
@ -58,7 +51,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<RecoveryKeyCubit>().load();
|
||||
context.read<RecoveryKeyBloc>().add(const RecoveryKeyStatusRefresh());
|
||||
},
|
||||
child: BrandHeroScreen(
|
||||
heroTitle: 'recovery_key.key_main_header'.tr(),
|
||||
|
@ -83,7 +76,7 @@ class _RecoveryKeyContentState extends State<RecoveryKeyContent> {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final RecoveryKeyState keyStatus = context.watch<RecoveryKeyCubit>().state;
|
||||
final RecoveryKeyState keyStatus = context.watch<RecoveryKeyBloc>().state;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
|
@ -243,7 +236,7 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
});
|
||||
try {
|
||||
final String token =
|
||||
await context.read<RecoveryKeyCubit>().generateRecoveryKey(
|
||||
await context.read<RecoveryKeyBloc>().generateRecoveryKey(
|
||||
numberOfUses: _isAmountToggled
|
||||
? int.tryParse(_amountController.text)
|
||||
: null,
|
||||
|
@ -257,7 +250,7 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
});
|
||||
await Navigator.of(context).push(
|
||||
materialRoute(
|
||||
RecoveryKeyReceiving(recoveryKey: token), // TO DO
|
||||
RecoveryKeyReceiving(recoveryKey: token),
|
||||
),
|
||||
);
|
||||
} on GenerationError catch (e) {
|
||||
|
|
|
@ -13,7 +13,7 @@ class RecoveryKeyReceiving extends StatelessWidget {
|
|||
Widget build(final BuildContext context) => BrandHeroScreen(
|
||||
heroTitle: 'recovery_key.key_main_header'.tr(),
|
||||
heroSubtitle: 'recovery_key.key_receiving_description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasBackButton: false,
|
||||
hasFlashButton: false,
|
||||
children: [
|
||||
const Divider(),
|
||||
|
|
|
@ -2,13 +2,12 @@ import 'package:auto_route/auto_route.dart';
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
||||
import 'package:selfprivacy/logic/models/job.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
||||
|
@ -81,7 +80,7 @@ class _ServerDetailsScreenState extends State<ServerDetailsScreen>
|
|||
heroSubtitle: 'server.description'.tr(),
|
||||
children: [
|
||||
StorageCard(
|
||||
diskStatus: context.watch<ApiServerVolumeCubit>().state.diskStatus,
|
||||
diskStatus: context.watch<VolumesBloc>().state.diskStatus,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const _ServerSettings(),
|
||||
|
|
|
@ -30,24 +30,32 @@ class _ServerSettingsState extends State<_ServerSettings> {
|
|||
value: allowAutoUpgrade ?? false,
|
||||
onChanged: (final switched) {
|
||||
context.read<JobsCubit>().addJob(
|
||||
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
|
||||
);
|
||||
context
|
||||
.read<ServerDetailsCubit>()
|
||||
.repository
|
||||
.setAutoUpgradeSettings(
|
||||
AutoUpgradeSettings(
|
||||
enable: switched,
|
||||
ChangeAutoUpgradeSettingsJob(
|
||||
allowReboot: rebootAfterUpgrade ?? false,
|
||||
enable: switched,
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
allowAutoUpgrade = switched;
|
||||
});
|
||||
},
|
||||
title: Text('server.allow_autoupgrade'.tr()),
|
||||
title: Text(
|
||||
'server.allow_autoupgrade'.tr(),
|
||||
style: TextStyle(
|
||||
fontStyle: allowAutoUpgrade !=
|
||||
serverDetailsState.autoUpgradeSettings.enable
|
||||
? FontStyle.italic
|
||||
: FontStyle.normal,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'server.allow_autoupgrade_hint'.tr(),
|
||||
style: TextStyle(
|
||||
fontStyle: allowAutoUpgrade !=
|
||||
serverDetailsState.autoUpgradeSettings.enable
|
||||
? FontStyle.italic
|
||||
: FontStyle.normal,
|
||||
),
|
||||
),
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
|
@ -55,24 +63,32 @@ class _ServerSettingsState extends State<_ServerSettings> {
|
|||
value: rebootAfterUpgrade ?? false,
|
||||
onChanged: (final switched) {
|
||||
context.read<JobsCubit>().addJob(
|
||||
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
|
||||
);
|
||||
context
|
||||
.read<ServerDetailsCubit>()
|
||||
.repository
|
||||
.setAutoUpgradeSettings(
|
||||
AutoUpgradeSettings(
|
||||
enable: allowAutoUpgrade ?? false,
|
||||
ChangeAutoUpgradeSettingsJob(
|
||||
allowReboot: switched,
|
||||
enable: allowAutoUpgrade ?? false,
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
rebootAfterUpgrade = switched;
|
||||
});
|
||||
},
|
||||
title: Text('server.reboot_after_upgrade'.tr()),
|
||||
title: Text(
|
||||
'server.reboot_after_upgrade'.tr(),
|
||||
style: TextStyle(
|
||||
fontStyle: rebootAfterUpgrade !=
|
||||
serverDetailsState.autoUpgradeSettings.allowReboot
|
||||
? FontStyle.italic
|
||||
: FontStyle.normal,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'server.reboot_after_upgrade_hint'.tr(),
|
||||
style: TextStyle(
|
||||
fontStyle: rebootAfterUpgrade !=
|
||||
serverDetailsState.autoUpgradeSettings.allowReboot
|
||||
? FontStyle.italic
|
||||
: FontStyle.normal,
|
||||
),
|
||||
),
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
|
@ -82,9 +98,6 @@ class _ServerSettingsState extends State<_ServerSettings> {
|
|||
serverDetailsState.serverTimezone.toString(),
|
||||
),
|
||||
onTap: () {
|
||||
context.read<JobsCubit>().addJob(
|
||||
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
|
||||
);
|
||||
Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const SelectTimezone(),
|
||||
|
|
|
@ -140,8 +140,10 @@ class _SelectTimezoneState extends State<SelectTimezone> {
|
|||
'GMT ${duration.toTimezoneOffsetFormat()} ${area.isNotEmpty ? '($area)' : ''}',
|
||||
),
|
||||
onTap: () {
|
||||
context.read<ServerDetailsCubit>().repository.setTimezone(
|
||||
location.name,
|
||||
context.read<JobsCubit>().addJob(
|
||||
ChangeServerTimezoneJob(
|
||||
timezone: location.name,
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_linear_indicator/brand_linear_indicator.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
|
||||
class MigrationProcessPage extends StatefulWidget {
|
||||
const MigrationProcessPage({super.key});
|
||||
|
||||
@override
|
||||
State<MigrationProcessPage> createState() => _MigrationProcessPageState();
|
||||
}
|
||||
|
||||
class _MigrationProcessPageState extends State<MigrationProcessPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
ServerJob? job;
|
||||
String? subtitle = '';
|
||||
double value = 0.0;
|
||||
List<Widget> children = [];
|
||||
|
||||
final serverJobsState = context.watch<ServerJobsCubit>().state;
|
||||
if (serverJobsState.migrationJobUid != null) {
|
||||
job = context.read<ServerJobsCubit>().getServerJobByUid(
|
||||
serverJobsState.migrationJobUid!,
|
||||
);
|
||||
}
|
||||
|
||||
if (job == null) {
|
||||
subtitle = 'basis.loading'.tr();
|
||||
} else {
|
||||
value = job.progress == null ? 0.0 : job.progress! / 100;
|
||||
subtitle = job.statusText;
|
||||
children = [
|
||||
...children,
|
||||
const SizedBox(height: 16),
|
||||
if (job.finishedAt != null)
|
||||
Text(
|
||||
job.result!,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
if (job.finishedAt != null) const SizedBox(height: 16),
|
||||
if (job.finishedAt != null)
|
||||
BrandButton.filled(
|
||||
child: Text('storage.migration_done'.tr()),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
materialRoute(const RootPage()),
|
||||
(final predicate) => false,
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
return BrandHeroScreen(
|
||||
hasBackButton: false,
|
||||
heroTitle: 'storage.migration_process'.tr(),
|
||||
heroSubtitle: subtitle,
|
||||
children: [
|
||||
BrandLinearIndicator(
|
||||
value: value,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
height: 4.0,
|
||||
),
|
||||
...children,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
|
@ -18,13 +18,11 @@ class ServicesMigrationPage extends StatefulWidget {
|
|||
const ServicesMigrationPage({
|
||||
required this.services,
|
||||
required this.diskStatus,
|
||||
required this.isMigration,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final DiskStatus diskStatus;
|
||||
final List<Service> services;
|
||||
final bool isMigration;
|
||||
|
||||
@override
|
||||
State<ServicesMigrationPage> createState() => _ServicesMigrationPageState();
|
||||
|
@ -171,24 +169,20 @@ class _ServicesMigrationPageState extends State<ServicesMigrationPage> {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (widget.isMigration || (!widget.isMigration && isVolumePicked))
|
||||
if (isVolumePicked)
|
||||
BrandButton.filled(
|
||||
child: Text('storage.start_migration_button'.tr()),
|
||||
onPressed: () {
|
||||
if (widget.isMigration) {
|
||||
context.read<ServerJobsCubit>().migrateToBinds(
|
||||
serviceToDisk,
|
||||
);
|
||||
} else {
|
||||
for (final service in widget.services) {
|
||||
if (serviceToDisk[service.id] != null) {
|
||||
context.read<ServicesCubit>().moveService(
|
||||
service.id,
|
||||
context.read<ServicesBloc>().add(
|
||||
ServiceMove(
|
||||
service,
|
||||
serviceToDisk[service.id]!,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
context.router.popUntilRoot();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
|
|
What the hell 😨 , looks scary. is this how it's supposed to be now for all GraphQL requests?
I think there can be a better approach, but in general, yeah, every request must return a GenericResult or something like that.