Merge pull request #2239 from 821938089/little-fix

一些优化
pull/2240/head
kunfei 2 years ago committed by GitHub
commit 8fe39a6967
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/src/main/assets/epub/main.css
  2. 2
      app/src/main/java/io/legado/app/api/controller/BookController.kt
  3. 18
      app/src/main/java/io/legado/app/help/AppWebDav.kt
  4. 8
      app/src/main/java/io/legado/app/lib/webdav/WebDav.kt
  5. 48
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  6. 3
      app/src/main/java/io/legado/app/model/localBook/EpubFile.kt
  7. 10
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  8. 1
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  9. 37
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  10. 47
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt
  11. 4
      app/src/main/java/io/legado/app/ui/book/remote/manager/RemoteBookWebDav.kt
  12. 2
      app/src/main/java/io/legado/app/utils/HtmlFormatter.kt
  13. 25
      app/src/main/java/io/legado/app/web/HttpServer.kt

@ -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,8 +115,9 @@ 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)
@ -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 {
try {
newFixedLengthResponse(Gson().toJson(returnData)) 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"])

Loading…
Cancel
Save