pull/737/head
Robot 4 years ago
commit 15fd808d68
  1. 20
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  2. 2
      app/src/main/java/io/legado/app/help/http/parser/ByteParser.kt
  3. 14
      app/src/main/java/io/legado/app/help/http/parser/InputStreamParser.kt
  4. 2
      app/src/main/java/io/legado/app/help/http/parser/TextParser.kt
  5. 2
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  6. 26
      app/src/main/java/io/legado/app/help/storage/BookWebDav.kt
  7. 73
      app/src/main/java/io/legado/app/lib/webdav/WebDav.kt
  8. 7
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  9. 6
      app/src/main/java/io/legado/app/utils/ZipUtils.kt

@ -3,10 +3,12 @@ package io.legado.app.help.http
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.* import okhttp3.*
import retrofit2.Retrofit import retrofit2.Retrofit
import java.io.IOException
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.Proxy import java.net.Proxy
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@Suppress("unused") @Suppress("unused")
object HttpHelper { object HttpHelper {
@ -35,6 +37,24 @@ object HttpHelper {
builder.build() builder.build()
} }
suspend fun awaitResponse(request: Request): Response = suspendCancellableCoroutine { block ->
val call = client.newCall(request)
block.invokeOnCancellation {
call.cancel()
}
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
block.resumeWithException(e)
}
override fun onResponse(call: Call, response: Response) {
block.resume(response)
}
})
}
inline fun <reified T> getApiService( inline fun <reified T> getApiService(
baseUrl: String, baseUrl: String,
encode: String? = null, encode: String? = null,

@ -1,4 +1,4 @@
package io.legado.app.help.http package io.legado.app.help.http.parser
import okhttp3.Response import okhttp3.Response
import rxhttp.wrapper.annotation.Parser import rxhttp.wrapper.annotation.Parser

@ -0,0 +1,14 @@
package io.legado.app.help.http.parser
import okhttp3.Response
import rxhttp.wrapper.annotation.Parser
import java.io.InputStream
@Parser(name = "InputStream")
class InputStreamParser : rxhttp.wrapper.parse.Parser<InputStream> {
override fun onParse(response: Response): InputStream {
return response.body()!!.byteStream()
}
}

@ -1,4 +1,4 @@
package io.legado.app.help.http package io.legado.app.help.http.parser
import io.legado.app.utils.EncodingDetect import io.legado.app.utils.EncodingDetect
import io.legado.app.utils.UTF8BOMFighter import io.legado.app.utils.UTF8BOMFighter

@ -56,7 +56,6 @@ object Backup {
suspend fun backup(context: Context, path: String, isAuto: Boolean = false) { suspend fun backup(context: Context, path: String, isAuto: Boolean = false) {
context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis()) context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis())
withContext(IO) { withContext(IO) {
synchronized(this@Backup) {
FileUtils.deleteFile(backupPath) FileUtils.deleteFile(backupPath)
writeListToJson(App.db.bookDao.all, "bookshelf.json", backupPath) writeListToJson(App.db.bookDao.all, "bookshelf.json", backupPath)
writeListToJson(App.db.bookmarkDao.all, "bookmark.json", backupPath) writeListToJson(App.db.bookmarkDao.all, "bookmark.json", backupPath)
@ -107,7 +106,6 @@ object Backup {
} }
} }
} }
}
private fun writeListToJson(list: List<Any>, fileName: String, path: String) { private fun writeListToJson(list: List<Any>, fileName: String, path: String) {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {

@ -39,7 +39,7 @@ object BookWebDav {
return url return url
} }
fun initWebDav(): Boolean { suspend fun initWebDav(): Boolean {
val account = App.INSTANCE.getPrefString(PreferKey.webDavAccount) val account = App.INSTANCE.getPrefString(PreferKey.webDavAccount)
val password = App.INSTANCE.getPrefString(PreferKey.webDavPassword) val password = App.INSTANCE.getPrefString(PreferKey.webDavPassword)
if (!account.isNullOrBlank() && !password.isNullOrBlank()) { if (!account.isNullOrBlank() && !password.isNullOrBlank()) {
@ -52,7 +52,7 @@ object BookWebDav {
} }
@Throws(Exception::class) @Throws(Exception::class)
private 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 (initWebDav()) { if (initWebDav()) {
@ -77,7 +77,11 @@ object BookWebDav {
items = names items = names
) { _, index -> ) { _, index ->
if (index in 0 until names.size) { if (index in 0 until names.size) {
Coroutine.async {
restoreWebDav(names[index]) restoreWebDav(names[index])
}.onError {
App.INSTANCE.toast("WebDavError:${it.localizedMessage}")
}
} }
} }
} }
@ -86,8 +90,7 @@ object BookWebDav {
} }
} }
private fun restoreWebDav(name: String) { private suspend fun restoreWebDav(name: String) {
Coroutine.async {
rootWebDavUrl.let { rootWebDavUrl.let {
val webDav = WebDav(it + name) val webDav = WebDav(it + name)
webDav.downloadTo(zipFilePath, true) webDav.downloadTo(zipFilePath, true)
@ -96,12 +99,9 @@ object BookWebDav {
Restore.restoreDatabase() Restore.restoreDatabase()
Restore.restoreConfig() Restore.restoreConfig()
} }
}.onError {
App.INSTANCE.toast("WebDavError:${it.localizedMessage}")
}
} }
fun backUpWebDav(path: String) { suspend fun backUpWebDav(path: String) {
try { try {
if (initWebDav()) { if (initWebDav()) {
val paths = arrayListOf(*Backup.backupFileNames) val paths = arrayListOf(*Backup.backupFileNames)
@ -123,7 +123,7 @@ object BookWebDav {
} }
} }
fun exportWebDav(path: String, fileName: String) { suspend fun exportWebDav(path: String, fileName: String) {
try { try {
if (initWebDav()) { if (initWebDav()) {
// 默认导出到legado文件夹下exports目录 // 默认导出到legado文件夹下exports目录
@ -155,16 +155,16 @@ object BookWebDav {
durChapterTitle = book.durChapterTitle durChapterTitle = book.durChapterTitle
) )
val json = GSON.toJson(bookProgress) val json = GSON.toJson(bookProgress)
val url = geProtresstUrl(book) val url = getProgressUrl(book)
if (initWebDav()) { if (initWebDav()) {
WebDav(url).upload(json.toByteArray()) WebDav(url).upload(json.toByteArray())
} }
} }
} }
fun getBookProgress(book: Book): BookProgress? { suspend fun getBookProgress(book: Book): BookProgress? {
if (initWebDav()) { if (initWebDav()) {
val url = geProtresstUrl(book) val url = getProgressUrl(book)
WebDav(url).download()?.let { byteArray -> WebDav(url).download()?.let { byteArray ->
val json = String(byteArray) val json = String(byteArray)
GSON.fromJsonObject<BookProgress>(json)?.let { GSON.fromJsonObject<BookProgress>(json)?.let {
@ -175,7 +175,7 @@ object BookWebDav {
return null return null
} }
private fun geProtresstUrl(book: Book): String { private fun getProgressUrl(book: Book): String {
return bookProgressUrl + book.name + "_" + book.author + ".json" return bookProgressUrl + book.name + "_" + book.author + ".json"
} }
} }

@ -3,6 +3,8 @@ package io.legado.app.lib.webdav
import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.HttpHelper
import okhttp3.* import okhttp3.*
import org.jsoup.Jsoup import org.jsoup.Jsoup
import rxhttp.wrapper.param.RxHttp
import rxhttp.wrapper.param.toInputStream
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -54,17 +56,16 @@ class WebDav(urlStr: String) {
/** /**
* 填充文件信息实例化WebDAVFile对象时并没有将远程文件的信息填充到实例中需要手动填充 * 填充文件信息实例化WebDAVFile对象时并没有将远程文件的信息填充到实例中需要手动填充
*
* @return 远程文件是否存在 * @return 远程文件是否存在
*/ */
@Throws(IOException::class) suspend fun indexFileInfo(): Boolean {
fun indexFileInfo(): Boolean {
propFindResponse(ArrayList())?.let { response -> propFindResponse(ArrayList())?.let { response ->
if (!response.isSuccessful) { if (!response.isSuccessful) {
this.exists = false this.exists = false
return false return false
} }
response.body()?.let { response.body()?.let {
@Suppress("BlockingMethodInNonBlockingContext")
if (it.string().isNotEmpty()) { if (it.string().isNotEmpty()) {
return true return true
} }
@ -79,12 +80,11 @@ class WebDav(urlStr: String) {
* @param propsList 指定列出文件的哪些属性 * @param propsList 指定列出文件的哪些属性
* @return 文件列表 * @return 文件列表
*/ */
@Throws(IOException::class) suspend fun listFiles(propsList: ArrayList<String> = ArrayList()): List<WebDav> {
@JvmOverloads
fun listFiles(propsList: ArrayList<String> = ArrayList()): List<WebDav> {
propFindResponse(propsList)?.let { response -> propFindResponse(propsList)?.let { response ->
if (response.isSuccessful) { if (response.isSuccessful) {
response.body()?.let { body -> response.body()?.let { body ->
@Suppress("BlockingMethodInNonBlockingContext")
return parseDir(body.string()) return parseDir(body.string())
} }
} }
@ -93,7 +93,7 @@ class WebDav(urlStr: String) {
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun propFindResponse(propsList: ArrayList<String>, depth: Int = 1): Response? { private suspend fun propFindResponse(propsList: ArrayList<String>, depth: Int = 1): Response? {
val requestProps = StringBuilder() val requestProps = StringBuilder()
for (p in propsList) { for (p in propsList) {
requestProps.append("<a:").append(p).append("/>\n") requestProps.append("<a:").append(p).append("/>\n")
@ -105,23 +105,18 @@ class WebDav(urlStr: String) {
String.format(DIR, requestProps.toString() + "\n") String.format(DIR, requestProps.toString() + "\n")
} }
httpUrl?.let { url -> httpUrl?.let { url ->
val request = Request.Builder()
.url(url)
// 添加RequestBody对象,可以只返回的属性。如果设为null,则会返回全部属性 // 添加RequestBody对象,可以只返回的属性。如果设为null,则会返回全部属性
// 注意:尽量手动指定需要返回的属性。若返回全部属性,可能后由于Prop.java里没有该属性名,而崩溃。 // 注意:尽量手动指定需要返回的属性。若返回全部属性,可能后由于Prop.java里没有该属性名,而崩溃。
.method( val requestBody = RequestBody.create(MediaType.parse("text/plain"), requestPropsStr)
"PROPFIND", val request = Request.Builder()
RequestBody.create(MediaType.parse("text/plain"), requestPropsStr) .url(url)
) .method("PROPFIND", requestBody)
HttpAuth.auth?.let { HttpAuth.auth?.let {
request.header( request.header("Authorization", Credentials.basic(it.user, it.pass))
"Authorization",
Credentials.basic(it.user, it.pass)
)
} }
request.header("Depth", if (depth < 0) "infinity" else depth.toString()) request.header("Depth", if (depth < 0) "infinity" else depth.toString())
return HttpHelper.client.newCall(request.build()).execute() return HttpHelper.awaitResponse(request.build())
} }
return null return null
} }
@ -165,8 +160,7 @@ class WebDav(urlStr: String) {
* *
* @return 是否创建成功 * @return 是否创建成功
*/ */
@Throws(IOException::class) suspend fun makeAsDir(): Boolean {
fun makeAsDir(): Boolean {
httpUrl?.let { url -> httpUrl?.let { url ->
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
@ -183,7 +177,7 @@ class WebDav(urlStr: String) {
* @param replaceExisting 是否替换本地的同名文件 * @param replaceExisting 是否替换本地的同名文件
* @return 下载是否成功 * @return 下载是否成功
*/ */
fun downloadTo(savedPath: String, replaceExisting: Boolean): Boolean { suspend fun downloadTo(savedPath: String, replaceExisting: Boolean): Boolean {
if (File(savedPath).exists()) { if (File(savedPath).exists()) {
if (!replaceExisting) return false if (!replaceExisting) return false
} }
@ -192,7 +186,7 @@ class WebDav(urlStr: String) {
return true return true
} }
fun download(): ByteArray? { suspend fun download(): ByteArray? {
val inputS = getInputStream() ?: return null val inputS = getInputStream() ?: return null
return inputS.readBytes() return inputS.readBytes()
} }
@ -200,8 +194,7 @@ class WebDav(urlStr: String) {
/** /**
* 上传文件 * 上传文件
*/ */
@Throws(IOException::class) suspend fun upload(localPath: String, contentType: String? = null): Boolean {
fun upload(localPath: String, contentType: String? = null): Boolean {
val file = File(localPath) val file = File(localPath)
if (!file.exists()) return false if (!file.exists()) return false
val mediaType = contentType?.let { MediaType.parse(it) } val mediaType = contentType?.let { MediaType.parse(it) }
@ -216,7 +209,7 @@ class WebDav(urlStr: String) {
return false return false
} }
fun upload(byteArray: ByteArray, contentType: String? = null): Boolean { suspend fun upload(byteArray: ByteArray, contentType: String? = null): Boolean {
val mediaType = contentType?.let { MediaType.parse(it) } val mediaType = contentType?.let { MediaType.parse(it) }
// 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息 // 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息
val fileBody = RequestBody.create(mediaType, byteArray) val fileBody = RequestBody.create(mediaType, byteArray)
@ -235,30 +228,22 @@ class WebDav(urlStr: String) {
* @return 请求执行的结果 * @return 请求执行的结果
*/ */
@Throws(IOException::class) @Throws(IOException::class)
private fun execRequest(requestBuilder: Request.Builder): Boolean { private suspend fun execRequest(requestBuilder: Request.Builder): Boolean {
HttpAuth.auth?.let { HttpAuth.auth?.let {
requestBuilder.header( requestBuilder.header("Authorization", Credentials.basic(it.user, it.pass))
"Authorization",
Credentials.basic(it.user, it.pass)
)
} }
val response = HttpHelper.client.newCall(requestBuilder.build()).execute() val response = HttpHelper.awaitResponse(requestBuilder.build())
return response.isSuccessful return response.isSuccessful
} }
private fun getInputStream(): InputStream? { @Throws(IOException::class)
httpUrl?.let { url -> private suspend fun getInputStream(): InputStream? {
val request = Request.Builder().url(url) val url = httpUrl
HttpAuth.auth?.let { val auth = HttpAuth.auth
request.header("Authorization", Credentials.basic(it.user, it.pass)) if (url != null && auth != null) {
} return RxHttp.get(url)
try { .addHeader("Authorization", Credentials.basic(auth.user, auth.pass))
return HttpHelper.client.newCall(request.build()).execute().body()?.byteStream() .toInputStream().await()
} catch (e: IOException) {
e.printStackTrace()
} catch (e: IllegalArgumentException) {
e.printStackTrace()
}
} }
return null return null
} }

@ -10,6 +10,7 @@ import io.legado.app.help.CacheManager
import io.legado.app.help.JsExtensions import io.legado.app.help.JsExtensions
import io.legado.app.help.http.CookieStore import io.legado.app.help.http.CookieStore
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.runBlocking
import org.jsoup.nodes.Entities import org.jsoup.nodes.Entities
import org.mozilla.javascript.NativeObject import org.mozilla.javascript.NativeObject
import java.net.URL import java.net.URL
@ -645,9 +646,9 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
override fun ajax(urlStr: String): String? { override fun ajax(urlStr: String): String? {
return try { return try {
val analyzeUrl = AnalyzeUrl(urlStr, book = book) val analyzeUrl = AnalyzeUrl(urlStr, book = book)
val call = analyzeUrl.getResponse(urlStr) runBlocking {
val response = call.execute() analyzeUrl.getRes(urlStr).body
response.body() }
} catch (e: Exception) { } catch (e: Exception) {
e.localizedMessage e.localizedMessage
} }

@ -18,8 +18,7 @@ object ZipUtils {
* @return `true`: success<br></br>`false`: fail * @return `true`: success<br></br>`false`: fail
* @throws IOException if an I/O error has occurred * @throws IOException if an I/O error has occurred
*/ */
@Throws(IOException::class) suspend fun zipFiles(
fun zipFiles(
srcFiles: Collection<String>, srcFiles: Collection<String>,
zipFilePath: String zipFilePath: String
): Boolean { ): Boolean {
@ -35,8 +34,7 @@ object ZipUtils {
* @return `true`: success<br></br>`false`: fail * @return `true`: success<br></br>`false`: fail
* @throws IOException if an I/O error has occurred * @throws IOException if an I/O error has occurred
*/ */
@Throws(IOException::class) suspend fun zipFiles(
fun zipFiles(
srcFilePaths: Collection<String>?, srcFilePaths: Collection<String>?,
zipFilePath: String?, zipFilePath: String?,
comment: String? comment: String?

Loading…
Cancel
Save