Skip to content

Commit 8de676d

Browse files
committed
Fix: Save all option overrides attachment files
1 parent 60091bc commit 8de676d

3 files changed

Lines changed: 219 additions & 204 deletions

File tree

legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java

Lines changed: 0 additions & 196 deletions
This file was deleted.
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package com.fsck.k9.ui.messageview
2+
3+
import android.content.ActivityNotFoundException
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.net.Uri
7+
import android.provider.DocumentsContract
8+
import android.widget.Toast
9+
import androidx.annotation.WorkerThread
10+
import app.k9mail.legacy.message.controller.SimpleMessagingListener
11+
import com.fsck.k9.Preferences.Companion.getPreferences
12+
import com.fsck.k9.controller.MessagingController
13+
import com.fsck.k9.mail.Message
14+
import com.fsck.k9.mail.Part
15+
import com.fsck.k9.mailstore.AttachmentViewInfo
16+
import com.fsck.k9.mailstore.LocalPart
17+
import com.fsck.k9.provider.AttachmentTempFileProvider
18+
import com.fsck.k9.ui.R
19+
import java.io.IOException
20+
import kotlin.coroutines.resume
21+
import kotlinx.coroutines.CoroutineScope
22+
import kotlinx.coroutines.Dispatchers
23+
import kotlinx.coroutines.launch
24+
import kotlinx.coroutines.suspendCancellableCoroutine
25+
import kotlinx.coroutines.withContext
26+
import net.thunderbird.core.android.account.LegacyAccountDto
27+
import net.thunderbird.core.logging.legacy.Log.e
28+
import org.apache.commons.io.IOUtils
29+
30+
class AttachmentController internal constructor(
31+
private val context: Context,
32+
private val controller: MessagingController,
33+
private val messageViewFragment: MessageViewFragment,
34+
private val attachment: AttachmentViewInfo,
35+
) {
36+
private val viewIntentFinder = ViewIntentFinder(context)
37+
38+
fun viewAttachment(scope: CoroutineScope) {
39+
scope.launch {
40+
if (!attachment.isContentAvailable) {
41+
val success = downloadAttachment()
42+
if (success) {
43+
messageViewFragment.refreshAttachmentThumbnail(attachment)
44+
viewLocalAttachment()
45+
}
46+
} else {
47+
viewLocalAttachment()
48+
}
49+
}
50+
}
51+
52+
fun saveAttachmentTo(scope: CoroutineScope, documentUri: Uri?) {
53+
if (documentUri == null) return
54+
scope.launch {
55+
if (!attachment.isContentAvailable) {
56+
val success = downloadAttachment()
57+
if (success) {
58+
messageViewFragment.refreshAttachmentThumbnail(attachment)
59+
saveLocalAttachmentTo(documentUri)
60+
}
61+
} else {
62+
saveLocalAttachmentTo(documentUri)
63+
}
64+
}
65+
}
66+
67+
fun saveAttachmentToDirectory(scope: CoroutineScope, directoryUri: Uri?) {
68+
if (directoryUri == null) return
69+
scope.launch {
70+
if (!attachment.isContentAvailable) {
71+
val success = downloadAttachment()
72+
if (success) {
73+
messageViewFragment.refreshAttachmentThumbnail(attachment)
74+
saveLocalAttachmentToDirectory(directoryUri)
75+
}
76+
} else {
77+
saveLocalAttachmentToDirectory(directoryUri)
78+
}
79+
}
80+
}
81+
82+
private suspend fun saveLocalAttachmentTo(documentUri: Uri) {
83+
val success = withContext(Dispatchers.IO) {
84+
try {
85+
writeAttachment(documentUri)
86+
true
87+
} catch (e: IOException) {
88+
e(e, "Error saving attachment")
89+
false
90+
}
91+
}
92+
if (!success) displayAttachmentNotSavedMessage()
93+
}
94+
95+
private suspend fun saveLocalAttachmentToDirectory(directoryUri: Uri) {
96+
val success = withContext(Dispatchers.IO) {
97+
try {
98+
val contentResolver = context.contentResolver
99+
val treeId = DocumentsContract.getTreeDocumentId(directoryUri)
100+
val rootDocUri = DocumentsContract.buildDocumentUriUsingTree(directoryUri, treeId)
101+
102+
val documentUri = DocumentsContract.createDocument(
103+
contentResolver,
104+
rootDocUri,
105+
attachment.mimeType,
106+
attachment.displayName
107+
) ?: return@withContext false
108+
109+
writeAttachment(documentUri)
110+
true
111+
} catch (e: IOException) {
112+
e(e, "Error saving attachment to directory")
113+
false
114+
}
115+
}
116+
if (!success) displayAttachmentNotSavedMessage()
117+
}
118+
119+
private suspend fun downloadAttachment(): Boolean = suspendCancellableCoroutine { continuation ->
120+
val localPart = attachment.part as LocalPart
121+
val account = getPreferences().getAccount(localPart.accountUuid)
122+
123+
messageViewFragment.showAttachmentLoadingDialog()
124+
controller.loadAttachment(account, localPart.message, attachment.part,
125+
object : SimpleMessagingListener() {
126+
override fun loadAttachmentFinished(account: LegacyAccountDto?, message: Message?, part: Part?) {
127+
attachment.setContentAvailable()
128+
messageViewFragment.hideAttachmentLoadingDialogOnMainThread()
129+
if (continuation.isActive) {
130+
continuation.resume(true)
131+
}
132+
}
133+
134+
override fun loadAttachmentFailed(account: LegacyAccountDto?, message: Message?, part: Part?, reason: String?) {
135+
messageViewFragment.hideAttachmentLoadingDialogOnMainThread()
136+
if (continuation.isActive) {
137+
continuation.resume(false)
138+
}
139+
}
140+
})
141+
}
142+
143+
private suspend fun viewLocalAttachment() {
144+
val intent = withContext(Dispatchers.IO) { getBestViewIntent() }
145+
if (intent != null) {
146+
try {
147+
context.startActivity(intent)
148+
} catch (_: ActivityNotFoundException) {
149+
val errorMsg = context.getString(R.string.message_view_no_viewer, attachment.mimeType)
150+
displayMessageToUser(errorMsg)
151+
}
152+
}
153+
}
154+
155+
@Throws(IOException::class)
156+
private fun writeAttachment(documentUri: Uri) {
157+
val contentResolver = context.contentResolver
158+
contentResolver.openInputStream(attachment.internalUri).use { inputStream ->
159+
contentResolver.openOutputStream(documentUri, "wt").use { outputStream ->
160+
if (inputStream != null && outputStream != null) {
161+
IOUtils.copy(inputStream, outputStream)
162+
outputStream.flush()
163+
}
164+
}
165+
}
166+
}
167+
168+
@WorkerThread
169+
private fun getBestViewIntent(): Intent? {
170+
return try {
171+
val intentDataUri = AttachmentTempFileProvider.createTempUriForContentUri(
172+
context, attachment.internalUri, attachment.displayName
173+
)
174+
viewIntentFinder.getBestViewIntent(intentDataUri, attachment.displayName, attachment.mimeType)
175+
} catch (e: IOException) {
176+
e(e, "Error creating temp file for attachment!")
177+
null
178+
}
179+
}
180+
181+
private fun displayAttachmentNotSavedMessage() {
182+
displayMessageToUser(context.getString(R.string.message_view_status_attachment_not_saved))
183+
}
184+
185+
private fun displayMessageToUser(message: String?) {
186+
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
187+
}
188+
}

0 commit comments

Comments
 (0)