Merge pull request #4759 from nextcloud/attach_files

Attach files to messages for fresh users
This commit is contained in:
Marcel Hibbe 2025-03-06 12:39:59 +00:00 committed by GitHub
commit 70bca0582e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 152 additions and 36 deletions

View File

@ -151,7 +151,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
} else { } else {
Log.d(TAG, "starting normal upload (not chunked) of $fileName") Log.d(TAG, "starting normal upload (not chunked) of $fileName")
FileUploader(context, currentUser, roomToken, ncApi) FileUploader(okHttpClient, context, currentUser, roomToken, ncApi, file!!)
.upload(sourceFileUri, fileName, remotePath, metaData) .upload(sourceFileUri, fileName, remotePath, metaData)
.blockingFirst() .blockingFirst()
} }

View File

@ -10,7 +10,10 @@ package com.nextcloud.talk.upload.normal
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import at.bitfire.dav4jvm.DavResource
import at.bitfire.dav4jvm.exception.HttpException
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.dagger.modules.RestModule
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.jobs.ShareOperationWorker import com.nextcloud.talk.jobs.ShareOperationWorker
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
@ -18,24 +21,48 @@ import com.nextcloud.talk.utils.FileUtils
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.Response
import java.io.File
import java.io.IOException
import java.io.InputStream import java.io.InputStream
class FileUploader( class FileUploader(
okHttpClient: OkHttpClient,
val context: Context, val context: Context,
val currentUser: User, val currentUser: User,
val roomToken: String, val roomToken: String,
val ncApi: NcApi val ncApi: NcApi,
val file: File
) { ) {
private var okHttpClientNoRedirects: OkHttpClient? = null
private var okhttpClient: OkHttpClient = okHttpClient
init {
initHttpClient(okHttpClient, currentUser)
}
fun upload(sourceFileUri: Uri, fileName: String, remotePath: String, metaData: String?): Observable<Boolean> { fun upload(sourceFileUri: Uri, fileName: String, remotePath: String, metaData: String?): Observable<Boolean> {
return ncApi.uploadFile( return ncApi.uploadFile(
ApiUtils.getCredentials(currentUser.username, currentUser.token), ApiUtils.getCredentials(
ApiUtils.getUrlForFileUpload(currentUser.baseUrl!!, currentUser.userId!!, remotePath), currentUser.username,
currentUser.token
),
ApiUtils.getUrlForFileUpload(
currentUser.baseUrl!!,
currentUser.userId!!,
remotePath
),
createRequestBody(sourceFileUri) createRequestBody(sourceFileUri)
) )
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).map { response -> .observeOn(AndroidSchedulers.mainThread())
.flatMap { response ->
if (response.isSuccessful) { if (response.isSuccessful) {
ShareOperationWorker.shareFile( ShareOperationWorker.shareFile(
roomToken, roomToken,
@ -44,12 +71,51 @@ class FileUploader(
metaData metaData
) )
FileUtils.copyFileToCache(context, sourceFileUri, fileName) FileUtils.copyFileToCache(context, sourceFileUri, fileName)
true Observable.just(true)
} else { } else {
false if (response.code() == HTTP_CODE_NOT_FOUND ||
response.code() == HTTP_CODE_CONFLICT
) {
createDavResource(sourceFileUri, fileName, remotePath, metaData)
} else {
Observable.just(false)
} }
} }
} }
}
private fun createDavResource(
sourceFileUri: Uri,
fileName: String,
remotePath: String,
metaData: String?
): Observable<Boolean> {
return Observable.fromCallable {
val userFileUploadPath = ApiUtils.userFileUploadPath(
currentUser.baseUrl!!,
currentUser.userId!!
)
val userTalkAttachmentsUploadPath = ApiUtils.userTalkAttachmentsUploadPath(
currentUser.baseUrl!!,
currentUser.userId!!
)
var davResource = DavResource(
okHttpClientNoRedirects!!,
userFileUploadPath.toHttpUrlOrNull()!!
)
createFolder(davResource)
initHttpClient(okHttpClient = okhttpClient, currentUser)
davResource = DavResource(
okHttpClientNoRedirects!!,
userTalkAttachmentsUploadPath.toHttpUrlOrNull()!!
)
createFolder(davResource)
true
}
.subscribeOn(Schedulers.io())
.flatMap { upload(sourceFileUri, fileName, remotePath, metaData) }
}
@Suppress("Detekt.TooGenericExceptionCaught") @Suppress("Detekt.TooGenericExceptionCaught")
private fun createRequestBody(sourceFileUri: Uri): RequestBody? { private fun createRequestBody(sourceFileUri: Uri): RequestBody? {
@ -68,7 +134,49 @@ class FileUploader(
return requestBody return requestBody
} }
private fun initHttpClient(okHttpClient: OkHttpClient, currentUser: User) {
val okHttpClientBuilder: OkHttpClient.Builder = okHttpClient.newBuilder()
okHttpClientBuilder.followRedirects(false)
okHttpClientBuilder.followSslRedirects(false)
okHttpClientBuilder.protocols(listOf(Protocol.HTTP_1_1))
okHttpClientBuilder.authenticator(
RestModule.HttpAuthenticator(
ApiUtils.getCredentials(
currentUser.username,
currentUser.token
)!!,
"Authorization"
)
)
this.okHttpClientNoRedirects = okHttpClientBuilder.build()
}
@Suppress("Detekt.ThrowsCount")
private fun createFolder(davResource: DavResource) {
try {
davResource.mkCol(
xmlBody = null
) { response: Response ->
if (!response.isSuccessful) {
throw IOException("failed to create folder. response code: " + response.code)
}
}
} catch (e: IOException) {
throw IOException("failed to create folder", e)
} catch (e: HttpException) {
if (e.code == METHOD_NOT_ALLOWED_CODE) {
Log.d(TAG, "Folder most probably already exists, that's okay, just continue..")
} else {
throw IOException("failed to create folder", e)
}
}
}
companion object { companion object {
private val TAG = FileUploader::class.simpleName private val TAG = FileUploader::class.simpleName
private const val METHOD_NOT_ALLOWED_CODE: Int = 405
private const val HTTP_CODE_NOT_FOUND: Int = 404
private const val HTTP_CODE_CONFLICT: Int = 409
} }
} }

View File

@ -459,6 +459,14 @@ object ApiUtils {
return "$baseUrl/remote.php/dav/files/$user/$remotePath" return "$baseUrl/remote.php/dav/files/$user/$remotePath"
} }
fun userFileUploadPath(baseUrl: String, user: String): String {
return "$baseUrl/remote.php/dav/files/$user"
}
fun userTalkAttachmentsUploadPath(baseUrl: String, user: String): String {
return "$baseUrl/remote.php/dav/files/$user/Talk"
}
fun getUrlForTempAvatar(baseUrl: String): String { fun getUrlForTempAvatar(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/apps/spreed/temp-user-avatar" return "$baseUrl$OCS_API_VERSION/apps/spreed/temp-user-avatar"
} }

View File

@ -90,6 +90,35 @@
</RelativeLayout> </RelativeLayout>
<LinearLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
android:id="@+id/notification_settings_view"
layout="@layout/item_notification_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_quarter_margin" />
<include
android:id="@+id/webinar_info_view"
layout="@layout/item_webinar_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_quarter_margin" />
<include
android:id="@+id/guest_access_view"
layout="@layout/item_guest_access_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_quarter_margin" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/conversation_description" android:id="@+id/conversation_description"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -139,35 +168,6 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
android:id="@+id/notification_settings_view"
layout="@layout/item_notification_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_quarter_margin" />
<include
android:id="@+id/webinar_info_view"
layout="@layout/item_webinar_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_quarter_margin" />
<include
android:id="@+id/guest_access_view"
layout="@layout/item_guest_access_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_quarter_margin" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/shared_items" android:id="@+id/shared_items"
android:layout_width="match_parent" android:layout_width="match_parent"