合并上游更改

感谢:@821938089、@zhongerxll、@youke2580c、@Leroy-X、@Dark-Tide、@lhjgege、@3harmony、@mgz0227
This commit is contained in:
HapeLee committed 2025-09-01 00:57:17 +08:00
1 parent dccb72adab
commit 8eb4881767
87 files changed
+800 -365

No files matched your search

@@ -31,6 +31,22 @@ import splitties.init.appCtx
@SuppressLint("StaticFieldLeak")
@Suppress("unused")
object AudioPlay : CoroutineScope by MainScope() {
enum class PlayMode(val iconRes: Int) {
LIST_END_STOP(R.drawable.ic_play_mode_list_end_stop),
RANDOM(R.drawable.ic_play_mode_random),
LIST_LOOP(R.drawable.ic_play_mode_list_loop);
fun next(): PlayMode {
return when (this) {
LIST_END_STOP -> RANDOM
RANDOM -> LIST_LOOP
LIST_LOOP -> LIST_END_STOP
}
}
}
var playMode = PlayMode.LIST_END_STOP
var status = Status.STOP
private var activityContext: Context? = null
private var serviceContext: Context? = null
@@ -48,6 +64,11 @@ object AudioPlay : CoroutineScope by MainScope() {
var bookSource: BookSource? = null
val loadingChapters = arrayListOf<Int>()
fun changePlayMode() {
playMode = playMode.next()
postEvent(EventBus.PLAY_MODE_CHANGED, playMode)
}
fun upData(book: Book) {
AudioPlay.book = book
chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
@@ -259,12 +280,32 @@ object AudioPlay : CoroutineScope by MainScope() {
fun next() {
stopPlay()
if (durChapterIndex + 1 < simulatedChapterSize) {
durChapterIndex += 1
durChapterPos = 0
durPlayUrl = ""
saveRead()
loadPlayUrl()
when (playMode) {
PlayMode.LIST_END_STOP -> {
if (durChapterIndex + 1 < simulatedChapterSize) {
durChapterIndex += 1
durChapterPos = 0
durPlayUrl = ""
saveRead()
loadPlayUrl()
}
}
PlayMode.RANDOM -> {
durChapterIndex = (0 until simulatedChapterSize).random()
durChapterPos = 0
durPlayUrl = ""
saveRead()
loadPlayUrl()
}
PlayMode.LIST_LOOP -> {
durChapterIndex = (durChapterIndex + 1) % simulatedChapterSize
durChapterPos = 0
durPlayUrl = ""
saveRead()
loadPlayUrl()
}
}
}
@@ -135,7 +135,6 @@ object BookCover {
/**
* 加载漫画图片
*/
@SuppressLint("CheckResult")
fun loadManga(
context: Context,
path: String?,
@@ -155,8 +154,9 @@ object BookCover {
.skipMemoryCache(true).let {
if (transformation != null) {
it.transform(transformation)
} else {
it
}
it
}
.transition(DrawableTransitionOptions.withCrossFade())
}
@@ -11,6 +11,7 @@ import io.legato.kazusa.data.entities.BookSource
import io.legato.kazusa.exception.ConcurrentException
import io.legato.kazusa.help.book.BookHelp
import io.legato.kazusa.help.book.isLocal
import io.legato.kazusa.help.coroutine.CompositeCoroutine
import io.legato.kazusa.help.coroutine.Coroutine
import io.legato.kazusa.model.webBook.WebBook
import io.legato.kazusa.service.CacheBookService
@@ -96,15 +97,11 @@ object CacheBook {
}
}
fun clear() {
successDownloadSet.clear()
errorDownloadMap.clear()
}
fun close() {
cacheBookMap.forEach { it.value.stop() }
cacheBookMap.clear()
clear()
successDownloadSet.clear()
errorDownloadMap.clear()
}
val downloadSummary: String
@@ -146,6 +143,7 @@ object CacheBook {
private val waitDownloadSet = linkedSetOf<Int>()
private val onDownloadSet = linkedSetOf<Int>()
private val tasks = CompositeCoroutine()
private var isStopped = false
private var waitingRetry = false
@@ -158,7 +156,7 @@ object CacheBook {
@Synchronized
fun isRun(): Boolean {
return waitDownloadSet.size > 0 || onDownloadSet.size > 0
return waitDownloadSet.isNotEmpty() || onDownloadSet.isNotEmpty()
}
@Synchronized
@@ -169,6 +167,7 @@ object CacheBook {
@Synchronized
fun stop() {
waitDownloadSet.clear()
tasks.clear()
isStopped = true
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
}
@@ -309,6 +308,8 @@ object CacheBook {
onCancel(chapterIndex)
}.onFinally {
onFinally()
}.apply {
tasks.add(this)
}.start()
}
@@ -63,20 +63,8 @@ object ReadBook : CoroutineScope by MainScope() {
var isLocalBook = true
var chapterChanged = false
var prevTextChapter: TextChapter? = null
set(value) {
field?.cancelLayout()
field = value
}
var curTextChapter: TextChapter? = null
set(value) {
field?.cancelLayout()
field = value
}
var nextTextChapter: TextChapter? = null
set(value) {
field?.cancelLayout()
field = value
}
var bookSource: BookSource? = null
var msg: String? = null
private val loadingChapters = arrayListOf<Int>()
@@ -222,6 +210,9 @@ object ReadBook : CoroutineScope by MainScope() {
}
fun clearTextChapter() {
prevTextChapter?.cancelLayout()
curTextChapter?.cancelLayout()
nextTextChapter?.cancelLayout()
prevTextChapter = null
curTextChapter = null
nextTextChapter = null
@@ -333,6 +324,7 @@ object ReadBook : CoroutineScope by MainScope() {
if (durChapterIndex < simulatedChapterSize - 1) {
durChapterPos = 0
durChapterIndex++
prevTextChapter?.cancelLayout()
prevTextChapter = curTextChapter
curTextChapter = nextTextChapter
nextTextChapter = null
@@ -363,6 +355,7 @@ object ReadBook : CoroutineScope by MainScope() {
if (durChapterIndex < simulatedChapterSize - 1) {
durChapterPos = 0
durChapterIndex++
prevTextChapter?.cancelLayout()
prevTextChapter = curTextChapter
curTextChapter = nextTextChapter
nextTextChapter = null
@@ -394,6 +387,7 @@ object ReadBook : CoroutineScope by MainScope() {
if (durChapterIndex > 0) {
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
durChapterIndex--
nextTextChapter?.cancelLayout()
nextTextChapter = curTextChapter
curTextChapter = prevTextChapter
prevTextChapter = null
@@ -703,6 +697,7 @@ object ReadBook : CoroutineScope by MainScope() {
)
when (val offset = chapter.index - durChapterIndex) {
0 -> {
curTextChapter?.cancelLayout()
curTextChapter = textChapter
callBack?.upMenuView()
var available = false
@@ -727,12 +722,14 @@ object ReadBook : CoroutineScope by MainScope() {
}
-1 -> {
prevTextChapter?.cancelLayout()
prevTextChapter = textChapter
textChapter.layoutChannel.receiveAsFlow().collect()
if (upContent) callBack?.upContent(offset, resetPageOffset)
}
1 -> {
nextTextChapter?.cancelLayout()
nextTextChapter = textChapter
for (page in textChapter.layoutChannel) {
if (page.index > 1) {
@@ -776,6 +773,7 @@ object ReadBook : CoroutineScope by MainScope() {
)
when (val offset = chapter.index - durChapterIndex) {
0 -> {
curTextChapter?.cancelLayout()
curTextChapter = textChapter
callBack?.upMenuView()
var available = false
@@ -800,12 +798,14 @@ object ReadBook : CoroutineScope by MainScope() {
}
-1 -> {
prevTextChapter?.cancelLayout()
prevTextChapter = textChapter
textChapter.layoutChannel.receiveAsFlow().collect()
if (upContent) callBack?.upContent(offset, resetPageOffset)
}
1 -> {
nextTextChapter?.cancelLayout()
nextTextChapter = textChapter
for (page in textChapter.layoutChannel) {
if (page.index > 1) {
@@ -858,21 +858,25 @@ object ReadBook : CoroutineScope by MainScope() {
fun saveRead(pageChanged: Boolean = false) {
executor.execute {
val book = book ?: return@execute
book.lastCheckCount = 0
book.durChapterTime = System.currentTimeMillis()
val chapterChanged = book.durChapterIndex != durChapterIndex
book.durChapterIndex = durChapterIndex
book.durChapterPos = durChapterPos
if (!pageChanged || chapterChanged) {
appDb.bookChapterDao.getChapter(book.bookUrl, durChapterIndex)?.let {
book.durChapterTitle = it.getDisplayTitle(
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules(),
book.getUseReplaceRule()
)
kotlin.runCatching {
val book = book ?: return@execute
book.lastCheckCount = 0
book.durChapterTime = System.currentTimeMillis()
val chapterChanged = book.durChapterIndex != durChapterIndex
book.durChapterIndex = durChapterIndex
book.durChapterPos = durChapterPos
if (!pageChanged || chapterChanged) {
appDb.bookChapterDao.getChapter(book.bookUrl, durChapterIndex)?.let {
book.durChapterTitle = it.getDisplayTitle(
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules(),
book.getUseReplaceRule()
)
}
}
appDb.bookDao.update(book)
}.onFailure {
AppLog.put("保存书籍阅读进度信息出错\n$it", it)
}
appDb.bookDao.update(book)
}
}
@@ -327,21 +327,25 @@ object ReadManga : CoroutineScope by MainScope() {
fun saveRead(pageChanged: Boolean = false) {
executor.execute {
val book = book ?: return@execute
book.lastCheckCount = 0
book.durChapterTime = System.currentTimeMillis()
val chapterChanged = book.durChapterIndex != durChapterIndex
book.durChapterIndex = durChapterIndex
book.durChapterPos = durChapterPos
if (!pageChanged || chapterChanged) {
appDb.bookChapterDao.getChapter(book.bookUrl, durChapterIndex)?.let {
book.durChapterTitle = it.getDisplayTitle(
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules(),
book.getUseReplaceRule()
)
kotlin.runCatching {
val book = book ?: return@execute
book.lastCheckCount = 0
book.durChapterTime = System.currentTimeMillis()
val chapterChanged = book.durChapterIndex != durChapterIndex
book.durChapterIndex = durChapterIndex
book.durChapterPos = durChapterPos
if (!pageChanged || chapterChanged) {
appDb.bookChapterDao.getChapter(book.bookUrl, durChapterIndex)?.let {
book.durChapterTitle = it.getDisplayTitle(
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules(),
book.getUseReplaceRule()
)
}
}
appDb.bookDao.update(book)
}.onFailure {
AppLog.put("保存漫画阅读进度信息出错\n$it", it)
}
appDb.bookDao.update(book)
}
}
@@ -39,7 +39,6 @@ import io.legato.kazusa.help.http.postForm
import io.legato.kazusa.help.http.postJson
import io.legato.kazusa.help.http.postMultipart
import io.legato.kazusa.help.source.getShareScope
import io.legato.kazusa.model.Debug
import io.legato.kazusa.utils.EncoderUtils
import io.legato.kazusa.utils.GSON
import io.legato.kazusa.utils.GSONStrict
@@ -225,32 +224,32 @@ class AnalyzeUrl(
if (urlOption == null) {
urlOption = GSON.fromJsonObject<UrlOption>(urlOptionStr).getOrNull()
if (urlOption != null) {
Debug.log("≡链接参数 JSON 格式不规范,请改为规范格式")
log("链接参数 JSON 格式不规范,请改为规范格式")
}
}
urlOption?.let { option ->
option.getMethod()?.let {
if (it.equals("POST", true)) method = RequestMethod.POST
}
option.getHeaderMap()?.forEach { entry ->
headerMap[entry.key.toString()] = entry.value.toString()
}
option.getBody()?.let {
body = it
}
type = option.getType()
charset = option.getCharset()
retry = option.getRetry()
useWebView = option.useWebView()
webJs = option.getWebJs()
option.getJs()?.let { jsStr ->
evalJS(jsStr, url)?.toString()?.let {
url = it
}
option.getMethod()?.let {
if (it.equals("POST", true)) method = RequestMethod.POST
}
option.getHeaderMap()?.forEach { entry ->
headerMap[entry.key.toString()] = entry.value.toString()
}
option.getBody()?.let {
body = it
}
type = option.getType()
charset = option.getCharset()
retry = option.getRetry()
useWebView = option.useWebView()
webJs = option.getWebJs()
option.getJs()?.let { jsStr ->
evalJS(jsStr, url)?.toString()?.let {
url = it
}
serverID = option.getServerID()
webViewDelayTime = max(0, option.getWebViewDelayTime() ?: 0)
}
}
}
urlNoQuery = url
when (method) {
@@ -458,7 +457,7 @@ class AnalyzeUrl(
else -> get(urlNoQuery, encodedQuery)
}
}.let {
val isXml = it.raw.body?.contentType()?.toString()
val isXml = it.raw.body.contentType()?.toString()
?.matches(AppPattern.xmlContentTypeRegex) == true
if (isXml && it.body?.trim()?.startsWith("<?xml", true) == false) {
StrResponse(it.raw, "<?xml version=\"1.0\"?>" + it.body)
@@ -553,7 +552,7 @@ class AnalyzeUrl(
getByteArrayIfDataUri()?.let {
return it
}
return getResponseAwait().body!!.bytes()
return getResponseAwait().body.bytes()
}
fun getByteArray(): ByteArray {
@@ -569,7 +568,7 @@ class AnalyzeUrl(
getByteArrayIfDataUri()?.let {
return ByteArrayInputStream(it)
}
return getResponseAwait().body!!.byteStream()
return getResponseAwait().body.byteStream()
}
fun getInputStream(): InputStream {
@@ -2,6 +2,7 @@ package io.legato.kazusa.model.localBook
import android.net.Uri
import android.util.Base64
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import com.script.ScriptBindings
import com.script.rhino.RhinoScriptEngine
@@ -103,7 +104,7 @@ object LocalBook {
fun getLastModified(book: Book): Result<Long> {
return kotlin.runCatching {
val uri = Uri.parse(book.bookUrl)
val uri = book.bookUrl.toUri()
if (uri.isContentScheme()) {
return@runCatching DocumentFile.fromSingleUri(appCtx, uri)!!.lastModified()
}
@@ -144,6 +145,9 @@ object LocalBook {
val list = ArrayList(LinkedHashSet(chapters))
list.forEachIndexed { index, bookChapter ->
bookChapter.index = index
if (bookChapter.title.isEmpty()) {
bookChapter.title = "无标题章节"
}
}
val replaceRules = ContentProcessor.get(book).getTitleReplaceRules()
book.durChapterTitle = list.getOrElse(book.durChapterIndex) { list.last() }
@@ -191,6 +195,9 @@ object LocalBook {
return StringEscapeUtils.unescapeHtml4(content)
}
}
if (content.isNullOrEmpty()) {
return null
}
return content
}
@@ -374,7 +381,7 @@ object LocalBook {
}
if (deleteOriginal) {
if (book.bookUrl.isContentScheme()) {
val uri = Uri.parse(book.bookUrl)
val uri = book.bookUrl.toUri()
DocumentFile.fromSingleUri(appCtx, uri)?.delete()
} else {
FileUtils.delete(book.bookUrl)
@@ -419,7 +426,7 @@ object LocalBook {
inputStream.use {
val defaultBookTreeUri = AppConfig.defaultBookTreeUri
if (defaultBookTreeUri.isNullOrBlank()) throw NoBooksDirException()
val treeUri = Uri.parse(defaultBookTreeUri)
val treeUri = defaultBookTreeUri.toUri()
return if (treeUri.isContentScheme()) {
val treeDoc = DocumentFile.fromTreeUri(appCtx, treeUri)
var doc = treeDoc!!.findFile(fileName)
@@ -229,7 +229,7 @@ class MobiFile(var book: Book) {
break
}
section = section.next ?: break
if (section.linear) {
if (!section.linear) {
continue
}
if (section.href == nextSectionHref) {
@@ -215,6 +215,7 @@ class TextFile(private var book: Book) {
val curChapter = BookChapter()
curChapter.title = matcher.group()
curChapter.start = curOffset + chapterLength
curChapter.end = curChapter.start
toc.add(curChapter)
lastChapterWordCount = 0
} else if (seekPos == 0 && chapterStart != 0) {
@@ -242,6 +243,7 @@ class TextFile(private var book: Book) {
val curChapter = BookChapter()
curChapter.title = matcher.group()
curChapter.start = curOffset + chapterLength
curChapter.end = curChapter.start
toc.add(curChapter)
} else { //否则就block分割之后,上一个章节的剩余内容
//获取上一章节
@@ -257,6 +259,7 @@ class TextFile(private var book: Book) {
val curChapter = BookChapter()
curChapter.title = matcher.group()
curChapter.start = lastChapter.end
curChapter.end = curChapter.start
toc.add(curChapter)
}
bookWordCount += chapterContent.length
@@ -275,6 +278,7 @@ class TextFile(private var book: Book) {
val curChapter = BookChapter()
curChapter.title = matcher.group()
curChapter.start = lastChapter.end
curChapter.end = curChapter.start
toc.add(curChapter)
} else { //如果章节不存在则创建章节
val curChapter = BookChapter()
@@ -17,8 +17,10 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
@@ -39,7 +41,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
private var bookSourceParts = emptyList<BookSourcePart>()
private var searchBooks = arrayListOf<SearchBook>()
private var searchJob: Job? = null
private var workingState = MutableStateFlow(true)
private fun initSearchPool() {
searchPool?.close()
@@ -82,6 +84,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
bs.getBookSource()?.let {
emit(it)
}
workingState.first { it }
}
}.onStart {
callBack.onSearchStart()
@@ -191,6 +194,14 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
mSearchId = 0L
}
fun pause() {
workingState.value = false
}
fun resume() {
workingState.value = true
}
interface CallBack {
fun getSearchScope(): SearchScope
fun onSearchStart()