-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Backup & Restore functionality
- Loading branch information
1 parent
2d50694
commit 6d04261
Showing
7 changed files
with
406 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:archive/archive_io.dart'; | ||
import 'package:file_picker/file_picker.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:get/get.dart'; | ||
import 'package:harmonymusic/ui/screens/Settings/settings_screen_controller.dart'; | ||
import 'package:harmonymusic/ui/widgets/loader.dart'; | ||
|
||
import '../../services/permission_service.dart'; | ||
import 'common_dialog_widget.dart'; | ||
|
||
class BackupDialog extends StatelessWidget { | ||
const BackupDialog({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final backupDialogController = Get.put(BackupDialogController()); | ||
return CommonDialog( | ||
child: Container( | ||
height: 300, | ||
padding: | ||
const EdgeInsets.only(top: 20, bottom: 30, left: 20, right: 20), | ||
child: Stack( | ||
children: [ | ||
Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ | ||
Container( | ||
padding: const EdgeInsets.only(bottom: 10.0, top: 10), | ||
child: Text( | ||
"backupSettingsAndPlaylists".tr, | ||
style: Theme.of(context).textTheme.titleMedium, | ||
), | ||
), | ||
SizedBox( | ||
height: 150, | ||
child: Center( | ||
child: Obx(() => backupDialogController.exportProgress | ||
.toInt() == | ||
backupDialogController.filesToExport.length | ||
? Text("backupMsg".tr) | ||
: backupDialogController.exportRunning.isTrue | ||
? Column( | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
children: [ | ||
Text( | ||
"${backupDialogController.exportProgress.toInt()}/${backupDialogController.filesToExport.length}", | ||
style: | ||
Theme.of(context).textTheme.titleLarge), | ||
const SizedBox( | ||
height: 10, | ||
), | ||
Text("exporting".tr) | ||
], | ||
) | ||
: backupDialogController.ready.isTrue | ||
? Text( | ||
"${backupDialogController.filesToExport.length} ${"backFilesFound".tr}") | ||
: backupDialogController.scanning.isTrue | ||
? Column( | ||
mainAxisAlignment: | ||
MainAxisAlignment.center, | ||
children: [ | ||
const LoadingIndicator(), | ||
const SizedBox( | ||
height: 10, | ||
), | ||
Text("scanning".tr) | ||
], | ||
) | ||
: const SizedBox()), | ||
), | ||
), | ||
SizedBox( | ||
width: double.maxFinite, | ||
child: Align( | ||
child: Container( | ||
decoration: BoxDecoration( | ||
color: Theme.of(context).textTheme.titleLarge!.color, | ||
borderRadius: BorderRadius.circular(10)), | ||
child: InkWell( | ||
onTap: () { | ||
if (backupDialogController.exportProgress.toInt() == | ||
backupDialogController.filesToExport.length) { | ||
Navigator.of(context).pop(); | ||
} else { | ||
backupDialogController.backup(); | ||
} | ||
}, | ||
child: Padding( | ||
padding: const EdgeInsets.symmetric( | ||
horizontal: 15.0, vertical: 10), | ||
child: Obx( | ||
() => Text( | ||
backupDialogController.exportProgress.toInt() == | ||
backupDialogController.filesToExport.length | ||
? "close".tr | ||
: "export".tr, | ||
style: | ||
TextStyle(color: Theme.of(context).canvasColor), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
]), | ||
], | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
class BackupDialogController extends GetxController { | ||
final scanning = true.obs; | ||
final ready = false.obs; | ||
final exportRunning = false.obs; | ||
final exportProgress = (-1).obs; | ||
List<String> filesToExport = []; | ||
|
||
@override | ||
void onInit() { | ||
scanFilesToBackup(); | ||
super.onInit(); | ||
} | ||
|
||
Future<void> scanFilesToBackup() async { | ||
final supportDirPath = Get.find<SettingsScreenController>().supportDirPath; | ||
final filesEntityList = | ||
Directory("$supportDirPath/db").listSync(recursive: false); | ||
final filesPath = filesEntityList.map((entity) => entity.path).toList(); | ||
filesToExport.addAll(filesPath); | ||
scanning.value = false; | ||
ready.value = true; | ||
} | ||
|
||
Future<void> backup() async { | ||
if (!await PermissionService.getExtStoragePermission()) { | ||
return; | ||
} | ||
|
||
if (!await PermissionService.getExtStoragePermission()) { | ||
return; | ||
} | ||
|
||
final String? pickedFolderPath = await FilePicker.platform | ||
.getDirectoryPath(dialogTitle: "Select backup file folder"); | ||
if (pickedFolderPath == '/' || pickedFolderPath == null) { | ||
return; | ||
} | ||
|
||
exportProgress.value = 0; | ||
exportRunning.value = true; | ||
final exportDirPath = pickedFolderPath.toString(); | ||
|
||
var encoder = ZipFileEncoder(); | ||
encoder.create( | ||
'$exportDirPath/${DateTime.now().millisecondsSinceEpoch.toString()}.hmb'); | ||
final length_ = filesToExport.length; | ||
for (int i = 0; i < length_; i++) { | ||
final filePath = filesToExport[i]; | ||
await encoder.addFile(File(filePath)); | ||
exportProgress.value = i + 1; | ||
} | ||
encoder.close(); | ||
exportRunning.value = false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:archive/archive_io.dart'; | ||
import 'package:file_picker/file_picker.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:get/get.dart'; | ||
import 'package:harmonymusic/ui/screens/Settings/settings_screen_controller.dart'; | ||
|
||
import '../../services/permission_service.dart'; | ||
import 'common_dialog_widget.dart'; | ||
|
||
import 'package:path/path.dart' as p; | ||
|
||
class RestoreDialog extends StatelessWidget { | ||
const RestoreDialog({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final restoreDialogController = Get.put(RestoreDialogController()); | ||
return CommonDialog( | ||
child: Container( | ||
height: 300, | ||
padding: | ||
const EdgeInsets.only(top: 20, bottom: 30, left: 20, right: 20), | ||
child: Stack( | ||
children: [ | ||
Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ | ||
Container( | ||
padding: const EdgeInsets.only(bottom: 10.0, top: 10), | ||
child: Text( | ||
"restoreSettingsAndPlaylists".tr, | ||
style: Theme.of(context).textTheme.titleMedium, | ||
), | ||
), | ||
SizedBox( | ||
height: 150, | ||
child: Center( | ||
child: Obx(() => restoreDialogController.restoreProgress | ||
.toInt() == | ||
restoreDialogController.filesToRestore.toInt() | ||
? Text("restoreMsg".tr) | ||
: restoreDialogController.restoreRunning.isTrue | ||
? Column( | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
children: [ | ||
Text( | ||
"${restoreDialogController.restoreProgress.toInt()}/${restoreDialogController.filesToRestore.toInt()}", | ||
style: | ||
Theme.of(context).textTheme.titleLarge), | ||
const SizedBox( | ||
height: 10, | ||
), | ||
Text("restoring".tr) | ||
], | ||
) | ||
: const SizedBox()), | ||
), | ||
), | ||
SizedBox( | ||
width: double.maxFinite, | ||
child: Align( | ||
child: Container( | ||
decoration: BoxDecoration( | ||
color: Theme.of(context).textTheme.titleLarge!.color, | ||
borderRadius: BorderRadius.circular(10)), | ||
child: InkWell( | ||
onTap: () { | ||
if (restoreDialogController.restoreProgress.toInt() == | ||
restoreDialogController.filesToRestore.toInt()) { | ||
exit(0); | ||
} else { | ||
restoreDialogController.backup(); | ||
} | ||
}, | ||
child: Padding( | ||
padding: const EdgeInsets.symmetric( | ||
horizontal: 15.0, vertical: 10), | ||
child: Obx( | ||
() => Text( | ||
restoreDialogController.restoreProgress.toInt() == | ||
restoreDialogController.filesToRestore | ||
.toInt() | ||
? "closeApp".tr | ||
: "restore".tr, | ||
style: | ||
TextStyle(color: Theme.of(context).canvasColor), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
]), | ||
], | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
class RestoreDialogController extends GetxController { | ||
final restoreRunning = false.obs; | ||
final restoreProgress = (-1).obs; | ||
final filesToRestore = (0).obs; | ||
|
||
Future<void> backup() async { | ||
if (!await PermissionService.getExtStoragePermission()) { | ||
return; | ||
} | ||
|
||
if (!await PermissionService.getExtStoragePermission()) { | ||
return; | ||
} | ||
|
||
final FilePickerResult? pickedFileResult = await FilePicker.platform | ||
.pickFiles( | ||
dialogTitle: "Select backup file", | ||
type: FileType.custom, | ||
allowedExtensions: ['hmb'], | ||
allowMultiple: false); | ||
|
||
final String? pickedFile = pickedFileResult?.files.first.path; | ||
|
||
// is this check necessary? | ||
if (pickedFile == '/' || pickedFile == null) { | ||
return; | ||
} | ||
|
||
restoreProgress.value = 0; | ||
restoreRunning.value = true; | ||
final restoreFilePath = pickedFile.toString(); | ||
final dbDirPath = | ||
p.join(Get.find<SettingsScreenController>().supportDirPath, "db"); | ||
final Directory dbDir = Directory(dbDirPath); | ||
printInfo(info: dbDir.path); | ||
await Get.find<SettingsScreenController>().closeAllDatabases(); | ||
await dbDir.delete(recursive: true); | ||
final bytes = await File(restoreFilePath).readAsBytes(); | ||
final archive = ZipDecoder().decodeBytes(bytes); | ||
filesToRestore.value = archive.length; | ||
for (final file in archive) { | ||
final filename = file.name; | ||
if (file.isFile) { | ||
final data = file.content as List<int>; | ||
final outputFile = File('$dbDirPath/$filename'); | ||
await outputFile.create(recursive: true); | ||
await outputFile.writeAsBytes(data); | ||
restoreProgress.value++; | ||
} | ||
} | ||
restoreRunning.value = false; | ||
} | ||
} |
Oops, something went wrong.