New cbz write utility
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user