Merge remote-tracking branch 'origin/master'

pull/52/head
kunfei 5 years ago
commit 03fe6ef2ee
  1. 4
      app/src/main/java/io/legado/app/constant/BookType.kt
  2. 3
      app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt
  3. 39
      app/src/main/java/io/legado/app/data/entities/BookSource.kt
  4. 4
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  5. 2
      app/src/main/java/io/legado/app/help/storage/OldRule.kt
  6. 20
      app/src/main/java/io/legado/app/model/Debug.kt
  7. 2
      app/src/main/java/io/legado/app/model/WebBook.kt
  8. 15
      app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt
  9. 2
      app/src/main/java/io/legado/app/model/webbook/BookChapterList.kt
  10. 23
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt
  11. 1
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  12. 19
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt
  13. 1
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt
  14. 21
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt
  15. 1
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
  16. 2
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt
  17. 16
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
  18. 5
      app/src/main/res/menu/book_source.xml
  19. 5
      app/src/main/res/menu/replace_rule.xml
  20. 5
      app/src/main/res/menu/rss_source.xml
  21. 5
      app/src/main/res/menu/source_edit.xml
  22. 12
      app/src/main/res/values/strings.xml

@ -1,7 +1,7 @@
package io.legado.app.constant
object BookType {
const val default = 0
const val audio = 1
const val default = 0 // 0 文本
const val audio = 1 // 1 音频
const val local = "loc_book"
}

@ -10,6 +10,9 @@ interface RssSourceDao {
@Query("select * from rssSources where sourceUrl = :key")
fun getByKey(key: String): RssSource?
@Query("select * from rssSources where sourceUrl in (:sourceUrls)")
fun getRssSources(vararg sourceUrls: String):List<RssSource>
@get:Query("SELECT * FROM rssSources")
val all: List<RssSource>

@ -8,6 +8,7 @@ import androidx.room.PrimaryKey
import io.legado.app.App
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppConst.userAgent
import io.legado.app.constant.BookType
import io.legado.app.data.entities.rule.*
import io.legado.app.help.JsExtensions
import io.legado.app.utils.ACache
@ -25,26 +26,26 @@ import javax.script.SimpleBindings
indices = [(Index(value = ["bookSourceUrl"], unique = false))]
)
data class BookSource(
var bookSourceName: String = "", // 名称
var bookSourceGroup: String? = null, // 分组
var bookSourceName: String = "", // 名称
var bookSourceGroup: String? = null, // 分组
@PrimaryKey
var bookSourceUrl: String = "", // 地址,包括 http/https
var bookSourceType: Int = 0, // 类型,0 文本,1 音频
var bookUrlPattern: String? = null, //详情页url正则
var customOrder: Int = 0, // 手动排序编号
var enabled: Boolean = true, // 是否启用
var enabledExplore: Boolean = true, //启用发现
var header: String? = null,
var loginUrl: String? = null, // 登录地址
var lastUpdateTime: Long = 0, // 最后更新时间,用于排序
var weight: Int = 0, // 智能排序的权重
var exploreUrl: String? = null, //发现url
var ruleExplore: String? = null, // 发现规则
var searchUrl: String? = null, //搜索url
var ruleSearch: String? = null, // 搜索规则
var ruleBookInfo: String? = null, // 书籍信息页规则
var ruleToc: String? = null, // 目录页规则
var ruleContent: String? = null // 正文页规则
var bookSourceUrl: String = "", // 地址,包括 http/https
var bookSourceType: Int = BookType.default, // 类型,0 文本,1 音频
var bookUrlPattern: String? = null, // 详情页url正则
var customOrder: Int = 0, // 手动排序编号
var enabled: Boolean = true, // 是否启用
var enabledExplore: Boolean = true, // 启用发现
var header: String? = null, // 请求头
var loginUrl: String? = null, // 登录地址
var lastUpdateTime: Long = 0, // 最后更新时间,用于排序
var weight: Int = 0, // 智能排序的权重
var exploreUrl: String? = null, // 发现url
var ruleExplore: String? = null, // 发现规则
var searchUrl: String? = null, // 搜索url
var ruleSearch: String? = null, // 搜索规则
var ruleBookInfo: String? = null, // 书籍信息页规则
var ruleToc: String? = null, // 目录页规则
var ruleContent: String? = null // 正文页规则
) : Parcelable, JsExtensions {
@Ignore
@IgnoredOnParcel

@ -23,6 +23,10 @@ object Backup {
defaultPath + File.separator + "legadoBackUp"
}
val exportPath by lazy {
legadoPath + File.separator + "Export"
}
fun backup() {
doAsync {
val path = legadoPath

@ -118,7 +118,7 @@ object OldRule {
}
if (newRule.contains("|") && !newRule.contains("||")) {
if (newRule.contains("##")) {
var list = newRule.split("##")
val list = newRule.split("##")
if (list[0].contains("|")) {
newRule = list[0].replace("|", "||")
for (i in 1 until list.size) {

@ -53,18 +53,26 @@ object Debug {
fun startDebug(rssSource: RssSource) {
cancelDebug()
debugSource = rssSource.sourceUrl
log(debugSource, "︾开始解析RSS")
log(debugSource, "︾开始解析")
Rss.getArticles(rssSource, null)
.onSuccess {
if (it == null) {
log(debugSource, "︽解析失败", state = -1)
} else if(it.articles.isEmpty()) {
log(debugSource, "⇒列表页解析成功,为空")
log(debugSource, "︽解析完成", state = 1000)
} else {
val ruleContent = rssSource.ruleContent
if (it.articles.isNotEmpty() && it.articles[0].description.isNullOrEmpty() && !ruleContent.isNullOrEmpty()) {
log(debugSource, "︽解析完成")
if (!rssSource.ruleArticles.isNullOrBlank() && rssSource.ruleDescription.isNullOrBlank()) {
log(debugSource, "列表页解析完成")
log(debugSource, showTime = false)
rssContentDebug(it.articles[0], ruleContent)
if (ruleContent.isNullOrEmpty()) {
log(debugSource, "⇒内容规则为空,默认获取整个网页", state = 1000)
} else {
rssContentDebug(it.articles[0], ruleContent)
}
} else {
log(debugSource, "⇒存在描述规则,不解析内容页")
log(debugSource, "︽解析完成", state = 1000)
}
}
@ -75,11 +83,11 @@ object Debug {
}
private fun rssContentDebug(rssArticle: RssArticle, ruleContent: String) {
log(debugSource, "︾开始解析内容")
log(debugSource, "︾开始解析内容")
Rss.getContent(rssArticle, ruleContent)
.onSuccess {
log(debugSource, it)
log(debugSource, "︽解析完成", state = 1000)
log(debugSource, "内容页解析完成", state = 1000)
}
.onError {
log(debugSource, it.localizedMessage, state = -1)

@ -138,7 +138,7 @@ class WebBook(val bookSource: BookSource) {
): Coroutine<String> {
return Coroutine.async(scope, context) {
if (bookSource.getContentRule().content.isNullOrEmpty()) {
Debug.log(sourceUrl, "内容规则为空,直接返回目录Url:${bookChapter.url}")
Debug.log(sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}")
return@async bookChapter.url
}
val body = if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) {

@ -25,7 +25,7 @@ object RssParserByRule {
Debug.log(sourceUrl, "≡获取成功:$sourceUrl")
var ruleArticles = rssSource.ruleArticles
if (ruleArticles.isNullOrBlank()) {
Debug.log(sourceUrl, "列表规则为空, 使用默认规则解析")
Debug.log(sourceUrl, "列表规则为空, 使用默认规则解析")
return RssParser.parseXML(body, sourceUrl)
} else {
val articleList = mutableListOf<RssArticle>()
@ -40,12 +40,12 @@ object RssParserByRule {
val collections = analyzeRule.getElements(ruleArticles)
Debug.log(sourceUrl, "└列表大小:${collections.size}")
if (!rssSource.ruleNextPage.isNullOrEmpty()) {
Debug.log(sourceUrl, "┌获取下一页Url")
Debug.log(sourceUrl, "┌获取下一页链接")
nextUrl = analyzeRule.getString(rssSource.ruleNextPage)
if (nextUrl.isNotEmpty()) {
nextUrl = NetworkUtils.getAbsoluteURL(sourceUrl, nextUrl)
}
Debug.log(sourceUrl, "获取下一页Url:$nextUrl")
Debug.log(sourceUrl, "$nextUrl")
}
val ruleTitle = analyzeRule.splitSourceRule(rssSource.ruleTitle)
val rulePubDate = analyzeRule.splitSourceRule(rssSource.rulePubDate)
@ -88,8 +88,13 @@ object RssParserByRule {
rssArticle.pubDate = analyzeRule.getString(rulePubDate)
Debug.log(sourceUrl, "${rssArticle.pubDate}", log)
Debug.log(sourceUrl, "┌获取描述", log)
rssArticle.description = analyzeRule.getString(ruleDescription)
Debug.log(sourceUrl, "${rssArticle.description}", log)
if (ruleDescription.isNullOrEmpty()) {
rssArticle.description = null
Debug.log(sourceUrl, "└描述规则为空,将会解析内容页", log)
} else {
rssArticle.description = analyzeRule.getString(ruleDescription)
Debug.log(sourceUrl, "${rssArticle.description}", log)
}
Debug.log(sourceUrl, "┌获取图片url", log)
rssArticle.image = analyzeRule.getString(ruleImage, true)
Debug.log(sourceUrl, "${rssArticle.image}", log)

@ -167,6 +167,8 @@ object BookChapterList {
Debug.log(bookSource.bookSourceUrl, "${chapterList[0].title}", log)
Debug.log(bookSource.bookSourceUrl, "┌获取首章链接", log)
Debug.log(bookSource.bookSourceUrl, "${chapterList[0].url}", log)
Debug.log(bookSource.bookSourceUrl, "┌获取首章信息", log)
Debug.log(bookSource.bookSourceUrl, "${chapterList[0].tag}", log)
}
return ChapterData(chapterList, nextUrlList)
}

@ -4,7 +4,9 @@ import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.view.Gravity
import android.view.Menu
@ -85,6 +87,27 @@ class BookSourceEditActivity :
}
}
R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) }
R.id.menu_share_str -> {
GSON.toJson(getSource())?.let { sourceStr ->
try {
val textIntent = Intent(Intent.ACTION_SEND)
textIntent.setType("text/plain")
textIntent.putExtra(Intent.EXTRA_TEXT, sourceStr)
startActivity(Intent.createChooser(textIntent, "Source Share"))
} catch (e: Exception) {
toast(R.string.can_not_share)
}
}
}
R.id.menu_rule_summary -> {
try {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(getString(R.string.source_rule_url))
startActivity(intent)
} catch (e: Exception) {
toast(R.string.can_not_open)
}
}
}
return super.onCompatOptionsItemSelected(item)
}

@ -92,6 +92,7 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
R.id.menu_enable_explore -> viewModel.enableSelectExplore(adapter.getSelectionIds())
R.id.menu_disable_explore -> viewModel.disableSelectExplore(adapter.getSelectionIds())
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelectionIds())
R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelectionIds())
R.id.menu_check_source ->
startService<CheckSourceService>(Pair("data", adapter.getSelectionIds()))
R.id.menu_import_source_onLine -> showImportDialog()

@ -7,12 +7,15 @@ import io.legado.app.App
import io.legado.app.base.BaseViewModel
import io.legado.app.data.api.IHttpGetApi
import io.legado.app.data.entities.BookSource
import io.legado.app.help.FileHelp
import io.legado.app.help.http.HttpHelper
import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.OldRule
import io.legado.app.help.storage.Restore.jsonPath
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.anko.toast
import java.io.File
class BookSourceViewModel(application: Application) : BaseViewModel(application) {
@ -82,6 +85,22 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
}
}
fun exportSelection(ids: LinkedHashSet<String>) {
execute {
ids.map {
App.db.bookSourceDao().getBookSource(it)
}.let {
val json = GSON.toJson(it)
val file = FileHelp.getFile(Backup.exportPath + File.separator + "exportBookSource.json")
file.writeText(json)
}
}.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {
context.toast("导出失败\n${it.localizedMessage}")
}
}
fun addGroup(group: String) {
execute {
val sources = App.db.bookSourceDao().noGroup

@ -66,6 +66,7 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelectionIds())
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelectionIds())
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelectionIds())
R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelectionIds())
}
return super.onCompatOptionsItemSelected(item)
}

@ -5,7 +5,12 @@ import android.text.TextUtils
import io.legado.app.App
import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.FileHelp
import io.legado.app.help.storage.Backup
import io.legado.app.utils.GSON
import io.legado.app.utils.splitNotBlank
import org.jetbrains.anko.toast
import java.io.File
class ReplaceRuleViewModel(application: Application) : BaseViewModel(application) {
@ -57,6 +62,22 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
}
}
fun exportSelection(ids: LinkedHashSet<Long>) {
execute {
ids.map {
App.db.replaceRuleDao().findById(it)
}.let {
val json = GSON.toJson(it)
val file = FileHelp.getFile(Backup.exportPath + File.separator + "exportReplaceRule.json")
file.writeText(json)
}
}.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {
context.toast("导出失败\n${it.localizedMessage}")
}
}
fun addGroup(group: String) {
execute {
val sources = App.db.replaceRuleDao().noGroup

@ -81,6 +81,7 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelectionIds())
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelectionIds())
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelectionIds())
R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelectionIds())
R.id.menu_import_source_local -> selectFile()
R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode)

@ -38,7 +38,7 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) :
fun getSelectionIds(): LinkedHashSet<String> {
val selection = linkedSetOf<String>()
getItems().map {
getItems().forEach {
if (selectedIds.contains(it.sourceUrl)) {
selection.add(it.sourceUrl)
}

@ -7,9 +7,12 @@ import io.legado.app.App
import io.legado.app.base.BaseViewModel
import io.legado.app.data.api.IHttpGetApi
import io.legado.app.data.entities.RssSource
import io.legado.app.help.FileHelp
import io.legado.app.help.http.HttpHelper
import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.Restore.jsonPath
import io.legado.app.utils.*
import org.jetbrains.anko.toast
import java.io.File
class RssSourceViewModel(application: Application) : BaseViewModel(application) {
@ -57,6 +60,19 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
}
}
fun exportSelection(ids: LinkedHashSet<String>) {
execute {
App.db.rssSourceDao().getRssSources(*ids.toTypedArray()).let {
val json = GSON.toJson(it)
val file = FileHelp.getFile(Backup.exportPath + File.separator + "exportRssSource.json")
file.writeText(json)
}
}.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {
context.toast("导出失败\n${it.localizedMessage}")
}
}
fun addGroup(group: String) {
execute {

@ -47,6 +47,11 @@
android:title="@string/del_select"
app:showAsAction="never" />
<item
android:id="@+id/menu_export_selection"
android:title="@string/export_selection"
app:showAsAction="never" />
</menu>
</item>

@ -37,6 +37,11 @@
android:title="@string/del_select"
app:showAsAction="never" />
<item
android:id="@+id/menu_export_selection"
android:title="@string/export_selection"
app:showAsAction="never" />
</menu>
</item>

@ -37,6 +37,11 @@
android:title="@string/del_select"
app:showAsAction="never" />
<item
android:id="@+id/menu_export_selection"
android:title="@string/export_selection"
app:showAsAction="never" />
</menu>
</item>

@ -39,6 +39,11 @@
android:title="@string/qr_share"
app:showAsAction="never" />
<item
android:id="@+id/menu_share_str"
android:title="@string/str_share"
app:showAsAction="never" />
<item
android:id="@+id/menu_share_wifi"
android:title="@string/wifi_share"

@ -367,9 +367,9 @@
<string name="source_no_login">当前源没有配置登陆地址</string>
<!-- source start-->
<string name="source_name">书源名称(bookSourceName)</string>
<string name="source_url">书源URL(bookSourceUrl)</string>
<string name="source_group">书源分组(bookSourceGroup)</string>
<string name="source_name">源名称(sourceName)</string>
<string name="source_url">源URL(sourceUrl)</string>
<string name="source_group">源分组(sourceGroup)</string>
<string name="login_url">登录URL(loginUrl)</string>
<string name="r_search_url">搜索地址(url)</string>
<string name="r_find_url">发现地址规则(url)</string>
@ -389,8 +389,8 @@
<string name="rule_chapter_list">目录列表规则(chapterList)</string>
<string name="rule_chapter_name">章节名称规则(ChapterName)</string>
<string name="rule_chapter_url">章节URL规则(chapterUrl)</string>
<string name="rule_is_vip">是否VIP(isVip)</string>
<string name="rule_update_time">更新时间(updateTime)</string>
<string name="rule_is_vip">VIP标识(isVip)</string>
<string name="rule_update_time">章节信息(ChapterInfo)</string>
<string name="rule_book_content">正文规则(content)</string>
<string name="rule_next_content">正文下一页URL规则(nextContentUrl)</string>
<string name="rule_web_js">webJs</string>
@ -501,6 +501,7 @@
<string name="web_port_title">web端口</string>
<string name="web_port_summary">当前端口 %s</string>
<string name="qr_share">二维码分享</string>
<string name="str_share">字符串分享</string>
<string name="wifi_share">wifi分享</string>
<string name="please_grant_storage_permission">请给于存储权限</string>
<string name="fast_rewind">减速</string>
@ -543,6 +544,7 @@
<string name="disable_explore">禁用发现</string>
<string name="enable_selection">启用所选</string>
<string name="disable_selection">禁用所选</string>
<string name="export_selection">导出所选</string>
<string name="tts">TTS</string>
<string name="web_dav_pw_s">输入你的WebDav授权密码</string>
<string name="web_dav_url_s">输入你的服务器地址</string>

Loading…
Cancel
Save