Download file - WIP

This commit is contained in:
Benoit Marty
2019-07-08 19:06:17 +02:00
parent 12bd85e0a9
commit a07f8b615e
14 changed files with 193 additions and 62 deletions

View File

@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.api.session.room.RoomDirectoryService
@ -46,6 +47,7 @@ interface Session :
CacheService,
SignOutService,
FilterService,
FileService,
PushRuleService,
PushersService {

View File

@ -105,13 +105,6 @@ interface CryptoService {
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
/**
* Decrypt a file.
* Result will be a decrypted file, stored in the cache folder. id parameter will be used to create a sub folder to avoid name collision.
* You can pass the eventId
*/
fun decryptFile(id: String, filename: String, url: String, elementToDecrypt: ElementToDecrypt, callback: MatrixCallback<File>)
fun getEncryptionAlgorithm(roomId: String): String?
fun shouldEncryptForInvitedMembers(roomId: String): Boolean

View File

@ -0,0 +1,52 @@
/*
* 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.api.session.file
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import java.io.File
/**
* This interface defines methods to get files.
*/
interface FileService {
enum class DownloadMode {
/**
* Download file in external storage
*/
TO_EXPORT,
/**
* Download file in cache
*/
FOR_INTERNAL_USE
}
/**
* Download a file.
* Result will be a decrypted file, stored in the cache folder. id parameter will be used to create a sub folder to avoid name collision.
* You can pass the eventId
*/
fun downloadFile(
downloadMode: DownloadMode,
id: String,
fileName: String,
url: String?,
elementToDecrypt: ElementToDecrypt?,
callback: MatrixCallback<File>)
}

View File

@ -114,8 +114,6 @@ internal class CryptoManager @Inject constructor(
private val keysBackup: KeysBackup,
//
private val objectSigner: ObjectSigner,
// File decryptor
private val fileDecryptor: FileDecryptor,
//
private val oneTimeKeysUploader: OneTimeKeysUploader,
//
@ -611,10 +609,6 @@ internal class CryptoManager @Inject constructor(
}
}
override fun decryptFile(id: String, filename: String, url: String, elementToDecrypt: ElementToDecrypt, callback: MatrixCallback<File>) {
fileDecryptor.decryptFile(id, filename, url, elementToDecrypt, callback)
}
/**
* Decrypt an event
*

View File

@ -14,17 +14,18 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
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.session.SessionScope
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.md5
import im.vector.matrix.android.internal.util.writeToFile
@ -38,33 +39,29 @@ import java.io.File
import java.io.IOException
import javax.inject.Inject
@SessionScope
internal class FileDecryptor @Inject constructor(private val context: Context,
private val sessionParams: SessionParams,
private val contentUrlResolver: ContentUrlResolver,
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
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()
fun decryptFile(id: String,
fileName: String,
url: String,
elementToDecrypt: ElementToDecrypt,
callback: MatrixCallback<File>) {
/**
* 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 {
// Create dir tree:
// <cache>/DF/<md5(userId)>/<md5(id)>/
val tmpFolderRoot = File(context.cacheDir, "DF")
val tmpFolderUser = File(tmpFolderRoot, sessionParams.credentials.userId.md5())
val tmpFolder = File(tmpFolderUser, id.md5())
val folder = getFolder(downloadMode, id)
if (!tmpFolder.exists()) {
tmpFolder.mkdirs()
}
File(tmpFolder, fileName)
File(folder, fileName)
}.map { destFile ->
if (!destFile.exists()) {
Try {
@ -79,11 +76,16 @@ internal class FileDecryptor @Inject constructor(private val context: Context,
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) {
if (!response.isSuccessful
|| inputStream == null) {
throw IOException()
}
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) ?: throw IllegalStateException("Decryption error")
if (elementToDecrypt != null) {
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) ?: throw IllegalStateException("Decryption error")
} else {
inputStream
}
}
.map { inputStream ->
writeToFile(inputStream, destFile)
@ -96,4 +98,24 @@ internal class FileDecryptor @Inject constructor(private val context: Context,
.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()
}
}
}
}

View File

@ -21,7 +21,6 @@ import android.os.Looper
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.PushRuleService
@ -30,6 +29,7 @@ import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.api.session.room.RoomDirectoryService
@ -61,20 +61,22 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
private val pushRuleService: PushRuleService,
private val pushersService: PushersService,
private val cryptoService: CryptoManager,
private val fileService: FileService,
private val syncThread: SyncThread,
private val contentUrlResolver: ContentUrlResolver,
private val contentUploadProgressTracker: ContentUploadStateTracker)
: Session,
RoomService by roomService,
RoomDirectoryService by roomDirectoryService,
GroupService by groupService,
UserService by userService,
CryptoService by cryptoService,
CacheService by cacheService,
SignOutService by signOutService,
FilterService by filterService,
PushRuleService by pushRuleService,
PushersService by pushersService {
RoomService by roomService,
RoomDirectoryService by roomDirectoryService,
GroupService by groupService,
UserService by userService,
CryptoService by cryptoService,
CacheService by cacheService,
SignOutService by signOutService,
FilterService by filterService,
FileService by fileService,
PushRuleService by pushRuleService,
PushersService by pushersService {
private var isOpen = false

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room
import dagger.Binds
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.room.RoomDirectoryService
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.members.MembershipService
@ -27,6 +28,7 @@ import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.api.session.room.state.StateService
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.internal.session.DefaultFileService
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
import im.vector.matrix.android.internal.session.room.create.DefaultCreateRoomTask
@ -138,4 +140,6 @@ internal abstract class RoomModule {
@Binds
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService
@Binds
abstract fun bindFileService(fileService: DefaultFileService): FileService
}