BayernMessenger/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt

122 lines
5.3 KiB
Kotlin

/*
* Copyright 2019 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
import android.content.Context
import android.os.Environment
import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.md5
import im.vector.matrix.android.internal.util.writeToFile
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import timber.log.Timber
import java.io.File
import java.io.IOException
import javax.inject.Inject
internal class DefaultFileService @Inject constructor(private val context: Context,
private val sessionParams: SessionParams,
private val contentUrlResolver: ContentUrlResolver,
private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService {
val okHttpClient = OkHttpClient()
/**
* Download file in the cache folder, and eventually decrypt it
* TODO implement clear file, to delete "MF"
*/
override fun downloadFile(downloadMode: FileService.DownloadMode,
id: String,
fileName: String,
url: String?,
elementToDecrypt: ElementToDecrypt?,
callback: MatrixCallback<File>) {
GlobalScope.launch(coroutineDispatchers.main) {
withContext(coroutineDispatchers.io) {
Try {
val folder = getFolder(downloadMode, id)
File(folder, fileName)
}.flatMap { destFile ->
if (!destFile.exists() || downloadMode == FileService.DownloadMode.TO_EXPORT) {
Try {
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
val request = Request.Builder()
.url(resolvedUrl)
.build()
val response = okHttpClient.newCall(request).execute()
val inputStream = response.body()?.byteStream()
Timber.v("Response size ${response.body()?.contentLength()} - Stream available: ${inputStream?.available()}")
if (!response.isSuccessful
|| inputStream == null) {
throw IOException()
}
if (elementToDecrypt != null) {
Timber.v("## decrypt file")
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) ?: throw IllegalStateException("Decryption error")
} else {
inputStream
}
}
.map { inputStream ->
writeToFile(inputStream, destFile)
destFile
}
} else {
Try.just(destFile)
}
}
}
.foldToCallback(callback)
}
}
private fun getFolder(downloadMode: FileService.DownloadMode, id: String): File {
return when (downloadMode) {
FileService.DownloadMode.FOR_INTERNAL_USE -> {
// Create dir tree (MF stands for Matrix File):
// <cache>/MF/<md5(userId)>/<md5(id)>/
val tmpFolderRoot = File(context.cacheDir, "MF")
val tmpFolderUser = File(tmpFolderRoot, sessionParams.credentials.userId.md5())
File(tmpFolderUser, id.md5())
}
FileService.DownloadMode.TO_EXPORT -> {
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
}
}
.also { folder ->
if (!folder.exists()) {
folder.mkdirs()
}
}
}
}