2023-02-26 22:16:49 +01:00
|
|
|
package tachiyomi.source.local
|
2017-01-29 20:48:55 +01:00
|
|
|
|
|
|
|
|
import android.content.Context
|
2023-02-26 22:16:49 +01:00
|
|
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
2023-03-20 03:38:14 +01:00
|
|
|
import eu.kanade.tachiyomi.source.Source
|
2023-02-26 22:16:49 +01:00
|
|
|
import eu.kanade.tachiyomi.source.UnmeteredSource
|
2020-02-27 00:12:44 +01:00
|
|
|
import eu.kanade.tachiyomi.source.model.FilterList
|
|
|
|
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
|
|
|
|
import eu.kanade.tachiyomi.source.model.SChapter
|
|
|
|
|
import eu.kanade.tachiyomi.source.model.SManga
|
2020-02-03 04:22:54 +01:00
|
|
|
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
2020-02-03 04:04:11 +01:00
|
|
|
import eu.kanade.tachiyomi.util.storage.EpubFile
|
2021-11-19 22:42:19 +01:00
|
|
|
import kotlinx.coroutines.runBlocking
|
2020-10-12 20:20:54 +02:00
|
|
|
import kotlinx.serialization.json.Json
|
2021-09-06 17:54:00 +02:00
|
|
|
import kotlinx.serialization.json.decodeFromStream
|
|
|
|
|
import kotlinx.serialization.json.encodeToStream
|
2022-07-23 17:14:34 +02:00
|
|
|
import logcat.LogPriority
|
2022-09-18 16:55:30 +02:00
|
|
|
import nl.adaptivity.xmlutil.AndroidXmlReader
|
|
|
|
|
import nl.adaptivity.xmlutil.serialization.XML
|
2020-09-14 00:48:20 +02:00
|
|
|
import rx.Observable
|
2023-03-20 03:38:14 +01:00
|
|
|
import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
|
|
|
|
|
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
|
|
|
|
import tachiyomi.core.metadata.comicinfo.copyFromComicInfo
|
2023-02-26 22:16:49 +01:00
|
|
|
import tachiyomi.core.metadata.tachiyomi.MangaDetails
|
2023-01-28 04:31:12 +01:00
|
|
|
import tachiyomi.core.util.lang.withIOContext
|
2023-02-26 22:16:49 +01:00
|
|
|
import tachiyomi.core.util.system.ImageUtil
|
2023-01-28 04:31:12 +01:00
|
|
|
import tachiyomi.core.util.system.logcat
|
2023-02-18 21:24:04 +01:00
|
|
|
import tachiyomi.domain.chapter.service.ChapterRecognition
|
2023-03-20 03:38:14 +01:00
|
|
|
import tachiyomi.domain.manga.model.Manga
|
2023-02-26 22:16:49 +01:00
|
|
|
import tachiyomi.source.local.filter.OrderBy
|
|
|
|
|
import tachiyomi.source.local.image.LocalCoverManager
|
|
|
|
|
import tachiyomi.source.local.io.Archive
|
|
|
|
|
import tachiyomi.source.local.io.Format
|
|
|
|
|
import tachiyomi.source.local.io.LocalSourceFileSystem
|
|
|
|
|
import tachiyomi.source.local.metadata.fillChapterMetadata
|
|
|
|
|
import tachiyomi.source.local.metadata.fillMangaMetadata
|
2021-09-06 17:54:00 +02:00
|
|
|
import uy.kohesive.injekt.injectLazy
|
2017-01-29 20:48:55 +01:00
|
|
|
import java.io.File
|
|
|
|
|
import java.io.FileInputStream
|
|
|
|
|
import java.io.InputStream
|
2022-09-18 16:55:30 +02:00
|
|
|
import java.nio.charset.StandardCharsets
|
2017-01-29 20:48:55 +01:00
|
|
|
import java.util.zip.ZipFile
|
2023-03-20 13:41:44 +01:00
|
|
|
import kotlin.time.Duration.Companion.days
|
2023-02-26 22:16:49 +01:00
|
|
|
import com.github.junrar.Archive as JunrarArchive
|
2023-03-20 13:41:44 +01:00
|
|
|
import tachiyomi.domain.source.model.Source as DomainSource
|
2017-01-29 20:48:55 +01:00
|
|
|
|
2023-03-05 16:16:19 +01:00
|
|
|
actual class LocalSource(
|
2022-05-25 00:02:02 +02:00
|
|
|
private val context: Context,
|
2023-02-26 22:16:49 +01:00
|
|
|
private val fileSystem: LocalSourceFileSystem,
|
|
|
|
|
private val coverManager: LocalCoverManager,
|
|
|
|
|
// SY -->
|
|
|
|
|
private val allowHiddenFiles: () -> Boolean,
|
|
|
|
|
// SY <--
|
2022-05-25 00:02:02 +02:00
|
|
|
) : CatalogueSource, UnmeteredSource {
|
2017-01-29 20:48:55 +01:00
|
|
|
|
2021-09-06 17:54:00 +02:00
|
|
|
private val json: Json by injectLazy()
|
2022-09-18 16:55:30 +02:00
|
|
|
private val xml: XML by injectLazy()
|
2021-09-11 17:48:32 +02:00
|
|
|
|
2023-02-26 22:16:49 +01:00
|
|
|
private val POPULAR_FILTERS = FilterList(OrderBy.Popular(context))
|
|
|
|
|
private val LATEST_FILTERS = FilterList(OrderBy.Latest(context))
|
2021-09-06 17:54:00 +02:00
|
|
|
|
2022-05-25 00:02:02 +02:00
|
|
|
override val name: String = context.getString(R.string.local_source)
|
|
|
|
|
|
|
|
|
|
override val id: Long = ID
|
|
|
|
|
|
|
|
|
|
override val lang: String = "other"
|
2017-01-29 20:48:55 +01:00
|
|
|
|
2021-10-31 00:36:23 +02:00
|
|
|
override fun toString() = name
|
2017-01-29 20:48:55 +01:00
|
|
|
|
2022-05-25 00:02:02 +02:00
|
|
|
override val supportsLatest: Boolean = true
|
|
|
|
|
|
|
|
|
|
// Browse related
|
2017-01-29 20:48:55 +01:00
|
|
|
override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", POPULAR_FILTERS)
|
|
|
|
|
|
2022-05-25 00:02:02 +02:00
|
|
|
override fun fetchLatestUpdates(page: Int) = fetchSearchManga(page, "", LATEST_FILTERS)
|
|
|
|
|
|
2017-01-29 20:48:55 +01:00
|
|
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
2023-02-26 22:16:49 +01:00
|
|
|
val baseDirsFiles = fileSystem.getFilesInBaseDirectories()
|
|
|
|
|
val lastModifiedLimit by lazy { if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L }
|
2020-09-06 02:13:44 +02:00
|
|
|
// SY -->
|
2023-02-26 22:16:49 +01:00
|
|
|
val allowLocalSourceHiddenFolders = allowHiddenFiles()
|
2020-09-06 02:13:44 +02:00
|
|
|
// SY <--
|
2017-01-29 20:48:55 +01:00
|
|
|
|
2022-05-25 00:02:02 +02:00
|
|
|
var mangaDirs = baseDirsFiles
|
|
|
|
|
// Filter out files that are hidden and is not a folder
|
|
|
|
|
.filter { it.isDirectory && /* SY --> */ (!it.name.startsWith('.') || allowLocalSourceHiddenFolders) /* SY <-- */ }
|
2020-04-25 20:24:45 +02:00
|
|
|
.distinctBy { it.name }
|
2023-02-26 22:16:49 +01:00
|
|
|
.filter { // Filter by query or last modified
|
|
|
|
|
if (lastModifiedLimit == 0L) {
|
|
|
|
|
it.name.contains(query, ignoreCase = true)
|
|
|
|
|
} else {
|
|
|
|
|
it.lastModified() >= lastModifiedLimit
|
|
|
|
|
}
|
2017-01-29 20:48:55 +01:00
|
|
|
}
|
2022-05-25 00:02:02 +02:00
|
|
|
|
|
|
|
|
filters.forEach { filter ->
|
|
|
|
|
when (filter) {
|
2023-02-26 22:16:49 +01:00
|
|
|
is OrderBy.Popular -> {
|
|
|
|
|
mangaDirs = if (filter.state!!.ascending) {
|
|
|
|
|
mangaDirs.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
|
|
|
|
} else {
|
|
|
|
|
mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
is OrderBy.Latest -> {
|
|
|
|
|
mangaDirs = if (filter.state!!.ascending) {
|
|
|
|
|
mangaDirs.sortedBy(File::lastModified)
|
|
|
|
|
} else {
|
|
|
|
|
mangaDirs.sortedByDescending(File::lastModified)
|
2022-05-25 00:02:02 +02:00
|
|
|
}
|
2020-04-25 20:24:45 +02:00
|
|
|
}
|
2022-05-25 00:02:02 +02:00
|
|
|
|
2022-08-18 20:06:32 +02:00
|
|
|
else -> {
|
|
|
|
|
/* Do nothing */
|
|
|
|
|
}
|
2017-01-29 20:48:55 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-25 00:02:02 +02:00
|
|
|
// Transform mangaDirs to list of SManga
|
2017-01-29 20:48:55 +01:00
|
|
|
val mangas = mangaDirs.map { mangaDir ->
|
|
|
|
|
SManga.create().apply {
|
|
|
|
|
title = mangaDir.name
|
|
|
|
|
url = mangaDir.name
|
|
|
|
|
|
|
|
|
|
// Try to find the cover
|
2023-02-26 22:16:49 +01:00
|
|
|
coverManager.find(mangaDir.name)
|
|
|
|
|
?.takeIf(File::exists)
|
|
|
|
|
?.let { thumbnail_url = it.absolutePath }
|
2022-05-25 00:02:02 +02:00
|
|
|
}
|
|
|
|
|
}
|
2017-01-29 20:48:55 +01:00
|
|
|
|
2022-05-25 00:02:02 +02:00
|
|
|
// Fetch chapters of all the manga
|
|
|
|
|
mangas.forEach { manga ->
|
|
|
|
|
runBlocking {
|
2022-08-18 20:07:13 +02:00
|
|
|
val chapters = getChapterList(manga)
|
2022-05-25 00:02:02 +02:00
|
|
|
if (chapters.isNotEmpty()) {
|
2022-08-18 20:07:13 +02:00
|
|
|
val chapter = chapters.last()
|
2022-05-25 00:02:02 +02:00
|
|
|
val format = getFormat(chapter)
|
2020-04-28 04:49:06 +02:00
|
|
|
|
2022-05-25 00:02:02 +02:00
|
|
|
if (format is Format.Epub) {
|
|
|
|
|
EpubFile(format.file).use { epub ->
|
|
|
|
|
epub.fillMangaMetadata(manga)
|
2017-01-29 20:48:55 +01:00
|
|
|
}
|
|
|
|
|
}
|
2022-05-25 00:02:02 +02:00
|
|
|
|
|
|
|
|
// Copy the cover from the first chapter found if not available
|
|
|
|
|
if (manga.thumbnail_url == null) {
|
|
|
|
|
updateCover(chapter, manga)
|
|
|
|
|
}
|
2017-01-29 20:48:55 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-25 20:20:47 +02:00
|
|
|
|
|
|
|
|
return Observable.just(MangasPage(mangas.toList(), false))
|
2017-01-29 20:48:55 +01:00
|
|
|
}
|
|
|
|
|
|
2020-07-13 01:21:29 +02:00
|
|
|
// SY -->
|
2020-07-11 20:53:59 +02:00
|
|
|
fun updateMangaInfo(manga: SManga) {
|
2023-02-26 22:16:49 +01:00
|
|
|
val directory = fileSystem.getFilesInBaseDirectories().map { File(it, manga.url) }.find {
|
2020-07-11 20:53:59 +02:00
|
|
|
it.exists()
|
|
|
|
|
} ?: return
|
|
|
|
|
val existingFileName = directory.listFiles()?.find { it.extension == "json" }?.name
|
|
|
|
|
val file = File(directory, existingFileName ?: "info.json")
|
2021-09-06 17:54:00 +02:00
|
|
|
file.outputStream().use {
|
|
|
|
|
json.encodeToStream(manga.toJson(), it)
|
|
|
|
|
}
|
2020-07-11 20:53:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-18 20:07:13 +02:00
|
|
|
private fun SManga.toJson(): MangaDetails {
|
|
|
|
|
return MangaDetails(title, author, artist, description, genre?.split(", "), status)
|
2020-07-11 20:53:59 +02:00
|
|
|
}
|
2020-07-13 01:21:29 +02:00
|
|
|
// SY <--
|
2020-07-11 20:53:59 +02:00
|
|
|
|
2022-05-25 00:02:02 +02:00
|
|
|
// Manga details related
|
2022-09-18 16:55:30 +02:00
|
|
|
override suspend fun getMangaDetails(manga: SManga): SManga = withIOContext {
|
2023-02-26 22:16:49 +01:00
|
|
|
coverManager.find(manga.url)?.let {
|
2022-08-18 20:07:13 +02:00
|
|
|
manga.thumbnail_url = it.absolutePath
|
2022-05-25 00:02:02 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-18 16:55:30 +02:00
|
|
|
// Augment manga details based on metadata files
|
|
|
|
|
try {
|
2023-02-26 22:16:49 +01:00
|
|
|
val mangaDirFiles = fileSystem.getFilesInMangaDirectory(manga.url).toList()
|
2022-12-04 20:00:23 +01:00
|
|
|
|
2022-10-01 05:41:40 +02:00
|
|
|
val comicInfoFile = mangaDirFiles
|
|
|
|
|
.firstOrNull { it.name == COMIC_INFO_FILE }
|
|
|
|
|
val noXmlFile = mangaDirFiles
|
|
|
|
|
.firstOrNull { it.name == ".noxml" }
|
2022-12-04 20:00:23 +01:00
|
|
|
val legacyJsonDetailsFile = mangaDirFiles
|
|
|
|
|
.firstOrNull { it.extension == "json" }
|
2022-09-18 16:55:30 +02:00
|
|
|
|
|
|
|
|
when {
|
|
|
|
|
// Top level ComicInfo.xml
|
2022-10-01 05:41:40 +02:00
|
|
|
comicInfoFile != null -> {
|
2022-12-04 20:00:23 +01:00
|
|
|
noXmlFile?.delete()
|
2022-10-01 05:41:40 +02:00
|
|
|
setMangaDetailsFromComicInfoFile(comicInfoFile.inputStream(), manga)
|
2022-09-18 16:55:30 +02:00
|
|
|
}
|
|
|
|
|
|
2022-12-04 20:00:23 +01:00
|
|
|
// TODO: automatically convert these to ComicInfo.xml
|
|
|
|
|
legacyJsonDetailsFile != null -> {
|
|
|
|
|
json.decodeFromStream<MangaDetails>(legacyJsonDetailsFile.inputStream()).run {
|
|
|
|
|
title?.let { manga.title = it }
|
|
|
|
|
author?.let { manga.author = it }
|
|
|
|
|
artist?.let { manga.artist = it }
|
|
|
|
|
description?.let { manga.description = it }
|
|
|
|
|
genre?.let { manga.genre = it.joinToString() }
|
|
|
|
|
status?.let { manga.status = it }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-18 16:55:30 +02:00
|
|
|
// Copy ComicInfo.xml from chapter archive to top level if found
|
2022-10-01 05:41:40 +02:00
|
|
|
noXmlFile == null -> {
|
2022-09-18 16:55:30 +02:00
|
|
|
val chapterArchives = mangaDirFiles
|
2023-02-26 22:16:49 +01:00
|
|
|
.filter(Archive::isSupported)
|
2022-09-18 16:55:30 +02:00
|
|
|
.toList()
|
|
|
|
|
|
2023-02-26 22:16:49 +01:00
|
|
|
val mangaDir = fileSystem.getMangaDirectory(manga.url)
|
2022-09-18 16:55:30 +02:00
|
|
|
val folderPath = mangaDir?.absolutePath
|
|
|
|
|
|
|
|
|
|
val copiedFile = copyComicInfoFileFromArchive(chapterArchives, folderPath)
|
|
|
|
|
if (copiedFile != null) {
|
|
|
|
|
setMangaDetailsFromComicInfoFile(copiedFile.inputStream(), manga)
|
|
|
|
|
} else {
|
|
|
|
|
// Avoid re-scanning
|
|
|
|
|
File("$folderPath/.noxml").createNewFile()
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-18 20:07:13 +02:00
|
|
|
}
|
2022-09-18 16:55:30 +02:00
|
|
|
} catch (e: Throwable) {
|
|
|
|
|
logcat(LogPriority.ERROR, e) { "Error setting manga details from local metadata for ${manga.title}" }
|
|
|
|
|
}
|
2020-07-25 20:20:47 +02:00
|
|
|
|
2022-09-18 16:55:30 +02:00
|
|
|
return@withIOContext manga
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun copyComicInfoFileFromArchive(chapterArchives: List<File>, folderPath: String?): File? {
|
|
|
|
|
for (chapter in chapterArchives) {
|
2023-02-26 22:16:49 +01:00
|
|
|
when (Format.valueOf(chapter)) {
|
2022-09-18 16:55:30 +02:00
|
|
|
is Format.Zip -> {
|
|
|
|
|
ZipFile(chapter).use { zip: ZipFile ->
|
|
|
|
|
zip.getEntry(COMIC_INFO_FILE)?.let { comicInfoFile ->
|
|
|
|
|
zip.getInputStream(comicInfoFile).buffered().use { stream ->
|
|
|
|
|
return copyComicInfoFile(stream, folderPath)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
is Format.Rar -> {
|
2023-02-26 22:16:49 +01:00
|
|
|
JunrarArchive(chapter).use { rar ->
|
2022-09-18 16:55:30 +02:00
|
|
|
rar.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile ->
|
|
|
|
|
rar.getInputStream(comicInfoFile).buffered().use { stream ->
|
|
|
|
|
return copyComicInfoFile(stream, folderPath)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else -> {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun copyComicInfoFile(comicInfoFileStream: InputStream, folderPath: String?): File {
|
|
|
|
|
return File("$folderPath/$COMIC_INFO_FILE").apply {
|
|
|
|
|
outputStream().use { outputStream ->
|
|
|
|
|
comicInfoFileStream.use { it.copyTo(outputStream) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun setMangaDetailsFromComicInfoFile(stream: InputStream, manga: SManga) {
|
|
|
|
|
val comicInfo = AndroidXmlReader(stream, StandardCharsets.UTF_8.name()).use {
|
|
|
|
|
xml.decodeFromReader<ComicInfo>(it)
|
|
|
|
|
}
|
2022-11-16 19:52:17 +01:00
|
|
|
|
2022-11-13 18:01:19 +01:00
|
|
|
manga.copyFromComicInfo(comicInfo)
|
2020-01-11 21:59:43 +01:00
|
|
|
}
|
2017-02-05 12:01:58 +01:00
|
|
|
|
2022-08-18 20:07:13 +02:00
|
|
|
// Chapters
|
|
|
|
|
override suspend fun getChapterList(manga: SManga): List<SChapter> {
|
2023-02-26 22:16:49 +01:00
|
|
|
return fileSystem.getFilesInMangaDirectory(manga.url)
|
2022-05-25 00:02:02 +02:00
|
|
|
// Only keep supported formats
|
2023-02-26 22:16:49 +01:00
|
|
|
.filter { it.isDirectory || Archive.isSupported(it) }
|
2020-04-25 20:24:45 +02:00
|
|
|
.map { chapterFile ->
|
|
|
|
|
SChapter.create().apply {
|
2022-08-18 20:07:13 +02:00
|
|
|
url = "${manga.url}/${chapterFile.name}"
|
2020-04-28 04:49:06 +02:00
|
|
|
name = if (chapterFile.isDirectory) {
|
2020-04-25 20:24:45 +02:00
|
|
|
chapterFile.name
|
|
|
|
|
} else {
|
|
|
|
|
chapterFile.nameWithoutExtension
|
2017-02-05 12:01:58 +01:00
|
|
|
}
|
2020-04-25 20:24:45 +02:00
|
|
|
date_upload = chapterFile.lastModified()
|
2022-08-18 20:07:13 +02:00
|
|
|
chapter_number = ChapterRecognition.parseChapterNumber(manga.title, this.name, this.chapter_number)
|
2022-05-25 00:02:02 +02:00
|
|
|
|
2023-02-26 22:16:49 +01:00
|
|
|
val format = Format.valueOf(chapterFile)
|
2020-04-28 04:49:06 +02:00
|
|
|
if (format is Format.Epub) {
|
|
|
|
|
EpubFile(format.file).use { epub ->
|
|
|
|
|
epub.fillChapterMetadata(this)
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-05 12:01:58 +01:00
|
|
|
}
|
2020-04-25 20:24:45 +02:00
|
|
|
}
|
2021-05-24 22:50:07 +02:00
|
|
|
.sortedWith { c1, c2 ->
|
2022-08-18 20:07:13 +02:00
|
|
|
val c = c2.chapter_number.compareTo(c1.chapter_number)
|
2021-05-24 22:50:07 +02:00
|
|
|
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
|
|
|
|
|
}
|
2020-04-25 20:24:45 +02:00
|
|
|
.toList()
|
2017-02-05 12:01:58 +01:00
|
|
|
}
|
|
|
|
|
|
2022-05-25 00:02:02 +02:00
|
|
|
// Filters
|
2023-02-26 22:16:49 +01:00
|
|
|
override fun getFilterList() = FilterList(OrderBy.Popular(context))
|
2022-05-25 00:02:02 +02:00
|
|
|
|
|
|
|
|
// Unused stuff
|
2022-08-18 20:07:13 +02:00
|
|
|
override suspend fun getPageList(chapter: SChapter) = throw UnsupportedOperationException("Unused")
|
2021-11-19 22:42:19 +01:00
|
|
|
|
2018-09-01 17:12:59 +02:00
|
|
|
fun getFormat(chapter: SChapter): Format {
|
2023-02-26 22:16:49 +01:00
|
|
|
try {
|
|
|
|
|
return fileSystem.getBaseDirectories()
|
|
|
|
|
.map { directory -> File(directory, chapter.url) }
|
|
|
|
|
.find { chapterFile -> chapterFile.exists() }
|
|
|
|
|
?.let(Format.Companion::valueOf)
|
|
|
|
|
?: throw Exception(context.getString(R.string.chapter_not_found))
|
|
|
|
|
} catch (e: Format.UnknownFormatException) {
|
|
|
|
|
throw Exception(context.getString(R.string.local_invalid_format))
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
throw e
|
2017-02-05 12:01:58 +01:00
|
|
|
}
|
2017-01-29 20:48:55 +01:00
|
|
|
}
|
|
|
|
|
|
2018-09-01 17:12:59 +02:00
|
|
|
private fun updateCover(chapter: SChapter, manga: SManga): File? {
|
2022-07-23 17:14:34 +02:00
|
|
|
return try {
|
|
|
|
|
when (val format = getFormat(chapter)) {
|
|
|
|
|
is Format.Directory -> {
|
|
|
|
|
val entry = format.file.listFiles()
|
|
|
|
|
?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
|
|
|
|
|
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
|
|
|
|
|
|
2023-02-26 22:16:49 +01:00
|
|
|
entry?.let { coverManager.update(manga, it.inputStream()) }
|
2022-07-23 17:14:34 +02:00
|
|
|
}
|
|
|
|
|
is Format.Zip -> {
|
|
|
|
|
ZipFile(format.file).use { zip ->
|
|
|
|
|
val entry = zip.entries().toList()
|
|
|
|
|
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
|
|
|
|
|
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
|
2017-02-05 12:01:58 +01:00
|
|
|
|
2023-02-26 22:16:49 +01:00
|
|
|
entry?.let { coverManager.update(manga, zip.getInputStream(it)) }
|
2022-07-23 17:14:34 +02:00
|
|
|
}
|
2018-09-01 17:12:59 +02:00
|
|
|
}
|
2022-07-23 17:14:34 +02:00
|
|
|
is Format.Rar -> {
|
2023-02-26 22:16:49 +01:00
|
|
|
JunrarArchive(format.file).use { archive ->
|
2022-07-23 17:14:34 +02:00
|
|
|
val entry = archive.fileHeaders
|
|
|
|
|
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
|
|
|
|
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } }
|
2017-02-08 22:12:00 +01:00
|
|
|
|
2023-02-26 22:16:49 +01:00
|
|
|
entry?.let { coverManager.update(manga, archive.getInputStream(it)) }
|
2022-07-23 17:14:34 +02:00
|
|
|
}
|
2018-09-01 17:12:59 +02:00
|
|
|
}
|
2022-07-23 17:14:34 +02:00
|
|
|
is Format.Epub -> {
|
|
|
|
|
EpubFile(format.file).use { epub ->
|
|
|
|
|
val entry = epub.getImagesFromPages()
|
|
|
|
|
.firstOrNull()
|
|
|
|
|
?.let { epub.getEntry(it) }
|
2017-02-05 12:01:58 +01:00
|
|
|
|
2023-02-26 22:16:49 +01:00
|
|
|
entry?.let { coverManager.update(manga, epub.getInputStream(it)) }
|
2022-07-23 17:14:34 +02:00
|
|
|
}
|
2017-02-05 12:01:58 +01:00
|
|
|
}
|
|
|
|
|
}
|
2022-07-23 17:14:34 +02:00
|
|
|
} catch (e: Throwable) {
|
|
|
|
|
logcat(LogPriority.ERROR, e) { "Error updating cover for ${manga.title}" }
|
|
|
|
|
null
|
2017-02-05 12:01:58 +01:00
|
|
|
}
|
2018-09-01 17:12:59 +02:00
|
|
|
}
|
2017-02-05 12:01:58 +01:00
|
|
|
|
2022-05-25 00:02:02 +02:00
|
|
|
companion object {
|
|
|
|
|
const val ID = 0L
|
|
|
|
|
const val HELP_URL = "https://tachiyomi.org/help/guides/local-manga/"
|
|
|
|
|
|
2023-03-20 13:41:44 +01:00
|
|
|
private val LATEST_THRESHOLD = 7.days.inWholeMilliseconds
|
2022-05-25 00:02:02 +02:00
|
|
|
}
|
2018-09-01 17:12:59 +02:00
|
|
|
}
|
2023-03-20 03:38:14 +01:00
|
|
|
|
|
|
|
|
fun Manga.isLocal(): Boolean = source == LocalSource.ID
|
|
|
|
|
|
|
|
|
|
fun Source.isLocal(): Boolean = id == LocalSource.ID
|
2023-03-20 13:41:44 +01:00
|
|
|
|
|
|
|
|
fun DomainSource.isLocal(): Boolean = id == LocalSource.ID
|