Merge pull request #7 from gedoor/master

up
pull/68/head
口口吕 5 years ago committed by GitHub
commit 10493a4836
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      app/src/main/assets/updateLog.md
  2. 5
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  3. 25
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  4. 26
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  5. 21
      app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt
  6. 8
      app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt
  7. 84
      app/src/main/java/io/legado/app/ui/config/WebDavConfigFragment.kt
  8. 105
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  9. 126
      app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt

@ -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 权限

@ -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()

@ -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,7 +43,8 @@ object Backup {
)
}
fun backup(context: Context, uri: Uri?) {
suspend fun backup(context: Context, uri: Uri?) {
withContext(IO) {
App.db.bookDao().allBooks.let {
if (it.isNotEmpty()) {
val json = GSON.toJson(it)
@ -57,7 +60,8 @@ object Backup {
App.db.bookSourceDao().all.let {
if (it.isNotEmpty()) {
val json = GSON.toJson(it)
FileHelp.getFile(backupPath + File.separator + "bookSource.json").writeText(json)
FileHelp.getFile(backupPath + File.separator + "bookSource.json")
.writeText(json)
}
}
App.db.rssSourceDao().all.let {
@ -69,7 +73,8 @@ object Backup {
App.db.replaceRuleDao().all.let {
if (it.isNotEmpty()) {
val json = GSON.toJson(it)
FileHelp.getFile(backupPath + File.separator + "replaceRule.json").writeText(json)
FileHelp.getFile(backupPath + File.separator + "replaceRule.json")
.writeText(json)
}
}
GSON.toJson(ReadBookConfig.configList)?.let {
@ -97,11 +102,13 @@ object Backup {
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() {
try {
for (fileName in backupFileNames) {
FileHelp.getFile(backupPath + File.separator + "bookshelf.json")
.copyTo(FileHelp.getFile(legadoPath + File.separator + "bookshelf.json"))
.copyTo(FileHelp.getFile(legadoPath + File.separator + "bookshelf.json"), true)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}

@ -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) }
}
}

@ -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 {
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])
}
}
}
true
} else {
Restore.restore()
}
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)
}

@ -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()) {
val file = File(it)
if (file.exists()) {
content_text_view.typeface = Typeface.createFromFile(it)
return@let
} else {
content_text_view.typeface = Typeface.DEFAULT
context.putPrefString(PreferKey.readBookFont, "")
}
}
content_text_view.typeface = Typeface.DEFAULT
}
}

@ -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) {
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,9 +306,24 @@ class WebDavConfigFragment : PreferenceFragmentCompat(),
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 == 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)
}
}
}
}
}
}

@ -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<MainViewModel>(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<MainViewModel>(R.layout.activity_main),
override fun finish() {
if (!BuildConfig.DEBUG) {
launch {
withContext(IO) {
backup()
}
super.finish()
}
} else {
@ -137,6 +125,19 @@ class MainActivity : VMBaseActivity<MainViewModel>(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<MainViewModel>(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) {

@ -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<AboutActivity>()
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()
}
}
}
}
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)
}
}
R.id.menu_restore -> {
val activity = activity as? MainActivity
activity?.restore()
}
}
}

Loading…
Cancel
Save