Rework how the app handles the removal of plain copy of encrypted files when VFS is enabled

This commit is contained in:
Sylvain Berfini 2023-04-03 09:50:54 +02:00
parent 31e30e6214
commit 09d5868820
10 changed files with 42 additions and 48 deletions

View file

@ -24,6 +24,9 @@ Group changes to describe their impact on the project, as follows:
- Replaced voice recordings file name by localized placeholder text, like for video conferences invitations - Replaced voice recordings file name by localized placeholder text, like for video conferences invitations
- Removed jetifier as it is not needed - Removed jetifier as it is not needed
### Fixed
- Plain copy of encrypted files (when VFS is enabled) not cleaned
## [5.0.8] - 2023-03-20 ## [5.0.8] - 2023-03-20
### Fixed ### Fixed

View file

@ -209,7 +209,7 @@ class ChatMessageContentData(
private fun deletePlainFilePath() { private fun deletePlainFilePath() {
val path = filePath.value.orEmpty() val path = filePath.value.orEmpty()
if (path.isNotEmpty() && isFileEncrypted) { if (path.isNotEmpty() && isFileEncrypted) {
Log.i("[Content] Deleting file used for preview: $path") Log.i("[Content] [VFS] Deleting file used for preview: $path")
FileUtils.deleteFile(path) FileUtils.deleteFile(path)
filePath.value = "" filePath.value = ""
} }
@ -247,7 +247,7 @@ class ChatMessageContentData(
if (content.isFile || (content.isFileTransfer && chatMessage.isOutgoing)) { if (content.isFile || (content.isFileTransfer && chatMessage.isOutgoing)) {
val path = if (isFileEncrypted) { val path = if (isFileEncrypted) {
Log.i("[Content] Content is encrypted, requesting plain file path") Log.i("[Content] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]")
content.exportPlainFile() content.exportPlainFile()
} else { } else {
content.filePath ?: "" content.filePath ?: ""

View file

@ -1204,16 +1204,10 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
{ {
dialog.dismiss() dialog.dismiss()
lifecycleScope.launch { lifecycleScope.launch {
Log.w("[Chat Room] Content is encrypted, requesting plain file path") Log.i("[Chat Room] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]")
val plainFilePath = content.exportPlainFile() val plainFilePath = content.exportPlainFile()
Log.i("[Chat Room] Making a copy of [$plainFilePath] to the cache directory before exporting it") if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) {
val cacheCopyPath = FileUtils.copyFileToCache(plainFilePath) showDialogToSuggestOpeningFileAsText()
if (cacheCopyPath != null) {
Log.i("[Chat Room] Cache copy has been made: $cacheCopyPath")
FileUtils.deleteFile(plainFilePath)
if (!FileUtils.openFileInThirdPartyApp(requireActivity(), cacheCopyPath)) {
showDialogToSuggestOpeningFileAsText()
}
} }
} }
}, },

View file

@ -33,7 +33,7 @@ open class FileViewerViewModel(val content: Content) : ViewModel() {
init { init {
filePath = if (deleteAfterUse) { filePath = if (deleteAfterUse) {
Log.i("[File Viewer] Content is encrypted, requesting plain file path") Log.i("[File Viewer] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]")
content.exportPlainFile() content.exportPlainFile()
} else { } else {
content.filePath.orEmpty() content.filePath.orEmpty()
@ -42,7 +42,7 @@ open class FileViewerViewModel(val content: Content) : ViewModel() {
override fun onCleared() { override fun onCleared() {
if (deleteAfterUse) { if (deleteAfterUse) {
Log.i("[File Viewer] Deleting temporary plain file: $filePath") Log.i("[File Viewer] [VFS] Deleting temporary plain file [$filePath]")
FileUtils.deleteFile(filePath) FileUtils.deleteFile(filePath)
} }

View file

@ -168,7 +168,7 @@ class Api23Compatibility {
val isContentEncrypted = content.isFileEncrypted val isContentEncrypted = content.isFileEncrypted
val filePath = if (content.isFileEncrypted) { val filePath = if (content.isFileEncrypted) {
val plainFilePath = content.exportPlainFile().orEmpty() val plainFilePath = content.exportPlainFile().orEmpty()
Log.w("[Media Store] Content is encrypted, plain file path is: $plainFilePath") Log.i("[Media Store] [VFS] Content is encrypted, plain file path is: $plainFilePath")
plainFilePath plainFilePath
} else content.filePath } else content.filePath

View file

@ -125,7 +125,7 @@ class Api29Compatibility {
val isContentEncrypted = content.isFileEncrypted val isContentEncrypted = content.isFileEncrypted
val filePath = if (content.isFileEncrypted) { val filePath = if (content.isFileEncrypted) {
val plainFilePath = content.exportPlainFile().orEmpty() val plainFilePath = content.exportPlainFile().orEmpty()
Log.w("[Media Store] Content is encrypted, plain file path is: $plainFilePath") Log.i("[Media Store] [VFS] Content is encrypted, plain file path is: $plainFilePath")
plainFilePath plainFilePath
} else content.filePath } else content.filePath

View file

@ -358,6 +358,10 @@ class CoreContext(
collator.strength = Collator.NO_DECOMPOSITION collator.strength = Collator.NO_DECOMPOSITION
if (corePreferences.vfsEnabled) { if (corePreferences.vfsEnabled) {
val notClearedCount = FileUtils.countFilesInDirectory(corePreferences.vfsCachePath)
if (notClearedCount != 0) {
Log.w("[Context] [VFS] There are [$notClearedCount] plain files not cleared from previous app lifetime, removing them now")
}
FileUtils.clearExistingPlainFiles() FileUtils.clearExistingPlainFiles()
} }

View file

@ -661,6 +661,9 @@ class CorePreferences constructor(private val context: Context) {
val staticPicturePath: String val staticPicturePath: String
get() = context.filesDir.absolutePath + "/share/images/nowebcamcif.jpg" get() = context.filesDir.absolutePath + "/share/images/nowebcamcif.jpg"
val vfsCachePath: String
get() = context.cacheDir.absolutePath + "/evfs/"
fun copyAssetsFromPackage() { fun copyAssetsFromPackage() {
copy("linphonerc_default", configPath) copy("linphonerc_default", configPath)
copy("linphonerc_factory", factoryConfigPath, true) copy("linphonerc_factory", factoryConfigPath, true)

View file

@ -324,12 +324,12 @@ fun loadRoundImageWithCoil(imageView: ImageView, path: String?) {
@BindingAdapter("coil") @BindingAdapter("coil")
fun loadImageWithCoil(imageView: ImageView, path: String?) { fun loadImageWithCoil(imageView: ImageView, path: String?) {
if (path != null && path.isNotEmpty() && FileUtils.isExtensionImage(path)) { if (path != null && path.isNotEmpty() && FileUtils.isExtensionImage(path)) {
if (corePreferences.vfsEnabled && path.endsWith(FileUtils.VFS_PLAIN_FILE_EXTENSION)) { if (corePreferences.vfsEnabled && path.startsWith(corePreferences.vfsCachePath)) {
imageView.load(path) { imageView.load(path) {
diskCachePolicy(CachePolicy.DISABLED) diskCachePolicy(CachePolicy.DISABLED)
listener( listener(
onError = { _, result -> onError = { _, result ->
Log.e("[Data Binding] [Coil] Error loading [$path]: ${result.throwable}") Log.e("[Data Binding] [VFS] [Coil] Error loading [$path]: ${result.throwable}")
} }
) )
} }

View file

@ -37,13 +37,12 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R import org.linphone.R
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
class FileUtils { class FileUtils {
companion object { companion object {
const val VFS_PLAIN_FILE_EXTENSION = ".bctbx_evfs_plain"
fun getNameFromFilePath(filePath: String): String { fun getNameFromFilePath(filePath: String): String {
var name = filePath var name = filePath
val i = filePath.lastIndexOf('/') val i = filePath.lastIndexOf('/')
@ -54,15 +53,11 @@ class FileUtils {
} }
fun getExtensionFromFileName(fileName: String): String { fun getExtensionFromFileName(fileName: String): String {
val realFileName = if (fileName.endsWith(VFS_PLAIN_FILE_EXTENSION)) { var extension = MimeTypeMap.getFileExtensionFromUrl(fileName)
fileName.substring(0, fileName.length - VFS_PLAIN_FILE_EXTENSION.length)
} else fileName
var extension = MimeTypeMap.getFileExtensionFromUrl(realFileName)
if (extension.isNullOrEmpty()) { if (extension.isNullOrEmpty()) {
val i = realFileName.lastIndexOf('.') val i = fileName.lastIndexOf('.')
if (i > 0) { if (i > 0) {
extension = realFileName.substring(i + 1) extension = fileName.substring(i + 1)
} }
} }
@ -102,21 +97,10 @@ class FileUtils {
} }
fun clearExistingPlainFiles() { fun clearExistingPlainFiles() {
for (file in coreContext.context.filesDir.listFiles().orEmpty()) { val dir = File(corePreferences.vfsCachePath)
if (file.path.endsWith(VFS_PLAIN_FILE_EXTENSION)) { if (dir.exists()) {
Log.w("[File Utils] Found forgotten plain file: ${file.path}, deleting it") for (file in dir.listFiles().orEmpty()) {
deleteFile(file.path) Log.w("[File Utils] [VFS] Found forgotten plain file [${file.path}], deleting it")
}
}
for (file in coreContext.context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.listFiles().orEmpty()) {
if (file.path.endsWith(VFS_PLAIN_FILE_EXTENSION)) {
Log.w("[File Utils] Found forgotten plain file: ${file.path}, deleting it")
deleteFile(file.path)
}
}
for (file in coreContext.context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.listFiles().orEmpty()) {
if (file.path.endsWith(VFS_PLAIN_FILE_EXTENSION)) {
Log.w("[File Utils] Found forgotten plain file: ${file.path}, deleting it")
deleteFile(file.path) deleteFile(file.path)
} }
} }
@ -144,14 +128,10 @@ class FileUtils {
val path = coreContext.context.cacheDir val path = coreContext.context.cacheDir
Log.i("[File Utils] Cache directory is: $path") Log.i("[File Utils] Cache directory is: $path")
val realFileName = if (fileName.endsWith(VFS_PLAIN_FILE_EXTENSION)) { var file = File(path, fileName)
fileName.substring(0, fileName.length - VFS_PLAIN_FILE_EXTENSION.length)
} else fileName
var file = File(path, realFileName)
var prefix = 1 var prefix = 1
while (file.exists()) { while (file.exists()) {
file = File(path, prefix.toString() + "_" + realFileName) file = File(path, prefix.toString() + "_" + fileName)
Log.w("[File Utils] File with that name already exists, renamed to ${file.name}") Log.w("[File Utils] File with that name already exists, renamed to ${file.name}")
prefix += 1 prefix += 1
} }
@ -299,7 +279,9 @@ class FileUtils {
} else { } else {
Log.e("[File Utils] Copy failed") Log.e("[File Utils] Copy failed")
} }
remoteFile?.close() withContext(Dispatchers.IO) {
remoteFile?.close()
}
} }
} catch (e: IOException) { } catch (e: IOException) {
Log.e("[File Utils] getFilePath exception: ", e) Log.e("[File Utils] getFilePath exception: ", e)
@ -535,5 +517,13 @@ class FileUtils {
outStream.flush() outStream.flush()
outStream.close() outStream.close()
} }
fun countFilesInDirectory(path: String): Int {
val dir = File(path)
if (dir.exists()) {
return dir.listFiles().orEmpty().size
}
return -1
}
} }
} }