Merge branch 'gedoor:master' into master
This commit is contained in:
@@ -21,8 +21,8 @@ import io.legado.app.model.localBook.EpubFile
|
|||||||
import io.legado.app.model.localBook.LocalBook
|
import io.legado.app.model.localBook.LocalBook
|
||||||
import io.legado.app.model.localBook.UmdFile
|
import io.legado.app.model.localBook.UmdFile
|
||||||
import io.legado.app.model.webBook.WebBook
|
import io.legado.app.model.webBook.WebBook
|
||||||
import io.legado.app.utils.*
|
|
||||||
import io.legado.app.ui.book.read.page.provider.ImageProvider
|
import io.legado.app.ui.book.read.page.provider.ImageProvider
|
||||||
|
import io.legado.app.utils.*
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
@@ -87,9 +87,11 @@ object BookController {
|
|||||||
this.bookSource = appDb.bookSourceDao.getBookSource(book.origin)
|
this.bookSource = appDb.bookSourceDao.getBookSource(book.origin)
|
||||||
}
|
}
|
||||||
this.bookUrl = bookUrl
|
this.bookUrl = bookUrl
|
||||||
return ImageProvider.getImage(book, src, bookSource, width, width)?.let {
|
val bitmap = runBlocking {
|
||||||
returnData.setData(it)
|
ImageProvider.cacheImage(book, src, bookSource)
|
||||||
} ?: returnData.setErrorMsg("图片加载失败或不存在")
|
ImageProvider.getImage(book, src, width, width)
|
||||||
|
}
|
||||||
|
return returnData.setData(bitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ object AppLog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun putDebug(message: String?, throwable: Throwable? = null) {
|
fun putDebug(message: String?, throwable: Throwable? = null) {
|
||||||
if (AppConfig.recordLog) {
|
if (AppConfig.recordLog || BuildConfig.DEBUG) {
|
||||||
put(message, throwable)
|
put(message, throwable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,7 +193,14 @@ data class Book(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getUseReplaceRule(): Boolean {
|
fun getUseReplaceRule(): Boolean {
|
||||||
return config.useReplaceRule ?: AppConfig.replaceEnableDefault
|
val useReplaceRule = config.useReplaceRule
|
||||||
|
if (useReplaceRule != null) {
|
||||||
|
return useReplaceRule
|
||||||
|
}
|
||||||
|
if (type == BookType.image) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return AppConfig.replaceEnableDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setReSegment(reSegment: Boolean) {
|
fun setReSegment(reSegment: Boolean) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.legado.app.help
|
package io.legado.app.help
|
||||||
|
|
||||||
|
import io.legado.app.constant.AppLog
|
||||||
import io.legado.app.constant.AppPattern
|
import io.legado.app.constant.AppPattern
|
||||||
import io.legado.app.constant.EventBus
|
import io.legado.app.constant.EventBus
|
||||||
import io.legado.app.data.appDb
|
import io.legado.app.data.appDb
|
||||||
@@ -126,7 +127,7 @@ object BookHelp {
|
|||||||
).writeBytes(it)
|
).writeBytes(it)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printOnDebug()
|
AppLog.putDebug("${src}下载错误", e)
|
||||||
} finally {
|
} finally {
|
||||||
downloadImages.remove(src)
|
downloadImages.remove(src)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package io.legado.app.help.http.cronet
|
|||||||
|
|
||||||
import io.legado.app.help.http.okHttpClient
|
import io.legado.app.help.http.okHttpClient
|
||||||
import io.legado.app.utils.DebugLog
|
import io.legado.app.utils.DebugLog
|
||||||
import io.legado.app.utils.rethrowAsIOException
|
import io.legado.app.utils.asIOException
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.EventListener
|
import okhttp3.EventListener
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
@@ -27,12 +27,24 @@ abstract class AbsCallBack(
|
|||||||
|
|
||||||
var mResponse: Response
|
var mResponse: Response
|
||||||
|
|
||||||
var mException: IOException? = null
|
|
||||||
private var followCount = 0
|
private var followCount = 0
|
||||||
|
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
abstract fun waitForDone(urlRequest: UrlRequest): Response
|
abstract fun waitForDone(urlRequest: UrlRequest): Response
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当发生错误时,通知子类终止阻塞抛出错误
|
||||||
|
* @param error
|
||||||
|
*/
|
||||||
|
abstract fun onError(error: IOException)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求成功后,通知子类结束阻塞,返回response
|
||||||
|
* @param response
|
||||||
|
*/
|
||||||
|
abstract fun onSuccess(response: Response)
|
||||||
|
|
||||||
|
|
||||||
override fun onRedirectReceived(
|
override fun onRedirectReceived(
|
||||||
request: UrlRequest,
|
request: UrlRequest,
|
||||||
@@ -41,10 +53,13 @@ abstract class AbsCallBack(
|
|||||||
) {
|
) {
|
||||||
if (followCount > MAX_FOLLOW_COUNT) {
|
if (followCount > MAX_FOLLOW_COUNT) {
|
||||||
request.cancel()
|
request.cancel()
|
||||||
mException = IOException("Too many redirect")
|
onError(IOException("Too many redirect"))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (mCall.isCanceled()) {
|
if (mCall.isCanceled()) {
|
||||||
mException = IOException("Request Canceled")
|
onError(IOException("Request Canceled"))
|
||||||
|
request.cancel()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
followCount += 1
|
followCount += 1
|
||||||
val client = okHttpClient
|
val client = okHttpClient
|
||||||
@@ -55,7 +70,7 @@ abstract class AbsCallBack(
|
|||||||
} else if (okHttpClient.followRedirects) {
|
} else if (okHttpClient.followRedirects) {
|
||||||
request.followRedirect()
|
request.followRedirect()
|
||||||
} else {
|
} else {
|
||||||
mException = IOException("Too many redirect")
|
onError(IOException("Too many redirect"))
|
||||||
request.cancel()
|
request.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +88,7 @@ abstract class AbsCallBack(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(IOException::class)
|
||||||
override fun onReadCompleted(
|
override fun onReadCompleted(
|
||||||
request: UrlRequest,
|
request: UrlRequest,
|
||||||
info: UrlResponseInfo,
|
info: UrlResponseInfo,
|
||||||
@@ -83,7 +98,7 @@ abstract class AbsCallBack(
|
|||||||
|
|
||||||
if (mCall.isCanceled()) {
|
if (mCall.isCanceled()) {
|
||||||
request.cancel()
|
request.cancel()
|
||||||
mException = IOException("Request Canceled")
|
onError(IOException("Request Canceled"))
|
||||||
}
|
}
|
||||||
|
|
||||||
byteBuffer.flip()
|
byteBuffer.flip()
|
||||||
@@ -91,9 +106,9 @@ abstract class AbsCallBack(
|
|||||||
try {
|
try {
|
||||||
buffer.write(byteBuffer)
|
buffer.write(byteBuffer)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
DebugLog.i(javaClass.name, "IOException during ByteBuffer read. Details: ", e)
|
DebugLog.e(javaClass.name, "IOException during ByteBuffer read. Details: ", e)
|
||||||
mException = IOException("IOException during ByteBuffer read. Details:", e)
|
onError(IOException("IOException during ByteBuffer read. Details:", e))
|
||||||
throw e
|
return
|
||||||
}
|
}
|
||||||
byteBuffer.clear()
|
byteBuffer.clear()
|
||||||
request.read(byteBuffer)
|
request.read(byteBuffer)
|
||||||
@@ -108,7 +123,8 @@ abstract class AbsCallBack(
|
|||||||
buffer.asResponseBody(contentType)
|
buffer.asResponseBody(contentType)
|
||||||
val newRequest = originalRequest.newBuilder().url(info.url).build()
|
val newRequest = originalRequest.newBuilder().url(info.url).build()
|
||||||
this.mResponse = this.mResponse.newBuilder().body(responseBody).request(newRequest).build()
|
this.mResponse = this.mResponse.newBuilder().body(responseBody).request(newRequest).build()
|
||||||
DebugLog.i(javaClass.simpleName, "end[${info.negotiatedProtocol}]${info.url}")
|
onSuccess(this.mResponse)
|
||||||
|
//DebugLog.i(javaClass.simpleName, "end[${info.negotiatedProtocol}]${info.url}")
|
||||||
|
|
||||||
eventListener?.callEnd(mCall)
|
eventListener?.callEnd(mCall)
|
||||||
if (responseCallback != null) {
|
if (responseCallback != null) {
|
||||||
@@ -123,8 +139,8 @@ abstract class AbsCallBack(
|
|||||||
|
|
||||||
//UrlResponseInfo可能为null
|
//UrlResponseInfo可能为null
|
||||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
||||||
DebugLog.i(javaClass.name, error.message.toString())
|
DebugLog.e(javaClass.name, error.message.toString())
|
||||||
mException = error.rethrowAsIOException()
|
onError(error.asIOException())
|
||||||
this.eventListener?.callFailed(mCall, error)
|
this.eventListener?.callFailed(mCall, error)
|
||||||
responseCallback?.onFailure(mCall, error)
|
responseCallback?.onFailure(mCall, error)
|
||||||
}
|
}
|
||||||
@@ -132,7 +148,7 @@ abstract class AbsCallBack(
|
|||||||
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||||
super.onCanceled(request, info)
|
super.onCanceled(request, info)
|
||||||
this.eventListener?.callEnd(mCall)
|
this.eventListener?.callEnd(mCall)
|
||||||
mException = IOException("Cronet Request Canceled")
|
onError(IOException("Cronet Request Canceled"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package io.legado.app.help.http.cronet
|
|||||||
|
|
||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
|
import io.legado.app.help.http.okHttpClient
|
||||||
import io.legado.app.utils.DebugLog
|
import io.legado.app.utils.DebugLog
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
@@ -13,11 +14,8 @@ import org.chromium.net.UploadDataProviders
|
|||||||
import org.chromium.net.UrlRequest
|
import org.chromium.net.UrlRequest
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
|
//val executor: ExecutorService by lazy { Executors.newCachedThreadPool() }
|
||||||
val executor: ExecutorService by lazy { Executors.newCachedThreadPool() }
|
|
||||||
|
|
||||||
val cronetEngine: ExperimentalCronetEngine? by lazy {
|
val cronetEngine: ExperimentalCronetEngine? by lazy {
|
||||||
if (!AppConfig.isGooglePlay) {
|
if (!AppConfig.isGooglePlay) {
|
||||||
@@ -48,7 +46,11 @@ fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest? {
|
|||||||
val url = request.url.toString()
|
val url = request.url.toString()
|
||||||
val headers: Headers = request.headers
|
val headers: Headers = request.headers
|
||||||
val requestBody = request.body
|
val requestBody = request.body
|
||||||
return cronetEngine?.newUrlRequestBuilder(url, callback, executor)?.apply {
|
return cronetEngine?.newUrlRequestBuilder(
|
||||||
|
url,
|
||||||
|
callback,
|
||||||
|
okHttpClient.dispatcher.executorService
|
||||||
|
)?.apply {
|
||||||
setHttpMethod(request.method)//设置
|
setHttpMethod(request.method)//设置
|
||||||
allowDirectExecutor()
|
allowDirectExecutor()
|
||||||
headers.forEachIndexed { index, _ ->
|
headers.forEachIndexed { index, _ ->
|
||||||
@@ -65,7 +67,7 @@ fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest? {
|
|||||||
requestBody.writeTo(buffer)
|
requestBody.writeTo(buffer)
|
||||||
setUploadDataProvider(
|
setUploadDataProvider(
|
||||||
UploadDataProviders.create(buffer.readByteArray()),
|
UploadDataProviders.create(buffer.readByteArray()),
|
||||||
executor
|
okHttpClient.dispatcher.executorService
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
|
|||||||
OldCallback(request, call)
|
OldCallback(request, call)
|
||||||
}
|
}
|
||||||
buildRequest(request, callBack)?.let {
|
buildRequest(request, callBack)?.let {
|
||||||
it.start()
|
|
||||||
return callBack.waitForDone(it)
|
return callBack.waitForDone(it)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -268,7 +268,8 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
download = true
|
download = true
|
||||||
executor.execute {
|
|
||||||
|
Coroutine.async {
|
||||||
val result = downloadFileIfNotExist(url, downloadTempFile)
|
val result = downloadFileIfNotExist(url, downloadTempFile)
|
||||||
DebugLog.d(javaClass.simpleName, "download result:$result")
|
DebugLog.d(javaClass.simpleName, "download result:$result")
|
||||||
//文件md5再次校验
|
//文件md5再次校验
|
||||||
@@ -279,7 +280,7 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
|
|||||||
downloadTempFile.deleteOnExit()
|
downloadTempFile.deleteOnExit()
|
||||||
}
|
}
|
||||||
download = false
|
download = false
|
||||||
return@execute
|
return@async
|
||||||
}
|
}
|
||||||
DebugLog.d(javaClass.simpleName, "download success, copy to $destSuccessFile")
|
DebugLog.d(javaClass.simpleName, "download success, copy to $destSuccessFile")
|
||||||
//下载成功拷贝文件
|
//下载成功拷贝文件
|
||||||
@@ -289,6 +290,9 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
|
|||||||
@Suppress("SameParameterValue")
|
@Suppress("SameParameterValue")
|
||||||
deleteHistoryFile(parentFile!!, null)
|
deleteHistoryFile(parentFile!!, null)
|
||||||
}
|
}
|
||||||
|
// executor.execute {
|
||||||
|
//
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,11 +6,8 @@ import androidx.annotation.RequiresApi
|
|||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.chromium.net.CronetException
|
|
||||||
import org.chromium.net.UrlRequest
|
import org.chromium.net.UrlRequest
|
||||||
import org.chromium.net.UrlResponseInfo
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@@ -21,45 +18,29 @@ class NewCallBack(originalRequest: Request, mCall: Call) : AbsCallBack(originalR
|
|||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun waitForDone(urlRequest: UrlRequest): Response {
|
override fun waitForDone(urlRequest: UrlRequest): Response {
|
||||||
return responseFuture.get(mCall.timeout().timeoutNanos(), TimeUnit.NANOSECONDS)
|
urlRequest.start()
|
||||||
|
return if (mCall.timeout().timeoutNanos() > 0) {
|
||||||
|
responseFuture.get(mCall.timeout().timeoutNanos(), TimeUnit.NANOSECONDS)
|
||||||
|
} else {
|
||||||
|
return responseFuture.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRedirectReceived(
|
|
||||||
request: UrlRequest,
|
|
||||||
info: UrlResponseInfo,
|
|
||||||
newLocationUrl: String
|
|
||||||
) {
|
|
||||||
super.onRedirectReceived(request, info, newLocationUrl)
|
|
||||||
if (mException != null) {
|
|
||||||
responseFuture.completeExceptionally(mException)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
override fun onReadCompleted(
|
* 当发生错误时,通知子类终止阻塞抛出错误
|
||||||
request: UrlRequest,
|
* @param error
|
||||||
info: UrlResponseInfo,
|
*/
|
||||||
byteBuffer: ByteBuffer
|
override fun onError(error: IOException) {
|
||||||
) {
|
responseFuture.completeExceptionally(error)
|
||||||
super.onReadCompleted(request, info, byteBuffer)
|
|
||||||
if (mException != null) {
|
|
||||||
responseFuture.completeExceptionally(mException)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
/**
|
||||||
super.onSucceeded(request, info)
|
* 请求成功后,通知子类结束阻塞,返回response
|
||||||
responseFuture.complete(mResponse)
|
* @param response
|
||||||
}
|
*/
|
||||||
|
override fun onSuccess(response: Response) {
|
||||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
responseFuture.complete(response)
|
||||||
super.onFailed(request, info, error)
|
|
||||||
responseFuture.completeExceptionally(mException)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
|
||||||
super.onCanceled(request, info)
|
|
||||||
responseFuture.completeExceptionally(mException)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,20 +4,19 @@ import android.os.ConditionVariable
|
|||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.chromium.net.CronetException
|
|
||||||
import org.chromium.net.UrlRequest
|
import org.chromium.net.UrlRequest
|
||||||
import org.chromium.net.UrlResponseInfo
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
class OldCallback(originalRequest: Request, mCall: Call) : AbsCallBack(originalRequest, mCall) {
|
class OldCallback(originalRequest: Request, mCall: Call) : AbsCallBack(originalRequest, mCall) {
|
||||||
|
|
||||||
private val mResponseCondition = ConditionVariable()
|
private val mResponseCondition = ConditionVariable()
|
||||||
|
private var mException: IOException? = null
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun waitForDone(urlRequest: UrlRequest): Response {
|
override fun waitForDone(urlRequest: UrlRequest): Response {
|
||||||
//获取okhttp call的完整请求的超时时间
|
//获取okhttp call的完整请求的超时时间
|
||||||
val timeOutMs: Long = mCall.timeout().timeoutNanos() / 1000000
|
val timeOutMs: Long = mCall.timeout().timeoutNanos() / 1000000
|
||||||
|
urlRequest.start()
|
||||||
if (timeOutMs > 0) {
|
if (timeOutMs > 0) {
|
||||||
mResponseCondition.block(timeOutMs)
|
mResponseCondition.block(timeOutMs)
|
||||||
} else {
|
} else {
|
||||||
@@ -32,35 +31,25 @@ class OldCallback(originalRequest: Request, mCall: Call) : AbsCallBack(originalR
|
|||||||
if (mException != null) {
|
if (mException != null) {
|
||||||
throw mException as IOException
|
throw mException as IOException
|
||||||
}
|
}
|
||||||
return this.mResponse
|
return mResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
override fun onReadCompleted(
|
* 当发生错误时,通知子类终止阻塞抛出错误
|
||||||
request: UrlRequest,
|
* @param error
|
||||||
info: UrlResponseInfo,
|
*/
|
||||||
byteBuffer: ByteBuffer
|
override fun onError(error: IOException) {
|
||||||
) {
|
mException = error
|
||||||
super.onReadCompleted(request, info, byteBuffer)
|
|
||||||
if (mException != null) {
|
|
||||||
mResponseCondition.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
|
||||||
super.onSucceeded(request, info)
|
|
||||||
mResponseCondition.open()
|
mResponseCondition.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
/**
|
||||||
super.onFailed(request, info, error)
|
* 请求成功后,通知子类结束阻塞,返回response
|
||||||
|
* @param response
|
||||||
|
*/
|
||||||
|
override fun onSuccess(response: Response) {
|
||||||
mResponseCondition.open()
|
mResponseCondition.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
|
||||||
super.onCanceled(request, info)
|
|
||||||
mResponseCondition.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,9 +5,10 @@ import android.view.View
|
|||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseDialogFragment
|
import io.legado.app.base.BaseDialogFragment
|
||||||
import io.legado.app.databinding.DialogPhotoViewBinding
|
import io.legado.app.databinding.DialogPhotoViewBinding
|
||||||
|
import io.legado.app.help.BookHelp
|
||||||
|
import io.legado.app.help.glide.ImageLoader
|
||||||
import io.legado.app.model.BookCover
|
import io.legado.app.model.BookCover
|
||||||
import io.legado.app.model.ReadBook
|
import io.legado.app.model.ReadBook
|
||||||
import io.legado.app.ui.book.read.page.provider.ImageProvider
|
|
||||||
import io.legado.app.utils.setLayout
|
import io.legado.app.utils.setLayout
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||||
|
|
||||||
@@ -40,17 +41,12 @@ class PhotoDialog() : BaseDialogFragment(R.layout.dialog_photo_view) {
|
|||||||
arguments?.let {
|
arguments?.let {
|
||||||
val path = it.getString("path")
|
val path = it.getString("path")
|
||||||
if (path.isNullOrEmpty()) {
|
if (path.isNullOrEmpty()) {
|
||||||
val chapterIndex = it.getInt("chapterIndex")
|
|
||||||
val src = it.getString("src")
|
|
||||||
ReadBook.book?.let { book ->
|
ReadBook.book?.let { book ->
|
||||||
src?.let {
|
it.getString("src")?.let { src ->
|
||||||
execute {
|
val file = BookHelp.getImage(book, src)
|
||||||
ImageProvider.getImage(book, src, ReadBook.bookSource)
|
ImageLoader.load(requireContext(), file)
|
||||||
}.onSuccess { bitmap ->
|
.error(R.drawable.image_loading_error)
|
||||||
if (bitmap != null) {
|
.into(binding.photoView)
|
||||||
binding.photoView.setImageBitmap(bitmap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -82,43 +82,36 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
private fun drawPage(canvas: Canvas) {
|
private fun drawPage(canvas: Canvas) {
|
||||||
var relativeOffset = relativeOffset(0)
|
var relativeOffset = relativeOffset(0)
|
||||||
textPage.textLines.forEach { textLine ->
|
textPage.textLines.forEach { textLine ->
|
||||||
draw(canvas, textLine, relativeOffset)
|
draw(canvas, textPage, textLine, relativeOffset)
|
||||||
}
|
}
|
||||||
if (!callBack.isScroll) return
|
if (!callBack.isScroll) return
|
||||||
//滚动翻页
|
//滚动翻页
|
||||||
if (!pageFactory.hasNext()) return
|
if (!pageFactory.hasNext()) return
|
||||||
val nextPage = relativePage(1)
|
val textPage1 = relativePage(1)
|
||||||
relativeOffset = relativeOffset(1)
|
relativeOffset = relativeOffset(1)
|
||||||
nextPage.textLines.forEach { textLine ->
|
textPage1.textLines.forEach { textLine ->
|
||||||
draw(canvas, textLine, relativeOffset)
|
draw(canvas, textPage1, textLine, relativeOffset)
|
||||||
}
|
}
|
||||||
if (!pageFactory.hasNextPlus()) return
|
if (!pageFactory.hasNextPlus()) return
|
||||||
relativeOffset = relativeOffset(2)
|
relativeOffset = relativeOffset(2)
|
||||||
if (relativeOffset < ChapterProvider.visibleHeight) {
|
if (relativeOffset < ChapterProvider.visibleHeight) {
|
||||||
relativePage(2).textLines.forEach { textLine ->
|
val textPage2 = relativePage(2)
|
||||||
draw(canvas, textLine, relativeOffset)
|
textPage2.textLines.forEach { textLine ->
|
||||||
|
draw(canvas, textPage2, textLine, relativeOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun draw(
|
private fun draw(
|
||||||
canvas: Canvas,
|
canvas: Canvas,
|
||||||
|
textPage: TextPage,
|
||||||
textLine: TextLine,
|
textLine: TextLine,
|
||||||
relativeOffset: Float,
|
relativeOffset: Float,
|
||||||
) {
|
) {
|
||||||
val lineTop = textLine.lineTop + relativeOffset
|
val lineTop = textLine.lineTop + relativeOffset
|
||||||
val lineBase = textLine.lineBase + relativeOffset
|
val lineBase = textLine.lineBase + relativeOffset
|
||||||
val lineBottom = textLine.lineBottom + relativeOffset
|
val lineBottom = textLine.lineBottom + relativeOffset
|
||||||
drawChars(
|
drawChars(canvas, textPage, textLine, lineTop, lineBase, lineBottom)
|
||||||
canvas,
|
|
||||||
textLine.textChars,
|
|
||||||
lineTop,
|
|
||||||
lineBase,
|
|
||||||
lineBottom,
|
|
||||||
textLine.isTitle,
|
|
||||||
textLine.isReadAloud,
|
|
||||||
textLine.isImage
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,23 +119,21 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
*/
|
*/
|
||||||
private fun drawChars(
|
private fun drawChars(
|
||||||
canvas: Canvas,
|
canvas: Canvas,
|
||||||
textChars: List<TextChar>,
|
textPage: TextPage,
|
||||||
|
textLine: TextLine,
|
||||||
lineTop: Float,
|
lineTop: Float,
|
||||||
lineBase: Float,
|
lineBase: Float,
|
||||||
lineBottom: Float,
|
lineBottom: Float,
|
||||||
isTitle: Boolean,
|
|
||||||
isReadAloud: Boolean,
|
|
||||||
isImageLine: Boolean
|
|
||||||
) {
|
) {
|
||||||
val textPaint = if (isTitle) {
|
val textPaint = if (textLine.isTitle) {
|
||||||
ChapterProvider.titlePaint
|
ChapterProvider.titlePaint
|
||||||
} else {
|
} else {
|
||||||
ChapterProvider.contentPaint
|
ChapterProvider.contentPaint
|
||||||
}
|
}
|
||||||
val textColor = if (isReadAloud) context.accentColor else ReadBookConfig.textColor
|
val textColor = if (textLine.isReadAloud) context.accentColor else ReadBookConfig.textColor
|
||||||
textChars.forEach {
|
textLine.textChars.forEach {
|
||||||
if (it.isImage) {
|
if (it.isImage) {
|
||||||
drawImage(canvas, it, lineTop, lineBottom, isImageLine)
|
drawImage(canvas, textPage, textLine, it, lineTop, lineBottom)
|
||||||
} else {
|
} else {
|
||||||
textPaint.color = textColor
|
textPaint.color = textColor
|
||||||
if (it.isSearchResult) {
|
if (it.isSearchResult) {
|
||||||
@@ -159,22 +150,23 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
/**
|
/**
|
||||||
* 绘制图片
|
* 绘制图片
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun drawImage(
|
private fun drawImage(
|
||||||
canvas: Canvas,
|
canvas: Canvas,
|
||||||
|
textPage: TextPage,
|
||||||
|
textLine: TextLine,
|
||||||
textChar: TextChar,
|
textChar: TextChar,
|
||||||
lineTop: Float,
|
lineTop: Float,
|
||||||
lineBottom: Float,
|
lineBottom: Float
|
||||||
isImageLine: Boolean
|
|
||||||
) {
|
) {
|
||||||
val book = ReadBook.book ?: return
|
val book = ReadBook.book ?: return
|
||||||
ImageProvider.getImage(
|
val bitmap = ImageProvider.getImage(
|
||||||
book,
|
book,
|
||||||
textChar.charData,
|
textChar.charData,
|
||||||
ReadBook.bookSource,
|
|
||||||
(textChar.end - textChar.start).toInt(),
|
(textChar.end - textChar.start).toInt(),
|
||||||
(lineBottom - lineTop).toInt()
|
(lineBottom - lineTop).toInt()
|
||||||
)?.let { bitmap ->
|
)
|
||||||
val rectF = if (isImageLine) {
|
val rectF = if (textLine.isImage) {
|
||||||
RectF(textChar.start, lineTop, textChar.end, lineBottom)
|
RectF(textChar.start, lineTop, textChar.end, lineBottom)
|
||||||
} else {
|
} else {
|
||||||
/*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/
|
/*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/
|
||||||
@@ -188,7 +180,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
context.toastOnUi(e.localizedMessage)
|
context.toastOnUi(e.localizedMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 滚动事件
|
* 滚动事件
|
||||||
|
|||||||
@@ -208,22 +208,23 @@ object ChapterProvider {
|
|||||||
imageStyle: String?,
|
imageStyle: String?,
|
||||||
): Float {
|
): Float {
|
||||||
var durY = y
|
var durY = y
|
||||||
ImageProvider.getImageSize(book, src, ReadBook.bookSource).let {
|
val size = ImageProvider.getImageSize(book, src, ReadBook.bookSource)
|
||||||
|
if (size.width > 0 && size.height > 0) {
|
||||||
if (durY > visibleHeight) {
|
if (durY > visibleHeight) {
|
||||||
textPages.last().height = durY
|
textPages.last().height = durY
|
||||||
textPages.add(TextPage())
|
textPages.add(TextPage())
|
||||||
durY = 0f
|
durY = 0f
|
||||||
}
|
}
|
||||||
var height = it.height
|
var height = size.height
|
||||||
var width = it.width
|
var width = size.width
|
||||||
when (imageStyle?.toUpperCase(Locale.ROOT)) {
|
when (imageStyle?.toUpperCase(Locale.ROOT)) {
|
||||||
Book.imgStyleFull -> {
|
Book.imgStyleFull -> {
|
||||||
width = visibleWidth
|
width = visibleWidth
|
||||||
height = it.height * visibleWidth / it.width
|
height = size.height * visibleWidth / size.width
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
if (it.width > visibleWidth) {
|
if (size.width > visibleWidth) {
|
||||||
height = it.height * visibleWidth / it.width
|
height = size.height * visibleWidth / size.width
|
||||||
width = visibleWidth
|
width = visibleWidth
|
||||||
}
|
}
|
||||||
if (height > visibleHeight) {
|
if (height > visibleHeight) {
|
||||||
|
|||||||
@@ -9,16 +9,13 @@ import io.legado.app.data.entities.Book
|
|||||||
import io.legado.app.data.entities.BookSource
|
import io.legado.app.data.entities.BookSource
|
||||||
import io.legado.app.help.BookHelp
|
import io.legado.app.help.BookHelp
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
import io.legado.app.help.glide.ImageLoader
|
|
||||||
import io.legado.app.model.localBook.EpubFile
|
import io.legado.app.model.localBook.EpubFile
|
||||||
|
import io.legado.app.utils.BitmapUtils
|
||||||
import io.legado.app.utils.FileUtils
|
import io.legado.app.utils.FileUtils
|
||||||
import io.legado.app.utils.isXml
|
import io.legado.app.utils.isXml
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import kotlin.coroutines.resume
|
|
||||||
|
|
||||||
object ImageProvider {
|
object ImageProvider {
|
||||||
|
|
||||||
@@ -26,7 +23,7 @@ object ImageProvider {
|
|||||||
BitmapFactory.decodeResource(appCtx.resources, R.drawable.image_loading_error)
|
BitmapFactory.decodeResource(appCtx.resources, R.drawable.image_loading_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun cacheImage(
|
suspend fun cacheImage(
|
||||||
book: Book,
|
book: Book,
|
||||||
src: String,
|
src: String,
|
||||||
bookSource: BookSource?
|
bookSource: BookSource?
|
||||||
@@ -54,59 +51,26 @@ object ImageProvider {
|
|||||||
bookSource: BookSource?
|
bookSource: BookSource?
|
||||||
): Size {
|
): Size {
|
||||||
val file = cacheImage(book, src, bookSource)
|
val file = cacheImage(book, src, bookSource)
|
||||||
return suspendCancellableCoroutine { block ->
|
val op = BitmapFactory.Options()
|
||||||
kotlin.runCatching {
|
// inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;
|
||||||
ImageLoader.loadBitmap(appCtx, file.absolutePath).submit()
|
op.inJustDecodeBounds = true
|
||||||
.getSize { width, height ->
|
BitmapFactory.decodeFile(file.absolutePath, op)
|
||||||
block.resume(Size(width, height))
|
return Size(op.outWidth, op.outHeight)
|
||||||
}
|
|
||||||
}.onFailure {
|
|
||||||
block.resume(Size(errorBitmap.width, errorBitmap.height))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getImage(
|
fun getImage(
|
||||||
book: Book,
|
book: Book,
|
||||||
src: String,
|
src: String,
|
||||||
bookSource: BookSource?,
|
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int
|
height: Int
|
||||||
): Bitmap? {
|
): Bitmap {
|
||||||
val vFile = runBlocking {
|
val vFile = BookHelp.getImage(book, src)
|
||||||
cacheImage(book, src, bookSource)
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
}
|
|
||||||
return try {
|
return try {
|
||||||
ImageLoader.loadBitmap(appCtx, vFile.absolutePath)
|
BitmapUtils.decodeBitmap(vFile.absolutePath, width, height)
|
||||||
.submit(width, height)
|
|
||||||
.get()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Coroutine.async {
|
Coroutine.async {
|
||||||
putDebug("${vFile.absolutePath} 解码失败\n${e.toString()}", e)
|
putDebug("${vFile.absolutePath} 解码失败\n$e", e)
|
||||||
if (FileUtils.readText(vFile.absolutePath).isXml()) {
|
|
||||||
putDebug("${vFile.absolutePath}为xml,自动删除")
|
|
||||||
vFile.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errorBitmap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getImage(
|
|
||||||
book: Book,
|
|
||||||
src: String,
|
|
||||||
bookSource: BookSource?
|
|
||||||
): Bitmap? {
|
|
||||||
val vFile = runBlocking {
|
|
||||||
cacheImage(book, src, bookSource)
|
|
||||||
}
|
|
||||||
return try {
|
|
||||||
ImageLoader.loadBitmap(appCtx, vFile.absolutePath)
|
|
||||||
.submit(ChapterProvider.visibleWidth, ChapterProvider.visibleHeight)
|
|
||||||
.get()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Coroutine.async {
|
|
||||||
putDebug("${vFile.absolutePath} 解码失败\n${e.toString()}", e)
|
|
||||||
if (FileUtils.readText(vFile.absolutePath).isXml()) {
|
if (FileUtils.readText(vFile.absolutePath).isXml()) {
|
||||||
putDebug("${vFile.absolutePath}为xml,自动删除")
|
putDebug("${vFile.absolutePath}为xml,自动删除")
|
||||||
vFile.delete()
|
vFile.delete()
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.graphics.*
|
|||||||
import android.graphics.Bitmap.Config
|
import android.graphics.Bitmap.Config
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.google.android.renderscript.Toolkit
|
import com.google.android.renderscript.Toolkit
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
@@ -27,10 +26,9 @@ object BitmapUtils {
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun decodeBitmap(path: String, width: Int, height: Int): Bitmap {
|
fun decodeBitmap(path: String, width: Int, height: Int): Bitmap {
|
||||||
val op = BitmapFactory.Options()
|
val op = BitmapFactory.Options()
|
||||||
val ips = FileInputStream(path)
|
|
||||||
// inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;
|
// inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;
|
||||||
op.inJustDecodeBounds = true
|
op.inJustDecodeBounds = true
|
||||||
BitmapFactory.decodeFileDescriptor(ips.fd, null, op)
|
BitmapFactory.decodeFile(path, op)
|
||||||
//获取比例大小
|
//获取比例大小
|
||||||
val wRatio = ceil((op.outWidth / width).toDouble()).toInt()
|
val wRatio = ceil((op.outWidth / width).toDouble()).toInt()
|
||||||
val hRatio = ceil((op.outHeight / height).toDouble()).toInt()
|
val hRatio = ceil((op.outHeight / height).toDouble()).toInt()
|
||||||
@@ -43,7 +41,7 @@ object BitmapUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
op.inJustDecodeBounds = false
|
op.inJustDecodeBounds = false
|
||||||
return BitmapFactory.decodeFileDescriptor(ips.fd, null, op)
|
return BitmapFactory.decodeFile(path, op)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 从path中获取Bitmap图片
|
/** 从path中获取Bitmap图片
|
||||||
@@ -53,12 +51,11 @@ object BitmapUtils {
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun decodeBitmap(path: String): Bitmap {
|
fun decodeBitmap(path: String): Bitmap {
|
||||||
val opts = BitmapFactory.Options()
|
val opts = BitmapFactory.Options()
|
||||||
val ips = FileInputStream(path)
|
|
||||||
opts.inJustDecodeBounds = true
|
opts.inJustDecodeBounds = true
|
||||||
BitmapFactory.decodeFileDescriptor(ips.fd, null, opts)
|
BitmapFactory.decodeFile(path, opts)
|
||||||
opts.inSampleSize = computeSampleSize(opts, -1, 128 * 128)
|
opts.inSampleSize = computeSampleSize(opts, -1, 128 * 128)
|
||||||
opts.inJustDecodeBounds = false
|
opts.inJustDecodeBounds = false
|
||||||
return BitmapFactory.decodeFileDescriptor(ips.fd, null, opts)
|
return BitmapFactory.decodeFile(path, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ val Throwable.msg: String
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Throwable.rethrowAsIOException(): IOException {
|
fun Throwable.asIOException(): IOException {
|
||||||
val newException = IOException(this.message)
|
val newException = IOException(this.message)
|
||||||
newException.initCause(this)
|
newException.initCause(this)
|
||||||
throw newException
|
return newException
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user