From 82bdf634194734851c429d60b68f9ce7c7e51d91 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 26 Nov 2023 22:46:55 -0500 Subject: [PATCH] Differ extra attempts to load local series' covers until chapter loading --- .../tachiyomi/source/local/LocalSource.kt | 101 ++++++++---------- .../source/local/image/LocalCoverManager.kt | 4 +- .../source/local/io/LocalSourceFileSystem.kt | 15 ++- .../source/local/metadata/EpubFile.kt | 26 ++--- 4 files changed, 61 insertions(+), 85 deletions(-) diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt index f971caa1d0..3282137eaa 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt @@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.storage.EpubFile +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import logcat.LogPriority @@ -36,8 +38,7 @@ 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 +import tachiyomi.source.local.metadata.fillMetadata import uy.kohesive.injekt.injectLazy import java.io.File import java.io.InputStream @@ -74,21 +75,21 @@ actual class LocalSource( override suspend fun getLatestUpdates(page: Int) = getSearchManga(page, "", LATEST_FILTERS) - override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage { - val baseDirFiles = fileSystem.getFilesInBaseDirectory() - val lastModifiedLimit by lazy { - if (filters === LATEST_FILTERS) { - System.currentTimeMillis() - LATEST_THRESHOLD - } else { - 0L - } + override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage = withIOContext { + val lastModifiedLimit = if (filters === LATEST_FILTERS) { + System.currentTimeMillis() - LATEST_THRESHOLD + } else { + 0L } - var mangaDirs = baseDirFiles + + var mangaDirs = fileSystem.getFilesInBaseDirectory() // Filter out files that are hidden and is not a folder .filter { it.isDirectory && !it.name.orEmpty().startsWith('.') } .distinctBy { it.name } - .filter { // Filter by query or last modified - if (lastModifiedLimit == 0L) { + .filter { + if (lastModifiedLimit == 0L && query.isBlank()) { + true + } else if (lastModifiedLimit == 0L) { it.name.orEmpty().contains(query, ignoreCase = true) } else { it.lastModified() >= lastModifiedLimit @@ -111,59 +112,41 @@ actual class LocalSource( mangaDirs.sortedByDescending(UniFile::lastModified) } } - else -> { /* Do nothing */ } } } - // Transform mangaDirs to list of SManga - val mangas = mangaDirs.map { mangaDir -> - SManga.create().apply { - title = mangaDir.name.orEmpty() - url = mangaDir.name.orEmpty() + val mangas = mangaDirs + .map { mangaDir -> + async { + SManga.create().apply { + title = mangaDir.name.orEmpty() + url = mangaDir.name.orEmpty() - // Try to find the cover - coverManager.find(mangaDir.name.orEmpty()) - ?.takeIf(UniFile::exists) - ?.let { thumbnail_url = it.uri.toString() } - } - } - - // Fetch chapters of all the manga - mangas.forEach { manga -> - val chapters = getChapterList(manga) - if (chapters.isNotEmpty()) { - val chapter = chapters.last() - val format = getFormat(chapter) - - if (format is Format.Epub) { - EpubFile(format.file).use { epub -> - epub.fillMangaMetadata(manga) + // Try to find the cover + coverManager.find(mangaDir.name.orEmpty())?.let { + thumbnail_url = it.filePath + } } } - - // Copy the cover from the first chapter found if not available - if (manga.thumbnail_url == null) { - updateCover(chapter, manga) - } } - } + .awaitAll() - return MangasPage(mangas.toList(), false) + MangasPage(mangas, false) } // Manga details related override suspend fun getMangaDetails(manga: SManga): SManga = withIOContext { coverManager.find(manga.url)?.let { - manga.thumbnail_url = it.uri.toString() + manga.thumbnail_url = it.filePath } // Augment manga details based on metadata files try { - val mangaDir = fileSystem.getMangaDirectory(manga.url) - val mangaDirFiles = fileSystem.getFilesInMangaDirectory(manga.url).toList() + val mangaDir by lazy { fileSystem.getMangaDirectory(manga.url) } + val mangaDirFiles = fileSystem.getFilesInMangaDirectory(manga.url) val comicInfoFile = mangaDirFiles .firstOrNull { it.name == COMIC_INFO_FILE } @@ -215,7 +198,7 @@ actual class LocalSource( setMangaDetailsFromComicInfoFile(copiedFile.inputStream(), manga) } else { // Avoid re-scanning - File("$folderPath/.noxml").createNewFile() + mangaDir?.createFile(".noxml") } } } @@ -270,18 +253,18 @@ actual class LocalSource( } // Chapters - override suspend fun getChapterList(manga: SManga): List { - return fileSystem.getFilesInMangaDirectory(manga.url) + override suspend fun getChapterList(manga: SManga): List = withIOContext { + val chapters = fileSystem.getFilesInMangaDirectory(manga.url) // Only keep supported formats .filter { it.isDirectory || Archive.isSupported(it) } .map { chapterFile -> SChapter.create().apply { url = "${manga.url}/${chapterFile.name}" name = if (chapterFile.isDirectory) { - chapterFile.name.orEmpty() + chapterFile.name } else { - chapterFile.nameWithoutExtension.orEmpty() - } + chapterFile.nameWithoutExtension + }.orEmpty() date_upload = chapterFile.lastModified() chapter_number = ChapterRecognition .parseChapterNumber(manga.title, this.name, this.chapter_number.toDouble()) @@ -290,7 +273,7 @@ actual class LocalSource( val format = Format.valueOf(chapterFile) if (format is Format.Epub) { EpubFile(format.file).use { epub -> - epub.fillChapterMetadata(this) + epub.fillMetadata(manga, this) } } } @@ -299,7 +282,15 @@ actual class LocalSource( val c = c2.chapter_number.compareTo(c1.chapter_number) if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c } - .toList() + + // Copy the cover from the first chapter found if not available + if (manga.thumbnail_url.isNullOrBlank()) { + chapters.lastOrNull()?.let { chapter -> + updateCover(chapter, manga) + } + } + + chapters } // Filters @@ -310,7 +301,7 @@ actual class LocalSource( fun getFormat(chapter: SChapter): Format { try { - val (mangaDirName, chapterName) = chapter.url.split(File.separator, limit = 2) + val (mangaDirName, chapterName) = chapter.url.split('/', limit = 2) return fileSystem.getBaseDirectory() ?.findFile(mangaDirName) ?.findFile(chapterName) diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt index b19968fb22..d793357f51 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt @@ -21,9 +21,7 @@ actual class LocalCoverManager( // Get all file whose names start with "cover" .filter { it.isFile && it.nameWithoutExtension.equals("cover", ignoreCase = true) } // Get the first actual image - .firstOrNull { - ImageUtil.isImage(it.name) { it.openInputStream() } - } + .firstOrNull { ImageUtil.isImage(it.name) { it.openInputStream() } } } actual fun update( diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt index 8642ac21b9..ad95b39ce7 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt @@ -16,16 +16,15 @@ actual class LocalSourceFileSystem( } actual fun getMangaDirectory(name: String): UniFile? { - return getFilesInBaseDirectory() - // Get the first mangaDir or null - .firstOrNull { it.isDirectory && it.name == name } + return getBaseDirectory() + ?.findFile(name, true) + ?.takeIf { it.isDirectory } } actual fun getFilesInMangaDirectory(name: String): List { - return getFilesInBaseDirectory() - // Filter out ones that are not related to the manga and is not a directory - .filter { it.isDirectory && it.name == name } - // Get all the files inside the filtered folders - .flatMap { it.listFiles().orEmpty().toList() } + return getBaseDirectory() + ?.findFile(name, true) + ?.takeIf { it.isDirectory } + ?.listFiles().orEmpty().toList() } } diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/metadata/EpubFile.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/metadata/EpubFile.kt index d9ce323d5e..6bade530b2 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/metadata/EpubFile.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/metadata/EpubFile.kt @@ -8,37 +8,25 @@ import java.text.SimpleDateFormat import java.util.Locale /** - * Fills manga metadata using this epub file's metadata. + * Fills manga and chapter metadata using this epub file's metadata. */ -fun EpubFile.fillMangaMetadata(manga: SManga) { - val ref = getPackageHref() - val doc = getPackageDocument(ref) - - val creator = doc.getElementsByTag("dc:creator").first() - val description = doc.getElementsByTag("dc:description").first() - - manga.author = creator?.text() - manga.description = description?.text() -} - -/** - * Fills chapter metadata using this epub file's metadata. - */ -fun EpubFile.fillChapterMetadata(chapter: SChapter) { +fun EpubFile.fillMetadata(manga: SManga, chapter: SChapter) { val ref = getPackageHref() val doc = getPackageDocument(ref) val title = doc.getElementsByTag("dc:title").first() val publisher = doc.getElementsByTag("dc:publisher").first() val creator = doc.getElementsByTag("dc:creator").first() + val description = doc.getElementsByTag("dc:description").first() var date = doc.getElementsByTag("dc:date").first() if (date == null) { date = doc.select("meta[property=dcterms:modified]").first() } - if (title != null) { - chapter.name = title.text() - } + creator?.text()?.let { manga.author = it } + description?.text()?.let { manga.description = it } + + title?.text()?.let { chapter.name = it } if (publisher != null) { chapter.scanlator = publisher.text()