diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 000000000..13639f500
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/ReversibleHandle.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/ReversibleHandle.kt
index 43c9bf7e4..e15d59958 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/domain/ReversibleHandle.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/ReversibleHandle.kt
@@ -1,8 +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 {
@@ -10,7 +14,13 @@ fun interface ReversibleHandle {
}
fun ReversibleHandle.reverseAsync() = processLifecycleScope.launch(Dispatchers.Default) {
- reverse()
+ runCatchingCancellable {
+ withContext(NonCancellable) {
+ reverse()
+ }
+ }.onFailure {
+ it.printStackTraceDebug()
+ }
}
operator fun ReversibleHandle.plus(other: ReversibleHandle) = ReversibleHandle {
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt
index 27dd10255..88b6048f2 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt
@@ -7,6 +7,7 @@ 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
private const val PAGE_SIZE = 10
@@ -84,7 +85,7 @@ class BackupRepository(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)
@@ -99,7 +100,7 @@ class BackupRepository(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)
}
}
@@ -115,7 +116,7 @@ class BackupRepository(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)
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/os/ShortcutsUpdater.kt b/app/src/main/java/org/koitharu/kotatsu/core/os/ShortcutsUpdater.kt
index a201295c5..c372605d9 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/os/ShortcutsUpdater.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/os/ShortcutsUpdater.kt
@@ -6,6 +6,7 @@ import android.content.pm.ShortcutManager
import android.media.ThumbnailUtils
import android.os.Build
import android.util.Size
+import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
@@ -25,6 +26,7 @@ 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
class ShortcutsUpdater(
private val context: Context,
@@ -37,10 +39,12 @@ class ShortcutsUpdater(
private var shortcutsUpdateJob: Job? = null
override fun onInvalidated(tables: MutableSet) {
- val prevJob = shortcutsUpdateJob
- shortcutsUpdateJob = processLifecycleScope.launch(Dispatchers.Default) {
- prevJob?.join()
- updateShortcutsImpl()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
+ val prevJob = shortcutsUpdateJob
+ shortcutsUpdateJob = processLifecycleScope.launch(Dispatchers.Default) {
+ prevJob?.join()
+ updateShortcutsImpl()
+ }
}
}
@@ -48,7 +52,7 @@ class ShortcutsUpdater(
return ShortcutManagerCompat.requestPinShortcut(
context,
buildShortcutInfo(manga).build(),
- null
+ null,
)
}
@@ -57,7 +61,8 @@ class ShortcutsUpdater(
return shortcutsUpdateJob?.join() != null
}
- private suspend fun updateShortcutsImpl() = runCatching {
+ @RequiresApi(Build.VERSION_CODES.N_MR1)
+ 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() }
@@ -68,17 +73,17 @@ class ShortcutsUpdater(
}
private suspend fun buildShortcutInfo(manga: Manga): ShortcutInfoCompat.Builder {
- val icon = runCatching {
+ val icon = runCatchingCancellable {
val bmp = coil.execute(
ImageRequest.Builder(context)
.data(manga.coverUrl)
.size(iconSize.width, iconSize.height)
- .build()
+ .build(),
).requireBitmap()
ThumbnailUtils.extractThumbnail(bmp, iconSize.width, iconSize.height, 0)
}.fold(
onSuccess = { IconCompat.createWithAdaptiveBitmap(it) },
- onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) }
+ onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },
)
mangaRepository.storeManga(manga)
return ShortcutInfoCompat.Builder(context, manga.id.toString())
@@ -87,7 +92,7 @@ class ShortcutsUpdater(
.setIcon(icon)
.setIntent(
ReaderActivity.newIntent(context, manga.id)
- .setAction(ReaderActivity.ACTION_MANGA_READ)
+ .setAction(ReaderActivity.ACTION_MANGA_READ),
)
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt
index a8bade779..523d40e22 100644
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt
@@ -38,6 +38,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
class DetailsViewModel(
intent: MangaIntent,
@@ -165,7 +166,7 @@ class DetailsViewModel(
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)
@@ -204,7 +205,7 @@ class DetailsViewModel(
reload()
} else {
viewModelScope.launch(Dispatchers.Default) {
- runCatching {
+ runCatchingCancellable {
localMangaRepository.getDetails(downloadedManga)
}.onSuccess {
delegate.relatedManga.value = it
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt
index 49bbde5ce..8f0355aa5 100644
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt
@@ -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,
@@ -44,9 +45,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
MangaRepository(m.source).getDetails(m)
} else {
localMangaRepository.findSavedManga(manga)
diff --git a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt
index 8452512be..c37c2ab00 100644
--- a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt
@@ -6,10 +6,18 @@ import coil.ImageLoader
import coil.request.ImageRequest
import coil.size.Scale
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
@@ -28,6 +36,7 @@ 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
private const val MAX_FAILSAFE_ATTEMPTS = 2
@@ -226,7 +235,7 @@ class DownloadManager(
)
}
- private suspend fun loadCover(manga: Manga) = runCatching {
+ private suspend fun loadCover(manga: Manga) = runCatchingCancellable {
imageLoader.execute(
ImageRequest.Builder(context)
.data(manga.coverUrl)
diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt
index 44f89c767..a8375ff65 100644
--- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt
@@ -22,6 +22,7 @@ import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
+import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
class FavouritesListViewModel(
private val categoryId: Long,
@@ -45,7 +46,7 @@ class FavouritesListViewModel(
} else {
repository.observeAll(categoryId)
},
- createListModeFlow()
+ createListModeFlow(),
) { list, mode ->
when {
list.isEmpty() -> listOf(
@@ -58,8 +59,9 @@ class FavouritesListViewModel(
R.string.favourites_category_empty
},
actionStringRes = 0,
- )
+ ),
)
+
else -> list.toUi(mode, this)
}
}.catch {
diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt
index 5ea361168..7f8ec8b05 100644
--- a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt
@@ -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,
@@ -152,7 +159,7 @@ class FilterCoordinator(
}
private fun loadTagsAsync() = coroutineScope.async(Dispatchers.Default, CoroutineStart.LAZY) {
- runCatching {
+ runCatchingCancellable {
repository.getTags()
}.onFailure { error ->
error.printStackTraceDebug()
@@ -203,4 +210,4 @@ class FilterCoordinator(
return collator?.compare(t1, t2) ?: compareValues(t1, t2)
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt
index e7d79dea3..ffc101543 100644
--- a/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt
@@ -8,19 +8,31 @@ import androidx.collection.ArraySet
import androidx.core.net.toFile
import androidx.core.net.toUri
import java.io.File
-import java.io.IOException
-import java.util.*
+import java.util.Enumeration
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
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.runInterruptible
+import kotlinx.coroutines.withContext
+import okio.IOException
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
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
@@ -28,6 +40,7 @@ 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.resolveName
+import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
private const val MAX_PARALLELISM = 4
@@ -70,6 +83,7 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
manga.source != MangaSource.LOCAL -> requireNotNull(findSavedManga(manga)) {
"Manga is not local or saved"
}
+
else -> getFromFile(Uri.parse(manga.url).toFile())
}
@@ -226,7 +240,7 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
context: CoroutineContext,
): Deferred = async(context) {
runInterruptible {
- runCatching { getFromFile(file) }.getOrNull()
+ runCatchingCancellable { getFromFile(file) }.getOrNull()
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt
index ebd347913..fe8703130 100644
--- a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.local.ui
import android.net.Uri
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@@ -10,6 +11,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import okio.IOException
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.download.ui.service.DownloadService
@@ -21,8 +23,8 @@ import org.koitharu.kotatsu.parsers.model.Manga
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
import org.koitharu.kotatsu.utils.progress.Progress
-import java.io.IOException
class LocalListViewModel(
private val repository: LocalMangaRepository,
@@ -40,7 +42,7 @@ class LocalListViewModel(
override val content = combine(
mangaList,
createListModeFlow(),
- listError
+ listError,
) { list, mode, error ->
when {
error != null -> listOf(error.toErrorState(canRetry = true))
@@ -51,8 +53,9 @@ class LocalListViewModel(
textPrimary = R.string.text_local_holder_primary,
textSecondary = R.string.text_local_holder_secondary,
actionStringRes = R.string._import,
- )
+ ),
)
+
else -> ArrayList(list.size + 1).apply {
add(headerModel)
list.toUi(this, mode)
@@ -60,7 +63,7 @@ class LocalListViewModel(
}
}.asLiveDataDistinct(
viewModelScope.coroutineContext + Dispatchers.Default,
- listOf(LoadingState)
+ listOf(LoadingState),
)
init {
@@ -97,7 +100,7 @@ class LocalListViewModel(
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 ->
@@ -113,6 +116,8 @@ class LocalListViewModel(
try {
listError.value = null
mangaList.value = repository.getList(0, null, null)
+ } catch (e: CancellationException) {
+ throw e
} catch (e: Throwable) {
listError.value = e
}
@@ -121,7 +126,7 @@ class LocalListViewModel(
private fun cleanup() {
if (!DownloadService.isRunning) {
viewModelScope.launch {
- runCatching {
+ runCatchingCancellable {
repository.cleanup()
}.onFailure { error ->
error.printStackTraceDebug()
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
index 895bb6628..3ffeeb818 100644
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
@@ -6,6 +6,7 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
+import java.util.Date
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.koitharu.kotatsu.R
@@ -31,7 +32,7 @@ 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.processLifecycleScope
-import java.util.*
+import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
private const val BOUNDS_PAGE_OFFSET = 2
private const val PAGES_TRIM_THRESHOLD = 120
@@ -69,7 +70,7 @@ class ReaderViewModel(
mangaName = manga?.title,
chapterName = chapter?.name,
chapterNumber = chapter?.number ?: 0,
- chaptersTotal = chapters.size()
+ chaptersTotal = chapters.size(),
)
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, null)
@@ -80,7 +81,7 @@ class ReaderViewModel(
val readerAnimation = settings.observeAsLiveData(
context = viewModelScope.coroutineContext + Dispatchers.Default,
key = AppSettings.KEY_READER_ANIMATION,
- valueProducer = { readerAnimation }
+ valueProducer = { readerAnimation },
)
val isScreenshotsBlockEnabled = combine(
@@ -123,12 +124,12 @@ class ReaderViewModel(
val manga = checkNotNull(mangaData.value)
dataRepository.savePreferences(
manga = manga,
- mode = newMode
+ mode = newMode,
)
readerMode.value = newMode
content.value?.run {
content.value = copy(
- state = getCurrentState()
+ state = getCurrentState(),
)
}
}
@@ -358,7 +359,7 @@ class ReaderViewModel(
?: manga.chapters?.randomOrNull()
?: error("There are no chapters in this manga")
val pages = repo.getPages(chapter)
- return runCatching {
+ return runCatchingCancellable {
val isWebtoon = MangaUtils.determineMangaIsWebtoon(pages)
if (isWebtoon) ReaderMode.WEBTOON else defaultMode
}.onSuccess {
@@ -389,7 +390,7 @@ class ReaderViewModel(
*/
private fun HistoryRepository.saveStateAsync(manga: Manga, state: ReaderState, percent: Float): Job {
return processLifecycleScope.launch(Dispatchers.Default) {
- runCatching {
+ runCatchingCancellable {
addOrUpdate(
manga = manga,
chapterId = state.chapterId,
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/PageHolderDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/PageHolderDelegate.kt
index 5630cb1ba..b32ecc884 100644
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/PageHolderDelegate.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/PageHolderDelegate.kt
@@ -3,24 +3,30 @@ package org.koitharu.kotatsu.reader.ui.pager
import android.net.Uri
import androidx.core.net.toUri
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
-import kotlinx.coroutines.*
+import java.io.File
+import java.io.IOException
+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.core.model.ZoomMode
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.reader.domain.PageLoader
-import java.io.File
-import java.io.IOException
class PageHolderDelegate(
private val loader: PageLoader,
private val settings: AppSettings,
private val callback: Callback,
- private val exceptionResolver: ExceptionResolver
+ private val exceptionResolver: ExceptionResolver,
) : SubsamplingScaleImageView.DefaultOnImageEventListener() {
private val scope = loader.loaderScope + Dispatchers.Main.immediate
@@ -88,6 +94,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
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt
index 42529810b..c5ab420ea 100644
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt
@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
import org.koitharu.kotatsu.utils.ext.referer
+import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import org.koitharu.kotatsu.utils.ext.setTextColorAttr
fun pageThumbnailAD(
@@ -69,7 +70,7 @@ fun pageThumbnailAD(
text = (item.number).toString()
}
job = scope.launch {
- val drawable = runCatching {
+ val drawable = runCatchingCancellable {
loadPageThumbnail(item)
}.getOrNull()
binding.imageViewThumb.setImageDrawable(drawable)
diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
index 092194b96..7249b75bc 100644
--- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
@@ -2,10 +2,16 @@ package org.koitharu.kotatsu.remotelist.ui
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
+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
@@ -16,7 +22,14 @@ 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.CurrentFilterModel
+import org.koitharu.kotatsu.list.ui.model.EmptyState
+import org.koitharu.kotatsu.list.ui.model.ListHeader
+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.MangaTag
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
@@ -132,6 +145,8 @@ class RemoteListViewModel(
mangaList.value = mangaList.value?.plus(list) ?: list
}
hasNextPage.value = list.isNotEmpty()
+ } catch (e: CancellationException) {
+ throw e
} catch (e: Throwable) {
e.printStackTraceDebug()
listError.value = e
diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt
index b730d19cd..1db58e204 100644
--- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt
@@ -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
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt
index 2c95fb632..b86bb5475 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt
@@ -19,6 +19,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(
private val settings: AppSettings,
@@ -30,7 +31,7 @@ class MangaSearchRepository(
fun globalSearch(query: String, concurrency: Int = DEFAULT_CONCURRENCY): Flow =
settings.getMangaSources(includeHidden = false).asFlow()
.flatMapMerge(concurrency) { source ->
- runCatching {
+ runCatchingCancellable {
MangaRepository(source).getList(
offset = 0,
query = query,
@@ -63,7 +64,7 @@ class MangaSearchRepository(
SUGGESTION_PROJECTION,
"${SearchManager.SUGGEST_COLUMN_QUERY} LIKE ?",
arrayOf("%$query%"),
- "date DESC"
+ "date DESC",
)?.use { cursor ->
val count = minOf(cursor.count, limit)
if (count == 0) {
@@ -113,7 +114,7 @@ class MangaSearchRepository(
SUGGESTION_PROJECTION,
null,
arrayOfNulls(1),
- null
+ null,
)?.use { cursor -> cursor.count } ?: 0
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt
index 9615e84a6..713daf8f6 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt
@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.search.ui
import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@@ -9,14 +10,20 @@ 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.utils.ext.asLiveDataDistinct
class SearchViewModel(
private val repository: MangaRepository,
private val query: String,
- settings: AppSettings
+ settings: AppSettings,
) : MangaListViewModel(settings) {
private val mangaList = MutableStateFlow?>(null)
@@ -28,7 +35,7 @@ class SearchViewModel(
mangaList,
createListModeFlow(),
listError,
- hasNextPage
+ hasNextPage,
) { list, mode, error, hasNext ->
when {
list.isNullOrEmpty() && error != null -> listOf(error.toErrorState(canRetry = true))
@@ -39,8 +46,9 @@ class SearchViewModel(
textPrimary = R.string.nothing_found,
textSecondary = R.string.text_search_holder_secondary,
actionStringRes = 0,
- )
+ ),
)
+
else -> {
val result = ArrayList(list.size + 1)
list.toUi(result, mode)
@@ -88,6 +96,8 @@ class SearchViewModel(
mangaList.value = mangaList.value?.plus(list) ?: list
}
hasNextPage.value = list.isNotEmpty()
+ } catch (e: CancellationException) {
+ throw e
} catch (e: Throwable) {
listError.value = e
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt
index b2ababf6d..cd0f6d199 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt
@@ -17,6 +17,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
@@ -48,8 +49,9 @@ class MultiSearchViewModel(
textSecondary = R.string.text_search_holder_secondary,
actionStringRes = 0,
)
- }
+ },
)
+
loading -> list + LoadingFooter
else -> list
}
@@ -81,6 +83,8 @@ class MultiSearchViewModel(
loadingData.value = true
query.postValue(q)
searchImpl(q)
+ } catch (e: CancellationException) {
+ throw e
} catch (e: Throwable) {
listError.value = e
} finally {
@@ -94,7 +98,7 @@ class MultiSearchViewModel(
val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM)
val deferredList = sources.map { source ->
async(dispatcher) {
- runCatching {
+ runCatchingCancellable {
val list = MangaRepository(source).getList(offset = 0, query = q)
.toUi(ListMode.GRID)
if (list.isNotEmpty()) {
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/AppUpdateChecker.kt b/app/src/main/java/org/koitharu/kotatsu/settings/AppUpdateChecker.kt
index 603fe1ce5..62fe1b7ba 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/AppUpdateChecker.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/AppUpdateChecker.kt
@@ -8,6 +8,12 @@ import androidx.activity.ComponentActivity
import androidx.annotation.MainThread
import androidx.core.net.toUri
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import java.io.ByteArrayInputStream
+import java.io.InputStream
+import java.security.MessageDigest
+import java.security.cert.CertificateFactory
+import java.security.cert.X509Certificate
+import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
@@ -20,12 +26,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.util.byte2HexFormatted
import org.koitharu.kotatsu.utils.FileSize
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
-import java.io.ByteArrayInputStream
-import java.io.InputStream
-import java.security.MessageDigest
-import java.security.cert.CertificateFactory
-import java.security.cert.X509Certificate
-import java.util.concurrent.TimeUnit
+import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
class AppUpdateChecker(private val activity: ComponentActivity) {
@@ -41,7 +42,7 @@ class AppUpdateChecker(private val activity: ComponentActivity) {
null
}
- suspend fun checkNow() = runCatching {
+ suspend fun checkNow() = runCatchingCancellable {
val version = repo.getLatestVersion()
val newVersionId = VersionId(version.name)
val currentVersionId = VersionId(BuildConfig.VERSION_NAME)
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt
index c4c5f46ba..8d807be6b 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt
@@ -7,6 +7,7 @@ import android.view.View
import androidx.preference.Preference
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject
@@ -65,18 +66,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()
@@ -85,11 +90,12 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
Snackbar.make(
view ?: return@launch,
R.string.updates_feed_cleared,
- Snackbar.LENGTH_SHORT
+ Snackbar.LENGTH_SHORT,
).show()
}
true
}
+
AppSettings.KEY_SHIKIMORI -> {
if (!shikimoriRepository.isAuthorized) {
launchShikimoriAuth()
@@ -98,6 +104,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
super.onPreferenceTreeClick(preference)
}
}
+
else -> super.onPreferenceTreeClick(preference)
}
}
@@ -110,6 +117,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 {
@@ -136,7 +145,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
Snackbar.make(
view ?: return@launch,
R.string.search_history_cleared,
- Snackbar.LENGTH_SHORT
+ Snackbar.LENGTH_SHORT,
).show()
}
}.show()
@@ -154,7 +163,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
Snackbar.make(
listView ?: return@launch,
R.string.cookies_cleared,
- Snackbar.LENGTH_SHORT
+ Snackbar.LENGTH_SHORT,
).show()
}
}.show()
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt
index b5209896b..463d43972 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt
@@ -18,7 +18,13 @@ 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
class SourceSettingsFragment : BasePreferenceFragment(0) {
@@ -60,12 +66,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())
@@ -85,6 +92,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
).setAction(ExceptionResolver.getResolveStringId(error)) { resolveError(error) }
.show()
}
+
else -> preference.summary = error.getDisplayMessage(preference.context.resources)
}
error.printStackTraceDebug()
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupDialogFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupDialogFragment.kt
index b309a588b..281f57a12 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupDialogFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupDialogFragment.kt
@@ -9,14 +9,14 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isVisible
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import java.io.File
+import java.io.FileOutputStream
import org.koin.androidx.viewmodel.ext.android.viewModel
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
class BackupDialogFragment : AlertDialogFragment() {
@@ -24,7 +24,7 @@ class BackupDialogFragment : AlertDialogFragment() {
private var backup: File? = null
private val saveFileContract = registerForActivityResult(
- ActivityResultContracts.CreateDocument("*/*")
+ ActivityResultContracts.CreateDocument("*/*"),
) { uri ->
val file = backup
if (uri != null && file != null) {
@@ -88,6 +88,8 @@ class BackupDialogFragment : AlertDialogFragment() {
}
Toast.makeText(requireContext(), R.string.backup_saved, Toast.LENGTH_LONG).show()
dismiss()
+ } catch (e: InterruptedException) {
+ throw e
} catch (e: Exception) {
onError(e)
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt
index 7433e689b..eab01f9ee 100644
--- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt
@@ -8,6 +8,8 @@ import androidx.annotation.FloatRange
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.work.*
+import java.util.concurrent.TimeUnit
+import kotlin.math.pow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@@ -24,8 +26,6 @@ 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.trySetForeground
-import java.util.concurrent.TimeUnit
-import kotlin.math.pow
class SuggestionsWorker(appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params), KoinComponent {
@@ -47,7 +47,7 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) :
val channel = NotificationChannel(
WORKER_CHANNEL_ID,
title,
- NotificationManager.IMPORTANCE_LOW
+ NotificationManager.IMPORTANCE_LOW,
)
channel.setShowBadge(false)
channel.enableVibration(false)
@@ -118,7 +118,7 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) :
}.map { manga ->
MangaSuggestion(
manga = manga,
- relevance = computeRelevance(manga.tags, allTags)
+ relevance = computeRelevance(manga.tags, allTags),
)
}.sortedBy { it.relevance }.take(LIMIT)
suggestionRepository.replace(suggestions)
diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt
index 51aedc866..721f8080b 100644
--- a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt
@@ -27,6 +27,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
@@ -80,7 +81,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
val deferredList = coroutineScope {
tracks.map { (track, channelId) ->
async(dispatcher) {
- runCatching {
+ runCatchingCancellable {
tracker.fetchUpdates(track, commit = true)
}.onSuccess { updates ->
if (updates.isValid && updates.isNotEmpty()) {
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/PausingDispatcher.kt b/app/src/main/java/org/koitharu/kotatsu/utils/PausingDispatcher.kt
deleted file mode 100644
index 57eb100a4..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/utils/PausingDispatcher.kt
+++ /dev/null
@@ -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()
-
- 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,
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt
index 28f50e7de..ba56a4b23 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt
@@ -29,7 +29,7 @@ val Context.activityManager: ActivityManager?
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
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt
index d4f654525..0fe14cfd8 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt
@@ -4,10 +4,16 @@ 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.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
@@ -19,12 +25,16 @@ 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 ContentUnavailableException -> message
is ParseException -> shortMessage
- is SocketTimeoutException -> resources.getString(R.string.network_error)
+ 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
@@ -46,4 +56,16 @@ private val reportableExceptions = arraySetOf>(
IllegalArgumentException::class.java,
ConcurrentModificationException::class.java,
UnsupportedOperationException::class.java,
-)
\ No newline at end of file
+)
+
+inline fun runCatchingCancellable(block: () -> R): Result {
+ return try {
+ Result.success(block())
+ } catch (e: InterruptedException) {
+ throw e
+ } catch (e: CancellationException) {
+ throw e
+ } catch (e: Throwable) {
+ Result.failure(e)
+ }
+}
\ No newline at end of file