Retry send on connectivity change

pull/162/head
M66B 5 years ago
parent ea4c7fa8f2
commit 7ed2db16cd

@ -31,6 +31,7 @@ import android.net.NetworkRequest;
import android.os.PowerManager;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.Observer;
@ -60,8 +61,8 @@ import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
public class ServiceSend extends ServiceBase {
private int lastUnsent = 0;
private boolean lastSuitable = false;
private TwoStateOwner cowner;
private PowerManager.WakeLock wlOutbox;
private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
private static final int IDENTITY_ERROR_AFTER = 30; // minutes
@ -72,11 +73,11 @@ public class ServiceSend extends ServiceBase {
super.onCreate();
startForeground(Helper.NOTIFICATION_SEND, getNotificationService(null, null).build());
cowner = new TwoStateOwner(ServiceSend.this, "send");
final DB db = DB.getInstance(this);
final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wlOutbox = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":send");
// Observe unsent count
DB db = DB.getInstance(this);
db.operation().liveUnsent().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer unsent) {
@ -86,10 +87,8 @@ public class ServiceSend extends ServiceBase {
});
// Observe send operations
db.operation().liveOperations(null).observe(cowner, new Observer<List<EntityOperation>>() {
db.operation().liveOperations(null).observe(this, new Observer<List<EntityOperation>>() {
private List<Long> handling = new ArrayList<>();
private PowerManager.WakeLock wlFolder = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":send");
@Override
public void onChanged(final List<EntityOperation> operations) {
@ -112,95 +111,7 @@ public class ServiceSend extends ServiceBase {
executor.submit(new Runnable() {
@Override
public void run() {
try {
wlFolder.acquire();
EntityFolder outbox = db.folder().getOutbox();
try {
db.folder().setFolderError(outbox.id, null);
db.folder().setFolderSyncState(outbox.id, "syncing");
List<EntityOperation> ops = db.operation().getOperations(outbox.id);
Log.i(outbox.name + " pending operations=" + ops.size());
for (EntityOperation op : ops) {
EntityMessage message = null;
try {
Log.i(outbox.name +
" start op=" + op.id + "/" + op.name +
" msg=" + op.message +
" args=" + op.args);
Map<String, String> crumb = new HashMap<>();
crumb.put("name", op.name);
crumb.put("args", op.args);
crumb.put("folder", op.folder + ":outbox");
if (op.message != null)
crumb.put("message", Long.toString(op.message));
crumb.put("free", Integer.toString(Log.getFreeMemMb()));
Log.breadcrumb("operation", crumb);
switch (op.name) {
case EntityOperation.SYNC:
db.folder().setFolderError(outbox.id, null);
break;
case EntityOperation.SEND:
message = db.message().getMessage(op.message);
if (message == null)
throw new MessageRemovedException();
onSend(message);
break;
case EntityOperation.ANSWERED:
break;
default:
throw new IllegalArgumentException("Unknown operation=" + op.name);
}
db.operation().deleteOperation(op.id);
} catch (Throwable ex) {
Log.e(outbox.name, ex);
EntityLog.log(
ServiceSend.this,
outbox.name + " " + Helper.formatThrowable(ex, false));
db.operation().setOperationError(op.id, Helper.formatThrowable(ex));
if (message != null)
db.message().setMessageError(message.id, Helper.formatThrowable(ex));
if (ex instanceof OutOfMemoryError ||
ex instanceof MessageRemovedException ||
ex instanceof FileNotFoundException ||
ex instanceof SendFailedException ||
ex instanceof IllegalArgumentException) {
Log.w("Unrecoverable");
db.operation().deleteOperation(op.id);
continue;
} else
throw ex;
} finally {
Log.i(outbox.name + " end op=" + op.id + "/" + op.name);
}
if (!ConnectionHelper.getNetworkState(ServiceSend.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, Helper.formatThrowable(ex));
} finally {
db.folder().setFolderState(outbox.id, null);
db.folder().setFolderSyncState(outbox.id, null);
}
} finally {
wlFolder.release();
}
processOperations();
}
});
}
@ -269,32 +180,136 @@ public class ServiceSend extends ServiceBase {
}
ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
private boolean suitable = false;
@Override
public void onAvailable(Network network) {
Log.i("Service send available=" + network);
check();
checkConnectivity();
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities caps) {
Log.i("Service send network=" + network + " caps=" + caps);
check();
checkConnectivity();
}
@Override
public void onLost(@NonNull Network network) {
Log.i("Service send lost=" + network);
checkConnectivity();
}
private void check() {
boolean suitable = ConnectionHelper.getNetworkState(ServiceSend.this).isSuitable();
Log.i("OUTBOX suitable=" + suitable);
private void checkConnectivity() {
boolean current = ConnectionHelper.getNetworkState(ServiceSend.this).isSuitable();
if (suitable != current) {
suitable = current;
EntityLog.log(ServiceSend.this, "Service send suitable=" + suitable);
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(Helper.NOTIFICATION_SEND, getNotificationService(null, suitable).build());
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(Helper.NOTIFICATION_SEND, getNotificationService(null, suitable).build());
if (suitable)
cowner.start();
else
cowner.stop();
if (suitable)
executor.submit(new Runnable() {
@Override
public void run() {
processOperations();
}
});
}
}
};
private void processOperations() {
try {
wlOutbox.acquire();
DB db = DB.getInstance(this);
EntityFolder outbox = db.folder().getOutbox();
try {
db.folder().setFolderError(outbox.id, null);
db.folder().setFolderSyncState(outbox.id, "syncing");
List<EntityOperation> ops = db.operation().getOperations(outbox.id);
Log.i(outbox.name + " pending operations=" + ops.size());
for (EntityOperation op : ops) {
EntityMessage message = null;
try {
Log.i(outbox.name +
" start op=" + op.id + "/" + op.name +
" msg=" + op.message +
" args=" + op.args);
Map<String, String> crumb = new HashMap<>();
crumb.put("name", op.name);
crumb.put("args", op.args);
crumb.put("folder", op.folder + ":outbox");
if (op.message != null)
crumb.put("message", Long.toString(op.message));
crumb.put("free", Integer.toString(Log.getFreeMemMb()));
Log.breadcrumb("operation", crumb);
switch (op.name) {
case EntityOperation.SYNC:
db.folder().setFolderError(outbox.id, null);
break;
case EntityOperation.SEND:
message = db.message().getMessage(op.message);
if (message == null)
throw new MessageRemovedException();
onSend(message);
break;
case EntityOperation.ANSWERED:
break;
default:
throw new IllegalArgumentException("Unknown operation=" + op.name);
}
db.operation().deleteOperation(op.id);
} catch (Throwable ex) {
Log.e(outbox.name, ex);
EntityLog.log(this, outbox.name + " " + Helper.formatThrowable(ex, false));
db.operation().setOperationError(op.id, Helper.formatThrowable(ex));
if (message != null)
db.message().setMessageError(message.id, Helper.formatThrowable(ex));
if (ex instanceof OutOfMemoryError ||
ex instanceof MessageRemovedException ||
ex instanceof FileNotFoundException ||
ex instanceof SendFailedException ||
ex instanceof IllegalArgumentException) {
Log.w("Unrecoverable");
db.operation().deleteOperation(op.id);
continue;
} else
throw ex;
} finally {
Log.i(outbox.name + " end op=" + op.id + "/" + op.name);
}
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, Helper.formatThrowable(ex));
} finally {
db.folder().setFolderState(outbox.id, null);
db.folder().setFolderSyncState(outbox.id, null);
}
} finally {
wlOutbox.release();
}
}
private void onSend(EntityMessage message) throws MessagingException, IOException {
DB db = DB.getInstance(this);
@ -367,13 +382,15 @@ public class ServiceSend extends ServiceBase {
iservice.connect(ident);
db.identity().setIdentityState(ident.id, "connected");
// Send message
Address[] to = imessage.getAllRecipients();
String via = "via " + ident.host + "/" + ident.user +
" to " + TextUtils.join(", ", to);
// Send message
EntityLog.log(this, "Sending " + via);
iservice.getTransport().sendMessage(imessage, to);
long time = new Date().getTime();
EntityLog.log(this,
"Sent via " + ident.host + "/" + ident.user +
" to " + TextUtils.join(", ", to));
EntityLog.log(this, "Sent " + via);
try {
db.beginTransaction();

Loading…
Cancel
Save