From 7bfa5be53c208fe681436ff892bf04a56cba6cc3 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Tue, 24 Sep 2024 20:28:22 +0800 Subject: [PATCH] Fix concurrent creation of the same directory --- .../ehviewer/coil/DownloadThumbInterceptor.kt | 20 +++++------ .../com/hippo/ehviewer/spider/SpiderDen.kt | 7 ++-- .../com/hippo/files/AndroidFileSystem.kt | 36 +++++++++---------- app/src/main/kotlin/com/hippo/files/Paths.kt | 2 ++ 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt index 96f9c3987..488fa4e5e 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt @@ -7,9 +7,9 @@ import com.hippo.ehviewer.EhApplication.Companion.imageCache import com.hippo.ehviewer.download.downloadLocation import com.hippo.ehviewer.spider.DownloadInfoMagics.decodeMagicRequestOrUrl import com.hippo.ehviewer.util.sendTo +import com.hippo.files.isDirectory import com.hippo.files.isFile import com.hippo.files.toUri -import eu.kanade.tachiyomi.util.lang.withIOContext object DownloadThumbInterceptor : Interceptor { private const val THUMB_FILE = "thumb.jpg" @@ -18,22 +18,20 @@ object DownloadThumbInterceptor : Interceptor { if (magicOrUrl != null) { val (url, location) = decodeMagicRequestOrUrl(magicOrUrl) if (location != null) { - val thumb = withIOContext { downloadLocation / location / THUMB_FILE } - if (withIOContext { thumb.isFile }) { + val thumb = downloadLocation / location / THUMB_FILE + if (thumb.isFile) { val new = chain.request.newBuilder().data(thumb.toUri()).build() val result = chain.withRequest(new).proceed() if (result is SuccessResult) return result } val new = chain.request.newBuilder().data(url).build() val result = chain.withRequest(new).proceed() - if (result is SuccessResult) { - withIOContext { - // Accessing the recreated file immediately after deleting it throws - // FileNotFoundException, so we just overwrite the existing file. - val key = requireNotNull(chain.request.memoryCacheKey) - imageCache.read(key) { - data sendTo thumb - } + if (result is SuccessResult && thumb.parent?.isDirectory == true) { + // Accessing the recreated file immediately after deleting it throws + // FileNotFoundException, so we just overwrite the existing file. + val key = requireNotNull(chain.request.memoryCacheKey) + imageCache.read(key) { + data sendTo thumb } } return result 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 f24e58dfa..07995cd0c 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.mkdirs import com.hippo.files.moveTo import com.hippo.files.openFileDescriptor import com.hippo.files.openOutputStream @@ -91,10 +92,10 @@ class SpiderDen(val info: GalleryInfo) { mode = value if (mode == SpiderQueen.MODE_DOWNLOAD) { if (downloadDir == null) { - downloadDir = getGalleryDownloadDir(info) + downloadDir = getGalleryDownloadDir(info).apply { mkdirs() } } if (saveAsCbz && tempDownloadDir == null) { - tempDownloadDir = info.tempDownloadDir!! + tempDownloadDir = info.tempDownloadDir!!.apply { mkdirs() } } } } @@ -342,7 +343,7 @@ class SpiderDen(val info: GalleryInfo) { } suspend fun initDownloadDir() { - downloadDir = getGalleryDownloadDir(info) + downloadDir = getGalleryDownloadDir(info).apply { mkdirs() } } suspend fun writeComicInfo(fetchMetadata: Boolean = true) { diff --git a/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt b/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt index 1831b1127..1fdb0e797 100644 --- a/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt +++ b/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt @@ -30,8 +30,9 @@ class AndroidFileSystem(context: Context) : FileSystem() { return physicalFileSystem.atomicMove(source, target) } - DocumentsContract.renameDocument(contentResolver, source.toUri(), target.name) - ?: throw IOException("Failed to move $source to $target") + source.runCatching { + DocumentsContract.renameDocument(contentResolver, toUri(), target.name) + }.getOrNull() ?: throw IOException("Failed to move $source to $target") } override fun canonicalize(path: Path): Path { @@ -52,9 +53,9 @@ class AndroidFileSystem(context: Context) : FileSystem() { } } - dir.parent?.let { - DocumentsContract.createDocument(contentResolver, it.toUri(), Document.MIME_TYPE_DIR, dir.name) - } ?: throw IOException("Failed to create directory: $dir") + dir.parent?.runCatching { + DocumentsContract.createDocument(contentResolver, toUri(), Document.MIME_TYPE_DIR, dir.name) + }?.getOrNull() ?: throw IOException("Failed to create directory: $dir") } override fun createSymlink(source: Path, target: Path) { @@ -74,7 +75,9 @@ class AndroidFileSystem(context: Context) : FileSystem() { uri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getDocumentId(uri) + '/') } - val deleted = DocumentsContract.deleteDocument(contentResolver, uri) + val deleted = runCatching { + DocumentsContract.deleteDocument(contentResolver, uri) + }.getOrDefault(false) if (!deleted) { throw IOException("Failed to delete $path") } @@ -173,25 +176,20 @@ class AndroidFileSystem(context: Context) : FileSystem() { } fun openFileDescriptor(path: Path, mode: String): ParcelFileDescriptor { + if (path.isPhysicalFile()) { + return ParcelFileDescriptor.open(path.toFile(), ParcelFileDescriptor.parseMode(mode)) + } + if ('w' in mode && !exists(path)) { - val parent = path.parent ?: throw IOException("Failed to open file: $path") - createDirectories(parent) - if (path.isPhysicalFile()) { - physicalFileSystem.openReadWrite(path).close() - } else { + path.parent?.runCatching { val displayName = path.name val extension = displayName.substringAfterLast('.', "").ifEmpty { null }?.lowercase() val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) ?: "application/octet-stream" - DocumentsContract.createDocument(contentResolver, parent.toUri(), mimeType, displayName) - ?: throw IOException("Failed to open file: $path") - } + DocumentsContract.createDocument(contentResolver, toUri(), mimeType, displayName) + }?.getOrNull() ?: throw IOException("Failed to open file: $path") } - return if (path.isPhysicalFile()) { - ParcelFileDescriptor.open(path.toFile(), ParcelFileDescriptor.parseMode(mode)) - } else { - contentResolver.openFileDescriptor(path.toUri(), mode) - } ?: throw IOException("Failed to open file: $path") + return contentResolver.openFileDescriptor(path.toUri(), mode) ?: throw IOException("Failed to open file: $path") } } diff --git a/app/src/main/kotlin/com/hippo/files/Paths.kt b/app/src/main/kotlin/com/hippo/files/Paths.kt index c215d8bab..4630773dc 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.mkdirs() = SystemFileSystem.createDirectories(this) + fun Path.moveTo(target: Path) = SystemFileSystem.atomicMove(this, target) fun Path.openFileDescriptor(mode: String) = SystemFileSystem.openFileDescriptor(this, mode)