Migrate OkHttp to Ktor
This commit is contained in:
@@ -54,7 +54,7 @@ dependencies {
|
||||
|
||||
implementation(libs.bundles.androidx.lifecycle)
|
||||
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.bundles.ktor)
|
||||
|
||||
api(project(":libs:webdav"))
|
||||
implementation(project(":core:i18n"))
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package de.mm20.launcher2.nextcloud
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
data class NcUser(
|
||||
val displayName: String,
|
||||
val username: String
|
||||
val displayName: String,
|
||||
val username: String
|
||||
)
|
||||
|
||||
|
||||
@@ -11,19 +11,27 @@ import androidx.security.crypto.MasterKey
|
||||
import de.mm20.launcher2.serialization.Json
|
||||
import de.mm20.launcher2.webdav.WebDavApi
|
||||
import de.mm20.launcher2.webdav.WebDavFile
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.client.plugins.defaultRequest
|
||||
import io.ktor.client.request.basicAuth
|
||||
import io.ktor.client.request.delete
|
||||
import io.ktor.client.request.forms.submitForm
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.parameters
|
||||
import io.ktor.http.path
|
||||
import io.ktor.http.takeFrom
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import okhttp3.Authenticator
|
||||
import okhttp3.Credentials
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.Route
|
||||
import okhttp3.internal.EMPTY_REQUEST
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
@@ -31,18 +39,18 @@ class NextcloudApiHelper(val context: Context) {
|
||||
|
||||
|
||||
private val httpClient by lazy {
|
||||
OkHttpClient.Builder()
|
||||
.authenticator(object : Authenticator {
|
||||
override fun authenticate(route: Route?, response: Response): Request? {
|
||||
if (response.priorResponse?.priorResponse != null) return null
|
||||
return response.request
|
||||
.newBuilder()
|
||||
.addHeader("Authorization", getAuthorization() ?: return null)
|
||||
.build()
|
||||
HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(Json.Lenient)
|
||||
}
|
||||
defaultRequest {
|
||||
val user = getUserName()
|
||||
val token = getToken()
|
||||
if (user != null && token != null) {
|
||||
basicAuth(user, token)
|
||||
}
|
||||
|
||||
})
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val preferences by lazy {
|
||||
@@ -81,26 +89,28 @@ class NextcloudApiHelper(val context: Context) {
|
||||
* and return the response. Returns null if an error occurs.
|
||||
*/
|
||||
internal suspend fun startLoginFlow(serverUrl: String): LoginFlowResponse? {
|
||||
val request = Request.Builder()
|
||||
.url("$serverUrl/index.php/login/v2")
|
||||
.method("POST", EMPTY_REQUEST)
|
||||
.header("user-agent", context.getString(R.string.app_name))
|
||||
.build()
|
||||
val response = try {
|
||||
withContext(Dispatchers.IO) {
|
||||
httpClient.newCall(request).execute()
|
||||
httpClient.post {
|
||||
url {
|
||||
takeFrom(serverUrl)
|
||||
path("index.php", "login", "v2")
|
||||
}
|
||||
header(HttpHeaders.UserAgent, context.getString(R.string.app_name))
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e("NextcloudApiHelper", "HTTP error", e)
|
||||
null
|
||||
}
|
||||
if (response?.code != 200 || response.body == null) {
|
||||
Log.e("NextcloudApiHelper", "Invalid response: ${response?.code} ${response?.message}")
|
||||
if (response?.status != HttpStatusCode.OK) {
|
||||
Log.e(
|
||||
"NextcloudApiHelper",
|
||||
"Invalid response: ${response?.status} ${response?.bodyAsText()}"
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
Json.Lenient.decodeFromStream<LoginFlowResponse>(response.body!!.byteStream())
|
||||
response.body<LoginFlowResponse>()
|
||||
} catch (e: SerializationException) {
|
||||
Log.e("NextcloudApiHelper", "Invalid response body", e)
|
||||
null
|
||||
@@ -108,25 +118,27 @@ class NextcloudApiHelper(val context: Context) {
|
||||
}
|
||||
|
||||
internal suspend fun pollLoginFlow(loginFlow: LoginFlowResponse): LoginPollResponse? {
|
||||
val request = Request.Builder()
|
||||
.url(loginFlow.poll.endpoint)
|
||||
.method("POST", FormBody.Builder().add("token", loginFlow.poll.token).build())
|
||||
.build()
|
||||
val response = try {
|
||||
withContext(Dispatchers.IO) {
|
||||
httpClient.newCall(request).execute()
|
||||
}
|
||||
httpClient.submitForm(
|
||||
url = loginFlow.poll.endpoint,
|
||||
formParameters = parameters {
|
||||
append("token", loginFlow.poll.token)
|
||||
}
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
Log.e("NextcloudApiHelper", "HTTP error", e)
|
||||
null
|
||||
}
|
||||
if (response?.code != 200 || response.body == null) {
|
||||
Log.e("NextcloudApiHelper", "Invalid response: ${response?.code} ${response?.message}")
|
||||
if (response?.status != HttpStatusCode.OK) {
|
||||
Log.e(
|
||||
"NextcloudApiHelper",
|
||||
"Invalid response: ${response?.status} ${response?.bodyAsText()}"
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
Json.Lenient.decodeFromStream<LoginPollResponse>(response.body!!.byteStream())
|
||||
response.body<LoginPollResponse>()
|
||||
} catch (e: SerializationException) {
|
||||
Log.e("NextcloudApiHelper", "Invalid response body", e)
|
||||
null
|
||||
@@ -164,28 +176,33 @@ class NextcloudApiHelper(val context: Context) {
|
||||
|
||||
val server = getServer() ?: return null
|
||||
|
||||
val request = Request.Builder()
|
||||
.addHeader("OCS-APIRequest", "true")
|
||||
.url("$server/ocs/v1.php/cloud/user?format=json")
|
||||
.build()
|
||||
|
||||
val response = runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
httpClient.newCall(request).execute()
|
||||
val response = try {
|
||||
httpClient.get {
|
||||
url {
|
||||
takeFrom(server)
|
||||
path("ocs", "v1.php", "cloud", "user")
|
||||
parameter("format", "json")
|
||||
}
|
||||
header("OCS-APIRequest", "true")
|
||||
}
|
||||
}.getOrNull() ?: return getUserName()
|
||||
} catch (e: Exception) {
|
||||
Log.e("NextcloudApiHelper", "HTTP error", e)
|
||||
return getUserName()
|
||||
}
|
||||
|
||||
if (response.code != 200) {
|
||||
if (response.status != HttpStatusCode.OK) {
|
||||
logout()
|
||||
return null
|
||||
}
|
||||
val body = response.body ?: return getUserName()
|
||||
val body = try {
|
||||
response.body<UserReponse>()
|
||||
} catch (e: SerializationException) {
|
||||
Log.e("NextcloudApiHelper", "Invalid response body", e)
|
||||
return getUserName()
|
||||
}
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
val json = JSONObject(body.string())
|
||||
val name = json.optJSONObject("ocs")
|
||||
?.optJSONObject("data")
|
||||
?.optString("display-name")
|
||||
val name = body.ocs.data.displayName
|
||||
|
||||
preferences.edit {
|
||||
putString("displayname", name)
|
||||
@@ -196,10 +213,6 @@ class NextcloudApiHelper(val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAuthorization(): String? {
|
||||
return Credentials.basic(getUserName() ?: return null, getToken() ?: return null)
|
||||
}
|
||||
|
||||
fun getServer(): String? {
|
||||
return preferences.getString("server", null)
|
||||
}
|
||||
@@ -225,15 +238,15 @@ class NextcloudApiHelper(val context: Context) {
|
||||
val username = getUserName()
|
||||
val token = getToken()
|
||||
if (server == null || username == null || token == null) return
|
||||
val request = Request.Builder()
|
||||
.addHeader("OCS-APIREQUEST", "true")
|
||||
.delete()
|
||||
.url("$server/ocs/v2.php/core/apppassword")
|
||||
.build()
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val response = httpClient.newCall(request).execute()
|
||||
response
|
||||
httpClient.delete {
|
||||
url {
|
||||
takeFrom(server)
|
||||
path("ocs", "v2.php", "core", "apppassword")
|
||||
}
|
||||
header("OCS-APIREQUEST", "true")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e("NextcloudApiHelper", "Error during Nextcloud logout", e)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.mm20.launcher2.nextcloud
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class UserReponse(
|
||||
val ocs: UserReponseOcs
|
||||
)
|
||||
|
||||
|
||||
@Serializable
|
||||
internal data class UserReponseOcs(
|
||||
val data: UserReponseOcsData
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class UserReponseOcsData(
|
||||
@SerialName("display-name") val displayName: String?,
|
||||
)
|
||||
@@ -54,11 +54,12 @@ dependencies {
|
||||
|
||||
implementation(libs.bundles.androidx.lifecycle)
|
||||
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.bundles.ktor)
|
||||
|
||||
api(project(":libs:webdav"))
|
||||
implementation(project(":core:crashreporter"))
|
||||
implementation(project(":core:ktx"))
|
||||
implementation(project(":core:i18n"))
|
||||
implementation(project(":core:base"))
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.mm20.launcher2.owncloud
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class UserReponse(
|
||||
val ocs: UserReponseOcs
|
||||
)
|
||||
|
||||
|
||||
@Serializable
|
||||
internal data class UserReponseOcs(
|
||||
val data: UserReponseOcsData
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class UserReponseOcsData(
|
||||
@SerialName("display-name") val displayName: String?,
|
||||
)
|
||||
@@ -8,13 +8,24 @@ import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKey
|
||||
import androidx.security.crypto.MasterKeys
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.serialization.Json
|
||||
import de.mm20.launcher2.webdav.WebDavApi
|
||||
import de.mm20.launcher2.webdav.WebDavFile
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.*
|
||||
import org.json.JSONObject
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.client.plugins.defaultRequest
|
||||
import io.ktor.client.request.basicAuth
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.appendPathSegments
|
||||
import io.ktor.http.path
|
||||
import io.ktor.http.takeFrom
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.serialization.SerializationException
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
@@ -22,18 +33,18 @@ class OwncloudClient(val context: Context) {
|
||||
|
||||
|
||||
private val httpClient by lazy {
|
||||
OkHttpClient.Builder()
|
||||
.authenticator(object : Authenticator {
|
||||
override fun authenticate(route: Route?, response: Response): Request? {
|
||||
if (response.priorResponse?.priorResponse != null) return null
|
||||
return response.request
|
||||
.newBuilder()
|
||||
.addHeader("Authorization", getAuthorization() ?: return null)
|
||||
.build()
|
||||
}
|
||||
|
||||
})
|
||||
.build()
|
||||
HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(Json.Lenient)
|
||||
}
|
||||
defaultRequest {
|
||||
val user = getUserName()
|
||||
val token = getToken()
|
||||
if (user != null && token != null) {
|
||||
basicAuth(user, token)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val preferences by lazy {
|
||||
@@ -42,7 +53,8 @@ class OwncloudClient(val context: Context) {
|
||||
|
||||
private fun createPreferences(catchErrors: Boolean = true): SharedPreferences {
|
||||
try {
|
||||
val masterKey = MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
|
||||
val masterKey =
|
||||
MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
|
||||
return EncryptedSharedPreferences.create(
|
||||
context,
|
||||
"owncloud",
|
||||
@@ -71,34 +83,42 @@ class OwncloudClient(val context: Context) {
|
||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||
url = "https://$url"
|
||||
}
|
||||
val request = Request.Builder()
|
||||
.url("$url/remote.php/webdav")
|
||||
.build()
|
||||
val response = runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
httpClient.newCall(request).execute()
|
||||
}
|
||||
}.getOrNull() ?: return false
|
||||
return response.code == 200 || response.code == 401
|
||||
}
|
||||
|
||||
internal suspend fun checkOwncloudCredentials(server: String, username: String, password: String): Boolean {
|
||||
val request = Request.Builder()
|
||||
.addHeader("authorization", Credentials.basic(username, password))
|
||||
.url("$server/ocs/v1.php/cloud/user?format=json")
|
||||
.build()
|
||||
|
||||
val response = try {
|
||||
withContext(Dispatchers.IO) {
|
||||
httpClient.newCall(request).execute()
|
||||
httpClient.get {
|
||||
url {
|
||||
takeFrom(url)
|
||||
appendPathSegments("remote.php", "webdav")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
|
||||
return response.status == HttpStatusCode.OK || response.status == HttpStatusCode.Unauthorized
|
||||
}
|
||||
|
||||
internal suspend fun checkOwncloudCredentials(
|
||||
server: String,
|
||||
username: String,
|
||||
password: String
|
||||
): Boolean {
|
||||
val response = try {
|
||||
httpClient.get {
|
||||
url {
|
||||
takeFrom(server)
|
||||
path("ocs", "v1.php", "cloud", "user")
|
||||
parameter("format", "json")
|
||||
}
|
||||
basicAuth(username, password)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e("OwncloudClient", "HTTP error", e)
|
||||
return false
|
||||
}
|
||||
|
||||
if (response.code != 200) {
|
||||
Log.e("OwncloudClient", "HTTP error: ${response.code}")
|
||||
if (response.status != HttpStatusCode.OK) {
|
||||
Log.e("OwncloudClient", "HTTP error: ${response.status}")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -118,8 +138,8 @@ class OwncloudClient(val context: Context) {
|
||||
val displayName = getDisplayName() ?: return null
|
||||
|
||||
return OcUser(
|
||||
displayName,
|
||||
username
|
||||
displayName,
|
||||
username
|
||||
)
|
||||
}
|
||||
|
||||
@@ -134,36 +154,31 @@ class OwncloudClient(val context: Context) {
|
||||
}
|
||||
|
||||
val server = getServer() ?: return null
|
||||
|
||||
val request = Request.Builder()
|
||||
.addHeader("OCS-APIRequest", "true")
|
||||
.url("$server/ocs/v1.php/cloud/user?format=json")
|
||||
.build()
|
||||
|
||||
val response = runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
httpClient.newCall(request).execute()
|
||||
val response = try {
|
||||
httpClient.get {
|
||||
url {
|
||||
takeFrom(server)
|
||||
path("ocs", "v1.php", "cloud", "user")
|
||||
parameter("format", "json")
|
||||
}
|
||||
header("OCS-APIRequest", "true")
|
||||
}
|
||||
}.getOrNull() ?: return getUserName()
|
||||
} catch (e: Exception) {
|
||||
CrashReporter.logException(e)
|
||||
return getUserName()
|
||||
}
|
||||
|
||||
if (response.code != 200) {
|
||||
if (response.status != HttpStatusCode.OK) {
|
||||
logout()
|
||||
return null
|
||||
}
|
||||
val body = response.body ?: return getUserName()
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
val json = JSONObject(body.string())
|
||||
|
||||
return@withContext json.optJSONObject("ocs")
|
||||
?.optJSONObject("data")
|
||||
?.optString("display-name")
|
||||
?: getUserName()
|
||||
val body = try {
|
||||
response.body<UserReponse>()
|
||||
} catch (e: SerializationException) {
|
||||
CrashReporter.logException(e)
|
||||
return getUserName()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAuthorization(): String? {
|
||||
return Credentials.basic(getUserName() ?: return null, getToken() ?: return null)
|
||||
return body.ocs.data.displayName ?: getUserName()
|
||||
}
|
||||
|
||||
fun getServer(): String? {
|
||||
|
||||
@@ -42,7 +42,7 @@ dependencies {
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.appcompat)
|
||||
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.bundles.ktor)
|
||||
|
||||
implementation(project(":core:crashreporter"))
|
||||
implementation(project(":core:ktx"))
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
package de.mm20.launcher2.webdav
|
||||
|
||||
import com.balsikandar.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.ktx.castToOrNull
|
||||
import de.mm20.launcher2.ktx.decodeUrl
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.request
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.request.url
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.http.appendPathSegments
|
||||
import io.ktor.http.contentType
|
||||
import io.ktor.http.takeFrom
|
||||
import io.ktor.utils.io.jvm.javaio.toInputStream
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.w3c.dom.Element
|
||||
import org.xml.sax.SAXException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.lang.Exception
|
||||
import java.net.URLDecoder
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.parsers.ParserConfigurationException
|
||||
|
||||
object WebDavApi {
|
||||
suspend fun search(webDavUrl: String, username: String, query: String, client: OkHttpClient): List<WebDavFile> {
|
||||
suspend fun search(
|
||||
webDavUrl: String,
|
||||
username: String,
|
||||
query: String,
|
||||
client: HttpClient
|
||||
): List<WebDavFile> {
|
||||
val requestBody = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<d:searchrequest xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
@@ -52,54 +57,55 @@ object WebDavApi {
|
||||
</d:basicsearch>
|
||||
</d:searchrequest>
|
||||
""".trimIndent()
|
||||
val request = Request.Builder()
|
||||
.url(webDavUrl)
|
||||
.method("SEARCH", requestBody.toRequestBody("text/xml".toMediaType()))
|
||||
.build()
|
||||
return withContext(Dispatchers.IO) {
|
||||
val results = mutableListOf<WebDavFile>()
|
||||
try {
|
||||
val response = client.newCall(request).execute()
|
||||
val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(response.body?.byteStream()
|
||||
?: return@withContext emptyList<WebDavFile>())
|
||||
val response = client.request {
|
||||
method = HttpMethod("SEARCH")
|
||||
url(webDavUrl)
|
||||
setBody(requestBody)
|
||||
contentType(ContentType.Text.Xml)
|
||||
}
|
||||
val document = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||
.parse(response.bodyAsChannel().toInputStream())
|
||||
val responses = document.getElementsByTagName("d:response")
|
||||
for (i in 0 until responses.length) {
|
||||
val res = responses.item(i) as? Element ?: continue
|
||||
val url = res.getElementsByTagName("d:href")
|
||||
.takeIf { it.length > 0 }?.item(0)
|
||||
?.textContent?.takeIf { it.isNotEmpty() } ?: continue
|
||||
.takeIf { it.length > 0 }?.item(0)
|
||||
?.textContent?.takeIf { it.isNotEmpty() } ?: continue
|
||||
val fileId = res.getElementsByTagName("oc:fileid")
|
||||
.takeIf { it.length > 0 }?.item(0)
|
||||
?.textContent?.toLongOrNull() ?: continue
|
||||
.takeIf { it.length > 0 }?.item(0)
|
||||
?.textContent?.toLongOrNull() ?: continue
|
||||
|
||||
val displayName = res.getElementsByTagName("d:displayname")
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?: url.trimEnd('/').substringAfterLast("/").decodeUrl("utf8")
|
||||
?: continue
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?: url.trimEnd('/').substringAfterLast("/").decodeUrl("utf8")
|
||||
?: continue
|
||||
|
||||
val isDirectory = res.getElementsByTagName("d:resourcetype")
|
||||
.takeIf { it.length > 0 }
|
||||
?.item(0)?.childNodes?.length == 1
|
||||
.takeIf { it.length > 0 }
|
||||
?.item(0)?.childNodes?.length == 1
|
||||
val mimeType = res.getElementsByTagName("d:getcontenttype")
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent?.takeIf { it.isNotEmpty() }
|
||||
?: if (isDirectory) "inode/directory" else "application/octet-stream"
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent?.takeIf { it.isNotEmpty() }
|
||||
?: if (isDirectory) "inode/directory" else "application/octet-stream"
|
||||
val size = res.getElementsByTagName("oc:size")
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent?.toLongOrNull()
|
||||
?: 0L
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent?.toLongOrNull()
|
||||
?: 0L
|
||||
val owner = res.getElementsByTagName("oc:owner-display-name")
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
|
||||
|
||||
results += WebDavFile(
|
||||
name = displayName,
|
||||
id = fileId,
|
||||
isDirectory = isDirectory,
|
||||
mimeType = mimeType,
|
||||
size = size,
|
||||
owner = owner,
|
||||
url = url
|
||||
name = displayName,
|
||||
id = fileId,
|
||||
isDirectory = isDirectory,
|
||||
mimeType = mimeType,
|
||||
size = size,
|
||||
owner = owner,
|
||||
url = url
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -131,56 +137,65 @@ object WebDavApi {
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
suspend fun searchReport(webDavUrl: String, username: String, query: String, client: OkHttpClient): List<WebDavFile> {
|
||||
suspend fun searchReport(
|
||||
webDavUrl: String,
|
||||
username: String,
|
||||
query: String,
|
||||
client: HttpClient
|
||||
): List<WebDavFile> {
|
||||
val requestBody = getSearchRequestBody(query)
|
||||
val request = Request.Builder()
|
||||
.url("${webDavUrl}files/$username")
|
||||
.method("REPORT", requestBody.toRequestBody())
|
||||
.build()
|
||||
return withContext(Dispatchers.IO) {
|
||||
val results = mutableListOf<WebDavFile>()
|
||||
try {
|
||||
val response = client.newCall(request).execute()
|
||||
val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(response.body?.byteStream()
|
||||
?: return@withContext emptyList<WebDavFile>())
|
||||
val response = client.request {
|
||||
method = HttpMethod("REPORT")
|
||||
url {
|
||||
takeFrom(webDavUrl)
|
||||
appendPathSegments("files", username)
|
||||
}
|
||||
setBody(requestBody)
|
||||
contentType(ContentType.Text.Xml)
|
||||
}
|
||||
val document = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||
.parse(response.bodyAsChannel().toInputStream())
|
||||
val responses = document.getElementsByTagName("d:response")
|
||||
for (i in 0 until responses.length) {
|
||||
val res = responses.item(i) as? Element ?: continue
|
||||
val url = res.getElementsByTagName("d:href")
|
||||
.takeIf { it.length > 0 }?.item(0)
|
||||
?.textContent?.takeIf { it.isNotEmpty() } ?: continue
|
||||
.takeIf { it.length > 0 }?.item(0)
|
||||
?.textContent?.takeIf { it.isNotEmpty() } ?: continue
|
||||
val fileId = res.getElementsByTagName("oc:fileid")
|
||||
.takeIf { it.length > 0 }?.item(0)
|
||||
?.textContent?.toLongOrNull() ?: continue
|
||||
.takeIf { it.length > 0 }?.item(0)
|
||||
?.textContent?.toLongOrNull() ?: continue
|
||||
|
||||
val displayName = res.getElementsByTagName("d:displayname")
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?: url.trimEnd('/').substringAfterLast("/").decodeUrl("utf8")
|
||||
?: continue
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?: url.trimEnd('/').substringAfterLast("/").decodeUrl("utf8")
|
||||
?: continue
|
||||
|
||||
val isDirectory = res.getElementsByTagName("d:resourcetype")
|
||||
.takeIf { it.length > 0 }
|
||||
?.item(0)?.childNodes?.length == 1
|
||||
.takeIf { it.length > 0 }
|
||||
?.item(0)?.childNodes?.length == 1
|
||||
val mimeType = res.getElementsByTagName("d:getcontenttype")
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent?.takeIf { it.isNotEmpty() }
|
||||
?: if (isDirectory) "inode/directory" else "application/octet-stream"
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent?.takeIf { it.isNotEmpty() }
|
||||
?: if (isDirectory) "inode/directory" else "application/octet-stream"
|
||||
val size = res.getElementsByTagName("oc:size")
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent?.toLongOrNull()
|
||||
?: 0L
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent?.toLongOrNull()
|
||||
?: 0L
|
||||
val owner = res.getElementsByTagName("oc:owner-display-name")
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
.takeIf { it.length > 0 }?.item(0)?.textContent
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
|
||||
|
||||
results += WebDavFile(
|
||||
name = displayName,
|
||||
id = fileId,
|
||||
isDirectory = isDirectory,
|
||||
mimeType = mimeType,
|
||||
size = size,
|
||||
owner = owner,
|
||||
url = url
|
||||
name = displayName,
|
||||
id = fileId,
|
||||
isDirectory = isDirectory,
|
||||
mimeType = mimeType,
|
||||
size = size,
|
||||
owner = owner,
|
||||
url = url
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
Reference in New Issue
Block a user