pull/32/head
kunfei 5 years ago
parent d7fb601d33
commit 079088d6e5
  1. 8
      app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt
  2. 4
      app/src/main/java/io/legado/app/data/entities/Book.kt
  3. 30
      app/src/main/java/io/legado/app/data/entities/BookSource.kt
  4. 4
      app/src/main/java/io/legado/app/data/entities/SearchBook.kt
  5. 8
      app/src/main/java/io/legado/app/data/entities/rule/BaseRule.kt
  6. 17
      app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt
  7. 10
      app/src/main/java/io/legado/app/data/entities/rule/ChapterRule.kt
  8. 5
      app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt
  9. 17
      app/src/main/java/io/legado/app/data/entities/rule/ExploreRule.kt
  10. 6
      app/src/main/java/io/legado/app/data/entities/rule/PutRule.kt
  11. 120
      app/src/main/java/io/legado/app/data/entities/rule/Rule.kt
  12. 17
      app/src/main/java/io/legado/app/data/entities/rule/SearchRule.kt
  13. 8
      app/src/main/java/io/legado/app/data/entities/rule/TocRule.kt
  14. 62
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  15. 6
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeHeaders.kt
  16. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  17. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  18. 4
      app/src/main/java/io/legado/app/ui/main/booksource/BookSourceAdapter.kt
  19. 2
      app/src/main/java/io/legado/app/ui/main/booksource/BookSourceFragment.kt
  20. 4
      app/src/main/java/io/legado/app/ui/main/findbook/FindBookAdapter.kt
  21. 5
      app/src/main/java/io/legado/app/ui/sourceedit/SourceEditActivity.kt
  22. 2
      app/src/main/java/io/legado/app/ui/sourceedit/SourceEditViewModel.kt
  23. 2
      app/src/main/java/io/legado/app/utils/GsonExtensions.kt

@ -10,13 +10,13 @@ interface BookSourceDao {
@Query("select * from book_sources order by customOrder asc")
fun observeAll(): DataSource.Factory<Int, BookSource>
@Query("select * from book_sources where bookSourceName like :searchKey or `bookSourceGroup` like :searchKey or bookSourceOrigin like :searchKey order by customOrder asc")
@Query("select * from book_sources where bookSourceName like :searchKey or `bookSourceGroup` like :searchKey or bookSourceUrl like :searchKey order by customOrder asc")
fun observeSearch(searchKey: String = ""): DataSource.Factory<Int, BookSource>
@Query("select distinct enabled from book_sources where bookSourceName like :searchKey or `bookSourceGroup` like :searchKey or bookSourceOrigin like :searchKey")
@Query("select distinct enabled from book_sources where bookSourceName like :searchKey or `bookSourceGroup` like :searchKey or bookSourceUrl like :searchKey")
fun searchIsEnable(searchKey: String = ""): List<Boolean>
@Query("UPDATE book_sources SET enabled = :enable where bookSourceName like :searchKey or `bookSourceGroup` like :searchKey or bookSourceOrigin like :searchKey")
@Query("UPDATE book_sources SET enabled = :enable where bookSourceName like :searchKey or `bookSourceGroup` like :searchKey or bookSourceUrl like :searchKey")
fun enableAllSearch(searchKey: String = "", enable: String = "1")
@Query("select * from book_sources where enabledExplore = 1 order by customOrder asc")
@ -25,7 +25,7 @@ interface BookSourceDao {
@get:Query("select * from book_sources order by customOrder asc")
val all: List<BookSource>
@Query("select * from book_sources where bookSourceOrigin = :key")
@Query("select * from book_sources where bookSourceUrl = :key")
fun findByKey(key: String): BookSource?
@Query("select count(*) from book_sources")

@ -8,7 +8,7 @@ import androidx.room.Index
import androidx.room.PrimaryKey
import io.legado.app.constant.AppConst.NOT_AVAILABLE
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJson
import io.legado.app.utils.fromJsonObject
import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize
@ -69,7 +69,7 @@ data class Book(
variableMap = if (isEmpty(variable)) {
HashMap()
} else {
GSON.fromJson<HashMap<String, String>>(variable!!)
GSON.fromJsonObject<HashMap<String, String>>(variable!!)
}
}
}

@ -4,18 +4,21 @@ import android.os.Parcelable
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import io.legado.app.data.entities.rule.*
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject
import kotlinx.android.parcel.Parcelize
@Parcelize
@Entity(
tableName = "book_sources",
indices = [(Index(value = ["bookSourceOrigin"], unique = false))]
indices = [(Index(value = ["bookSourceUrl"], unique = false))]
)
data class BookSource(
var bookSourceName: String = "", // 名称
var bookSourceGroup: String? = null, // 分组
@PrimaryKey
var bookSourceOrigin: String = "", // 地址,包括 http/https
var bookSourceUrl: String = "", // 地址,包括 http/https
var bookSourceType: Int = 0, // 类型,0 文本,1 音频
var customOrder: Int = 0, // 手动排序编号
var enabled: Boolean = true, // 是否启用
@ -29,4 +32,25 @@ data class BookSource(
var ruleBookInfo: String? = null, // 书籍信息页规则
var ruleToc: String? = null, // 目录页规则
var ruleContent: String? = null // 正文页规则
) : Parcelable
) : Parcelable {
fun getSearchRule(): SearchRule? {
return GSON.fromJsonObject<SearchRule>(ruleSearch)
}
fun getExploreRule(): ExploreRule? {
return GSON.fromJsonObject<ExploreRule>(ruleExplore)
}
fun getBookInfoRule(): BookInfoRule? {
return GSON.fromJsonObject<BookInfoRule>(ruleBookInfo)
}
fun getTocRule(): TocRule? {
return GSON.fromJsonObject<TocRule>(ruleToc)
}
fun getContentRule(): ContentRule? {
return GSON.fromJsonObject<ContentRule>(ruleContent)
}
}

@ -7,7 +7,7 @@ import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJson
import io.legado.app.utils.fromJsonObject
import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize
@ -40,7 +40,7 @@ data class SearchBook(
variableMap = if (TextUtils.isEmpty(variable)) {
HashMap()
} else {
GSON.fromJson<HashMap<String, String>>(variable!!)
GSON.fromJsonObject<HashMap<String, String>>(variable!!)
}
}
}

@ -1,8 +0,0 @@
package io.legado.app.data.entities.rule
data class BaseRule(
var selector: String = "",
var template: String? = null,
var attr: String? = null,
var type: RuleType
)

@ -1,11 +1,14 @@
package io.legado.app.data.entities.rule
data class BookInfoRule(
var name: Rule,
var author: Rule,
var desc: Rule,
var meta: Rule,
var updateTime: Rule,
var tocUrl: Rule,
var store: List<PutRule>
var urlPattern: String? = null,
var init: String? = null,
var name: String? = null,
var author: String? = null,
var desc: String? = null,
var meta: String? = null,
var lastChapter: String? = null,
var updateTime: String? = null,
var coverUrl: String? = null,
var tocUrl: String? = null
)

@ -1,10 +0,0 @@
package io.legado.app.data.entities.rule
data class ChapterRule(
var chapterList: Rule,
var isReversed: Boolean = false,
var title: Rule,
var contentUrl: Rule,
var resourceUrl: Rule,
var nextUrl: Rule
)

@ -1,7 +1,6 @@
package io.legado.app.data.entities.rule
data class ContentRule(
var fulltext: Rule,
var resourceUrl: Rule,
var nextUrl: Rule
var content: String? = null,
var nextUrl: String? = null
)

@ -1,11 +1,14 @@
package io.legado.app.data.entities.rule
data class ExploreRule(
var bookList: Rule,
var name: Rule,
var author: Rule,
var desc: Rule,
var meta: Rule,
var bookUrl: Rule,
var store: PutRule
var exploreUrl: String? = null,
var bookList: String? = null,
var name: String? = null,
var author: String? = null,
var desc: String? = null,
var meta: String? = null,
var lastChapter: String? = null,
var updateTime: String? = null,
var bookUrl: String? = null,
var coverUrl: String? = null
)

@ -1,6 +0,0 @@
package io.legado.app.data.entities.rule
data class PutRule(
var selector: Rule,
var key: String
)

@ -1,120 +0,0 @@
package io.legado.app.data.entities.rule
import io.legado.app.utils.safeTrim
import io.legado.app.utils.splitNotBlank
data class Rule(
var selectors: List<BaseRule>,
var regex: String?,
var replacement: String?,
var javascript: String?,
var extra: String?
) {
companion object {
val JS_PATTERN = Regex("""\{\{([^}]+?)}}""")
val CONST_PATTERN = Regex("""\{(\$\.[^}]+?)}""")
fun parse(input: String) = when {
input.startsWith("$.") -> parseJSON(input)
input.startsWith("//") -> parseXPATH(input)
input.startsWith("RE:") -> parseREGEX(input)
isJsRule(input) -> parseJS(input)
isConstRule(input) -> parseCONST(input)
else -> parseCSS(input)
}
private fun isJsRule(input: String): Boolean {
val open = input.indexOf("{{")
if (open < 0) return false
val close = input.indexOf("}}", open)
return close > 0
}
private fun isConstRule(input: String): Boolean {
val open = input.indexOf("{")
if (open < 0) return false
val close = input.indexOf("}", open)
return close > 0
}
private fun parseCSS(rawRule: String): List<BaseRule> {
val rules = mutableListOf<BaseRule>()
for (line in rawRule.splitNotBlank("\n")) {
val baseRule = BaseRule(type = RuleType.CSS)
if (line.contains("@@")) {
val temp = line.split("@@")
baseRule.selector = temp[0].trim()
baseRule.attr = temp[1].safeTrim() ?: "text" // 写了 @@ 但是后面空白的也默认为 text
} else {
baseRule.selector = line
baseRule.attr = "text"
}
rules.add(baseRule)
}
return rules
}
private fun parseJSON(rawRule: String): List<BaseRule> {
val rules = mutableListOf<BaseRule>()
for (line in rawRule.splitNotBlank("\n")) {
val baseRule = BaseRule(type = RuleType.JSON)
baseRule.selector = line
rules.add(baseRule)
}
return rules
}
private fun parseXPATH(rawRule: String): List<BaseRule> {
val rules = mutableListOf<BaseRule>()
for (line in rawRule.splitNotBlank("\n")) {
val baseRule = BaseRule(type = RuleType.XPATH)
baseRule.selector = line
rules.add(baseRule)
}
return rules
}
private fun parseCONST(rawRule: String): List<BaseRule> {
val rules = mutableListOf<BaseRule>()
val subRule = mutableListOf<String>()
for (line in rawRule.splitNotBlank("\n")) {
subRule.clear()
val baseRule = BaseRule(type = RuleType.JSON)
// 保留 URL 中的 % 字符
baseRule.template = CONST_PATTERN.replace(line.replace("%", "%%")) { match ->
subRule.add(match.groupValues[1])
"%s"
}
baseRule.selector = subRule.joinToString("\n")
rules.add(baseRule)
}
return rules
}
private fun parseJS(rawRule: String): List<BaseRule> {
TODO()
}
private fun parseREGEX(rawRule: String): List<BaseRule> {
TODO()
}
}
}
/*
*
* CSS 规则说明
* 使用 JSOUP "选择规则"后面加上需要获取的属性名或者方法 href, data-url, text 如果不加默认为 text
* CSS "选择规则" "属性方法" 之间用 @@ 隔开
*
* CONST 规则说明
* {$.xxx} 表示要获取 JSON 变量 $.xxx最先解析
* {@.yyy} 表示要获取之前存储的变量 yyy
* {#.zzz} 表示直接输出 zzz可以留空{#.} 什么也不输出
*
* */
enum class RuleType {
CSS, XPATH, JSON, REGEX, CONST, JS, HYBRID
}

@ -1,11 +1,14 @@
package io.legado.app.data.entities.rule
data class SearchRule(
var bookList: Rule,
var name: Rule,
var author: Rule,
var desc: Rule,
var meta: Rule,
var bookUrl: Rule,
var store: List<PutRule>
var searchUrl: String? = null,
var bookList: String? = null,
var name: String? = null,
var author: String? = null,
var desc: String? = null,
var meta: String? = null,
var lastChapter: String? = null,
var updateTime: String? = null,
var bookUrl: String? = null,
var coverUrl: String? = null
)

@ -0,0 +1,8 @@
package io.legado.app.data.entities.rule
data class TocRule(
var chapterList: String? = null,
var chapterName: String? = null,
var chapterUrl: String? = null,
var nextUrl: String? = null
)

@ -10,6 +10,7 @@ import io.legado.app.constant.AppConst
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.data.entities.rule.*
import io.legado.app.utils.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.toast
@ -94,17 +95,64 @@ object Restore {
if (shelfFile.exists()) try {
doAsync {
val items: List<Map<String, Any>> = jsonPath.parse(sourceFile.readText()).read("$")
val existings = App.db.bookSourceDao().all.map { it.origin }.toSet()
val existingSources = App.db.bookSourceDao().all.map { it.bookSourceUrl }.toSet()
for (item in items) {
val jsonItem = jsonPath.parse(item)
val source = BookSource()
source.origin = jsonItem.readString("bookSourceUrl") ?: ""
if (source.origin.isBlank()) continue
if (source.origin in existings) continue
source.name = jsonItem.readString("bookSourceName") ?: ""
source.group = jsonItem.readString("bookSourceGroup") ?: ""
source.bookSourceUrl = jsonItem.readString("bookSourceUrl") ?: ""
if (source.bookSourceUrl.isBlank()) continue
if (source.bookSourceUrl in existingSources) continue
source.bookSourceName = jsonItem.readString("bookSourceName") ?: ""
source.bookSourceGroup = jsonItem.readString("bookSourceGroup") ?: ""
source.loginUrl = jsonItem.readString("loginUrl")
val searchRule = SearchRule(
searchUrl = jsonItem.readString("ruleSearchUrl"),
bookList = jsonItem.readString("ruleSearchList"),
name = jsonItem.readString("ruleSearchName"),
author = jsonItem.readString("ruleSearchAuthor"),
desc = jsonItem.readString("ruleSearchIntroduce"),
meta = jsonItem.readString("ruleSearchKind"),
bookUrl = jsonItem.readString("ruleSearchNoteUrl"),
coverUrl = jsonItem.readString("ruleSearchCoverUrl"),
lastChapter = jsonItem.readString("ruleSearchLastChapter")
)
source.ruleSearch = GSON.toJson(searchRule)
val exploreRule = ExploreRule(
exploreUrl = jsonItem.readString("ruleFindUrl"),
bookList = jsonItem.readString("ruleFindList"),
name = jsonItem.readString("ruleFindName"),
author = jsonItem.readString("ruleFindAuthor"),
desc = jsonItem.readString("ruleFindIntroduce"),
meta = jsonItem.readString("ruleFindKind"),
bookUrl = jsonItem.readString("ruleFindNoteUrl"),
coverUrl = jsonItem.readString("ruleFindCoverUrl"),
lastChapter = jsonItem.readString("ruleFindLastChapter")
)
source.ruleExplore = GSON.toJson(exploreRule)
val bookInfoRule = BookInfoRule(
urlPattern = jsonItem.readString("ruleBookUrlPattern"),
init = jsonItem.readString("ruleBookInfoInit"),
name = jsonItem.readString("ruleBookName"),
author = jsonItem.readString("ruleBookAuthor"),
desc = jsonItem.readString("ruleIntroduce"),
meta = jsonItem.readString("ruleBookKind"),
coverUrl = jsonItem.readString("ruleCoverUrl"),
lastChapter = jsonItem.readString("ruleBookLastChapter"),
tocUrl = jsonItem.readString("ruleChapterUrl")
)
source.ruleBookInfo = GSON.toJson(bookInfoRule)
val chapterRule = TocRule(
chapterList = jsonItem.readString("ruleChapterUrlNext"),
chapterName = jsonItem.readString("ruleChapterName"),
chapterUrl = jsonItem.readString("ruleContentUrl"),
nextUrl = jsonItem.readString("ruleChapterUrlNext")
)
source.ruleToc = GSON.toJson(chapterRule)
val contentRule = ContentRule(
content = jsonItem.readString("ruleBookContent"),
nextUrl = jsonItem.readString("ruleContentUrlNext")
)
source.ruleContent = GSON.toJson(contentRule)
bookSources.add(source)
}
App.db.bookSourceDao().insert(*bookSources.toTypedArray())

@ -5,7 +5,7 @@ import io.legado.app.App
import io.legado.app.R
import io.legado.app.data.entities.BookSource
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJson
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.getPrefString
import java.util.*
@ -24,12 +24,12 @@ object AnalyzeHeaders {
val headerMap = HashMap<String, String>()
if (bookSource != null && !isEmpty(bookSource.header)) {
bookSource.header?.let {
val map: HashMap<String, String>? = GSON.fromJson<HashMap<String, String>>(it)
val map: HashMap<String, String>? = GSON.fromJsonObject<HashMap<String, String>>(it)
map?.let { headerMap.putAll(map) }
}
}
if (bookSource != null) {
val cookie = App.db.sourceCookieDao().getCookieByUrl(bookSource.origin)
val cookie = App.db.sourceCookieDao().getCookieByUrl(bookSource.bookSourceUrl)
cookie?.let { headerMap["Cookie"] = cookie }
}
return headerMap

@ -259,7 +259,7 @@ class AnalyzeRule(private var book: BaseBook? = null) {
val putMatcher = putPattern.matcher(ruleStr)
while (putMatcher.find()) {
ruleStr = ruleStr.replace(putMatcher.group(), "")
val map = GSON.fromJson<Map<String, String>>(putMatcher.group(1))
val map = GSON.fromJsonObject<Map<String, String>>(putMatcher.group(1))
putRule(map)
}
return ruleStr

@ -121,7 +121,7 @@ constructor(ruleUrl: String, key: String?, page: Int?, headerMapF: Map<String, S
// ruleUrl = ruleUrl.replace(find, "")
// find = find.substring(8)
// try {
// val map = Gson().fromJson<Map<String, String>>(find)
// val map = Gson().fromJsonObject<Map<String, String>>(find)
// headerMap.putAll(map)
// } catch (ignored: Exception) {
// }

@ -21,10 +21,10 @@ class BookSourceAdapter : PagedListAdapter<BookSource, BookSourceAdapter.MyViewH
@JvmField
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<BookSource>() {
override fun areItemsTheSame(oldItem: BookSource, newItem: BookSource): Boolean =
oldItem.bookSourceOrigin == newItem.bookSourceOrigin
oldItem.bookSourceUrl == newItem.bookSourceUrl
override fun areContentsTheSame(oldItem: BookSource, newItem: BookSource): Boolean =
oldItem.bookSourceOrigin == newItem.bookSourceOrigin
oldItem.bookSourceUrl == newItem.bookSourceUrl
&& oldItem.bookSourceName == newItem.bookSourceName
&& oldItem.bookSourceGroup == newItem.bookSourceGroup
&& oldItem.enabled == newItem.enabled

@ -124,6 +124,6 @@ class BookSourceFragment : BaseFragment(R.layout.fragment_book_source), BookSour
}
override fun edit(bookSource: BookSource) {
context?.startActivity<SourceEditActivity>(Pair("data", bookSource.bookSourceOrigin))
context?.startActivity<SourceEditActivity>(Pair("data", bookSource.bookSourceUrl))
}
}

@ -17,10 +17,10 @@ class FindBookAdapter:PagedListAdapter<BookSource, FindBookAdapter.MyViewHolder>
companion object {
var DIFF_CALLBACK = object : DiffUtil.ItemCallback<BookSource>() {
override fun areItemsTheSame(oldItem: BookSource, newItem: BookSource): Boolean =
oldItem.bookSourceOrigin == newItem.bookSourceOrigin
oldItem.bookSourceUrl == newItem.bookSourceUrl
override fun areContentsTheSame(oldItem: BookSource, newItem: BookSource): Boolean =
oldItem.bookSourceOrigin == newItem.bookSourceOrigin
oldItem.bookSourceUrl == newItem.bookSourceUrl
&& oldItem.bookSourceName == newItem.bookSourceName
}
}

@ -68,9 +68,10 @@ class SourceEditActivity : BaseActivity<SourceEditViewModel>() {
cb_is_enable_find.isChecked = it.enabledExplore
}
sourceEditEntities.clear()
sourceEditEntities.add(SourceEditEntity("origin", bookSource?.bookSourceOrigin, R.string.book_source_url))
sourceEditEntities.add(SourceEditEntity("origin", bookSource?.bookSourceUrl, R.string.book_source_url))
sourceEditEntities.add(SourceEditEntity("name", bookSource?.bookSourceName, R.string.book_source_name))
sourceEditEntities.add(SourceEditEntity("group", bookSource?.bookSourceGroup, R.string.book_source_group))
adapter.sourceEditEntities = sourceEditEntities
adapter.notifyDataSetChanged()
}
@ -85,7 +86,7 @@ class SourceEditActivity : BaseActivity<SourceEditViewModel>() {
if (entity.value == null) {
return null
} else {
bookSource.bookSourceOrigin = entity.value!!
bookSource.bookSourceUrl = entity.value!!
}
}
"name" -> {

@ -20,7 +20,7 @@ class SourceEditViewModel(application: Application) : BaseViewModel(application)
fun save(bookSource: BookSource, finally: (() -> Unit)? = null) {
launch(IO) {
val source = App.db.bookSourceDao().findByKey(bookSource.bookSourceOrigin)
val source = App.db.bookSourceDao().findByKey(bookSource.bookSourceUrl)
if (source == null) {
bookSource.customOrder = App.db.bookSourceDao().allCount()
}

@ -7,7 +7,7 @@ import org.jetbrains.anko.attempt
val GSON: Gson by lazy { GsonBuilder().create() }
inline fun <reified T> Gson.fromJson(json: String): T = fromJson(json, T::class.java)
inline fun <reified T> Gson.fromJsonObject(json: String?): T? = fromJson(json, T::class.java)
inline fun <reified T> Gson.fromJsonArray(json: String): ArrayList<T>? {
return attempt {

Loading…
Cancel
Save