mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Simplify uploading room and user avatar.
This commit is contained in:
parent
a93cbf3548
commit
56f8e52352
@ -32,7 +32,6 @@ import im.vector.matrix.android.internal.session.account.AccountModule
|
||||
import im.vector.matrix.android.internal.session.cache.CacheModule
|
||||
import im.vector.matrix.android.internal.session.call.CallModule
|
||||
import im.vector.matrix.android.internal.session.content.ContentModule
|
||||
import im.vector.matrix.android.internal.session.content.UploadAvatarWorker
|
||||
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
||||
import im.vector.matrix.android.internal.session.filter.FilterModule
|
||||
import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
|
||||
@ -118,8 +117,6 @@ internal interface SessionComponent {
|
||||
|
||||
fun inject(worker: UploadContentWorker)
|
||||
|
||||
fun inject(worker: UploadAvatarWorker)
|
||||
|
||||
fun inject(worker: SyncWorker)
|
||||
|
||||
fun inject(worker: AddHttpPusherWorker)
|
||||
|
@ -16,12 +16,16 @@
|
||||
|
||||
package im.vector.matrix.android.internal.session.content
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.squareup.moshi.Moshi
|
||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||
import im.vector.matrix.android.internal.di.Authenticated
|
||||
import im.vector.matrix.android.internal.network.ProgressRequestBody
|
||||
import im.vector.matrix.android.internal.network.awaitResponse
|
||||
import im.vector.matrix.android.internal.network.toFailure
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
@ -31,12 +35,14 @@ import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class FileUploader @Inject constructor(@Authenticated
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val eventBus: EventBus,
|
||||
private val context: Context,
|
||||
contentUrlResolver: ContentUrlResolver,
|
||||
moshi: Moshi) {
|
||||
|
||||
@ -59,6 +65,19 @@ internal class FileUploader @Inject constructor(@Authenticated
|
||||
return upload(uploadBody, filename, progressListener)
|
||||
}
|
||||
|
||||
suspend fun uploadFromUri(uri: Uri,
|
||||
filename: String?,
|
||||
mimeType: String?,
|
||||
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
|
||||
val inputStream = withContext(Dispatchers.IO) {
|
||||
context.contentResolver.openInputStream(uri)
|
||||
} ?: throw FileNotFoundException()
|
||||
|
||||
inputStream.use {
|
||||
return uploadByteArray(it.readBytes(), filename, mimeType, progressListener)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun upload(uploadBody: RequestBody, filename: String?, progressListener: ProgressRequestBody.Listener?): ContentUploadResponse {
|
||||
val urlBuilder = uploadUrl.toHttpUrlOrNull()?.newBuilder() ?: throw RuntimeException()
|
||||
|
||||
|
@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.content
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.worker.SessionWorkerParams
|
||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||
import im.vector.matrix.android.internal.worker.getSessionComponent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Possible previous worker: None
|
||||
* Possible next worker : None
|
||||
*/
|
||||
internal class UploadAvatarWorker(val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
override val sessionId: String,
|
||||
val queryUri: Uri,
|
||||
val fileName: String,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class OutputParams(
|
||||
override val sessionId: String,
|
||||
val imageUrl: String? = null,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
|
||||
@Inject lateinit var fileUploader: FileUploader
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
|
||||
Timber.v("Starting upload media work with params $params")
|
||||
|
||||
if (params.lastFailureMessage != null) {
|
||||
// Transmit the error
|
||||
return Result.success(inputData)
|
||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
||||
}
|
||||
|
||||
// Just defensive code to ensure that we never have an uncaught exception that could break the queue
|
||||
return try {
|
||||
internalDoWork(params)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure)
|
||||
handleFailure(params, failure)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun internalDoWork(params: Params): Result {
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
|
||||
try {
|
||||
val inputStream = context.contentResolver.openInputStream(params.queryUri)
|
||||
?: return Result.success(
|
||||
WorkerParamsFactory.toData(
|
||||
params.copy(
|
||||
lastFailureMessage = "Cannot openInputStream for file: " + params.queryUri.toString()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
inputStream.use {
|
||||
return try {
|
||||
Timber.v("## UploadAvatarWorker - Uploading avatar...")
|
||||
val response = fileUploader.uploadByteArray(inputStream.readBytes(), params.fileName, "image/jpeg")
|
||||
Timber.v("## UploadAvatarWorker - Uploadeded avatar: ${response.contentUri}")
|
||||
handleSuccess(params, response.contentUri)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "## UploadAvatarWorker - Uploading avatar failed...")
|
||||
handleFailure(params, t)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
return Result.success(
|
||||
WorkerParamsFactory.toData(
|
||||
params.copy(
|
||||
lastFailureMessage = e.localizedMessage
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFailure(params: Params, failure: Throwable): Result {
|
||||
return Result.success(
|
||||
WorkerParamsFactory.toData(
|
||||
params.copy(
|
||||
lastFailureMessage = failure.localizedMessage
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleSuccess(params: Params, imageUrl: String): Result {
|
||||
Timber.v("handleSuccess $imageUrl, work is stopped $isStopped")
|
||||
|
||||
val sendParams = OutputParams(params.sessionId, imageUrl, params.lastFailureMessage)
|
||||
return Result.success(WorkerParamsFactory.toData(sendParams))
|
||||
}
|
||||
}
|
@ -19,29 +19,23 @@ package im.vector.matrix.android.internal.session.profile
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.CancelableBag
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.database.model.UserThreePidEntity
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||
import im.vector.matrix.android.internal.session.content.UploadAvatarWorker
|
||||
import im.vector.matrix.android.internal.session.content.FileUploader
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.util.CancelableWork
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||
import io.realm.kotlin.where
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val UPLOAD_AVATAR_WORK = "UPLOAD_AVATAR_WORK"
|
||||
@ -54,7 +48,8 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
||||
private val refreshUserThreePidsTask: RefreshUserThreePidsTask,
|
||||
private val getProfileInfoTask: GetProfileInfoTask,
|
||||
private val setDisplayNameTask: SetDisplayNameTask,
|
||||
private val setAvatarUrlTask: SetAvatarUrlTask) : ProfileService {
|
||||
private val setAvatarUrlTask: SetAvatarUrlTask,
|
||||
private val fileUploader: FileUploader) : ProfileService {
|
||||
|
||||
override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
||||
val params = GetProfileInfoTask.Params(userId)
|
||||
@ -83,38 +78,14 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
||||
}
|
||||
|
||||
override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||
val cancelableBag = CancelableBag()
|
||||
val workerParams = UploadAvatarWorker.Params(sessionId, newAvatarUri, fileName)
|
||||
val workerData = WorkerParamsFactory.toData(workerParams)
|
||||
|
||||
val uploadAvatarWork = workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadAvatarWorker>()
|
||||
.setConstraints(WorkManagerProvider.workConstraints)
|
||||
.setInputData(workerData)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
workManagerProvider.workManager
|
||||
.beginUniqueWork("${userId}_$UPLOAD_AVATAR_WORK", ExistingWorkPolicy.REPLACE, uploadAvatarWork)
|
||||
.enqueue()
|
||||
|
||||
cancelableBag.add(CancelableWork(workManagerProvider.workManager, uploadAvatarWork.id))
|
||||
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
workManagerProvider.workManager.getWorkInfoByIdLiveData(uploadAvatarWork.id)
|
||||
.observeForever { info ->
|
||||
if (info != null && info.state.isFinished) {
|
||||
val result = WorkerParamsFactory.fromData<UploadAvatarWorker.OutputParams>(info.outputData)
|
||||
cancelableBag.add(
|
||||
setAvatarUrlTask
|
||||
.configureWith(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = result!!.imageUrl!!)) {
|
||||
callback = matrixCallback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
)
|
||||
}
|
||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) {
|
||||
val response = fileUploader.uploadFromUri(newAvatarUri, fileName, "image/jpeg")
|
||||
setAvatarUrlTask
|
||||
.configureWith(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri)) {
|
||||
callback = matrixCallback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
return cancelableBag
|
||||
}
|
||||
|
||||
override fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
||||
|
@ -18,8 +18,6 @@ package im.vector.matrix.android.internal.session.room.state
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
@ -28,19 +26,15 @@ import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.state.StateService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.CancelableBag
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||
import im.vector.matrix.android.internal.session.content.UploadAvatarWorker
|
||||
import im.vector.matrix.android.internal.session.content.FileUploader
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.util.CancelableWork
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val UPLOAD_AVATAR_WORK = "UPLOAD_AVATAR_WORK"
|
||||
|
||||
@ -50,7 +44,8 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||
private val sendStateTask: SendStateTask,
|
||||
@SessionId private val sessionId: String,
|
||||
private val workManagerProvider: WorkManagerProvider,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val fileUploader: FileUploader
|
||||
) : StateService {
|
||||
|
||||
@AssistedInject.Factory
|
||||
@ -130,38 +125,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||
}
|
||||
|
||||
override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val cancelableBag = CancelableBag()
|
||||
val workerParams = UploadAvatarWorker.Params(sessionId, avatarUri, fileName)
|
||||
val workerData = WorkerParamsFactory.toData(workerParams)
|
||||
|
||||
val uploadAvatarWork = workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadAvatarWorker>()
|
||||
.setConstraints(WorkManagerProvider.workConstraints)
|
||||
.setInputData(workerData)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
workManagerProvider.workManager
|
||||
.beginUniqueWork("${roomId}_$UPLOAD_AVATAR_WORK", ExistingWorkPolicy.REPLACE, uploadAvatarWork)
|
||||
.enqueue()
|
||||
|
||||
cancelableBag.add(CancelableWork(workManagerProvider.workManager, uploadAvatarWork.id))
|
||||
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
workManagerProvider.workManager.getWorkInfoByIdLiveData(uploadAvatarWork.id)
|
||||
.observeForever { info ->
|
||||
if (info != null && info.state.isFinished) {
|
||||
val result = WorkerParamsFactory.fromData<UploadAvatarWorker.OutputParams>(info.outputData)
|
||||
cancelableBag.add(
|
||||
sendStateEvent(
|
||||
eventType = EventType.STATE_ROOM_AVATAR,
|
||||
body = mapOf("url" to result?.imageUrl!!),
|
||||
callback = callback,
|
||||
stateKey = null
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg")
|
||||
sendStateEvent(
|
||||
eventType = EventType.STATE_ROOM_AVATAR,
|
||||
body = mapOf("url" to response.contentUri),
|
||||
callback = callback,
|
||||
stateKey = null
|
||||
)
|
||||
}
|
||||
return cancelableBag
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user