Browse Source

Send messages with attachments

main
M66B 6 years ago
parent
commit
f62dd4ee90
5 changed files with 125 additions and 59 deletions
  1. +20
    -4
      app/src/main/java/eu/faircode/email/DaoAttachment.java
  2. +16
    -3
      app/src/main/java/eu/faircode/email/FragmentCompose.java
  3. +33
    -5
      app/src/main/java/eu/faircode/email/MessageHelper.java
  4. +55
    -47
      app/src/main/java/eu/faircode/email/ServiceSynchronize.java
  5. +1
    -0
      app/src/main/res/values/strings.xml

+ 20
- 4
app/src/main/java/eu/faircode/email/DaoAttachment.java View File

@ -30,17 +30,33 @@ import androidx.room.Update;
@Dao
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" +
" FROM attachment WHERE message = :message")
" FROM attachment" +
" WHERE message = :message" +
" ORDER BY sequence")
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);
@Query("SELECT COUNT(attachment.id) FROM attachment WHERE message = :message")
@Query("SELECT COUNT(attachment.id)" +
" FROM attachment" +
" WHERE message = :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")
void setProgress(long id, int progress);


+ 16
- 3
app/src/main/java/eu/faircode/email/FragmentCompose.java View File

@ -99,6 +99,8 @@ public class FragmentCompose extends FragmentEx {
private AdapterAttachment adapter;
private static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes
@Override
@Nullable
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.progress = 0;
db.attachment().insertAttachment(attachment);
attachment.id = db.attachment().insertAttachment(attachment);
InputStream is = null;
try {
@ -425,7 +427,7 @@ public class FragmentCompose extends FragmentEx {
ByteArrayOutputStream os = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[8192];
byte[] buffer = new byte[ATTACHMENT_BUFFER_SIZE];
while ((len = is.read(buffer)) > 0) {
os.write(buffer, 0, len);
@ -445,7 +447,6 @@ public class FragmentCompose extends FragmentEx {
is.close();
}
return null;
} finally {
if (cursor != null)
@ -728,6 +729,13 @@ public class FragmentCompose extends FragmentEx {
if (draft.to == null && draft.cc == null && draft.bcc == null)
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)
draft.ui_hide = true;
db.message().updateMessage(draft);
@ -740,6 +748,11 @@ public class FragmentCompose extends FragmentEx {
draft.ui_hide = false;
draft.id = db.message().insertMessage(draft);
for (EntityAttachment attachment : attachments) {
attachment.message = draft.id;
db.attachment().insertAttachment(attachment);
}
EntityOperation.queue(db, draft, EntityOperation.SEND);
}


+ 33
- 5
app/src/main/java/eu/faircode/email/MessageHelper.java View File

@ -27,11 +27,14 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Flags;
@ -42,7 +45,10 @@ import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.ContentType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;
public class MessageHelper {
private MimeMessage imessage;
@ -78,7 +84,7 @@ public class MessageHelper {
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);
if (message.from != null && message.from.length > 0)
@ -96,16 +102,38 @@ public class MessageHelper {
if (message.subject != null)
imessage.setSubject(message.subject);
if (message.body != null)
imessage.setText(message.body, null, "html");
if (attachments.size() == 0) {
if (message.body != null)
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());
return imessage;
}
static MimeMessageEx from(EntityMessage message, EntityMessage reply, Session isession) throws MessagingException {
MimeMessageEx imessage = from(message, isession);
static MimeMessageEx from(EntityMessage message, EntityMessage reply, List<EntityAttachment> attachments, Session isession) throws MessagingException {
MimeMessageEx imessage = from(message, attachments, isession);
imessage.addHeader("In-Reply-To", reply.msgid);
imessage.addHeader("References", (reply.references == null ? "" : reply.references + " ") + reply.msgid);
return imessage;


+ 55
- 47
app/src/main/java/eu/faircode/email/ServiceSynchronize.java View File

@ -103,7 +103,7 @@ public class ServiceSynchronize extends LifecycleService {
private static final long NOOP_INTERVAL = 9 * 60 * 1000L; // ms
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_OUTBOX = BuildConfig.APPLICATION_ID + ".PROCESS_OUTBOX";
@ -705,10 +705,12 @@ public class ServiceSynchronize extends LifecycleService {
if (EntityOperation.SEEN.equals(op.name))
doSeen(folder, ifolder, jargs, message);
else if (EntityOperation.ADD.equals(op.name))
doAdd(folder, ifolder, message);
else if (EntityOperation.MOVE.equals(op.name))
else if (EntityOperation.ADD.equals(op.name)) {
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
for (EntityAttachment attachment : attachments)
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);
else if (EntityOperation.DELETE.equals(op.name))
@ -774,11 +776,11 @@ public class ServiceSynchronize extends LifecycleService {
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
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
MimeMessage imessage = MessageHelper.from(message, isession);
MimeMessage imessage = MessageHelper.from(message, attachments, isession);
ifolder.appendMessages(new Message[]{imessage});
}
@ -825,9 +827,13 @@ public class ServiceSynchronize extends LifecycleService {
// Append copy
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();
Session isession = Session.getInstance(props, null);
MimeMessage icopy = MessageHelper.from(message, isession);
MimeMessage icopy = MessageHelper.from(message, attachments, isession);
itarget.appendMessages(new Message[]{icopy});
}
@ -856,51 +862,54 @@ public class ServiceSynchronize extends LifecycleService {
private void doSend(DB db, EntityMessage message) throws MessagingException {
// Send message
EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying));
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) {
// Message will remain in outbox
return;
}
try {
db.beginTransaction();
// Create session
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
// 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 message
MimeMessage imessage;
if (reply == null)
imessage = MessageHelper.from(message, attachments, isession);
else
imessage = MessageHelper.from(message, reply, attachments, isession);
if (ident.replyto != null)
imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)});
// Create session
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
// Create transport
// TODO: cache transport?
Transport itransport = isession.getTransport(ident.starttls ? "smtp" : "smtps");
try {
// Connect transport
itransport.connect(ident.host, ident.port, ident.user, ident.password);
// Create message
MimeMessage imessage;
if (reply == null)
imessage = MessageHelper.from(message, isession);
else
imessage = MessageHelper.from(message, reply, isession);
if (ident.replyto != null)
imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)});
// Create transport
// TODO: cache transport?
Transport itransport = isession.getTransport(ident.starttls ? "smtp" : "smtps");
try {
// Connect transport
itransport.connect(ident.host, ident.port, ident.user, ident.password);
// Send message
Address[] to = imessage.getAllRecipients();
itransport.sendMessage(imessage, to);
Log.i(Helper.TAG, "Sent via " + ident.host + "/" + ident.user +
" to " + TextUtils.join(", ", to));
// Send message
Address[] to = imessage.getAllRecipients();
itransport.sendMessage(imessage, to);
Log.i(Helper.TAG, "Sent via " + ident.host + "/" + ident.user +
" 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
if (message.thread == null)
@ -913,13 +922,12 @@ public class ServiceSynchronize extends LifecycleService {
if (sent != null)
EntityOperation.queue(db, message, EntityOperation.ADD); // Could already exist
db.setTransactionSuccessful();
} finally {
itransport.close();
db.endTransaction();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
itransport.close();
}
}
@ -943,7 +951,7 @@ public class ServiceSynchronize extends LifecycleService {
// Download attachment
InputStream is = a.part.getInputStream();
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)) {
os.write(buffer, 0, len);


+ 1
- 0
app/src/main/res/values/strings.xml View File

@ -141,6 +141,7 @@
<string name="title_from_missing">Sender 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_saved">Draft saved</string>
<string name="title_queued">Sending message</string>


Loading…
Cancel
Save