From d587737e40f35773dba7ef15a4681b8e237ba6df Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Thu, 15 Oct 2026 10:27:06 +0200 Subject: [PATCH 1/3] Fix: areImageDimensionsSame remote file check is now symmetric to local one (it doesn't fail when dimensions are unknown) Signed-off-by: daniele-verducci --- .../utils/extensions/RemoteFileExtensions.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt index 0518325e92c8..760c6bfb1d60 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt @@ -9,6 +9,7 @@ package com.nextcloud.utils.extensions import com.nextcloud.utils.TimeConstants import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.files.model.RemoteFile import com.owncloud.android.lib.resources.shares.ShareeUser import com.owncloud.android.lib.resources.tags.Tag @@ -51,17 +52,24 @@ fun RemoteFile.toAlbumItem(): OCFile = FileStorageUtils.fillOCFile(this).apply { @Suppress("ReturnCount") private fun RemoteFile.areImageDimensionsSame(path: String): Boolean { + val TAG = "areImageDimensionsSame" if (!MimeTypeUtil.isImage(mimeType)) { - // can't compare it's not image + Log_OC.i(TAG, "Can't compare: it's not an image") return true } val localFileImageDimension = path.getExifSize() ?: path.getBitmapSize() if (localFileImageDimension == null) { - // can't compare local file image dimension is not determined + Log_OC.w(TAG, "Can't compare: local file image dimension is not determined") return true } - return localFileImageDimension.first.toFloat() == imageDimension?.width && - localFileImageDimension.second.toFloat() == imageDimension?.height + val remoteFileImageDimension = imageDimension + if (remoteFileImageDimension == null) { + Log_OC.w(TAG, "Can't compare: remote file image dimension is not determined") + return true + } + + return localFileImageDimension.first.toFloat() == remoteFileImageDimension.width && + localFileImageDimension.second.toFloat() == remoteFileImageDimension.height } From 3f501f54c1223d215ac799f782dbfac8ef0d74a1 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Thu, 15 Oct 2026 14:11:50 +0200 Subject: [PATCH 2/3] Implemented tests Signed-off-by: daniele-verducci --- .../RemoteFileExtensionsImageTest.kt | 131 ++++++++++++++++++ .../utils/extensions/RemoteFileExtensions.kt | 4 +- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 app/src/androidTest/java/com/nextcloud/extensions/RemoteFileExtensionsImageTest.kt diff --git a/app/src/androidTest/java/com/nextcloud/extensions/RemoteFileExtensionsImageTest.kt b/app/src/androidTest/java/com/nextcloud/extensions/RemoteFileExtensionsImageTest.kt new file mode 100644 index 000000000000..48e2b4fa73b3 --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/extensions/RemoteFileExtensionsImageTest.kt @@ -0,0 +1,131 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Your Name + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.extensions + +import android.graphics.Bitmap +import com.nextcloud.utils.extensions.areImageDimensionsSame +import com.owncloud.android.lib.resources.files.model.ImageDimension +import com.owncloud.android.lib.resources.files.model.RemoteFile +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import java.io.File +import java.io.OutputStream +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.exists + +class RemoteFileExtensionsImageTest { + + private lateinit var tempDir: Path + + @Test + fun remoteFileAreImageDimensionsSame_checkForAllConditions() { + val remoteFile = RemoteFile().apply { + mimeType = "image/jpeg" + imageDimension = ImageDimension(IMAGE_WIDTH.toFloat(), IMAGE_HEIGHT.toFloat()) + } + + // local not an image + assertTrue( + "comparing with non-image", + remoteFile.areImageDimensionsSame(getPath(TEXT_FILENAME).toString()) + ) + + // same dimensions + assertTrue( + "comparing with same-size image", + remoteFile.areImageDimensionsSame(getPath(IMAGE_FILENAME).toString()) + ) + + // different dimensions + val biggerRemoteImage = RemoteFile().apply { + mimeType = "image/jpeg" + imageDimension = ImageDimension((IMAGE_WIDTH * 2).toFloat(), IMAGE_HEIGHT.toFloat()) + } + assertFalse( + "comparing with different-size image", + biggerRemoteImage.areImageDimensionsSame(getPath(IMAGE_FILENAME).toString()) + ) + } + + @Test + fun remoteFileAreImageDimensionsSame_checkForEdgeCases() { + val remoteFileWithDimensions = RemoteFile().apply { + mimeType = "image/jpeg" + imageDimension = ImageDimension(IMAGE_WIDTH.toFloat(), IMAGE_HEIGHT.toFloat()) + } + val remoteFileWithoutDimensions = RemoteFile().apply { + mimeType = "image/jpeg" + imageDimension = null + } + + // local file with null dimensions + assertTrue( + "comparing null-dimensions local file with valid remote file", + remoteFileWithoutDimensions.areImageDimensionsSame(getPath(TEXT_FILENAME).toString()) + ) + + // remote file with null dimensions + assertTrue( + "comparing with null-dimensions remote file", + remoteFileWithoutDimensions.areImageDimensionsSame(getPath(IMAGE_FILENAME).toString()) + ) + + // both files with null dimensions + assertTrue( + "comparing null-dimensions local file with null-dimensions remote file", + remoteFileWithoutDimensions.areImageDimensionsSame(getPath(TEXT_FILENAME).toString()) + ) + } + + @After + fun cleanup() { + val tf = File(getPath(TEXT_FILENAME).toUri()) + val imgf = File(getPath(IMAGE_FILENAME).toUri()) + tf.delete() + imgf.delete() + } + + @Before + fun setup() { + tempDir = Files.createTempDirectory(TMP_DIR) + assertTrue("Temp directory should exist", tempDir.exists()) + + createImageFile() + createTextFile() + } + + private fun getPath(filename: String): Path = tempDir.resolve("test_$filename") + + private fun createImageFile() { + val bitmap = Bitmap.createBitmap(IMAGE_WIDTH, IMAGE_HEIGHT, Bitmap.Config.ARGB_8888) + val imagePath = getPath(IMAGE_FILENAME) + + Files.newOutputStream(imagePath).use { out: OutputStream -> + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out) + } + + assertTrue(imagePath.exists()) + } + + private fun createTextFile() { + val f = File(getPath(TEXT_FILENAME).toUri()) + f.writeText("Lorem ipsum") + } + + companion object { + val TMP_DIR = "remoteFileExtensionsTest" + val TEXT_FILENAME = "text.txt" + val IMAGE_FILENAME = "image.jpg" + + val IMAGE_WIDTH = 123 + val IMAGE_HEIGHT = 45 + } +} diff --git a/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt index 760c6bfb1d60..96a9816af991 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt @@ -7,6 +7,7 @@ package com.nextcloud.utils.extensions +import androidx.annotation.VisibleForTesting import com.nextcloud.utils.TimeConstants import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.common.utils.Log_OC @@ -51,7 +52,8 @@ fun RemoteFile.toAlbumItem(): OCFile = FileStorageUtils.fillOCFile(this).apply { } @Suppress("ReturnCount") -private fun RemoteFile.areImageDimensionsSame(path: String): Boolean { +@VisibleForTesting +fun RemoteFile.areImageDimensionsSame(path: String): Boolean { val TAG = "areImageDimensionsSame" if (!MimeTypeUtil.isImage(mimeType)) { Log_OC.i(TAG, "Can't compare: it's not an image") From 19a0263c07096e7d7c68f8d4d41aeeec7a90a1e7 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Thu, 15 Oct 2026 14:19:17 +0200 Subject: [PATCH 3/3] Lint Signed-off-by: daniele-verducci --- .../nextcloud/utils/extensions/RemoteFileExtensions.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt index 96a9816af991..607a3d891d65 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/RemoteFileExtensions.kt @@ -18,6 +18,7 @@ import com.owncloud.android.utils.FileStorageUtils import com.owncloud.android.utils.FileUtil import com.owncloud.android.utils.MimeTypeUtil +private const val TAG = "RemoteFileExtensions" fun RemoteFile.isSame(path: String?): Boolean { val localFile = path?.toFile() ?: return false @@ -54,21 +55,20 @@ fun RemoteFile.toAlbumItem(): OCFile = FileStorageUtils.fillOCFile(this).apply { @Suppress("ReturnCount") @VisibleForTesting fun RemoteFile.areImageDimensionsSame(path: String): Boolean { - val TAG = "areImageDimensionsSame" if (!MimeTypeUtil.isImage(mimeType)) { - Log_OC.i(TAG, "Can't compare: it's not an image") + Log_OC.i(TAG, "areImageDimensionsSame: Can't compare: it's not an image") return true } val localFileImageDimension = path.getExifSize() ?: path.getBitmapSize() if (localFileImageDimension == null) { - Log_OC.w(TAG, "Can't compare: local file image dimension is not determined") + Log_OC.w(TAG, "areImageDimensionsSame: Can't compare: local file image dimension is not determined") return true } val remoteFileImageDimension = imageDimension if (remoteFileImageDimension == null) { - Log_OC.w(TAG, "Can't compare: remote file image dimension is not determined") + Log_OC.w(TAG, "areImageDimensionsSame: Can't compare: remote file image dimension is not determined") return true }