diff --git a/app/build.gradle b/app/build.gradle index cc9dc482d..b46a5067a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,10 +24,6 @@ android { } } } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } buildTypes { debug { applicationIdSuffix = '.debug' @@ -45,16 +41,10 @@ android { sourceSets { androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } - lintOptions { - disable 'MissingTranslation' - abortOnError false + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } - testOptions { - unitTests.includeAndroidResources = true - unitTests.returnDefaultValues = false - } -} -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() freeCompilerArgs += [ @@ -63,6 +53,14 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { '-Xopt-in=kotlin.contracts.ExperimentalContracts', ] } + lintOptions { + disable 'MissingTranslation' + abortOnError false + } + testOptions { + unitTests.includeAndroidResources = true + unitTests.returnDefaultValues = false + } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt index d9f845c65..25b0ac52f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt @@ -3,7 +3,9 @@ package org.koitharu.kotatsu.base.domain import android.graphics.BitmapFactory import android.net.Uri import android.util.Size -import androidx.annotation.WorkerThread +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible +import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request import org.koin.core.component.KoinComponent @@ -24,18 +26,18 @@ object MangaUtils : KoinComponent { * Automatic determine type of manga by page size * @return ReaderMode.WEBTOON if page is wide */ - @WorkerThread - @Suppress("BlockingMethodInNonBlockingContext") suspend fun determineMangaIsWebtoon(pages: List): Boolean? { try { val page = pages.medianOrNull() ?: return null val url = MangaRepository(page.source).getPageUrl(page) val uri = Uri.parse(url) val size = if (uri.scheme == "cbz") { - val zip = ZipFile(uri.schemeSpecificPart) - val entry = zip.getEntry(uri.fragment) - zip.getInputStream(entry).use { - getBitmapSize(it) + runInterruptible(Dispatchers.IO) { + val zip = ZipFile(uri.schemeSpecificPart) + val entry = zip.getEntry(uri.fragment) + zip.getInputStream(entry).use { + getBitmapSize(it) + } } } else { val client = get() @@ -46,7 +48,9 @@ object MangaUtils : KoinComponent { .cacheControl(CacheUtils.CONTROL_DISABLED) .build() client.newCall(request).await().use { - getBitmapSize(it.body?.byteStream()) + withContext(Dispatchers.IO) { + getBitmapSize(it.body?.byteStream()) + } } } return size.width * 2 < size.height diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupArchive.kt b/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupArchive.kt index c0af8d788..d0ec8a8cb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupArchive.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupArchive.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.core.backup import android.content.Context import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withContext import org.json.JSONArray import org.koitharu.kotatsu.R @@ -33,8 +34,7 @@ class BackupArchive(file: File) : MutableZipFile(file) { private const val DIR_BACKUPS = "backups" - @Suppress("BlockingMethodInNonBlockingContext") - suspend fun createNew(context: Context): BackupArchive = withContext(Dispatchers.IO) { + suspend fun createNew(context: Context): BackupArchive = runInterruptible(Dispatchers.IO) { val dir = context.run { getExternalFilesDir(DIR_BACKUPS) ?: File(filesDir, DIR_BACKUPS) } diff --git a/app/src/main/java/org/koitharu/kotatsu/local/data/WritableCbzFile.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/WritableCbzFile.kt index b7c5f7b9f..fbc2637aa 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/data/WritableCbzFile.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/WritableCbzFile.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.local.data import androidx.annotation.CheckResult import kotlinx.coroutines.* +import org.koitharu.kotatsu.utils.ext.deleteAwait import java.io.File import java.io.FileInputStream import java.io.FileOutputStream @@ -13,7 +14,6 @@ class WritableCbzFile(private val file: File) { private val dir = File(file.parentFile, file.nameWithoutExtension) - @Suppress("BlockingMethodInNonBlockingContext") suspend fun prepare() = withContext(Dispatchers.IO) { check(dir.list().isNullOrEmpty()) { "Dir ${dir.name} is not empty" @@ -45,11 +45,10 @@ class WritableCbzFile(private val file: File) { } @CheckResult - @Suppress("BlockingMethodInNonBlockingContext") suspend fun flush() = withContext(Dispatchers.IO) { val tempFile = File(file.path + ".tmp") if (tempFile.exists()) { - tempFile.delete() + tempFile.deleteAwait() } try { runInterruptible { @@ -63,7 +62,7 @@ class WritableCbzFile(private val file: File) { tempFile.renameTo(file) } finally { if (tempFile.exists()) { - tempFile.delete() + tempFile.deleteAwait() } } } 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 cedfe10a9..2dfb798f2 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,6 +8,7 @@ import androidx.collection.ArraySet import androidx.core.net.toFile import androidx.core.net.toUri import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withContext import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.parser.MangaRepository @@ -43,38 +44,39 @@ class LocalMangaRepository(private val context: Context) : MangaRepository { getFromFile(Uri.parse(manga.url).toFile()) } else manga - @Suppress("BlockingMethodInNonBlockingContext") override suspend fun getPages(chapter: MangaChapter): List { - val uri = Uri.parse(chapter.url) - val file = uri.toFile() - val zip = ZipFile(file) - val index = zip.getEntry(MangaZip.INDEX_ENTRY)?.let(zip::readText)?.let(::MangaIndex) - var entries = zip.entries().asSequence() - entries = if (index != null) { - val pattern = index.getChapterNamesPattern(chapter) - entries.filter { x -> !x.isDirectory && x.name.substringBefore('.').matches(pattern) } - } else { - val parent = uri.fragment.orEmpty() - entries.filter { x -> - !x.isDirectory && x.name.substringBeforeLast( - File.separatorChar, - "" - ) == parent + return runInterruptible(Dispatchers.IO){ + val uri = Uri.parse(chapter.url) + val file = uri.toFile() + val zip = ZipFile(file) + val index = zip.getEntry(MangaZip.INDEX_ENTRY)?.let(zip::readText)?.let(::MangaIndex) + var entries = zip.entries().asSequence() + entries = if (index != null) { + val pattern = index.getChapterNamesPattern(chapter) + entries.filter { x -> !x.isDirectory && x.name.substringBefore('.').matches(pattern) } + } else { + val parent = uri.fragment.orEmpty() + entries.filter { x -> + !x.isDirectory && x.name.substringBeforeLast( + File.separatorChar, + "" + ) == parent + } } + entries + .toList() + .sortedWith(compareBy(AlphanumComparator()) { x -> x.name }) + .map { x -> + val entryUri = zipUri(file, x.name) + MangaPage( + id = entryUri.longHashCode(), + url = entryUri, + preview = null, + referer = chapter.url, + source = MangaSource.LOCAL, + ) + } } - return entries - .toList() - .sortedWith(compareBy(AlphanumComparator()) { x -> x.name }) - .map { x -> - val entryUri = zipUri(file, x.name) - MangaPage( - id = entryUri.longHashCode(), - url = entryUri, - preview = null, - referer = chapter.url, - source = MangaSource.LOCAL, - ) - } } suspend fun delete(manga: Manga): Boolean { @@ -138,20 +140,18 @@ class LocalMangaRepository(private val context: Context) : MangaRepository { val file = runCatching { Uri.parse(localManga.url).toFile() }.getOrNull() ?: return null - return withContext(Dispatchers.IO) { - @Suppress("BlockingMethodInNonBlockingContext") + return runInterruptible(Dispatchers.IO) { ZipFile(file).use { zip -> val entry = zip.getEntry(MangaZip.INDEX_ENTRY) - val index = entry?.let(zip::readText)?.let(::MangaIndex) ?: return@withContext null - index.getMangaInfo() + val index = entry?.let(zip::readText)?.let(::MangaIndex) + index?.getMangaInfo() } } } - suspend fun findSavedManga(remoteManga: Manga): Manga? = withContext(Dispatchers.IO) { + suspend fun findSavedManga(remoteManga: Manga): Manga? = runInterruptible(Dispatchers.IO) { val files = getAllFiles() for (file in files) { - @Suppress("BlockingMethodInNonBlockingContext") val index = ZipFile(file).use { zip -> val entry = zip.getEntry(MangaZip.INDEX_ENTRY) entry?.let(zip::readText)?.let(::MangaIndex) @@ -159,7 +159,7 @@ class LocalMangaRepository(private val context: Context) : MangaRepository { val info = index.getMangaInfo() ?: continue if (info.id == remoteManga.id) { val fileUri = file.toUri().toString() - return@withContext info.copy( + return@runInterruptible info.copy( source = MangaSource.LOCAL, url = fileUri, chapters = info.chapters?.map { c -> c.copy(url = fileUri) } 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 8e40277c9..b1fc2493e 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 @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withContext import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException @@ -81,10 +82,11 @@ class LocalListViewModel( } val dest = settings.getStorageDir(context)?.let { File(it, name) } ?: throw IOException("External files dir unavailable") - @Suppress("BlockingMethodInNonBlockingContext") - contentResolver.openInputStream(uri)?.use { source -> - dest.outputStream().use { output -> - source.copyTo(output) + runInterruptible { + contentResolver.openInputStream(uri)?.use { source -> + dest.outputStream().use { output -> + source.copyTo(output) + } } } ?: throw IOException("Cannot open input stream: $uri") } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/backup/RestoreViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/backup/RestoreViewModel.kt index 1948b6585..ed2613fb8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/backup/RestoreViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/backup/RestoreViewModel.kt @@ -5,6 +5,7 @@ import android.net.Uri import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withContext import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.backup.BackupArchive @@ -32,8 +33,7 @@ class RestoreViewModel( } val contentResolver = context.contentResolver - @Suppress("BlockingMethodInNonBlockingContext") - val backup = withContext(Dispatchers.IO) { + val backup = runInterruptible(Dispatchers.IO) { val tempFile = File.createTempFile("backup_", ".tmp") (contentResolver.openInputStream(uri) ?: throw FileNotFoundException()).use { input -> diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/MutableZipFile.kt b/app/src/main/java/org/koitharu/kotatsu/utils/MutableZipFile.kt index a57bae29c..b785a62d2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/MutableZipFile.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/MutableZipFile.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.utils import androidx.annotation.CheckResult import androidx.annotation.WorkerThread import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withContext import java.io.File import java.io.FileInputStream @@ -11,12 +12,11 @@ import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream -@Suppress("BlockingMethodInNonBlockingContext") open class MutableZipFile(val file: File) { protected val dir = File(file.parentFile, file.nameWithoutExtension) - suspend fun unpack(): Unit = withContext(Dispatchers.IO) { + suspend fun unpack(): Unit = runInterruptible(Dispatchers.IO) { check(dir.list().isNullOrEmpty()) { "Dir ${dir.name} is not empty" } @@ -24,7 +24,7 @@ open class MutableZipFile(val file: File) { dir.mkdir() } if (!file.exists()) { - return@withContext + return@runInterruptible } ZipInputStream(FileInputStream(file)).use { zip -> var entry = zip.nextEntry @@ -45,7 +45,7 @@ open class MutableZipFile(val file: File) { } @CheckResult - suspend fun flush(): Boolean = withContext(Dispatchers.IO) { + suspend fun flush(): Boolean = runInterruptible(Dispatchers.IO) { val tempFile = File(file.path + ".tmp") if (tempFile.exists()) { tempFile.delete() @@ -57,7 +57,7 @@ open class MutableZipFile(val file: File) { } zip.flush() } - return@withContext tempFile.renameTo(file) + tempFile.renameTo(file) } finally { if (tempFile.exists()) { tempFile.delete()