优化
This commit is contained in:
@@ -31,6 +31,16 @@ interface BookSourceDao {
|
||||
)
|
||||
fun flowSearch(searchKey: String): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources
|
||||
where bookSourceName like '%' || :searchKey || '%'
|
||||
or bookSourceGroup like '%' || :searchKey || '%'
|
||||
or bookSourceUrl like '%' || :searchKey || '%'
|
||||
or bookSourceComment like '%' || :searchKey || '%'
|
||||
order by customOrder asc"""
|
||||
)
|
||||
fun search(searchKey: String): List<BookSource>
|
||||
|
||||
@Query(
|
||||
"""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
|
||||
@@ -56,27 +66,45 @@ interface BookSourceDao {
|
||||
)
|
||||
fun flowGroupSearch(searchKey: String): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query("""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||
@Query(
|
||||
"""select * from book_sources
|
||||
where bookSourceGroup = :searchKey
|
||||
or bookSourceGroup like :searchKey || ',%'
|
||||
or bookSourceGroup like '%,' || :searchKey
|
||||
or bookSourceGroup like '%,' || :searchKey || ',%'
|
||||
order by customOrder asc"""
|
||||
)
|
||||
fun groupSearch(searchKey: String): List<BookSource>
|
||||
|
||||
@Query(
|
||||
"""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
|
||||
from book_sources where enabled = 1 order by customOrder asc""")
|
||||
from book_sources where enabled = 1 order by customOrder asc"""
|
||||
)
|
||||
fun flowEnabled(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query("""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||
@Query(
|
||||
"""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
|
||||
from book_sources where enabled = 0 order by customOrder asc""")
|
||||
from book_sources where enabled = 0 order by customOrder asc"""
|
||||
)
|
||||
fun flowDisabled(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' order by customOrder asc")
|
||||
fun flowExplore(): Flow<List<BookSource>>
|
||||
|
||||
@Query("""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||
@Query(
|
||||
"""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
|
||||
from book_sources where loginUrl is not null and loginUrl != ''""")
|
||||
from book_sources where loginUrl is not null and loginUrl != ''"""
|
||||
)
|
||||
fun flowLogin(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query("""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||
@Query(
|
||||
"""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
|
||||
from book_sources where bookSourceGroup is null or bookSourceGroup = '' or bookSourceGroup like '%未分组%'""")
|
||||
from book_sources where bookSourceGroup is null or bookSourceGroup = '' or bookSourceGroup like '%未分组%'"""
|
||||
)
|
||||
fun flowNoGroup(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query(
|
||||
@@ -147,6 +175,15 @@ interface BookSourceDao {
|
||||
@get:Query("select * from book_sources where enabled = 1 order by customOrder")
|
||||
val allEnabled: List<BookSource>
|
||||
|
||||
@get:Query("select * from book_sources where enabled = 0 order by customOrder")
|
||||
val allDisabled: List<BookSource>
|
||||
|
||||
@get:Query("select * from book_sources where bookSourceGroup is null or bookSourceGroup = '' or bookSourceGroup like '%未分组%'")
|
||||
val allNoGroup: List<BookSource>
|
||||
|
||||
@get:Query("select * from book_sources where loginUrl is not null and loginUrl != ''")
|
||||
val allLogin: List<BookSource>
|
||||
|
||||
@get:Query("select * from book_sources where enabled = 1 and bookSourceType = 0 order by customOrder")
|
||||
val allTextEnabled: List<BookSource>
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ class ContentProcessor private constructor(
|
||||
if (matcher.find()) {
|
||||
mContent = mContent.substring(matcher.end())
|
||||
sameTitleRemoved = true
|
||||
} else if (useReplace) {
|
||||
} else if (useReplace && book.getUseReplaceRule()) {
|
||||
title = Pattern.quote(
|
||||
chapter.getDisplayTitle(
|
||||
contentReplaceRules,
|
||||
|
||||
@@ -174,12 +174,13 @@ open class WebDav(
|
||||
val baseUrl = NetworkUtils.getBaseUrl(urlStr)
|
||||
for (element in elements) {
|
||||
//依然是优化支持 caddy 自建的 WebDav ,其目录后缀都为“/”, 所以删除“/”的判定,不然无法获取该目录项
|
||||
val href = URLDecoder.decode(element.findNS("href", ns)[0].text(), "UTF-8")
|
||||
val href = element.findNS("href", ns)[0].text().replace("+", "%20")
|
||||
val hrefDecode = URLDecoder.decode(href, "UTF-8")
|
||||
.removeSuffix("/")
|
||||
val fileName = href.substringAfterLast("/")
|
||||
val fileName = hrefDecode.substringAfterLast("/")
|
||||
val webDavFile: WebDav
|
||||
try {
|
||||
val urlName = href.ifEmpty {
|
||||
val urlName = hrefDecode.ifEmpty {
|
||||
url.file.replace("/", "")
|
||||
}
|
||||
val contentType = element
|
||||
@@ -199,7 +200,7 @@ open class WebDav(
|
||||
.toInstant(ZoneOffset.of("+8")).toEpochMilli()
|
||||
}
|
||||
}.getOrNull() ?: 0
|
||||
val fullURL = NetworkUtils.getAbsoluteURL(baseUrl, href)
|
||||
val fullURL = NetworkUtils.getAbsoluteURL(baseUrl, hrefDecode)
|
||||
webDavFile = WebDavFile(
|
||||
fullURL,
|
||||
authorization,
|
||||
|
||||
@@ -13,10 +13,10 @@ class WebDavFile(
|
||||
val contentType: String,
|
||||
val resourceType: String,
|
||||
val lastModify: Long
|
||||
) : WebDav(urlStr, authorization) {
|
||||
) : WebDav(urlStr, authorization) {
|
||||
|
||||
val isDir by lazy {
|
||||
contentType == "httpd/unix-directory" || resourceType.lowercase().contains("collection")
|
||||
}
|
||||
val isDir by lazy {
|
||||
contentType == "httpd/unix-directory" || resourceType.lowercase().contains("collection")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ class AnalyzeRule(
|
||||
}
|
||||
if (result == null) result = ""
|
||||
val str = if (unescape) {
|
||||
StringEscapeUtils.unescapeHtml3(result.toString())
|
||||
StringEscapeUtils.unescapeHtml4(result.toString())
|
||||
} else result.toString()
|
||||
if (isUrl) {
|
||||
return if (str.isBlank()) {
|
||||
|
||||
@@ -134,7 +134,7 @@ object LocalBook {
|
||||
}
|
||||
if (book.isEpub) {
|
||||
content = content?.replace("<img", "< img", true) ?: return null
|
||||
return StringEscapeUtils.unescapeHtml3(content)
|
||||
return StringEscapeUtils.unescapeHtml4(content)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
@@ -40,7 +40,9 @@ class RemoteBookWebDav(
|
||||
val remoteWebDavFileList: List<WebDavFile> = WebDav(path, authorization).listFiles()
|
||||
//转化远程文件信息到本地对象
|
||||
remoteWebDavFileList.forEach { webDavFile ->
|
||||
if (webDavFile.isDir || bookFileRegex.matches(webDavFile.displayName) || archiveFileRegex.matches(webDavFile.displayName)
|
||||
if (webDavFile.isDir
|
||||
|| bookFileRegex.matches(webDavFile.displayName)
|
||||
|| archiveFileRegex.matches(webDavFile.displayName)
|
||||
) {
|
||||
//扩展名符合阅读的格式则认为是书籍
|
||||
remoteBooks.add(RemoteBook(webDavFile))
|
||||
@@ -70,7 +72,7 @@ class RemoteBookWebDav(
|
||||
if (!NetworkUtils.isAvailable()) throw NoStackTraceException("网络不可用")
|
||||
val localBookUri = Uri.parse(book.bookUrl)
|
||||
val putUrl = "$rootBookUrl${File.separator}${book.originName}"
|
||||
val webDav = WebDav(putUrl, authorization)
|
||||
val webDav = WebDav(putUrl, authorization)
|
||||
if (localBookUri.isContentScheme()) {
|
||||
webDav.upload(
|
||||
byteArray = localBookUri.readBytes(appCtx),
|
||||
@@ -80,8 +82,8 @@ class RemoteBookWebDav(
|
||||
webDav.upload(localBookUri.path!!)
|
||||
}
|
||||
book.origin = BookType.webDavTag + CustomUrl(putUrl)
|
||||
.putAttribute("serverID", serverID)
|
||||
.toString()
|
||||
.putAttribute("serverID", serverID)
|
||||
.toString()
|
||||
book.save()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
import splitties.init.appCtx
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
@@ -168,6 +169,7 @@ object BookContent {
|
||||
//获取正文
|
||||
var content = analyzeRule.getString(contentRule.content, unescape = false)
|
||||
content = HtmlFormatter.formatKeepImg(content, rUrl)
|
||||
content = StringEscapeUtils.unescapeHtml4(content)
|
||||
//获取下一页链接
|
||||
if (getNextPageUrl) {
|
||||
val nextUrlRule = contentRule.nextContentUrl
|
||||
|
||||
@@ -17,7 +17,6 @@ import io.legado.app.constant.AppLog
|
||||
import io.legado.app.constant.EventBus
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.BookSourcePart
|
||||
import io.legado.app.data.entities.toBookSource
|
||||
import io.legado.app.databinding.ActivityBookSourceBinding
|
||||
import io.legado.app.databinding.DialogEditTextBinding
|
||||
import io.legado.app.help.DirectLinkUpload
|
||||
@@ -71,6 +70,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
||||
private var sortAscending = true
|
||||
private var snackBar: Snackbar? = null
|
||||
private var isPaused = false
|
||||
private var searchKey: String? = null
|
||||
private val qrResult = registerForActivityResult(QrCodeResult()) {
|
||||
it ?: return@registerForActivityResult
|
||||
showDialogFragment(ImportBookSourceDialog(it))
|
||||
@@ -235,6 +235,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
||||
|
||||
|
||||
private fun upBookSource(searchKey: String? = null) {
|
||||
this.searchKey = searchKey
|
||||
sourceFlowJob?.cancel()
|
||||
sourceFlowJob = launch {
|
||||
when {
|
||||
@@ -376,7 +377,12 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
||||
R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.selection.toTypedArray())
|
||||
R.id.menu_add_group -> selectionAddToGroups()
|
||||
R.id.menu_remove_group -> selectionRemoveFromGroups()
|
||||
R.id.menu_export_selection -> viewModel.saveToFile(adapter.selection.toBookSource()) { file ->
|
||||
R.id.menu_export_selection -> viewModel.saveToFile(
|
||||
adapter,
|
||||
searchKey,
|
||||
sortAscending,
|
||||
sort
|
||||
) { file ->
|
||||
exportDir.launch {
|
||||
mode = HandleFileContract.EXPORT
|
||||
fileData = HandleFileContract.FileData(
|
||||
@@ -387,7 +393,12 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
||||
}
|
||||
}
|
||||
|
||||
R.id.menu_share_source -> viewModel.saveToFile(adapter.selection.toBookSource()) {
|
||||
R.id.menu_share_source -> viewModel.saveToFile(
|
||||
adapter,
|
||||
searchKey,
|
||||
sortAscending,
|
||||
sort
|
||||
) {
|
||||
share(it)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,19 @@ package io.legado.app.ui.book.source.manage
|
||||
|
||||
import android.app.Application
|
||||
import android.text.TextUtils
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.BookSource
|
||||
import io.legado.app.data.entities.BookSourcePart
|
||||
import io.legado.app.data.entities.toBookSource
|
||||
import io.legado.app.help.config.SourceConfig
|
||||
import io.legado.app.utils.*
|
||||
import splitties.init.appCtx
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import io.legado.app.ui.book.source.manage.BookSourceActivity.Sort
|
||||
|
||||
/**
|
||||
* 书源管理数据修改
|
||||
@@ -122,13 +127,15 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
|
||||
}
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
fun saveToFile(sources: List<BookSource>, success: (file: File) -> Unit) {
|
||||
private fun saveToFile(sources: List<BookSource>, success: (file: File) -> Unit) {
|
||||
execute {
|
||||
val path = "${context.filesDir}/shareBookSource.json"
|
||||
FileUtils.delete(path)
|
||||
val file = FileUtils.createFileWithReplace(path)
|
||||
FileOutputStream(file).use {
|
||||
GSON.writeToOutputStream(it, sources)
|
||||
FileOutputStream(file).use { out ->
|
||||
BufferedOutputStream(out, 64 * 1024).use {
|
||||
GSON.writeToOutputStream(it, sources)
|
||||
}
|
||||
}
|
||||
file
|
||||
}.onSuccess {
|
||||
@@ -138,6 +145,107 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
|
||||
}
|
||||
}
|
||||
|
||||
fun saveToFile(
|
||||
adapter: BookSourceAdapter,
|
||||
searchKey: String?,
|
||||
sortAscending: Boolean,
|
||||
sort: Sort,
|
||||
success: (file: File) -> Unit
|
||||
) {
|
||||
execute {
|
||||
val selection = adapter.selection
|
||||
val selectedRate = selection.size.toFloat() / adapter.itemCount.toFloat()
|
||||
val sources = if (selectedRate == 1f) {
|
||||
getBookSources(searchKey, sortAscending, sort)
|
||||
} else if (selectedRate < 0.3) {
|
||||
selection.toBookSource()
|
||||
} else {
|
||||
val keys = selection.map { it.bookSourceUrl }.toHashSet()
|
||||
val bookSources = getBookSources(searchKey, sortAscending, sort)
|
||||
bookSources.filter {
|
||||
keys.contains(it.bookSourceUrl)
|
||||
}
|
||||
}
|
||||
saveToFile(sources, success)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBookSources(
|
||||
searchKey: String?,
|
||||
sortAscending: Boolean,
|
||||
sort: Sort
|
||||
): List<BookSource> {
|
||||
return when {
|
||||
searchKey.isNullOrEmpty() -> {
|
||||
appDb.bookSourceDao.all
|
||||
}
|
||||
|
||||
searchKey == appCtx.getString(R.string.enabled) -> {
|
||||
appDb.bookSourceDao.allEnabled
|
||||
}
|
||||
|
||||
searchKey == appCtx.getString(R.string.disabled) -> {
|
||||
appDb.bookSourceDao.allDisabled
|
||||
}
|
||||
|
||||
searchKey == appCtx.getString(R.string.need_login) -> {
|
||||
appDb.bookSourceDao.allLogin
|
||||
}
|
||||
|
||||
searchKey == appCtx.getString(R.string.no_group) -> {
|
||||
appDb.bookSourceDao.allNoGroup
|
||||
}
|
||||
|
||||
searchKey.startsWith("group:") -> {
|
||||
val key = searchKey.substringAfter("group:")
|
||||
appDb.bookSourceDao.groupSearch(key)
|
||||
}
|
||||
|
||||
else -> {
|
||||
appDb.bookSourceDao.search(searchKey)
|
||||
}
|
||||
}.let { data ->
|
||||
if (sortAscending) when (sort) {
|
||||
Sort.Weight -> data.sortedBy { it.weight }
|
||||
Sort.Name -> data.sortedWith { o1, o2 ->
|
||||
o1.bookSourceName.cnCompare(o2.bookSourceName)
|
||||
}
|
||||
|
||||
Sort.Url -> data.sortedBy { it.bookSourceUrl }
|
||||
Sort.Update -> data.sortedByDescending { it.lastUpdateTime }
|
||||
Sort.Respond -> data.sortedBy { it.respondTime }
|
||||
Sort.Enable -> data.sortedWith { o1, o2 ->
|
||||
var sortNum = -o1.enabled.compareTo(o2.enabled)
|
||||
if (sortNum == 0) {
|
||||
sortNum = o1.bookSourceName.cnCompare(o2.bookSourceName)
|
||||
}
|
||||
sortNum
|
||||
}
|
||||
|
||||
else -> data
|
||||
}
|
||||
else when (sort) {
|
||||
Sort.Weight -> data.sortedByDescending { it.weight }
|
||||
Sort.Name -> data.sortedWith { o1, o2 ->
|
||||
o2.bookSourceName.cnCompare(o1.bookSourceName)
|
||||
}
|
||||
|
||||
Sort.Url -> data.sortedByDescending { it.bookSourceUrl }
|
||||
Sort.Update -> data.sortedBy { it.lastUpdateTime }
|
||||
Sort.Respond -> data.sortedByDescending { it.respondTime }
|
||||
Sort.Enable -> data.sortedWith { o1, o2 ->
|
||||
var sortNum = o1.enabled.compareTo(o2.enabled)
|
||||
if (sortNum == 0) {
|
||||
sortNum = o1.bookSourceName.cnCompare(o2.bookSourceName)
|
||||
}
|
||||
sortNum
|
||||
}
|
||||
|
||||
else -> data.reversed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addGroup(group: String) {
|
||||
execute {
|
||||
val sources = appDb.bookSourceDao.noGroup
|
||||
|
||||
@@ -32,9 +32,6 @@ object HtmlFormatter {
|
||||
.replace("\\s*\\n+\\s*".toRegex(), "\n ")
|
||||
.replace("^[\\n\\s]+".toRegex(), " ")
|
||||
.replace("[\\n\\s]+$".toRegex(), "")
|
||||
.let {
|
||||
StringEscapeUtils.unescapeHtml3(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun formatKeepImg(html: String?, redirectUrl: URL? = null): String {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.legado.app.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
@@ -21,6 +22,7 @@ object NetworkUtils {
|
||||
/**
|
||||
* 判断是否联网
|
||||
*/
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
@Suppress("DEPRECATION")
|
||||
fun isAvailable(): Boolean {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
@@ -31,7 +33,9 @@ object NetworkUtils {
|
||||
// 移动数据
|
||||
mWiFiNetworkInfo.type == ConnectivityManager.TYPE_MOBILE ||
|
||||
// 以太网
|
||||
mWiFiNetworkInfo.type == ConnectivityManager.TYPE_ETHERNET
|
||||
mWiFiNetworkInfo.type == ConnectivityManager.TYPE_ETHERNET ||
|
||||
// VPN
|
||||
mWiFiNetworkInfo.type == ConnectivityManager.TYPE_VPN
|
||||
}
|
||||
} else {
|
||||
val network = connectivityManager.activeNetwork
|
||||
@@ -43,7 +47,9 @@ object NetworkUtils {
|
||||
// 移动数据
|
||||
nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
|
||||
// 以太网
|
||||
nc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
nc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) ||
|
||||
// VPN
|
||||
nc.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user