8.9 KiB
Temporary File Cleanup Analysis
Overview
Analysis of temporary file creation and cleanup patterns across the Amethyst codebase. The goal is to identify opportunities for more aggressive cleanup — deleting temp files as soon as they are no longer needed rather than deferring to cache cleanup.
Temporary File Creation Sites (Android)
| Area | File | Creates | Cleanup | Status |
|---|---|---|---|---|
| Image compression | MediaCompressor.kt:94 |
Temp copy via MediaCompressorFileUtils.from() |
Deleted immediately after compression | FIXED |
| Image metadata strip | MetadataStripper.kt:199 |
stripped_*.jpg in cacheDir |
Deleted eagerly by orchestrator | FIXED |
| Video metadata strip | MetadataStripper.kt:238 |
stripped_video_*.mp4 in cacheDir |
Deleted eagerly by orchestrator | FIXED |
| Audio metadata strip | MetadataStripper.kt:286 |
stripped_audio_*.m4a in cacheDir |
Deleted eagerly by orchestrator | FIXED |
| MP3 metadata strip | MetadataStripper.kt:307,363 |
mp3_input_* + stripped_mp3_* |
Mixed: some immediate, some orchestrator | Already OK |
| File encryption | EncryptFiles.kt:47 |
EncryptFiles*.encrypted in cacheDir |
Deleted eagerly by orchestrator | FIXED |
| URI temp copy | MediaCompressorFileUtils.kt:41 |
Random UUID temp file | Deleted by MediaCompressor after use | FIXED |
| Video compression | VideoCompressionHelper.kt |
Compressed video in app storage | Abandoned file deleted when larger than original | FIXED |
| Voice anonymization | VoiceAnonymizationController.kt:85 |
Distorted voice files | deleteDistortedFiles() explicit |
Already OK |
| Video sharing | ZoomableContentView.kt:905 |
Temp video + sharable copy | Delayed GlobalScope (1 min) | Skipped (intentional) |
| Camera capture | TakePicture.kt:244 |
Camera temp file | System/caller | Skipped (system-managed) |
Temporary File Creation Sites (Desktop)
| Area | File | Creates | Cleanup | Notes |
|---|---|---|---|---|
| Clipboard paste | ClipboardPasteHandler.kt:43 |
clipboard_*.png |
deleteOnExit() only |
Leaks until JVM exit |
| Image compression | DesktopMediaCompressor.kt:42 |
stripped_*.jpg |
deleteOnExit() only |
Leaks until JVM exit |
The Upload Pipeline
The UploadOrchestrator is the central cleanup coordinator. Each intermediate temp file
is now deleted as soon as the next pipeline stage produces its output:
1. MediaCompressorFileUtils.from() --> temp copy of original URI
2. MediaCompressor.compress() --> compressed file; temp copy from #1 deleted immediately
3. MetadataStripper.strip*() --> stripped file; compressed file from #2 deleted immediately
4. (optional) EncryptFiles.encrypt() --> encrypted file; stripped file from #3 deleted immediately
5. Upload to server
6. finally: delete the last remaining intermediate
What Was Fixed
1. MediaCompressor temp file leak (MediaCompressor.kt)
MediaCompressorFileUtils.from()created a temp copy that was never deleted- Now deleted immediately after
Compressor.compress()produces a separate output file - Also cleaned up on compression failure (catch block)
2. Eager pipeline cleanup (UploadOrchestrator.kt)
upload(): compressed file deleted right after stripping producesfinalUriuploadEncrypted(): compressed file deleted after stripping, stripped file deleted after encryption — only the encrypted file survives until after upload- Cancel path also cleans up compressed file via
.also {}block finallyblock now only handles the last surviving intermediate
3. Abandoned compressed video (VideoCompressionHelper.kt)
- When compressed video is larger than original, the original is used instead
- The abandoned compressed file was leaked — now deleted before returning
What Was Skipped
Desktop temp files (out of scope for this change)
ClipboardPasteHandler.ktandDesktopMediaCompressor.ktusedeleteOnExit()- Files persist until JVM process exits — not ideal for a long-running desktop app
DesktopUploadOrchestrator.ktuses bareprocessedFile.delete()with no error handling or logging, diverging from the AndroiddeleteTempUripattern- Reason: User requested Android-only focus for this iteration
Voice anonymization intermediates
VoiceAnonymizationController.deleteDistortedFiles()is already reasonably aggressive- Called explicitly by
ShortNotePostViewModelafter upload - Reason: Already working well, no leak identified
Video sharing delay
ZoomableContentView.ktuses a 1-minute delay before cleanup- Reason: Intentional — receiving app needs time to read the shared file
Camera capture temp files
TakePicture.ktcreates temp files via camera provider- Reason: Managed by the Android system/camera provider, not our responsibility
Exception: Video Sharing
Sharing a video to other Android apps requires the temporary file to remain accessible
for at least 1 minute. The current SHARED_VIDEO_CLEANUP_DELAY_MS delay in
ZoomableContentView.kt handles this correctly and should not be made more aggressive.
Manual Test Plan
Setup
Enable adb logcat filtering to observe cleanup behavior:
adb logcat -s MediaCompressor:* UploadOrchestrator:* VideoCompressionHelper:* MetadataStripper:*
To verify temp files are actually being deleted, monitor the cache directory before and after each test:
adb shell "ls -la /data/data/com.vitorpamplona.amethyst/cache/ | grep -E 'stripped_|EncryptFiles|mp3_input|stripped_mp3|stripped_video|stripped_audio'"
Test 1: Image upload with compression
- Open a new note compose screen
- Attach a JPEG photo from the gallery
- Set compression quality to Medium
- Post the note
- Verify in logcat:
MediaCompressor: Image compression successappearsMediaCompressor: Failed to delete temp filedoes NOT appearUploadOrchestrator: Deleted temp fileappears (for the stripped file after upload)
- Verify in cache dir: No
stripped_*.jpgfiles remain after upload completes
Test 2: Image upload without compression
- Open a new note compose screen
- Attach a JPEG photo from the gallery
- Set compression quality to Uncompressed
- Post the note
- Verify in logcat:
- No
MediaCompressorcompression log appears UploadOrchestrator: Deleted temp fileappears for the stripped file
- No
- Verify in cache dir: No
stripped_*files remain
Test 3: Video upload with compression
- Open a new note compose screen
- Attach a video from the gallery
- Set compression quality to Medium
- Post the note
- Verify in logcat:
VideoCompressionHelper: Compression successappearsUploadOrchestrator: Deleted temp fileappears
- Verify in cache dir: No
stripped_video_*.mp4files remain
Test 4: Video compression produces larger file
- Attach a very small or already-compressed video
- Set compression to Low quality
- Post the note
- Verify in logcat:
VideoCompressionHelper: Compressed file larger than original. Using original.appears- The compressed file is deleted (no orphaned file in cache)
Test 5: Audio/voice message upload
- Record a voice message in a note or reply
- Send it
- Verify in logcat:
UploadOrchestrator: Deleted temp fileappears for the stripped audio
- Verify in cache dir: No
stripped_audio_*.m4afiles remain
Test 6: MP3 upload
- Attach an MP3 file (with ID3 tags) from the file picker
- Post the note
- Verify in logcat:
MetadataStripper: Stripped ID3 tags from MP3appearsUploadOrchestrator: Deleted temp fileappears
- Verify in cache dir: No
mp3_input_*orstripped_mp3_*files remain
Test 7: Encrypted file upload (NIP-44 DM)
- Open a DM conversation
- Attach an image
- Send the message (triggers encrypted upload path)
- Verify in logcat:
- Compressed file is deleted after stripping
- Stripped file is deleted after encryption
- Encrypted file is deleted after upload
- Three separate
UploadOrchestrator: Deleted temp filelog lines appear
- Verify in cache dir: No
stripped_*orEncryptFiles*files remain
Test 8: Upload cancellation
- Open a new note compose screen
- Attach a large image or video
- Cancel the upload while compression or upload is in progress
- Verify in cache dir: No temp files remain from the cancelled upload
Test 9: Video sharing to other apps
- Open a note with a video
- Long-press or use the share button to share the video to another app
- Verify: The receiving app successfully receives the video
- Verify: After ~1 minute, the temp file in the share directory is cleaned up
- This test confirms the 1-minute delay was not broken by our changes
Test 10: Image compression failure fallback
- Attach a GIF or SVG file (compression is skipped for these)
- Post the note
- Verify: Upload succeeds using the original file
- Verify in cache dir: No orphaned temp files