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