mirror of
https://github.com/M66B/FairEmail.git
synced 2026-01-02 19:10:11 +01:00
Send messages with attachments
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
// Create session
|
||||
Properties props = MessageHelper.getSessionProperties();
|
||||
Session isession = Session.getInstance(props, 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 transport
|
||||
// TODO: cache transport?
|
||||
Transport itransport = isession.getTransport(ident.starttls ? "smtp" : "smtps");
|
||||
try {
|
||||
db.beginTransaction();
|
||||
// Connect transport
|
||||
itransport.connect(ident.host, ident.port, ident.user, ident.password);
|
||||
|
||||
// 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;
|
||||
}
|
||||
// Send message
|
||||
Address[] to = imessage.getAllRecipients();
|
||||
itransport.sendMessage(imessage, to);
|
||||
Log.i(Helper.TAG, "Sent via " + ident.host + "/" + ident.user +
|
||||
" to " + TextUtils.join(", ", to));
|
||||
|
||||
// Create session
|
||||
Properties props = MessageHelper.getSessionProperties();
|
||||
Session isession = Session.getInstance(props, null);
|
||||
|
||||
// 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);
|
||||
db.beginTransaction();
|
||||
|
||||
// Send message
|
||||
Address[] to = imessage.getAllRecipients();
|
||||
itransport.sendMessage(imessage, to);
|
||||
Log.i(Helper.TAG, "Sent via " + ident.host + "/" + ident.user +
|
||||
" to " + TextUtils.join(", ", to));
|
||||
// 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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user