Add Save As CBZ (#84)

Co-authored-by: Fahad1998 <f1998>
This commit is contained in:
Fahad1998 2020-08-23 03:37:03 +06:00 committed by GitHub
parent fae290cf22
commit 4333999b85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 122 additions and 12 deletions

View File

@ -145,7 +145,7 @@ class DownloadCache(
mangaDirs.values.forEach { mangaDir -> mangaDirs.values.forEach { mangaDir ->
val chapterDirs = mangaDir.dir.listFiles() val chapterDirs = mangaDir.dir.listFiles()
.orEmpty() .orEmpty()
.mapNotNull { it.name } .mapNotNull { it.name?.replace(".cbz", "") }
.toHashSet() .toHashSet()
mangaDir.files = chapterDirs mangaDir.files = chapterDirs

View File

@ -89,7 +89,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) } .mapNotNull { mangaDir?.findFile(it) ?: mangaDir?.findFile("$it.cbz") }
.firstOrNull() .firstOrNull()
} }
@ -104,7 +104,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) } .mapNotNull { mangaDir.findFile(it) ?: mangaDir.findFile("$it.cbz") }
.firstOrNull() .firstOrNull()
} }
} }
@ -127,7 +127,7 @@ class DownloadProvider(private val context: Context) {
( (
chapters.find { chp -> chapters.find { chp ->
getValidChapterDirNames(chp).any { dir -> getValidChapterDirNames(chp).any { dir ->
mangaDir.findFile(dir) != null mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null
} }
} == null } == null
) || it.name?.endsWith("_tmp") == true ) || it.name?.endsWith("_tmp") == true

View File

@ -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.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue 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.SourceManager
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource 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.DiskUtil
import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.saveTo
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import java.io.BufferedOutputStream
import java.io.File import java.io.File
import java.util.zip.CRC32
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlinx.coroutines.async import kotlinx.coroutines.async
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
@ -30,6 +35,8 @@ import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
/** /**
@ -53,6 +60,8 @@ class Downloader(
private val sourceManager: SourceManager private val sourceManager: SourceManager
) { ) {
private val preferences: PreferencesHelper = Injekt.get()
private val chapterCache: ChapterCache by injectLazy() private val chapterCache: ChapterCache by injectLazy()
/** /**
@ -464,7 +473,39 @@ class Downloader(
// Only rename the directory if it's downloaded. // Only rename the directory if it's downloaded.
if (download.status == Download.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) cache.addChapter(dirname, mangaDir, download.manga)
DiskUtil.createNoMediaFile(tmpDir, context) DiskUtil.createNoMediaFile(tmpDir, context)

View File

@ -302,4 +302,8 @@ object PreferenceKeys {
const val dataSaverServer = "data_saver_server" const val dataSaverServer = "data_saver_server"
const val dataSaverColorBW = "data_saver_color_bw" const val dataSaverColorBW = "data_saver_color_bw"
const val saveChaptersAsCBZ = "save_chapter_as_cbz"
const val saveChaptersAsCBZLevel = "save_chapter_as_cbz_level"
} }

View File

@ -407,4 +407,8 @@ class PreferencesHelper(val context: Context) {
fun dataSaverServer() = flowPrefs.getString(Keys.dataSaverServer, "") fun dataSaverServer() = flowPrefs.getString(Keys.dataSaverServer, "")
fun dataSaverColorBW() = flowPrefs.getBoolean(Keys.dataSaverColorBW, false) fun dataSaverColorBW() = flowPrefs.getBoolean(Keys.dataSaverColorBW, false)
fun saveChaptersAsCBZ() = flowPrefs.getBoolean(Keys.saveChaptersAsCBZ, false)
fun saveChaptersAsCBZLevel() = flowPrefs.getInt(Keys.saveChaptersAsCBZLevel, 0)
} }

View File

@ -4,10 +4,16 @@ import android.app.Application
import android.net.Uri import android.net.Uri
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 java.io.File
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -26,20 +32,54 @@ class DownloadPageLoader(
*/ */
private val context by injectLazy<Application>() private val context by injectLazy<Application>()
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>> {
return downloadManager.buildPageList(source, manga, chapter.chapter) val chapterPath = downloadProvider.findChapterDir(chapter.chapter, manga, source)
.map { pages ->
pages.map { page -> if (chapterPath?.isFile!!) {
ReaderPage(page.index, page.url, page.imageUrl) { val zip = if (!File(chapterPath.filePath!!).canRead()) {
context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!! val tmpFile = File.createTempFile(chapterPath.name!!.replace(".cbz", ""), ".cbz")
}.apply { 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<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
.mapIndexed { i, entry ->
val streamFn = { zip.getInputStream(entry) }
ReaderPage(i).apply {
stream = streamFn
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

@ -64,6 +64,24 @@ class SettingsDownloadController : SettingsController() {
titleRes = R.string.pref_download_only_over_wifi titleRes = R.string.pref_download_only_over_wifi
defaultValue = true 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 { preferenceCategory {
titleRes = R.string.pref_category_delete_chapters titleRes = R.string.pref_category_delete_chapters

View File

@ -478,5 +478,8 @@
<item quantity="other">%2$s, %1$d pages</item> <item quantity="other">%2$s, %1$d pages</item>
</plurals> </plurals>
<string name="save_chapter_as_cbz">Save Chapters as CBZ</string>
<string name="save_chapter_as_cbz_level">CBZ Compression level</string>
</resources> </resources>