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
targetSdkVersion 30
versionCode gitCommits
versionName '0.5.2'
versionName '0.5.3'
kapt {
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
import androidx.annotation.CheckResult
import androidx.annotation.WorkerThread
import org.koitharu.kotatsu.core.local.WritableCbzFile
import org.koitharu.kotatsu.core.model.Manga
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.toFileNameSafe
import java.io.File
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
@WorkerThread
class MangaZip(val file: File) {
private val dir = file.parentFile?.sub(file.name + ".tmp")?.takeIf { it.mkdir() }
?: throw RuntimeException("Cannot create temporary directory")
private val writableCbz = WritableCbzFile(file)
private var index = MangaIndex(null)
fun prepare(manga: Manga) {
extract()
index = MangaIndex(dir.sub(INDEX_ENTRY).takeIfReadable()?.readText())
suspend fun prepare(manga: Manga) {
writableCbz.prepare()
index = MangaIndex(writableCbz[INDEX_ENTRY].takeIfReadable()?.readText())
index.setMangaInfo(manga, append = true)
}
fun cleanup() {
dir.deleteRecursively()
suspend fun cleanup() {
writableCbz.cleanup()
}
fun compress() {
dir.sub(INDEX_ENTRY).writeText(index.toString())
ZipOutputStream(file.outputStream()).use { out ->
for (file in dir.listFiles().orEmpty()) {
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()
}
}
@CheckResult
suspend fun compress(): Boolean {
writableCbz[INDEX_ENTRY].writeText(index.toString())
return writableCbz.flush()
}
fun addCover(file: File, ext: String) {
@@ -68,7 +40,7 @@ class MangaZip(val file: File) {
append(ext)
}
}
file.copyTo(dir.sub(name), overwrite = true)
writableCbz[name] = file
index.setCoverEntry(name)
}
@@ -80,7 +52,7 @@ class MangaZip(val file: File) {
append(ext)
}
}
file.copyTo(dir.sub(name), overwrite = true)
writableCbz[name] = file
index.addChapter(chapter)
}

View File

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