From 442315a7434fda3229c62e197301cf3c8b0ef57f Mon Sep 17 00:00:00 2001 From: niuhuan Date: Mon, 18 Oct 2021 17:31:05 +0800 Subject: [PATCH] migrate --- .../kotlin/niuhuan/pikapi/MainActivity.kt | 75 +++++++++--- go/pikapi/controller/pikapi.go | 5 +- lib/basic/Method.dart | 16 +++ lib/screens/DownloadListScreen.dart | 1 - lib/screens/MigrateScreen.dart | 108 ++++++++++++++++++ lib/screens/SettingsScreen.dart | 15 ++- 6 files changed, 199 insertions(+), 21 deletions(-) create mode 100644 lib/screens/MigrateScreen.dart diff --git a/android/app/src/main/kotlin/niuhuan/pikapi/MainActivity.kt b/android/app/src/main/kotlin/niuhuan/pikapi/MainActivity.kt index 983a927..ab4a00a 100644 --- a/android/app/src/main/kotlin/niuhuan/pikapi/MainActivity.kt +++ b/android/app/src/main/kotlin/niuhuan/pikapi/MainActivity.kt @@ -8,6 +8,7 @@ import android.os.Environment import android.os.Handler import android.os.Looper import android.provider.MediaStore +import android.util.Log import android.view.Display import android.view.KeyEvent import androidx.annotation.NonNull @@ -24,6 +25,10 @@ import mobile.Mobile import java.io.File import java.io.FileInputStream import java.io.FileOutputStream +import java.lang.IllegalStateException +import java.nio.file.CopyOption +import java.nio.file.Files +import java.nio.file.StandardCopyOption import java.util.concurrent.Executors class MainActivity : FlutterActivity() { @@ -54,6 +59,7 @@ class MainActivity : FlutterActivity() { } } } catch (e: Exception) { + Log.e("Method", "Exception", e) uiThreadHandler.post { error("", e.message, "") } @@ -90,11 +96,11 @@ class MainActivity : FlutterActivity() { // exportComicDownloadAndroidQ(call.argument("comicId")!!) // } // 现在的文件储存路径, 默认路径返回空字符串 "" - "androidDataLocal" -> androidDataLocal() + "dataLocal" -> androidDataLocal() + // 迁移到那个地方, 如果是空字符串则迁移会默认位置 + "migrate" -> androidMigrate(call.argument("path")!!) // 获取可以迁移数据地址 "androidGetExtendDirs" -> androidGetExtendDirs() - // 迁移到那个地方, 如果是空字符串则迁移会默认位置 - "androidMigrate" -> androidMigrate(call.argument("path")!!) else -> { notImplementedToken } @@ -159,8 +165,12 @@ class MainActivity : FlutterActivity() { private fun androidGetExtendDirs(): String { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - return context!!.getExternalFilesDirs("")?.joinToString(",") { it.toString() } - ?: "" + val result = context!!.getExternalFilesDirs("")?.toMutableList()?.also { + it.add(context!!.filesDir.absoluteFile) + }?.joinToString("|") + if (result != null) { + return result + } } throw Exception("System version too low") } @@ -170,13 +180,21 @@ class MainActivity : FlutterActivity() { if (current == path) { return } - Runtime.getRuntime().exec( - arrayOf( - "mv", - "$current/*", - "$path/" - ) - ).exitValue() + // 删除位置配置文件 + if (File(current, "data.local").exists()) { + File(current, "data.local").delete() + } + // 目标位置文件夹不存在就创建,存在则清理 + val target = File(path) + if (!target.exists()) { + target.mkdirs() + } + target.listFiles().forEach { delete(it) } + // 移动所有文件夹 + + File(current).listFiles().forEach { + move(it, File(target, it.name)) + } val localFile = File(context!!.filesDir.absolutePath, "data.local") if (path == context!!.filesDir.absolutePath) { localFile.delete() @@ -185,6 +203,35 @@ class MainActivity : FlutterActivity() { } } + private fun delete(f: File) { + f.delete() + } + + private fun move(f: File, t: File) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (f.isDirectory) { + Files.createDirectories(t.toPath()) + f.listFiles().forEach { move(it, File(t, it.name)) } + Files.delete(f.toPath()) + } else { + Files.move(f.toPath(), t.toPath()) + } + } else { + if (f.isDirectory) { + t.mkdirs() + f.listFiles().forEach { move(it, File(t, it.name)) } + f.delete() + } else { + FileOutputStream(t).use { o -> + FileInputStream(f).use { i -> + o.write(i.readBytes()) + } + } + f.delete() + } + } + } + // save_image private fun saveImage(path: String) { @@ -261,7 +308,7 @@ class MainActivity : FlutterActivity() { } } - // volume_buttons +// volume_buttons private var volumeEvents: EventChannel.EventSink? = null @@ -294,7 +341,7 @@ class MainActivity : FlutterActivity() { return super.onKeyDown(keyCode, event) } - // 安卓11以上使用了 MANAGE_EXTERNAL_STORAGE 权限来管理整个外置存储 (危险权限) +// 安卓11以上使用了 MANAGE_EXTERNAL_STORAGE 权限来管理整个外置存储 (危险权限) // private val resourceQueue: LinkedBlockingQueue = LinkedBlockingQueue() // private var tmpComicId: String? = null // private val exportComicDownloadAndroidQRequestCode = 2 diff --git a/go/pikapi/controller/pikapi.go b/go/pikapi/controller/pikapi.go index 77b7f61..823efc2 100644 --- a/go/pikapi/controller/pikapi.go +++ b/go/pikapi/controller/pikapi.go @@ -555,9 +555,10 @@ func FlatInvoke(method string, params string) (string, error) { case "setDownloadRunning": b, e := strconv.ParseBool(params) if e != nil { - setDownloadRunning(b) + return "", e } - return "", e + setDownloadRunning(b) + return "", nil case "createDownload": return "", createDownload(params) case "addDownload": diff --git a/lib/basic/Method.dart b/lib/basic/Method.dart index 279bf15..93ed314 100644 --- a/lib/basic/Method.dart +++ b/lib/basic/Method.dart @@ -537,4 +537,20 @@ class Method { Future androidGetVersion() async { return await _channel.invokeMethod("androidGetVersion", {}); } + + Future dataLocal() async { + return await _channel.invokeMethod("dataLocal", {}); + } + + Future> androidGetExtendDirs() async { + String? tmp = await _channel.invokeMethod("androidGetExtendDirs", {}); + if (tmp != null && tmp.isNotEmpty) { + return tmp.split("|"); + } + return []; + } + + Future migrate(String path) async { + return _channel.invokeMethod("migrate", {"path": path}); + } } diff --git a/lib/screens/DownloadListScreen.dart b/lib/screens/DownloadListScreen.dart index 4dc726d..b4f262e 100644 --- a/lib/screens/DownloadListScreen.dart +++ b/lib/screens/DownloadListScreen.dart @@ -117,7 +117,6 @@ class _DownloadListScreenState extends State { onPressed: () async { Navigator.pop(context); var to = !_downloadRunning; - // properties.saveDownloading(to); await method.setDownloadRunning(to); setState(() { _downloadRunning = to; diff --git a/lib/screens/MigrateScreen.dart b/lib/screens/MigrateScreen.dart new file mode 100644 index 0000000..7a08a71 --- /dev/null +++ b/lib/screens/MigrateScreen.dart @@ -0,0 +1,108 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:pikapi/basic/Common.dart'; +import 'package:pikapi/basic/Method.dart'; +import 'package:pikapi/screens/components/ContentBuilder.dart'; +import 'package:pikapi/screens/components/ContentLoading.dart'; + +class MigrateScreen extends StatefulWidget { + @override + State createState() => _MigrateScreenState(); +} + +class _MigrateScreenState extends State { + late Future _future = _load(); + late String _current; + late List paths; + String _message = ""; + + int _migrate = 0; // 0 没有开始迁移,1 正在迁移,2 迁移成功,3 迁移失败 + + Future _load() async { + await method.setDownloadRunning(false); + _current = await method.dataLocal(); + if (Platform.isAndroid) { + paths = await method.androidGetExtendDirs(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('数据迁移'), + ), + body: ContentBuilder( + future: _future, + onRefresh: () async {}, + successBuilder: + (BuildContext context, AsyncSnapshot snapshot) { + switch (_migrate) { + case 0: + return ListView( + children: [ + Container( + padding: EdgeInsets.all(10), + child: Text( + "1. 为了手机数据存储空间不足, 且具有内存卡的安卓手机设计, 可将数据迁移到内存卡上。\n\n" + "2. 您在迁移之前, 请确保您的下载处于暂停状态, 或下载均已完成, 以保证您的数据完整性。\n\n" + "3. 如果迁移中断, 迁移失败, 或其他原因导致程序无法启动, 图片失效等问题, 您可在程序管理中清除本应用程序的数据, 以回复正常使用。\n\n" + "4. 如果您将数据迁移后将内存卡取出, 将会使用默认本地存储, 再次插入同一张内存卡会继续使用该储存卡, 不支持更换内存卡, 途中您若再次迁移会发生数据覆盖, 这必然会丢失一部分数据.\n\n" + "5. 您不能更改, 删除, 移动这些数据, 否则程序可能不能正常执行\n\n" + "6. 迁移成功之前一定不要退出应用程序, 也不要按返回键\n\n" + "7. 如果您已经了解此功能, 悉知文件迁移的风险, 可以在下面的按钮中选择一项执行\n\n", + ), + ), + Container( + padding: EdgeInsets.all(10), + child: Text("当前文件储存路径 : $_current"), + ), + ...paths.map((e) => Container( + padding: EdgeInsets.all(10), + child: MaterialButton( + color: Theme.of(context).accentColor, + textColor: Theme.of(context) + .accentTextTheme + .subtitle1 + ?.color, + padding: EdgeInsets.all(10), + onPressed: () async { + if (!await confirmDialog(context, "文件迁移", + "您将要迁移到$e, 迁移过程中一定《 不 要 关 闭 程 序 》")) { + return; + } + setState(() { + _migrate = 1; + }); + try { + await method.migrate(e); + setState(() { + _migrate = 2; + }); + } catch (ex, tr) { + _message = "$ex\n$tr\n"; + setState(() { + _migrate = 3; + }); + } + }, + child: Text("迁移到 $e"), + ), + )), + ], + ); + case 1: + return ContentLoading(label: "迁移中"); + case 2: + return Center(child: Text("迁移成功 您需要关闭应用程序重新启动")); + case 3: + return Center(child: Text("迁移失败\n$_message")); + default: + throw ""; + } + }, + ), + ); + } +} diff --git a/lib/screens/SettingsScreen.dart b/lib/screens/SettingsScreen.dart index caac76c..3d80ba9 100644 --- a/lib/screens/SettingsScreen.dart +++ b/lib/screens/SettingsScreen.dart @@ -21,6 +21,7 @@ import 'package:pikapi/basic/config/VolumeController.dart'; import 'package:pikapi/screens/components/NetworkSetting.dart'; import 'CleanScreen.dart'; +import 'MigrateScreen.dart'; class SettingsScreen extends StatefulWidget { @override @@ -153,13 +154,19 @@ class _SettingsScreenState extends State { title: Text("文件迁移"), subtitle: Text("更换您的数据文件夹"), onTap: () async { - var f = confirmDialog( + var f = await confirmDialog( context, "文件迁移", - "为了手机数据存储空间不足, 且具有内存卡的手机设计, 可将数据迁移到内存卡上。" - " 您在迁移之前, 请确保您的下载处于暂停状态, 或下载均已完成, 已保证您的数据完整性。" - " 如果迁移中断或其他原因导致程序无法启动, 图片失效等问题, 您可在程序管理中清除本应用程序的数据, 以回复正常使用。", + "此功能菜单保存后, 需要重启程序, 您确认吗" ); + if (f) { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute(builder: (BuildContext context) { + return MigrateScreen(); + }), + (route) => false, + ); + } }, ); }