Skip to content

Commit

Permalink
Fix local manga directories chapters
Browse files Browse the repository at this point in the history
  • Loading branch information
Koitharu committed Feb 28, 2024
1 parent 9f31133 commit f8cbc96
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 41 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 34
versionCode = 625
versionName = '6.7.3'
versionCode = 626
versionName = '6.7.4'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {
Expand Down
15 changes: 11 additions & 4 deletions app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/File.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import java.nio.file.attribute.BasicFileAttributes
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.PathWalkOption
import kotlin.io.path.readAttributes
import kotlin.io.path.walk

Expand Down Expand Up @@ -72,7 +73,7 @@ fun ContentResolver.resolveName(uri: Uri): String? {
}

suspend fun File.computeSize(): Long = runInterruptible(Dispatchers.IO) {
walkCompat().sumOf { it.length() }
walkCompat(includeDirectories = false).sumOf { it.length() }
}

fun File.children() = FileSequence(this)
Expand All @@ -87,10 +88,16 @@ val File.creationTime
}

@OptIn(ExperimentalPathApi::class)
fun File.walkCompat() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
fun File.walkCompat(includeDirectories: Boolean): Sequence<File> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Use lazy loading on Android 8.0 and later
toPath().walk().map { it.toFile() }
val walk = if (includeDirectories) {
toPath().walk(PathWalkOption.INCLUDE_DIRECTORIES)
} else {
toPath().walk()
}
walk.map { it.toFile() }
} else {
// Directories are excluded by default in Path.walk(), so do it here as well
walk().filter { it.isFile }
val walk = walk()
if (includeDirectories) walk else walk.filter { it.isFile }
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ fun File.hasCbzExtension() = isCbzExtension(extension)
fun Uri.isZipUri() = scheme.let {
it == URI_SCHEME_ZIP || it == "cbz" || it == "zip"
}

fun Uri.isFileUri() = scheme == "file"
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.core.net.toUri
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
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
Expand Down Expand Up @@ -100,8 +101,8 @@ 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.walkCompat()
.filter { hasImageExtension(it) }
file.children()
.filter { it.isFile && hasImageExtension(it) }
.toListSorted(compareBy(AlphanumComparator()) { x -> x.name })
.map {
val pageUri = it.toUri().toString()
Expand Down Expand Up @@ -129,14 +130,16 @@ class LocalMangaDirInput(root: File) : LocalMangaInput(root) {

private fun String.toHumanReadable() = replace("_", " ").toCamelCase()

private fun getChaptersFiles() = root.walkCompat()
.filter { it.hasCbzExtension() }
private fun getChaptersFiles() = root.walkCompat(includeDirectories = true)
.filter { it != root && it.isChapterDirectory() || it.hasCbzExtension() }
.associateByTo(TreeMap(AlphanumComparator())) { it.name }

private fun findFirstImageEntry(): String? {
return root.walkCompat().firstOrNull { hasImageExtension(it) }?.toUri()?.toString()
return root.walkCompat(includeDirectories = false)
.firstOrNull { hasImageExtension(it) }?.toUri()?.toString()
?: run {
val cbz = root.walkCompat().firstOrNull { it.hasCbzExtension() } ?: return null
val cbz = root.walkCompat(includeDirectories = false)
.firstOrNull { it.hasCbzExtension() } ?: return null
ZipFile(cbz).use { zip ->
zip.entries().asSequence()
.firstOrNull { !it.isDirectory && hasImageExtension(it.name) }
Expand All @@ -148,4 +151,8 @@ class LocalMangaDirInput(root: File) : LocalMangaInput(root) {
private fun fileUri(base: File, name: String): String {
return File(base, name).toUri().toString()
}

private fun File.isChapterDirectory(): Boolean {
return isDirectory && children().any { hasImageExtension(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import org.koitharu.kotatsu.core.util.ext.ramAvailable
import org.koitharu.kotatsu.core.util.ext.withProgress
import org.koitharu.kotatsu.core.util.progress.ProgressDeferred
import org.koitharu.kotatsu.local.data.PagesCache
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
Expand Down Expand Up @@ -203,20 +204,23 @@ class PageLoader @Inject constructor(
val pageUrl = getPageUrl(page)
check(pageUrl.isNotBlank()) { "Cannot obtain full image url for $page" }
val uri = Uri.parse(pageUrl)
return if (uri.isZipUri()) {
if (uri.scheme == URI_SCHEME_ZIP) {
return when {
uri.isZipUri() -> if (uri.scheme == URI_SCHEME_ZIP) {
uri
} else { // legacy uri
uri.buildUpon().scheme(URI_SCHEME_ZIP).build()
}
} else {
val request = createPageRequest(page, pageUrl)
imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response ->
val body = checkNotNull(response.body) { "Null response body" }
body.withProgress(progress).use {
cache.put(pageUrl, it.source())
}
}.toUri()

uri.isFileUri() -> uri
else -> {
val request = createPageRequest(page, pageUrl)
imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response ->
val body = checkNotNull(response.body) { "Null response body" }
body.withProgress(progress).use {
cache.put(pageUrl, it.source())
}
}.toUri()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.koitharu.kotatsu.reader.ui.thumbnails

import android.content.Context
import android.webkit.MimeTypeMap
import androidx.core.net.toFile
import androidx.core.net.toUri
import coil.ImageLoader
import coil.decode.DataSource
Expand All @@ -20,6 +22,7 @@ import org.koitharu.kotatsu.core.network.ImageProxyInterceptor
import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.data.isFileUri
import org.koitharu.kotatsu.local.data.isZipUri
import org.koitharu.kotatsu.local.data.util.withExtraCloseable
import org.koitharu.kotatsu.parsers.model.MangaPage
Expand Down Expand Up @@ -56,8 +59,8 @@ class MangaPageFetcher(

private suspend fun loadPage(pageUrl: String): SourceResult {
val uri = pageUrl.toUri()
return if (uri.isZipUri()) {
runInterruptible(Dispatchers.IO) {
return when {
uri.isZipUri() -> runInterruptible(Dispatchers.IO) {
val zip = ZipFile(uri.schemeSpecificPart)
val entry = zip.getEntry(uri.fragment)
SourceResult(
Expand All @@ -66,32 +69,48 @@ class MangaPageFetcher(
context = context,
metadata = MangaPageMetadata(page),
),
mimeType = null,
mimeType = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(entry.name.substringAfterLast('.', "")),
dataSource = DataSource.DISK,
)
}
} else {
val request = PageLoader.createPageRequest(page, pageUrl)
imageProxyInterceptor.interceptPageRequest(request, okHttpClient).use { response ->
check(response.isSuccessful) {
"Invalid response: ${response.code} ${response.message} at $pageUrl"
}
val body = checkNotNull(response.body) {
"Null response"
}
val mimeType = response.mimeType
val file = body.use {
pagesCache.put(pageUrl, it.source())
}

uri.isFileUri() -> runInterruptible(Dispatchers.IO) {
val file = uri.toFile()
SourceResult(
source = ImageSource(
file = file.toOkioPath(),
source = file.source().buffer(),
context = context,
metadata = MangaPageMetadata(page),
),
mimeType = mimeType,
dataSource = DataSource.NETWORK,
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension),
dataSource = DataSource.DISK,
)
}

else -> {
val request = PageLoader.createPageRequest(page, pageUrl)
imageProxyInterceptor.interceptPageRequest(request, okHttpClient).use { response ->
check(response.isSuccessful) {
"Invalid response: ${response.code} ${response.message} at $pageUrl"
}
val body = checkNotNull(response.body) {
"Null response"
}
val mimeType = response.mimeType
val file = body.use {
pagesCache.put(pageUrl, it.source())
}
SourceResult(
source = ImageSource(
file = file.toOkioPath(),
metadata = MangaPageMetadata(page),
),
mimeType = mimeType,
dataSource = DataSource.NETWORK,
)
}
}
}
}

Expand Down

0 comments on commit f8cbc96

Please sign in to comment.