Support nested cbz covers
This commit is contained in:
@@ -23,8 +23,9 @@ class CbzFetcher(
|
||||
override suspend fun fetch() = runInterruptible {
|
||||
val filePath = uri.schemeSpecificPart.toPath()
|
||||
val entryName = requireNotNull(uri.fragment)
|
||||
val fs = options.fileSystem.openZip(filePath)
|
||||
SourceFetchResult(
|
||||
source = ImageSource(entryName.toPath(), options.fileSystem.openZip(filePath)),
|
||||
source = ImageSource(entryName.toPath(), fs, closeable = fs),
|
||||
mimeType = MimeTypes.getMimeTypeFromExtension(entryName)?.toString(),
|
||||
dataSource = DataSource.DISK,
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.koitharu.kotatsu.core.model.LocalMangaSource
|
||||
import org.koitharu.kotatsu.core.util.AlphanumComparator
|
||||
import org.koitharu.kotatsu.core.util.MimeTypes
|
||||
import org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP
|
||||
import org.koitharu.kotatsu.core.util.ext.isDirectory
|
||||
import org.koitharu.kotatsu.core.util.ext.isFileUri
|
||||
import org.koitharu.kotatsu.core.util.ext.isImage
|
||||
import org.koitharu.kotatsu.core.util.ext.isRegularFile
|
||||
@@ -59,12 +60,13 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX)
|
||||
val mangaInfo = index?.getMangaInfo()
|
||||
if (mangaInfo != null) {
|
||||
val coverEntry: Path? =
|
||||
index.getCoverEntry()?.let { rootPath / it } ?: fileSystem.findFirstImage(rootPath)
|
||||
val coverEntry: Path? = index.getCoverEntry()?.let { rootPath / it }
|
||||
mangaInfo.copy(
|
||||
source = LocalMangaSource,
|
||||
url = rootFile.toUri().toString(),
|
||||
coverUrl = coverEntry?.let { uri.child(it, resolve = true).toString() },
|
||||
coverUrl = coverEntry?.let {
|
||||
uri.child(it, resolve = true).toString()
|
||||
} ?: fileSystem.findFirstImageUri(rootPath)?.toString(),
|
||||
largeCoverUrl = null,
|
||||
chapters = if (withDetails) {
|
||||
mangaInfo.chapters?.mapNotNull { c ->
|
||||
@@ -86,19 +88,17 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
)
|
||||
} else {
|
||||
val title = rootFile.name.fileNameToTitle()
|
||||
val coverEntry = fileSystem.findFirstImage(rootPath)
|
||||
Manga(
|
||||
id = rootFile.absolutePath.longHashCode(),
|
||||
title = title,
|
||||
url = rootFile.toUri().toString(),
|
||||
publicUrl = rootFile.toUri().toString(),
|
||||
source = LocalMangaSource,
|
||||
coverUrl = coverEntry?.let { uri.child(it, resolve = true).toString() },
|
||||
coverUrl = fileSystem.findFirstImageUri(rootPath)?.toString(),
|
||||
chapters = if (withDetails) {
|
||||
val chapters = fileSystem.listRecursively(rootPath)
|
||||
.mapNotNullTo(HashSet()) { path ->
|
||||
when {
|
||||
path == coverEntry -> null
|
||||
!fileSystem.isRegularFile(path) -> null
|
||||
path.isImage() -> path.parent
|
||||
hasZipExtension(path.name) -> path
|
||||
@@ -171,21 +171,55 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
}
|
||||
|
||||
private fun Uri.child(path: Path, resolve: Boolean): Uri {
|
||||
val file = toFile()
|
||||
val builder = buildUpon()
|
||||
if (isZipUri() || !resolve) {
|
||||
val isZip = isZipUri() || file.isZipArchive
|
||||
if (isZip) {
|
||||
builder.scheme(URI_SCHEME_ZIP)
|
||||
}
|
||||
if (isZip || !resolve) {
|
||||
builder.fragment(path.toString().removePrefix(Path.DIRECTORY_SEPARATOR))
|
||||
} else {
|
||||
val file = toFile()
|
||||
if (file.isZipArchive) {
|
||||
builder.fragment(path.toString().removePrefix(Path.DIRECTORY_SEPARATOR))
|
||||
builder.scheme(URI_SCHEME_ZIP)
|
||||
} else {
|
||||
builder.appendEncodedPath(path.relativeTo(file.toOkioPath()).toString())
|
||||
}
|
||||
builder.appendEncodedPath(path.relativeTo(file.toOkioPath()).toString())
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun FileSystem.findFirstImageUri(
|
||||
rootPath: Path,
|
||||
recursive: Boolean = false
|
||||
): Uri? = runCatchingCancellable {
|
||||
val list = list(rootPath)
|
||||
for (file in list.sortedWith(compareBy(AlphanumComparator()) { x -> x.name })) {
|
||||
if (isRegularFile(file)) {
|
||||
if (file.isImage()) {
|
||||
return@runCatchingCancellable uri.child(file, resolve = true)
|
||||
}
|
||||
if (recursive && file.isZip()) {
|
||||
openZip(file).use { zipFs ->
|
||||
zipFs.findFirstImageUri(Path.DIRECTORY_SEPARATOR.toPath())?.let { subUri ->
|
||||
val subPath = subUri.path.orEmpty().removePrefix(uri.path.orEmpty())
|
||||
.replace(REGEX_PARENT_PATH_PREFIX, "")
|
||||
return@runCatchingCancellable uri.child(file, resolve = true)
|
||||
.child(subPath.toPath(), resolve = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (recursive && isDirectory(file)) {
|
||||
findFirstImageUri(file, true)?.let {
|
||||
return@runCatchingCancellable it
|
||||
}
|
||||
}
|
||||
}
|
||||
if (recursive) {
|
||||
null
|
||||
} else {
|
||||
findFirstImageUri(rootPath, recursive = true)
|
||||
}
|
||||
}.onFailure { e ->
|
||||
e.printStackTraceDebug()
|
||||
}.getOrNull()
|
||||
|
||||
private class FsAndPath(
|
||||
val fileSystem: FileSystem,
|
||||
val path: Path,
|
||||
@@ -205,6 +239,8 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
|
||||
companion object {
|
||||
|
||||
private val REGEX_PARENT_PATH_PREFIX = Regex("^(/\\.\\.)+")
|
||||
|
||||
@Blocking
|
||||
fun getOrNull(file: File): LocalMangaParser? = if ((file.isDirectory || file.isZipArchive) && file.canRead()) {
|
||||
LocalMangaParser(file)
|
||||
@@ -225,26 +261,10 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
}
|
||||
}.flowOn(Dispatchers.Default).firstOrNull()
|
||||
|
||||
private fun FileSystem.findFirstImage(rootPath: Path) = findFirstImageImpl(rootPath, false)
|
||||
?: findFirstImageImpl(rootPath, true)
|
||||
|
||||
private fun FileSystem.findFirstImageImpl(
|
||||
rootPath: Path,
|
||||
recursive: Boolean
|
||||
): Path? = runCatchingCancellable {
|
||||
if (recursive) {
|
||||
listRecursively(rootPath)
|
||||
} else {
|
||||
list(rootPath).asSequence()
|
||||
}.filter { isRegularFile(it) && it.isImage() }
|
||||
.toListSorted(compareBy(AlphanumComparator()) { x -> x.toString() })
|
||||
.firstOrNull()
|
||||
}.onFailure { e ->
|
||||
e.printStackTraceDebug()
|
||||
}.getOrNull()
|
||||
|
||||
private fun Path.isImage(): Boolean = MimeTypes.getMimeTypeFromExtension(name)?.isImage == true
|
||||
|
||||
private fun Path.isZip(): Boolean = hasZipExtension(name)
|
||||
|
||||
private fun Uri.resolve(): Uri = if (isFileUri()) {
|
||||
val file = toFile()
|
||||
if (file.isZipArchive) {
|
||||
|
||||
Reference in New Issue
Block a user