From b3b7759fe71fab62ccb96184b256e247de3630a0 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Mon, 12 Oct 2026 02:45:22 -0700 Subject: [PATCH] fix: refresh share badges when folder etag is unchanged When the etag still matches but sharedByLink, permissions, or the boolean sharedWithSharee value differ (null and false count as equal), that child is added to the files saved for the folder. The saved row keeps local properties, the existing etag, and the thumbnail-update flag, keeps the remote share fields, and is marked available offline through the parent when the parent is available offline. A unit test covers refreshFolder persisting a folder child in that case, and the unreleased changelog notes the badge refresh. Fixes #4938 Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> --- changelog/unreleased/4938 | 6 ++ .../data/files/repository/OCFileRepository.kt | 23 +++++++ .../files/repository/OCFileRepositoryTest.kt | 65 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 changelog/unreleased/4938 diff --git a/changelog/unreleased/4938 b/changelog/unreleased/4938 new file mode 100644 index 00000000000..ce917d70c73 --- /dev/null +++ b/changelog/unreleased/4938 @@ -0,0 +1,6 @@ +Bugfix: Refresh share badges when the folder etag is unchanged + +Share badges have been refreshed on pull-to-refresh when a folder etag is unchanged +after the folder is shared or unshared on ownCloud 10. + +https://github.com/owncloud/android/issues/4938 diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index e272d465cfc..60f7280b4b3 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -404,6 +404,9 @@ class OCFileRepository( } // Fix: What about renames? Need to fix storage path }) + } else if (shareMetadataChanged(localChildToSync, remoteChild)) { + // oC10 does not change the etag when only share metadata changes. + folderContentUpdated.add(childWithRefreshedShareMetadata(remoteChild, localChildToSync, remoteFolder)) } } @@ -609,6 +612,26 @@ class OCFileRepository( deleteOrResetFileFromDatabase(ocFile, onlyFromLocalStorage) } + private fun shareMetadataChanged(localChild: OCFile, remoteChild: OCFile): Boolean = + localChild.sharedByLink != remoteChild.sharedByLink || + (localChild.sharedWithSharee == true) != (remoteChild.sharedWithSharee == true) || + localChild.permissions != remoteChild.permissions + + private fun childWithRefreshedShareMetadata( + remoteChild: OCFile, + localChild: OCFile, + remoteFolder: OCFile, + ): OCFile = + remoteChild.apply { + copyLocalPropertiesFrom(localChild) + // DO NOT update etag till contents are synced. Share fields stay on the remote child. + etag = localChild.etag + needsToUpdateThumbnail = localChild.needsToUpdateThumbnail + if (remoteFolder.isAvailableOffline) { + availableOfflineStatus = AVAILABLE_OFFLINE_PARENT + } + } + private fun deleteOrResetFileFromDatabase(ocFile: OCFile, onlyFromLocalStorage: Boolean) { if (onlyFromLocalStorage) { localFileDataSource.saveFile(ocFile.copy(storagePath = null, etagInConflict = null, lastUsage = null, etag = null)) diff --git a/owncloudData/src/test/java/com/owncloud/android/data/files/repository/OCFileRepositoryTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/files/repository/OCFileRepositoryTest.kt index 70929cc9cb6..fc57d5cf5a1 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/files/repository/OCFileRepositoryTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/files/repository/OCFileRepositoryTest.kt @@ -1685,6 +1685,71 @@ class OCFileRepositoryTest { } } + @Suppress("MaxLineLength") + @Test + fun `refreshFolder saves a folder child when the etag matches but share badges changed`() { + val ocParentFolderWithoutSpaceId = OC_PARENT_FOLDER_WITH_SPACE_ID.copy(spaceId = null) + val remoteFolderChild = OC_FOLDER_WITH_SPACE_ID.copy(spaceId = null, sharedByLink = true) + val expectedFolderChild = OC_FOLDER_WITH_SPACE_ID.copy(sharedByLink = true) + every { + remoteFileDataSource.refreshFolder( + remotePath = OC_PARENT_FOLDER_WITH_SPACE_ID.remotePath, + accountName = OC_PARENT_FOLDER_WITH_SPACE_ID.owner, + spaceWebDavUrl = OC_SPACE_PERSONAL.root.webDavUrl + ) + } returns listOf(ocParentFolderWithoutSpaceId, remoteFolderChild) + every { + localFileDataSource.getFileByRemotePath( + remotePath = OC_PARENT_FOLDER_WITH_SPACE_ID.remotePath, + owner = OC_PARENT_FOLDER_WITH_SPACE_ID.owner, + spaceId = OC_PARENT_FOLDER_WITH_SPACE_ID.spaceId + ) + } returns OC_PARENT_FOLDER_WITH_SPACE_ID + every { + localFileDataSource.getFolderContent( + folderId = OC_PARENT_FOLDER_WITH_SPACE_ID.id!! + ) + } returns listOf(OC_FOLDER_WITH_SPACE_ID) + every { + localFileDataSource.saveFilesInFolderAndReturnTheFilesThatChanged( + listOfFiles = listOf(expectedFolderChild), + folder = OC_PARENT_FOLDER_WITH_SPACE_ID, + ) + } returns listOf(expectedFolderChild) + + val listOfFiles = ocFileRepository.refreshFolder( + OC_PARENT_FOLDER_WITH_SPACE_ID.remotePath, + OC_PARENT_FOLDER_WITH_SPACE_ID.owner, + OC_PARENT_FOLDER_WITH_SPACE_ID.spaceId, + false + ) + assertEquals(listOf(expectedFolderChild), listOfFiles) + + verify(exactly = 1) { + localSpacesDataSource.getWebDavUrlForSpace( + spaceId = OC_PARENT_FOLDER_WITH_SPACE_ID.spaceId, + accountName = OC_PARENT_FOLDER_WITH_SPACE_ID.owner + ) + remoteFileDataSource.refreshFolder( + remotePath = OC_PARENT_FOLDER_WITH_SPACE_ID.remotePath, + accountName = OC_PARENT_FOLDER_WITH_SPACE_ID.owner, + spaceWebDavUrl = OC_SPACE_PERSONAL.root.webDavUrl + ) + localFileDataSource.getFileByRemotePath( + remotePath = OC_PARENT_FOLDER_WITH_SPACE_ID.remotePath, + owner = OC_PARENT_FOLDER_WITH_SPACE_ID.owner, + spaceId = OC_PARENT_FOLDER_WITH_SPACE_ID.spaceId + ) + localFileDataSource.getFolderContent( + folderId = OC_PARENT_FOLDER_WITH_SPACE_ID.id!! + ) + localFileDataSource.saveFilesInFolderAndReturnTheFilesThatChanged( + listOfFiles = listOf(expectedFolderChild), + folder = OC_PARENT_FOLDER_WITH_SPACE_ID, + ) + } + } + @Suppress("MaxLineLength") @Test fun `refreshFolder returns an empty list of OCFiles when folder and its content already exists in database but there are additional files in conflict in local to be removed`() {