Send messages with attachments

pull/50/head
M66B 7 years ago
parent 1405d3be98
commit f62dd4ee90

@ -32,15 +32,31 @@ import androidx.room.Update;
public interface DaoAttachment { public interface DaoAttachment {
@Query("SELECT id, message, sequence, name, type, size, progress" + @Query("SELECT id, message, sequence, name, type, size, progress" +
", (NOT content IS NULL) as content" + ", (NOT content IS NULL) as content" +
" FROM attachment WHERE message = :message") " FROM attachment" +
" WHERE message = :message" +
" ORDER BY sequence")
LiveData<List<TupleAttachment>> liveAttachments(long message); LiveData<List<TupleAttachment>> liveAttachments(long message);
@Query("SELECT * FROM attachment WHERE message = :message AND sequence = :sequence") @Query("SELECT * FROM attachment" +
" WHERE message = :message" +
" AND sequence = :sequence")
EntityAttachment getAttachment(long message, int sequence); EntityAttachment getAttachment(long message, int sequence);
@Query("SELECT COUNT(attachment.id) FROM attachment WHERE message = :message") @Query("SELECT COUNT(attachment.id)" +
" FROM attachment" +
" WHERE message = :message")
int getAttachmentCount(long message); int getAttachmentCount(long message);
@Query("SELECT SUM(CASE WHEN content IS NULL THEN 1 ELSE 0 END)" +
" FROM attachment" +
" WHERE message = :message")
int getAttachmentCountWithoutContent(long message);
@Query("SELECT id, message, sequence, name, type, size, progress, NULL AS content FROM attachment" +
" WHERE message = :message" +
" ORDER BY sequence")
List<EntityAttachment> getAttachments(long message);
@Query("UPDATE attachment SET progress = :progress WHERE id = :id") @Query("UPDATE attachment SET progress = :progress WHERE id = :id")
void setProgress(long id, int progress); void setProgress(long id, int progress);

@ -99,6 +99,8 @@ public class FragmentCompose extends FragmentEx {
private AdapterAttachment adapter; private AdapterAttachment adapter;
private static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes
@Override @Override
@Nullable @Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@ -417,7 +419,7 @@ public class FragmentCompose extends FragmentEx {
attachment.size = (size == null ? null : Integer.parseInt(size)); attachment.size = (size == null ? null : Integer.parseInt(size));
attachment.progress = 0; attachment.progress = 0;
db.attachment().insertAttachment(attachment); attachment.id = db.attachment().insertAttachment(attachment);
InputStream is = null; InputStream is = null;
try { try {
@ -425,7 +427,7 @@ public class FragmentCompose extends FragmentEx {
ByteArrayOutputStream os = new ByteArrayOutputStream(); ByteArrayOutputStream os = new ByteArrayOutputStream();
int len; int len;
byte[] buffer = new byte[8192]; byte[] buffer = new byte[ATTACHMENT_BUFFER_SIZE];
while ((len = is.read(buffer)) > 0) { while ((len = is.read(buffer)) > 0) {
os.write(buffer, 0, len); os.write(buffer, 0, len);
@ -445,7 +447,6 @@ public class FragmentCompose extends FragmentEx {
is.close(); is.close();
} }
return null; return null;
} finally { } finally {
if (cursor != null) if (cursor != null)
@ -728,6 +729,13 @@ public class FragmentCompose extends FragmentEx {
if (draft.to == null && draft.cc == null && draft.bcc == null) if (draft.to == null && draft.cc == null && draft.bcc == null)
throw new IllegalArgumentException(getContext().getString(R.string.title_to_missing)); throw new IllegalArgumentException(getContext().getString(R.string.title_to_missing));
if (db.attachment().getAttachmentCountWithoutContent(draft.id) > 0)
throw new IllegalArgumentException(getContext().getString(R.string.title_attachments_missing));
List<EntityAttachment> attachments = db.attachment().getAttachments(draft.id);
for (EntityAttachment attachment : attachments)
attachment.content = db.attachment().getContent(attachment.id);
// Delete draft (cannot move to outbox) // Delete draft (cannot move to outbox)
draft.ui_hide = true; draft.ui_hide = true;
db.message().updateMessage(draft); db.message().updateMessage(draft);
@ -740,6 +748,11 @@ public class FragmentCompose extends FragmentEx {
draft.ui_hide = false; draft.ui_hide = false;
draft.id = db.message().insertMessage(draft); draft.id = db.message().insertMessage(draft);
for (EntityAttachment attachment : attachments) {
attachment.message = draft.id;
db.attachment().insertAttachment(attachment);
}
EntityOperation.queue(db, draft, EntityOperation.SEND); EntityOperation.queue(db, draft, EntityOperation.SEND);
} }

@ -27,11 +27,14 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.Address; import javax.mail.Address;
import javax.mail.BodyPart; import javax.mail.BodyPart;
import javax.mail.Flags; import javax.mail.Flags;
@ -42,7 +45,10 @@ import javax.mail.Part;
import javax.mail.Session; import javax.mail.Session;
import javax.mail.internet.ContentType; import javax.mail.internet.ContentType;
import javax.mail.internet.InternetAddress; import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;
public class MessageHelper { public class MessageHelper {
private MimeMessage imessage; private MimeMessage imessage;
@ -78,7 +84,7 @@ public class MessageHelper {
return props; return props;
} }
static MimeMessageEx from(EntityMessage message, Session isession) throws MessagingException { static MimeMessageEx from(EntityMessage message, List<EntityAttachment> attachments, Session isession) throws MessagingException {
MimeMessageEx imessage = new MimeMessageEx(isession, message.id); MimeMessageEx imessage = new MimeMessageEx(isession, message.id);
if (message.from != null && message.from.length > 0) if (message.from != null && message.from.length > 0)
@ -96,16 +102,38 @@ public class MessageHelper {
if (message.subject != null) if (message.subject != null)
imessage.setSubject(message.subject); imessage.setSubject(message.subject);
if (attachments.size() == 0) {
if (message.body != null) if (message.body != null)
imessage.setText(message.body, null, "html"); imessage.setText(message.body, Charset.defaultCharset().name(), "html");
} else {
Multipart multipart = new MimeMultipart();
if (message.body != null) {
BodyPart bpMessage = new MimeBodyPart();
bpMessage.setContent(message.body, "text/html; charset=" + Charset.defaultCharset().name());
multipart.addBodyPart(bpMessage);
}
for (EntityAttachment attachment : attachments) {
BodyPart bpAttachment = new MimeBodyPart();
bpAttachment.setFileName(attachment.name);
DataSource dataSource = new ByteArrayDataSource(attachment.content, attachment.type);
bpAttachment.setDataHandler(new DataHandler(dataSource));
multipart.addBodyPart(bpAttachment);
}
imessage.setContent(multipart);
}
imessage.setSentDate(new Date()); imessage.setSentDate(new Date());
return imessage; return imessage;
} }
static MimeMessageEx from(EntityMessage message, EntityMessage reply, Session isession) throws MessagingException { static MimeMessageEx from(EntityMessage message, EntityMessage reply, List<EntityAttachment> attachments, Session isession) throws MessagingException {
MimeMessageEx imessage = from(message, isession); MimeMessageEx imessage = from(message, attachments, isession);
imessage.addHeader("In-Reply-To", reply.msgid); imessage.addHeader("In-Reply-To", reply.msgid);
imessage.addHeader("References", (reply.references == null ? "" : reply.references + " ") + reply.msgid); imessage.addHeader("References", (reply.references == null ? "" : reply.references + " ") + reply.msgid);
return imessage; return imessage;

@ -103,7 +103,7 @@ public class ServiceSynchronize extends LifecycleService {
private static final long NOOP_INTERVAL = 9 * 60 * 1000L; // ms private static final long NOOP_INTERVAL = 9 * 60 * 1000L; // ms
private static final int FETCH_BATCH_SIZE = 10; private static final int FETCH_BATCH_SIZE = 10;
private static final int DOWNLOAD_BUFFER_SIZE = 8192; // bytes private static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes
static final String ACTION_PROCESS_FOLDER = BuildConfig.APPLICATION_ID + ".PROCESS_FOLDER"; static final String ACTION_PROCESS_FOLDER = BuildConfig.APPLICATION_ID + ".PROCESS_FOLDER";
static final String ACTION_PROCESS_OUTBOX = BuildConfig.APPLICATION_ID + ".PROCESS_OUTBOX"; static final String ACTION_PROCESS_OUTBOX = BuildConfig.APPLICATION_ID + ".PROCESS_OUTBOX";
@ -705,10 +705,12 @@ public class ServiceSynchronize extends LifecycleService {
if (EntityOperation.SEEN.equals(op.name)) if (EntityOperation.SEEN.equals(op.name))
doSeen(folder, ifolder, jargs, message); doSeen(folder, ifolder, jargs, message);
else if (EntityOperation.ADD.equals(op.name)) else if (EntityOperation.ADD.equals(op.name)) {
doAdd(folder, ifolder, message); List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
for (EntityAttachment attachment : attachments)
else if (EntityOperation.MOVE.equals(op.name)) attachment.content = db.attachment().getContent(attachment.id);
doAdd(folder, ifolder, message, attachments);
} else if (EntityOperation.MOVE.equals(op.name))
doMove(folder, istore, ifolder, db, jargs, message); doMove(folder, istore, ifolder, db, jargs, message);
else if (EntityOperation.DELETE.equals(op.name)) else if (EntityOperation.DELETE.equals(op.name))
@ -774,11 +776,11 @@ public class ServiceSynchronize extends LifecycleService {
imessage.setFlag(Flags.Flag.SEEN, jargs.getBoolean(0)); imessage.setFlag(Flags.Flag.SEEN, jargs.getBoolean(0));
} }
private void doAdd(EntityFolder folder, IMAPFolder ifolder, EntityMessage message) throws MessagingException { private void doAdd(EntityFolder folder, IMAPFolder ifolder, EntityMessage message, List<EntityAttachment> attachments) throws MessagingException {
// Append message // Append message
Properties props = MessageHelper.getSessionProperties(); Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null); Session isession = Session.getInstance(props, null);
MimeMessage imessage = MessageHelper.from(message, isession); MimeMessage imessage = MessageHelper.from(message, attachments, isession);
ifolder.appendMessages(new Message[]{imessage}); ifolder.appendMessages(new Message[]{imessage});
} }
@ -825,9 +827,13 @@ public class ServiceSynchronize extends LifecycleService {
// Append copy // Append copy
if (!EntityFolder.ARCHIVE.equals(target.type)) { if (!EntityFolder.ARCHIVE.equals(target.type)) {
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
for (EntityAttachment attachment : attachments)
attachment.content = db.attachment().getContent(attachment.id);
Properties props = MessageHelper.getSessionProperties(); Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null); Session isession = Session.getInstance(props, null);
MimeMessage icopy = MessageHelper.from(message, isession); MimeMessage icopy = MessageHelper.from(message, attachments, isession);
itarget.appendMessages(new Message[]{icopy}); itarget.appendMessages(new Message[]{icopy});
} }
@ -856,26 +862,17 @@ public class ServiceSynchronize extends LifecycleService {
private void doSend(DB db, EntityMessage message) throws MessagingException { private void doSend(DB db, EntityMessage message) throws MessagingException {
// Send message // Send message
EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying));
EntityIdentity ident = db.identity().getIdentity(message.identity); EntityIdentity ident = db.identity().getIdentity(message.identity);
EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying));
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
for (EntityAttachment attachment : attachments)
attachment.content = db.attachment().getContent(attachment.id);
if (!ident.synchronize) { if (!ident.synchronize) {
// Message will remain in outbox // Message will remain in outbox
return; return;
} }
try {
db.beginTransaction();
// Move message to sent
EntityFolder sent = db.folder().getFolderByType(ident.account, EntityFolder.SENT);
if (sent == null)
; // Leave message in outbox
else {
message.folder = sent.id;
message.uid = null;
}
// Create session // Create session
Properties props = MessageHelper.getSessionProperties(); Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null); Session isession = Session.getInstance(props, null);
@ -883,9 +880,9 @@ public class ServiceSynchronize extends LifecycleService {
// Create message // Create message
MimeMessage imessage; MimeMessage imessage;
if (reply == null) if (reply == null)
imessage = MessageHelper.from(message, isession); imessage = MessageHelper.from(message, attachments, isession);
else else
imessage = MessageHelper.from(message, reply, isession); imessage = MessageHelper.from(message, reply, attachments, isession);
if (ident.replyto != null) if (ident.replyto != null)
imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)}); imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)});
@ -902,6 +899,18 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, "Sent via " + ident.host + "/" + ident.user + Log.i(Helper.TAG, "Sent via " + ident.host + "/" + ident.user +
" to " + TextUtils.join(", ", to)); " to " + TextUtils.join(", ", to));
try {
db.beginTransaction();
// Move message to sent
EntityFolder sent = db.folder().getFolderByType(ident.account, EntityFolder.SENT);
if (sent == null)
; // Leave message in outbox
else {
message.folder = sent.id;
message.uid = null;
}
// Update state // Update state
if (message.thread == null) if (message.thread == null)
message.thread = imessage.getMessageID(); message.thread = imessage.getMessageID();
@ -913,14 +922,13 @@ public class ServiceSynchronize extends LifecycleService {
if (sent != null) if (sent != null)
EntityOperation.queue(db, message, EntityOperation.ADD); // Could already exist EntityOperation.queue(db, message, EntityOperation.ADD); // Could already exist
} finally {
itransport.close();
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();
} }
} finally {
itransport.close();
}
} }
private void doAttachment(EntityFolder folder, IMAPFolder ifolder, DB db, EntityOperation op, JSONArray jargs, EntityMessage message) throws JSONException, MessagingException, IOException { private void doAttachment(EntityFolder folder, IMAPFolder ifolder, DB db, EntityOperation op, JSONArray jargs, EntityMessage message) throws JSONException, MessagingException, IOException {
@ -943,7 +951,7 @@ public class ServiceSynchronize extends LifecycleService {
// Download attachment // Download attachment
InputStream is = a.part.getInputStream(); InputStream is = a.part.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream(); ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[DOWNLOAD_BUFFER_SIZE]; byte[] buffer = new byte[ATTACHMENT_BUFFER_SIZE];
for (int len = is.read(buffer); len != -1; len = is.read(buffer)) { for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
os.write(buffer, 0, len); os.write(buffer, 0, len);

@ -141,6 +141,7 @@
<string name="title_from_missing">Sender missing</string> <string name="title_from_missing">Sender missing</string>
<string name="title_to_missing">Recipient missing</string> <string name="title_to_missing">Recipient missing</string>
<string name="title_attachments_missing">Attachments still loading</string>
<string name="title_draft_trashed">Draft trashed</string> <string name="title_draft_trashed">Draft trashed</string>
<string name="title_draft_saved">Draft saved</string> <string name="title_draft_saved">Draft saved</string>
<string name="title_queued">Sending message</string> <string name="title_queued">Sending message</string>

Loading…
Cancel
Save