Fix StrictMode errors

This commit is contained in:
Koitharu
2024-10-02 16:39:21 +03:00
parent ac96c49b60
commit e5b6947586
13 changed files with 105 additions and 117 deletions

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,6 @@ import org.jetbrains.annotations.Blocking
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.fs.FileSequence
import java.io.File
import java.io.FileFilter
import java.io.InputStream
import java.nio.file.attribute.BasicFileAttributes
import java.util.zip.ZipEntry
@@ -87,9 +86,13 @@ suspend fun File.computeSize(): Long = runInterruptible(Dispatchers.IO) {
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
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,10 +15,9 @@ import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
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.filterWith
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.input.LocalMangaInput
import org.koitharu.kotatsu.local.data.output.LocalMangaOutput
@@ -216,10 +215,15 @@ class LocalMangaRepository @Inject constructor(
}
val dirs = storageManager.getWriteableDirs()
runInterruptible(Dispatchers.IO) {
dirs.flatMap { dir ->
dir.children().filterWith(TempFileFilter())
}.forEach { file ->
file.deleteRecursively()
val filter = TempFileFilter()
dirs.forEach { dir ->
dir.withChildren { children ->
children.forEach { child ->
if (filter.accept(child)) {
child.deleteRecursively()
}
}
}
}
}
return true
@@ -246,7 +250,7 @@ class LocalMangaRepository @Inject constructor(
private suspend fun getAllFiles() = storageManager.getReadableDirs()
.asSequence()
.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 }

View File

@@ -6,11 +6,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import org.koitharu.kotatsu.core.model.LocalMangaSource
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.longHashCode
import org.koitharu.kotatsu.core.util.ext.toListSorted
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.hasCbzExtension
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) {
val file = chapter.url.toUri().toFile()
if (file.isDirectory) {
file.children()
.filter { it.isFile && hasImageExtension(it) }
.toListSorted(compareBy(AlphanumComparator()) { x -> x.name })
.map {
val pageUri = it.toUri().toString()
MangaPage(pageUri.longHashCode(), pageUri, null, LocalMangaSource)
}
file.withChildren { children ->
children
.filter { it.isFile && hasImageExtension(it) }
.toListSorted(compareBy(AlphanumComparator()) { x -> x.name })
}.map {
val pageUri = it.toUri().toString()
MangaPage(pageUri.longHashCode(), pageUri, null, LocalMangaSource)
}
} else {
ZipFile(file).use { zip ->
zip.entries()
@@ -153,6 +154,6 @@ class LocalMangaDirInput(root: File) : LocalMangaInput(root) {
}
private fun File.isChapterDirectory(): Boolean {
return isDirectory && children().any { hasImageExtension(it) }
return isDirectory && withChildren { children -> children.any { hasImageExtension(it) } }
}
}

View File

@@ -129,7 +129,7 @@ class LocalMangaDirOutput(
index.getChapterFileName(chapter.value.id)?.let {
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
while (true) {
val name = (if (i == 0) baseName else baseName + "_$i") + ".cbz"

View File

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

View File

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