This commit is contained in:
HapeLee committed 2025-06-04 13:33:51 +08:00
1 parent b86d90021c
commit 6c0757c91b
972 files changed
+6885 -7321

No files matched your search

@@ -1,325 +0,0 @@
package io.legado.app.api.controller
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import androidx.core.graphics.drawable.toBitmap
import com.bumptech.glide.Glide
import io.legado.app.api.ReturnData
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookProgress
import io.legado.app.data.entities.BookSource
import io.legado.app.help.AppWebDav
import io.legado.app.help.CacheManager
import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.book.isLocal
import io.legado.app.help.config.AppConfig
import io.legado.app.help.glide.ImageLoader
import io.legado.app.model.BookCover
import io.legado.app.model.ImageProvider
import io.legado.app.model.ReadBook
import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.GSON
import io.legado.app.utils.cnCompare
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.printOnDebug
import io.legado.app.utils.stackTraceStr
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import splitties.init.appCtx
import java.io.File
import java.util.WeakHashMap
import java.util.concurrent.TimeUnit
object BookController {
private lateinit var book: Book
private var bookSource: BookSource? = null
private var bookUrl: String = ""
private val defaultCoverCache by lazy { WeakHashMap<Drawable, Bitmap>() }
/**
* 书架所有书籍
*/
val bookshelf: ReturnData
get() {
val books = appDb.bookDao.all
val returnData = ReturnData()
return if (books.isEmpty()) {
returnData.setErrorMsg("还没有添加小说")
} else {
val data = when (AppConfig.bookshelfSort) {
1 -> books.sortedByDescending { it.latestChapterTime }
2 -> books.sortedWith { o1, o2 ->
o1.name.cnCompare(o2.name)
}
3 -> books.sortedBy { it.order }
else -> books.sortedByDescending { it.durChapterTime }
}
returnData.setData(data)
}
}
/**
* 获取封面
*/
fun getCover(parameters: Map<String, List<String>>): ReturnData {
val returnData = ReturnData()
val coverPath = parameters["path"]?.firstOrNull()
val ftBitmap = ImageLoader.loadBitmap(appCtx, coverPath)
.override(84, 112)
.centerCrop()
.submit()
return try {
returnData.setData(ftBitmap.get(3, TimeUnit.SECONDS))
} catch (e: Exception) {
try {
val defaultBitmap = defaultCoverCache.getOrPut(BookCover.defaultDrawable) {
Glide.with(appCtx)
.asBitmap()
.load(BookCover.defaultDrawable.toBitmap())
.override(84, 112)
.centerCrop()
.submit()
.get()
}
returnData.setData(defaultBitmap)
} catch (e: Exception) {
returnData.setErrorMsg(e.localizedMessage ?: "getCover error")
}
}
}
/**
* 获取正文图片
*/
fun getImg(parameters: Map<String, List<String>>): ReturnData {
val returnData = ReturnData()
val bookUrl = parameters["url"]?.firstOrNull()
?: return returnData.setErrorMsg("bookUrl为空")
val src = parameters["path"]?.firstOrNull()
?: return returnData.setErrorMsg("图片链接为空")
val width = parameters["width"]?.firstOrNull()?.toInt() ?: 640
if (this.bookUrl != bookUrl) {
this.book = appDb.bookDao.getBook(bookUrl)
?: return returnData.setErrorMsg("bookUrl不对")
this.bookSource = appDb.bookSourceDao.getBookSource(book.origin)
}
this.bookUrl = bookUrl
val bitmap = runBlocking {
ImageProvider.cacheImage(book, src, bookSource)
ImageProvider.getImage(book, src, width)
}
return returnData.setData(bitmap)
}
/**
* 更新目录
*/
fun refreshToc(parameters: Map<String, List<String>>): ReturnData {
val returnData = ReturnData()
try {
val bookUrl = parameters["url"]?.firstOrNull()
if (bookUrl.isNullOrEmpty()) {
return returnData.setErrorMsg("参数url不能为空,请指定书籍地址")
}
val book = appDb.bookDao.getBook(bookUrl)
?: return returnData.setErrorMsg("未在数据库找到对应书籍,请先添加")
if (book.isLocal) {
val toc = LocalBook.getChapterList(book)
appDb.bookChapterDao.delByBook(book.bookUrl)
appDb.bookChapterDao.insert(*toc.toTypedArray())
appDb.bookDao.update(book)
return returnData.setData(toc)
} else {
val bookSource = appDb.bookSourceDao.getBookSource(book.origin)
?: return returnData.setErrorMsg("未找到对应书源,请换源")
val toc = runBlocking {
if (book.tocUrl.isBlank()) {
WebBook.getBookInfoAwait(bookSource, book)
}
WebBook.getChapterListAwait(bookSource, book).getOrThrow()
}
appDb.bookChapterDao.delByBook(book.bookUrl)
appDb.bookChapterDao.insert(*toc.toTypedArray())
appDb.bookDao.update(book)
return returnData.setData(toc)
}
} catch (e: Exception) {
return returnData.setErrorMsg(e.localizedMessage ?: "refresh toc error")
}
}
/**
* 获取目录
*/
fun getChapterList(parameters: Map<String, List<String>>): ReturnData {
val bookUrl = parameters["url"]?.firstOrNull()
val returnData = ReturnData()
if (bookUrl.isNullOrEmpty()) {
return returnData.setErrorMsg("参数url不能为空,请指定书籍地址")
}
val chapterList = appDb.bookChapterDao.getChapterList(bookUrl)
if (chapterList.isEmpty()) {
return refreshToc(parameters)
}
return returnData.setData(chapterList)
}
/**
* 获取正文
*/
fun getBookContent(parameters: Map<String, List<String>>): ReturnData {
val bookUrl = parameters["url"]?.firstOrNull()
val index = parameters["index"]?.firstOrNull()?.toInt()
val returnData = ReturnData()
if (bookUrl.isNullOrEmpty()) {
return returnData.setErrorMsg("参数url不能为空,请指定书籍地址")
}
if (index == null) {
return returnData.setErrorMsg("参数index不能为空, 请指定目录序号")
}
val book = appDb.bookDao.getBook(bookUrl)
val chapter = runBlocking {
var chapter = appDb.bookChapterDao.getChapter(bookUrl, index)
var wait = 0
while (chapter == null && wait < 30) {
delay(1000)
chapter = appDb.bookChapterDao.getChapter(bookUrl, index)
wait++
}
chapter
}
if (book == null || chapter == null) {
return returnData.setErrorMsg("未找到")
}
var content: String? = BookHelp.getContent(book, chapter)
if (content != null) {
val contentProcessor = ContentProcessor.get(book.name, book.origin)
content = runBlocking {
contentProcessor.getContent(book, chapter, content, includeTitle = false)
.toString()
}
return returnData.setData(content)
}
val bookSource = appDb.bookSourceDao.getBookSource(book.origin)
?: return returnData.setErrorMsg("未找到书源")
try {
content = runBlocking {
WebBook.getContentAwait(bookSource, book, chapter).let {
val contentProcessor = ContentProcessor.get(book.name, book.origin)
contentProcessor.getContent(book, chapter, it, includeTitle = false)
.toString()
}
}
returnData.setData(content)
} catch (e: Exception) {
returnData.setErrorMsg(e.stackTraceStr)
}
return returnData
}
/**
* 保存书籍
*/
suspend fun saveBook(postData: String?): ReturnData {
val returnData = ReturnData()
GSON.fromJsonObject<Book>(postData).getOrNull()?.let { book ->
AppWebDav.uploadBookProgress(book)
book.save()
return returnData.setData("")
}
return returnData.setErrorMsg("格式不对")
}
/**
* 删除书籍
*/
fun deleteBook(postData: String?): ReturnData {
val returnData = ReturnData()
GSON.fromJsonObject<Book>(postData).getOrNull()?.let { book ->
book.delete()
return returnData.setData("")
}
return returnData.setErrorMsg("格式不对")
}
/**
* 保存进度
*/
suspend fun saveBookProgress(postData: String?): ReturnData {
val returnData = ReturnData()
GSON.fromJsonObject<BookProgress>(postData)
.onFailure { it.printOnDebug() }
.getOrNull()?.let { bookProgress ->
appDb.bookDao.getBook(bookProgress.name, bookProgress.author)?.let { book ->
book.durChapterIndex = bookProgress.durChapterIndex
book.durChapterPos = bookProgress.durChapterPos
book.durChapterTitle = bookProgress.durChapterTitle
book.durChapterTime = bookProgress.durChapterTime
AppWebDav.uploadBookProgress(bookProgress) {
book.syncTime = System.currentTimeMillis()
}
appDb.bookDao.update(book)
ReadBook.book?.let {
if (it.name == bookProgress.name &&
it.author == bookProgress.author
) {
ReadBook.webBookProgress = bookProgress
}
}
return returnData.setData("")
}
}
return returnData.setErrorMsg("格式不对")
}
/**
* 添加本地书籍
*/
fun addLocalBook(
parameters: Map<String, List<String>>,
files: Map<String, String>
): ReturnData {
val returnData = ReturnData()
val fileName = parameters["fileName"]?.firstOrNull()
?: return returnData.setErrorMsg("fileName 不能为空")
val fileData = files["fileData"]
?: return returnData.setErrorMsg("fileData 不能为空")
kotlin.runCatching {
val uri = LocalBook.saveBookFile(File(fileData).inputStream(), fileName)
LocalBook.importFile(uri)
}.onFailure {
return when (it) {
is SecurityException -> returnData.setErrorMsg("需重新设置书籍保存位置!")
else -> returnData.setErrorMsg("保存书籍错误\n${it.localizedMessage}")
}
}
return returnData.setData(true)
}
/**
* 保存web阅读界面配置
*/
fun saveWebReadConfig(postData: String?): ReturnData {
val returnData = ReturnData()
postData?.let {
CacheManager.put("webReadConfig", postData)
} ?: CacheManager.delete("webReadConfig")
return returnData.setData("")
}
/**
* 获取web阅读界面配置
*/
fun getWebReadConfig(): ReturnData {
val returnData = ReturnData()
val data = CacheManager.get("webReadConfig")
?: return returnData.setErrorMsg("没有配置")
return returnData.setData(data)
}
}
@@ -1,80 +0,0 @@
package io.legado.app.api.controller
import android.text.TextUtils
import io.legado.app.api.ReturnData
import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource
import io.legado.app.help.source.SourceHelp
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonArray
import io.legado.app.utils.fromJsonObject
object BookSourceController {
val sources: ReturnData
get() {
val bookSources = appDb.bookSourceDao.all
val returnData = ReturnData()
return if (bookSources.isEmpty()) {
returnData.setErrorMsg("设备源列表为空")
} else returnData.setData(bookSources)
}
fun saveSource(postData: String?): ReturnData {
val returnData = ReturnData()
postData ?: return returnData.setErrorMsg("数据不能为空")
val bookSource = GSON.fromJsonObject<BookSource>(postData).getOrNull()
if (bookSource != null) {
if (TextUtils.isEmpty(bookSource.bookSourceName) || TextUtils.isEmpty(bookSource.bookSourceUrl)) {
returnData.setErrorMsg("源名称和URL不能为空")
} else {
appDb.bookSourceDao.insert(bookSource)
returnData.setData("")
}
} else {
returnData.setErrorMsg("转换源失败")
}
return returnData
}
fun saveSources(postData: String?): ReturnData {
postData ?: return ReturnData().setErrorMsg("数据为空")
val okSources = arrayListOf<BookSource>()
val bookSources = GSON.fromJsonArray<BookSource>(postData).getOrNull()
if (bookSources.isNullOrEmpty()) {
return ReturnData().setErrorMsg("转换源失败")
}
bookSources.forEach { bookSource ->
if (bookSource.bookSourceName.isNotBlank()
&& bookSource.bookSourceUrl.isNotBlank()
) {
appDb.bookSourceDao.insert(bookSource)
okSources.add(bookSource)
}
}
return ReturnData().setData(okSources)
}
fun getSource(parameters: Map<String, List<String>>): ReturnData {
val url = parameters["url"]?.firstOrNull()
val returnData = ReturnData()
if (url.isNullOrEmpty()) {
return returnData.setErrorMsg("参数url不能为空,请指定源地址")
}
val bookSource = appDb.bookSourceDao.getBookSource(url)
?: return returnData.setErrorMsg("未找到源,请检查书源地址")
return returnData.setData(bookSource)
}
fun deleteSources(postData: String?): ReturnData {
kotlin.runCatching {
GSON.fromJsonArray<BookSource>(postData).getOrThrow().let {
SourceHelp.deleteBookSources(it)
}
}.onFailure {
return ReturnData().setErrorMsg(it.localizedMessage ?: "数据格式错误")
}
return ReturnData().setData("已执行"/*okSources*/)
}
}
@@ -1,97 +0,0 @@
package io.legado.app.api.controller
import io.legado.app.api.ReturnData
import io.legado.app.data.appDb
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.replace
import io.legado.app.utils.stackTraceStr
object ReplaceRuleController {
val allRules: ReturnData
get() {
val rules = appDb.replaceRuleDao.all
val returnData = ReturnData()
returnData.setData(GSON.toJson(rules))
return returnData
}
fun saveRule(postData: String?): ReturnData {
val returnData = ReturnData()
postData ?: return returnData.setErrorMsg("数据不能为空")
val rule = GSON.fromJsonObject<ReplaceRule>(postData).getOrNull()
if (rule == null) {
returnData.setErrorMsg("格式不对")
} else {
if (rule.order == Int.MIN_VALUE) {
rule.order = appDb.replaceRuleDao.maxOrder + 1
}
appDb.replaceRuleDao.insert(rule)
}
return returnData
}
fun delete(postData: String?): ReturnData {
val returnData = ReturnData()
postData ?: return returnData.setErrorMsg("数据不能为空")
val rule = GSON.fromJsonObject<ReplaceRule>(postData).getOrNull()
if (rule == null) {
returnData.setErrorMsg("格式不对")
} else {
appDb.replaceRuleDao.delete(rule)
}
return returnData
}
/**
* 传入测试数据格式
* {
* rule: Replace,
* text: "xxx"
* }
*/
fun testRule(postData: String?): ReturnData {
val returnData = ReturnData()
postData ?: return returnData.setErrorMsg("数据不能为空")
val map = GSON.fromJsonObject<Map<String, *>>(postData).getOrNull()
if (map == null) {
returnData.setErrorMsg("格式不对")
} else {
val rule = map["rule"]?.let {
if (it is String) {
GSON.fromJsonObject<ReplaceRule>(it).getOrNull()
} else {
GSON.fromJsonObject<ReplaceRule>(GSON.toJson(it)).getOrNull()
}
}
if (rule == null) {
returnData.setErrorMsg("格式不对")
return returnData
}
if (rule.pattern.isEmpty()) {
returnData.setErrorMsg("替换规则不能为空")
}
val text = map["text"] as String
val content = try {
if (rule.isRegex) {
text.replace(
rule.pattern.toRegex(),
rule.replacement,
rule.getValidTimeoutMillisecond()
)
} else {
text.replace(rule.pattern, rule.replacement)
}
} catch (e: Exception) {
e.stackTraceStr
}
returnData.setData(content)
}
return returnData
}
}
@@ -1,77 +0,0 @@
package io.legado.app.api.controller
import android.text.TextUtils
import io.legado.app.api.ReturnData
import io.legado.app.data.appDb
import io.legado.app.data.entities.RssSource
import io.legado.app.help.source.SourceHelp
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonArray
import io.legado.app.utils.fromJsonObject
object RssSourceController {
val sources: ReturnData
get() {
val source = appDb.rssSourceDao.all
val returnData = ReturnData()
return if (source.isEmpty()) {
returnData.setErrorMsg("源列表为空")
} else returnData.setData(source)
}
fun saveSource(postData: String?): ReturnData {
val returnData = ReturnData()
postData ?: return returnData.setErrorMsg("数据不能为空")
GSON.fromJsonObject<RssSource>(postData).onFailure {
returnData.setErrorMsg("转换源失败${it.localizedMessage}")
}.onSuccess { source ->
if (TextUtils.isEmpty(source.sourceName) || TextUtils.isEmpty(source.sourceUrl)) {
returnData.setErrorMsg("源名称和URL不能为空")
} else {
appDb.rssSourceDao.insert(source)
returnData.setData("")
}
}
return returnData
}
fun saveSources(postData: String?): ReturnData {
postData ?: return ReturnData().setErrorMsg("数据不能为空")
val okSources = arrayListOf<RssSource>()
val source = GSON.fromJsonArray<RssSource>(postData).getOrNull()
if (source.isNullOrEmpty()) {
return ReturnData().setErrorMsg("转换源失败")
}
for (rssSource in source) {
if (rssSource.sourceName.isBlank() || rssSource.sourceUrl.isBlank()) {
continue
}
appDb.rssSourceDao.insert(rssSource)
okSources.add(rssSource)
}
return ReturnData().setData(okSources)
}
fun getSource(parameters: Map<String, List<String>>): ReturnData {
val url = parameters["url"]?.firstOrNull()
val returnData = ReturnData()
if (url.isNullOrEmpty()) {
return returnData.setErrorMsg("参数url不能为空,请指定书源地址")
}
val source = appDb.rssSourceDao.getByKey(url)
?: return returnData.setErrorMsg("未找到源,请检查源地址")
return returnData.setData(source)
}
fun deleteSources(postData: String?): ReturnData {
postData ?: return ReturnData().setErrorMsg("没有传递数据")
GSON.fromJsonArray<RssSource>(postData).onFailure {
return ReturnData().setErrorMsg("格式不对")
}.onSuccess {
SourceHelp.deleteRssSources(it)
}
return ReturnData().setData("已执行"/*okSources*/)
}
}