1
0
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:
Daniel Applebaum 2009-04-09 17:48:05 +00:00
parent e92ac38052
commit bf98b5c2af
6 changed files with 148 additions and 14 deletions

View File

@ -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 -

View File

@ -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;

View File

@ -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()) {

View File

@ -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());
}

View File

@ -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);

View File

@ -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;
}