Fix zip closing

master
Koitharu 2 years ago
parent 3255fba3c4
commit c5bd979645
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -104,8 +104,11 @@ class ZipOutput(
} }
val zipEntry = ZipEntry(name) val zipEntry = ZipEntry(name)
putNextEntry(zipEntry) putNextEntry(zipEntry)
fis.copyTo(this) try {
closeEntry() fis.copyTo(this)
} finally {
closeEntry()
}
} }
} }
return true return true
@ -118,8 +121,11 @@ class ZipOutput(
} }
val zipEntry = ZipEntry(name) val zipEntry = ZipEntry(name)
putNextEntry(zipEntry) putNextEntry(zipEntry)
content.byteInputStream().copyTo(this) try {
closeEntry() content.byteInputStream().copyTo(this)
} finally {
closeEntry()
}
return true return true
} }
} }

@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import okhttp3.internal.closeQuietly
import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.util.ext.deleteAwait import org.koitharu.kotatsu.core.util.ext.deleteAwait
import org.koitharu.kotatsu.core.util.ext.takeIfReadable import org.koitharu.kotatsu.core.util.ext.takeIfReadable
@ -90,7 +91,7 @@ class LocalMangaDirOutput(
override fun close() { override fun close() {
for (output in chaptersOutput.values) { for (output in chaptersOutput.values) {
output.close() output.closeQuietly()
} }
} }
@ -119,10 +120,21 @@ class LocalMangaDirOutput(
} }
private suspend fun ZipOutput.flushAndFinish() = runInterruptible(Dispatchers.IO) { private suspend fun ZipOutput.flushAndFinish() = runInterruptible(Dispatchers.IO) {
finish() val e: Throwable? = try {
close() finish()
val resFile = File(file.absolutePath.removeSuffix(SUFFIX_TMP)) null
file.renameTo(resFile) } catch (e: Throwable) {
e
} finally {
close()
}
if (e == null) {
val resFile = File(file.absolutePath.removeSuffix(SUFFIX_TMP))
file.renameTo(resFile)
} else {
file.delete()
throw e
}
} }
private fun chapterFileName(chapter: IndexedValue<MangaChapter>): String { private fun chapterFileName(chapter: IndexedValue<MangaChapter>): String {

@ -2,8 +2,6 @@ package org.koitharu.kotatsu.local.data.output
import androidx.core.net.toFile import androidx.core.net.toFile
import androidx.core.net.toUri import androidx.core.net.toUri
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
@ -16,26 +14,14 @@ class LocalMangaUtil(
} }
suspend fun deleteChapters(ids: Set<Long>) { suspend fun deleteChapters(ids: Set<Long>) {
newOutput().use { output ->
when (output) {
is LocalMangaZipOutput -> runInterruptible(Dispatchers.IO) {
LocalMangaZipOutput.filterChapters(output, ids)
}
is LocalMangaDirOutput -> {
output.deleteChapters(ids)
output.finish()
}
}
}
}
private suspend fun newOutput(): LocalMangaOutput = runInterruptible(Dispatchers.IO) {
val file = manga.url.toUri().toFile() val file = manga.url.toUri().toFile()
if (file.isDirectory) { if (file.isDirectory) {
LocalMangaDirOutput(file, manga) LocalMangaDirOutput(file, manga).use { output ->
output.deleteChapters(ids)
output.finish()
}
} else { } else {
LocalMangaZipOutput(file, manga) LocalMangaZipOutput.filterChapters(file, manga, ids)
} }
} }
} }

@ -5,6 +5,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import okhttp3.internal.closeQuietly
import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.util.ext.deleteAwait import org.koitharu.kotatsu.core.util.ext.deleteAwait
import org.koitharu.kotatsu.core.util.ext.readText import org.koitharu.kotatsu.core.util.ext.readText
@ -52,27 +53,29 @@ class LocalMangaZipOutput(
index.setCoverEntry(name) index.setCoverEntry(name)
} }
override suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, ext: String) = mutex.withLock { override suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, ext: String) =
val name = buildString { mutex.withLock {
append(FILENAME_PATTERN.format(chapter.value.branch.hashCode(), chapter.index + 1, pageNumber)) val name = buildString {
if (ext.isNotEmpty() && ext.length <= 4) { append(FILENAME_PATTERN.format(chapter.value.branch.hashCode(), chapter.index + 1, pageNumber))
append('.') if (ext.isNotEmpty() && ext.length <= 4) {
append(ext) append('.')
append(ext)
}
} }
runInterruptible(Dispatchers.IO) {
output.put(name, file)
}
index.addChapter(chapter, null)
} }
runInterruptible(Dispatchers.IO) {
output.put(name, file)
}
index.addChapter(chapter, null)
}
override suspend fun flushChapter(chapter: MangaChapter): Boolean = false override suspend fun flushChapter(chapter: MangaChapter): Boolean = false
override suspend fun finish() = mutex.withLock { override suspend fun finish() = mutex.withLock {
runInterruptible(Dispatchers.IO) { runInterruptible(Dispatchers.IO) {
output.put(ENTRY_NAME_INDEX, index.toString()) output.use { output ->
output.finish() output.put(ENTRY_NAME_INDEX, index.toString())
output.close() output.finish()
}
} }
rootFile.deleteAwait() rootFile.deleteAwait()
output.file.renameTo(rootFile) output.file.renameTo(rootFile)
@ -115,42 +118,53 @@ class LocalMangaZipOutput(
private const val FILENAME_PATTERN = "%08d_%03d%03d" private const val FILENAME_PATTERN = "%08d_%03d%03d"
@WorkerThread suspend fun filterChapters(file: File, manga: Manga, idsToRemove: Set<Long>) =
fun filterChapters(subject: LocalMangaZipOutput, idsToRemove: Set<Long>) { runInterruptible(Dispatchers.IO) {
ZipFile(subject.rootFile).use { zip -> val subject = LocalMangaZipOutput(file, manga)
val index = MangaIndex(zip.readText(zip.getEntry(ENTRY_NAME_INDEX))) try {
idsToRemove.forEach { id -> index.removeChapter(id) } ZipFile(subject.rootFile).use { zip ->
val patterns = requireNotNull(index.getMangaInfo()?.chapters).map { val index = MangaIndex(zip.readText(zip.getEntry(ENTRY_NAME_INDEX)))
index.getChapterNamesPattern(it) idsToRemove.forEach { id -> index.removeChapter(id) }
} val patterns = requireNotNull(index.getMangaInfo()?.chapters).map {
val coverEntryName = index.getCoverEntry() index.getChapterNamesPattern(it)
for (entry in zip.entries()) {
when {
entry.name == ENTRY_NAME_INDEX -> {
subject.output.put(ENTRY_NAME_INDEX, index.toString())
}
entry.isDirectory -> {
subject.output.addDirectory(entry.name)
}
entry.name == coverEntryName -> {
subject.output.copyEntryFrom(zip, entry)
} }
val coverEntryName = index.getCoverEntry()
else -> { for (entry in zip.entries()) {
val name = entry.name.substringBefore('.') when {
if (patterns.any { it.matches(name) }) { entry.name == ENTRY_NAME_INDEX -> {
subject.output.copyEntryFrom(zip, entry) subject.output.put(ENTRY_NAME_INDEX, index.toString())
}
entry.isDirectory -> {
subject.output.addDirectory(entry.name)
}
entry.name == coverEntryName -> {
subject.output.copyEntryFrom(zip, entry)
}
else -> {
val name = entry.name.substringBefore('.')
if (patterns.any { it.matches(name) }) {
subject.output.copyEntryFrom(zip, entry)
}
}
} }
} }
subject.output.finish()
subject.output.close()
subject.rootFile.delete()
subject.output.file.renameTo(subject.rootFile)
} }
} catch (e: Throwable) {
subject.closeQuietly()
try {
subject.output.file.delete()
} catch (e2: Throwable) {
e.addSuppressed(e2)
}
throw e
} }
subject.output.finish()
subject.output.close()
subject.rootFile.delete()
subject.output.file.renameTo(subject.rootFile)
} }
}
} }
} }

Loading…
Cancel
Save