Add compress to CBZ on download (#6360)

(cherry picked from commit 5336c5b46e6985b6524e31ad837d7cf8907169fe)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt
This commit is contained in:
Seishirou101 2022-01-01 19:22:35 +00:00 committed by Jobobby04
parent dc992ee932
commit fe77aa9ab1
14 changed files with 77 additions and 132 deletions

View File

@ -81,7 +81,7 @@ class DownloadCache(
if (sourceDir != null) { if (sourceDir != null) {
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] val mangaDir = sourceDir.files[provider.getMangaDirName(manga)]
if (mangaDir != null) { if (mangaDir != null) {
return provider.getValidChapterDirNames(chapter).any { it in mangaDir.files || "$it.cbz" in mangaDir.files } return provider.getValidChapterDirNames(chapter).any { it in mangaDir.files }
} }
} }
return false return false
@ -196,8 +196,6 @@ class DownloadCache(
provider.getValidChapterDirNames(chapter).forEach { provider.getValidChapterDirNames(chapter).forEach {
if (it in mangaDir.files) { if (it in mangaDir.files) {
mangaDir.files -= it mangaDir.files -= it
} else if ("$it.cbz" in mangaDir.files) {
mangaDir.files -= "$it.cbz"
} }
} }
} }
@ -229,8 +227,6 @@ class DownloadCache(
provider.getValidChapterDirNames(chapter).forEach { provider.getValidChapterDirNames(chapter).forEach {
if (it in mangaDir.files) { if (it in mangaDir.files) {
mangaDir.files -= it mangaDir.files -= it
} else if ("$it.cbz" in mangaDir.files) {
mangaDir.files -= "$it.cbz"
} }
} }
} }

View File

@ -41,7 +41,7 @@ class DownloadManager(
/** /**
* Downloads provider, used to retrieve the folders where the chapters are or should be stored. * Downloads provider, used to retrieve the folders where the chapters are or should be stored.
*/ */
private val provider = DownloadProvider(context) val provider = DownloadProvider(context)
/** /**
* Cache of downloaded chapters. * Cache of downloaded chapters.
@ -386,12 +386,12 @@ class DownloadManager(
// Assume there's only 1 version of the chapter name formats present // Assume there's only 1 version of the chapter name formats present
val oldFolder = oldNames.asSequence() val oldFolder = oldNames.asSequence()
.mapNotNull { mangaDir.findFile(it) ?: mangaDir.findFile("$it.cbz") } .mapNotNull { mangaDir.findFile(it) }
.firstOrNull() .firstOrNull()
if (oldFolder?.renameTo(newName + if (oldFolder.name?.endsWith(".cbz") == true) ".cbz" else "") == true) { if (oldFolder?.renameTo(newName) == true) {
cache.removeChapter(oldChapter, manga) cache.removeChapter(oldChapter, manga)
cache.addChapter(newName + if (oldFolder.name?.endsWith(".cbz") == true) ".cbz" else "", mangaDir, manga) cache.addChapter(newName, mangaDir, manga)
} else { } else {
logcat(LogPriority.ERROR) { "Could not rename downloaded chapter: ${oldNames.joinToString()}." } logcat(LogPriority.ERROR) { "Could not rename downloaded chapter: ${oldNames.joinToString()}." }
} }

View File

@ -90,7 +90,7 @@ class DownloadProvider(private val context: Context) {
fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? { fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? {
val mangaDir = findMangaDir(manga, source) val mangaDir = findMangaDir(manga, source)
return getValidChapterDirNames(chapter).asSequence() return getValidChapterDirNames(chapter).asSequence()
.mapNotNull { mangaDir?.findFile(it, true) ?: mangaDir?.findFile("$it.cbz", true) } .mapNotNull { mangaDir?.findFile(it, true) }
.firstOrNull() .firstOrNull()
} }
@ -105,7 +105,7 @@ class DownloadProvider(private val context: Context) {
val mangaDir = findMangaDir(manga, source) ?: return emptyList() val mangaDir = findMangaDir(manga, source) ?: return emptyList()
return chapters.mapNotNull { chapter -> return chapters.mapNotNull { chapter ->
getValidChapterDirNames(chapter).asSequence() getValidChapterDirNames(chapter).asSequence()
.mapNotNull { mangaDir.findFile(it) ?: mangaDir.findFile("$it.cbz") } .mapNotNull { mangaDir.findFile(it) }
.firstOrNull() .firstOrNull()
} }
} }
@ -127,7 +127,7 @@ class DownloadProvider(private val context: Context) {
return mangaDir.listFiles().orEmpty().asList().filter { return mangaDir.listFiles().orEmpty().asList().filter {
chapters.find { chp -> chapters.find { chp ->
getValidChapterDirNames(chp).any { dir -> getValidChapterDirNames(chp).any { dir ->
mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null mangaDir.findFile(dir) != null
} }
} == null || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true } == null || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
} }
@ -174,10 +174,14 @@ class DownloadProvider(private val context: Context) {
* @param chapter the chapter to query. * @param chapter the chapter to query.
*/ */
fun getValidChapterDirNames(chapter: Chapter): List<String> { fun getValidChapterDirNames(chapter: Chapter): List<String> {
val chapterName = getChapterDirName(chapter)
return listOf( return listOf(
getChapterDirName(chapter), // Folder of images
chapterName,
// Archived chapters
"$chapterName.cbz",
// TODO: remove this
// Legacy chapter directory name used in v0.9.2 and before // Legacy chapter directory name used in v0.9.2 and before
DiskUtil.buildValidFilename(chapter.name) DiskUtil.buildValidFilename(chapter.name)
) )

View File

@ -38,6 +38,7 @@ import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.BufferedOutputStream
import java.io.File import java.io.File
import java.util.zip.CRC32 import java.util.zip.CRC32
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
@ -64,10 +65,10 @@ class Downloader(
private val sourceManager: SourceManager private val sourceManager: SourceManager
) { ) {
private val preferences: PreferencesHelper by injectLazy()
private val chapterCache: ChapterCache by injectLazy() private val chapterCache: ChapterCache by injectLazy()
private val preferences: PreferencesHelper by injectLazy()
/** /**
* Store for persisting downloads across restarts. * Store for persisting downloads across restarts.
*/ */
@ -496,41 +497,8 @@ class Downloader(
// Only rename the directory if it's downloaded. // Only rename the directory if it's downloaded.
if (download.status == Download.State.DOWNLOADED) { if (download.status == Download.State.DOWNLOADED) {
var zip: UniFile? = null if (preferences.saveChaptersAsCBZ().get()) {
if ( archiveChapter(mangaDir, dirname, tmpDir)
preferences.saveChaptersAsCBZ().get() &&
mangaDir.createFile("$dirname.cbz.tmp").also { zip = it } != null
) {
ZipOutputStream(zip!!.openOutputStream().buffered()).use { zipOut ->
val compressionLevel = preferences.saveChaptersAsCBZLevel().get()
zipOut.setLevel(compressionLevel)
if (compressionLevel == 0) {
zipOut.setMethod(ZipEntry.STORED)
}
tmpDir.listFiles()?.forEach { img ->
img.openInputStream().use { input ->
val data = input.readBytes()
val entry = ZipEntry(img.name)
if (compressionLevel == 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)
zipOut.closeEntry()
}
}
}
zip!!.renameTo("$dirname.cbz")
tmpDir.delete()
} else { } else {
tmpDir.renameTo(dirname) tmpDir.renameTo(dirname)
} }
@ -540,6 +508,40 @@ class Downloader(
} }
} }
/**
* Archive the chapter pages as a CBZ.
*/
private fun archiveChapter(
mangaDir: UniFile,
dirname: String,
tmpDir: UniFile,
) {
val zip = mangaDir.createFile("$dirname.cbz.tmp")
ZipOutputStream(BufferedOutputStream(zip.openOutputStream())).use { zipOut ->
zipOut.setMethod(ZipEntry.STORED)
tmpDir.listFiles()?.forEach { img ->
img.openInputStream().use { input ->
val data = input.readBytes()
val size = img.length()
val entry = ZipEntry(img.name).apply {
val crc = CRC32().apply {
update(data)
}
setCrc(crc.value)
compressedSize = size
setSize(size)
}
zipOut.putNextEntry(entry)
zipOut.write(data)
}
}
}
zip.renameTo("$dirname.cbz")
tmpDir.delete()
}
/** /**
* Completes a download. This method is called in the main thread. * Completes a download. This method is called in the main thread.
*/ */

View File

@ -209,6 +209,8 @@ class PreferencesHelper(val context: Context) {
fun downloadOnlyOverWifi() = prefs.getBoolean(Keys.downloadOnlyOverWifi, true) fun downloadOnlyOverWifi() = prefs.getBoolean(Keys.downloadOnlyOverWifi, true)
fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", false)
fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false) fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false)
fun numberOfBackups() = flowPrefs.getInt("backup_slots", 1) fun numberOfBackups() = flowPrefs.getInt("backup_slots", 1)
@ -486,10 +488,6 @@ class PreferencesHelper(val context: Context) {
fun dataSaverDownloader() = flowPrefs.getBoolean("data_saver_downloader", true) fun dataSaverDownloader() = flowPrefs.getBoolean("data_saver_downloader", true)
fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", false)
fun saveChaptersAsCBZLevel() = flowPrefs.getInt("save_chapter_as_cbz_level", 0)
fun allowLocalSourceHiddenFolders() = flowPrefs.getBoolean("allow_local_source_hidden_folders", false) fun allowLocalSourceHiddenFolders() = flowPrefs.getBoolean("allow_local_source_hidden_folders", false)
fun authenticatorTimeRanges() = flowPrefs.getStringSet("biometric_time_ranges", mutableSetOf()) fun authenticatorTimeRanges() = flowPrefs.getStringSet("biometric_time_ranges", mutableSetOf())

View File

@ -2,19 +2,16 @@ package eu.kanade.tachiyomi.ui.reader.loader
import android.app.Application import android.app.Application
import android.net.Uri import android.net.Uri
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager 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.Source
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.system.ImageUtil
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.util.zip.ZipFile
/** /**
* Loader used to load a chapter from the downloaded chapters. * Loader used to load a chapter from the downloaded chapters.
@ -29,54 +26,34 @@ class DownloadPageLoader(
// Needed to open input streams // Needed to open input streams
private val context: Application by injectLazy() private val context: Application by injectLazy()
private val downloadProvider by lazy { DownloadProvider(context) }
/** /**
* Returns an observable containing the pages found on this downloaded chapter. * Returns an observable containing the pages found on this downloaded chapter.
*/ */
override fun getPages(): Observable<List<ReaderPage>> { override fun getPages(): Observable<List<ReaderPage>> {
val chapterPath = downloadProvider.findChapterDir(chapter.chapter, manga, source) val chapterPath = downloadManager.provider.findChapterDir(chapter.chapter, manga, source)
return if (chapterPath?.isFile == true) {
getPagesFromArchive(chapterPath)
} else {
getPagesFromDirectory()
}
}
if (chapterPath?.isFile == true) { private fun getPagesFromArchive(chapterPath: UniFile): Observable<List<ReaderPage>> {
val zip = if (!File(chapterPath.filePath!!).canRead()) { val loader = ZipPageLoader(File(chapterPath.filePath!!))
val tmpFile = File.createTempFile(chapterPath.name!!.replace(".cbz", ""), ".cbz") return loader.getPages()
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() private fun getPagesFromDirectory(): Observable<List<ReaderPage>> {
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } return downloadManager.buildPageList(source, manga, chapter.chapter)
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } .map { pages ->
.mapIndexed { i, entry -> pages.map { page ->
val streamFn = { zip.getInputStream(entry) } ReaderPage(page.index, page.url, page.imageUrl) {
ReaderPage(i).apply { context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!!
stream = streamFn }.apply {
status = Page.READY 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<Int> { override fun getPage(page: ReaderPage): Observable<Int> {

View File

@ -68,23 +68,10 @@ class SettingsDownloadController : SettingsController() {
titleRes = R.string.connected_to_wifi titleRes = R.string.connected_to_wifi
defaultValue = true defaultValue = true
} }
// SY -->
switchPreference { switchPreference {
bindTo(preferences.saveChaptersAsCBZ()) bindTo(preferences.saveChaptersAsCBZ())
titleRes = R.string.save_chapter_as_cbz titleRes = R.string.save_chapter_as_cbz
} }
intListPreference {
bindTo(preferences.saveChaptersAsCBZLevel())
titleRes = R.string.save_chapter_as_cbz_level
entries = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
entryValues = entries
visibleIf(preferences.saveChaptersAsCBZ()) { it }
}
// SY <--
preferenceCategory { preferenceCategory {
titleRes = R.string.pref_category_delete_chapters titleRes = R.string.pref_category_delete_chapters

View File

@ -186,10 +186,6 @@
<string name="pref_local_source_hidden_folders">Dossiers cachés de la source locale</string> <string name="pref_local_source_hidden_folders">Dossiers cachés de la source locale</string>
<string name="pref_local_source_hidden_folders_summery">Autoriser la source locale à lire les dossiers cachés</string> <string name="pref_local_source_hidden_folders_summery">Autoriser la source locale à lire les dossiers cachés</string>
<!-- Download settings -->
<string name="save_chapter_as_cbz">Enregistrer les chapitres sous CBZ</string>
<string name="save_chapter_as_cbz_level">Niveau de compression CBZ</string>
<!-- Security settings --> <!-- Security settings -->
<string name="biometric_lock_times">Temps de verrouillage biométrique</string> <string name="biometric_lock_times">Temps de verrouillage biométrique</string>
<string name="action_edit_biometric_lock_times">Modifier les heures de verrouillage</string> <string name="action_edit_biometric_lock_times">Modifier les heures de verrouillage</string>

View File

@ -205,10 +205,6 @@
<string name="custom_manga_info">Info manga yang diedit</string> <string name="custom_manga_info">Info manga yang diedit</string>
<string name="all_read_manga">Semua manga yang dibaca</string> <string name="all_read_manga">Semua manga yang dibaca</string>
<!-- Download settings -->
<string name="save_chapter_as_cbz">Simpan bab sebagai CBZ</string>
<string name="save_chapter_as_cbz_level">Level kompresi CBZ</string>
<!-- Security settings --> <!-- Security settings -->
<string name="biometric_lock_times">Waktu kunci biometrik</string> <string name="biometric_lock_times">Waktu kunci biometrik</string>
<string name="action_edit_biometric_lock_times">Edit waktu kunci</string> <string name="action_edit_biometric_lock_times">Edit waktu kunci</string>

View File

@ -201,10 +201,6 @@
<string name="custom_manga_info">Info. de mangá personalizada</string> <string name="custom_manga_info">Info. de mangá personalizada</string>
<string name="all_read_manga">Todos os mangás lidos</string> <string name="all_read_manga">Todos os mangás lidos</string>
<!-- Download settings -->
<string name="save_chapter_as_cbz">Salvar Capítulos como CBZ</string>
<string name="save_chapter_as_cbz_level">Nível de Compressão de CBZ</string>
<!-- Security settings --> <!-- Security settings -->
<string name="biometric_lock_times">Tempos de bloqueio biométrico</string> <string name="biometric_lock_times">Tempos de bloqueio biométrico</string>
<string name="action_edit_biometric_lock_times">Editar tempos de bloqueio</string> <string name="action_edit_biometric_lock_times">Editar tempos de bloqueio</string>

View File

@ -204,10 +204,6 @@
<string name="custom_manga_info">Сведенья пользователя о серии</string> <string name="custom_manga_info">Сведенья пользователя о серии</string>
<string name="all_read_manga">Все прочитанные серии</string> <string name="all_read_manga">Все прочитанные серии</string>
<!-- Download settings -->
<string name="save_chapter_as_cbz">Сохранить главы как «CBZ»</string>
<string name="save_chapter_as_cbz_level">Степень сжатия «CBZ»</string>
<!-- Security settings --> <!-- Security settings -->
<string name="biometric_lock_times">Биометрическое время блокировки</string> <string name="biometric_lock_times">Биометрическое время блокировки</string>
<string name="action_edit_biometric_lock_times">Изменить биометрическое время блокировки</string> <string name="action_edit_biometric_lock_times">Изменить биометрическое время блокировки</string>

View File

@ -398,6 +398,7 @@
<string name="pref_category_auto_download">Auto-download</string> <string name="pref_category_auto_download">Auto-download</string>
<string name="pref_download_new">Download new chapters</string> <string name="pref_download_new">Download new chapters</string>
<string name="pref_download_new_categories_details">Manga in excluded categories will not be downloaded even if they are also in included categories.</string> <string name="pref_download_new_categories_details">Manga in excluded categories will not be downloaded even if they are also in included categories.</string>
<string name="save_chapter_as_cbz">Save as CBZ archive</string>
<!-- Tracking section --> <!-- Tracking section -->
<string name="tracking_guide">Tracking guide</string> <string name="tracking_guide">Tracking guide</string>

View File

@ -205,10 +205,6 @@
<string name="custom_manga_info">Custom manga info</string> <string name="custom_manga_info">Custom manga info</string>
<string name="all_read_manga">All read manga</string> <string name="all_read_manga">All read manga</string>
<!-- Download settings -->
<string name="save_chapter_as_cbz">Save Chapters as CBZ</string>
<string name="save_chapter_as_cbz_level">CBZ Compression level</string>
<!-- Security settings --> <!-- Security settings -->
<string name="biometric_lock_times">Biometric lock times</string> <string name="biometric_lock_times">Biometric lock times</string>
<string name="action_edit_biometric_lock_times">Edit lock times</string> <string name="action_edit_biometric_lock_times">Edit lock times</string>