pull/1822/head
kunfei 3 years ago
parent d131863547
commit 4c38acd664
  1. 57
      app/src/main/java/io/legado/app/help/storage/AppWebDav.kt
  2. 19
      app/src/main/java/io/legado/app/lib/webdav/Authorization.kt
  3. 9
      app/src/main/java/io/legado/app/lib/webdav/HttpAuth.kt
  4. 132
      app/src/main/java/io/legado/app/lib/webdav/WebDav.kt

@ -12,7 +12,7 @@ import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.webdav.HttpAuth import io.legado.app.lib.webdav.Authorization
import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.WebDav
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -31,7 +31,9 @@ object AppWebDav {
val syncBookProgress get() = appCtx.getPrefBoolean(PreferKey.syncBookProgress, true) val syncBookProgress get() = appCtx.getPrefBoolean(PreferKey.syncBookProgress, true)
var isOk = false var authorization: Authorization? = null
val isOk get() = authorization != null
init { init {
runBlocking { runBlocking {
@ -61,14 +63,15 @@ object AppWebDav {
suspend fun upConfig() { suspend fun upConfig() {
kotlin.runCatching { kotlin.runCatching {
isOk = false authorization = null
val account = appCtx.getPrefString(PreferKey.webDavAccount) val account = appCtx.getPrefString(PreferKey.webDavAccount)
val password = appCtx.getPrefString(PreferKey.webDavPassword) val password = appCtx.getPrefString(PreferKey.webDavPassword)
if (!account.isNullOrBlank() && !password.isNullOrBlank()) { if (!account.isNullOrBlank() && !password.isNullOrBlank()) {
HttpAuth.auth = HttpAuth.Auth(account, password) val mAuthorization = Authorization(account, password)
WebDav(rootWebDavUrl).makeAsDir() WebDav(rootWebDavUrl, mAuthorization).makeAsDir()
WebDav(bookProgressUrl).makeAsDir() WebDav(bookProgressUrl, mAuthorization).makeAsDir()
isOk = true
authorization = mAuthorization
} }
} }
} }
@ -77,18 +80,16 @@ object AppWebDav {
private suspend fun getWebDavFileNames(): ArrayList<String> { private suspend fun getWebDavFileNames(): ArrayList<String> {
val url = rootWebDavUrl val url = rootWebDavUrl
val names = arrayListOf<String>() val names = arrayListOf<String>()
if (isOk) { authorization?.let {
var files = WebDav(url).listFiles() var files = WebDav(url, it).listFiles()
files = files.reversed() files = files.reversed()
files.forEach { files.forEach { webDav ->
val name = it.displayName val name = webDav.displayName
if (name?.startsWith("backup") == true) { if (name?.startsWith("backup") == true) {
names.add(name) names.add(name)
} }
} }
} else { } ?: throw NoStackTraceException("webDav没有配置")
throw NoStackTraceException("webDav没有配置")
}
return names return names
} }
@ -115,8 +116,8 @@ object AppWebDav {
} }
private suspend fun restoreWebDav(name: String) { private suspend fun restoreWebDav(name: String) {
rootWebDavUrl.let { authorization?.let {
val webDav = WebDav(it + name) val webDav = WebDav(rootWebDavUrl + name, it)
webDav.downloadTo(zipFilePath, true) webDav.downloadTo(zipFilePath, true)
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
ZipUtils.unzipFile(zipFilePath, Backup.backupPath) ZipUtils.unzipFile(zipFilePath, Backup.backupPath)
@ -126,16 +127,16 @@ object AppWebDav {
} }
suspend fun hasBackUp(): Boolean { suspend fun hasBackUp(): Boolean {
if (isOk) { authorization?.let {
val url = "${rootWebDavUrl}${backupFileName}" val url = "${rootWebDavUrl}${backupFileName}"
return WebDav(url).exists() return WebDav(url, it).exists()
} }
return false return false
} }
suspend fun backUpWebDav(path: String) { suspend fun backUpWebDav(path: String) {
try { try {
if (isOk && NetworkUtils.isAvailable()) { authorization?.let {
val paths = arrayListOf(*Backup.backupFileNames) val paths = arrayListOf(*Backup.backupFileNames)
for (i in 0 until paths.size) { for (i in 0 until paths.size) {
paths[i] = path + File.separator + paths[i] paths[i] = path + File.separator + paths[i]
@ -143,7 +144,7 @@ object AppWebDav {
FileUtils.delete(zipFilePath) FileUtils.delete(zipFilePath)
if (ZipUtils.zipFiles(paths, zipFilePath)) { if (ZipUtils.zipFiles(paths, zipFilePath)) {
val putUrl = "${rootWebDavUrl}${backupFileName}" val putUrl = "${rootWebDavUrl}${backupFileName}"
WebDav(putUrl).upload(zipFilePath) WebDav(putUrl, it).upload(zipFilePath)
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -153,14 +154,14 @@ object AppWebDav {
suspend fun exportWebDav(byteArray: ByteArray, fileName: String) { suspend fun exportWebDav(byteArray: ByteArray, fileName: String) {
try { try {
if (isOk && NetworkUtils.isAvailable()) { authorization?.let {
// 默认导出到legado文件夹下exports目录 // 默认导出到legado文件夹下exports目录
val exportsWebDavUrl = rootWebDavUrl + EncoderUtils.escape("exports") + "/" val exportsWebDavUrl = rootWebDavUrl + EncoderUtils.escape("exports") + "/"
// 在legado文件夹创建exports目录,如果不存在的话 // 在legado文件夹创建exports目录,如果不存在的话
WebDav(exportsWebDavUrl).makeAsDir() WebDav(exportsWebDavUrl, it).makeAsDir()
// 如果导出的本地文件存在,开始上传 // 如果导出的本地文件存在,开始上传
val putUrl = exportsWebDavUrl + fileName val putUrl = exportsWebDavUrl + fileName
WebDav(putUrl).upload(byteArray, "text/plain") WebDav(putUrl, it).upload(byteArray, "text/plain")
} }
} catch (e: Exception) { } catch (e: Exception) {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
@ -170,14 +171,14 @@ object AppWebDav {
} }
fun uploadBookProgress(book: Book) { fun uploadBookProgress(book: Book) {
if (!isOk) return val authorization = authorization ?: return
if (!syncBookProgress) return if (!syncBookProgress) return
if (!NetworkUtils.isAvailable()) return if (!NetworkUtils.isAvailable()) return
Coroutine.async { Coroutine.async {
val bookProgress = BookProgress(book) val bookProgress = BookProgress(book)
val json = GSON.toJson(bookProgress) val json = GSON.toJson(bookProgress)
val url = getProgressUrl(book) val url = getProgressUrl(book)
WebDav(url).upload(json.toByteArray(), "application/json") WebDav(url, authorization).upload(json.toByteArray(), "application/json")
} }
} }
@ -189,9 +190,9 @@ object AppWebDav {
* 获取书籍进度 * 获取书籍进度
*/ */
suspend fun getBookProgress(book: Book): BookProgress? { suspend fun getBookProgress(book: Book): BookProgress? {
if (isOk && NetworkUtils.isAvailable()) { authorization?.let {
val url = getProgressUrl(book) val url = getProgressUrl(book)
WebDav(url).download()?.let { byteArray -> WebDav(url, it).download()?.let { byteArray ->
val json = String(byteArray) val json = String(byteArray)
if (json.isJson()) { if (json.isJson()) {
return GSON.fromJsonObject<BookProgress>(json).getOrNull() return GSON.fromJsonObject<BookProgress>(json).getOrNull()
@ -202,7 +203,7 @@ object AppWebDav {
} }
suspend fun downloadAllBookProgress() { suspend fun downloadAllBookProgress() {
if (!isOk) return authorization ?: return
appDb.bookDao.all.forEach { book -> appDb.bookDao.all.forEach { book ->
getBookProgress(book)?.let { bookProgress -> getBookProgress(book)?.let { bookProgress ->
if (bookProgress.durChapterIndex > book.durChapterIndex || if (bookProgress.durChapterIndex > book.durChapterIndex ||

@ -0,0 +1,19 @@
package io.legado.app.lib.webdav
import okhttp3.Credentials
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
data class Authorization(
val username: String,
val password: String,
val charset: Charset = StandardCharsets.ISO_8859_1
) {
val data: String = Credentials.basic(username, password, charset)
override fun toString(): String {
return data
}
}

@ -1,9 +0,0 @@
package io.legado.app.lib.webdav
object HttpAuth {
var auth: Auth? = null
class Auth internal constructor(val user: String, val pass: String)
}

@ -1,17 +1,17 @@
package io.legado.app.lib.webdav package io.legado.app.lib.webdav
import android.util.Log
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.help.http.newCallResponse
import io.legado.app.help.http.newCallResponseBody import io.legado.app.help.http.newCallResponseBody
import io.legado.app.help.http.okHttpClient import io.legado.app.help.http.okHttpClient
import io.legado.app.help.http.text import io.legado.app.help.http.text
import io.legado.app.utils.printOnDebug import io.legado.app.utils.printOnDebug
import okhttp3.Credentials
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import org.jsoup.Jsoup import org.jsoup.Jsoup
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.net.MalformedURLException import java.net.MalformedURLException
@ -19,7 +19,7 @@ import java.net.URL
import java.net.URLEncoder import java.net.URLEncoder
@Suppress("unused", "MemberVisibilityCanBePrivate") @Suppress("unused", "MemberVisibilityCanBePrivate")
class WebDav(urlStr: String) { class WebDav(urlStr: String, val authorization: Authorization) {
companion object { companion object {
// 指定返回哪些属性 // 指定返回哪些属性
@Language("xml") @Language("xml")
@ -88,24 +88,20 @@ class WebDav(urlStr: String) {
} else { } else {
String.format(DIR, requestProps.toString() + "\n") String.format(DIR, requestProps.toString() + "\n")
} }
val url = httpUrl val url = httpUrl ?: return null
val auth = HttpAuth.auth return kotlin.runCatching {
if (url != null && auth != null) { okHttpClient.newCallResponseBody {
return kotlin.runCatching { url(url)
okHttpClient.newCallResponseBody { addHeader("Authorization", authorization.data)
url(url) addHeader("Depth", "1")
addHeader("Authorization", Credentials.basic(auth.user, auth.pass)) // 添加RequestBody对象,可以只返回的属性。如果设为null,则会返回全部属性
addHeader("Depth", "1") // 注意:尽量手动指定需要返回的属性。若返回全部属性,可能后由于Prop.java里没有该属性名,而崩溃。
// 添加RequestBody对象,可以只返回的属性。如果设为null,则会返回全部属性 val requestBody = requestPropsStr.toRequestBody("text/plain".toMediaType())
// 注意:尽量手动指定需要返回的属性。若返回全部属性,可能后由于Prop.java里没有该属性名,而崩溃。 method("PROPFIND", requestBody)
val requestBody = requestPropsStr.toRequestBody("text/plain".toMediaType()) }.text()
method("PROPFIND", requestBody) }.onFailure { e ->
}.text() e.printOnDebug()
}.onFailure { e -> }.getOrNull()
e.printOnDebug()
}.getOrNull()
}
return null
} }
private fun parseDir(s: String): List<WebDav> { private fun parseDir(s: String): List<WebDav> {
@ -120,7 +116,7 @@ class WebDav(urlStr: String) {
val fileName = href.substring(href.lastIndexOf("/") + 1) val fileName = href.substring(href.lastIndexOf("/") + 1)
val webDavFile: WebDav val webDavFile: WebDav
try { try {
webDavFile = WebDav(baseUrl + fileName) webDavFile = WebDav(baseUrl + fileName, authorization)
webDavFile.displayName = fileName webDavFile.displayName = fileName
webDavFile.contentType = element webDavFile.contentType = element
.getElementsByTag("d:getcontenttype") .getElementsByTag("d:getcontenttype")
@ -157,23 +153,19 @@ class WebDav(urlStr: String) {
* @return 是否创建成功 * @return 是否创建成功
*/ */
suspend fun makeAsDir(): Boolean { suspend fun makeAsDir(): Boolean {
val url = httpUrl val url = httpUrl ?: return false
val auth = HttpAuth.auth //防止报错
if (url != null && auth != null) { return kotlin.runCatching {
//防止报错 if (!exists()) {
return kotlin.runCatching { okHttpClient.newCallResponseBody {
if (!exists()) { url(url)
okHttpClient.newCallResponseBody { method("MKCOL", null)
url(url) addHeader("Authorization", authorization.data)
method("MKCOL", null) }.close()
addHeader("Authorization", Credentials.basic(auth.user, auth.pass)) }
}.close() }.onFailure {
} AppLog.put(it.localizedMessage)
}.onFailure { }.isSuccess
AppLog.put(it.localizedMessage)
}.isSuccess
}
return false
} }
/** /**
@ -208,49 +200,41 @@ class WebDav(urlStr: String) {
if (!file.exists()) return false if (!file.exists()) return false
// 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息 // 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息
val fileBody = file.asRequestBody(contentType.toMediaType()) val fileBody = file.asRequestBody(contentType.toMediaType())
val url = httpUrl val url = httpUrl ?: return false
val auth = HttpAuth.auth return kotlin.runCatching {
if (url != null && auth != null) { okHttpClient.newCallResponse {
return kotlin.runCatching { url(url)
okHttpClient.newCallResponseBody { put(fileBody)
url(url) addHeader("Authorization", authorization.data)
put(fileBody) }.body?.string()?.let {
addHeader("Authorization", Credentials.basic(auth.user, auth.pass)) Log.d("webDav", it)
}.close() }
}.isSuccess }.onFailure {
} it.printOnDebug()
return false }.isSuccess
} }
suspend fun upload(byteArray: ByteArray, contentType: String): Boolean { suspend fun upload(byteArray: ByteArray, contentType: String): Boolean {
// 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息 // 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息
val fileBody = byteArray.toRequestBody(contentType.toMediaType()) val fileBody = byteArray.toRequestBody(contentType.toMediaType())
val url = httpUrl val url = httpUrl ?: return false
val auth = HttpAuth.auth return kotlin.runCatching {
if (url != null && auth != null) { okHttpClient.newCallResponseBody {
return kotlin.runCatching { url(url)
okHttpClient.newCallResponseBody { put(fileBody)
url(url) addHeader("Authorization", authorization.data)
put(fileBody) }.close()
addHeader("Authorization", Credentials.basic(auth.user, auth.pass)) }.isSuccess
}.close()
}.isSuccess
}
return false
} }
private suspend fun getInputStream(): InputStream? { private suspend fun getInputStream(): InputStream? {
val url = httpUrl val url = httpUrl ?: return null
val auth = HttpAuth.auth return kotlin.runCatching {
if (url != null && auth != null) { okHttpClient.newCallResponseBody {
return kotlin.runCatching { url(url)
okHttpClient.newCallResponseBody { addHeader("Authorization", authorization.data)
url(url) }.byteStream()
addHeader("Authorization", Credentials.basic(auth.user, auth.pass)) }.getOrNull()
}.byteStream()
}.getOrNull()
}
return null
} }
} }
Loading…
Cancel
Save