From 85ed42da3390c41188d2bddee2f872ef6a61641b Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 4 Dec 2023 09:42:14 +0100 Subject: [PATCH 1/4] Sign off commits Signed-off-by: alperozturk --- .../files/ReadFileRemoteOperationIT.kt | 28 +++++++++++++++++++ .../android/lib/common/network/WebdavEntry.kt | 21 ++++++++++++++ .../lib/common/network/WebdavUtils.java | 4 +++ .../lib/resources/files/model/RemoteFile.kt | 12 +++++++- 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt index eaf0e9e59..8c2570bbd 100644 --- a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt @@ -47,6 +47,34 @@ class ReadFileRemoteOperationIT : AbstractIT() { assertEquals(remotePath, (result.data[0] as RemoteFile).remotePath) } + @Test + fun testLivePhoto() { + val movieFile = createFile("sample") + val movieFilePath = "/sampleMovie" + assertTrue( + UploadFileRemoteOperation(movieFile, movieFilePath, "video/mov", RANDOM_MTIME) + .execute(client).isSuccess + ) + + val livePhoto = createFile("sample") + val livePhotoPath = "/samplePic" + assertTrue( + UploadFileRemoteOperation(livePhoto, livePhotoPath, "image/jpeg", RANDOM_MTIME) + .execute(client).isSuccess + ) + + val movieFileResult = ReadFileRemoteOperation(movieFilePath).execute(client) + assertTrue(movieFileResult.isSuccess) + val movieRemoteFile = movieFileResult.data[0] as RemoteFile + + val livePhotoResult = ReadFileRemoteOperation(livePhotoPath).execute(client) + assertTrue(livePhotoResult.isSuccess) + val livePhotoRemoteFile = livePhotoResult.data[0] as RemoteFile + + assertTrue(movieRemoteFile.hidden) + assertTrue(livePhotoRemoteFile.livePhoto == movieRemoteFile.livePhoto) + } + @Test fun readRemoteFile() { // create file diff --git a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt index 48c5e34ea..dfae70a38 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt +++ b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt @@ -102,6 +102,10 @@ class WebdavEntry constructor(ms: MultiStatusResponse, splitElement: String) { var tags = arrayOfNulls(0) var imageDimension: ImageDimension? = null var geoLocation: GeoLocation? = null + var hidden = false + private set + var livePhoto: String? = null + private set private val gson = Gson() @@ -461,6 +465,20 @@ class WebdavEntry constructor(ms: MultiStatusResponse, splitElement: String) { GeoLocation(latitude, longitude) } + // NC metadata live photo property: + prop = propSet[EXTENDED_PROPERTY_METADATA_LIVE_PHOTO, ncNamespace] + if (prop != null && prop.value != null) { + livePhoto = prop.value.toString() + } + + // NC has hidden property + prop = propSet[EXTENDED_PROPERTY_HIDDEN, ncNamespace] + hidden = if (prop != null) { + java.lang.Boolean.valueOf(prop.value.toString()) + } else { + false + } + parseLockProperties(ncNamespace, propSet) } else { Log_OC.e("WebdavEntry", "General error, no status for webdav response") @@ -619,6 +637,9 @@ class WebdavEntry constructor(ms: MultiStatusResponse, splitElement: String) { const val EXTENDED_PROPERTY_METADATA_SIZE = "file-metadata-size" const val EXTENDED_PROPERTY_METADATA_GPS = "file-metadata-gps" + const val EXTENDED_PROPERTY_HIDDEN = "hidden" + const val EXTENDED_PROPERTY_METADATA_LIVE_PHOTO = "metadata-files-live-photo" + const val EXTENDED_PROPERTY_METADATA_PHOTOS_SIZE = "metadata-photos-size" const val EXTENDED_PROPERTY_METADATA_PHOTOS_GPS = "metadata-photos-gps" const val TRASHBIN_FILENAME = "trashbin-filename" diff --git a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.java b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.java index 2c16ab837..e81b08f07 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.java +++ b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.java @@ -130,6 +130,8 @@ public static DavPropertyNameSet getAllPropSet() { propSet.add(WebdavEntry.EXTENDED_PROPERTY_METADATA_GPS, ncNamespace); propSet.add(WebdavEntry.EXTENDED_PROPERTY_METADATA_PHOTOS_SIZE, ncNamespace); propSet.add(WebdavEntry.EXTENDED_PROPERTY_METADATA_PHOTOS_GPS, ncNamespace); + propSet.add(WebdavEntry.EXTENDED_PROPERTY_METADATA_LIVE_PHOTO, ncNamespace); + propSet.add(WebdavEntry.EXTENDED_PROPERTY_HIDDEN, ncNamespace); return propSet; } @@ -173,6 +175,8 @@ public static DavPropertyNameSet getFilePropSet() { propSet.add(WebdavEntry.EXTENDED_PROPERTY_METADATA_GPS, ncNamespace); propSet.add(WebdavEntry.EXTENDED_PROPERTY_METADATA_PHOTOS_SIZE, ncNamespace); propSet.add(WebdavEntry.EXTENDED_PROPERTY_METADATA_PHOTOS_GPS, ncNamespace); + propSet.add(WebdavEntry.EXTENDED_PROPERTY_METADATA_LIVE_PHOTO, ncNamespace); + propSet.add(WebdavEntry.EXTENDED_PROPERTY_HIDDEN, ncNamespace); return propSet; } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/model/RemoteFile.kt b/library/src/main/java/com/owncloud/android/lib/resources/files/model/RemoteFile.kt index 4e45def89..37c4eec83 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/model/RemoteFile.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/model/RemoteFile.kt @@ -70,6 +70,8 @@ class RemoteFile : Parcelable, Serializable { var tags: Array? = null var imageDimension: ImageDimension? = null var geoLocation: GeoLocation? = null + var hidden = false + var livePhoto: String? = null constructor() { resetData() @@ -85,7 +87,7 @@ class RemoteFile : Parcelable, Serializable { */ constructor(path: String?) { resetData() - require(!(path == null || path.isEmpty() || !path.startsWith(FileUtils.PATH_SEPARATOR))) { + require(!(path.isNullOrEmpty() || !path.startsWith(FileUtils.PATH_SEPARATOR))) { "Trying to create a OCFile with a non valid remote path: $path" } remotePath = path @@ -123,6 +125,8 @@ class RemoteFile : Parcelable, Serializable { tags = we.tags imageDimension = we.imageDimension geoLocation = we.geoLocation + livePhoto = we.livePhoto + hidden = we.hidden } /** @@ -153,6 +157,8 @@ class RemoteFile : Parcelable, Serializable { lockTimeout = 0 lockToken = null tags = null + hidden = false + livePhoto = null } /** @@ -191,6 +197,8 @@ class RemoteFile : Parcelable, Serializable { lockTimestamp = source.readLong() lockTimeout = source.readLong() lockToken = source.readString() + livePhoto = source.readString() + hidden = source.readInt() == 1 } override fun describeContents(): Int { @@ -227,6 +235,8 @@ class RemoteFile : Parcelable, Serializable { dest.writeLong(lockTimestamp) dest.writeLong(lockTimeout) dest.writeString(lockToken) + dest.writeString(livePhoto) + dest.writeInt(if (hidden) 1 else 0) } companion object { From 444ebdc6c79b795319635d45af74a43846b3dfe0 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 4 Dec 2023 09:47:39 +0100 Subject: [PATCH 2/4] Add link live photo Signed-off-by: alperozturk --- .../files/ReadFileRemoteOperationIT.kt | 21 ++++- .../files/LinkLivePhotoRemoteOperation.kt | 83 +++++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/files/LinkLivePhotoRemoteOperation.kt diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt index 8c2570bbd..e6639c211 100644 --- a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt @@ -50,19 +50,34 @@ class ReadFileRemoteOperationIT : AbstractIT() { @Test fun testLivePhoto() { val movieFile = createFile("sample") - val movieFilePath = "/sampleMovie" + val movieFilePath = "/sampleMovie.mov" assertTrue( UploadFileRemoteOperation(movieFile, movieFilePath, "video/mov", RANDOM_MTIME) .execute(client).isSuccess ) val livePhoto = createFile("sample") - val livePhotoPath = "/samplePic" + val livePhotoPath = "/samplePic.jpg" assertTrue( UploadFileRemoteOperation(livePhoto, livePhotoPath, "image/jpeg", RANDOM_MTIME) .execute(client).isSuccess ) + // link them + assertTrue( + LinkLivePhotoRemoteOperation( + livePhotoPath, + movieFilePath + ).execute(client).isSuccess + ) + + assertTrue( + LinkLivePhotoRemoteOperation( + movieFilePath, + livePhotoPath + ).execute(client).isSuccess + ) + val movieFileResult = ReadFileRemoteOperation(movieFilePath).execute(client) assertTrue(movieFileResult.isSuccess) val movieRemoteFile = movieFileResult.data[0] as RemoteFile @@ -71,8 +86,8 @@ class ReadFileRemoteOperationIT : AbstractIT() { assertTrue(livePhotoResult.isSuccess) val livePhotoRemoteFile = livePhotoResult.data[0] as RemoteFile + assertEquals(livePhotoRemoteFile.livePhoto, movieRemoteFile.remotePath) assertTrue(movieRemoteFile.hidden) - assertTrue(livePhotoRemoteFile.livePhoto == movieRemoteFile.livePhoto) } @Test diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/LinkLivePhotoRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/files/LinkLivePhotoRemoteOperation.kt new file mode 100644 index 000000000..774f7f663 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/LinkLivePhotoRemoteOperation.kt @@ -0,0 +1,83 @@ +/* Nextcloud Android Library is available under MIT license + * + * @author Tobias Kaminsky + * Copyright (C) 2018 Tobias Kaminsky + * Copyright (C) 2018 Nextcloud GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.resources.files + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.network.WebdavEntry +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import org.apache.commons.httpclient.HttpStatus +import org.apache.jackrabbit.webdav.client.methods.PropPatchMethod +import org.apache.jackrabbit.webdav.property.DavPropertyNameSet +import org.apache.jackrabbit.webdav.property.DavPropertySet +import org.apache.jackrabbit.webdav.property.DefaultDavProperty +import org.apache.jackrabbit.webdav.xml.Namespace +import java.io.IOException + +/** + * Links live photos + */ +class LinkLivePhotoRemoteOperation( + private val path: String, + private val linkedFileName: String +) : RemoteOperation() { + @Deprecated("Deprecated in Java") + override fun run(client: OwnCloudClient): RemoteOperationResult { + var result: RemoteOperationResult + lateinit var propPatchMethod: PropPatchMethod + val newProps = DavPropertySet() + val removeProperties = DavPropertyNameSet() + val readMarkerProperty = + DefaultDavProperty( + "nc:metadata-files-live-photo", + linkedFileName, + Namespace.getNamespace(WebdavEntry.NAMESPACE_NC) + ) + newProps.add(readMarkerProperty) + + val commentsPath = client.getFilesDavUri(path) + try { + propPatchMethod = PropPatchMethod(commentsPath, newProps, removeProperties) + val status = client.executeMethod(propPatchMethod) + val isSuccess = + status == HttpStatus.SC_NO_CONTENT || status == HttpStatus.SC_OK || status == HttpStatus.SC_MULTI_STATUS + result = + if (isSuccess) { + RemoteOperationResult(true, status, propPatchMethod.responseHeaders) + } else { + client.exhaustResponse(propPatchMethod.responseBodyAsStream) + RemoteOperationResult(false, status, propPatchMethod.responseHeaders) + } + } catch (e: IOException) { + result = RemoteOperationResult(e) + } finally { + propPatchMethod.releaseConnection() + } + return result + } +} From 3bea1436c07a1f7648c9381a927e1de05e20592b Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 4 Dec 2023 10:01:06 +0100 Subject: [PATCH 3/4] Fix kotlin spotless check Signed-off-by: alperozturk --- .../android/lib/common/network/WebdavEntry.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt index dfae70a38..6906b44e4 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt +++ b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt @@ -473,11 +473,12 @@ class WebdavEntry constructor(ms: MultiStatusResponse, splitElement: String) { // NC has hidden property prop = propSet[EXTENDED_PROPERTY_HIDDEN, ncNamespace] - hidden = if (prop != null) { - java.lang.Boolean.valueOf(prop.value.toString()) - } else { - false - } + hidden = + if (prop != null) { + java.lang.Boolean.valueOf(prop.value.toString()) + } else { + false + } parseLockProperties(ncNamespace, propSet) } else { From b3f2a36565ff456c4069bf330a8b3bccb128341f Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Mon, 4 Dec 2023 10:57:06 +0100 Subject: [PATCH 4/4] test only on NC28+ Signed-off-by: tobiasKaminsky --- .../android/lib/resources/files/ReadFileRemoteOperationIT.kt | 2 ++ .../owncloud/android/lib/resources/status/NextcloudVersion.kt | 3 +++ 2 files changed, 5 insertions(+) diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt index e6639c211..9861d2cd3 100644 --- a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt @@ -49,6 +49,8 @@ class ReadFileRemoteOperationIT : AbstractIT() { @Test fun testLivePhoto() { + testOnlyOnServer(NextcloudVersion.nextcloud_28) + val movieFile = createFile("sample") val movieFilePath = "/sampleMovie.mov" assertTrue( diff --git a/library/src/main/java/com/owncloud/android/lib/resources/status/NextcloudVersion.kt b/library/src/main/java/com/owncloud/android/lib/resources/status/NextcloudVersion.kt index b4b5f8ae1..e189208a2 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/status/NextcloudVersion.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/status/NextcloudVersion.kt @@ -44,6 +44,9 @@ class NextcloudVersion : OwnCloudVersion { @JvmField val nextcloud_27 = NextcloudVersion(0x1B000000) // 27.0 + + @JvmField + val nextcloud_28 = NextcloudVersion(0x1C000000) // 28.0 } constructor(string: String) : super(string)