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.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.sync.Semaphore
import kotlin.coroutines.CoroutineContext
abstract class BaseService : LifecycleService() {
@@ -28,8 +29,9 @@ abstract class BaseService : LifecycleService() {
context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> T
) = Coroutine.async(scope, context, start, executeContext, block)
) = Coroutine.async(scope, context, start, executeContext, semaphore, block)
@CallSuper
override fun onCreate() {
@@ -10,6 +10,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Semaphore
import kotlin.coroutines.CoroutineContext
@Suppress("unused")
@@ -22,18 +23,22 @@ open class BaseViewModel(application: Application) : AndroidViewModel(applicatio
context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> T
): Coroutine<T> {
return Coroutine.async(scope, context, start, executeContext, block)
return Coroutine.async(scope, context, start, executeContext, semaphore, block)
}
fun <T> executeLazy(
scope: CoroutineScope = viewModelScope,
context: CoroutineContext = Dispatchers.IO,
executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> 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(
@@ -132,6 +132,9 @@ interface BookDao {
@Query("select exists(select 1 from books where bookUrl = :bookUrl)")
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(
"""select exists(select 1 from books where type & ${BookType.local} > 0
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.launch
import kotlinx.coroutines.plus
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import kotlin.coroutines.CoroutineContext
@@ -23,10 +24,11 @@ import kotlin.coroutines.CoroutineContext
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
class Coroutine<T>(
val scope: CoroutineScope,
private val scope: CoroutineScope,
context: CoroutineContext = Dispatchers.IO,
val startOption: CoroutineStart = CoroutineStart.DEFAULT,
val executeContext: CoroutineContext = Dispatchers.Main,
private val startOption: CoroutineStart = CoroutineStart.DEFAULT,
private val executeContext: CoroutineContext = Dispatchers.Main,
private val semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> T
) {
@@ -39,9 +41,10 @@ class Coroutine<T>(
context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> 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
): Job {
return (scope.plus(executeContext)).launch(start = startOption) {
semaphore?.acquire()
try {
start?.let { dispatchVoidCallback(this, it) }
ensureActive()
@@ -185,7 +189,11 @@ class Coroutine<T>(
error?.let { dispatchCallback(this, e, it) }
}
} 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.ensureActive
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
@@ -283,12 +282,14 @@ object WebBook {
executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
): Coroutine<String> {
return Coroutine.async(scope, context, start = start, executeContext = executeContext) {
semaphore?.withPermit {
getContentAwait(bookSource, book, bookChapter, nextChapterUrl, needSave)
} ?: run {
getContentAwait(bookSource, book, bookChapter, nextChapterUrl, needSave)
}
return Coroutine.async(
scope,
context,
start = start,
executeContext = executeContext,
semaphore = semaphore
) {
getContentAwait(bookSource, book, bookChapter, nextChapterUrl, needSave)
}
}
@@ -360,8 +361,9 @@ object WebBook {
name: String,
author: String,
context: CoroutineContext = Dispatchers.IO,
semaphore: Semaphore? = null,
): Coroutine<Pair<Book, BookSource>> {
return Coroutine.async(scope, context) {
return Coroutine.async(scope, context, semaphore = semaphore) {
for (s in bookSourceParts) {
val source = s.getBookSource() ?: continue
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.BookSourcePart
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.http.decompressed
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.printOnDebug
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.FileOutputStream
import java.io.OutputStreamWriter
@@ -154,21 +156,26 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
private fun importBookshelfByJson(json: String, groupId: Long) {
execute {
val bookSourceParts = appDb.bookSourceDao.allEnabledPart
val semaphore = Semaphore(AppConfig.threadCount + 1)
GSON.fromJsonArray<Map<String, String?>>(json).getOrThrow().forEach { bookInfo ->
if (!isActive) return@execute
val name = bookInfo["name"] ?: ""
val author = bookInfo["author"] ?: ""
if (name.isNotEmpty() && appDb.bookDao.getBook(name, author) == null) {
WebBook.preciseSearch(this, bookSourceParts, name, author)
.onSuccess {
val book = it.first
if (groupId > 0) {
book.group = groupId
}
book.save()
}.onError { e ->
context.toastOnUi(e.localizedMessage)
if (name.isEmpty() || appDb.bookDao.has(name, author)) {
return@forEach
}
semaphore.withPermit {
WebBook.preciseSearch(
this, bookSourceParts, name, author,
semaphore = semaphore
).onSuccess {
val book = it.first
if (groupId > 0) {
book.group = groupId
}
book.save()
}.onError { e ->
context.toastOnUi(e.localizedMessage)
}
}
}
}.onError {