New cbz write utility

pull/26/head v0.5.3
Koitharu 6 years ago
parent df599e9d50
commit b7e4c6b8c0

@ -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 {

@ -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)
}
}
}
}
}

@ -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)
} }

@ -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()

Loading…
Cancel
Save