Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5de6a8f110 | |||
| d4789c2373 | |||
| 8dc5a0fc0b | |||
| 105551f658 | |||
| 6a57bc601f |
@@ -22,3 +22,6 @@
|
||||
*.woff binary
|
||||
*.pyc binary
|
||||
*.swp binary
|
||||
|
||||
# Android packages
|
||||
*.apk binary
|
||||
|
||||
@@ -33,8 +33,8 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "app.mihon"
|
||||
|
||||
versionCode = 30
|
||||
versionName = "0.20.4"
|
||||
versionCode = 31
|
||||
versionName = "0.20.5"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getLatestCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getLatestCommitSha()}\"")
|
||||
|
||||
@@ -117,6 +117,37 @@ class DownloadProvider(
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a downloaded EPUB/TXT document for a chapter if it exists.
|
||||
*
|
||||
* Document files are checked explicitly before the generic chapter path so
|
||||
* a stale image directory with the same chapter name cannot shadow them.
|
||||
*/
|
||||
fun findChapterDocument(
|
||||
chapterName: String,
|
||||
chapterScanlator: String?,
|
||||
chapterUrl: String,
|
||||
mangaTitle: String,
|
||||
source: Source,
|
||||
): UniFile? {
|
||||
val mangaDir = findMangaDir(mangaTitle, source) ?: return null
|
||||
val chapterDirName = getChapterDirName(chapterName, chapterScanlator, chapterUrl)
|
||||
val legacyChapterDirNames = getLegacyChapterDirNames(chapterName, chapterScanlator, chapterUrl)
|
||||
|
||||
return buildList {
|
||||
DOCUMENT_DOWNLOAD_EXTENSIONS.forEach { extension ->
|
||||
add("$chapterDirName.$extension")
|
||||
}
|
||||
legacyChapterDirNames.forEach { legacyName ->
|
||||
DOCUMENT_DOWNLOAD_EXTENSIONS.forEach { extension ->
|
||||
add("$legacyName.$extension")
|
||||
}
|
||||
}
|
||||
}.asSequence()
|
||||
.mapNotNull { mangaDir.findFile(it) }
|
||||
.firstOrNull { it.isFile }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of downloaded directories for the chapters that exist.
|
||||
*
|
||||
|
||||
@@ -130,6 +130,33 @@ class ChapterLoader(
|
||||
)
|
||||
val bookOasisRef = dbChapter.url.toBookOasisChapterRef()
|
||||
val kavitaChapterId = dbChapter.url.toKavitaChapterId()
|
||||
val httpSource = source as? HttpSource
|
||||
val documentFallback: (() -> PageLoader)? = when {
|
||||
httpSource != null && httpSource.isBookOasisSource() && bookOasisRef != null &&
|
||||
(bookOasisRef.format == "epub" || bookOasisRef.format == "txt") -> {
|
||||
{
|
||||
BookOasisFilePageLoader(
|
||||
context,
|
||||
httpSource,
|
||||
bookOasisRef,
|
||||
readerPreferences.textReaderStyle(context),
|
||||
)
|
||||
}
|
||||
}
|
||||
httpSource != null && httpSource.isKavitaSource() && kavitaChapterId != null -> {
|
||||
{
|
||||
KavitaCompatPageLoader(
|
||||
context = context,
|
||||
chapter = chapter,
|
||||
source = httpSource,
|
||||
chapterCache = chapterCache,
|
||||
chapterId = kavitaChapterId,
|
||||
textStyle = readerPreferences.textReaderStyle(context),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
return when {
|
||||
isDownloaded -> DownloadPageLoader(
|
||||
@@ -138,6 +165,7 @@ class ChapterLoader(
|
||||
source,
|
||||
downloadManager,
|
||||
downloadProvider,
|
||||
documentFallback,
|
||||
)
|
||||
source is LocalSource -> source.getFormat(chapter.chapter).let { format ->
|
||||
when (format) {
|
||||
|
||||
@@ -10,12 +10,14 @@ import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import mihon.core.archive.archiveReader
|
||||
import mihon.core.archive.epubReader
|
||||
import tachiyomi.core.common.storage.extension
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Loader used to load a chapter from the downloaded chapters.
|
||||
@@ -26,6 +28,7 @@ internal class DownloadPageLoader(
|
||||
private val source: Source,
|
||||
private val downloadManager: DownloadManager,
|
||||
private val downloadProvider: DownloadProvider,
|
||||
private val documentFallback: (() -> PageLoader)? = null,
|
||||
) : PageLoader() {
|
||||
|
||||
private val context: Context by injectLazy()
|
||||
@@ -35,9 +38,35 @@ internal class DownloadPageLoader(
|
||||
|
||||
override var isLocal: Boolean = true
|
||||
override val usesTextReaderStyle: Boolean
|
||||
get() = pageLoader?.usesTextReaderStyle == true
|
||||
get() = pageLoader?.usesTextReaderStyle == true || findDownloadedDocument() != null
|
||||
|
||||
override suspend fun getPages(): List<ReaderPage> {
|
||||
check(!isRecycled)
|
||||
|
||||
return try {
|
||||
val pages = getDownloadedPages()
|
||||
if (pages.isNotEmpty() || documentFallback == null) {
|
||||
pages
|
||||
} else {
|
||||
getFallbackPages()
|
||||
}
|
||||
} catch (error: Throwable) {
|
||||
if (error is CancellationException || documentFallback == null) throw error
|
||||
getFallbackPages()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getDownloadedPages(): List<ReaderPage> {
|
||||
val downloadedDocument = findDownloadedDocument()
|
||||
if (downloadedDocument != null) {
|
||||
val readableDocument = prepareDownloadedDocument(downloadedDocument)
|
||||
return when (readableDocument.extension?.lowercase()) {
|
||||
"epub" -> getPagesFromEpub(readableDocument)
|
||||
"txt" -> getPagesFromText(readableDocument)
|
||||
else -> error("Unsupported downloaded document: ${downloadedDocument.name}")
|
||||
}
|
||||
}
|
||||
|
||||
val dbChapter = chapter.chapter
|
||||
val chapterPath = downloadProvider.findChapterDir(
|
||||
dbChapter.name,
|
||||
@@ -46,17 +75,79 @@ internal class DownloadPageLoader(
|
||||
manga.title,
|
||||
source,
|
||||
)
|
||||
|
||||
return if (chapterPath?.isFile == true) {
|
||||
when (chapterPath.extension?.lowercase()) {
|
||||
"epub" -> getPagesFromEpub(chapterPath)
|
||||
"txt" -> getPagesFromText(chapterPath)
|
||||
else -> getPagesFromArchive(chapterPath)
|
||||
}
|
||||
getPagesFromArchive(chapterPath)
|
||||
} else {
|
||||
getPagesFromDirectory()
|
||||
}
|
||||
}
|
||||
|
||||
private fun findDownloadedDocument(): UniFile? {
|
||||
val dbChapter = chapter.chapter
|
||||
return downloadProvider.findChapterDocument(
|
||||
dbChapter.name,
|
||||
dbChapter.scanlator,
|
||||
dbChapter.url,
|
||||
manga.title,
|
||||
source,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror SAF-backed documents into the app cache before opening them.
|
||||
* EPUB uses mmap internally, which is not guaranteed to work with every
|
||||
* document provider even though regular stream reads/writes succeed.
|
||||
*/
|
||||
private fun prepareDownloadedDocument(file: UniFile): UniFile {
|
||||
val extension = file.extension?.lowercase()
|
||||
?: error("Downloaded document has no extension: ${file.name}")
|
||||
val sourceLength = file.length()
|
||||
if (sourceLength <= 0L) {
|
||||
error("Downloaded document is empty: ${file.name}")
|
||||
}
|
||||
|
||||
val cacheDir = File(context.cacheDir, "downloaded_reader").apply { mkdirs() }
|
||||
val prefix = "${source.id}-${chapter.chapter.id}-"
|
||||
val target = File(
|
||||
cacheDir,
|
||||
"$prefix$sourceLength-${file.lastModified()}.$extension",
|
||||
)
|
||||
|
||||
if (!target.isFile || target.length() != sourceLength) {
|
||||
cacheDir.listFiles()
|
||||
?.filter { it.name.startsWith(prefix) }
|
||||
?.forEach { it.delete() }
|
||||
|
||||
val temp = File(cacheDir, "${target.name}.part")
|
||||
if (temp.exists()) temp.delete()
|
||||
file.openInputStream().use { input ->
|
||||
temp.outputStream().use { output -> input.copyTo(output) }
|
||||
}
|
||||
if (temp.length() != sourceLength) {
|
||||
temp.delete()
|
||||
error("Downloaded document cache copy is incomplete: ${file.name}")
|
||||
}
|
||||
if (!temp.renameTo(target)) {
|
||||
temp.copyTo(target, overwrite = true)
|
||||
temp.delete()
|
||||
}
|
||||
}
|
||||
|
||||
return UniFile.fromFile(target)
|
||||
?: error("Unable to open cached downloaded document: ${target.absolutePath}")
|
||||
}
|
||||
|
||||
private suspend fun getFallbackPages(): List<ReaderPage> {
|
||||
pageLoader?.recycle()
|
||||
val fallback = documentFallback?.invoke()
|
||||
?: error("Downloaded document fallback is unavailable")
|
||||
pageLoader = fallback
|
||||
val pages = fallback.getPages()
|
||||
isLocal = fallback.isLocal
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
super.recycle()
|
||||
pageLoader?.recycle()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user