2024-01-29 13:54:09 +00:00
|
|
|
import 'dart:async';
|
|
|
|
|
|
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
|
|
import 'package:equatable/equatable.dart';
|
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
|
|
import 'package:selfprivacy/config/get_it_config.dart';
|
|
|
|
import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
|
|
|
|
import 'package:selfprivacy/logic/models/backup.dart';
|
|
|
|
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
|
|
|
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
|
|
|
|
import 'package:selfprivacy/logic/models/initialize_repository_input.dart';
|
|
|
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
|
|
|
import 'package:selfprivacy/logic/models/service.dart';
|
|
|
|
|
|
|
|
part 'backups_event.dart';
|
|
|
|
part 'backups_state.dart';
|
|
|
|
|
|
|
|
class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
|
|
|
|
BackupsBloc() : super(BackupsInitial()) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
on<BackupsServerLoaded>(
|
|
|
|
_loadState,
|
|
|
|
);
|
|
|
|
on<BackupsServerReset>(
|
|
|
|
_resetState,
|
|
|
|
);
|
|
|
|
on<BackupsStateChanged>(
|
|
|
|
_updateState,
|
|
|
|
);
|
|
|
|
on<InitializeBackupsRepository>(
|
|
|
|
_initializeRepository,
|
|
|
|
);
|
|
|
|
on<ForceSnapshotListUpdate>(
|
|
|
|
_forceSnapshotListUpdate,
|
|
|
|
);
|
|
|
|
on<CreateBackups>(
|
|
|
|
_createBackups,
|
|
|
|
);
|
|
|
|
on<RestoreBackup>(
|
|
|
|
_restoreBackup,
|
|
|
|
);
|
|
|
|
on<SetAutobackupPeriod>(
|
|
|
|
_setAutobackupPeriod,
|
|
|
|
);
|
|
|
|
on<SetAutobackupQuotas>(
|
|
|
|
_setAutobackupQuotas,
|
|
|
|
);
|
|
|
|
on<ForgetSnapshot>(
|
|
|
|
_forgetSnapshot,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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(
|
2024-01-29 15:57:22 +00:00
|
|
|
result.message ?? "Couldn't initialize repository on your server.",
|
|
|
|
);
|
2024-01-29 13:54:09 +00:00
|
|
|
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) {
|
|
|
|
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());
|
|
|
|
} else {
|
|
|
|
final backups = getIt<ApiConnectionRepository>().apiData.backups.data;
|
|
|
|
if (backups != null) {
|
|
|
|
getIt<ApiConnectionRepository>().apiData.backups.data = backups
|
|
|
|
.where((final Backup backup) => backup.id != event.backupId)
|
|
|
|
.toList();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
emit(currentState);
|
|
|
|
getIt<ApiConnectionRepository>().emitData();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@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;
|
|
|
|
}
|