Fix StrictMode errors

master
Koitharu 2 years ago
parent ac96c49b60
commit e5b6947586
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -25,51 +25,50 @@ class KotatsuApp : BaseApp() {
null null
} }
StrictMode.setThreadPolicy( StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder() StrictMode.ThreadPolicy.Builder().apply {
.detectAll() detectNetwork()
.penaltyLog() detectDiskWrites()
.run { detectCustomSlowCalls()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && notifier != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) detectUnbufferedIo()
penaltyListener(notifier.executor, notifier) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) detectResourceMismatches()
} else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) detectExplicitGc()
this penaltyLog()
} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && notifier != null) {
}.build(), penaltyListener(notifier.executor, notifier)
}
}.build(),
) )
StrictMode.setVmPolicy( StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder() StrictMode.VmPolicy.Builder().apply {
.detectActivityLeaks() detectActivityLeaks()
.detectLeakedSqlLiteObjects() detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects() detectLeakedClosableObjects()
.detectLeakedRegistrationObjects() detectLeakedRegistrationObjects()
.setClassInstanceLimit(LocalMangaRepository::class.java, 1) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) detectContentUriWithoutPermission()
.setClassInstanceLimit(PagesCache::class.java, 1) detectFileUriExposure()
.setClassInstanceLimit(MangaLoaderContext::class.java, 1) setClassInstanceLimit(LocalMangaRepository::class.java, 1)
.setClassInstanceLimit(PageLoader::class.java, 1) setClassInstanceLimit(PagesCache::class.java, 1)
.setClassInstanceLimit(ReaderViewModel::class.java, 1) setClassInstanceLimit(MangaLoaderContext::class.java, 1)
.penaltyLog() setClassInstanceLimit(PageLoader::class.java, 1)
.run { setClassInstanceLimit(ReaderViewModel::class.java, 1)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && notifier != null) { penaltyLog()
penaltyListener(notifier.executor, notifier) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && notifier != null) {
} else { penaltyListener(notifier.executor, notifier)
this
}
}.build(),
)
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder()
.penaltyDeath()
.detectFragmentReuse()
.detectWrongFragmentContainer()
.detectRetainInstanceUsage()
.detectSetUserVisibleHint()
.detectFragmentTagUsage()
.penaltyLog()
.run {
if (notifier != null) {
penaltyListener(notifier)
} else {
this
} }
}.build() }.build()
)
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder().apply {
detectWrongFragmentContainer()
detectFragmentTagUsage()
detectRetainInstanceUsage()
detectSetUserVisibleHint()
detectWrongNestedHierarchy()
detectTargetFragmentUsage()
detectFragmentReuse()
penaltyLog()
if (notifier != null) {
penaltyListener(notifier)
}
}.build()
} }
} }

@ -1,20 +1,31 @@
package org.koitharu.kotatsu.core.fs package org.koitharu.kotatsu.core.fs
import android.os.Build import android.os.Build
import org.koitharu.kotatsu.core.util.iterator.CloseableIterator import androidx.annotation.RequiresApi
import org.koitharu.kotatsu.core.util.CloseableSequence
import org.koitharu.kotatsu.core.util.iterator.MappingIterator import org.koitharu.kotatsu.core.util.iterator.MappingIterator
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
class FileSequence(private val dir: File) : Sequence<File> { sealed interface FileSequence : CloseableSequence<File> {
override fun iterator(): Iterator<File> { @RequiresApi(Build.VERSION_CODES.O)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { class StreamImpl(dir: File) : FileSequence {
val stream = Files.newDirectoryStream(dir.toPath())
CloseableIterator(MappingIterator(stream.iterator(), Path::toFile), stream) private val stream = Files.newDirectoryStream(dir.toPath())
} else {
dir.listFiles().orEmpty().iterator() override fun iterator(): Iterator<File> = MappingIterator(stream.iterator(), Path::toFile)
}
override fun close() = stream.close()
}
class ListImpl(dir: File) : FileSequence {
private val list = dir.listFiles().orEmpty()
override fun iterator(): Iterator<File> = list.iterator()
override fun close() = Unit
} }
} }

@ -0,0 +1,3 @@
package org.koitharu.kotatsu.core.util
interface CloseableSequence<T> : Sequence<T>, AutoCloseable

@ -15,7 +15,6 @@ import org.jetbrains.annotations.Blocking
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.fs.FileSequence import org.koitharu.kotatsu.core.fs.FileSequence
import java.io.File import java.io.File
import java.io.FileFilter
import java.io.InputStream import java.io.InputStream
import java.nio.file.attribute.BasicFileAttributes import java.nio.file.attribute.BasicFileAttributes
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
@ -87,9 +86,13 @@ suspend fun File.computeSize(): Long = runInterruptible(Dispatchers.IO) {
walkCompat(includeDirectories = false).sumOf { it.length() } walkCompat(includeDirectories = false).sumOf { it.length() }
} }
fun File.children() = FileSequence(this) inline fun <R> File.withChildren(block: (children: Sequence<File>) -> R): R = FileSequence(this).use(block)
fun Sequence<File>.filterWith(filter: FileFilter): Sequence<File> = filter { f -> filter.accept(f) } fun FileSequence(dir: File): FileSequence = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
FileSequence.StreamImpl(dir)
} else {
FileSequence.ListImpl(dir)
}
val File.creationTime val File.creationTime
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

@ -1,36 +0,0 @@
package org.koitharu.kotatsu.core.util.iterator
import okhttp3.internal.closeQuietly
import okio.Closeable
class CloseableIterator<T>(
private val upstream: Iterator<T>,
private val closeable: Closeable,
) : Iterator<T>, Closeable {
private var isClosed = false
override fun hasNext(): Boolean {
val result = upstream.hasNext()
if (!result) {
close()
}
return result
}
override fun next(): T {
try {
return upstream.next()
} catch (e: NoSuchElementException) {
close()
throw e
}
}
override fun close() {
if (!isClosed) {
closeable.closeQuietly()
isClosed = true
}
}
}

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.core.zip
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.collection.ArraySet import androidx.collection.ArraySet
import okio.Closeable import okio.Closeable
import org.koitharu.kotatsu.core.util.ext.children import org.koitharu.kotatsu.core.util.ext.withChildren
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.util.zip.Deflater import java.util.zip.Deflater
@ -91,8 +91,10 @@ class ZipOutput(
} }
putNextEntry(entry) putNextEntry(entry)
closeEntry() closeEntry()
fileToZip.children().forEach { childFile -> fileToZip.withChildren { children ->
appendFile(childFile, "$name/${childFile.name}") children.forEach { childFile ->
appendFile(childFile, "$name/${childFile.name}")
}
} }
} else { } else {
FileInputStream(fileToZip).use { fis -> FileInputStream(fileToZip).use { fis ->

@ -29,6 +29,7 @@ import org.koitharu.kotatsu.local.data.isZipUri
import org.koitharu.kotatsu.local.data.util.withExtraCloseable import org.koitharu.kotatsu.local.data.util.withExtraCloseable
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.mimeType import org.koitharu.kotatsu.parsers.util.mimeType
import org.koitharu.kotatsu.parsers.util.requireBody
import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import java.util.zip.ZipFile import java.util.zip.ZipFile
import javax.inject.Inject import javax.inject.Inject
@ -98,9 +99,7 @@ class MangaPageFetcher(
if (!response.isSuccessful) { if (!response.isSuccessful) {
throw HttpException(response) throw HttpException(response)
} }
val body = checkNotNull(response.body) { val body = response.requireBody()
"Null response"
}
val mimeType = response.mimeType val mimeType = response.mimeType
val file = body.use { val file = body.use {
pagesCache.put(pageUrl, it.source()) pagesCache.put(pageUrl, it.source())

@ -81,6 +81,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.requireBody
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import java.io.File import java.io.File
@ -359,7 +360,7 @@ class DownloadWorker @AssistedInject constructor(
.use { response -> .use { response ->
val file = File(destination, UUID.randomUUID().toString() + ".tmp") val file = File(destination, UUID.randomUUID().toString() + ".tmp")
try { try {
checkNotNull(response.body).use { body -> response.requireBody().use { body ->
file.sink(append = false).buffer().use { file.sink(append = false).buffer().use {
it.writeAllCancellable(body.source()) it.writeAllCancellable(body.source())
} }

@ -15,10 +15,9 @@ import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.AlphanumComparator import org.koitharu.kotatsu.core.util.AlphanumComparator
import org.koitharu.kotatsu.core.util.ext.children
import org.koitharu.kotatsu.core.util.ext.deleteAwait import org.koitharu.kotatsu.core.util.ext.deleteAwait
import org.koitharu.kotatsu.core.util.ext.filterWith
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.withChildren
import org.koitharu.kotatsu.local.data.index.LocalMangaIndex import org.koitharu.kotatsu.local.data.index.LocalMangaIndex
import org.koitharu.kotatsu.local.data.input.LocalMangaInput import org.koitharu.kotatsu.local.data.input.LocalMangaInput
import org.koitharu.kotatsu.local.data.output.LocalMangaOutput import org.koitharu.kotatsu.local.data.output.LocalMangaOutput
@ -216,10 +215,15 @@ class LocalMangaRepository @Inject constructor(
} }
val dirs = storageManager.getWriteableDirs() val dirs = storageManager.getWriteableDirs()
runInterruptible(Dispatchers.IO) { runInterruptible(Dispatchers.IO) {
dirs.flatMap { dir -> val filter = TempFileFilter()
dir.children().filterWith(TempFileFilter()) dirs.forEach { dir ->
}.forEach { file -> dir.withChildren { children ->
file.deleteRecursively() children.forEach { child ->
if (filter.accept(child)) {
child.deleteRecursively()
}
}
}
} }
} }
return true return true
@ -246,7 +250,7 @@ class LocalMangaRepository @Inject constructor(
private suspend fun getAllFiles() = storageManager.getReadableDirs() private suspend fun getAllFiles() = storageManager.getReadableDirs()
.asSequence() .asSequence()
.flatMap { dir -> .flatMap { dir ->
dir.children().filterNot { it.isHidden } dir.withChildren { children -> children.filterNot { it.isHidden }.toList() }
} }
private fun Collection<LocalManga>.unwrap(): List<Manga> = map { it.manga } private fun Collection<LocalManga>.unwrap(): List<Manga> = map { it.manga }

@ -6,11 +6,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import org.koitharu.kotatsu.core.model.LocalMangaSource import org.koitharu.kotatsu.core.model.LocalMangaSource
import org.koitharu.kotatsu.core.util.AlphanumComparator import org.koitharu.kotatsu.core.util.AlphanumComparator
import org.koitharu.kotatsu.core.util.ext.children
import org.koitharu.kotatsu.core.util.ext.creationTime import org.koitharu.kotatsu.core.util.ext.creationTime
import org.koitharu.kotatsu.core.util.ext.longHashCode import org.koitharu.kotatsu.core.util.ext.longHashCode
import org.koitharu.kotatsu.core.util.ext.toListSorted import org.koitharu.kotatsu.core.util.ext.toListSorted
import org.koitharu.kotatsu.core.util.ext.walkCompat import org.koitharu.kotatsu.core.util.ext.walkCompat
import org.koitharu.kotatsu.core.util.ext.withChildren
import org.koitharu.kotatsu.local.data.MangaIndex import org.koitharu.kotatsu.local.data.MangaIndex
import org.koitharu.kotatsu.local.data.hasCbzExtension import org.koitharu.kotatsu.local.data.hasCbzExtension
import org.koitharu.kotatsu.local.data.hasImageExtension import org.koitharu.kotatsu.local.data.hasImageExtension
@ -101,13 +101,14 @@ class LocalMangaDirInput(root: File) : LocalMangaInput(root) {
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> = runInterruptible(Dispatchers.IO) { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> = runInterruptible(Dispatchers.IO) {
val file = chapter.url.toUri().toFile() val file = chapter.url.toUri().toFile()
if (file.isDirectory) { if (file.isDirectory) {
file.children() file.withChildren { children ->
.filter { it.isFile && hasImageExtension(it) } children
.toListSorted(compareBy(AlphanumComparator()) { x -> x.name }) .filter { it.isFile && hasImageExtension(it) }
.map { .toListSorted(compareBy(AlphanumComparator()) { x -> x.name })
val pageUri = it.toUri().toString() }.map {
MangaPage(pageUri.longHashCode(), pageUri, null, LocalMangaSource) val pageUri = it.toUri().toString()
} MangaPage(pageUri.longHashCode(), pageUri, null, LocalMangaSource)
}
} else { } else {
ZipFile(file).use { zip -> ZipFile(file).use { zip ->
zip.entries() zip.entries()
@ -153,6 +154,6 @@ class LocalMangaDirInput(root: File) : LocalMangaInput(root) {
} }
private fun File.isChapterDirectory(): Boolean { private fun File.isChapterDirectory(): Boolean {
return isDirectory && children().any { hasImageExtension(it) } return isDirectory && withChildren { children -> children.any { hasImageExtension(it) } }
} }
} }

@ -129,7 +129,7 @@ class LocalMangaDirOutput(
index.getChapterFileName(chapter.value.id)?.let { index.getChapterFileName(chapter.value.id)?.let {
return it return it
} }
val baseName = "${chapter.index}_${chapter.value.name.toFileNameSafe()}".take(18) val baseName = "${chapter.index}_${chapter.value.name.toFileNameSafe()}".take(32)
var i = 0 var i = 0
while (true) { while (true) {
val name = (if (i == 0) baseName else baseName + "_$i") + ".cbz" val name = (if (i == 0) baseName else baseName + "_$i") + ".cbz"

@ -56,6 +56,7 @@ import org.koitharu.kotatsu.local.data.isFileUri
import org.koitharu.kotatsu.local.data.isZipUri import org.koitharu.kotatsu.local.data.isZipUri
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.requireBody
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import java.util.LinkedList import java.util.LinkedList
@ -233,8 +234,7 @@ class PageLoader @Inject constructor(
else -> { else -> {
val request = createPageRequest(pageUrl, page.source) val request = createPageRequest(pageUrl, page.source)
imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response -> imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response ->
val body = checkNotNull(response.body) { "Null response body" } response.requireBody().withProgress(progress).use {
body.withProgress(progress).use {
cache.put(pageUrl, it.source()) cache.put(pageUrl, it.source())
} }
}.toUri() }.toUri()

@ -9,6 +9,7 @@ import org.koitharu.kotatsu.core.network.BaseHttpClient
import org.koitharu.kotatsu.core.util.ext.toRequestBody import org.koitharu.kotatsu.core.util.ext.toRequestBody
import org.koitharu.kotatsu.parsers.util.await import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.parseJson import org.koitharu.kotatsu.parsers.util.parseJson
import org.koitharu.kotatsu.parsers.util.parseRaw
import org.koitharu.kotatsu.parsers.util.removeSurrounding import org.koitharu.kotatsu.parsers.util.removeSurrounding
import javax.inject.Inject import javax.inject.Inject
@ -30,7 +31,7 @@ class SyncAuthApi @Inject constructor(
return response.parseJson().getString("token") return response.parseJson().getString("token")
} else { } else {
val code = response.code val code = response.code
val message = response.use { checkNotNull(it.body).string() }.removeSurrounding('"') val message = response.parseRaw().removeSurrounding('"')
throw SyncApiException(message, code) throw SyncApiException(message, code)
} }
} }

Loading…
Cancel
Save