mirror of
https://github.com/moparisthebest/k-9
synced 2025-02-17 07:30:16 -05:00
Issue 366
Significant hardening of the email sending process. A given message in the Outbox is only attempted to be sent 5 times. Once the threshold is reached, the message is flagged. asses through the Outbox skip flagged messages. The message can be tried again by manually unflagging. When any messages are flagged during a pass through the Outbox, K-9 raises a notification with a fast flashing red LED, alerting the user to the failure to send. A note is also placed in the K9mail-errors. The read timeout on SMTP connections has been changed to 300000ms (5 minutes) The send attempt counter is kept in memory, not stored with the message, so a phone or application restart will clear the counters, but not the flagged state. As the intent of this revision is to avoid runaway message sending, this is deemed to be acceptable for now. The moving of messages from the Outbox to the Sent folder has been changed to an atomic move. Extra error checking has been added to the SMTP communication code. The flashing LED may be excessive.
This commit is contained in:
parent
e92ac38052
commit
bf98b5c2af
@ -129,6 +129,13 @@
|
||||
<string name="special_mailbox_name_trash_fmt"><xliff:g id="folder">%s</xliff:g> (Trash)</string>
|
||||
<string name="special_mailbox_name_sent_fmt"><xliff:g id="folder">%s</xliff:g> (Sent)</string>
|
||||
|
||||
<string name="send_failure_subject">Failed to send some messages</string>
|
||||
<string name="send_failure_body_abbrev">See your <xliff:g id="errorFolder">%s</xliff:g> folder for details.</string>
|
||||
<string name="send_failure_body_fmt">Some messages in your Outbox could not be sent. Those messages are now flagged.
|
||||
Unflag those messages to allow for more sending attempts.
|
||||
Long-press the Outbox to find the "Send messages" action in order to initiate another sending attempt.\u000A
|
||||
The <xliff:g id="errorFolder">%s</xliff:g> folder may contain error messages regarding the failures.</string>
|
||||
|
||||
<string name="end_of_folder">No more messages</string>
|
||||
|
||||
<string name="accounts_welcome">
|
||||
@ -437,7 +444,7 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
|
||||
<string name="account_setup_failed_dlg_invalid_certificate_title">Unrecognized Certificate</string>
|
||||
<string name="account_setup_failed_dlg_invalid_certificate_accept">Accept Key</string>
|
||||
<string name="account_setup_failed_dlg_invalid_certificate_reject">Reject Key</string>
|
||||
|
||||
|
||||
<string name="message_help_key">Del (or D) - Delete\u000AR -
|
||||
Reply\u000AA - Reply All\u000AF - Forward\u000AJ or P - Previous
|
||||
Message\u000AK, N - Next Message\u000AM - Move\u000AY - Copy\u000AZ - Zoom Out\u000AShift-Z -
|
||||
|
@ -110,6 +110,8 @@ public class Email extends Application {
|
||||
* Number of additioanl messages to load when a user selectes "Load more messages..."
|
||||
*/
|
||||
public static int VISIBLE_LIMIT_INCREMENT = 25;
|
||||
|
||||
public static int MAX_SEND_ATTEMPTS = 5;
|
||||
|
||||
/**
|
||||
* The maximum size of an attachment we're willing to download (either View or Save)
|
||||
@ -145,6 +147,8 @@ public class Email extends Application {
|
||||
public static final int NOTIFICATION_LED_DIM_COLOR = 0x77770077;
|
||||
public static final int NOTIFICATION_LED_FAST_ON_TIME = 100;
|
||||
public static final int NOTIFICATION_LED_FAST_OFF_TIME = 100;
|
||||
|
||||
public static final int NOTIFICATION_LED_SENDING_FAILURE_COLOR = 0xffff0000;
|
||||
|
||||
// Must not conflict with an account number
|
||||
public static final int FETCHING_EMAIL_NOTIFICATION_ID = -4;
|
||||
|
@ -19,6 +19,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.Notification;
|
||||
@ -111,6 +112,8 @@ public class MessagingController implements Runnable {
|
||||
private Set<MessagingListener> mListeners = new CopyOnWriteArraySet<MessagingListener>();
|
||||
|
||||
private HashMap<SORT_TYPE, Boolean> sortAscending = new HashMap<SORT_TYPE, Boolean>();
|
||||
|
||||
private ConcurrentHashMap<String, AtomicInteger> sendCount = new ConcurrentHashMap<String, AtomicInteger>();
|
||||
|
||||
public enum SORT_TYPE {
|
||||
SORT_DATE(R.string.sort_earliest_first, R.string.sort_latest_first, false),
|
||||
@ -1619,6 +1622,56 @@ s * critical data as fast as possible, and then we'll fill in the de
|
||||
}
|
||||
}
|
||||
|
||||
public void addErrorMessage(Account account, String subject, String body)
|
||||
{
|
||||
if (Email.ENABLE_ERROR_FOLDER == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (loopCatch.compareAndSet(false, true) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (body == null || body.length() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication);
|
||||
LocalFolder localFolder = (LocalFolder)localStore.getFolder(account.getErrorFolderName());
|
||||
Message[] messages = new Message[1];
|
||||
Message message = new MimeMessage();
|
||||
|
||||
|
||||
message.setBody(new TextBody(body));
|
||||
message.setFlag(Flag.X_DOWNLOADED_FULL, true);
|
||||
message.setSubject(subject);
|
||||
|
||||
long nowTime = System.currentTimeMillis();
|
||||
Date nowDate = new Date(nowTime);
|
||||
message.setInternalDate(nowDate);
|
||||
message.setSentDate(nowDate);
|
||||
message.setFrom(new Address(account.getEmail(), "K9mail internal"));
|
||||
messages[0] = message;
|
||||
|
||||
localFolder.appendMessages(messages);
|
||||
|
||||
localFolder.deleteMessagesOlderThan(nowTime - (15 * 60 * 1000));
|
||||
|
||||
}
|
||||
catch (Throwable it)
|
||||
{
|
||||
Log.e(Email.LOG_TAG, "Could not save error message to " + account.getErrorFolderName(), it);
|
||||
}
|
||||
finally
|
||||
{
|
||||
loopCatch.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void markAllMessagesRead(final Account account, final String folder)
|
||||
{
|
||||
@ -1668,6 +1721,14 @@ s * critical data as fast as possible, and then we'll fill in the de
|
||||
Message message = localFolder.getMessage(uid);
|
||||
message.setFlag(flag, newState);
|
||||
|
||||
// Allows for re-allowing sending of messages that could not be sent
|
||||
if (flag == Flag.FLAGGED && newState == false
|
||||
&& uid != null
|
||||
&& account.getOutboxFolderName().equals(folder))
|
||||
{
|
||||
sendCount.remove(uid);
|
||||
}
|
||||
|
||||
for (MessagingListener l : getListeners()) {
|
||||
l.folderStatusChanged(account, folder);
|
||||
}
|
||||
@ -1998,7 +2059,7 @@ s * critical data as fast as possible, and then we'll fill in the de
|
||||
localFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
Message[] localMessages = localFolder.getMessages(null);
|
||||
|
||||
boolean anyFlagged = false;
|
||||
/*
|
||||
* The profile we will use to pull all of the content
|
||||
* for a given local message into memory for sending.
|
||||
@ -2010,24 +2071,47 @@ s * critical data as fast as possible, and then we'll fill in the de
|
||||
LocalFolder localSentFolder =
|
||||
(LocalFolder) localStore.getFolder(
|
||||
account.getSentFolderName());
|
||||
|
||||
Log.i(Email.LOG_TAG, "Scanning folder '" + account.getOutboxFolderName() + "' (" + ((LocalFolder)localFolder).getId() + ") for messages to send");
|
||||
Transport transport = Transport.getInstance(account.getTransportUri());
|
||||
for (Message message : localMessages) {
|
||||
if (message.isSet(Flag.DELETED)) {
|
||||
message.setFlag(Flag.X_DESTROYED, true);
|
||||
continue;
|
||||
}
|
||||
if (message.isSet(Flag.FLAGGED)) {
|
||||
Log.i(Email.LOG_TAG, "Skipping sending FLAGGED message " + message.getUid());
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
AtomicInteger count = new AtomicInteger(0);
|
||||
AtomicInteger oldCount = sendCount.putIfAbsent(message.getUid(), count);
|
||||
if (oldCount != null)
|
||||
{
|
||||
count = oldCount;
|
||||
}
|
||||
Log.i(Email.LOG_TAG, "Send count for message " + message.getUid() + " is " + count.get());
|
||||
if (count.incrementAndGet() > Email.MAX_SEND_ATTEMPTS)
|
||||
{
|
||||
Log.e(Email.LOG_TAG, "Send count for message " + message.getUid() + " has exceeded maximum attempt threshold, flagging");
|
||||
message.setFlag(Flag.FLAGGED, true);
|
||||
anyFlagged = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
localFolder.fetch(new Message[] { message }, fp, null);
|
||||
try {
|
||||
message.setFlag(Flag.X_SEND_IN_PROGRESS, true);
|
||||
Log.i(Email.LOG_TAG, "Sending message with UID " + message.getUid());
|
||||
transport.sendMessage(message);
|
||||
message.setFlag(Flag.X_SEND_IN_PROGRESS, false);
|
||||
message.setFlag(Flag.SEEN, true);
|
||||
localFolder.copyMessages(
|
||||
|
||||
Log.i(Email.LOG_TAG, "Moving sent message to folder '" + account.getSentFolderName() + "' (" + localSentFolder.getId() + ") ");
|
||||
localFolder.moveMessages(
|
||||
new Message[] { message },
|
||||
localSentFolder);
|
||||
|
||||
Log.i(Email.LOG_TAG, "Moved sent message to folder '" + account.getSentFolderName() + "' (" + localSentFolder.getId() + ") ");
|
||||
|
||||
PendingCommand command = new PendingCommand();
|
||||
command.command = PENDING_COMMAND_APPEND;
|
||||
command.arguments =
|
||||
@ -2036,7 +2120,6 @@ s * critical data as fast as possible, and then we'll fill in the de
|
||||
message.getUid() };
|
||||
queuePendingCommand(account, command);
|
||||
processPendingCommands(account);
|
||||
message.setFlag(Flag.X_DESTROYED, true);
|
||||
}
|
||||
catch (Exception e) {
|
||||
message.setFlag(Flag.X_SEND_FAILED, true);
|
||||
@ -2074,6 +2157,30 @@ s * critical data as fast as possible, and then we'll fill in the de
|
||||
for (MessagingListener l : getListeners()) {
|
||||
l.sendPendingMessagesCompleted(account);
|
||||
}
|
||||
if (anyFlagged)
|
||||
{
|
||||
addErrorMessage(account, mApplication.getString(R.string.send_failure_subject),
|
||||
mApplication.getString(R.string.send_failure_body_fmt, Email.ERROR_FOLDER_NAME));
|
||||
|
||||
NotificationManager notifMgr =
|
||||
(NotificationManager)mApplication.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
Notification notif = new Notification(R.drawable.stat_notify_email_generic,
|
||||
mApplication.getString(R.string.send_failure_subject), System.currentTimeMillis());
|
||||
|
||||
Intent i = FolderMessageList.actionHandleAccountIntent(mApplication, account, account.getErrorFolderName());
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, i, 0);
|
||||
|
||||
notif.setLatestEventInfo(mApplication, mApplication.getString(R.string.send_failure_subject),
|
||||
mApplication.getString(R.string.send_failure_body_abbrev, Email.ERROR_FOLDER_NAME), pi);
|
||||
|
||||
notif.flags |= Notification.FLAG_SHOW_LIGHTS;
|
||||
notif.ledARGB = Email.NOTIFICATION_LED_SENDING_FAILURE_COLOR;
|
||||
notif.ledOnMS = Email.NOTIFICATION_LED_FAST_ON_TIME;
|
||||
notif.ledOffMS = Email.NOTIFICATION_LED_FAST_OFF_TIME;
|
||||
notifMgr.notify(-1000 - account.getAccountNumber(), notif);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
for (MessagingListener l : getListeners()) {
|
||||
|
@ -645,6 +645,7 @@ public class FolderMessageList extends ExpandableListActivity
|
||||
|
||||
NotificationManager notifMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notifMgr.cancel(mAccount.getAccountNumber());
|
||||
notifMgr.cancel(-1000 - mAccount.getAccountNumber());
|
||||
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,9 @@ import com.android.email.mail.transport.WebDavTransport;
|
||||
public abstract class Transport {
|
||||
protected static final int SOCKET_CONNECT_TIMEOUT = 10000;
|
||||
|
||||
// RFC 1047
|
||||
protected static final int SOCKET_READ_TIMEOUT = 300000;
|
||||
|
||||
public synchronized static Transport getInstance(String uri) throws MessagingException {
|
||||
if (uri.startsWith("smtp")) {
|
||||
return new SmtpTransport(uri);
|
||||
|
@ -28,6 +28,7 @@ import com.android.email.mail.Address;
|
||||
import com.android.email.mail.AuthenticationFailedException;
|
||||
import com.android.email.mail.Message;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Store;
|
||||
import com.android.email.mail.Transport;
|
||||
import com.android.email.mail.CertificateValidationException;
|
||||
import com.android.email.mail.Message.RecipientType;
|
||||
@ -132,6 +133,9 @@ public class SmtpTransport extends Transport {
|
||||
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
|
||||
}
|
||||
|
||||
// RFC 1047
|
||||
mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
|
||||
|
||||
mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), 1024));
|
||||
mOut = mSocket.getOutputStream();
|
||||
|
||||
@ -286,7 +290,7 @@ public class SmtpTransport extends Transport {
|
||||
}
|
||||
}
|
||||
String ret = sb.toString();
|
||||
if (Config.LOGD) {
|
||||
if (true || Config.LOGD) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, "<<< " + ret);
|
||||
}
|
||||
@ -295,7 +299,7 @@ public class SmtpTransport extends Transport {
|
||||
}
|
||||
|
||||
private void writeLine(String s) throws IOException {
|
||||
if (Config.LOGD) {
|
||||
if (true || Config.LOGD) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, ">>> " + s);
|
||||
}
|
||||
@ -306,25 +310,33 @@ public class SmtpTransport extends Transport {
|
||||
mOut.flush();
|
||||
}
|
||||
|
||||
private void checkLine(String line) throws MessagingException
|
||||
{
|
||||
if (line.length() < 1)
|
||||
{
|
||||
throw new MessagingException("SMTP response is 0 length");
|
||||
}
|
||||
char c = line.charAt(0);
|
||||
if ((c == '4') || (c == '5')) {
|
||||
throw new MessagingException(line);
|
||||
}
|
||||
}
|
||||
|
||||
private String executeSimpleCommand(String command) throws IOException, MessagingException {
|
||||
if (command != null) {
|
||||
writeLine(command);
|
||||
}
|
||||
|
||||
String line = readLine();
|
||||
checkLine(line);
|
||||
|
||||
String result = line;
|
||||
|
||||
while (line.length() >= 4 && line.charAt(3) == '-') {
|
||||
line = readLine();
|
||||
checkLine(line);
|
||||
result += line.substring(3);
|
||||
}
|
||||
|
||||
char c = result.charAt(0);
|
||||
if ((c == '4') || (c == '5')) {
|
||||
throw new MessagingException(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user