diff --git a/app/src/main/java/eu/faircode/email/DaoOperation.java b/app/src/main/java/eu/faircode/email/DaoOperation.java index f5d60f9718..41912bd415 100644 --- a/app/src/main/java/eu/faircode/email/DaoOperation.java +++ b/app/src/main/java/eu/faircode/email/DaoOperation.java @@ -145,4 +145,7 @@ public interface DaoOperation { @Query("DELETE FROM operation WHERE id = :id") int deleteOperation(long id); + + @Query("DELETE FROM operation WHERE folder = :folder") + int deleteOperations(long folder); } diff --git a/app/src/main/java/eu/faircode/email/EntityOperation.java b/app/src/main/java/eu/faircode/email/EntityOperation.java index 8d5ee15c0d..83a9afbcbc 100644 --- a/app/src/main/java/eu/faircode/email/EntityOperation.java +++ b/app/src/main/java/eu/faircode/email/EntityOperation.java @@ -482,4 +482,9 @@ public class EntityOperation { } else return false; } + + @Override + public String toString() { + return Long.toString(id); + } } diff --git a/app/src/main/java/eu/faircode/email/ServiceSend.java b/app/src/main/java/eu/faircode/email/ServiceSend.java index 17e0d33c07..7825e07887 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSend.java +++ b/app/src/main/java/eu/faircode/email/ServiceSend.java @@ -69,12 +69,11 @@ public class ServiceSend extends ServiceBase { private PowerManager.WakeLock wlOutbox; private TwoStateOwner owner = new TwoStateOwner("send"); - + private List handling = new ArrayList<>(); private static ExecutorService executor = Helper.getBackgroundExecutor(1, "send"); private static final int PI_SEND = 1; private static final long CONNECTIVITY_DELAY = 5000L; // milliseconds - private static final int IDENTITY_ERROR_AFTER = 30; // minutes private static final int RETRY_MAX = 3; @Override @@ -106,33 +105,42 @@ public class ServiceSend extends ServiceBase { // Observe send operations db.operation().liveOperations(null).observe(owner, new Observer>() { - private List handling = new ArrayList<>(); - @Override - public void onChanged(final List operations) { - boolean process = false; + public void onChanged(List operations) { + if (operations == null) + operations = new ArrayList<>(); + + if (operations.size() == 0) + stopSelf(); + + final List process = new ArrayList<>(); + List ops = new ArrayList<>(); - for (EntityOperation op : operations) { + for (TupleOperationEx op : operations) { if (!handling.contains(op.id)) - process = true; + process.add(op); ops.add(op.id); } - handling = ops; - if (process) { - Log.i("OUTBOX operations=" + operations.size()); + if (process.size() > 0) { + Log.i("OUTBOX process=" + TextUtils.join(",", process) + + " handling=" + TextUtils.join(",", handling)); executor.submit(new Runnable() { @Override public void run() { - processOperations(); + processOperations(process); } }); } } }); + lastSuitable = ConnectionHelper.getNetworkState(this).isSuitable(); + if (lastSuitable) + owner.start(); + ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); @@ -153,6 +161,9 @@ public class ServiceSend extends ServiceBase { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); cm.unregisterNetworkCallback(networkCallback); + owner.stop(); + handling.clear(); + stopForeground(true); NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); @@ -252,36 +263,29 @@ public class ServiceSend extends ServiceBase { if (suitable) owner.start(); - else + else { owner.stop(); + handling.clear(); + } } - - if (suitable) - executor.submit(new Runnable() { - @Override - public void run() { - processOperations(); - } - }); } - private void processOperations() { + private void processOperations(List ops) { try { wlOutbox.acquire(); - if (!ConnectionHelper.getNetworkState(this).isSuitable()) - return; - DB db = DB.getInstance(this); EntityFolder outbox = db.folder().getOutbox(); try { db.folder().setFolderError(outbox.id, null); db.folder().setFolderSyncState(outbox.id, "syncing"); - List ops = db.operation().getOperations(outbox.id); - Log.i(outbox.name + " pending operations=" + ops.size()); + Log.i(outbox.name + " processing operations=" + ops.size()); while (ops.size() > 0) { + if (!ConnectionHelper.getNetworkState(this).isSuitable()) + break; + TupleOperationEx op = ops.get(0); EntityMessage message = null; @@ -292,6 +296,7 @@ public class ServiceSend extends ServiceBase { Log.i(outbox.name + " start op=" + op.id + "/" + op.name + " msg=" + op.message + + " tries=" + op.tries + " args=" + op.args); db.operation().setOperationTries(op.id, ++op.tries); @@ -364,41 +369,14 @@ public class ServiceSend extends ServiceBase { } continue; - } else { - if (message != null) { - String title = MessageHelper.formatAddresses(message.to); - PendingIntent pi = getPendingIntent(this); - - EntityLog.log(this, title + " last attempt: " + new Date(message.last_attempt)); - - long now = new Date().getTime(); - long delayed = now - message.last_attempt; - if (delayed > IDENTITY_ERROR_AFTER * 60 * 1000L) { - Log.i("Reporting send error after=" + delayed); - try { - NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - nm.notify("send:" + message.id, 1, - Core.getNotificationError(this, "warning", title, ex, pi).build()); - } catch (Throwable ex1) { - Log.w(ex1); - } - } - } - + } else throw ex; - } } finally { Log.i(outbox.name + " end op=" + op.id + "/" + op.name); db.operation().setOperationState(op.id, null); } - - if (!ConnectionHelper.getNetworkState(this).isSuitable()) - break; } - if (db.operation().getOperations(outbox.id).size() == 0) - stopSelf(); - } catch (Throwable ex) { Log.e(outbox.name, ex); db.folder().setFolderError(outbox.id, Log.formatThrowable(ex)); @@ -414,20 +392,27 @@ public class ServiceSend extends ServiceBase { private void onSync(EntityFolder outbox) { DB db = DB.getInstance(this); - db.folder().setFolderError(outbox.id, null); + try { + db.beginTransaction(); + + db.folder().setFolderError(outbox.id, null); - // Restore snooze timers - for (EntityMessage message : db.message().getSnoozed(outbox.id)) - EntityMessage.snooze(this, message.id, message.ui_snoozed); + // Delete pending operations + db.operation().deleteOperations(outbox.id); - // Retry failed message - for (long id : db.message().getMessageByFolder(outbox.id)) { - int ops = db.operation().getOperationCount(outbox.id, id, EntityOperation.SEND); - if (ops == 0) { + // Requeue operations + for (long id : db.message().getMessageByFolder(outbox.id)) { EntityMessage message = db.message().getMessage(id); - if (message != null && message.ui_snoozed == null) - EntityOperation.queue(this, message, EntityOperation.SEND); + if (message != null) + if (message.ui_snoozed == null) + EntityOperation.queue(this, message, EntityOperation.SEND); + else + EntityMessage.snooze(this, message.id, message.ui_snoozed); } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); } } @@ -558,8 +543,6 @@ public class ServiceSend extends ServiceBase { // Send message EntityLog.log(this, "Sending " + via); - if (BuildConfig.DEBUG && false) - throw new SendFailedException("Test"); iservice.getTransport().sendMessage(imessage, to); long time = new Date().getTime(); EntityLog.log(this, "Sent " + via);