New cbz write utility

This commit is contained in:
Koitharu
2020-09-26 17:50:40 +03:00
parent df599e9d50
commit b7e4c6b8c0
4 changed files with 111 additions and 44 deletions

View File

@@ -16,7 +16,7 @@ android {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 30
versionCode gitCommits versionCode gitCommits
versionName '0.5.2' versionName '0.5.3'
kapt { kapt {
arguments { arguments {

View File

@@ -0,0 +1,93 @@
package org.koitharu.kotatsu.core.local
import androidx.annotation.CheckResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
class WritableCbzFile(private val file: File) {
private val dir = File(file.parentFile, file.nameWithoutExtension)
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun prepare() = withContext(Dispatchers.IO) {
check(dir.list().isNullOrEmpty()) {
"Dir ${dir.name} is not empty"
}
if (!dir.exists()) {
dir.mkdir()
}
ZipInputStream(FileInputStream(file)).use { zip ->
var entry = zip.nextEntry
while (entry != null) {
val target = File(dir.path + File.separator + entry.name)
target.parentFile?.mkdirs()
target.outputStream().use { out ->
zip.copyTo(out)
}
zip.closeEntry()
entry = zip.nextEntry
}
}
}
suspend fun cleanup() = withContext(Dispatchers.IO) {
dir.deleteRecursively()
}
@CheckResult
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun flush() = withContext(Dispatchers.IO) {
val tempFile = File(file.path + ".tmp")
if (tempFile.exists()) {
tempFile.delete()
}
try {
ZipOutputStream(FileOutputStream(tempFile)).use { zip ->
dir.listFiles()?.forEach {
zipFile(it, it.name, zip)
}
zip.flush()
}
tempFile.renameTo(file)
} finally {
if (tempFile.exists()) {
tempFile.delete()
}
}
}
operator fun get(name: String) = File(dir, name)
operator fun set(name: String, file: File) {
file.copyTo(this[name], overwrite = true)
}
companion object {
private fun zipFile(fileToZip: File, fileName: String, zipOut: ZipOutputStream) {
if (fileToZip.isDirectory) {
if (fileName.endsWith("/")) {
zipOut.putNextEntry(ZipEntry(fileName))
} else {
zipOut.putNextEntry(ZipEntry("$fileName/"))
}
zipOut.closeEntry()
fileToZip.listFiles()?.forEach { childFile ->
zipFile(childFile, "$fileName/${childFile.name}", zipOut)
}
} else {
FileInputStream(fileToZip).use { fis ->
val zipEntry = ZipEntry(fileName)
zipOut.putNextEntry(zipEntry)
fis.copyTo(zipOut)
}
}
}
}
}

View File

@@ -1,63 +1,35 @@
package org.koitharu.kotatsu.domain.local package org.koitharu.kotatsu.domain.local
import androidx.annotation.CheckResult
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import org.koitharu.kotatsu.core.local.WritableCbzFile
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.utils.ext.sub
import org.koitharu.kotatsu.utils.ext.takeIfReadable import org.koitharu.kotatsu.utils.ext.takeIfReadable
import org.koitharu.kotatsu.utils.ext.toFileNameSafe import org.koitharu.kotatsu.utils.ext.toFileNameSafe
import java.io.File import java.io.File
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
@WorkerThread @WorkerThread
class MangaZip(val file: File) { class MangaZip(val file: File) {
private val dir = file.parentFile?.sub(file.name + ".tmp")?.takeIf { it.mkdir() } private val writableCbz = WritableCbzFile(file)
?: throw RuntimeException("Cannot create temporary directory")
private var index = MangaIndex(null) private var index = MangaIndex(null)
fun prepare(manga: Manga) { suspend fun prepare(manga: Manga) {
extract() writableCbz.prepare()
index = MangaIndex(dir.sub(INDEX_ENTRY).takeIfReadable()?.readText()) index = MangaIndex(writableCbz[INDEX_ENTRY].takeIfReadable()?.readText())
index.setMangaInfo(manga, append = true) index.setMangaInfo(manga, append = true)
} }
fun cleanup() { suspend fun cleanup() {
dir.deleteRecursively() writableCbz.cleanup()
} }
fun compress() { @CheckResult
dir.sub(INDEX_ENTRY).writeText(index.toString()) suspend fun compress(): Boolean {
ZipOutputStream(file.outputStream()).use { out -> writableCbz[INDEX_ENTRY].writeText(index.toString())
for (file in dir.listFiles().orEmpty()) { return writableCbz.flush()
val entry = ZipEntry(file.name)
out.putNextEntry(entry)
file.inputStream().use { stream ->
stream.copyTo(out)
}
out.closeEntry()
}
}
}
private fun extract() {
if (!file.exists()) {
return
}
ZipInputStream(file.inputStream()).use { input ->
while (true) {
val entry = input.nextEntry ?: return
if (!entry.isDirectory) {
dir.sub(entry.name).outputStream().use { out ->
input.copyTo(out)
}
}
input.closeEntry()
}
}
} }
fun addCover(file: File, ext: String) { fun addCover(file: File, ext: String) {
@@ -68,7 +40,7 @@ class MangaZip(val file: File) {
append(ext) append(ext)
} }
} }
file.copyTo(dir.sub(name), overwrite = true) writableCbz[name] = file
index.setCoverEntry(name) index.setCoverEntry(name)
} }
@@ -80,7 +52,7 @@ class MangaZip(val file: File) {
append(ext) append(ext)
} }
} }
file.copyTo(dir.sub(name), overwrite = true) writableCbz[name] = file
index.addChapter(chapter) index.addChapter(chapter)
} }

View File

@@ -142,7 +142,9 @@ class DownloadService : BaseService() {
notification.setCancelId(0) notification.setCancelId(0)
notification.setPostProcessing() notification.setPostProcessing()
notification.update() notification.update()
output.compress() if (!output.compress()) {
throw RuntimeException("Cannot create target file")
}
val result = MangaProviderFactory.createLocal().getFromFile(output.file) val result = MangaProviderFactory.createLocal().getFromFile(output.file)
notification.setDone(result) notification.setDone(result)
notification.dismiss() notification.dismiss()