本地备份也采用压缩包,和webDav备份保持一致,从webDav下载的备份文件不需要解压可以直接恢复
This commit is contained in:
@@ -27,8 +27,6 @@ import kotlinx.coroutines.ensureActive
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.File
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.coroutineContext
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
@@ -37,7 +35,6 @@ import kotlin.coroutines.coroutineContext
|
|||||||
*/
|
*/
|
||||||
object AppWebDav {
|
object AppWebDav {
|
||||||
private const val defaultWebDavUrl = "https://dav.jianguoyun.com/dav/"
|
private const val defaultWebDavUrl = "https://dav.jianguoyun.com/dav/"
|
||||||
private val zipFilePath = "${appCtx.externalFiles.absolutePath}${File.separator}backup.zip"
|
|
||||||
private val bookProgressUrl get() = "${rootWebDavUrl}bookProgress/"
|
private val bookProgressUrl get() = "${rootWebDavUrl}bookProgress/"
|
||||||
private val exportsWebDavUrl get() = "${rootWebDavUrl}books/"
|
private val exportsWebDavUrl get() = "${rootWebDavUrl}books/"
|
||||||
|
|
||||||
@@ -69,18 +66,6 @@ object AppWebDav {
|
|||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
private val backupFileName: String
|
|
||||||
get() {
|
|
||||||
val backupDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
|
||||||
.format(Date(System.currentTimeMillis()))
|
|
||||||
val deviceName = AppConfig.webDavDeviceName
|
|
||||||
return if (deviceName?.isNotBlank() == true) {
|
|
||||||
"backup${backupDate}-${deviceName}.zip"
|
|
||||||
} else {
|
|
||||||
"backup${backupDate}.zip"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun upConfig() {
|
suspend fun upConfig() {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
authorization = null
|
authorization = null
|
||||||
@@ -153,17 +138,17 @@ object AppWebDav {
|
|||||||
suspend fun restoreWebDav(name: String) {
|
suspend fun restoreWebDav(name: String) {
|
||||||
authorization?.let {
|
authorization?.let {
|
||||||
val webDav = WebDav(rootWebDavUrl + name, it)
|
val webDav = WebDav(rootWebDavUrl + name, it)
|
||||||
webDav.downloadTo(zipFilePath, true)
|
webDav.downloadTo(Backup.zipFilePath, true)
|
||||||
FileUtils.delete(Backup.backupPath)
|
FileUtils.delete(Backup.backupPath)
|
||||||
ZipUtils.unzipFile(zipFilePath, Backup.backupPath)
|
ZipUtils.unzipFile(Backup.zipFilePath, Backup.backupPath)
|
||||||
Restore.restoreDatabase()
|
Restore.restoreDatabase()
|
||||||
Restore.restoreConfig()
|
Restore.restoreConfig()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun hasBackUp(): Boolean {
|
suspend fun hasBackUp(backUpName: String): Boolean {
|
||||||
authorization?.let {
|
authorization?.let {
|
||||||
val url = "$rootWebDavUrl$backupFileName"
|
val url = "$rootWebDavUrl${backUpName}"
|
||||||
return WebDav(url, it).exists()
|
return WebDav(url, it).exists()
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -187,19 +172,16 @@ object AppWebDav {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* webDav备份
|
||||||
|
* @param fileName 备份文件名
|
||||||
|
*/
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
suspend fun backUpWebDav(path: String) {
|
suspend fun backUpWebDav(fileName: String) {
|
||||||
if (!NetworkUtils.isAvailable()) return
|
if (!NetworkUtils.isAvailable()) return
|
||||||
authorization?.let {
|
authorization?.let {
|
||||||
val paths = arrayListOf(*Backup.backupFileNames)
|
val putUrl = "$rootWebDavUrl$fileName"
|
||||||
for (i in 0 until paths.size) {
|
WebDav(putUrl, it).upload(Backup.zipFilePath)
|
||||||
paths[i] = path + File.separator + paths[i]
|
|
||||||
}
|
|
||||||
FileUtils.delete(zipFilePath)
|
|
||||||
if (ZipUtils.zipFiles(paths, zipFilePath)) {
|
|
||||||
val putUrl = "$rootWebDavUrl$backupFileName"
|
|
||||||
WebDav(putUrl, it).upload(zipFilePath)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import io.legado.app.constant.PreferKey
|
|||||||
import io.legado.app.data.appDb
|
import io.legado.app.data.appDb
|
||||||
import io.legado.app.help.AppWebDav
|
import io.legado.app.help.AppWebDav
|
||||||
import io.legado.app.help.DirectLinkUpload
|
import io.legado.app.help.DirectLinkUpload
|
||||||
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.config.LocalConfig
|
import io.legado.app.help.config.LocalConfig
|
||||||
import io.legado.app.help.config.ReadBookConfig
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
import io.legado.app.help.config.ThemeConfig
|
import io.legado.app.help.config.ThemeConfig
|
||||||
@@ -18,7 +19,10 @@ import kotlinx.coroutines.ensureActive
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,8 +33,9 @@ object Backup {
|
|||||||
val backupPath: String by lazy {
|
val backupPath: String by lazy {
|
||||||
appCtx.filesDir.getFile("backup").createFolderIfNotExist().absolutePath
|
appCtx.filesDir.getFile("backup").createFolderIfNotExist().absolutePath
|
||||||
}
|
}
|
||||||
|
val zipFilePath = "${appCtx.externalFiles.absolutePath}${File.separator}backup.zip"
|
||||||
|
|
||||||
val backupFileNames by lazy {
|
private val backupFileNames by lazy {
|
||||||
arrayOf(
|
arrayOf(
|
||||||
"bookshelf.json",
|
"bookshelf.json",
|
||||||
"bookmark.json",
|
"bookmark.json",
|
||||||
@@ -55,12 +60,24 @@ object Backup {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getNowZipFileName(): String {
|
||||||
|
val backupDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||||
|
.format(Date(System.currentTimeMillis()))
|
||||||
|
val deviceName = AppConfig.webDavDeviceName
|
||||||
|
return if (deviceName?.isNotBlank() == true) {
|
||||||
|
"backup${backupDate}-${deviceName}.zip"
|
||||||
|
} else {
|
||||||
|
"backup${backupDate}.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun autoBack(context: Context) {
|
fun autoBack(context: Context) {
|
||||||
val lastBackup = LocalConfig.lastBackup
|
val lastBackup = LocalConfig.lastBackup
|
||||||
if (lastBackup + TimeUnit.DAYS.toMillis(1) < System.currentTimeMillis()) {
|
if (lastBackup + TimeUnit.DAYS.toMillis(1) < System.currentTimeMillis()) {
|
||||||
Coroutine.async {
|
Coroutine.async {
|
||||||
if (!AppWebDav.hasBackUp()) {
|
val backupZipFileName = getNowZipFileName()
|
||||||
backup(context, context.getPrefString(PreferKey.backupPath), true)
|
if (!AppWebDav.hasBackUp(backupZipFileName)) {
|
||||||
|
backup(context, context.getPrefString(PreferKey.backupPath))
|
||||||
} else {
|
} else {
|
||||||
LocalConfig.lastBackup = System.currentTimeMillis()
|
LocalConfig.lastBackup = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
@@ -70,7 +87,7 @@ object Backup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun backup(context: Context, path: String?, isAuto: Boolean = false) {
|
suspend fun backup(context: Context, path: String?) {
|
||||||
LocalConfig.lastBackup = System.currentTimeMillis()
|
LocalConfig.lastBackup = System.currentTimeMillis()
|
||||||
withContext(IO) {
|
withContext(IO) {
|
||||||
FileUtils.delete(backupPath)
|
FileUtils.delete(backupPath)
|
||||||
@@ -124,18 +141,26 @@ object Backup {
|
|||||||
edit.commit()
|
edit.commit()
|
||||||
}
|
}
|
||||||
ensureActive()
|
ensureActive()
|
||||||
when {
|
val zipFileName = getNowZipFileName()
|
||||||
path.isNullOrBlank() -> {
|
val paths = arrayListOf(*backupFileNames)
|
||||||
copyBackup(context.getExternalFilesDir(null)!!, false)
|
for (i in 0 until paths.size) {
|
||||||
|
paths[i] = backupPath + File.separator + paths[i]
|
||||||
|
}
|
||||||
|
FileUtils.delete(zipFilePath)
|
||||||
|
if (ZipUtils.zipFiles(paths, zipFilePath)) {
|
||||||
|
when {
|
||||||
|
path.isNullOrBlank() -> {
|
||||||
|
copyBackup(context.getExternalFilesDir(null)!!, zipFileName)
|
||||||
|
}
|
||||||
|
path.isContentScheme() -> {
|
||||||
|
copyBackup(context, Uri.parse(path), zipFileName)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
copyBackup(File(path), zipFileName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
path.isContentScheme() -> {
|
AppWebDav.backUpWebDav(zipFileName)
|
||||||
copyBackup(context, Uri.parse(path), isAuto)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
copyBackup(File(path), isAuto)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
AppWebDav.backUpWebDav(backupPath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,40 +174,23 @@ object Backup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
private fun copyBackup(context: Context, uri: Uri, isAuto: Boolean) {
|
private fun copyBackup(context: Context, uri: Uri, fileName: String) {
|
||||||
DocumentFile.fromTreeUri(context, uri)?.let { treeDoc ->
|
DocumentFile.fromTreeUri(context, uri)?.let { treeDoc ->
|
||||||
for (fileName in backupFileNames) {
|
treeDoc.findFile(fileName)?.delete()
|
||||||
val file = File(backupPath + File.separator + fileName)
|
treeDoc.createFile("", fileName)?.openOutputStream()?.use { outputS ->
|
||||||
if (file.exists()) {
|
FileInputStream(File(zipFilePath)).use { inputS ->
|
||||||
if (isAuto) {
|
inputS.copyTo(outputS)
|
||||||
treeDoc.findFile("auto")?.findFile(fileName)?.delete()
|
|
||||||
DocumentUtils.createFileIfNotExist(
|
|
||||||
treeDoc,
|
|
||||||
fileName,
|
|
||||||
subDirs = arrayOf("auto")
|
|
||||||
)?.writeBytes(context, file.readBytes())
|
|
||||||
} else {
|
|
||||||
treeDoc.findFile(fileName)?.delete()
|
|
||||||
treeDoc.createFile("", fileName)
|
|
||||||
?.writeBytes(context, file.readBytes())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
private fun copyBackup(rootFile: File, isAuto: Boolean) {
|
private fun copyBackup(rootFile: File, fileName: String) {
|
||||||
for (fileName in backupFileNames) {
|
FileInputStream(File(zipFilePath)).use { inputS ->
|
||||||
val file = File(backupPath + File.separator + fileName)
|
val file = FileUtils.createFileIfNotExist(rootFile, fileName)
|
||||||
if (file.exists()) {
|
FileOutputStream(file).use { outputS ->
|
||||||
file.copyTo(
|
inputS.copyTo(outputS)
|
||||||
if (isAuto) {
|
|
||||||
FileUtils.createFileIfNotExist(rootFile, "auto", fileName)
|
|
||||||
} else {
|
|
||||||
FileUtils.createFileIfNotExist(rootFile, fileName)
|
|
||||||
}, true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,35 +27,21 @@ import kotlinx.coroutines.withContext
|
|||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 恢复
|
* 恢复
|
||||||
*/
|
*/
|
||||||
object Restore {
|
object Restore {
|
||||||
|
|
||||||
suspend fun restore(context: Context, path: String) {
|
suspend fun restore(context: Context, uri: Uri) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
if (path.isContentScheme()) {
|
FileUtils.delete(Backup.backupPath)
|
||||||
DocumentFile.fromTreeUri(context, Uri.parse(path))?.listFiles()?.forEach { doc ->
|
if (uri.isContentScheme()) {
|
||||||
if (Backup.backupFileNames.contains(doc.name)) {
|
DocumentFile.fromTreeUri(context, uri)?.openInputStream()!!.use {
|
||||||
context.contentResolver.openInputStream(doc.uri)?.use { inputStream ->
|
ZipUtils.unZipToPath(it, Backup.backupPath)
|
||||||
val file = File("${Backup.backupPath}${File.separator}${doc.name}")
|
|
||||||
FileOutputStream(file).use { outputStream ->
|
|
||||||
inputStream.copyTo(outputStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val dir = File(path)
|
ZipUtils.unzipFile(uri.path!!, Backup.backupPath)
|
||||||
for (fileName in Backup.backupFileNames) {
|
|
||||||
val file = dir.getFile(fileName)
|
|
||||||
if (file.exists()) {
|
|
||||||
val target = File("${Backup.backupPath}${File.separator}$fileName")
|
|
||||||
file.copyTo(target, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
AppLog.put("恢复复制文件出错\n${it.localizedMessage}", it)
|
AppLog.put("恢复复制文件出错\n${it.localizedMessage}", it)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import android.view.View
|
|||||||
import androidx.core.view.MenuProvider
|
import androidx.core.view.MenuProvider
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
@@ -38,7 +37,6 @@ import io.legado.app.ui.widget.dialog.WaitDialog
|
|||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
@@ -87,20 +85,10 @@ class BackupConfigFragment : PreferenceFragment(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private val restoreDir = registerForActivityResult(HandleFileContract()) {
|
private val restoreDoc = registerForActivityResult(HandleFileContract()) {
|
||||||
it.uri?.let { uri ->
|
it.uri?.let { uri ->
|
||||||
if (uri.isContentScheme()) {
|
Coroutine.async {
|
||||||
AppConfig.backupPath = uri.toString()
|
Restore.restore(appCtx, uri)
|
||||||
Coroutine.async {
|
|
||||||
Restore.restore(appCtx, uri.toString())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uri.path?.let { path ->
|
|
||||||
AppConfig.backupPath = path
|
|
||||||
Coroutine.async {
|
|
||||||
Restore.restore(appCtx, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +123,10 @@ class BackupConfigFragment : PreferenceFragment(),
|
|||||||
upPreferenceSummary(PreferKey.webDavDeviceName, AppConfig.webDavDeviceName)
|
upPreferenceSummary(PreferKey.webDavDeviceName, AppConfig.webDavDeviceName)
|
||||||
upPreferenceSummary(PreferKey.backupPath, getPrefString(PreferKey.backupPath))
|
upPreferenceSummary(PreferKey.backupPath, getPrefString(PreferKey.backupPath))
|
||||||
findPreference<io.legado.app.lib.prefs.Preference>("web_dav_restore")
|
findPreference<io.legado.app.lib.prefs.Preference>("web_dav_restore")
|
||||||
?.onLongClick { restoreDir.launch(); true }
|
?.onLongClick {
|
||||||
|
restoreFromLocal()
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -333,7 +324,7 @@ class BackupConfigFragment : PreferenceFragment(),
|
|||||||
AppLog.put("恢复备份出错WebDavError\n${it.localizedMessage}", it)
|
AppLog.put("恢复备份出错WebDavError\n${it.localizedMessage}", it)
|
||||||
alert {
|
alert {
|
||||||
setTitle(R.string.restore)
|
setTitle(R.string.restore)
|
||||||
setMessage("WebDavError\n${it.localizedMessage}\n将从本地备份恢复。\n从WebDav手动下载备份文件需要解压才能恢复。")
|
setMessage("WebDavError\n${it.localizedMessage}\n将从本地备份恢复。")
|
||||||
okButton {
|
okButton {
|
||||||
restoreFromLocal()
|
restoreFromLocal()
|
||||||
}
|
}
|
||||||
@@ -345,38 +336,11 @@ class BackupConfigFragment : PreferenceFragment(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreFromLocal() {
|
private fun restoreFromLocal() {
|
||||||
val backupPath = getPrefString(PreferKey.backupPath)
|
restoreDoc.launch {
|
||||||
if (backupPath?.isNotEmpty() == true) {
|
title = getString(R.string.select_restore_file)
|
||||||
if (backupPath.isContentScheme()) {
|
mode = HandleFileContract.FILE
|
||||||
val uri = Uri.parse(backupPath)
|
allowExtensions = arrayOf("zip")
|
||||||
val doc = DocumentFile.fromTreeUri(requireContext(), uri)
|
|
||||||
if (doc?.canWrite() == true) {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
Restore.restore(requireContext(), backupPath)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
restoreDir.launch()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
restoreUsePermission(backupPath)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
restoreDir.launch()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreUsePermission(path: String) {
|
|
||||||
PermissionsCompat.Builder()
|
|
||||||
.addPermissions(*Permissions.Group.STORAGE)
|
|
||||||
.rationale(R.string.tip_perm_request_storage)
|
|
||||||
.onGranted {
|
|
||||||
Coroutine.async {
|
|
||||||
AppConfig.backupPath = path
|
|
||||||
Restore.restoreDatabase(path)
|
|
||||||
Restore.restoreConfig(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.request()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,8 @@ import io.legado.app.exception.NoStackTraceException
|
|||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import splitties.systemservices.downloadManager
|
import splitties.systemservices.downloadManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
|
||||||
@@ -236,6 +238,16 @@ fun DocumentFile.listFileDocs(filter: FileDocFilter? = null): ArrayList<FileDoc>
|
|||||||
return FileDoc.fromDocumentFile(this).list(filter)
|
return FileDoc.fromDocumentFile(this).list(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun DocumentFile.openInputStream(): InputStream? {
|
||||||
|
return appCtx.contentResolver.openInputStream(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun DocumentFile.openOutputStream(): OutputStream? {
|
||||||
|
return appCtx.contentResolver.openOutputStream(uri)
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun DocumentFile.writeText(context: Context, data: String, charset: Charset = Charsets.UTF_8) {
|
fun DocumentFile.writeText(context: Context, data: String, charset: Charset = Charsets.UTF_8) {
|
||||||
uri.writeText(context, data, charset)
|
uri.writeText(context, data, charset)
|
||||||
|
|||||||
@@ -2,12 +2,9 @@ package io.legado.app.utils
|
|||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.util.zip.GZIPOutputStream
|
import java.util.zip.*
|
||||||
import java.util.zip.ZipEntry
|
|
||||||
import java.util.zip.ZipFile
|
|
||||||
import java.util.zip.ZipOutputStream
|
|
||||||
|
|
||||||
@Suppress("unused", "BlockingMethodInNonBlockingContext", "MemberVisibilityCanBePrivate")
|
@Suppress("unused", "BlockingMethodInNonBlockingContext", "MemberVisibilityCanBePrivate")
|
||||||
object ZipUtils {
|
object ZipUtils {
|
||||||
@@ -177,6 +174,36 @@ object ZipUtils {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun unZipToPath(inputStream: InputStream, path: String) {
|
||||||
|
val zipInputStream = ZipInputStream(inputStream)
|
||||||
|
unZipToPath(zipInputStream, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unZipToPath(zipInputStream: ZipInputStream, path: String) {
|
||||||
|
var entry: ZipEntry
|
||||||
|
while (zipInputStream.nextEntry.also { entry = it } != null) {
|
||||||
|
val entryFile = File(path, entry.name)
|
||||||
|
if (entry.isDirectory) {
|
||||||
|
if (!entryFile.exists()) {
|
||||||
|
entryFile.mkdirs()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (entryFile.parentFile?.exists() != true) {
|
||||||
|
entryFile.parentFile?.mkdirs()
|
||||||
|
}
|
||||||
|
if (!entryFile.exists()) {
|
||||||
|
entryFile.createNewFile()
|
||||||
|
entryFile.setReadable(true)
|
||||||
|
entryFile.setExecutable(true)
|
||||||
|
}
|
||||||
|
FileOutputStream(entryFile).use {
|
||||||
|
zipInputStream.copyTo(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zipInputStream.close()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unzip the file.
|
* Unzip the file.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user