Improved file copy code by using coroutines

This commit is contained in:
Sylvain Berfini 2020-04-09 14:57:14 +02:00
parent de389858ce
commit e1dc8ad8c2
8 changed files with 158 additions and 119 deletions

View file

@ -30,12 +30,14 @@ import android.view.inputmethod.InputMethodManager
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.findNavController
import com.google.android.material.snackbar.Snackbar
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
import kotlinx.coroutines.*
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
@ -147,11 +149,15 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
private fun handleIntentParams(intent: Intent) {
when (intent.action) {
Intent.ACTION_SEND -> {
lifecycleScope.launch {
handleSendImage(intent)
}
}
Intent.ACTION_SEND_MULTIPLE -> {
lifecycleScope.launch {
handleSendMultipleImages(intent)
}
}
Intent.ACTION_VIEW -> {
if (intent.type == AppUtils.getString(R.string.linphone_address_mime_type)) {
val contactUri = intent.data
@ -218,14 +224,19 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
}
}
private fun handleSendImage(intent: Intent) {
private suspend fun handleSendImage(intent: Intent) {
(intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri)?.let {
val list = arrayListOf<String>()
val path = FileUtils.getFilePath(this, it)
coroutineScope {
val deferred = async {
FileUtils.getFilePath(this@MainActivity, it)
}
val path = deferred.await()
if (path != null) {
list.add(path)
Log.i("[Main Activity] Found single file to share: $path")
}
}
sharedViewModel.filesToShare.value = list
val deepLink = "linphone-android://chat/"
@ -234,15 +245,21 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
}
}
private fun handleSendMultipleImages(intent: Intent) {
private suspend fun handleSendMultipleImages(intent: Intent) {
intent.getParcelableArrayListExtra<Parcelable>(Intent.EXTRA_STREAM)?.let {
val list = arrayListOf<String>()
coroutineScope {
val deferred = arrayListOf<Deferred<String?>>()
for (parcelable in it) {
val uri = parcelable as Uri
val path = FileUtils.getFilePath(this, uri)
deferred.add(async { FileUtils.getFilePath(this@MainActivity, uri) })
}
val paths = deferred.awaitAll()
for (path in paths) {
Log.i("[Main Activity] Found file to share: $path")
if (path != null) list.add(path)
}
}
sharedViewModel.filesToShare.value = list
val deepLink = "linphone-android://chat/"

View file

@ -35,12 +35,14 @@ import androidx.appcompat.view.menu.MenuPopupHelper
import androidx.core.content.FileProvider
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import java.io.File
import kotlinx.android.synthetic.main.tabs_fragment.*
import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
@ -299,8 +301,8 @@ class DetailChatRoomFragment : MasterFragment() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
lifecycleScope.launch {
var fileToUploadPath: String? = null
val temporaryFileUploadPath = chatSendingViewModel.temporaryFileUploadPath
if (temporaryFileUploadPath != null) {
if (data != null) {
@ -336,6 +338,7 @@ class DetailChatRoomFragment : MasterFragment() {
}
}
}
}
private fun enterEditionMode() {
listSelectionViewModel.isEditionEnabled.value = true

View file

@ -22,6 +22,8 @@ package org.linphone.activities.main.chat.viewmodels
import android.os.CountDownTimer
import android.text.Spanned
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
@ -205,6 +207,7 @@ class ChatMessageViewModel(
}
private fun addContentToMediaStore(content: Content) {
viewModelScope.launch {
when (content.type) {
"image" -> {
if (Compatibility.addImageToMediaStore(coreContext.context, content)) {
@ -233,3 +236,4 @@ class ChatMessageViewModel(
}
}
}
}

View file

@ -31,8 +31,10 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import java.io.File
import kotlinx.coroutines.launch
import org.linphone.R
import org.linphone.activities.main.MainActivity
import org.linphone.activities.main.contact.viewmodels.*
@ -130,6 +132,7 @@ class ContactEditorFragment : Fragment() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
lifecycleScope.launch {
var fileToUploadPath: String? = null
val temporaryFileUploadPath = temporaryPicturePath
@ -167,6 +170,7 @@ class ContactEditorFragment : Fragment() {
}
}
}
}
private fun pickFile() {
val cameraIntents = ArrayList<Intent>()

View file

@ -57,7 +57,7 @@ class Api21Compatibility {
vibrator.vibrate(pattern, 1)
}
fun addImageToMediaStore(context: Context, content: Content): Boolean {
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath
val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_PICTURES}/$appName"
@ -88,7 +88,7 @@ class Api21Compatibility {
return copyOk
}
fun addVideoToMediaStore(context: Context, content: Content): Boolean {
suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath
val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_MOVIES}/$appName"
@ -120,7 +120,7 @@ class Api21Compatibility {
return copyOk
}
fun addAudioToMediaStore(context: Context, content: Content): Boolean {
suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath
val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_MUSIC}/$appName"

View file

@ -33,7 +33,7 @@ import org.linphone.utils.FileUtils
@TargetApi(29)
class Api29Compatibility {
companion object {
fun addImageToMediaStore(context: Context, content: Content): Boolean {
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath
val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_PICTURES}/$appName"
@ -70,7 +70,7 @@ class Api29Compatibility {
return copyOk
}
fun addVideoToMediaStore(context: Context, content: Content): Boolean {
suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath
val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_MOVIES}/$appName"
@ -108,7 +108,7 @@ class Api29Compatibility {
return copyOk
}
fun addAudioToMediaStore(context: Context, content: Content): Boolean {
suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath
val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_MUSIC}/$appName"

View file

@ -115,21 +115,21 @@ class Compatibility {
// TODO Use removeLongLivedShortcuts() starting Android R (API 30)
}
fun addImageToMediaStore(context: Context, content: Content): Boolean {
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) {
return Api29Compatibility.addImageToMediaStore(context, content)
}
return Api21Compatibility.addImageToMediaStore(context, content)
}
fun addVideoToMediaStore(context: Context, content: Content): Boolean {
suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean {
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) {
return Api29Compatibility.addVideoToMediaStore(context, content)
}
return Api21Compatibility.addVideoToMediaStore(context, content)
}
fun addAudioToMediaStore(context: Context, content: Content): Boolean {
suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean {
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) {
return Api29Compatibility.addAudioToMediaStore(context, content)
}

View file

@ -26,6 +26,10 @@ import android.provider.OpenableColumns
import java.io.*
import java.text.SimpleDateFormat
import java.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withContext
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.tools.Log
@ -109,7 +113,7 @@ class FileUtils {
}
}
fun getFilePath(context: Context, uri: Uri): String? {
suspend fun getFilePath(context: Context, uri: Uri): String? {
var result: String? = null
val name: String = getNameFromUri(uri, context)
@ -123,13 +127,16 @@ class FileUtils {
" to local file " +
localFile.absolutePath
)
if (copyToFile(remoteFile, localFile)) {
coroutineScope {
val deferred = async { copyToFile(remoteFile, localFile) }
if (deferred.await()) {
Log.i("[File Utils] Copy successful")
result = localFile.absolutePath
} else {
Log.e("[File Utils] Copy failed")
}
remoteFile?.close()
}
} catch (e: IOException) {
Log.e("[File Utils] getFilePath exception: ", e)
}
@ -158,7 +165,7 @@ class FileUtils {
return name
}
fun copyFileTo(filePath: String, outputStream: OutputStream?): Boolean {
suspend fun copyFileTo(filePath: String, outputStream: OutputStream?): Boolean {
if (outputStream == null) {
Log.e("[File Utils] Can't copy file $filePath to given null output stream")
return false
@ -171,12 +178,14 @@ class FileUtils {
}
try {
withContext(Dispatchers.IO) {
val inputStream = FileInputStream(file)
val buffer = ByteArray(4096)
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } >= 0) {
outputStream.write(buffer, 0, bytesRead)
}
}
return true
} catch (e: IOException) {
Log.e("[File Utils] copyFileTo exception: $e")
@ -184,9 +193,10 @@ class FileUtils {
return false
}
private fun copyToFile(inputStream: InputStream?, destFile: File?): Boolean {
private suspend fun copyToFile(inputStream: InputStream?, destFile: File?): Boolean {
if (inputStream == null || destFile == null) return false
try {
withContext(Dispatchers.IO) {
FileOutputStream(destFile).use { out ->
val buffer = ByteArray(4096)
var bytesRead: Int
@ -194,6 +204,7 @@ class FileUtils {
out.write(buffer, 0, bytesRead)
}
}
}
return true
} catch (e: IOException) {
Log.e("[File Utils] copyToFile exception: $e")