pull/94/head
kunfei 5 years ago
parent 7c2a5b28e7
commit 55c74c7565
  1. 4
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  2. 149
      app/src/main/java/io/legado/app/help/storage/ImportOldData.kt
  3. 81
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  4. 132
      app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt
  5. 32
      app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt
  6. 6
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt

@ -19,10 +19,6 @@ object Backup {
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"
}
val legadoPath by lazy { val legadoPath by lazy {
FileUtils.getSdCardPath() + File.separator + "YueDu3.0" FileUtils.getSdCardPath() + File.separator + "YueDu3.0"
} }

@ -0,0 +1,149 @@
package io.legado.app.help.storage
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import io.legado.app.App
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.utils.DocumentUtils
import io.legado.app.utils.FileUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.anko.toast
import java.io.File
object ImportOldData {
val yueDuPath by lazy {
FileUtils.getSdCardPath() + File.separator + "YueDu"
}
fun import(context: Context) {
GlobalScope.launch(Dispatchers.IO) {
try {// 导入书架
val shelfFile =
FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookShelf.json")
val json = shelfFile.readText()
val importCount = importOldBookshelf(json)
withContext(Dispatchers.Main) {
context.toast("成功导入书籍${importCount}")
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
context.toast("导入书籍失败\n${e.localizedMessage}")
}
}
try {// Book source
val sourceFile =
FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookSource.json")
val json = sourceFile.readText()
val importCount = importOldSource(json)
withContext(Dispatchers.Main) {
context.toast("成功导入书源${importCount}")
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
context.toast("导入源失败\n${e.localizedMessage}")
}
}
try {// Replace rules
val ruleFile =
FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookReplaceRule.json")
val json = ruleFile.readText()
val importCount = importOldReplaceRule(json)
withContext(Dispatchers.Main) {
context.toast("成功导入替换规则${importCount}")
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
context.toast("导入替换规则失败\n${e.localizedMessage}")
}
}
}
}
fun importUri(uri: Uri) {
Coroutine.async {
DocumentFile.fromTreeUri(App.INSTANCE, uri)?.listFiles()?.forEach {
when (it.name) {
"myBookShelf.json" ->
try {
DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json ->
val importCount = importOldBookshelf(json)
withContext(Dispatchers.Main) {
App.INSTANCE.toast("成功导入书籍${importCount}")
}
}
} catch (e: java.lang.Exception) {
withContext(Dispatchers.Main) {
App.INSTANCE.toast("导入书籍失败\n${e.localizedMessage}")
}
}
"myBookSource.json" ->
try {
DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json ->
val importCount = importOldSource(json)
withContext(Dispatchers.Main) {
App.INSTANCE.toast("成功导入书源${importCount}")
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
App.INSTANCE.toast("导入源失败\n${e.localizedMessage}")
}
}
"myBookReplaceRule.json" ->
try {
DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json ->
val importCount = importOldReplaceRule(json)
withContext(Dispatchers.Main) {
App.INSTANCE.toast("成功导入替换规则${importCount}")
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
App.INSTANCE.toast("导入替换规则失败\n${e.localizedMessage}")
}
}
}
}
}
}
fun importOldBookshelf(json: String): Int {
val books = OldBook.toNewBook(json)
App.db.bookDao().insert(*books.toTypedArray())
return books.size
}
fun importOldSource(json: String): Int {
val bookSources = mutableListOf<BookSource>()
val items: List<Map<String, Any>> = Restore.jsonPath.parse(json).read("$")
for (item in items) {
val jsonItem = Restore.jsonPath.parse(item)
OldRule.jsonToBookSource(jsonItem.jsonString())?.let {
bookSources.add(it)
}
}
App.db.bookSourceDao().insert(*bookSources.toTypedArray())
return bookSources.size
}
fun importOldReplaceRule(json: String): Int {
val replaceRules = mutableListOf<ReplaceRule>()
val items: List<Map<String, Any>> = Restore.jsonPath.parse(json).read("$")
for (item in items) {
val jsonItem = Restore.jsonPath.parse(item)
OldRule.jsonToReplaceRule(jsonItem.jsonString())?.let {
replaceRules.add(it)
}
}
App.db.replaceRuleDao().insert(*replaceRules.toTypedArray())
return replaceRules.size
}
}

@ -13,12 +13,8 @@ import io.legado.app.data.entities.*
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jetbrains.anko.defaultSharedPreferences import org.jetbrains.anko.defaultSharedPreferences
import org.jetbrains.anko.toast
import java.io.File import java.io.File
object Restore { object Restore {
@ -109,81 +105,4 @@ object Restore {
return null return null
} }
fun importYueDuData(context: Context) {
GlobalScope.launch(IO) {
try {// 导入书架
val shelfFile =
FileUtils.createFileIfNotExist(Backup.defaultPath + File.separator + "myBookShelf.json")
val json = shelfFile.readText()
val importCount = importOldBookshelf(json)
withContext(Main) {
context.toast("成功导入书籍${importCount}")
}
} catch (e: Exception) {
withContext(Main) {
context.toast("导入书籍失败\n${e.localizedMessage}")
}
}
try {// Book source
val sourceFile =
FileUtils.createFileIfNotExist(Backup.defaultPath + File.separator + "myBookSource.json")
val json = sourceFile.readText()
val importCount = importOldSource(json)
withContext(Main) {
context.toast("成功导入书源${importCount}")
}
} catch (e: Exception) {
withContext(Main) {
context.toast("导入源失败\n${e.localizedMessage}")
}
}
try {// Replace rules
val ruleFile =
FileUtils.createFileIfNotExist(Backup.defaultPath + File.separator + "myBookReplaceRule.json")
val json = ruleFile.readText()
val importCount = importOldReplaceRule(json)
withContext(Main) {
context.toast("成功导入替换规则${importCount}")
}
} catch (e: Exception) {
withContext(Main) {
context.toast("导入替换规则失败\n${e.localizedMessage}")
}
}
}
}
fun importOldBookshelf(json: String): Int {
val books = OldBook.toNewBook(json)
App.db.bookDao().insert(*books.toTypedArray())
return books.size
}
fun importOldSource(json: String): Int {
val bookSources = mutableListOf<BookSource>()
val items: List<Map<String, Any>> = jsonPath.parse(json).read("$")
for (item in items) {
val jsonItem = jsonPath.parse(item)
OldRule.jsonToBookSource(jsonItem.jsonString())?.let {
bookSources.add(it)
}
}
App.db.bookSourceDao().insert(*bookSources.toTypedArray())
return bookSources.size
}
fun importOldReplaceRule(json: String): Int {
val replaceRules = mutableListOf<ReplaceRule>()
val items: List<Map<String, Any>> = jsonPath.parse(json).read("$")
for (item in items) {
val jsonItem = jsonPath.parse(item)
OldRule.jsonToReplaceRule(jsonItem.jsonString())?.let {
replaceRules.add(it)
}
}
App.db.replaceRuleDao().insert(*replaceRules.toTypedArray())
return replaceRules.size
}
} }

@ -1,51 +1,26 @@
package io.legado.app.ui.config package io.legado.app.ui.config
import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.InputType import android.text.InputType
import android.view.View import android.view.View
import androidx.documentfile.provider.DocumentFile
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.IntentHelp
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.help.storage.Restore
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.utils.DocumentUtils
import io.legado.app.utils.LogUtils
import io.legado.app.utils.applyTint
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import org.jetbrains.anko.toast
import kotlin.coroutines.CoroutineContext
class BackupConfigFragment : PreferenceFragmentCompat(), class BackupConfigFragment : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener, SharedPreferences.OnSharedPreferenceChangeListener,
FileChooserDialog.CallBack, FileChooserDialog.CallBack {
CoroutineScope {
private lateinit var job: Job
private val oldDataRequestCode = 11
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
job = Job()
addPreferencesFromResource(R.xml.pref_config_backup) addPreferencesFromResource(R.xml.pref_config_backup)
findPreference<EditTextPreference>(PreferKey.webDavUrl)?.let { findPreference<EditTextPreference>(PreferKey.webDavUrl)?.let {
it.setOnBindEditTextListener { editText -> it.setOnBindEditTextListener { editText ->
@ -80,7 +55,6 @@ class BackupConfigFragment : PreferenceFragmentCompat(),
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
job.cancel()
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
@ -132,107 +106,11 @@ class BackupConfigFragment : PreferenceFragmentCompat(),
PreferKey.backupPath -> BackupRestoreUi.selectBackupFolder(this) PreferKey.backupPath -> BackupRestoreUi.selectBackupFolder(this)
"web_dav_backup" -> BackupRestoreUi.backup(this) "web_dav_backup" -> BackupRestoreUi.backup(this)
"web_dav_restore" -> BackupRestoreUi.restore(this) "web_dav_restore" -> BackupRestoreUi.restore(this)
"import_old" -> importOldData() "import_old" -> BackupRestoreUi.importOldData(this)
} }
return super.onPreferenceTreeClick(preference) return super.onPreferenceTreeClick(preference)
} }
private fun importOldData() {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, oldDataRequestCode)
} catch (e: Exception) {
needInstallApps {
alert(title = "导入") {
message = "是否导入旧版本数据"
yesButton {
PermissionsCompat.Builder(this@BackupConfigFragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
Restore.importYueDuData(requireContext())
}
.request()
}
noButton {
}
}.show().applyTint()
}
}
}
private fun importOldData(uri: Uri) {
launch(IO) {
DocumentFile.fromTreeUri(requireContext(), uri)?.listFiles()?.forEach {
when (it.name) {
"myBookShelf.json" ->
try {
DocumentUtils.readText(requireContext(), it.uri)?.let { json ->
val importCount = Restore.importOldBookshelf(json)
withContext(Dispatchers.Main) {
requireContext().toast("成功导入书籍${importCount}")
}
}
} catch (e: java.lang.Exception) {
withContext(Dispatchers.Main) {
requireContext().toast("导入书籍失败\n${e.localizedMessage}")
}
}
"myBookSource.json" ->
try {
DocumentUtils.readText(requireContext(), it.uri)?.let { json ->
val importCount = Restore.importOldSource(json)
withContext(Dispatchers.Main) {
requireContext().toast("成功导入书源${importCount}")
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
requireContext().toast("导入源失败\n${e.localizedMessage}")
}
}
"myBookReplaceRule.json" ->
try {
DocumentUtils.readText(requireContext(), it.uri)?.let { json ->
val importCount = Restore.importOldReplaceRule(json)
withContext(Dispatchers.Main) {
requireContext().toast("成功导入替换规则${importCount}")
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
requireContext().toast("导入替换规则失败\n${e.localizedMessage}")
}
}
}
}
}
}
private fun needInstallApps(callback: () -> Unit) {
fun canRequestPackageInstalls(): Boolean {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
return requireContext().packageManager.canRequestPackageInstalls()
}
return true
}
if (!canRequestPackageInstalls()) {
alert(title = "开启权限提示") {
message = "需要打开「安装外部来源应用」权限才能导入旧版数据,请去设置中开启"
yesButton {
IntentHelp.toInstallUnknown(requireContext())
}
noButton {
}
}.show().applyTint()
} else {
LogUtils.d("xxx", "import old")
callback()
}
}
override fun onFilePicked(requestCode: Int, currentPath: String) { override fun onFilePicked(requestCode: Int, currentPath: String) {
BackupRestoreUi.onFilePicked(requestCode, currentPath) BackupRestoreUi.onFilePicked(requestCode, currentPath)
} }
@ -240,11 +118,5 @@ class BackupConfigFragment : PreferenceFragmentCompat(),
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
BackupRestoreUi.onActivityResult(requestCode, resultCode, data) BackupRestoreUi.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
oldDataRequestCode ->
if (resultCode == RESULT_OK) data?.data?.let { uri ->
importOldData(uri)
}
}
} }
} }

@ -13,10 +13,14 @@ import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.ImportOldData
import io.legado.app.help.storage.Restore import io.legado.app.help.storage.Restore
import io.legado.app.help.storage.WebDavHelp import io.legado.app.help.storage.WebDavHelp
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.utils.applyTint
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import io.legado.app.utils.isContentPath import io.legado.app.utils.isContentPath
import io.legado.app.utils.toast import io.legado.app.utils.toast
@ -27,6 +31,7 @@ object BackupRestoreUi {
private const val backupSelectRequestCode = 22 private const val backupSelectRequestCode = 22
private const val restoreSelectRequestCode = 33 private const val restoreSelectRequestCode = 33
private const val oldDataRequestCode = 11
fun backup(fragment: Fragment) { fun backup(fragment: Fragment) {
val backupPath = AppConfig.backupPath val backupPath = AppConfig.backupPath
@ -163,6 +168,29 @@ object BackupRestoreUi {
}.show() }.show()
} }
fun importOldData(fragment: Fragment) {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
fragment.startActivityForResult(intent, oldDataRequestCode)
} catch (e: Exception) {
fragment.alert(title = "导入") {
message = "是否导入旧版本数据"
yesButton {
PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
ImportOldData.import(fragment.requireContext())
}
.request()
}
noButton {
}
}.show().applyTint()
}
}
fun onFilePicked(requestCode: Int, currentPath: String) { fun onFilePicked(requestCode: Int, currentPath: String) {
when (requestCode) { when (requestCode) {
backupSelectRequestCode -> { backupSelectRequestCode -> {
@ -214,6 +242,10 @@ object BackupRestoreUi {
} }
} }
} }
oldDataRequestCode ->
if (resultCode == RESULT_OK) data?.data?.let { uri ->
ImportOldData.importUri(uri)
}
} }
} }

@ -8,7 +8,7 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.HttpHelper
import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.Restore import io.legado.app.help.storage.ImportOldData
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isAbsUrl
@ -22,10 +22,10 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
execute { execute {
if (text.isAbsUrl()) { if (text.isAbsUrl()) {
HttpHelper.simpleGet(text)?.let { HttpHelper.simpleGet(text)?.let {
Restore.importOldReplaceRule(it) ImportOldData.importOldReplaceRule(it)
} }
} else { } else {
Restore.importOldReplaceRule(text) ImportOldData.importOldReplaceRule(text)
} }
}.onError { }.onError {
showMsg(it.localizedMessage ?: "ERROR") showMsg(it.localizedMessage ?: "ERROR")

Loading…
Cancel
Save