Fix blocking calls in coroutines

This commit is contained in:
Koitharu
2022-01-26 19:24:27 +02:00
parent e3a80b5a6d
commit cbcf98e1d4
8 changed files with 77 additions and 74 deletions

View File

@@ -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
@@ -23,18 +25,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<MangaPage>): Boolean? {
try {
val page = pages.medianOrNull() ?: return null
val url = page.source.repository.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<OkHttpClient>()
@@ -45,7 +47,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

View File

@@ -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)
}

View File

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

View File

@@ -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
@@ -42,38 +43,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<MangaPage> {
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 {
@@ -137,20 +139,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)
@@ -158,7 +158,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) }

View File

@@ -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")
}

View File

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

View File

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