Horis 1 an în urmă
părinte
comite
65995bcedd

+ 3 - 1
app/src/main/java/io/legado/app/base/BaseService.kt

@@ -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() {

+ 7 - 2
app/src/main/java/io/legado/app/base/BaseViewModel.kt

@@ -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(

+ 3 - 0
app/src/main/java/io/legado/app/data/dao/BookDao.kt

@@ -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 - 5
app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt

@@ -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,
-    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
     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()
+                }
             }
             }
         }
         }
     }
     }

+ 10 - 8
app/src/main/java/io/legado/app/model/webBook/WebBook.kt

@@ -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) {
-            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,
         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()

+ 19 - 12
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt

@@ -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) {
-                    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 {
         }.onError {