|
|
|
@ -55,117 +55,119 @@ class LocalMangaParser(private val uri: Uri) {
|
|
|
|
private val rootFile: File = File(uri.schemeSpecificPart)
|
|
|
|
private val rootFile: File = File(uri.schemeSpecificPart)
|
|
|
|
|
|
|
|
|
|
|
|
suspend fun getManga(withDetails: Boolean): LocalManga = runInterruptible(Dispatchers.IO) {
|
|
|
|
suspend fun getManga(withDetails: Boolean): LocalManga = runInterruptible(Dispatchers.IO) {
|
|
|
|
val (fileSystem, rootPath) = uri.resolveFsAndPath()
|
|
|
|
(uri.resolveFsAndPath()).use { (fileSystem, rootPath) ->
|
|
|
|
val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX)
|
|
|
|
val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX)
|
|
|
|
val mangaInfo = index?.getMangaInfo()
|
|
|
|
val mangaInfo = index?.getMangaInfo()
|
|
|
|
if (mangaInfo != null) {
|
|
|
|
if (mangaInfo != null) {
|
|
|
|
val coverEntry: Path? = index.getCoverEntry()?.let { rootPath / it } ?: fileSystem.findFirstImage(rootPath)
|
|
|
|
val coverEntry: Path? =
|
|
|
|
mangaInfo.copy(
|
|
|
|
index.getCoverEntry()?.let { rootPath / it } ?: fileSystem.findFirstImage(rootPath)
|
|
|
|
source = LocalMangaSource,
|
|
|
|
mangaInfo.copy(
|
|
|
|
url = rootFile.toUri().toString(),
|
|
|
|
source = LocalMangaSource,
|
|
|
|
coverUrl = coverEntry?.let { uri.child(it, resolve = true).toString() }.orEmpty(),
|
|
|
|
url = rootFile.toUri().toString(),
|
|
|
|
largeCoverUrl = null,
|
|
|
|
coverUrl = coverEntry?.let { uri.child(it, resolve = true).toString() },
|
|
|
|
chapters = if (withDetails) {
|
|
|
|
largeCoverUrl = null,
|
|
|
|
mangaInfo.chapters?.mapNotNull { c ->
|
|
|
|
chapters = if (withDetails) {
|
|
|
|
val path = index.getChapterFileName(c.id)?.toPath()
|
|
|
|
mangaInfo.chapters?.mapNotNull { c ->
|
|
|
|
if (path != null && !fileSystem.exists(rootPath / path)) {
|
|
|
|
val path = index.getChapterFileName(c.id)?.toPath()
|
|
|
|
null
|
|
|
|
if (path != null && !fileSystem.exists(rootPath / path)) {
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
c.copy(
|
|
|
|
} else {
|
|
|
|
url = path?.let {
|
|
|
|
c.copy(
|
|
|
|
uri.child(it, resolve = false).toString()
|
|
|
|
url = path?.let {
|
|
|
|
} ?: uri.toString(),
|
|
|
|
uri.child(it, resolve = false).toString()
|
|
|
|
|
|
|
|
} ?: uri.toString(),
|
|
|
|
|
|
|
|
source = LocalMangaSource,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
null
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
} 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() },
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
else -> null
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}.sortedWith(compareBy(AlphanumComparator()) { x -> x.toString() })
|
|
|
|
|
|
|
|
chapters.mapIndexed { i, p ->
|
|
|
|
|
|
|
|
val s = if (p.root == rootPath.root) {
|
|
|
|
|
|
|
|
p.relativeTo(rootPath).toString()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
p
|
|
|
|
|
|
|
|
}.toString().removePrefix(Path.DIRECTORY_SEPARATOR)
|
|
|
|
|
|
|
|
MangaChapter(
|
|
|
|
|
|
|
|
id = "$i$s".longHashCode(),
|
|
|
|
|
|
|
|
name = s.fileNameToTitle().ifEmpty { title },
|
|
|
|
|
|
|
|
number = 0f,
|
|
|
|
|
|
|
|
volume = 0,
|
|
|
|
source = LocalMangaSource,
|
|
|
|
source = LocalMangaSource,
|
|
|
|
|
|
|
|
uploadDate = 0L,
|
|
|
|
|
|
|
|
url = uri.child(p.relativeTo(rootPath), resolve = false).toString(),
|
|
|
|
|
|
|
|
scanlator = null,
|
|
|
|
|
|
|
|
branch = null,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
null
|
|
|
|
},
|
|
|
|
},
|
|
|
|
altTitle = null,
|
|
|
|
)
|
|
|
|
rating = -1f,
|
|
|
|
} else {
|
|
|
|
isNsfw = false,
|
|
|
|
val title = rootFile.name.fileNameToTitle()
|
|
|
|
tags = setOf(),
|
|
|
|
val coverEntry = fileSystem.findFirstImage(rootPath)
|
|
|
|
state = null,
|
|
|
|
Manga(
|
|
|
|
author = null,
|
|
|
|
id = rootFile.absolutePath.longHashCode(),
|
|
|
|
largeCoverUrl = null,
|
|
|
|
title = title,
|
|
|
|
description = null,
|
|
|
|
url = rootFile.toUri().toString(),
|
|
|
|
)
|
|
|
|
publicUrl = rootFile.toUri().toString(),
|
|
|
|
}.let { LocalManga(it, rootFile) }
|
|
|
|
source = LocalMangaSource,
|
|
|
|
}
|
|
|
|
coverUrl = coverEntry?.let {
|
|
|
|
|
|
|
|
uri.child(it, resolve = true).toString()
|
|
|
|
|
|
|
|
}.orEmpty(),
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
else -> null
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}.sortedWith(compareBy(AlphanumComparator()) { x -> x.toString() })
|
|
|
|
|
|
|
|
chapters.mapIndexed { i, p ->
|
|
|
|
|
|
|
|
val s = if (p.root == rootPath.root) {
|
|
|
|
|
|
|
|
p.relativeTo(rootPath).toString()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
p
|
|
|
|
|
|
|
|
}.toString().removePrefix(Path.DIRECTORY_SEPARATOR)
|
|
|
|
|
|
|
|
MangaChapter(
|
|
|
|
|
|
|
|
id = "$i$s".longHashCode(),
|
|
|
|
|
|
|
|
name = s.fileNameToTitle().ifEmpty { title },
|
|
|
|
|
|
|
|
number = 0f,
|
|
|
|
|
|
|
|
volume = 0,
|
|
|
|
|
|
|
|
source = LocalMangaSource,
|
|
|
|
|
|
|
|
uploadDate = 0L,
|
|
|
|
|
|
|
|
url = uri.child(p.relativeTo(rootPath), resolve = false).toString(),
|
|
|
|
|
|
|
|
scanlator = null,
|
|
|
|
|
|
|
|
branch = null,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
null
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
altTitle = null,
|
|
|
|
|
|
|
|
rating = -1f,
|
|
|
|
|
|
|
|
isNsfw = false,
|
|
|
|
|
|
|
|
tags = setOf(),
|
|
|
|
|
|
|
|
state = null,
|
|
|
|
|
|
|
|
author = null,
|
|
|
|
|
|
|
|
largeCoverUrl = null,
|
|
|
|
|
|
|
|
description = null,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}.let { LocalManga(it, rootFile) }
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
suspend fun getMangaInfo(): Manga? = runInterruptible(Dispatchers.IO) {
|
|
|
|
suspend fun getMangaInfo(): Manga? = runInterruptible(Dispatchers.IO) {
|
|
|
|
val (fileSystem, rootPath) = uri.resolveFsAndPath()
|
|
|
|
uri.resolveFsAndPath().use { (fileSystem, rootPath) ->
|
|
|
|
val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX)
|
|
|
|
val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX)
|
|
|
|
index?.getMangaInfo()
|
|
|
|
index?.getMangaInfo()
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
suspend fun getPages(chapter: MangaChapter): List<MangaPage> = runInterruptible(Dispatchers.IO) {
|
|
|
|
suspend fun getPages(chapter: MangaChapter): List<MangaPage> = runInterruptible(Dispatchers.IO) {
|
|
|
|
val chapterUri = chapter.url.toUri().resolve()
|
|
|
|
val chapterUri = chapter.url.toUri().resolve()
|
|
|
|
val (fileSystem, rootPath) = chapterUri.resolveFsAndPath()
|
|
|
|
chapterUri.resolveFsAndPath().use { (fileSystem, rootPath) ->
|
|
|
|
val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX)
|
|
|
|
val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX)
|
|
|
|
val entries = fileSystem.listRecursively(rootPath)
|
|
|
|
val entries = fileSystem.listRecursively(rootPath)
|
|
|
|
.filter { fileSystem.isRegularFile(it) }
|
|
|
|
.filter { fileSystem.isRegularFile(it) }
|
|
|
|
if (index != null) {
|
|
|
|
if (index != null) {
|
|
|
|
val pattern = index.getChapterNamesPattern(chapter)
|
|
|
|
val pattern = index.getChapterNamesPattern(chapter)
|
|
|
|
entries.filter { x -> x.name.substringBefore('.').matches(pattern) }
|
|
|
|
entries.filter { x -> x.name.substringBefore('.').matches(pattern) }
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
entries.filter { x -> x.isImage() && x.parent == rootPath }
|
|
|
|
entries.filter { x -> x.isImage() && x.parent == rootPath }
|
|
|
|
}.toListSorted(compareBy(AlphanumComparator()) { x -> x.toString() })
|
|
|
|
}.toListSorted(compareBy(AlphanumComparator()) { x -> x.toString() })
|
|
|
|
.map { x ->
|
|
|
|
.map { x ->
|
|
|
|
val entryUri = chapterUri.child(x, resolve = true).toString()
|
|
|
|
val entryUri = chapterUri.child(x, resolve = true).toString()
|
|
|
|
MangaPage(
|
|
|
|
MangaPage(
|
|
|
|
id = entryUri.longHashCode(),
|
|
|
|
id = entryUri.longHashCode(),
|
|
|
|
url = entryUri,
|
|
|
|
url = entryUri,
|
|
|
|
preview = null,
|
|
|
|
preview = null,
|
|
|
|
source = LocalMangaSource,
|
|
|
|
source = LocalMangaSource,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun Uri.child(path: Path, resolve: Boolean): Uri {
|
|
|
|
private fun Uri.child(path: Path, resolve: Boolean): Uri {
|
|
|
|
@ -184,6 +186,23 @@ class LocalMangaParser(private val uri: Uri) {
|
|
|
|
return builder.build()
|
|
|
|
return builder.build()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class FsAndPath(
|
|
|
|
|
|
|
|
val fileSystem: FileSystem,
|
|
|
|
|
|
|
|
val path: Path,
|
|
|
|
|
|
|
|
private val isCloseable: Boolean,
|
|
|
|
|
|
|
|
) : AutoCloseable {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun close() {
|
|
|
|
|
|
|
|
if (isCloseable) {
|
|
|
|
|
|
|
|
fileSystem.close()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
operator fun component1() = fileSystem
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
operator fun component2() = path
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
companion object {
|
|
|
|
|
|
|
|
|
|
|
|
@Blocking
|
|
|
|
@Blocking
|
|
|
|
@ -240,20 +259,25 @@ class LocalMangaParser(private val uri: Uri) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Blocking
|
|
|
|
@Blocking
|
|
|
|
private fun Uri.resolveFsAndPath(): Pair<FileSystem, Path> {
|
|
|
|
private fun Uri.resolveFsAndPath(): FsAndPath {
|
|
|
|
val resolved = resolve()
|
|
|
|
val resolved = resolve()
|
|
|
|
return when {
|
|
|
|
return when {
|
|
|
|
resolved.isZipUri() -> {
|
|
|
|
resolved.isZipUri() -> FsAndPath(
|
|
|
|
FileSystem.SYSTEM.openZip(resolved.schemeSpecificPart.toPath()) to resolved.fragment.orEmpty()
|
|
|
|
FileSystem.SYSTEM.openZip(resolved.schemeSpecificPart.toPath()),
|
|
|
|
.toRootedPath()
|
|
|
|
resolved.fragment.orEmpty().toRootedPath(),
|
|
|
|
}
|
|
|
|
isCloseable = true,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
isFileUri() -> {
|
|
|
|
isFileUri() -> {
|
|
|
|
val file = toFile()
|
|
|
|
val file = toFile()
|
|
|
|
if (file.isZipArchive) {
|
|
|
|
if (file.isZipArchive) {
|
|
|
|
FileSystem.SYSTEM.openZip(schemeSpecificPart.toPath()) to fragment.orEmpty().toRootedPath()
|
|
|
|
FsAndPath(
|
|
|
|
|
|
|
|
FileSystem.SYSTEM.openZip(schemeSpecificPart.toPath()),
|
|
|
|
|
|
|
|
fragment.orEmpty().toRootedPath(),
|
|
|
|
|
|
|
|
isCloseable = true,
|
|
|
|
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
FileSystem.SYSTEM to file.toOkioPath()
|
|
|
|
FsAndPath(FileSystem.SYSTEM, file.toOkioPath(), isCloseable = false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|