|
|
@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|
|
|
import eu.kanade.tachiyomi.source.model.SManga
|
|
|
|
import eu.kanade.tachiyomi.source.model.SManga
|
|
|
|
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
|
|
|
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
|
|
|
import eu.kanade.tachiyomi.util.storage.EpubFile
|
|
|
|
import eu.kanade.tachiyomi.util.storage.EpubFile
|
|
|
|
|
|
|
|
import kotlinx.coroutines.async
|
|
|
|
|
|
|
|
import kotlinx.coroutines.awaitAll
|
|
|
|
import kotlinx.serialization.json.Json
|
|
|
|
import kotlinx.serialization.json.Json
|
|
|
|
import kotlinx.serialization.json.decodeFromStream
|
|
|
|
import kotlinx.serialization.json.decodeFromStream
|
|
|
|
import logcat.LogPriority
|
|
|
|
import logcat.LogPriority
|
|
|
@ -36,8 +38,7 @@ import tachiyomi.source.local.image.LocalCoverManager
|
|
|
|
import tachiyomi.source.local.io.Archive
|
|
|
|
import tachiyomi.source.local.io.Archive
|
|
|
|
import tachiyomi.source.local.io.Format
|
|
|
|
import tachiyomi.source.local.io.Format
|
|
|
|
import tachiyomi.source.local.io.LocalSourceFileSystem
|
|
|
|
import tachiyomi.source.local.io.LocalSourceFileSystem
|
|
|
|
import tachiyomi.source.local.metadata.fillChapterMetadata
|
|
|
|
import tachiyomi.source.local.metadata.fillMetadata
|
|
|
|
import tachiyomi.source.local.metadata.fillMangaMetadata
|
|
|
|
|
|
|
|
import uy.kohesive.injekt.injectLazy
|
|
|
|
import uy.kohesive.injekt.injectLazy
|
|
|
|
import java.io.File
|
|
|
|
import java.io.File
|
|
|
|
import java.io.InputStream
|
|
|
|
import java.io.InputStream
|
|
|
@ -74,21 +75,21 @@ actual class LocalSource(
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun getLatestUpdates(page: Int) = getSearchManga(page, "", LATEST_FILTERS)
|
|
|
|
override suspend fun getLatestUpdates(page: Int) = getSearchManga(page, "", LATEST_FILTERS)
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
|
|
|
|
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage = withIOContext {
|
|
|
|
val baseDirFiles = fileSystem.getFilesInBaseDirectory()
|
|
|
|
val lastModifiedLimit = if (filters === LATEST_FILTERS) {
|
|
|
|
val lastModifiedLimit by lazy {
|
|
|
|
|
|
|
|
if (filters === LATEST_FILTERS) {
|
|
|
|
|
|
|
|
System.currentTimeMillis() - LATEST_THRESHOLD
|
|
|
|
System.currentTimeMillis() - LATEST_THRESHOLD
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
0L
|
|
|
|
0L
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var mangaDirs = baseDirFiles
|
|
|
|
var mangaDirs = fileSystem.getFilesInBaseDirectory()
|
|
|
|
// Filter out files that are hidden and is not a folder
|
|
|
|
// Filter out files that are hidden and is not a folder
|
|
|
|
.filter { it.isDirectory && !it.name.orEmpty().startsWith('.') }
|
|
|
|
.filter { it.isDirectory && !it.name.orEmpty().startsWith('.') }
|
|
|
|
.distinctBy { it.name }
|
|
|
|
.distinctBy { it.name }
|
|
|
|
.filter { // Filter by query or last modified
|
|
|
|
.filter {
|
|
|
|
if (lastModifiedLimit == 0L) {
|
|
|
|
if (lastModifiedLimit == 0L && query.isBlank()) {
|
|
|
|
|
|
|
|
true
|
|
|
|
|
|
|
|
} else if (lastModifiedLimit == 0L) {
|
|
|
|
it.name.orEmpty().contains(query, ignoreCase = true)
|
|
|
|
it.name.orEmpty().contains(query, ignoreCase = true)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
it.lastModified() >= lastModifiedLimit
|
|
|
|
it.lastModified() >= lastModifiedLimit
|
|
|
@ -111,59 +112,41 @@ actual class LocalSource(
|
|
|
|
mangaDirs.sortedByDescending(UniFile::lastModified)
|
|
|
|
mangaDirs.sortedByDescending(UniFile::lastModified)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
else -> {
|
|
|
|
/* Do nothing */
|
|
|
|
/* Do nothing */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Transform mangaDirs to list of SManga
|
|
|
|
val mangas = mangaDirs
|
|
|
|
val mangas = mangaDirs.map { mangaDir ->
|
|
|
|
.map { mangaDir ->
|
|
|
|
|
|
|
|
async {
|
|
|
|
SManga.create().apply {
|
|
|
|
SManga.create().apply {
|
|
|
|
title = mangaDir.name.orEmpty()
|
|
|
|
title = mangaDir.name.orEmpty()
|
|
|
|
url = mangaDir.name.orEmpty()
|
|
|
|
url = mangaDir.name.orEmpty()
|
|
|
|
|
|
|
|
|
|
|
|
// Try to find the cover
|
|
|
|
// Try to find the cover
|
|
|
|
coverManager.find(mangaDir.name.orEmpty())
|
|
|
|
coverManager.find(mangaDir.name.orEmpty())?.let {
|
|
|
|
?.takeIf(UniFile::exists)
|
|
|
|
thumbnail_url = it.filePath
|
|
|
|
?.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)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.awaitAll()
|
|
|
|
|
|
|
|
|
|
|
|
// Copy the cover from the first chapter found if not available
|
|
|
|
MangasPage(mangas, false)
|
|
|
|
if (manga.thumbnail_url == null) {
|
|
|
|
|
|
|
|
updateCover(chapter, manga)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return MangasPage(mangas.toList(), false)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Manga details related
|
|
|
|
// Manga details related
|
|
|
|
override suspend fun getMangaDetails(manga: SManga): SManga = withIOContext {
|
|
|
|
override suspend fun getMangaDetails(manga: SManga): SManga = withIOContext {
|
|
|
|
coverManager.find(manga.url)?.let {
|
|
|
|
coverManager.find(manga.url)?.let {
|
|
|
|
manga.thumbnail_url = it.uri.toString()
|
|
|
|
manga.thumbnail_url = it.filePath
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Augment manga details based on metadata files
|
|
|
|
// Augment manga details based on metadata files
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
val mangaDir = fileSystem.getMangaDirectory(manga.url)
|
|
|
|
val mangaDir by lazy { fileSystem.getMangaDirectory(manga.url) }
|
|
|
|
val mangaDirFiles = fileSystem.getFilesInMangaDirectory(manga.url).toList()
|
|
|
|
val mangaDirFiles = fileSystem.getFilesInMangaDirectory(manga.url)
|
|
|
|
|
|
|
|
|
|
|
|
val comicInfoFile = mangaDirFiles
|
|
|
|
val comicInfoFile = mangaDirFiles
|
|
|
|
.firstOrNull { it.name == COMIC_INFO_FILE }
|
|
|
|
.firstOrNull { it.name == COMIC_INFO_FILE }
|
|
|
@ -215,7 +198,7 @@ actual class LocalSource(
|
|
|
|
setMangaDetailsFromComicInfoFile(copiedFile.inputStream(), manga)
|
|
|
|
setMangaDetailsFromComicInfoFile(copiedFile.inputStream(), manga)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
// Avoid re-scanning
|
|
|
|
// Avoid re-scanning
|
|
|
|
File("$folderPath/.noxml").createNewFile()
|
|
|
|
mangaDir?.createFile(".noxml")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -270,18 +253,18 @@ actual class LocalSource(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Chapters
|
|
|
|
// Chapters
|
|
|
|
override suspend fun getChapterList(manga: SManga): List<SChapter> {
|
|
|
|
override suspend fun getChapterList(manga: SManga): List<SChapter> = withIOContext {
|
|
|
|
return fileSystem.getFilesInMangaDirectory(manga.url)
|
|
|
|
val chapters = fileSystem.getFilesInMangaDirectory(manga.url)
|
|
|
|
// Only keep supported formats
|
|
|
|
// Only keep supported formats
|
|
|
|
.filter { it.isDirectory || Archive.isSupported(it) }
|
|
|
|
.filter { it.isDirectory || Archive.isSupported(it) }
|
|
|
|
.map { chapterFile ->
|
|
|
|
.map { chapterFile ->
|
|
|
|
SChapter.create().apply {
|
|
|
|
SChapter.create().apply {
|
|
|
|
url = "${manga.url}/${chapterFile.name}"
|
|
|
|
url = "${manga.url}/${chapterFile.name}"
|
|
|
|
name = if (chapterFile.isDirectory) {
|
|
|
|
name = if (chapterFile.isDirectory) {
|
|
|
|
chapterFile.name.orEmpty()
|
|
|
|
chapterFile.name
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
chapterFile.nameWithoutExtension.orEmpty()
|
|
|
|
chapterFile.nameWithoutExtension
|
|
|
|
}
|
|
|
|
}.orEmpty()
|
|
|
|
date_upload = chapterFile.lastModified()
|
|
|
|
date_upload = chapterFile.lastModified()
|
|
|
|
chapter_number = ChapterRecognition
|
|
|
|
chapter_number = ChapterRecognition
|
|
|
|
.parseChapterNumber(manga.title, this.name, this.chapter_number.toDouble())
|
|
|
|
.parseChapterNumber(manga.title, this.name, this.chapter_number.toDouble())
|
|
|
@ -290,7 +273,7 @@ actual class LocalSource(
|
|
|
|
val format = Format.valueOf(chapterFile)
|
|
|
|
val format = Format.valueOf(chapterFile)
|
|
|
|
if (format is Format.Epub) {
|
|
|
|
if (format is Format.Epub) {
|
|
|
|
EpubFile(format.file).use { 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)
|
|
|
|
val c = c2.chapter_number.compareTo(c1.chapter_number)
|
|
|
|
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
|
|
|
|
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
|
|
|
|
// Filters
|
|
|
@ -310,7 +301,7 @@ actual class LocalSource(
|
|
|
|
|
|
|
|
|
|
|
|
fun getFormat(chapter: SChapter): Format {
|
|
|
|
fun getFormat(chapter: SChapter): Format {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
val (mangaDirName, chapterName) = chapter.url.split(File.separator, limit = 2)
|
|
|
|
val (mangaDirName, chapterName) = chapter.url.split('/', limit = 2)
|
|
|
|
return fileSystem.getBaseDirectory()
|
|
|
|
return fileSystem.getBaseDirectory()
|
|
|
|
?.findFile(mangaDirName)
|
|
|
|
?.findFile(mangaDirName)
|
|
|
|
?.findFile(chapterName)
|
|
|
|
?.findFile(chapterName)
|
|
|
|