From 6e0d77a8ab36c5b59a2f699ad44356d86d37cbfc Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Fri, 6 Sep 2024 20:22:15 +0800 Subject: [PATCH] Fix unfinished files being considered downloaded --- .../com/hippo/ehviewer/spider/SpiderDen.kt | 25 +++++++++++++++---- .../com/hippo/ehviewer/spider/SpiderQueen.kt | 2 +- .../com/hippo/files/AndroidFileSystem.kt | 7 +++++- app/src/main/kotlin/com/hippo/files/Paths.kt | 2 ++ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt index 805c4eaa4..392bb0d27 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt @@ -46,6 +46,7 @@ import com.hippo.files.exists import com.hippo.files.find import com.hippo.files.isDirectory import com.hippo.files.list +import com.hippo.files.moveTo import com.hippo.files.openFileDescriptor import com.hippo.files.openOutputStream import eu.kanade.tachiyomi.util.system.logcat @@ -103,9 +104,11 @@ class SpiderDen(val info: GalleryInfo) { return sCache.read(key) { true } ?: false } - private fun findImageFile(index: Int) = synchronized(fileCache) { + private fun findImageFile(index: Int, temp: Boolean = false) = synchronized(fileCache) { val head = perFilename(index) - fileCache.entries.firstOrNull { (name) -> name.startsWith(head) }?.value + fileCache.entries.firstOrNull { (name) -> + name.startsWith(head) && temp == name.endsWith(TEMP_SUFFIX) + }?.value } private fun containInDownloadDir(index: Int): Boolean = findImageFile(index) != null @@ -144,14 +147,17 @@ class SpiderDen(val info: GalleryInfo) { return sCache.remove(key) } - private fun removeFromDownloadDir(index: Int) = findImageFile(index)?.run { + private fun removeTempFile(index: Int) = findImageFile(index, temp = true)?.run { delete() synchronized(fileCache) { fileCache.remove(name) } } - fun remove(index: Int) = removeFromCache(index).also { removeFromDownloadDir(index) } + fun removeIntermediateFiles(index: Int) { + removeFromCache(index) + removeTempFile(index) + } private fun Path.findDownloadFileForIndex(index: Int, extension: String): Path { val name = perFilename(index, extension) @@ -185,7 +191,15 @@ class SpiderDen(val info: GalleryInfo) { crossinline fops: suspend (Path) -> Unit, ): Boolean { imageDir?.run { - fops(findDownloadFileForIndex(index, ext)) + val tempFile = findDownloadFileForIndex(index, "$ext$TEMP_SUFFIX") + fops(tempFile) + val file = resolve(tempFile.name.removeSuffix(TEMP_SUFFIX)) + file.delete() + tempFile.moveTo(file) + synchronized(fileCache) { + fileCache.remove(tempFile.name) + fileCache[file.name] = file + } return true } @@ -353,6 +367,7 @@ class SpiderDen(val info: GalleryInfo) { } } +private const val TEMP_SUFFIX = ".tmp" private val FileNameRegex = Regex("^\\d{8}\\.\\w{3,4}") private val FileHashRegex = Regex("/([0-9a-f]{40})(?:-\\d+){3}-\\w+") diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt index 503e7000a..a5fbc3efe 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt @@ -694,7 +694,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS updatePageState(index, STATE_FINISHED) return }.onFailure { - mSpiderDen.remove(index) + mSpiderDen.removeIntermediateFiles(index) logcat(WORKER_DEBUG_TAG) { "Download image $index attempt #$times failed" } error = when (it) { is TimeoutCancellationException -> ERROR_TIMEOUT diff --git a/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt b/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt index a50ee78ce..1831b1127 100644 --- a/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt +++ b/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt @@ -26,7 +26,12 @@ class AndroidFileSystem(context: Context) : FileSystem() { } override fun atomicMove(source: Path, target: Path) { - TODO("Not yet implemented") + if (source.isPhysicalFile()) { + return physicalFileSystem.atomicMove(source, target) + } + + DocumentsContract.renameDocument(contentResolver, source.toUri(), target.name) + ?: throw IOException("Failed to move $source to $target") } override fun canonicalize(path: Path): Path { diff --git a/app/src/main/kotlin/com/hippo/files/Paths.kt b/app/src/main/kotlin/com/hippo/files/Paths.kt index 78a28603f..c215d8bab 100644 --- a/app/src/main/kotlin/com/hippo/files/Paths.kt +++ b/app/src/main/kotlin/com/hippo/files/Paths.kt @@ -23,6 +23,8 @@ fun Path.delete() = SystemFileSystem.deleteRecursively(this) fun Path.list() = SystemFileSystem.listOrNull(this).orEmpty() +fun Path.moveTo(target: Path) = SystemFileSystem.atomicMove(this, target) + fun Path.openFileDescriptor(mode: String) = SystemFileSystem.openFileDescriptor(this, mode) fun Path.openInputStream(): FileInputStream =