Fix coroutines cancellation
This commit is contained in:
3
.idea/kotlinc.xml
generated
3
.idea/kotlinc.xml
generated
@@ -3,4 +3,7 @@
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="1.8" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.6.21" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 497
|
||||
versionName '4.0-beta1'
|
||||
versionCode 498
|
||||
versionName '4.0-beta2'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package org.koitharu.kotatsu.base.domain
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
|
||||
fun interface ReversibleHandle {
|
||||
|
||||
@@ -11,8 +14,10 @@ fun interface ReversibleHandle {
|
||||
}
|
||||
|
||||
fun ReversibleHandle.reverseAsync() = processLifecycleScope.launch(Dispatchers.Default) {
|
||||
runCatching {
|
||||
reverse()
|
||||
runCatchingCancellable {
|
||||
withContext(NonCancellable) {
|
||||
reverse()
|
||||
}
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
}
|
||||
@@ -21,4 +26,4 @@ fun ReversibleHandle.reverseAsync() = processLifecycleScope.launch(Dispatchers.D
|
||||
operator fun ReversibleHandle.plus(other: ReversibleHandle) = ReversibleHandle {
|
||||
this.reverse()
|
||||
other.reverse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package org.koitharu.kotatsu.core.backup
|
||||
|
||||
import androidx.room.withTransaction
|
||||
import javax.inject.Inject
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.parsers.util.json.JSONIterator
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val PAGE_SIZE = 10
|
||||
|
||||
@@ -85,7 +86,7 @@ class BackupRepository @Inject constructor(private val db: MangaDatabase) {
|
||||
JsonDeserializer(it).toTagEntity()
|
||||
}
|
||||
val history = JsonDeserializer(item).toHistoryEntity()
|
||||
result += runCatching {
|
||||
result += runCatchingCancellable {
|
||||
db.withTransaction {
|
||||
db.tagsDao.upsert(tags)
|
||||
db.mangaDao.upsert(manga, tags)
|
||||
@@ -100,7 +101,7 @@ class BackupRepository @Inject constructor(private val db: MangaDatabase) {
|
||||
val result = CompositeResult()
|
||||
for (item in entry.data.JSONIterator()) {
|
||||
val category = JsonDeserializer(item).toFavouriteCategoryEntity()
|
||||
result += runCatching {
|
||||
result += runCatchingCancellable {
|
||||
db.favouriteCategoriesDao.upsert(category)
|
||||
}
|
||||
}
|
||||
@@ -116,7 +117,7 @@ class BackupRepository @Inject constructor(private val db: MangaDatabase) {
|
||||
JsonDeserializer(it).toTagEntity()
|
||||
}
|
||||
val favourite = JsonDeserializer(item).toFavouriteEntity()
|
||||
result += runCatching {
|
||||
result += runCatchingCancellable {
|
||||
db.withTransaction {
|
||||
db.tagsDao.upsert(tags)
|
||||
db.mangaDao.upsert(manga, tags)
|
||||
|
||||
@@ -4,13 +4,6 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -24,6 +17,14 @@ import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
|
||||
import org.koitharu.kotatsu.parsers.util.parseJsonArray
|
||||
import org.koitharu.kotatsu.utils.ext.asArrayList
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val CERT_SHA1 = "2C:19:C7:E8:07:61:2B:8E:94:51:1B:FD:72:67:07:64:5D:C2:58:AE"
|
||||
|
||||
@@ -59,7 +60,7 @@ class AppUpdateRepository @Inject constructor(
|
||||
if (!isUpdateSupported()) {
|
||||
return@withContext null
|
||||
}
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
val currentVersion = VersionId(BuildConfig.VERSION_NAME)
|
||||
val available = getAvailableVersions().asArrayList()
|
||||
available.sortBy { it.versionId }
|
||||
|
||||
@@ -17,8 +17,6 @@ import coil.request.ImageRequest
|
||||
import coil.size.Precision
|
||||
import coil.size.Scale
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -32,6 +30,9 @@ import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.requireBitmap
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ShortcutsUpdater @Inject constructor(
|
||||
@@ -92,7 +93,7 @@ class ShortcutsUpdater @Inject constructor(
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||
private suspend fun updateShortcutsImpl() = runCatching {
|
||||
private suspend fun updateShortcutsImpl() = runCatchingCancellable {
|
||||
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
|
||||
val shortcuts = historyRepository.getList(0, manager.maxShortcutCountPerActivity)
|
||||
.filter { x -> x.title.isNotEmpty() }
|
||||
@@ -112,7 +113,7 @@ class ShortcutsUpdater @Inject constructor(
|
||||
}
|
||||
|
||||
private suspend fun buildShortcutInfo(manga: Manga): ShortcutInfoCompat.Builder {
|
||||
val icon = runCatching {
|
||||
val icon = runCatchingCancellable {
|
||||
coil.execute(
|
||||
ImageRequest.Builder(context)
|
||||
.data(manga.coverUrl)
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
|
||||
class DetailsViewModel @AssistedInject constructor(
|
||||
@Assisted intent: MangaIntent,
|
||||
@@ -189,7 +190,7 @@ class DetailsViewModel @AssistedInject constructor(
|
||||
checkNotNull(manga) { "Cannot find saved manga for ${m.title}" }
|
||||
val original = localMangaRepository.getRemoteManga(manga)
|
||||
localMangaRepository.delete(manga) || throw IOException("Unable to delete file")
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
historyRepository.deleteOrSwap(manga, original)
|
||||
}
|
||||
onMangaRemoved.postCall(manga)
|
||||
@@ -228,7 +229,7 @@ class DetailsViewModel @AssistedInject constructor(
|
||||
reload()
|
||||
} else {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
localMangaRepository.getDetails(downloadedManga)
|
||||
}.onSuccess {
|
||||
delegate.relatedManga.value = it
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
|
||||
class MangaDetailsDelegate(
|
||||
private val intent: MangaIntent,
|
||||
@@ -45,9 +46,9 @@ class MangaDetailsDelegate(
|
||||
val hist = historyRepository.getOne(manga)
|
||||
selectedBranch.value = manga.getPreferredBranch(hist)
|
||||
mangaData.value = manga
|
||||
relatedManga.value = runCatching {
|
||||
relatedManga.value = runCatchingCancellable {
|
||||
if (manga.source == MangaSource.LOCAL) {
|
||||
val m = localMangaRepository.getRemoteManga(manga) ?: return@runCatching null
|
||||
val m = localMangaRepository.getRemoteManga(manga) ?: return@runCatchingCancellable null
|
||||
mangaRepositoryFactory.create(m.source).getDetails(m)
|
||||
} else {
|
||||
localMangaRepository.findSavedManga(manga)
|
||||
|
||||
@@ -9,11 +9,18 @@ import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.internal.closeQuietly
|
||||
@@ -32,7 +39,9 @@ import org.koitharu.kotatsu.parsers.util.await
|
||||
import org.koitharu.kotatsu.utils.ext.deleteAwait
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.referer
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.utils.progress.PausingProgressJob
|
||||
import java.io.File
|
||||
|
||||
private const val MAX_FAILSAFE_ATTEMPTS = 2
|
||||
private const val DOWNLOAD_ERROR_DELAY = 500L
|
||||
@@ -231,7 +240,7 @@ class DownloadManager @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadCover(manga: Manga) = runCatching {
|
||||
private suspend fun loadCover(manga: Manga) = runCatchingCancellable {
|
||||
imageLoader.execute(
|
||||
ImageRequest.Builder(context)
|
||||
.data(manga.coverUrl)
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
|
||||
class FavouritesListViewModel @AssistedInject constructor(
|
||||
@Assisted private val categoryId: Long,
|
||||
@@ -69,6 +70,7 @@ class FavouritesListViewModel @AssistedInject constructor(
|
||||
actionStringRes = 0,
|
||||
),
|
||||
)
|
||||
|
||||
else -> list.toUi(mode, this)
|
||||
}
|
||||
}.catch {
|
||||
@@ -79,7 +81,7 @@ class FavouritesListViewModel @AssistedInject constructor(
|
||||
if (categoryId != NO_ID) {
|
||||
launchJob {
|
||||
categoryName = withContext(Dispatchers.Default) {
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
repository.getCategory(categoryId).title
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
@@ -6,15 +6,22 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
import java.util.TreeSet
|
||||
|
||||
class FilterCoordinator(
|
||||
private val repository: RemoteMangaRepository,
|
||||
@@ -153,7 +160,7 @@ class FilterCoordinator(
|
||||
}
|
||||
|
||||
private fun loadTagsAsync() = coroutineScope.async(Dispatchers.Default, CoroutineStart.LAZY) {
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
repository.getTags()
|
||||
}.onFailure { error ->
|
||||
error.printStackTraceDebug()
|
||||
@@ -204,4 +211,4 @@ class FilterCoordinator(
|
||||
return collator?.compare(t1, t2) ?: compareValues(t1, t2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,28 +7,40 @@ import androidx.annotation.WorkerThread
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filterNot
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.local.data.CbzFilter
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import org.koitharu.kotatsu.local.data.MangaIndex
|
||||
import org.koitharu.kotatsu.local.data.TempFileFilter
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.toCamelCase
|
||||
import org.koitharu.kotatsu.utils.AlphanumComparator
|
||||
import org.koitharu.kotatsu.utils.CompositeMutex
|
||||
import org.koitharu.kotatsu.utils.ext.deleteAwait
|
||||
import org.koitharu.kotatsu.utils.ext.longHashCode
|
||||
import org.koitharu.kotatsu.utils.ext.readText
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import java.io.File
|
||||
import java.util.Enumeration
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
private const val MAX_PARALLELISM = 4
|
||||
|
||||
@@ -73,6 +85,7 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
|
||||
manga.source != MangaSource.LOCAL -> requireNotNull(findSavedManga(manga)) {
|
||||
"Manga is not local or saved"
|
||||
}
|
||||
|
||||
else -> getFromFile(Uri.parse(manga.url).toFile())
|
||||
}
|
||||
|
||||
@@ -236,7 +249,7 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
|
||||
context: CoroutineContext,
|
||||
): Deferred<LocalManga?> = async(context) {
|
||||
runInterruptible {
|
||||
runCatching { LocalManga(getFromFile(file), file) }.getOrNull()
|
||||
runCatchingCancellable { LocalManga(getFromFile(file), file) }.getOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
|
||||
@HiltViewModel
|
||||
class LocalListViewModel @Inject constructor(
|
||||
@@ -63,6 +64,7 @@ class LocalListViewModel @Inject constructor(
|
||||
actionStringRes = R.string._import,
|
||||
),
|
||||
)
|
||||
|
||||
else -> buildList(list.size + 1) {
|
||||
add(createHeader(list, tags, order))
|
||||
list.toUi(this, mode, this@LocalListViewModel)
|
||||
@@ -104,7 +106,7 @@ class LocalListViewModel @Inject constructor(
|
||||
for (manga in itemsToRemove) {
|
||||
val original = repository.getRemoteManga(manga)
|
||||
repository.delete(manga) || throw IOException("Unable to delete file")
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
historyRepository.deleteOrSwap(manga, original)
|
||||
}
|
||||
mangaList.update { list ->
|
||||
@@ -120,6 +122,8 @@ class LocalListViewModel @Inject constructor(
|
||||
try {
|
||||
listError.value = null
|
||||
mangaList.value = repository.getList(0, selectedTags.value, sortOrder.value)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Throwable) {
|
||||
listError.value = e
|
||||
}
|
||||
@@ -128,7 +132,7 @@ class LocalListViewModel @Inject constructor(
|
||||
private fun cleanup() {
|
||||
if (!DownloadService.isRunning && !ImportService.isRunning && !LocalChaptersRemoveService.isRunning) {
|
||||
viewModelScope.launch {
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
repository.cleanup()
|
||||
}.onFailure { error ->
|
||||
error.printStackTraceDebug()
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.requireValue
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
|
||||
private const val BOUNDS_PAGE_OFFSET = 2
|
||||
private const val PREFETCH_LIMIT = 10
|
||||
@@ -326,7 +327,7 @@ class ReaderViewModel @AssistedInject constructor(
|
||||
?: manga.chapters?.randomOrNull()
|
||||
?: error("There are no chapters in this manga")
|
||||
val pages = repo.getPages(chapter)
|
||||
return runCatching {
|
||||
return runCatchingCancellable {
|
||||
val isWebtoon = dataRepository.determineMangaIsWebtoon(repo, pages)
|
||||
if (isWebtoon) ReaderMode.WEBTOON else defaultMode
|
||||
}.onSuccess {
|
||||
@@ -381,7 +382,7 @@ class ReaderViewModel @AssistedInject constructor(
|
||||
*/
|
||||
private fun HistoryRepository.saveStateAsync(manga: Manga, state: ReaderState, percent: Float): Job {
|
||||
return processLifecycleScope.launch(Dispatchers.Default) {
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
addOrUpdate(
|
||||
manga = manga,
|
||||
chapterId = state.chapterId,
|
||||
|
||||
@@ -4,17 +4,23 @@ import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.Observer
|
||||
import com.davemorrissey.labs.subscaleview.DefaultOnImageEventListener
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class PageHolderDelegate(
|
||||
private val loader: PageLoader,
|
||||
@@ -102,6 +108,8 @@ class PageHolderDelegate(
|
||||
loader.convertInPlace(file)
|
||||
state = State.CONVERTED
|
||||
callback.onImageReady(file.toUri())
|
||||
} catch (ce: CancellationException) {
|
||||
throw ce
|
||||
} catch (e2: Throwable) {
|
||||
e.addSuppressed(e2)
|
||||
state = State.ERROR
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
|
||||
import org.koitharu.kotatsu.utils.ext.decodeRegion
|
||||
import org.koitharu.kotatsu.utils.ext.isLowRamDevice
|
||||
import org.koitharu.kotatsu.utils.ext.referer
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.utils.ext.setTextColorAttr
|
||||
|
||||
fun pageThumbnailAD(
|
||||
@@ -72,7 +73,7 @@ fun pageThumbnailAD(
|
||||
text = (item.number).toString()
|
||||
}
|
||||
job = scope.launch {
|
||||
val drawable = runCatching {
|
||||
val drawable = runCatchingCancellable {
|
||||
loadPageThumbnail(item)
|
||||
}.getOrNull()
|
||||
binding.imageViewThumb.setImageDrawable(drawable)
|
||||
|
||||
@@ -5,11 +5,16 @@ import androidx.lifecycle.viewModelScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
@@ -21,13 +26,20 @@ import org.koitharu.kotatsu.list.ui.filter.FilterCoordinator
|
||||
import org.koitharu.kotatsu.list.ui.filter.FilterItem
|
||||
import org.koitharu.kotatsu.list.ui.filter.FilterState
|
||||
import org.koitharu.kotatsu.list.ui.filter.OnFilterChangedListener
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||
import org.koitharu.kotatsu.list.ui.model.ListHeader2
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorFooter
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
||||
import org.koitharu.kotatsu.list.ui.model.toUi
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import java.util.LinkedList
|
||||
|
||||
private const val FILTER_MIN_INTERVAL = 250L
|
||||
|
||||
@@ -138,6 +150,8 @@ class RemoteListViewModel @AssistedInject constructor(
|
||||
mangaList.value = mangaList.value?.plus(list) ?: list
|
||||
}
|
||||
hasNextPage.value = list.isNotEmpty()
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTraceDebug()
|
||||
listError.value = e
|
||||
|
||||
@@ -3,15 +3,20 @@ package org.koitharu.kotatsu.scrobbling.domain
|
||||
import androidx.collection.LongSparseArray
|
||||
import androidx.collection.getOrElse
|
||||
import androidx.core.text.parseAsHtml
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.scrobbling.data.ScrobblingEntity
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.*
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingInfo
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus
|
||||
import org.koitharu.kotatsu.utils.ext.findKeyByValue
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import java.util.EnumMap
|
||||
|
||||
abstract class Scrobbler(
|
||||
protected val db: MangaDatabase,
|
||||
@@ -47,7 +52,7 @@ abstract class Scrobbler(
|
||||
|
||||
private suspend fun ScrobblingEntity.toScrobblingInfo(mangaId: Long): ScrobblingInfo? {
|
||||
val mangaInfo = infoCache.getOrElse(targetId) {
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
getMangaInfo(targetId)
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
@@ -72,9 +77,9 @@ abstract class Scrobbler(
|
||||
}
|
||||
|
||||
suspend fun Scrobbler.tryScrobble(mangaId: Long, chapter: MangaChapter): Boolean {
|
||||
return runCatching {
|
||||
return runCatchingCancellable {
|
||||
scrobble(mangaId, chapter)
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
}.isSuccess
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
|
||||
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
|
||||
class MangaSearchRepository @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
@@ -33,7 +34,7 @@ class MangaSearchRepository @Inject constructor(
|
||||
fun globalSearch(query: String, concurrency: Int = DEFAULT_CONCURRENCY): Flow<Manga> =
|
||||
settings.getMangaSources(includeHidden = false).asFlow()
|
||||
.flatMapMerge(concurrency) { source ->
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
mangaRepositoryFactory.create(source).getList(
|
||||
offset = 0,
|
||||
query = query,
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -12,7 +13,13 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorFooter
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
||||
import org.koitharu.kotatsu.list.ui.model.toUi
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
@@ -47,6 +54,7 @@ class SearchViewModel @AssistedInject constructor(
|
||||
actionStringRes = 0,
|
||||
),
|
||||
)
|
||||
|
||||
else -> {
|
||||
val result = ArrayList<ListModel>(list.size + 1)
|
||||
list.toUi(result, mode)
|
||||
@@ -94,6 +102,8 @@ class SearchViewModel @AssistedInject constructor(
|
||||
mangaList.value = mangaList.value?.plus(list) ?: list
|
||||
}
|
||||
hasNextPage.value = list.isNotEmpty()
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Throwable) {
|
||||
listError.value = e
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
|
||||
private const val MAX_PARALLELISM = 4
|
||||
private const val MIN_HAS_MORE_ITEMS = 8
|
||||
@@ -54,6 +55,7 @@ class MultiSearchViewModel @AssistedInject constructor(
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
loading -> list + LoadingFooter
|
||||
else -> list
|
||||
}
|
||||
@@ -85,6 +87,8 @@ class MultiSearchViewModel @AssistedInject constructor(
|
||||
loadingData.value = true
|
||||
query.postValue(q)
|
||||
searchImpl(q)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Throwable) {
|
||||
listError.value = e
|
||||
} finally {
|
||||
@@ -98,7 +102,7 @@ class MultiSearchViewModel @AssistedInject constructor(
|
||||
val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM)
|
||||
val deferredList = sources.map { source ->
|
||||
async(dispatcher) {
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
val list = mangaRepositoryFactory.create(source).getList(offset = 0, query = q)
|
||||
.toUi(ListMode.GRID)
|
||||
if (list.isNotEmpty()) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.preference.Preference
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
@@ -23,6 +23,7 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.FileSize
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cache) {
|
||||
@@ -82,18 +83,22 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
||||
clearCache(preference, CacheDir.PAGES)
|
||||
true
|
||||
}
|
||||
|
||||
AppSettings.KEY_THUMBS_CACHE_CLEAR -> {
|
||||
clearCache(preference, CacheDir.THUMBS)
|
||||
true
|
||||
}
|
||||
|
||||
AppSettings.KEY_COOKIES_CLEAR -> {
|
||||
clearCookies()
|
||||
true
|
||||
}
|
||||
|
||||
AppSettings.KEY_SEARCH_HISTORY_CLEAR -> {
|
||||
clearSearchHistory(preference)
|
||||
true
|
||||
}
|
||||
|
||||
AppSettings.KEY_UPDATES_FEED_CLEAR -> {
|
||||
viewLifecycleScope.launch {
|
||||
trackerRepo.clearLogs()
|
||||
@@ -107,6 +112,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
AppSettings.KEY_SHIKIMORI -> {
|
||||
if (!shikimoriRepository.isAuthorized) {
|
||||
launchShikimoriAuth()
|
||||
@@ -115,6 +121,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
||||
super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
@@ -127,6 +134,8 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
||||
storageManager.clearCache(cache)
|
||||
val size = storageManager.computeCacheSize(cache)
|
||||
preference.summary = FileSize.BYTES.format(ctx, size)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
preference.summary = e.getDisplayMessage(ctx.resources)
|
||||
} finally {
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -20,7 +19,14 @@ import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
import org.koitharu.kotatsu.utils.ext.awaitViewLifecycle
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.utils.ext.serializableArgument
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SourceSettingsFragment : BasePreferenceFragment(0) {
|
||||
@@ -66,12 +72,13 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
|
||||
startActivity(SourceAuthActivity.newIntent(preference.context, source))
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadUsername(owner: LifecycleOwner, preference: Preference) = owner.lifecycleScope.launch {
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
preference.summary = null
|
||||
withContext(Dispatchers.Default) {
|
||||
requireNotNull(repository?.getAuthProvider()?.getUsername())
|
||||
@@ -91,6 +98,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
|
||||
).setAction(ExceptionResolver.getResolveStringId(error)) { resolveError(error) }
|
||||
.show()
|
||||
}
|
||||
|
||||
else -> preference.summary = error.getDisplayMessage(preference.context.resources)
|
||||
}
|
||||
error.printStackTraceDebug()
|
||||
|
||||
@@ -11,13 +11,13 @@ import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
|
||||
import org.koitharu.kotatsu.databinding.DialogProgressBinding
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.progress.Progress
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
||||
@@ -91,6 +91,8 @@ class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
||||
}
|
||||
Toast.makeText(requireContext(), R.string.backup_saved, Toast.LENGTH_LONG).show()
|
||||
dismiss()
|
||||
} catch (e: InterruptedException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
onError(e)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.koitharu.kotatsu.suggestions.domain.MangaSuggestion
|
||||
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asArrayList
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.utils.ext.trySetForeground
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.pow
|
||||
@@ -137,7 +138,7 @@ class SuggestionsWorker @AssistedInject constructor(
|
||||
return (weight / maxWeight).pow(2.0).toFloat()
|
||||
}
|
||||
|
||||
private suspend fun MangaRepository.getListSafe(tag: MangaTag) = runCatching {
|
||||
private suspend fun MangaRepository.getListSafe(tag: MangaTag) = runCatchingCancellable {
|
||||
getList(offset = 0, sortOrder = SortOrder.UPDATED, tags = setOf(tag))
|
||||
}.onFailure { error ->
|
||||
error.printStackTraceDebug()
|
||||
|
||||
@@ -35,4 +35,4 @@ class SyncAuthenticator(
|
||||
)
|
||||
}
|
||||
}.getOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.os.Bundle
|
||||
import org.koitharu.kotatsu.sync.domain.SyncController
|
||||
import org.koitharu.kotatsu.sync.domain.SyncHelper
|
||||
import org.koitharu.kotatsu.utils.ext.onError
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
|
||||
class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) {
|
||||
|
||||
@@ -20,9 +21,9 @@ class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(cont
|
||||
syncResult: SyncResult,
|
||||
) {
|
||||
val syncHelper = SyncHelper(context, account, provider)
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
syncHelper.syncFavourites(syncResult)
|
||||
SyncController(context).setLastSync(account, authority, System.currentTimeMillis())
|
||||
}.onFailure(syncResult::onError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.os.Bundle
|
||||
import org.koitharu.kotatsu.sync.domain.SyncController
|
||||
import org.koitharu.kotatsu.sync.domain.SyncHelper
|
||||
import org.koitharu.kotatsu.utils.ext.onError
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
|
||||
class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) {
|
||||
|
||||
@@ -20,9 +21,9 @@ class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context
|
||||
syncResult: SyncResult,
|
||||
) {
|
||||
val syncHelper = SyncHelper(context, account, provider)
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
syncHelper.syncHistory(syncResult)
|
||||
SyncController(context).setLastSync(account, authority, System.currentTimeMillis())
|
||||
}.onFailure(syncResult::onError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.koitharu.kotatsu.tracker.domain.Tracker
|
||||
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
||||
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
||||
import org.koitharu.kotatsu.utils.ext.referer
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
|
||||
import org.koitharu.kotatsu.utils.ext.trySetForeground
|
||||
|
||||
@@ -82,7 +83,7 @@ class TrackWorker @AssistedInject constructor(
|
||||
val deferredList = coroutineScope {
|
||||
tracks.map { (track, channelId) ->
|
||||
async(dispatcher) {
|
||||
runCatching {
|
||||
runCatchingCancellable {
|
||||
tracker.fetchUpdates(track, commit = true)
|
||||
}.onSuccess { updates ->
|
||||
if (updates.isValid && updates.isNotEmpty()) {
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.koitharu.kotatsu.utils
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Runnable
|
||||
|
||||
class PausingDispatcher(
|
||||
private val dispatcher: CoroutineDispatcher,
|
||||
) : CoroutineDispatcher() {
|
||||
|
||||
@Volatile
|
||||
private var isPaused = false
|
||||
private val queue = ConcurrentLinkedQueue<Task>()
|
||||
|
||||
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
|
||||
return isPaused || super.isDispatchNeeded(context)
|
||||
}
|
||||
|
||||
override fun dispatch(context: CoroutineContext, block: Runnable) {
|
||||
if (isPaused) {
|
||||
queue.add(Task(context, block))
|
||||
} else {
|
||||
dispatcher.dispatch(context, block)
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
fun pause() {
|
||||
isPaused = true
|
||||
}
|
||||
|
||||
@MainThread
|
||||
fun resume() {
|
||||
if (!isPaused) {
|
||||
return
|
||||
}
|
||||
isPaused = false
|
||||
while (true) {
|
||||
val task = queue.poll() ?: break
|
||||
dispatcher.dispatch(task.context, task.block)
|
||||
}
|
||||
}
|
||||
|
||||
private class Task(
|
||||
val context: CoroutineContext,
|
||||
val block: Runnable,
|
||||
)
|
||||
}
|
||||
@@ -46,7 +46,7 @@ val Context.connectivityManager: ConnectivityManager
|
||||
|
||||
fun String.toUriOrNull() = if (isEmpty()) null else Uri.parse(this)
|
||||
|
||||
suspend fun CoroutineWorker.trySetForeground(): Boolean = runCatching {
|
||||
suspend fun CoroutineWorker.trySetForeground(): Boolean = runCatchingCancellable {
|
||||
val info = getForegroundInfo()
|
||||
setForeground(info)
|
||||
}.isSuccess
|
||||
@@ -95,6 +95,7 @@ fun SyncResult.onError(error: Throwable) {
|
||||
is OperationApplicationException,
|
||||
is SQLException,
|
||||
-> databaseError = true
|
||||
|
||||
is JSONException -> stats.numParseExceptions++
|
||||
else -> if (BuildConfig.DEBUG) throw error
|
||||
}
|
||||
|
||||
@@ -3,16 +3,22 @@ package org.koitharu.kotatsu.utils.ext
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.res.Resources
|
||||
import androidx.collection.arraySetOf
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import okio.FileNotFoundException
|
||||
import org.acra.ktx.sendWithAcra
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.*
|
||||
import org.koitharu.kotatsu.core.exceptions.CaughtException
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||
import org.koitharu.kotatsu.core.exceptions.SyncApiException
|
||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
||||
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
|
||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||
import org.koitharu.kotatsu.parsers.exception.ContentUnavailableException
|
||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
|
||||
fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
|
||||
is AuthRequiredException -> resources.getString(R.string.auth_required)
|
||||
@@ -20,16 +26,19 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
|
||||
is ActivityNotFoundException,
|
||||
is UnsupportedOperationException,
|
||||
-> resources.getString(R.string.operation_not_supported)
|
||||
|
||||
is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported)
|
||||
is FileNotFoundException -> resources.getString(R.string.file_not_found)
|
||||
is EmptyHistoryException -> resources.getString(R.string.history_is_empty)
|
||||
is SyncApiException,
|
||||
is ContentUnavailableException,
|
||||
-> message
|
||||
|
||||
is ParseException -> shortMessage
|
||||
is UnknownHostException,
|
||||
is SocketTimeoutException,
|
||||
-> resources.getString(R.string.network_error)
|
||||
|
||||
is WrongPasswordException -> resources.getString(R.string.wrong_password)
|
||||
is NotFoundException -> resources.getString(R.string.not_found_404)
|
||||
else -> localizedMessage
|
||||
@@ -52,3 +61,15 @@ private val reportableExceptions = arraySetOf<Class<*>>(
|
||||
ConcurrentModificationException::class.java,
|
||||
UnsupportedOperationException::class.java,
|
||||
)
|
||||
|
||||
inline fun <R> runCatchingCancellable(block: () -> R): Result<R> {
|
||||
return try {
|
||||
Result.success(block())
|
||||
} catch (e: InterruptedException) {
|
||||
throw e
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Throwable) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ Object localProperty(String name, Object defaultValue = 'null') {
|
||||
String currentBranch() {
|
||||
def branchName = ""
|
||||
try {
|
||||
branchName = "git rev-parse --abbrev-ref HEAD".execute().text.trim();
|
||||
branchName = "git rev-parse --abbrev-ref HEAD".execute().text.trim()
|
||||
} catch (ignored) {
|
||||
println "Git not found"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user