This commit is contained in:
Horis
2025-04-28 11:11:08 +08:00
parent 57fac675a5
commit 7bd764a7f3
6 changed files with 55 additions and 28 deletions
@@ -16,6 +16,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.sync.Semaphore
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
abstract class BaseService : LifecycleService() { abstract class BaseService : LifecycleService() {
@@ -28,8 +29,9 @@ abstract class BaseService : LifecycleService() {
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main, executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
) = Coroutine.async(scope, context, start, executeContext, block) ) = Coroutine.async(scope, context, start, executeContext, semaphore, block)
@CallSuper @CallSuper
override fun onCreate() { override fun onCreate() {
@@ -10,6 +10,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Semaphore
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@Suppress("unused") @Suppress("unused")
@@ -22,18 +23,22 @@ open class BaseViewModel(application: Application) : AndroidViewModel(applicatio
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main, executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
): Coroutine<T> { ): Coroutine<T> {
return Coroutine.async(scope, context, start, executeContext, block) return Coroutine.async(scope, context, start, executeContext, semaphore, block)
} }
fun <T> executeLazy( fun <T> executeLazy(
scope: CoroutineScope = viewModelScope, scope: CoroutineScope = viewModelScope,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
executeContext: CoroutineContext = Dispatchers.Main, executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
): Coroutine<T> { ): Coroutine<T> {
return Coroutine.async(scope, context, CoroutineStart.LAZY, executeContext, block) return Coroutine.async(
scope, context, CoroutineStart.LAZY, executeContext, semaphore, block
)
} }
fun <R> submit( fun <R> submit(
@@ -132,6 +132,9 @@ interface BookDao {
@Query("select exists(select 1 from books where bookUrl = :bookUrl)") @Query("select exists(select 1 from books where bookUrl = :bookUrl)")
fun has(bookUrl: String): Boolean fun has(bookUrl: String): Boolean
@Query("select exists(select 1 from books where name = :name and author = :author)")
fun has(name: String, author: String): Boolean
@Query( @Query(
"""select exists(select 1 from books where type & ${BookType.local} > 0 """select exists(select 1 from books where type & ${BookType.local} > 0
and (originName = :fileName or (origin != '${BookType.localTag}' and origin like '%' || :fileName)))""" and (originName = :fileName or (origin != '${BookType.localTag}' and origin like '%' || :fileName)))"""
@@ -13,6 +13,7 @@ import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@@ -23,10 +24,11 @@ import kotlin.coroutines.CoroutineContext
*/ */
@Suppress("unused", "MemberVisibilityCanBePrivate") @Suppress("unused", "MemberVisibilityCanBePrivate")
class Coroutine<T>( class Coroutine<T>(
val scope: CoroutineScope, private val scope: CoroutineScope,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
val startOption: CoroutineStart = CoroutineStart.DEFAULT, private val startOption: CoroutineStart = CoroutineStart.DEFAULT,
val executeContext: CoroutineContext = Dispatchers.Main, private val executeContext: CoroutineContext = Dispatchers.Main,
private val semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
) { ) {
@@ -39,9 +41,10 @@ class Coroutine<T>(
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main, executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
): Coroutine<T> { ): Coroutine<T> {
return Coroutine(scope, context, start, executeContext, block) return Coroutine(scope, context, start, executeContext, semaphore, block)
} }
} }
@@ -169,6 +172,7 @@ class Coroutine<T>(
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
): Job { ): Job {
return (scope.plus(executeContext)).launch(start = startOption) { return (scope.plus(executeContext)).launch(start = startOption) {
semaphore?.acquire()
try { try {
start?.let { dispatchVoidCallback(this, it) } start?.let { dispatchVoidCallback(this, it) }
ensureActive() ensureActive()
@@ -185,7 +189,11 @@ class Coroutine<T>(
error?.let { dispatchCallback(this, e, it) } error?.let { dispatchCallback(this, e, it) }
} }
} finally { } finally {
finally?.let { dispatchVoidCallback(this, it) } try {
finally?.let { dispatchVoidCallback(this, it) }
} finally {
semaphore?.release()
}
} }
} }
} }
@@ -22,7 +22,6 @@ import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
@@ -283,12 +282,14 @@ object WebBook {
executeContext: CoroutineContext = Dispatchers.Main, executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null, semaphore: Semaphore? = null,
): Coroutine<String> { ): Coroutine<String> {
return Coroutine.async(scope, context, start = start, executeContext = executeContext) { return Coroutine.async(
semaphore?.withPermit { scope,
getContentAwait(bookSource, book, bookChapter, nextChapterUrl, needSave) context,
} ?: run { start = start,
getContentAwait(bookSource, book, bookChapter, nextChapterUrl, needSave) executeContext = executeContext,
} semaphore = semaphore
) {
getContentAwait(bookSource, book, bookChapter, nextChapterUrl, needSave)
} }
} }
@@ -360,8 +361,9 @@ object WebBook {
name: String, name: String,
author: String, author: String,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
semaphore: Semaphore? = null,
): Coroutine<Pair<Book, BookSource>> { ): Coroutine<Pair<Book, BookSource>> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context, semaphore = semaphore) {
for (s in bookSourceParts) { for (s in bookSourceParts) {
val source = s.getBookSource() ?: continue val source = s.getBookSource() ?: continue
val book = preciseSearchAwait(source, name, author).getOrNull() val book = preciseSearchAwait(source, name, author).getOrNull()
@@ -10,6 +10,7 @@ import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSourcePart import io.legado.app.data.entities.BookSourcePart
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.http.decompressed import io.legado.app.help.http.decompressed
import io.legado.app.help.http.newCallResponseBody import io.legado.app.help.http.newCallResponseBody
@@ -24,7 +25,8 @@ import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.isJsonArray import io.legado.app.utils.isJsonArray
import io.legado.app.utils.printOnDebug import io.legado.app.utils.printOnDebug
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.isActive import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.OutputStreamWriter import java.io.OutputStreamWriter
@@ -154,21 +156,26 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
private fun importBookshelfByJson(json: String, groupId: Long) { private fun importBookshelfByJson(json: String, groupId: Long) {
execute { execute {
val bookSourceParts = appDb.bookSourceDao.allEnabledPart val bookSourceParts = appDb.bookSourceDao.allEnabledPart
val semaphore = Semaphore(AppConfig.threadCount + 1)
GSON.fromJsonArray<Map<String, String?>>(json).getOrThrow().forEach { bookInfo -> GSON.fromJsonArray<Map<String, String?>>(json).getOrThrow().forEach { bookInfo ->
if (!isActive) return@execute
val name = bookInfo["name"] ?: "" val name = bookInfo["name"] ?: ""
val author = bookInfo["author"] ?: "" val author = bookInfo["author"] ?: ""
if (name.isNotEmpty() && appDb.bookDao.getBook(name, author) == null) { if (name.isEmpty() || appDb.bookDao.has(name, author)) {
WebBook.preciseSearch(this, bookSourceParts, name, author) return@forEach
.onSuccess { }
val book = it.first semaphore.withPermit {
if (groupId > 0) { WebBook.preciseSearch(
book.group = groupId this, bookSourceParts, name, author,
} semaphore = semaphore
book.save() ).onSuccess {
}.onError { e -> val book = it.first
context.toastOnUi(e.localizedMessage) if (groupId > 0) {
book.group = groupId
} }
book.save()
}.onError { e ->
context.toastOnUi(e.localizedMessage)
}
} }
} }
}.onError { }.onError {