From 77a8a4229c9fbf1b601a448dc919f3c5daa94985 Mon Sep 17 00:00:00 2001 From: arkon Date: Fri, 22 Sep 2023 16:16:23 -0400 Subject: [PATCH] Fix duplicate files being created when saving pages on Android 10+ with separate folders setting enabled Fixes #9943 --- .../kanade/tachiyomi/data/saver/ImageSaver.kt | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt index 9656c0fd9..7086f1023 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.saver -import android.annotation.SuppressLint import android.content.ContentUris import android.content.Context import android.graphics.Bitmap @@ -28,30 +27,59 @@ class ImageSaver( val context: Context, ) { - @SuppressLint("InlinedApi") fun save(image: Image): Uri { val data = image.data - val type = ImageUtil.findImageType(data) ?: throw Exception("Not an image") + val type = ImageUtil.findImageType(data) ?: throw IllegalArgumentException("Not an image") val filename = DiskUtil.buildValidFilename("${image.name}.${type.extension}") if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || image.location !is Location.Pictures) { return save(data(), image.location.directory(context), filename) } + return saveApi29(image, type, filename, data) + } + + private fun save(inputStream: InputStream, directory: File, filename: String): Uri { + directory.mkdirs() + + val destFile = File(directory, filename) + + inputStream.use { input -> + destFile.outputStream().use { output -> + input.copyTo(output) + } + } + + DiskUtil.scanMedia(context, destFile.toUri()) + + return destFile.getUriCompat(context) + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun saveApi29( + image: Image, + type: ImageUtil.ImageType, + filename: String, + data: () -> InputStream, + ): Uri { val pictureDir = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) - val folderRelativePath = "${Environment.DIRECTORY_PICTURES}/${context.getString(R.string.app_name)}/" val imageLocation = (image.location as Location.Pictures).relativePath + val relativePath = listOf( + Environment.DIRECTORY_PICTURES, + context.getString(R.string.app_name), + imageLocation, + ).joinToString(File.separator) val contentValues = contentValuesOf( + MediaStore.Images.Media.RELATIVE_PATH to relativePath, MediaStore.Images.Media.DISPLAY_NAME to image.name, MediaStore.Images.Media.MIME_TYPE to type.mime, - MediaStore.Images.Media.RELATIVE_PATH to folderRelativePath + imageLocation, ) - val picture = findUriOrDefault(folderRelativePath, "$imageLocation$filename") { + val picture = findUriOrDefault(relativePath, filename) { context.contentResolver.insert( pictureDir, contentValues, @@ -74,49 +102,34 @@ class ImageSaver( return picture } - private fun save(inputStream: InputStream, directory: File, filename: String): Uri { - directory.mkdirs() - - val destFile = File(directory, filename) - - inputStream.use { input -> - destFile.outputStream().use { output -> - input.copyTo(output) - } - } - - DiskUtil.scanMedia(context, destFile.toUri()) - - return destFile.getUriCompat(context) - } - @RequiresApi(Build.VERSION_CODES.Q) - private fun findUriOrDefault(relativePath: String, imagePath: String, default: () -> Uri): Uri { + private fun findUriOrDefault(path: String, filename: String, default: () -> Uri): Uri { val projection = arrayOf( MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DISPLAY_NAME, - MediaStore.Images.Media.MIME_TYPE, MediaStore.MediaColumns.RELATIVE_PATH, - MediaStore.MediaColumns.DATE_MODIFIED, ) val selection = "${MediaStore.MediaColumns.RELATIVE_PATH}=? AND ${MediaStore.MediaColumns.DISPLAY_NAME}=?" + // Need to make sure it ends with the separator + val normalizedPath = "${path.removeSuffix(File.separator)}${File.separator}" + context.contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, - arrayOf(relativePath, imagePath), + arrayOf(normalizedPath, filename), null, ).use { cursor -> if (cursor != null && cursor.count >= 1) { - cursor.moveToFirst().let { + if (cursor.moveToFirst()) { val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)) - return ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id) } } } + return default() } }