From 4333999b85f49ea9ef25cc5c7c280559bccf3b7d Mon Sep 17 00:00:00 2001 From: Fahad1998 <46712928+Fahad1998@users.noreply.github.com> Date: Sun, 23 Aug 2020 03:37:03 +0600 Subject: [PATCH] Add Save As CBZ (#84) Co-authored-by: Fahad1998 --- .../tachiyomi/data/download/DownloadCache.kt | 2 +- .../data/download/DownloadProvider.kt | 6 +-- .../tachiyomi/data/download/Downloader.kt | 43 ++++++++++++++- .../data/preference/PreferenceKeys.kt | 4 ++ .../data/preference/PreferencesHelper.kt | 4 ++ .../ui/reader/loader/DownloadPageLoader.kt | 54 ++++++++++++++++--- .../ui/setting/SettingsDownloadController.kt | 18 +++++++ app/src/main/res/values/strings_sy.xml | 3 ++ 8 files changed, 122 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt index 8795626ae..ec6651dba 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt @@ -145,7 +145,7 @@ class DownloadCache( mangaDirs.values.forEach { mangaDir -> val chapterDirs = mangaDir.dir.listFiles() .orEmpty() - .mapNotNull { it.name } + .mapNotNull { it.name?.replace(".cbz", "") } .toHashSet() mangaDir.files = chapterDirs diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt index edc3f9d70..310b64db2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt @@ -89,7 +89,7 @@ class DownloadProvider(private val context: Context) { fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? { val mangaDir = findMangaDir(manga, source) return getValidChapterDirNames(chapter).asSequence() - .mapNotNull { mangaDir?.findFile(it) } + .mapNotNull { mangaDir?.findFile(it) ?: mangaDir?.findFile("$it.cbz") } .firstOrNull() } @@ -104,7 +104,7 @@ class DownloadProvider(private val context: Context) { val mangaDir = findMangaDir(manga, source) ?: return emptyList() return chapters.mapNotNull { chapter -> getValidChapterDirNames(chapter).asSequence() - .mapNotNull { mangaDir.findFile(it) } + .mapNotNull { mangaDir.findFile(it) ?: mangaDir.findFile("$it.cbz") } .firstOrNull() } } @@ -127,7 +127,7 @@ class DownloadProvider(private val context: Context) { ( chapters.find { chp -> getValidChapterDirNames(chp).any { dir -> - mangaDir.findFile(dir) != null + mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null } } == null ) || it.name?.endsWith("_tmp") == true diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 4382333d8..329a8c29c 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.DownloadQueue +import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource @@ -22,7 +23,11 @@ import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.system.ImageUtil +import java.io.BufferedOutputStream import java.io.File +import java.util.zip.CRC32 +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream import kotlinx.coroutines.async import okhttp3.Response import rx.Observable @@ -30,6 +35,8 @@ import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import rx.subscriptions.CompositeSubscription import timber.log.Timber +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy /** @@ -53,6 +60,8 @@ class Downloader( private val sourceManager: SourceManager ) { + private val preferences: PreferencesHelper = Injekt.get() + private val chapterCache: ChapterCache by injectLazy() /** @@ -464,7 +473,39 @@ class Downloader( // Only rename the directory if it's downloaded. if (download.status == Download.DOWNLOADED) { - tmpDir.renameTo(dirname) + mangaDir.findFile(dirname + ".tmp")?.delete() + if (preferences.saveChaptersAsCBZ().get()) { + val zip = mangaDir.createFile(dirname + ".tmp") + val zipOut = ZipOutputStream(BufferedOutputStream(zip.openOutputStream())) + + zipOut.setLevel(preferences.saveChaptersAsCBZLevel().get()) + + if (preferences.saveChaptersAsCBZLevel().get() == 0) { + zipOut.setMethod(ZipEntry.STORED) + } + + tmpDir.listFiles()?.forEach { img -> + val input = img.openInputStream() + val data = input.readBytes() + val entry = ZipEntry(img.name) + if (preferences.saveChaptersAsCBZLevel().get() == 0) { + val crc = CRC32() + val size = img.length() + crc.update(data) + entry.crc = crc.value + entry.compressedSize = size + entry.size = size + } + zipOut.putNextEntry(entry) + zipOut.write(data) + input.close() + } + zipOut.close() + zip.renameTo(dirname + ".cbz") + tmpDir.delete() + } else { + tmpDir.renameTo(dirname) + } cache.addChapter(dirname, mangaDir, download.manga) DiskUtil.createNoMediaFile(tmpDir, context) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index 063f9bcf0..63800c9fc 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -302,4 +302,8 @@ object PreferenceKeys { const val dataSaverServer = "data_saver_server" const val dataSaverColorBW = "data_saver_color_bw" + + const val saveChaptersAsCBZ = "save_chapter_as_cbz" + + const val saveChaptersAsCBZLevel = "save_chapter_as_cbz_level" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index ff06e2ac9..650af403c 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -407,4 +407,8 @@ class PreferencesHelper(val context: Context) { fun dataSaverServer() = flowPrefs.getString(Keys.dataSaverServer, "") fun dataSaverColorBW() = flowPrefs.getBoolean(Keys.dataSaverColorBW, false) + + fun saveChaptersAsCBZ() = flowPrefs.getBoolean(Keys.saveChaptersAsCBZ, false) + + fun saveChaptersAsCBZLevel() = flowPrefs.getInt(Keys.saveChaptersAsCBZLevel, 0) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt index 838242d22..5367f8318 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt @@ -4,10 +4,16 @@ import android.app.Application import android.net.Uri import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.DownloadManager +import eu.kanade.tachiyomi.data.download.DownloadProvider 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 eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder +import eu.kanade.tachiyomi.util.system.ImageUtil +import java.io.File +import java.util.zip.ZipEntry +import java.util.zip.ZipFile import rx.Observable import uy.kohesive.injekt.injectLazy @@ -26,20 +32,54 @@ class DownloadPageLoader( */ private val context by injectLazy() + private val downloadProvider by lazy { DownloadProvider(context) } + /** * Returns an observable containing the pages found on this downloaded chapter. */ override fun getPages(): Observable> { - return downloadManager.buildPageList(source, manga, chapter.chapter) - .map { pages -> - pages.map { page -> - ReaderPage(page.index, page.url, page.imageUrl) { - context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!! - }.apply { + val chapterPath = downloadProvider.findChapterDir(chapter.chapter, manga, source) + + if (chapterPath?.isFile!!) { + val zip = if (!File(chapterPath.filePath!!).canRead()) { + val tmpFile = File.createTempFile(chapterPath.name!!.replace(".cbz", ""), ".cbz") + val buffer = ByteArray(1024) + chapterPath.openInputStream().use { input -> + tmpFile.outputStream().use { fileOut -> + while (true) { + val length = input.read(buffer) + if (length <= 0) break + fileOut.write(buffer, 0, length) + } + fileOut.flush() + } + } + ZipFile(tmpFile.absolutePath) + } else ZipFile(chapterPath.filePath) + + return zip.entries().toList() + .filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } + .sortedWith(Comparator { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }) + .mapIndexed { i, entry -> + val streamFn = { zip.getInputStream(entry) } + ReaderPage(i).apply { + stream = streamFn status = Page.READY } } - } + .let { Observable.just(it) } + } else { + return downloadManager.buildPageList(source, manga, chapter.chapter) + .map { pages -> + pages.map { page -> + ReaderPage(page.index, page.url, page.imageUrl) { + context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!! + }.apply { + status = Page.READY + } + } + } + } } override fun getPage(page: ReaderPage): Observable { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt index 82c055a0f..e10502441 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt @@ -64,6 +64,24 @@ class SettingsDownloadController : SettingsController() { titleRes = R.string.pref_download_only_over_wifi defaultValue = true } + + switchPreference { + key = Keys.saveChaptersAsCBZ + titleRes = R.string.save_chapter_as_cbz + defaultValue = false + } + + intListPreference { + titleRes = R.string.save_chapter_as_cbz_level + key = Keys.saveChaptersAsCBZLevel + entries = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9") + entryValues = entries + defaultValue = "0" + + preferences.saveChaptersAsCBZ().asImmediateFlow { isVisible = it } + .launchIn(scope) + } + preferenceCategory { titleRes = R.string.pref_category_delete_chapters diff --git a/app/src/main/res/values/strings_sy.xml b/app/src/main/res/values/strings_sy.xml index a1cf46c15..168932c7e 100644 --- a/app/src/main/res/values/strings_sy.xml +++ b/app/src/main/res/values/strings_sy.xml @@ -478,5 +478,8 @@ %2$s, %1$d pages + Save Chapters as CBZ + CBZ Compression level + \ No newline at end of file