Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions gradle/verification-keyring.keys
Original file line number Diff line number Diff line change
Expand Up @@ -1946,6 +1946,34 @@ ZTZhNjlAZ21haWwuY29tPg==
=JYcM
-----END PGP PUBLIC KEY BLOCK-----

pub E0CB7823CFD00FBF
uid Jake Wharton <jakew@google.com>

sub F3DBCE882C3A01AA
-----BEGIN PGP PUBLIC KEY BLOCK-----

xsBNBFsNoY0BCADIvRrJEX3k7UeuT6zt+F4++xH+5Qo7QzdicjFhhyb22PLPyIsI
Ema+T4QqiPDegUv8yKKTTBmHNw/vSUHTPX9ZUpglckopuOgdfnuQjTKEOEzrN7V/
ZGLB8yR+BoKEJBLdp6W4fGHIxza/o8Iwg8nV0/C9X1gvQI0I63aexqqA5Y0eYt4y
12mQur5sKabJRRWxkn1U8oQmcFs+P/KPVtS4lUb1nsGzFJbsiKY8AOI1RbnYgtIu
c6mzpiUk0NPO27xKScyDq7lKBwwOZMwRzX/6yX7jcAtyTWZnWS675MCbT2K07ubU
Ykrs9NObwDXlKZWLk6JuADgzQaueYIrjLRYxABEBAAG0H0pha2UgV2hhcnRvbiA8
amFrZXdAZ29vZ2xlLmNvbT7OwE0EWw2hjQEIAMvs0IpYeLX3fWxo7uRF2s4T3juq
cyocto97QnyZUsixgTQ+e0ETlwFHPdUBSLtwkbqhdYpoN8RyUo9vkGjLZJkD0znS
BS6xea/WNJj18uBP2QgPXNtM1Upi8pEReZ5RBq0boYz+jbd3cbG5TDAEYQolSmCg
Z2TuugIucMYP9NumoKDdidEwunGjyi7Spk7FsgdrF9BapdaiYrSQqAsXwpH+m1b/
m41ItWF42fW/mpOk8XaEdzGA8xLSATpQ89AWnYi93fSr7ETodUffn51n36fz16M5
W9CM1tHMdHa5gmdLvRh+Bx1MU985wxTiDUfwDN+6k1fbX+t84ULH6asLNtEAEQEA
AcLAfAQYAQgAJhYhBEe/WSJhzRqKabcDtODLeCPP0A+/BQJbDaGNAhsMBQkDwmcA
AAoJEODLeCPP0A+/SUwH/0Osdy2hl3iII7OJyO//y3eL1kzmj7PyWj4sMJdK375U
bs5xsUct/e49qU/Q/oz0zig+lHaYSnfLFMx4qpftW6VMZ/iGl/mekXoCdOrUxZ/0
+srpfJMpIDJyozdZ7n3bFtlq2no1Tbm/kyUrwu3M8Q/a2Lkh8jwygLBGqluz0PfN
sAECAGlxpqGwS1vWjrhHtPIfRb3UD3Ho++OFmz5zKKObjs0x7AlcYIr7Oe+Da2Mv
TMgZUgKMGqsJhvdHsMqZRKV6aVbSdPs3PJgt8UtD2waA4lLRA5xlLdgTdN5dp+Tm
u4awI7kDfdqz1V5s5prLGlkoPhACUxRZeznT3Js7NVE=
=hrDF
-----END PGP PUBLIC KEY BLOCK-----

pub E16AB52D79FD224F
sub 5A34A5E06B936F93
-----BEGIN PGP PUBLIC KEY BLOCK-----
Expand Down
57 changes: 33 additions & 24 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
<trusting group="org.slf4j"/>
</trusted-key>
<trusted-key id="4797B4F5DCC46CEA61059071A1AE06236CA2BA62" group="^com[.]diffplug($|([.].*))" regex="true"/>
<trusted-key id="47BF592261CD1A8A69B703B4E0CB7823CFD00FBF" group="com.jakewharton.retrofit" name="retrofit2-kotlinx-serialization-converter" version="1.0.0"/>
<trusted-key id="47EB6836245D2D40E89DFB4136D4E9618F3ADAB5">
<trusting group="io.github.oshai" name="kotlin-logging-jvm" version="5.1.4"/>
<trusting group="io.github.oshai" name="kotlin-logging-jvm" version="7.0.13"/>
Expand Down Expand Up @@ -8237,14 +8238,14 @@
<sha256 value="3cd4d937052bd4e80293023019d1307b39af97b1077f17031272b8780c7e6683" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.diffplug.spotless" name="spotless-lib" version="4.10.2">
<artifact name="spotless-lib-4.10.2.jar">
<sha256 value="8039fbcb016ef404de2d9939486c3e8e567d56727c2b3c61a1a5cd475e05122b" origin="Generated by Gradle"/>
</artifact>
<artifact name="spotless-lib-4.10.2.module">
<sha256 value="588c3d5bdad5e6f801e3ce3ffbe34bf3f785286f0ef82960c16e7352a1403951" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.diffplug.spotless" name="spotless-lib" version="4.10.2">
<artifact name="spotless-lib-4.10.2.jar">
<sha256 value="8039fbcb016ef404de2d9939486c3e8e567d56727c2b3c61a1a5cd475e05122b" origin="Generated by Gradle"/>
</artifact>
<artifact name="spotless-lib-4.10.2.module">
<sha256 value="588c3d5bdad5e6f801e3ce3ffbe34bf3f785286f0ef82960c16e7352a1403951" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.diffplug.spotless" name="spotless-lib" version="4.3.0">
<artifact name="spotless-lib-4.3.0.jar">
<sha256 value="669f844ec0a974941c6da04b670748f43178239b6d99fddb5b062ce9d06d0939" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -8365,14 +8366,14 @@
<sha256 value="ec5303b3ec91387c3b6cd76915c87821f4a49aed2de8a063943b1080a036a392" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.diffplug.spotless" name="spotless-lib-extra" version="4.10.2">
<artifact name="spotless-lib-extra-4.10.2.jar">
<sha256 value="a80d67a10f9cab563ff423965c2e9d10def07028871f1cc19fe1f98eb63facc4" origin="Generated by Gradle"/>
</artifact>
<artifact name="spotless-lib-extra-4.10.2.module">
<sha256 value="828c845d72bad648e5075488dff3eacfc85f967effea806b63f2754e33a202d9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.diffplug.spotless" name="spotless-lib-extra" version="4.10.2">
<artifact name="spotless-lib-extra-4.10.2.jar">
<sha256 value="a80d67a10f9cab563ff423965c2e9d10def07028871f1cc19fe1f98eb63facc4" origin="Generated by Gradle"/>
</artifact>
<artifact name="spotless-lib-extra-4.10.2.module">
<sha256 value="828c845d72bad648e5075488dff3eacfc85f967effea806b63f2754e33a202d9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.diffplug.spotless" name="spotless-lib-extra" version="4.3.0">
<artifact name="spotless-lib-extra-4.3.0.jar">
<sha256 value="754f804c09dfa2eb8d53963311d2be68f1b4a5621011dcb550d702ead28651d9" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -8493,14 +8494,14 @@
<sha256 value="50f921d2177447c99128f87c8e9f2d8e7349a7194a71a467763b60833de50ae6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.diffplug.spotless" name="spotless-plugin-gradle" version="8.10.2">
<artifact name="spotless-plugin-gradle-8.10.2.jar">
<sha256 value="bd1d47e81838ff94251b382133141a062343790164a6589d1f1b90d6c420c2c9" origin="Generated by Gradle"/>
</artifact>
<artifact name="spotless-plugin-gradle-8.10.2.module">
<sha256 value="42334ae4bd409a0ddad2ecb9c58f496ef5974d953f0c2a454dbaab645a946eac" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.diffplug.spotless" name="spotless-plugin-gradle" version="8.10.2">
<artifact name="spotless-plugin-gradle-8.10.2.jar">
<sha256 value="bd1d47e81838ff94251b382133141a062343790164a6589d1f1b90d6c420c2c9" origin="Generated by Gradle"/>
</artifact>
<artifact name="spotless-plugin-gradle-8.10.2.module">
<sha256 value="42334ae4bd409a0ddad2ecb9c58f496ef5974d953f0c2a454dbaab645a946eac" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.diffplug.spotless" name="spotless-plugin-gradle" version="8.2.1">
<artifact name="spotless-plugin-gradle-8.2.1.jar">
<sha256 value="dbf44f8df1832800241d4f135129fc921ba82fb22d72430cc42d4d3d5bbf9d3c" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -8735,6 +8736,14 @@
<sha256 value="8d22781e864476180e92e3210ddd3f4325e6bc651582b17f440c64493cbb0760" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.bitfireAT" name="dav4jvm" version="4.1.0">
<artifact name="dav4jvm-4.1.0.jar">
<sha256 value="35fd0d49956322144a8ceb5199379e3d2836467a130ecabcbfafd4f80882120d" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="dav4jvm-4.1.0.module">
<sha256 value="7f28a31ff5b6bd1f33f9b837876577df8270e696b884c9629232364039236c57" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.spotbugs" name="spotbugs" version="4.8.6">
<artifact name="spotbugs-4.8.6.jar">
<sha256 value="69fde8787971a26b2372d416015d806bf7df4f847f7121bd5eeef239324cf180" origin="Generated by Gradle"/>
Expand Down
6 changes: 4 additions & 2 deletions library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ buildscript {
junit_version = '4.13.2'
}
dependencies {
classpath 'com.android.tools.build:gradle:9.4.0'
classpath 'com.android.tools.build:gradle:9.4.1'
classpath 'com.github.spotbugs.snom:spotbugs-gradle-plugin:6.5.11'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
Expand Down Expand Up @@ -64,7 +64,9 @@ dependencies {

implementation 'org.apache.jackrabbit:jackrabbit-webdav:2.13.5'
api 'com.squareup.okhttp3:okhttp:5.5.0'
implementation 'com.github.bitfireAT:dav4jvm:3.0.2'
api 'com.github.bitfireAT:dav4jvm:2.2.1'
api 'com.squareup.retrofit2:retrofit:3.0.0'
api 'com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0'
// in transition phase, we use old and new libs
implementation group: 'com.google.code.gson', name: 'gson', version: '2.14.0'
implementation 'androidx.annotation:annotation:1.10.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Your Name <your@email.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.android.lib.resources.webdav

import at.bitfire.dav4jvm.DavResource
import at.bitfire.dav4jvm.Response
import at.bitfire.dav4jvm.property.DisplayName
import at.bitfire.dav4jvm.property.ResourceType
import com.owncloud.android.AbstractIT
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import com.owncloud.android.lib.ocs.SEPARATOR
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import org.junit.Assert.assertTrue
import org.junit.Test

/**
* Showcases dav4jvm (OkHttp-based) as an alternative to the [com.nextcloud.operations] OkHttp
* methods: a PROPFIND with `Depth: 1` on a user's WebDAV root, reusing the same authenticated
* OkHttpClient as [com.owncloud.android.AbstractIT.nextcloudClient].
*/
class DavResourcePropfindIT : AbstractIT() {
@Test
fun propfindRootFolderReturnsCollection() {
val davResource = DavResource(buildDavHttpClient(), rootFolderUrl())

var selfResponse: Response? = null
davResource.propfind(PROPFIND_DEPTH, DisplayName.NAME, ResourceType.NAME) { response, relation ->
if (relation == Response.HrefRelation.SELF) {
selfResponse = response
}
}

val resourceType = selfResponse?.get(ResourceType::class.java)
assertTrue("PROPFIND response must include the requested collection itself", selfResponse != null)
assertTrue(
"root resource must be reported as a collection",
resourceType?.types?.contains(ResourceType.COLLECTION) == true
)
}

private fun buildDavHttpClient(): OkHttpClient =
nextcloudClient.client
.newBuilder()
.followRedirects(false)
.addNetworkInterceptor { chain ->
val request =
chain
.request()
.newBuilder()
.header("Authorization", nextcloudClient.credentials)
.header("User-Agent", OwnCloudClientManagerFactory.getUserAgent())
.build()
chain.proceed(request)
}.build()

private fun rootFolderUrl(): HttpUrl = "${nextcloudClient.filesDavUri}$SEPARATOR".toHttpUrl()

companion object {
private const val PROPFIND_DEPTH = 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Your Name <your@email.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.android.lib.resources.users

import com.nextcloud.android.lib.resources.users.model.UserInfoResponse
import com.owncloud.android.lib.ocs.OcsKotlinResponse
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Query

private const val ENDPOINT_USER = "ocs/v2.php/cloud/user"
private const val QUERY_FORMAT = "format"
private const val OCS_API_REQUEST_HEADER = "OCS-APIREQUEST: true"

/**
* Retrofit definition of the OCS user endpoint. The caller supplies a [retrofit2.Retrofit]
* whose OkHttpClient adds authentication, e.g. one derived from [com.nextcloud.common.NextcloudClient.client].
*/
interface UserApi {
@Headers(OCS_API_REQUEST_HEADER)
@GET(ENDPOINT_USER)
fun getUser(

@alperozturk96 alperozturk96 Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are starting something new.

Prefixes like get, set should be used for internal data class, POJO object change like setName, getName or getting data from internal db.

and fetch should be used for remote operation thus we can have clear distinction between them.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we are exposing non-suspended version of the same function. Library should enforce the consumer to use suspending version of it. Thus we don't create more technical debts, ANRs.

Also library should never expose the suspend function calls without correct threading. This needs to be forced as well.

e.g.

interface UserApi -> this will be internal in the library

class UserRemoteRepository( -> this will be public
    private val api: UserApi
) {
    suspend fun fetchUser(): OcsKotlinResponse<UserInfoResponse> =
        withContext(Dispatchers.IO) {
            api.fetchUser("json")
        }
}

@Query(QUERY_FORMAT) format: String
): Call<OcsKotlinResponse<UserInfoResponse>>

@Headers(OCS_API_REQUEST_HEADER)
@GET(ENDPOINT_USER)
suspend fun fetchUser(
@Query(QUERY_FORMAT) format: String
): OcsKotlinResponse<UserInfoResponse>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Your Name <your@email.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.android.lib.resources.users.model

import kotlinx.serialization.Serializable

@Serializable
data class QuotaResponse(
val free: Long = 0,
val used: Long = 0,
val total: Long = 0,
val relative: Double = 0.0,
val quota: Long = 0
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Your Name <your@email.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.android.lib.resources.users.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class UserInfoResponse(
val id: String? = null,
val enabled: Boolean? = null,
@SerialName("display-name")
val displayName: String? = null,
val email: String? = null,
val phone: String? = null,
val address: String? = null,
val website: String? = null,
val twitter: String? = null,
val quota: QuotaResponse? = null,
val groups: List<String>? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ internal val ocsJson = Json { ignoreUnknownKeys = true }
* shared by every OCS endpoint. Reusable for any endpoint whose payload type [T] is `@Serializable`.
*/
@Serializable
internal data class OcsKotlinResponse<T>(
data class OcsKotlinResponse<T>(
val ocs: OcsKotlin<T>
)

@Serializable
internal data class OcsKotlin<T>(
data class OcsKotlin<T>(
val data: T
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Your Name <your@email.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.android.lib.resources.users

import com.nextcloud.android.lib.resources.users.model.UserInfoResponse
import com.owncloud.android.lib.ocs.OcsKotlinResponse
import com.owncloud.android.lib.ocs.ocsJson
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test

class UserInfoResponseParsingTest {
@Test
fun parsesUserInfoResponse() {
val json =
"""
{
"ocs": {
"meta": { "status": "ok", "statuscode": 200, "message": "OK" },
"data": {
"id": "user1",
"enabled": true,
"display-name": "User One",
"email": "user1@example.com",
"quota": {
"free": 100,
"used": 50,
"total": 150,
"relative": 33.3,
"quota": -3
},
"groups": ["admin", "users"]
}
}
}
""".trimIndent()

val userInfo =
ocsJson
.decodeFromString<OcsKotlinResponse<UserInfoResponse>>(json)
.ocs
.data

assertEquals("user1", userInfo.id)
assertEquals("User One", userInfo.displayName)
assertEquals("user1@example.com", userInfo.email)
assertEquals(-3L, userInfo.quota?.quota)
assertEquals(listOf("admin", "users"), userInfo.groups)
assertNull(userInfo.phone)
}
}
2 changes: 1 addition & 1 deletion sample_client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:9.4.0'
classpath 'com.android.tools.build:gradle:9.4.1'
classpath 'com.github.spotbugs.snom:spotbugs-gradle-plugin:6.5.11'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.8"
Expand Down
Loading