mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Load ZIP file contents to cache (#9381)
* Extract downloaded archives to tmp folder when loading for viewing * Generate sequence of entries from ZipInputStream instead of loading entire ZipFile
This commit is contained in:
		| @@ -78,7 +78,6 @@ class ChapterLoader( | |||||||
|         val isDownloaded = downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source, skipCache = true) |         val isDownloaded = downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source, skipCache = true) | ||||||
|         return when { |         return when { | ||||||
|             isDownloaded -> DownloadPageLoader(chapter, manga, source, downloadManager, downloadProvider) |             isDownloaded -> DownloadPageLoader(chapter, manga, source, downloadManager, downloadProvider) | ||||||
|             source is HttpSource -> HttpPageLoader(chapter, source) |  | ||||||
|             source is LocalSource -> source.getFormat(chapter.chapter).let { format -> |             source is LocalSource -> source.getFormat(chapter.chapter).let { format -> | ||||||
|                 when (format) { |                 when (format) { | ||||||
|                     is Format.Directory -> DirectoryPageLoader(format.file) |                     is Format.Directory -> DirectoryPageLoader(format.file) | ||||||
| @@ -91,6 +90,7 @@ class ChapterLoader( | |||||||
|                     is Format.Epub -> EpubPageLoader(format.file) |                     is Format.Epub -> EpubPageLoader(format.file) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             source is HttpSource -> HttpPageLoader(chapter, source) | ||||||
|             source is StubSource -> error(context.getString(R.string.source_not_installed, source.toString())) |             source is StubSource -> error(context.getString(R.string.source_not_installed, source.toString())) | ||||||
|             else -> error(context.getString(R.string.loader_not_implemented_error)) |             else -> error(context.getString(R.string.loader_not_implemented_error)) | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,61 +1,57 @@ | |||||||
| package eu.kanade.tachiyomi.ui.reader.loader | package eu.kanade.tachiyomi.ui.reader.loader | ||||||
|  |  | ||||||
|  | import android.app.Application | ||||||
| import com.github.junrar.Archive | import com.github.junrar.Archive | ||||||
| import com.github.junrar.rarfile.FileHeader | import com.github.junrar.rarfile.FileHeader | ||||||
| import eu.kanade.tachiyomi.source.model.Page |  | ||||||
| import eu.kanade.tachiyomi.ui.reader.model.ReaderPage | import eu.kanade.tachiyomi.ui.reader.model.ReaderPage | ||||||
| import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder | import uy.kohesive.injekt.injectLazy | ||||||
| import tachiyomi.core.util.system.ImageUtil |  | ||||||
| import java.io.File | import java.io.File | ||||||
| import java.io.InputStream | import java.io.InputStream | ||||||
| import java.io.PipedInputStream | import java.io.PipedInputStream | ||||||
| import java.io.PipedOutputStream | import java.io.PipedOutputStream | ||||||
| import java.util.concurrent.Executors |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Loader used to load a chapter from a .rar or .cbr file. |  * Loader used to load a chapter from a .rar or .cbr file. | ||||||
|  */ |  */ | ||||||
| internal class RarPageLoader(file: File) : PageLoader() { | internal class RarPageLoader(file: File) : PageLoader() { | ||||||
|  |  | ||||||
|     private val rar = Archive(file) |     private val context: Application by injectLazy() | ||||||
|  |     private val tmpDir = File(context.externalCacheDir, "reader_${file.hashCode()}").also { | ||||||
|  |         it.deleteRecursively() | ||||||
|  |         it.mkdirs() | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     init { | ||||||
|      * Pool for copying compressed files to an input stream. |         Archive(file).use { rar -> | ||||||
|      */ |             rar.fileHeaders.asSequence() | ||||||
|     private val pool = Executors.newFixedThreadPool(1) |                 .filterNot { it.isDirectory } | ||||||
|  |                 .forEach { header -> | ||||||
|  |                     val pageFile = File(tmpDir, header.fileName).also { it.createNewFile() } | ||||||
|  |                     getStream(rar, header).use { | ||||||
|  |                         it.copyTo(pageFile.outputStream()) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override var isLocal: Boolean = true |     override var isLocal: Boolean = true | ||||||
|  |  | ||||||
|     override suspend fun getPages(): List<ReaderPage> { |     override suspend fun getPages(): List<ReaderPage> { | ||||||
|         return rar.fileHeaders.asSequence() |         return DirectoryPageLoader(tmpDir).getPages() | ||||||
|             .filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { rar.getInputStream(it) } } |  | ||||||
|             .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } |  | ||||||
|             .mapIndexed { i, header -> |  | ||||||
|                 ReaderPage(i).apply { |  | ||||||
|                     stream = { getStream(header) } |  | ||||||
|                     status = Page.State.READY |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             .toList() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override suspend fun loadPage(page: ReaderPage) { |  | ||||||
|         check(!isRecycled) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun recycle() { |     override fun recycle() { | ||||||
|         super.recycle() |         super.recycle() | ||||||
|         rar.close() |         tmpDir.deleteRecursively() | ||||||
|         pool.shutdown() |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Returns an input stream for the given [header]. |      * Returns an input stream for the given [header]. | ||||||
|      */ |      */ | ||||||
|     private fun getStream(header: FileHeader): InputStream { |     private fun getStream(rar: Archive, header: FileHeader): InputStream { | ||||||
|         val pipeIn = PipedInputStream() |         val pipeIn = PipedInputStream() | ||||||
|         val pipeOut = PipedOutputStream(pipeIn) |         val pipeOut = PipedOutputStream(pipeIn) | ||||||
|         pool.execute { |         synchronized(this) { | ||||||
|             try { |             try { | ||||||
|                 pipeOut.use { |                 pipeOut.use { | ||||||
|                     rar.extractFile(header, it) |                     rar.extractFile(header, it) | ||||||
|   | |||||||
| @@ -1,46 +1,58 @@ | |||||||
| package eu.kanade.tachiyomi.ui.reader.loader | package eu.kanade.tachiyomi.ui.reader.loader | ||||||
|  |  | ||||||
|  | import android.app.Application | ||||||
| import android.os.Build | import android.os.Build | ||||||
| import eu.kanade.tachiyomi.source.model.Page |  | ||||||
| import eu.kanade.tachiyomi.ui.reader.model.ReaderPage | import eu.kanade.tachiyomi.ui.reader.model.ReaderPage | ||||||
| import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder | import uy.kohesive.injekt.injectLazy | ||||||
| import tachiyomi.core.util.system.ImageUtil |  | ||||||
| import java.io.File | import java.io.File | ||||||
| import java.nio.charset.StandardCharsets | import java.io.FileInputStream | ||||||
| import java.util.zip.ZipFile | import java.util.zip.ZipInputStream | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Loader used to load a chapter from a .zip or .cbz file. |  * Loader used to load a chapter from a .zip or .cbz file. | ||||||
|  */ |  */ | ||||||
| internal class ZipPageLoader(file: File) : PageLoader() { | internal class ZipPageLoader(file: File) : PageLoader() { | ||||||
|  |  | ||||||
|     private val zip = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |     private val context: Application by injectLazy() | ||||||
|         ZipFile(file, StandardCharsets.ISO_8859_1) |     private val tmpDir = File(context.externalCacheDir, "reader_${file.hashCode()}").also { | ||||||
|     } else { |         it.deleteRecursively() | ||||||
|         ZipFile(file) |         it.mkdirs() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         ZipInputStream(FileInputStream(file)).use { zipInputStream -> | ||||||
|  |             generateSequence { zipInputStream.nextEntry } | ||||||
|  |                 .filterNot { it.isDirectory } | ||||||
|  |                 .forEach { entry -> | ||||||
|  |                     File(tmpDir, entry.name).also { it.createNewFile() } | ||||||
|  |                         .outputStream().use { pageOutputStream -> | ||||||
|  |                             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { | ||||||
|  |                                 pageOutputStream.write(zipInputStream.readNBytes(entry.size.toInt())) | ||||||
|  |                             } else { | ||||||
|  |                                 val buffer = ByteArray(2048) | ||||||
|  |                                 var len: Int | ||||||
|  |                                 while ( | ||||||
|  |                                     zipInputStream.read(buffer, 0, buffer.size) | ||||||
|  |                                         .also { len = it } >= 0 | ||||||
|  |                                 ) { | ||||||
|  |                                     pageOutputStream.write(buffer, 0, len) | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                             pageOutputStream.flush() | ||||||
|  |                         } | ||||||
|  |                     zipInputStream.closeEntry() | ||||||
|  |                 } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override var isLocal: Boolean = true |     override var isLocal: Boolean = true | ||||||
|  |  | ||||||
|     override suspend fun getPages(): List<ReaderPage> { |     override suspend fun getPages(): List<ReaderPage> { | ||||||
|         return zip.entries().asSequence() |         return DirectoryPageLoader(tmpDir).getPages() | ||||||
|             .filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } |  | ||||||
|             .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } |  | ||||||
|             .mapIndexed { i, entry -> |  | ||||||
|                 ReaderPage(i).apply { |  | ||||||
|                     stream = { zip.getInputStream(entry) } |  | ||||||
|                     status = Page.State.READY |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             .toList() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override suspend fun loadPage(page: ReaderPage) { |  | ||||||
|         check(!isRecycled) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun recycle() { |     override fun recycle() { | ||||||
|         super.recycle() |         super.recycle() | ||||||
|         zip.close() |         tmpDir.deleteRecursively() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user