mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-08 17:11:14 +00:00
Merge branch 'master' into move-title-in-cards
This commit is contained in:
commit
b8b8ac43ea
7
.github/workflows/windows.yml
vendored
7
.github/workflows/windows.yml
vendored
|
@ -1,6 +1,9 @@
|
|||
name: Windows Builder
|
||||
|
||||
on: tag
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*.*.*'
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
|
@ -14,7 +17,7 @@ jobs:
|
|||
# Install Flutter
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.3.10'
|
||||
flutter-version: '3.16.1'
|
||||
channel: 'stable'
|
||||
|
||||
# Build Windows artifact
|
||||
|
|
|
@ -10,7 +10,7 @@ AppDir:
|
|||
id: org.selfprivacy.app
|
||||
name: SelfPrivacy
|
||||
icon: org.selfprivacy.app
|
||||
version: 0.10.0
|
||||
version: 0.10.1
|
||||
exec: selfprivacy
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
### Пра нас
|
||||
|
||||
Усё больш арганізацый жадаюць валодаць нашымі дадзенымі
|
||||
Праект дазваляе толькі Вам у поўнай меры распараджацца ўласнымі **дадзенымі** на сваім сэрвэры.
|
||||
|
||||
### Наша місія
|
||||
|
||||
Лічбавая незалежнасць і прыватнасць, даступныя кожнаму
|
||||
|
||||
### Мэта
|
||||
|
||||
Распрацаваць праграму, якая дазволіць кожнаму разгарнуць свае прыватныя паслугі для сябе і сваіх суседзяў.
|
|
@ -1,12 +0,0 @@
|
|||
### O nás
|
||||
|
||||
More and more corporations want to control our data.
|
||||
We want to have full control of our **data** on our own.
|
||||
|
||||
### Naše poslání
|
||||
|
||||
Digitální nezávislost a soukromí dostupné všem
|
||||
|
||||
### Cíl
|
||||
|
||||
Rozvíjet program, který umožní každému nasadit své soukromé služby pro sebe a své sousedy.
|
|
@ -1,12 +0,0 @@
|
|||
### Über uns
|
||||
|
||||
Immer mehr Unternehmen wollen unsere Daten kontrollieren.
|
||||
Wir wollen selbst die volle Kontrolle über unsere **data** haben.
|
||||
|
||||
### Unsere Mission
|
||||
|
||||
Digitale Unabhängigkeit und Privatsphäre für alle verfügbar
|
||||
|
||||
### Ziel
|
||||
|
||||
Das Programm entwickeln, das es jedem ermöglicht, seine privaten Dienste für sich und seine Nachbarn einzusetzen.
|
|
@ -1,12 +0,0 @@
|
|||
### About us
|
||||
|
||||
More and more corporations want to control our data.
|
||||
We want to have full control of our **data** on our own.
|
||||
|
||||
### Our mission
|
||||
|
||||
Digital independence and privacy, available to everyone
|
||||
|
||||
### Target
|
||||
|
||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
@ -1,12 +0,0 @@
|
|||
### About us
|
||||
|
||||
More and more corporations want to control our data.
|
||||
We want to have full control of our **data** on our own.
|
||||
|
||||
### Our mission
|
||||
|
||||
Digital independence and privacy, available to everyone
|
||||
|
||||
### Target
|
||||
|
||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
@ -1,12 +0,0 @@
|
|||
### About us
|
||||
|
||||
More and more corporations want to control our data.
|
||||
We want to have full control of our **data** on our own.
|
||||
|
||||
### Our mission
|
||||
|
||||
Digital independence and privacy, available to everyone
|
||||
|
||||
### Target
|
||||
|
||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
@ -1,12 +0,0 @@
|
|||
### About us
|
||||
|
||||
More and more corporations want to control our data.
|
||||
We want to have full control of our **data** on our own.
|
||||
|
||||
### Our mission
|
||||
|
||||
Digital independence and privacy, available to everyone
|
||||
|
||||
### Target
|
||||
|
||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
@ -1,12 +0,0 @@
|
|||
### About us
|
||||
|
||||
More and more corporations want to control our data.
|
||||
We want to have full control of our **data** on our own.
|
||||
|
||||
### Our mission
|
||||
|
||||
Digital independence and privacy, available to everyone
|
||||
|
||||
### Target
|
||||
|
||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
@ -1,12 +0,0 @@
|
|||
### About us
|
||||
|
||||
More and more corporations want to control our data.
|
||||
We want to have full control of our **data** on our own.
|
||||
|
||||
### Our mission
|
||||
|
||||
Digital independence and privacy, available to everyone
|
||||
|
||||
### Target
|
||||
|
||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
@ -1,12 +0,0 @@
|
|||
### About us
|
||||
|
||||
More and more corporations want to control our data.
|
||||
We want to have full control of our **data** on our own.
|
||||
|
||||
### Our mission
|
||||
|
||||
Digital independence and privacy, available to everyone
|
||||
|
||||
### Target
|
||||
|
||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
@ -1,12 +0,0 @@
|
|||
### About us
|
||||
|
||||
More and more corporations want to control our data.
|
||||
We want to have full control of our **data** on our own.
|
||||
|
||||
### Misja projektu
|
||||
|
||||
Niezależność i prywatność cyfrowa dostępna dla wszystkich
|
||||
|
||||
### Cel
|
||||
|
||||
Opracuj program, dzięki któremu każdy będzie mógł stworzyć prywatne usługi dla siebie i swoich bliskich.
|
|
@ -1,12 +0,0 @@
|
|||
### О проекте
|
||||
|
||||
Всё больше организаций хотят владеть нашими данными
|
||||
Проект позволяет только Вам в полной мере распоряжаться собственными **данными** на своём сервере.
|
||||
|
||||
### Миссия проекта
|
||||
|
||||
Цифровая независимость и приватность, доступная каждому
|
||||
|
||||
### Цель
|
||||
|
||||
Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких.
|
|
@ -1,12 +0,0 @@
|
|||
### O nás
|
||||
|
||||
More and more corporations want to control our data.
|
||||
We want to have full control of our **data** on our own.
|
||||
|
||||
### Naše poslanie
|
||||
|
||||
Digitálna nezávislosť a súkromie dostupné pre každého
|
||||
|
||||
### Cieľ
|
||||
|
||||
Vytvorte program, ktorý umožní každému vytvoriť súkromné služby pre seba a svojich blízkych.
|
|
@ -1,12 +0,0 @@
|
|||
### About us
|
||||
|
||||
More and more corporations want to control our data.
|
||||
We want to have full control of our **data** on our own.
|
||||
|
||||
### Our mission
|
||||
|
||||
Digital independence and privacy, available to everyone
|
||||
|
||||
### Target
|
||||
|
||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
@ -1,12 +0,0 @@
|
|||
### Про нас
|
||||
|
||||
Все більше корпорацій хочуть контролювати свої дані.
|
||||
Ми хочемо мати повний контроль над нашими.
|
||||
|
||||
### Наша місія
|
||||
|
||||
Цифрова незалежність і конфіденційність доступні кожному
|
||||
|
||||
### Ціль
|
||||
|
||||
Розробити програму, яка дозволить кожному розгорнути свої приватні послуги для себе та їх сусідів.
|
|
@ -36,29 +36,41 @@
|
|||
"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",
|
||||
"about_project": "About us",
|
||||
"about_application": "About",
|
||||
"onboarding": "Onboarding",
|
||||
"create_ssh_key": "Superuser SSH keys",
|
||||
"console": "Console",
|
||||
"application_settings": "Application settings"
|
||||
"create_ssh_key": "Superuser SSH keys"
|
||||
},
|
||||
"console_page": {
|
||||
"title": "Console",
|
||||
"waiting": "Waiting for initialization…",
|
||||
"copy": "Copy"
|
||||
},
|
||||
"about_us_page": {
|
||||
"title": "About us"
|
||||
},
|
||||
"about_application_page": {
|
||||
"title": "About",
|
||||
"application_version_text": "Application version {}",
|
||||
"api_version_text": "Server API version {}",
|
||||
"title": "About & support",
|
||||
"versions": "Versions",
|
||||
"application_version_text": "Application version",
|
||||
"api_version_text": "Server API version",
|
||||
"open_source_licenses": "Open source licenses",
|
||||
"links": "Links",
|
||||
"website": "Our website",
|
||||
"documentation": "Documentation",
|
||||
"matrix_channel": "Matrix channel",
|
||||
"telegram_channel": "Telegram channel",
|
||||
"get_support": "Get support",
|
||||
"matrix_support_chat": "Matrix support chat",
|
||||
"telegram_support_chat": "Telegram support chat",
|
||||
"email_support": "Email support",
|
||||
"contribute": "Contribute",
|
||||
"source_code": "Source code",
|
||||
"bug_report": "Report a bug",
|
||||
"bug_report_subtitle": "Due to spam, manual account confirmation is required. Contact us in the support chat to activate your account.",
|
||||
"help_translate": "Help us translate",
|
||||
"matrix_contributors_chat": "Matrix contributors chat",
|
||||
"telegram_contributors_chat": "Telegram contributors chat",
|
||||
"privacy_policy": "Privacy policy"
|
||||
},
|
||||
"application_settings": {
|
||||
|
@ -305,6 +317,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 +406,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 +607,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 +621,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",
|
||||
|
@ -634,4 +657,4 @@
|
|||
"reset_onboarding_description": "Reset onboarding switch to show onboarding screen again",
|
||||
"cubit_statuses": "Cubit loading statuses"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
fastlane/metadata/android/en-US/changelogs/0.10.1.txt
Normal file
15
fastlane/metadata/android/en-US/changelogs/0.10.1.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
### Features
|
||||
|
||||
- Enabled the following languages:
|
||||
- Azerbaijani
|
||||
- Belarusian
|
||||
- Hebrew
|
||||
- Latvian
|
||||
- Macedonian
|
||||
- Slovak
|
||||
- Slovenian
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Hetzner**: Fixed an issue where could not resize a volume on Hetzner ([#456](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/456), resolves [#455](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/455))
|
||||
- **DNS**: Make sure that we notice domain ownership lost ([#441](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/441), resolves [#390](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/390))
|
|
@ -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());
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -17,10 +17,6 @@ class StrayDeerPainter extends CustomPainter {
|
|||
final Color deerSkin =
|
||||
const Color(0xffe0ac9c).harmonizeWith(colorScheme.primary);
|
||||
|
||||
print('deerSkin: $deerSkin');
|
||||
print('colorScheme.primary: ${colorScheme.primary}');
|
||||
print('colorPalette.tertiary.get(10): ${colorPalette.tertiary.get(50)}');
|
||||
|
||||
final Path path0 = Path();
|
||||
path0.moveTo(size.width * 0.6099773, size.height * 0.6719577);
|
||||
path0.lineTo(size.width * 0.6088435, size.height * 0.6719577);
|
||||
|
|
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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();
|
||||
|
@ -59,17 +60,18 @@ mixin VolumeApi on GraphQLApiMap {
|
|||
|
||||
Future<GenericResult<String?>> migrateToBinds(
|
||||
final Map<String, String> serviceToDisk,
|
||||
final String fallbackDrive,
|
||||
) async {
|
||||
GenericResult<String?>? mutation;
|
||||
|
||||
try {
|
||||
final GraphQLClient client = await getClient();
|
||||
final input = Input$MigrateToBindsInput(
|
||||
bitwardenBlockDevice: serviceToDisk['bitwarden']!,
|
||||
emailBlockDevice: serviceToDisk['mailserver']!,
|
||||
giteaBlockDevice: serviceToDisk['gitea']!,
|
||||
nextcloudBlockDevice: serviceToDisk['nextcloud']!,
|
||||
pleromaBlockDevice: serviceToDisk['pleroma']!,
|
||||
bitwardenBlockDevice: serviceToDisk['bitwarden'] ?? fallbackDrive,
|
||||
emailBlockDevice: serviceToDisk['email'] ?? fallbackDrive,
|
||||
giteaBlockDevice: serviceToDisk['gitea'] ?? fallbackDrive,
|
||||
nextcloudBlockDevice: serviceToDisk['nextcloud'] ?? fallbackDrive,
|
||||
pleromaBlockDevice: serviceToDisk['pleroma'] ?? fallbackDrive,
|
||||
);
|
||||
final variables = Variables$Mutation$MigrateToBinds(input: input);
|
||||
final migrateMutation =
|
||||
|
|
|
@ -547,7 +547,7 @@ class HetznerApi extends RestApiMap {
|
|||
resizeVolumeResponse = await client.post(
|
||||
'/volumes/${volume.id}/actions/resize',
|
||||
data: {
|
||||
'size': size.gibibyte,
|
||||
'size': size.gibibyte.floor(),
|
||||
},
|
||||
);
|
||||
success =
|
||||
|
|
408
lib/logic/bloc/backups/backups_bloc.dart
Normal file
408
lib/logic/bloc/backups/backups_bloc.dart
Normal file
|
@ -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;
|
||||
}
|
||||
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;
|
||||
}
|
89
lib/logic/bloc/backups/backups_event.dart
Normal file
89
lib/logic/bloc/backups/backups_event.dart
Normal file
|
@ -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];
|
||||
}
|
170
lib/logic/bloc/backups/backups_state.dart
Normal file
170
lib/logic/bloc/backups/backups_state.dart
Normal file
|
@ -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 => [];
|
||||
}
|
39
lib/logic/bloc/connection_status/connection_status_bloc.dart
Normal file
39
lib/logic/bloc/connection_status/connection_status_bloc.dart
Normal file
|
@ -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];
|
||||
}
|
110
lib/logic/bloc/devices/devices_bloc.dart
Normal file
110
lib/logic/bloc/devices/devices_bloc.dart
Normal file
|
@ -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) {
|
||||
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)
|
||||
.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();
|
||||
}
|
||||
}
|
23
lib/logic/bloc/devices/devices_event.dart
Normal file
23
lib/logic/bloc/devices/devices_event.dart
Normal file
|
@ -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];
|
||||
}
|
53
lib/logic/bloc/devices/devices_state.dart
Normal file
53
lib/logic/bloc/devices/devices_state.dart
Normal file
|
@ -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];
|
||||
}
|
88
lib/logic/bloc/recovery_key/recovery_key_bloc.dart
Normal file
88
lib/logic/bloc/recovery_key/recovery_key_bloc.dart
Normal file
|
@ -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;
|
||||
}
|
21
lib/logic/bloc/recovery_key/recovery_key_event.dart
Normal file
21
lib/logic/bloc/recovery_key/recovery_key_event.dart
Normal file
|
@ -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 => [];
|
||||
}
|
56
lib/logic/bloc/recovery_key/recovery_key_state.dart
Normal file
56
lib/logic/bloc/recovery_key/recovery_key_state.dart
Normal file
|
@ -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];
|
||||
}
|
101
lib/logic/bloc/server_jobs/server_jobs_bloc.dart
Normal file
101
lib/logic/bloc/server_jobs/server_jobs_bloc.dart
Normal file
|
@ -0,0 +1,101 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.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();
|
||||
}
|
||||
|
||||
Future<void> migrateToBinds(final Map<String, String> serviceToDisk) async {
|
||||
final fallbackDrive = getIt<ApiConnectionRepository>()
|
||||
.apiData
|
||||
.volumes
|
||||
.data
|
||||
?.where((final drive) => drive.root)
|
||||
.first
|
||||
.name ??
|
||||
'sda1';
|
||||
final result = await getIt<ApiConnectionRepository>()
|
||||
.api
|
||||
.migrateToBinds(serviceToDisk, fallbackDrive);
|
||||
if (result.data == null) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(result.message!, behavior: SnackBarBehavior.floating);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(final Change<ServerJobsState> change) {
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_apiDataSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
28
lib/logic/bloc/server_jobs/server_jobs_event.dart
Normal file
28
lib/logic/bloc/server_jobs/server_jobs_event.dart
Normal file
|
@ -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 {}
|
55
lib/logic/bloc/server_jobs/server_jobs_state.dart
Normal file
55
lib/logic/bloc/server_jobs/server_jobs_state.dart
Normal file
|
@ -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]));
|
||||
}
|
149
lib/logic/bloc/services/services_bloc.dart
Normal file
149
lib/logic/bloc/services/services_bloc.dart
Normal file
|
@ -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>();
|
||||
|
||||
_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();
|
||||
}
|
||||
}
|
40
lib/logic/bloc/services/services_event.dart
Normal file
40
lib/logic/bloc/services/services_event.dart
Normal file
|
@ -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];
|
||||
}
|
115
lib/logic/bloc/services/services_state.dart
Normal file
115
lib/logic/bloc/services/services_state.dart
Normal file
|
@ -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];
|
||||
}
|
105
lib/logic/bloc/users/users_bloc.dart
Normal file
105
lib/logic/bloc/users/users_bloc.dart
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
30
lib/logic/bloc/users/users_event.dart
Normal file
30
lib/logic/bloc/users/users_event.dart
Normal file
|
@ -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];
|
||||
}
|
246
lib/logic/bloc/volumes/volumes_bloc.dart
Normal file
246
lib/logic/bloc/volumes/volumes_bloc.dart
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
43
lib/logic/bloc/volumes/volumes_event.dart
Normal file
43
lib/logic/bloc/volumes/volumes_event.dart
Normal file
|
@ -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];
|
||||
}
|
122
lib/logic/bloc/volumes/volumes_state.dart
Normal file
122
lib/logic/bloc/volumes/volumes_state.dart
Normal file
|
@ -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());
|
||||
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!);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final ServerApi api = ServerApi();
|
||||
final UsersCubit usersCubit;
|
||||
final ServicesCubit servicesCubit;
|
||||
StreamSubscription? _apiDataSubscription;
|
||||
|
||||
void addJob(final ClientJob job) {
|
||||
final jobs = currentJobList;
|
||||
if (job.canAddTo(jobs)) {
|
||||
_updateJobsState([
|
||||
...jobs,
|
||||
...[job],
|
||||
]);
|
||||
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,153 @@ 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 (rebootResult.success && rebootResult.data != null) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.reboot_success'.tr());
|
||||
} else {
|
||||
getIt<NavigationService>().showSnackBar('jobs.reboot_failed'.tr());
|
||||
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) {
|
||||
emit(
|
||||
JobsStateFinished(
|
||||
[
|
||||
RebootServerJob(
|
||||
status: JobStatusEnum.finished,
|
||||
message: rebootResult.message,
|
||||
),
|
||||
],
|
||||
null,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
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 if (result.success) {
|
||||
emit(
|
||||
JobsStateFinished(
|
||||
[UpgradeServerJob(status: JobStatusEnum.finished)],
|
||||
null,
|
||||
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();
|
||||
|
||||
emit(JobsStateEmpty());
|
||||
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,39 +27,45 @@ class DnsRecordsCubit
|
|||
),
|
||||
);
|
||||
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
||||
final String? ipAddress =
|
||||
serverInstallationCubit.state.serverDetails?.ip4;
|
||||
final ServerDomain? domain = getIt<ApiConnectionRepository>().serverDomain;
|
||||
final String? ipAddress =
|
||||
getIt<ApiConnectionRepository>().serverDetails?.ip4;
|
||||
|
||||
if (domain == null || ipAddress == null) {
|
||||
emit(const DnsRecordsState());
|
||||
return;
|
||||
}
|
||||
if (domain == null || ipAddress == null) {
|
||||
emit(const DnsRecordsState());
|
||||
return;
|
||||
}
|
||||
|
||||
final List<DnsRecord> allDnsRecords = await api.getDnsRecords();
|
||||
allDnsRecords.removeWhere((final record) => record.type == 'AAAA');
|
||||
final foundRecords = await validateDnsRecords(
|
||||
domain,
|
||||
extractDkimRecord(allDnsRecords)?.content ?? '',
|
||||
allDnsRecords,
|
||||
ipAddress,
|
||||
);
|
||||
|
||||
if (!foundRecords.success || foundRecords.data.isEmpty) {
|
||||
emit(const DnsRecordsState(dnsState: DnsRecordsStatus.error));
|
||||
return;
|
||||
}
|
||||
final List<DnsRecord> allDnsRecords = await api.getDnsRecords();
|
||||
final foundRecords = await validateDnsRecords(
|
||||
domain,
|
||||
extractDkimRecord(allDnsRecords)?.content ?? '',
|
||||
allDnsRecords,
|
||||
);
|
||||
|
||||
if (!foundRecords.success && foundRecords.message == 'link-local') {
|
||||
emit(
|
||||
DnsRecordsState(
|
||||
dnsState: DnsRecordsStatus.error,
|
||||
dnsRecords: foundRecords.data,
|
||||
dnsState: foundRecords.data.any((final r) => r.isSatisfied == false)
|
||||
? DnsRecordsStatus.error
|
||||
: DnsRecordsStatus.good,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!foundRecords.success || foundRecords.data.isEmpty) {
|
||||
emit(const DnsRecordsState());
|
||||
return;
|
||||
}
|
||||
|
||||
emit(
|
||||
DnsRecordsState(
|
||||
dnsRecords: foundRecords.data,
|
||||
dnsState: foundRecords.data.any((final r) => r.isSatisfied == false)
|
||||
? DnsRecordsStatus.error
|
||||
: DnsRecordsStatus.good,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Tries to check whether all known DNS records on the domain by ip4
|
||||
|
@ -74,19 +77,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) {
|
||||
|
@ -158,6 +149,17 @@ class DnsRecordsCubit
|
|||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
// If providerDnsRecords contains a link-local ipv6 record, return an error
|
||||
if (providerDnsRecords.any(
|
||||
(final r) =>
|
||||
r.type == 'AAAA' && (r.content?.trim().startsWith('fe80::') ?? false),
|
||||
)) {
|
||||
return GenericResult(
|
||||
data: foundRecords,
|
||||
success: false,
|
||||
message: 'link-local',
|
||||
);
|
||||
}
|
||||
return GenericResult(
|
||||
data: foundRecords,
|
||||
success: true,
|
||||
|
@ -184,14 +186,36 @@ class DnsRecordsCubit
|
|||
emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing));
|
||||
final List<DnsRecord> records = await api.getDnsRecords();
|
||||
|
||||
// If there are explicit link-local ipv6 records, remove them from the list
|
||||
records.removeWhere(
|
||||
(final r) =>
|
||||
r.type == 'AAAA' && (r.content?.trim().startsWith('fe80::') ?? false),
|
||||
);
|
||||
|
||||
// If there are no AAAA records, make empty copies of A records
|
||||
if (!records.any((final r) => r.type == 'AAAA')) {
|
||||
final recordsToAdd = records
|
||||
.where((final r) => r.type == 'A')
|
||||
.map(
|
||||
(final r) => DnsRecord(
|
||||
name: r.name,
|
||||
type: 'AAAA',
|
||||
content: null,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
records.addAll(recordsToAdd);
|
||||
}
|
||||
|
||||
|
||||
/// TODO: Error handling?
|
||||
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
||||
final ServerDomain? domain = getIt<ApiConnectionRepository>().serverDomain;
|
||||
await ProvidersController.currentDnsProvider!.removeDomainRecords(
|
||||
records: records,
|
||||
domain: domain!,
|
||||
);
|
||||
await ProvidersController.currentDnsProvider!.createDomainRecords(
|
||||
records: records,
|
||||
records: records.where((final r) => r.content != null).toList(),
|
||||
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;
|
||||
}
|
||||
|
|
435
lib/logic/get_it/api_connection_repository.dart
Normal file
435
lib/logic/get_it/api_connection_repository.dart
Normal file
|
@ -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
|
||||
@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 =>
|
||||
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,8 +1,7 @@
|
|||
import 'package:graphql/client.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
final DateFormat formatter = DateFormat('hh:mm');
|
||||
|
||||
/// TODO(misterfourtytwo): add equality override
|
||||
class Message {
|
||||
Message({this.text, this.severity = MessageSeverity.normal})
|
||||
: time = DateTime.now();
|
||||
|
@ -13,7 +12,9 @@ class Message {
|
|||
final String? text;
|
||||
final DateTime time;
|
||||
final MessageSeverity severity;
|
||||
String get timeString => formatter.format(time);
|
||||
|
||||
static final DateFormat _formatter = DateFormat('hh:mm');
|
||||
String get timeString => _formatter.format(time);
|
||||
}
|
||||
|
||||
enum MessageSeverity {
|
||||
|
|
|
@ -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(
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue