diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt index 4dc9fc0305..b95595ed23 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt @@ -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) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt index 1153b39b0a..6ee508b02a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt @@ -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() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadAvatarWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadAvatarWorker.kt deleted file mode 100644 index 8becdfa05c..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadAvatarWorker.kt +++ /dev/null @@ -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(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)) - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt index 7d510d2510..c76426b2cd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt @@ -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>): 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): Cancelable { - val cancelableBag = CancelableBag() - val workerParams = UploadAvatarWorker.Params(sessionId, newAvatarUri, fileName) - val workerData = WorkerParamsFactory.toData(workerParams) - - val uploadAvatarWork = workManagerProvider.matrixOneTimeWorkRequestBuilder() - .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(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>): Cancelable { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt index 7cc061d0ae..14465cf631 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt @@ -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): Cancelable { - val cancelableBag = CancelableBag() - val workerParams = UploadAvatarWorker.Params(sessionId, avatarUri, fileName) - val workerData = WorkerParamsFactory.toData(workerParams) - - val uploadAvatarWork = workManagerProvider.matrixOneTimeWorkRequestBuilder() - .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(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 } }