Repository navigation
Common network stack #2161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Common network stack #2161
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| @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 |
|---|---|---|
| @@ -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) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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,setshould be used for internal data class, POJO object change like setName, getName or getting data from internal db.and
fetchshould be used for remote operation thus we can have clear distinction between them.