Skip to content

Commit d53457e

Browse files
authored
Make sure we clean up the pre-processed and uploaded media (#5039)
* Add `MediaSender.cleanUp()` and `MediaPreProcessor.cleanUp()` methods: this will remove the temporary files created when pre-processing media before sending them. * Make sure we clean up also the previous temporary media. * Fix the condition for the custom back handler in the attachments preview screen * Tests: check the clean up is performed when needed
1 parent 9f11884 commit d53457e

File tree

7 files changed

+65
-8
lines changed

7 files changed

+65
-8
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,17 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
131131
dismissAfterSend = !useSendQueue,
132132
replyParameters = null,
133133
)
134+
135+
// Clean up the pre-processed media after it's been sent
136+
mediaSender.cleanUp()
134137
}
135138
}
136139
}
137140
AttachmentsPreviewEvents.CancelAndDismiss -> {
138141
// Cancel media preprocessing and sending
139142
preprocessMediaJob?.cancel()
143+
// If we couldn't send the pre-processed media, remove it
144+
mediaSender.cleanUp()
140145
ongoingSendAttachmentJob.value?.cancel()
141146

142147
// Dismiss the screen

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ fun AttachmentsPreviewView(
6666
state.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState)
6767
}
6868

69-
BackHandler(enabled = state.sendActionState !is SendActionState.Sending) {
69+
BackHandler(enabled = state.sendActionState !is SendActionState.Sending.Uploading) {
7070
postCancel()
7171
}
7272

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,10 @@ class AttachmentsPreviewPresenterTest {
123123
},
124124
)
125125
val onDoneListener = lambdaRecorder<Unit> { }
126+
val mediaPreProcessor = FakeMediaPreProcessor()
126127
val presenter = createAttachmentsPreviewPresenter(
127128
room = room,
129+
mediaPreProcessor = mediaPreProcessor,
128130
onDoneListener = { onDoneListener() },
129131
)
130132
moleculeFlow(RecompositionMode.Immediate) {
@@ -143,6 +145,7 @@ class AttachmentsPreviewPresenterTest {
143145
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
144146
sendFileResult.assertions().isCalledOnce()
145147
onDoneListener.assertions().isCalledOnce()
148+
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
146149
}
147150
}
148151

@@ -159,11 +162,10 @@ class AttachmentsPreviewPresenterTest {
159162
)
160163
val onDoneListener = lambdaRecorder<Unit> { }
161164
val processLatch = CompletableDeferred<Unit>()
165+
val mediaPreProcessor = FakeMediaPreProcessor(processLatch)
162166
val presenter = createAttachmentsPreviewPresenter(
163167
room = room,
164-
mediaPreProcessor = FakeMediaPreProcessor(
165-
processLatch = processLatch,
166-
),
168+
mediaPreProcessor = mediaPreProcessor,
167169
onDoneListener = { onDoneListener() },
168170
)
169171
moleculeFlow(RecompositionMode.Immediate) {
@@ -181,6 +183,7 @@ class AttachmentsPreviewPresenterTest {
181183
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
182184
sendFileResult.assertions().isCalledOnce()
183185
onDoneListener.assertions().isCalledOnce()
186+
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
184187
}
185188
}
186189

@@ -197,11 +200,10 @@ class AttachmentsPreviewPresenterTest {
197200
)
198201
val onDoneListener = lambdaRecorder<Unit> { }
199202
val processLatch = CompletableDeferred<Unit>()
203+
val mediaPreProcessor = FakeMediaPreProcessor(processLatch)
200204
val presenter = createAttachmentsPreviewPresenter(
201205
room = room,
202-
mediaPreProcessor = FakeMediaPreProcessor(
203-
processLatch = processLatch,
204-
),
206+
mediaPreProcessor = mediaPreProcessor,
205207
onDoneListener = { onDoneListener() },
206208
)
207209
moleculeFlow(RecompositionMode.Immediate) {
@@ -219,6 +221,7 @@ class AttachmentsPreviewPresenterTest {
219221
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
220222
sendFileResult.assertions().isCalledOnce()
221223
onDoneListener.assertions().isCalledOnce()
224+
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
222225
}
223226
}
224227

@@ -279,7 +282,9 @@ class AttachmentsPreviewPresenterTest {
279282
fun `present - cancel scenario`() = runTest {
280283
val onDoneListener = lambdaRecorder<Unit> { }
281284
val deleteCallback = lambdaRecorder<Uri?, Unit> {}
285+
val mediaPreProcessor = FakeMediaPreProcessor()
282286
val presenter = createAttachmentsPreviewPresenter(
287+
mediaPreProcessor = mediaPreProcessor,
283288
temporaryUriDeleter = FakeTemporaryUriDeleter(deleteCallback),
284289
onDoneListener = { onDoneListener() },
285290
)
@@ -293,6 +298,7 @@ class AttachmentsPreviewPresenterTest {
293298
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
294299
deleteCallback.assertions().isCalledOnce()
295300
onDoneListener.assertions().isCalledOnce()
301+
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
296302
}
297303
}
298304

libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaPreProcessor.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,10 @@ interface MediaPreProcessor {
2222
compressIfPossible: Boolean,
2323
): Result<MediaUploadInfo>
2424

25+
/**
26+
* Clean up any temporary files or resources used during the media processing.
27+
*/
28+
fun cleanUp()
29+
2530
data class Failure(override val cause: Throwable?) : Exception(cause)
2631
}

libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,9 @@ class MediaSender @Inject constructor(
200200
uploadHandler.await()
201201
}
202202
}
203+
204+
/**
205+
* Clean up any temporary files or resources used during the media processing.
206+
*/
207+
fun cleanUp() = preProcessor.cleanUp()
203208
}

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import kotlinx.coroutines.withContext
4141
import timber.log.Timber
4242
import java.io.File
4343
import java.io.InputStream
44+
import java.util.UUID
4445
import javax.inject.Inject
4546
import kotlin.time.Duration
4647
import kotlin.time.Duration.Companion.milliseconds
@@ -68,6 +69,9 @@ class AndroidMediaPreProcessor @Inject constructor(
6869

6970
private val contentResolver = context.contentResolver
7071

72+
private val cacheDir = context.cacheDir
73+
private val baseTmpFileDir = File(cacheDir, "uploads")
74+
7175
override suspend fun process(
7276
uri: Uri,
7377
mimeType: String,
@@ -100,6 +104,28 @@ class AndroidMediaPreProcessor @Inject constructor(
100104
}
101105
}.mapFailure { MediaPreProcessor.Failure(it) }
102106

107+
override fun cleanUp() {
108+
// Clear temporary files created in older versions of the app
109+
cacheDir.listFiles()?.onEach { file ->
110+
if (file.isFile) {
111+
val nameWithoutExtension = file.nameWithoutExtension
112+
// UUIDs are 36 characters long, so we check if we can take those 36 characters
113+
val nameWithoutExtensionAndRandom = if (nameWithoutExtension.length > 36) {
114+
nameWithoutExtension.substring(0, 36)
115+
} else {
116+
// Not a temp file
117+
return@onEach
118+
}
119+
val isUUID = tryOrNull { UUID.fromString(nameWithoutExtensionAndRandom) } != null
120+
if (isUUID && file.extension.isNotEmpty()) {
121+
file.delete()
122+
}
123+
}
124+
}
125+
// Clear temporary files created by this pre-processor in the separate uploads directory
126+
baseTmpFileDir.listFiles()?.onEach { it.delete() }
127+
}
128+
103129
private suspend fun processFile(uri: Uri, mimeType: String): MediaUploadInfo {
104130
val file = copyToTmpFile(uri)
105131
val info = FileInfo(
@@ -280,7 +306,10 @@ class AndroidMediaPreProcessor @Inject constructor(
280306
private suspend fun createTmpFileWithInput(inputStream: InputStream): File? {
281307
return withContext(coroutineDispatchers.io) {
282308
tryOrNull {
283-
val tmpFile = context.createTmpFile()
309+
if (!baseTmpFileDir.exists()) {
310+
baseTmpFileDir.mkdirs()
311+
}
312+
val tmpFile = context.createTmpFile(baseTmpFileDir)
284313
tmpFile.outputStream().use { inputStream.copyTo(it) }
285314
tmpFile
286315
}

libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaPreProcessor.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class FakeMediaPreProcessor(
2626
var processCallCount = 0
2727
private set
2828

29+
var cleanUpCallCount = 0
30+
private set
31+
2932
private var result: Result<MediaUploadInfo> = Result.success(
3033
MediaUploadInfo.AnyFile(
3134
File("test"),
@@ -108,4 +111,8 @@ class FakeMediaPreProcessor(
108111
)
109112
)
110113
}
114+
115+
override fun cleanUp() {
116+
cleanUpCallCount += 1
117+
}
111118
}

0 commit comments

Comments
 (0)