diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 2d01b7c58..e38e525d7 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -1,8 +1,14 @@ ## 更新日志 * 旧版数据导入教程: -* 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】,提示存储权限,选择允许即可导入成功。 -* 注意:由于安卓10更改了权限策略,还需要给「允许安装其他应用」的权限才能导入源。MIUI11也需要此权限。 +* 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 + +**2020/01/07** +* 修复备份问题 + +**2020/01/06** +* 适配Android 10 权限 +* 备份恢复不再需要存储权限 **2020/01/03** * 适配Android 10 权限 diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index cab698fbd..d65b43ada 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -7,7 +7,10 @@ import androidx.room.RoomDatabase import androidx.sqlite.db.SupportSQLiteDatabase import io.legado.app.data.dao.* import io.legado.app.data.entities.* +import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Restore +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch @Database( @@ -28,7 +31,7 @@ abstract class AppDatabase : RoomDatabase() { .fallbackToDestructiveMigration() .addCallback(object : Callback() { override fun onDestructiveMigration(db: SupportSQLiteDatabase) { - Restore.restore() + GlobalScope.launch { Restore.restore(Backup.backupPath) } } }) .build() diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index 3f8e65dd7..90bf3b9e2 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -9,13 +9,15 @@ import io.legado.app.help.ReadBookConfig import io.legado.app.utils.DocumentUtils import io.legado.app.utils.FileUtils import io.legado.app.utils.GSON +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.withContext import org.jetbrains.anko.defaultSharedPreferences import java.io.File object Backup { - private val backupPath = App.INSTANCE.filesDir.absolutePath + File.separator + "backup" + val backupPath = App.INSTANCE.filesDir.absolutePath + File.separator + "backup" val defaultPath by lazy { FileUtils.getSdCardPath() + File.separator + "YueDu" @@ -29,7 +31,7 @@ object Backup { legadoPath + File.separator + "Export" } - private val backupFileNames by lazy { + val backupFileNames by lazy { arrayOf( "bookshelf.json", "bookGroup.json", @@ -41,67 +43,72 @@ object Backup { ) } - fun backup(context: Context, uri: Uri?) { - App.db.bookDao().allBooks.let { - if (it.isNotEmpty()) { - val json = GSON.toJson(it) - FileHelp.getFile(backupPath + File.separator + "bookshelf.json").writeText(json) + suspend fun backup(context: Context, uri: Uri?) { + withContext(IO) { + App.db.bookDao().allBooks.let { + if (it.isNotEmpty()) { + val json = GSON.toJson(it) + FileHelp.getFile(backupPath + File.separator + "bookshelf.json").writeText(json) + } } - } - App.db.bookGroupDao().all().let { - if (it.isNotEmpty()) { - val json = GSON.toJson(it) - FileHelp.getFile(backupPath + File.separator + "bookGroup.json").writeText(json) + App.db.bookGroupDao().all().let { + if (it.isNotEmpty()) { + val json = GSON.toJson(it) + FileHelp.getFile(backupPath + File.separator + "bookGroup.json").writeText(json) + } } - } - App.db.bookSourceDao().all.let { - if (it.isNotEmpty()) { - val json = GSON.toJson(it) - FileHelp.getFile(backupPath + File.separator + "bookSource.json").writeText(json) + App.db.bookSourceDao().all.let { + if (it.isNotEmpty()) { + val json = GSON.toJson(it) + FileHelp.getFile(backupPath + File.separator + "bookSource.json") + .writeText(json) + } } - } - App.db.rssSourceDao().all.let { - if (it.isNotEmpty()) { - val json = GSON.toJson(it) - FileHelp.getFile(backupPath + File.separator + "rssSource.json").writeText(json) + App.db.rssSourceDao().all.let { + if (it.isNotEmpty()) { + val json = GSON.toJson(it) + FileHelp.getFile(backupPath + File.separator + "rssSource.json").writeText(json) + } } - } - App.db.replaceRuleDao().all.let { - if (it.isNotEmpty()) { - val json = GSON.toJson(it) - FileHelp.getFile(backupPath + File.separator + "replaceRule.json").writeText(json) + App.db.replaceRuleDao().all.let { + if (it.isNotEmpty()) { + val json = GSON.toJson(it) + FileHelp.getFile(backupPath + File.separator + "replaceRule.json") + .writeText(json) + } } - } - GSON.toJson(ReadBookConfig.configList)?.let { - FileHelp.getFile(backupPath + File.separator + ReadBookConfig.readConfigFileName) - .writeText(it) - } - Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp -> - val edit = sp.edit() - App.INSTANCE.defaultSharedPreferences.all.map { - when (val value = it.value) { - is Int -> edit.putInt(it.key, value) - is Boolean -> edit.putBoolean(it.key, value) - is Long -> edit.putLong(it.key, value) - is Float -> edit.putFloat(it.key, value) - is String -> edit.putString(it.key, value) - else -> Unit + GSON.toJson(ReadBookConfig.configList)?.let { + FileHelp.getFile(backupPath + File.separator + ReadBookConfig.readConfigFileName) + .writeText(it) + } + Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp -> + val edit = sp.edit() + App.INSTANCE.defaultSharedPreferences.all.map { + when (val value = it.value) { + is Int -> edit.putInt(it.key, value) + is Boolean -> edit.putBoolean(it.key, value) + is Long -> edit.putLong(it.key, value) + is Float -> edit.putFloat(it.key, value) + is String -> edit.putString(it.key, value) + else -> Unit + } } + edit.commit() + } + WebDavHelp.backUpWebDav(backupPath) + if (uri != null) { + copyBackup(context, uri) + } else { + copyBackup() } - edit.commit() - } - WebDavHelp.backUpWebDav(backupPath) - if (uri != null) { - copyBackup(context, uri) - } else { - copyBackup() } } private fun copyBackup(context: Context, uri: Uri) { DocumentFile.fromTreeUri(context, uri)?.let { treeDoc -> for (fileName in backupFileNames) { - treeDoc.createFile("text/plain", fileName)?.let { doc -> + val doc = treeDoc.findFile(fileName) ?: treeDoc.createFile("", fileName) + doc?.let { DocumentUtils.writeText( context, FileHelp.getFile(backupPath + File.separator + fileName).readText(), @@ -113,9 +120,13 @@ object Backup { } private fun copyBackup() { - for (fileName in backupFileNames) { - FileHelp.getFile(backupPath + File.separator + "bookshelf.json") - .copyTo(FileHelp.getFile(legadoPath + File.separator + "bookshelf.json")) + try { + for (fileName in backupFileNames) { + FileHelp.getFile(backupPath + File.separator + "bookshelf.json") + .copyTo(FileHelp.getFile(legadoPath + File.separator + "bookshelf.json"), true) + } + } catch (e: Exception) { + e.printStackTrace() } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt index 116419a63..a95626228 100644 --- a/app/src/main/java/io/legado/app/help/storage/Restore.kt +++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt @@ -1,13 +1,14 @@ package io.legado.app.help.storage import android.content.Context +import android.net.Uri import android.util.Log +import androidx.documentfile.provider.DocumentFile import com.jayway.jsonpath.Configuration import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.Option import com.jayway.jsonpath.ParseContext import io.legado.app.App -import io.legado.app.R import io.legado.app.constant.AppConst import io.legado.app.data.entities.* import io.legado.app.help.FileHelp @@ -19,9 +20,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jetbrains.anko.defaultSharedPreferences -import org.jetbrains.anko.doAsync import org.jetbrains.anko.toast -import org.jetbrains.anko.uiThread import java.io.File object Restore { @@ -33,8 +32,24 @@ object Restore { ) } - fun restore(path: String = Backup.defaultPath) { - doAsync { + suspend fun restore(context: Context, uri: Uri) { + withContext(IO) { + DocumentFile.fromTreeUri(context, uri)?.listFiles()?.forEach { doc -> + for (fileName in Backup.backupFileNames) { + if (doc.name == fileName) { + DocumentUtils.readText(context, doc.uri)?.let { + FileHelp.getFile(Backup.backupPath + File.separator + fileName) + .writeText(it) + } + } + } + } + } + restore(Backup.backupPath) + } + + suspend fun restore(path: String) { + withContext(IO) { try { val file = FileHelp.getFile(path + File.separator + "bookshelf.json") val json = file.readText() @@ -104,7 +119,6 @@ object Restore { } edit.commit() } - uiThread { App.INSTANCE.toast(R.string.restore_success) } } } diff --git a/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt index a4ac7fbcc..4a0f3e152 100644 --- a/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt +++ b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt @@ -4,13 +4,14 @@ import android.content.Context import io.legado.app.App import io.legado.app.help.FileHelp import io.legado.app.help.ReadBookConfig +import io.legado.app.help.coroutine.Coroutine import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.http.HttpAuth import io.legado.app.utils.ZipUtils import io.legado.app.utils.getPrefString -import org.jetbrains.anko.doAsync +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.withContext import org.jetbrains.anko.selector -import org.jetbrains.anko.uiThread import java.io.File import java.text.SimpleDateFormat import java.util.* @@ -54,28 +55,26 @@ object WebDavHelp { return names } - fun showRestoreDialog(context: Context) { - doAsync { - val names = getWebDavFileNames() - if (names.isNotEmpty()) { - uiThread { - context.selector(title = "选择恢复文件", items = names) { _, index -> - if (index in 0 until names.size) { - restoreWebDav(names[index]) - } - } + suspend fun showRestoreDialog(context: Context): Boolean { + val names = withContext(IO) { getWebDavFileNames() } + return if (names.isNotEmpty()) { + context.selector(title = "选择恢复文件", items = names) { _, index -> + if (index in 0 until names.size) { + restoreWebDav(names[index]) } - } else { - Restore.restore() } + true + } else { + false } } private fun restoreWebDav(name: String) { - doAsync { + Coroutine.async { getWebDavUrl()?.let { val file = WebDav(it + "legado/" + name) file.downloadTo(zipFilePath, true) + @Suppress("BlockingMethodInNonBlockingContext") ZipUtils.unzipFile(zipFilePath, unzipFilesPath) Restore.restore(unzipFilesPath) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt index 903cc3abc..cc9e9e666 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt @@ -17,6 +17,7 @@ import io.legado.app.utils.* import kotlinx.android.synthetic.main.view_book_page.view.* import org.jetbrains.anko.matchParent import org.jetbrains.anko.sdk27.listeners.onScrollChange +import java.io.File import java.util.* @@ -83,10 +84,15 @@ class ContentView : FrameLayout { } context.getPrefString(PreferKey.readBookFont)?.let { if (it.isNotEmpty()) { - content_text_view.typeface = Typeface.createFromFile(it) - } else { - content_text_view.typeface = Typeface.DEFAULT + val file = File(it) + if (file.exists()) { + content_text_view.typeface = Typeface.createFromFile(it) + return@let + } else { + context.putPrefString(PreferKey.readBookFont, "") + } } + content_text_view.typeface = Typeface.DEFAULT } } diff --git a/app/src/main/java/io/legado/app/ui/config/WebDavConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/WebDavConfigFragment.kt index 4dea5e0e7..446c93f68 100644 --- a/app/src/main/java/io/legado/app/ui/config/WebDavConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/WebDavConfigFragment.kt @@ -34,10 +34,10 @@ import kotlin.coroutines.CoroutineContext class WebDavConfigFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener, CoroutineScope { - lateinit var job: Job - private val oldDataRequestCode = 23156 - private val backupSelectRequestCode = 4567489 - private val restoreSelectRequestCode = 654872 + private lateinit var job: Job + private val oldDataRequestCode = 11 + private val backupSelectRequestCode = 22 + private val restoreSelectRequestCode = 33 override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main @@ -116,13 +116,7 @@ class WebDavConfigFragment : PreferenceFragmentCompat(), override fun onPreferenceTreeClick(preference: Preference?): Boolean { when (preference?.key) { "web_dav_backup" -> backup() - "web_dav_restore" -> PermissionsCompat.Builder(this) - .addPermissions(*Permissions.Group.STORAGE) - .rationale(R.string.tip_perm_request_storage) - .onGranted { - WebDavHelp.showRestoreDialog(requireContext()) - } - .request() + "web_dav_restore" -> restore() "import_old" -> importOldData() } return super.onPreferenceTreeClick(preference) @@ -130,16 +124,18 @@ class WebDavConfigFragment : PreferenceFragmentCompat(), private fun backup() { val backupPath = getPrefString(PreferKey.backupPath) - if (backupPath?.isEmpty() == true) { - selectBackupFolder() - } else { + if (backupPath?.isNotEmpty() == true) { val uri = Uri.parse(backupPath) val doc = DocumentFile.fromTreeUri(requireContext(), uri) if (doc?.canWrite() == true) { - Backup.backup(requireContext(), uri) + launch { + Backup.backup(requireContext(), uri) + } } else { selectBackupFolder() } + } else { + selectBackupFolder() } } @@ -152,7 +148,50 @@ class WebDavConfigFragment : PreferenceFragmentCompat(), PermissionsCompat.Builder(this) .addPermissions(*Permissions.Group.STORAGE) .rationale(R.string.tip_perm_request_storage) - .onGranted { Backup.backup(requireContext(), null) } + .onGranted { + launch { + Backup.backup(requireContext(), null) + } + } + .request() + } + } + + fun restore() { + launch { + if (!WebDavHelp.showRestoreDialog(requireContext())) { + val backupPath = getPrefString(PreferKey.backupPath) + if (backupPath?.isNotEmpty() == true) { + val uri = Uri.parse(backupPath) + val doc = DocumentFile.fromTreeUri(requireContext(), uri) + if (doc?.canWrite() == true) { + Restore.restore(requireContext(), uri) + toast(R.string.restore_success) + } else { + selectBackupFolder() + } + } else { + selectRestoreFolder() + } + } + } + } + + private fun selectRestoreFolder() { + try { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + startActivityForResult(intent, restoreSelectRequestCode) + } catch (e: java.lang.Exception) { + PermissionsCompat.Builder(this) + .addPermissions(*Permissions.Group.STORAGE) + .rationale(R.string.tip_perm_request_storage) + .onGranted { + launch { + Restore.restore(Backup.legadoPath) + toast(R.string.restore_success) + } + } .request() } } @@ -267,7 +306,22 @@ class WebDavConfigFragment : PreferenceFragmentCompat(), Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) putPrefString(PreferKey.backupPath, uri.toString()) - Backup.backup(requireContext(), uri) + launch { + Backup.backup(requireContext(), uri) + } + } + } + restoreSelectRequestCode -> if (resultCode == RESULT_OK) { + data?.data?.let { uri -> + requireContext().contentResolver.takePersistableUriPermission( + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + putPrefString(PreferKey.backupPath, uri.toString()) + launch { + Restore.restore(requireContext(), uri) + toast(R.string.restore_success) + } } } } diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index 0a6ca93c1..571b32139 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -1,7 +1,5 @@ package io.legado.app.ui.main -import android.app.Activity -import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.KeyEvent @@ -18,11 +16,7 @@ import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.constant.Bus import io.legado.app.constant.PreferKey -import io.legado.app.help.permission.Permissions -import io.legado.app.help.permission.PermissionsCompat import io.legado.app.help.storage.Backup -import io.legado.app.help.storage.Restore -import io.legado.app.help.storage.WebDavHelp import io.legado.app.lib.theme.ATH import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud @@ -33,15 +27,11 @@ import io.legado.app.ui.main.my.MyFragment import io.legado.app.ui.main.rss.RssFragment import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_main.* -import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext class MainActivity : VMBaseActivity(R.layout.activity_main), BottomNavigationView.OnNavigationItemSelectedListener, ViewPager.OnPageChangeListener by ViewPager.SimpleOnPageChangeListener() { - private val backupSelectRequestCode = 4567489 - private val restoreSelectRequestCode = 654872 override val viewModel: MainViewModel get() = getViewModel(MainViewModel::class.java) @@ -127,9 +117,7 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), override fun finish() { if (!BuildConfig.DEBUG) { launch { - withContext(IO) { - backup() - } + backup() super.finish() } } else { @@ -137,6 +125,19 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), } } + private suspend fun backup() { + val backupPath = getPrefString(PreferKey.backupPath) + if (backupPath?.isNotEmpty() == true) { + val uri = Uri.parse(backupPath) + val doc = DocumentFile.fromTreeUri(this, uri) + if (doc?.canWrite() == true) { + Backup.backup(this@MainActivity, uri) + } + } else { + Backup.backup(this@MainActivity, null) + } + } + override fun onDestroy() { super.onDestroy() ReadAloud.stop(this) @@ -156,86 +157,6 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), } } - fun backup() { - val backupPath = getPrefString(PreferKey.backupPath) - if (backupPath?.isEmpty() == true) { - selectBackupFolder() - } else { - val uri = Uri.parse(backupPath) - val doc = DocumentFile.fromTreeUri(this, uri) - if (doc?.canWrite() == true) { - Backup.backup(this, uri) - } else { - selectBackupFolder() - } - } - } - - fun restore() { - PermissionsCompat.Builder(this) - .addPermissions(*Permissions.Group.STORAGE) - .rationale(R.string.tip_perm_request_storage) - .onGranted { WebDavHelp.showRestoreDialog(this) } - .request() - } - - fun restore(uri: Uri) { - - } - - private fun selectBackupFolder() { - try { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - startActivityForResult(intent, backupSelectRequestCode) - } catch (e: java.lang.Exception) { - PermissionsCompat.Builder(this) - .addPermissions(*Permissions.Group.STORAGE) - .rationale(R.string.tip_perm_request_storage) - .onGranted { Backup.backup(this, null) } - .request() - } - } - - private fun selectRestoreFolder() { - try { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - startActivityForResult(intent, restoreSelectRequestCode) - } catch (e: java.lang.Exception) { - PermissionsCompat.Builder(this) - .addPermissions(*Permissions.Group.STORAGE) - .rationale(R.string.tip_perm_request_storage) - .onGranted { Restore.restore() } - .request() - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - backupSelectRequestCode -> if (resultCode == Activity.RESULT_OK) { - data?.data?.let { uri -> - contentResolver.takePersistableUriPermission( - uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - ) - putPrefString(PreferKey.backupPath, uri.toString()) - Backup.backup(this, uri) - } - } - restoreSelectRequestCode -> if (resultCode == Activity.RESULT_OK) { - data?.data?.let { uri -> - contentResolver.takePersistableUriPermission( - uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - ) - putPrefString(PreferKey.backupPath, uri.toString()) - } - } - } - } - private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { diff --git a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt index 5b2a24114..6d79f55d4 100644 --- a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt @@ -1,10 +1,14 @@ package io.legado.app.ui.main.my +import android.app.Activity +import android.content.Intent import android.content.SharedPreferences +import android.net.Uri import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View +import androidx.documentfile.provider.DocumentFile import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference @@ -12,7 +16,13 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseFragment import io.legado.app.constant.Bus +import io.legado.app.constant.PreferKey import io.legado.app.help.BookHelp +import io.legado.app.help.permission.Permissions +import io.legado.app.help.permission.PermissionsCompat +import io.legado.app.help.storage.Backup +import io.legado.app.help.storage.Restore +import io.legado.app.help.storage.WebDavHelp import io.legado.app.lib.theme.ATH import io.legado.app.service.WebService import io.legado.app.ui.about.AboutActivity @@ -20,13 +30,15 @@ import io.legado.app.ui.about.DonateActivity import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.config.ConfigActivity import io.legado.app.ui.config.ConfigViewModel -import io.legado.app.ui.main.MainActivity import io.legado.app.ui.replacerule.ReplaceRuleActivity import io.legado.app.utils.* import kotlinx.android.synthetic.main.view_title_bar.* +import kotlinx.coroutines.launch import org.jetbrains.anko.startActivity class MyFragment : BaseFragment(R.layout.fragment_my_config) { + private val backupSelectRequestCode = 22 + private val restoreSelectRequestCode = 33 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { setSupportToolbar(toolbar) @@ -43,13 +55,113 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config) { override fun onCompatOptionsItemSelected(item: MenuItem) { when (item.itemId) { R.id.menu_help -> startActivity() - R.id.menu_backup -> { - val activity = activity as? MainActivity - activity?.backup() + R.id.menu_backup -> backup() + R.id.menu_restore -> restore() + } + } + + + private fun backup() { + val backupPath = getPrefString(PreferKey.backupPath) + if (backupPath?.isNotEmpty() == true) { + val uri = Uri.parse(backupPath) + val doc = DocumentFile.fromTreeUri(requireContext(), uri) + if (doc?.canWrite() == true) { + launch { + Backup.backup(requireContext(), uri) + } + } else { + selectBackupFolder() + } + } else { + selectBackupFolder() + } + } + + private fun selectBackupFolder() { + try { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + startActivityForResult(intent, backupSelectRequestCode) + } catch (e: java.lang.Exception) { + PermissionsCompat.Builder(this) + .addPermissions(*Permissions.Group.STORAGE) + .rationale(R.string.tip_perm_request_storage) + .onGranted { + launch { + Backup.backup(requireContext(), null) + } + } + .request() + } + } + + fun restore() { + launch { + if (!WebDavHelp.showRestoreDialog(requireContext())) { + val backupPath = getPrefString(PreferKey.backupPath) + if (backupPath?.isNotEmpty() == true) { + val uri = Uri.parse(backupPath) + val doc = DocumentFile.fromTreeUri(requireContext(), uri) + if (doc?.canWrite() == true) { + Restore.restore(requireContext(), uri) + toast(R.string.restore_success) + } else { + selectBackupFolder() + } + } else { + selectRestoreFolder() + } } - R.id.menu_restore -> { - val activity = activity as? MainActivity - activity?.restore() + } + } + + private fun selectRestoreFolder() { + try { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + startActivityForResult(intent, restoreSelectRequestCode) + } catch (e: java.lang.Exception) { + PermissionsCompat.Builder(this) + .addPermissions(*Permissions.Group.STORAGE) + .rationale(R.string.tip_perm_request_storage) + .onGranted { + launch { + Restore.restore(Backup.legadoPath) + toast(R.string.restore_success) + } + } + .request() + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + backupSelectRequestCode -> if (resultCode == Activity.RESULT_OK) { + data?.data?.let { uri -> + requireContext().contentResolver.takePersistableUriPermission( + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + putPrefString(PreferKey.backupPath, uri.toString()) + launch { + Backup.backup(requireContext(), uri) + } + } + } + restoreSelectRequestCode -> if (resultCode == Activity.RESULT_OK) { + data?.data?.let { uri -> + requireContext().contentResolver.takePersistableUriPermission( + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + putPrefString(PreferKey.backupPath, uri.toString()) + launch { + Restore.restore(requireContext(), uri) + toast(R.string.restore_success) + } + } } } }