@@ -203,7 +203,7 @@ h2.head {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
margin: -3em 2em 2em 0;
|
margin: 1em 2em 2em 0;
|
||||||
color: #3f83e8;
|
color: #3f83e8;
|
||||||
line-height: 140%;
|
line-height: 140%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ object BookController {
|
|||||||
this.bookUrl = bookUrl
|
this.bookUrl = bookUrl
|
||||||
val bitmap = runBlocking {
|
val bitmap = runBlocking {
|
||||||
ImageProvider.cacheImage(book, src, bookSource)
|
ImageProvider.cacheImage(book, src, bookSource)
|
||||||
ImageProvider.getImage(book, src, width)
|
ImageProvider.getImage(book, src, width)!!
|
||||||
}
|
}
|
||||||
return returnData.setData(bitmap)
|
return returnData.setData(bitmap)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ 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.lib.webdav.WebDavException
|
import io.legado.app.lib.webdav.WebDavException
|
||||||
import io.legado.app.lib.webdav.WebDavFile
|
import io.legado.app.lib.webdav.WebDavFile
|
||||||
|
import io.legado.app.ui.widget.dialog.WaitDialog
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
@@ -109,10 +110,19 @@ object AppWebDav {
|
|||||||
items = names
|
items = names
|
||||||
) { _, index ->
|
) { _, index ->
|
||||||
if (index in 0 until names.size) {
|
if (index in 0 until names.size) {
|
||||||
Coroutine.async {
|
val waitDialog = WaitDialog(context)
|
||||||
|
waitDialog.setText("恢复中…")
|
||||||
|
waitDialog.show()
|
||||||
|
val task = Coroutine.async {
|
||||||
restoreWebDav(names[index])
|
restoreWebDav(names[index])
|
||||||
}.onError {
|
}.onError {
|
||||||
|
AppLog.put("WebDav恢复出错\n${it.localizedMessage}", it)
|
||||||
appCtx.toastOnUi("WebDav恢复出错\n${it.localizedMessage}")
|
appCtx.toastOnUi("WebDav恢复出错\n${it.localizedMessage}")
|
||||||
|
}.onFinally(Main) {
|
||||||
|
waitDialog.dismiss()
|
||||||
|
}
|
||||||
|
waitDialog.setOnCancelListener {
|
||||||
|
task.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,7 +196,7 @@ object AppWebDav {
|
|||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val msg = "WebDav导出\n${e.localizedMessage}"
|
val msg = "WebDav导出\n${e.localizedMessage}"
|
||||||
AppLog.put(msg)
|
AppLog.put(msg, e)
|
||||||
appCtx.toastOnUi(msg)
|
appCtx.toastOnUi(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,7 +211,7 @@ object AppWebDav {
|
|||||||
val url = getProgressUrl(book.name, book.author)
|
val url = getProgressUrl(book.name, book.author)
|
||||||
WebDav(url, authorization).upload(json.toByteArray(), "application/json")
|
WebDav(url, authorization).upload(json.toByteArray(), "application/json")
|
||||||
}.onError {
|
}.onError {
|
||||||
AppLog.put("上传进度失败\n${it.localizedMessage}")
|
AppLog.put("上传进度失败\n${it.localizedMessage}", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +224,7 @@ object AppWebDav {
|
|||||||
val url = getProgressUrl(bookProgress.name, bookProgress.author)
|
val url = getProgressUrl(bookProgress.name, bookProgress.author)
|
||||||
WebDav(url, authorization).upload(json.toByteArray(), "application/json")
|
WebDav(url, authorization).upload(json.toByteArray(), "application/json")
|
||||||
}.onError {
|
}.onError {
|
||||||
AppLog.put("上传进度失败\n${it.localizedMessage}")
|
AppLog.put("上传进度失败\n${it.localizedMessage}", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ open class WebDav(val path: String, val authorization: Authorization) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
AppLog.put("WebDav创建目录失败\n${it.localizedMessage}")
|
AppLog.put("WebDav创建目录失败\n${it.localizedMessage}", it)
|
||||||
}.isSuccess
|
}.isSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +286,7 @@ open class WebDav(val path: String, val authorization: Authorization) {
|
|||||||
checkResult(it)
|
checkResult(it)
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
AppLog.put("WebDav上传失败\n${it.localizedMessage}", it)
|
||||||
throw WebDavException("WebDav上传失败\n${it.localizedMessage}")
|
throw WebDavException("WebDav上传失败\n${it.localizedMessage}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,12 +304,13 @@ open class WebDav(val path: String, val authorization: Authorization) {
|
|||||||
checkResult(it)
|
checkResult(it)
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
AppLog.put("WebDav上传失败\n${it.localizedMessage}", it)
|
||||||
throw WebDavException("WebDav上传失败\n${it.localizedMessage}")
|
throw WebDavException("WebDav上传失败\n${it.localizedMessage}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(WebDavException::class)
|
@Throws(WebDavException::class)
|
||||||
private suspend fun downloadInputStream(): InputStream {
|
suspend fun downloadInputStream(): InputStream {
|
||||||
val url = httpUrl ?: throw WebDavException("WebDav下载出错\nurl为空")
|
val url = httpUrl ?: throw WebDavException("WebDav下载出错\nurl为空")
|
||||||
val byteStream = webDavClient.newCallResponse {
|
val byteStream = webDavClient.newCallResponse {
|
||||||
url(url)
|
url(url)
|
||||||
@@ -332,7 +334,7 @@ open class WebDav(val path: String, val authorization: Authorization) {
|
|||||||
checkResult(it)
|
checkResult(it)
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
AppLog.put("WebDav删除失败\n${it.localizedMessage}")
|
AppLog.put("WebDav删除失败\n${it.localizedMessage}", it)
|
||||||
}.isSuccess
|
}.isSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.InputStream
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
@@ -497,6 +499,52 @@ class AnalyzeUrl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问网站,返回InputStream
|
||||||
|
*/
|
||||||
|
suspend fun getInputStreamAwait(): InputStream {
|
||||||
|
val concurrentRecord = fetchStart()
|
||||||
|
|
||||||
|
@Suppress("RegExpRedundantEscape")
|
||||||
|
val dataUriFindResult = dataUriRegex.find(urlNoQuery)
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
if (dataUriFindResult != null) {
|
||||||
|
val dataUriBase64 = dataUriFindResult.groupValues[1]
|
||||||
|
val byteArray = Base64.decode(dataUriBase64, Base64.DEFAULT)
|
||||||
|
fetchEnd(concurrentRecord)
|
||||||
|
return ByteArrayInputStream(byteArray)
|
||||||
|
} else {
|
||||||
|
setCookie(source?.getKey())
|
||||||
|
val inputStream = getProxyClient(proxy).newCallResponseBody(retry) {
|
||||||
|
addHeaders(headerMap)
|
||||||
|
when (method) {
|
||||||
|
RequestMethod.POST -> {
|
||||||
|
url(urlNoQuery)
|
||||||
|
val contentType = headerMap["Content-Type"]
|
||||||
|
val body = body
|
||||||
|
if (fieldMap.isNotEmpty() || body.isNullOrBlank()) {
|
||||||
|
postForm(fieldMap, true)
|
||||||
|
} else if (!contentType.isNullOrBlank()) {
|
||||||
|
val requestBody = body.toRequestBody(contentType.toMediaType())
|
||||||
|
post(requestBody)
|
||||||
|
} else {
|
||||||
|
postJson(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> get(urlNoQuery, fieldMap, true)
|
||||||
|
}
|
||||||
|
}.byteStream()
|
||||||
|
fetchEnd(concurrentRecord)
|
||||||
|
return inputStream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInputStream(): InputStream {
|
||||||
|
return runBlocking {
|
||||||
|
getInputStreamAwait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传文件
|
* 上传文件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package io.legado.app.model.localBook
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
|
import io.legado.app.constant.AppLog
|
||||||
import io.legado.app.data.entities.Book
|
import io.legado.app.data.entities.Book
|
||||||
import io.legado.app.data.entities.BookChapter
|
import io.legado.app.data.entities.BookChapter
|
||||||
import io.legado.app.help.BookHelp
|
import io.legado.app.help.BookHelp
|
||||||
@@ -95,6 +96,7 @@ class EpubFile(var book: Book) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
AppLog.put("加载书籍封面失败\n${e.localizedMessage}", e)
|
||||||
e.printOnDebug()
|
e.printOnDebug()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,6 +110,7 @@ class EpubFile(var book: Book) {
|
|||||||
val zipFile = BookHelp.getEpubFile(book)
|
val zipFile = BookHelp.getEpubFile(book)
|
||||||
EpubReader().readEpubLazy(zipFile, "utf-8")
|
EpubReader().readEpubLazy(zipFile, "utf-8")
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
AppLog.put("读取Epub文件失败\n${it.localizedMessage}", it)
|
||||||
it.printOnDebug()
|
it.printOnDebug()
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,8 +235,8 @@ object LocalBook {
|
|||||||
AppConfig.defaultBookTreeUri
|
AppConfig.defaultBookTreeUri
|
||||||
?: throw NoStackTraceException("没有设置书籍保存位置!")
|
?: throw NoStackTraceException("没有设置书籍保存位置!")
|
||||||
val bytes = when {
|
val bytes = when {
|
||||||
str.isAbsUrl() -> AnalyzeUrl(str, source = source).getByteArray()
|
str.isAbsUrl() -> AnalyzeUrl(str, source = source).getInputStream()
|
||||||
str.isDataUrl() -> Base64.decode(str.substringAfter("base64,"), Base64.DEFAULT)
|
str.isDataUrl() -> ByteArrayInputStream(Base64.decode(str.substringAfter("base64,"), Base64.DEFAULT))
|
||||||
else -> throw NoStackTraceException("在线导入书籍支持http/https/DataURL")
|
else -> throw NoStackTraceException("在线导入书籍支持http/https/DataURL")
|
||||||
}
|
}
|
||||||
return saveBookFile(bytes, fileName)
|
return saveBookFile(bytes, fileName)
|
||||||
@@ -257,7 +257,7 @@ object LocalBook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun saveBookFile(
|
fun saveBookFile(
|
||||||
bytes: ByteArray,
|
inputStream: InputStream,
|
||||||
fileName: String
|
fileName: String
|
||||||
): Uri {
|
): Uri {
|
||||||
val defaultBookTreeUri = AppConfig.defaultBookTreeUri
|
val defaultBookTreeUri = AppConfig.defaultBookTreeUri
|
||||||
@@ -271,14 +271,14 @@ object LocalBook {
|
|||||||
?: throw SecurityException("Permission Denial")
|
?: throw SecurityException("Permission Denial")
|
||||||
}
|
}
|
||||||
appCtx.contentResolver.openOutputStream(doc.uri)!!.use { oStream ->
|
appCtx.contentResolver.openOutputStream(doc.uri)!!.use { oStream ->
|
||||||
oStream.write(bytes)
|
inputStream.copyTo(oStream)
|
||||||
}
|
}
|
||||||
doc.uri
|
doc.uri
|
||||||
} else {
|
} else {
|
||||||
val treeFile = File(treeUri.path!!)
|
val treeFile = File(treeUri.path!!)
|
||||||
val file = treeFile.getFile(fileName)
|
val file = treeFile.getFile(fileName)
|
||||||
FileOutputStream(file).use { oStream ->
|
FileOutputStream(file).use { oStream ->
|
||||||
oStream.write(bytes)
|
inputStream.copyTo(oStream)
|
||||||
}
|
}
|
||||||
Uri.fromFile(file)
|
Uri.fromFile(file)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
alertSync?.invoke(progress)
|
alertSync?.invoke(progress)
|
||||||
} else {
|
} else {
|
||||||
ReadBook.setProgress(progress)
|
ReadBook.setProgress(progress)
|
||||||
|
context.toastOnUi("自动同步阅读进度成功")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.graphics.RectF
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
|
import io.legado.app.constant.AppLog
|
||||||
import io.legado.app.constant.PreferKey
|
import io.legado.app.constant.PreferKey
|
||||||
import io.legado.app.data.entities.Bookmark
|
import io.legado.app.data.entities.Bookmark
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
@@ -44,6 +45,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
val selectEnd = TextPos(0, 0, 0)
|
val selectEnd = TextPos(0, 0, 0)
|
||||||
var textPage: TextPage = TextPage()
|
var textPage: TextPage = TextPage()
|
||||||
private set
|
private set
|
||||||
|
private var drawVisibleImageOnly = false
|
||||||
|
private var cacheIncreased = false
|
||||||
|
private val increaseSize = 8 * 1024 * 1024
|
||||||
|
private val maxCacheSize = 256 * 1024 * 1024
|
||||||
|
|
||||||
//滚动参数
|
//滚动参数
|
||||||
private val pageFactory: TextPageFactory get() = callBack.pageFactory
|
private val pageFactory: TextPageFactory get() = callBack.pageFactory
|
||||||
@@ -86,6 +91,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
super.onDraw(canvas)
|
super.onDraw(canvas)
|
||||||
canvas.clipRect(visibleRect)
|
canvas.clipRect(visibleRect)
|
||||||
drawPage(canvas)
|
drawPage(canvas)
|
||||||
|
drawVisibleImageOnly = false
|
||||||
|
cacheIncreased = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -173,12 +180,40 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
val book = ReadBook.book ?: return
|
val book = ReadBook.book ?: return
|
||||||
|
val isVisible = when {
|
||||||
|
lineTop > 0 -> lineTop < height
|
||||||
|
lineTop < 0 -> lineBottom > 0
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
if (drawVisibleImageOnly && !isVisible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (drawVisibleImageOnly &&
|
||||||
|
isVisible &&
|
||||||
|
!cacheIncreased &&
|
||||||
|
ImageProvider.isTriggerRecycled() &&
|
||||||
|
!ImageProvider.isImageAlive(book, textChar.charData)
|
||||||
|
) {
|
||||||
|
val newSize = ImageProvider.bitmapLruCache.maxSize() + increaseSize
|
||||||
|
if (newSize < maxCacheSize) {
|
||||||
|
ImageProvider.bitmapLruCache.resize(newSize)
|
||||||
|
AppLog.put("图片缓存不够大,自动扩增至${(newSize / 1024 / 1024)}MB。")
|
||||||
|
cacheIncreased = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
val bitmap = ImageProvider.getImage(
|
val bitmap = ImageProvider.getImage(
|
||||||
book,
|
book,
|
||||||
textChar.charData,
|
textChar.charData,
|
||||||
(textChar.end - textChar.start).toInt(),
|
(textChar.end - textChar.start).toInt(),
|
||||||
(lineBottom - lineTop).toInt()
|
(lineBottom - lineTop).toInt()
|
||||||
)
|
) {
|
||||||
|
if (!drawVisibleImageOnly && isVisible) {
|
||||||
|
drawVisibleImageOnly = true
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
} ?: return
|
||||||
|
|
||||||
val rectF = if (textLine.isImage) {
|
val rectF = if (textLine.isImage) {
|
||||||
RectF(textChar.start, lineTop, textChar.end, lineBottom)
|
RectF(textChar.start, lineTop, textChar.end, lineBottom)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,15 +6,18 @@ import android.util.Size
|
|||||||
import androidx.collection.LruCache
|
import androidx.collection.LruCache
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.constant.AppLog.putDebug
|
import io.legado.app.constant.AppLog.putDebug
|
||||||
|
import io.legado.app.constant.PageAnim
|
||||||
import io.legado.app.data.entities.Book
|
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.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.BookHelp
|
import io.legado.app.help.BookHelp
|
||||||
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.model.ReadBook
|
||||||
import io.legado.app.model.localBook.EpubFile
|
import io.legado.app.model.localBook.EpubFile
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -32,6 +35,7 @@ object ImageProvider {
|
|||||||
*/
|
*/
|
||||||
private const val M = 1024 * 1024
|
private const val M = 1024 * 1024
|
||||||
val cacheSize get() = AppConfig.bitmapCacheSize * M
|
val cacheSize get() = AppConfig.bitmapCacheSize * M
|
||||||
|
var triggerRecycled = false
|
||||||
val bitmapLruCache = object : LruCache<String, Bitmap>(cacheSize) {
|
val bitmapLruCache = object : LruCache<String, Bitmap>(cacheSize) {
|
||||||
|
|
||||||
override fun sizeOf(filePath: String, bitmap: Bitmap): Int {
|
override fun sizeOf(filePath: String, bitmap: Bitmap): Int {
|
||||||
@@ -47,6 +51,7 @@ object ImageProvider {
|
|||||||
//错误图片不能释放,占位用,防止一直重复获取图片
|
//错误图片不能释放,占位用,防止一直重复获取图片
|
||||||
if (oldBitmap != errorBitmap) {
|
if (oldBitmap != errorBitmap) {
|
||||||
oldBitmap.recycle()
|
oldBitmap.recycle()
|
||||||
|
triggerRecycled = true
|
||||||
putDebug("ImageProvider: trigger bitmap recycle. URI: $filePath")
|
putDebug("ImageProvider: trigger bitmap recycle. URI: $filePath")
|
||||||
putDebug("ImageProvider : cacheUsage ${size()}bytes / ${maxSize()}bytes")
|
putDebug("ImageProvider : cacheUsage ${size()}bytes / ${maxSize()}bytes")
|
||||||
}
|
}
|
||||||
@@ -110,12 +115,13 @@ object ImageProvider {
|
|||||||
book: Book,
|
book: Book,
|
||||||
src: String,
|
src: String,
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int? = null
|
height: Int? = null,
|
||||||
): Bitmap {
|
block: (() -> Unit)? = null
|
||||||
|
): Bitmap? {
|
||||||
//src为空白时 可能被净化替换掉了 或者规则失效
|
//src为空白时 可能被净化替换掉了 或者规则失效
|
||||||
if (book.getUseReplaceRule() && src.isBlank()) {
|
if (book.getUseReplaceRule() && src.isBlank()) {
|
||||||
book.setUseReplaceRule(false)
|
book.setUseReplaceRule(false)
|
||||||
appCtx.toastOnUi(R.string.error_image_url_empty)
|
appCtx.toastOnUi(R.string.error_image_url_empty)
|
||||||
}
|
}
|
||||||
val vFile = BookHelp.getImage(book, src)
|
val vFile = BookHelp.getImage(book, src)
|
||||||
if (!vFile.exists()) return errorBitmap
|
if (!vFile.exists()) return errorBitmap
|
||||||
@@ -123,6 +129,30 @@ object ImageProvider {
|
|||||||
//bitmapLruCache的key同一改成缓存文件的路径
|
//bitmapLruCache的key同一改成缓存文件的路径
|
||||||
val cacheBitmap = bitmapLruCache.get(vFile.absolutePath)
|
val cacheBitmap = bitmapLruCache.get(vFile.absolutePath)
|
||||||
if (cacheBitmap != null) return cacheBitmap
|
if (cacheBitmap != null) return cacheBitmap
|
||||||
|
if (height != null && ReadBook.pageAnim() == PageAnim.scrollPageAnim) {
|
||||||
|
Coroutine.async {
|
||||||
|
kotlin.runCatching {
|
||||||
|
val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height)
|
||||||
|
?: throw NoStackTraceException(appCtx.getString(R.string.error_decode_bitmap))
|
||||||
|
withContext(Main) {
|
||||||
|
bitmapLruCache.put(vFile.absolutePath, bitmap)
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
//错误图片占位,防止重复获取
|
||||||
|
withContext(Main) {
|
||||||
|
bitmapLruCache.put(vFile.absolutePath, errorBitmap)
|
||||||
|
}
|
||||||
|
putDebug(
|
||||||
|
"ImageProvider: decode bitmap failed. path: ${vFile.absolutePath}\n$it",
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
withContext(Main) {
|
||||||
|
block?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
return kotlin.runCatching {
|
return kotlin.runCatching {
|
||||||
val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height)
|
val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height)
|
||||||
@@ -139,4 +169,17 @@ object ImageProvider {
|
|||||||
}.getOrDefault(errorBitmap)
|
}.getOrDefault(errorBitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isImageAlive(book: Book, src: String): Boolean {
|
||||||
|
val vFile = BookHelp.getImage(book, src)
|
||||||
|
if (!vFile.exists()) return true // 使用 errorBitmap
|
||||||
|
val cacheBitmap = bitmapLruCache.get(vFile.absolutePath)
|
||||||
|
return cacheBitmap != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isTriggerRecycled(): Boolean {
|
||||||
|
val tmp = triggerRecycled
|
||||||
|
triggerRecycled = false
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ object RemoteBookWebDav : RemoteBookManager() {
|
|||||||
override suspend fun getRemoteBook(remoteBook: RemoteBook): Uri? {
|
override suspend fun getRemoteBook(remoteBook: RemoteBook): Uri? {
|
||||||
return AppWebDav.authorization?.let {
|
return AppWebDav.authorization?.let {
|
||||||
val webdav = WebDav(remoteBook.path, it)
|
val webdav = WebDav(remoteBook.path, it)
|
||||||
webdav.download().let { bytes ->
|
webdav.downloadInputStream().let { inputStream ->
|
||||||
LocalBook.saveBookFile(bytes, remoteBook.filename)
|
LocalBook.saveBookFile(inputStream, remoteBook.filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ object HtmlFormatter {
|
|||||||
private val notImgHtmlRegex = "</?(?!img)[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
|
private val notImgHtmlRegex = "</?(?!img)[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
|
||||||
private val otherHtmlRegex = "</?[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
|
private val otherHtmlRegex = "</?[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
|
||||||
private val formatImagePattern = Pattern.compile(
|
private val formatImagePattern = Pattern.compile(
|
||||||
"<img[^>]*src *= *\"([^\"{]*\\{(?:[^{}]|\\{[^}]+\\})+\\})\"[^>]*>|<img[^>]*data-[^=]*= *\"([^\"]*)\"[^>]*>|<img[^>]*src *= *\"([^\"]*)\"[^>]*>",
|
"<img[^>]*src *= *\"([^\"{>]*\\{(?:[^{}]|\\{[^}>]+\\})+\\})\"[^>]*>|<img[^>]*data-[^=>]*= *\"([^\">]*)\"[^>]*>|<img[^>]*src *= *\"([^\">]*)\"[^>]*>",
|
||||||
Pattern.CASE_INSENSITIVE
|
Pattern.CASE_INSENSITIVE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import io.legado.app.api.ReturnData
|
|||||||
import io.legado.app.api.controller.BookController
|
import io.legado.app.api.controller.BookController
|
||||||
import io.legado.app.api.controller.BookSourceController
|
import io.legado.app.api.controller.BookSourceController
|
||||||
import io.legado.app.api.controller.RssSourceController
|
import io.legado.app.api.controller.RssSourceController
|
||||||
|
import io.legado.app.utils.FileUtils
|
||||||
|
import io.legado.app.utils.externalFiles
|
||||||
import io.legado.app.web.utils.AssetsWeb
|
import io.legado.app.web.utils.AssetsWeb
|
||||||
import java.io.ByteArrayInputStream
|
import splitties.init.appCtx
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.*
|
||||||
|
|
||||||
|
|
||||||
class HttpServer(port: Int) : NanoHTTPD(port) {
|
class HttpServer(port: Int) : NanoHTTPD(port) {
|
||||||
@@ -91,7 +93,26 @@ class HttpServer(port: Int) : NanoHTTPD(port) {
|
|||||||
byteArray.size.toLong()
|
byteArray.size.toLong()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
newFixedLengthResponse(Gson().toJson(returnData))
|
try {
|
||||||
|
newFixedLengthResponse(Gson().toJson(returnData))
|
||||||
|
} catch (e: OutOfMemoryError) {
|
||||||
|
val path = FileUtils.getPath(
|
||||||
|
appCtx.externalFiles,
|
||||||
|
"book_cache",
|
||||||
|
"bookSources.json"
|
||||||
|
)
|
||||||
|
val file = FileUtils.createFileIfNotExist(path)
|
||||||
|
BufferedWriter(FileWriter(file)).use {
|
||||||
|
Gson().toJson(returnData, it)
|
||||||
|
}
|
||||||
|
val fis = FileInputStream(file)
|
||||||
|
newFixedLengthResponse(
|
||||||
|
Response.Status.OK,
|
||||||
|
"application/json",
|
||||||
|
fis,
|
||||||
|
fis.available().toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
response.addHeader("Access-Control-Allow-Methods", "GET, POST")
|
response.addHeader("Access-Control-Allow-Methods", "GET, POST")
|
||||||
response.addHeader("Access-Control-Allow-Origin", session.headers["origin"])
|
response.addHeader("Access-Control-Allow-Origin", session.headers["origin"])
|
||||||
|
|||||||
Reference in New Issue
Block a user