diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 0dd4b3546..13639f500 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -3,4 +3,7 @@ + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 9f2a294db..a44f7fc71 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" 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 d416216f4..f34c99e69 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,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() -} \ No newline at end of file +} 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 11e16e192..9d80b7af4 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 @@ -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) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/github/AppUpdateRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/github/AppUpdateRepository.kt index 84bc96d21..7b935f4ac 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/github/AppUpdateRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/github/AppUpdateRepository.kt @@ -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 } 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 ef8e341c1..82d7fa4bb 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 @@ -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) 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 ab9ffd418..62f340539 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 @@ -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 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 0f7a320a3..dd386bfad 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, @@ -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) 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 05be621b2..1967bbb2a 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 @@ -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) 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 85e7a45e6..fe57434da 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 @@ -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() } 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 310d2366f..42930502d 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, @@ -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) } } -} \ 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 4cca3afeb..f51736c8d 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 @@ -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 = async(context) { runInterruptible { - runCatching { LocalManga(getFromFile(file), file) }.getOrNull() + runCatchingCancellable { LocalManga(getFromFile(file), 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 745ac884d..1246ea44d 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 @@ -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() 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 147f8cd9f..57e36e6ce 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 @@ -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, 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 6b528eae7..7bee7ca84 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 @@ -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 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 212d12963..d992f747a 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 @@ -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) 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 53ed57bca..4c5455f57 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 @@ -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 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 1725620c4..140ed3917 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 @@ -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 = settings.getMangaSources(includeHidden = false).asFlow() .flatMapMerge(concurrency) { source -> - runCatching { + runCatchingCancellable { mangaRepositoryFactory.create(source).getList( offset = 0, query = query, 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 5c4e257ea..f4a18319c 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 @@ -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(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 } 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 e66c741c0..3fc76fc7e 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 @@ -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()) { 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 4389c39a6..595d47caa 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt @@ -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 { 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 eefc9ba6d..4542b1fca 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt @@ -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() 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 825575ad9..cbabc9b45 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 @@ -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() { @@ -91,6 +91,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 520bfc97b..813208aa4 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 @@ -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() diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt index 409feba44..02ab48f3f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt @@ -35,4 +35,4 @@ class SyncAuthenticator( ) } }.getOrNull() -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt index fa6995793..927204f8e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt @@ -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) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt index 43c8978c9..024ae3562 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt @@ -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) } -} \ No newline at end of file +} 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 4cdc2c962..bf20710cc 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 @@ -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()) { 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 ba3b02e93..df12e5507 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 @@ -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 } 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 5f7f51577..439ad2c4b 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 @@ -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>( ConcurrentModificationException::class.java, UnsupportedOperationException::class.java, ) + +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) + } +} diff --git a/build.gradle b/build.gradle index 3bf7a03ae..b70d20234 100644 --- a/build.gradle +++ b/build.gradle @@ -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" }