From c0983d24f596120d2f8ebe818ed06056f01bcc62 Mon Sep 17 00:00:00 2001 From: M66B Date: Fri, 1 Mar 2019 19:35:30 +0000 Subject: [PATCH] Single sync service --- .../java/eu/faircode/email/ActivityMain.java | 2 +- app/src/main/java/eu/faircode/email/Core.java | 2 +- .../java/eu/faircode/email/DaoAccount.java | 9 +- .../java/eu/faircode/email/DaoOperation.java | 6 + .../eu/faircode/email/EntityOperation.java | 11 +- .../eu/faircode/email/FragmentFolders.java | 6 +- .../eu/faircode/email/FragmentMessages.java | 2 +- .../eu/faircode/email/ServiceSynchronize.java | 85 ++++----- .../java/eu/faircode/email/ServiceUI.java | 177 +----------------- 9 files changed, 62 insertions(+), 238 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/ActivityMain.java b/app/src/main/java/eu/faircode/email/ActivityMain.java index b1593834ad..0e5ddaa0cf 100644 --- a/app/src/main/java/eu/faircode/email/ActivityMain.java +++ b/app/src/main/java/eu/faircode/email/ActivityMain.java @@ -49,7 +49,7 @@ public class ActivityMain extends AppCompatActivity implements FragmentManager.O new SimpleTask>() { @Override protected List onExecute(Context context, Bundle args) { - return DB.getInstance(context).account().getSynchronizingAccounts(); + return DB.getInstance(context).account().getSynchronizingAccounts(true); } @Override diff --git a/app/src/main/java/eu/faircode/email/Core.java b/app/src/main/java/eu/faircode/email/Core.java index 40976f0dff..636aaa1034 100644 --- a/app/src/main/java/eu/faircode/email/Core.java +++ b/app/src/main/java/eu/faircode/email/Core.java @@ -1269,7 +1269,7 @@ class Core { if (update) db.message().updateMessage(message); - else if (BuildConfig.DEBUG) + else if (false && BuildConfig.DEBUG) Log.i(folder.name + " unchanged uid=" + uid); } diff --git a/app/src/main/java/eu/faircode/email/DaoAccount.java b/app/src/main/java/eu/faircode/email/DaoAccount.java index 15df8e51fc..f30a262e64 100644 --- a/app/src/main/java/eu/faircode/email/DaoAccount.java +++ b/app/src/main/java/eu/faircode/email/DaoAccount.java @@ -32,8 +32,13 @@ public interface DaoAccount { @Query("SELECT * FROM account") List getAccounts(); - @Query("SELECT * FROM account WHERE synchronize") - List getSynchronizingAccounts(); + @Query("SELECT account.* FROM account" + + " LEFT JOIN folder ON folder.account = account.id" + // not outbox + " LEFT JOIN operation ON operation.folder = folder.id" + + " WHERE account.synchronize" + + " GROUP BY account.id" + + " HAVING :all OR COUNT(operation.id) > 0") + List getSynchronizingAccounts(boolean all); @Query("SELECT * FROM account WHERE tbd = 1") List getAccountsTbd(); diff --git a/app/src/main/java/eu/faircode/email/DaoOperation.java b/app/src/main/java/eu/faircode/email/DaoOperation.java index 786db60752..79403fd470 100644 --- a/app/src/main/java/eu/faircode/email/DaoOperation.java +++ b/app/src/main/java/eu/faircode/email/DaoOperation.java @@ -85,6 +85,12 @@ public interface DaoOperation { " AND message = :message") int getOperationCount(long folder, long message); + @Query("SELECT COUNT(operation.id) FROM operation" + + " JOIN folder ON folder.id = operation.folder" + + " JOIN account ON account.id = folder.account" + // not outbox + " WHERE account.synchronize") + LiveData livePendingOperationsCount(); + @Query("UPDATE operation SET error = :error WHERE id = :id") int setOperationError(long id, String error); diff --git a/app/src/main/java/eu/faircode/email/EntityOperation.java b/app/src/main/java/eu/faircode/email/EntityOperation.java index 882d9ff770..b1321055d5 100644 --- a/app/src/main/java/eu/faircode/email/EntityOperation.java +++ b/app/src/main/java/eu/faircode/email/EntityOperation.java @@ -123,8 +123,8 @@ public class EntityOperation { if (account == null) // Outbox ServiceSend.start(context); - else if (!"connected".equals(account.state)) - ServiceUI.process(context, fid); + else + ServiceSynchronize.start(context); Log.i("Queued sync folder=" + folder); } @@ -253,11 +253,8 @@ public class EntityOperation { if (SEND.equals(name)) ServiceSend.start(context); - else { - EntityAccount account = db.account().getAccount(message.account); - if (account != null && !"connected".equals(account.state)) - ServiceUI.process(context, operation.folder); - } + else + ServiceSynchronize.start(context); } @Override diff --git a/app/src/main/java/eu/faircode/email/FragmentFolders.java b/app/src/main/java/eu/faircode/email/FragmentFolders.java index f3eddf40a6..ce50a97b9c 100644 --- a/app/src/main/java/eu/faircode/email/FragmentFolders.java +++ b/app/src/main/java/eu/faircode/email/FragmentFolders.java @@ -302,11 +302,7 @@ public class FragmentFolders extends FragmentBase { EntityOperation.sync(context, folder.id); } else { // Folder list - EntityAccount account = db.account().getAccount(aid); - if (account != null && !"connected".equals(account.state)) - ServiceUI.fsync(context, aid); - else - ServiceSynchronize.reload(getContext(), "refresh folders"); + ServiceSynchronize.reload(getContext(), "refresh folders"); } db.setTransactionSuccessful(); diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index 1857478ba6..3bc0af293d 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -966,7 +966,7 @@ public class FragmentMessages extends FragmentBase { if (result.hasTrash == null) result.hasTrash = false; if (result.hasJunk == null) result.hasJunk = false; - result.accounts = db.account().getSynchronizingAccounts(); + result.accounts = db.account().getSynchronizingAccounts(true); final Collator collator = Collator.getInstance(Locale.getDefault()); collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 650d0a5e10..a7ec51f14f 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -97,7 +97,6 @@ public class ServiceSynchronize extends LifecycleService { private static final long RECONNECT_BACKOFF = 90 * 1000L; // milliseconds private static final int ACCOUNT_ERROR_AFTER = 90; // minutes private static final int BACKOFF_ERROR_AFTER = 16; // seconds - private static final long STOP_DELAY = 5000L; // milliseconds static final int PI_ALARM = 1; @@ -131,6 +130,16 @@ public class ServiceSynchronize extends LifecycleService { } }); + db.operation().livePendingOperationsCount().observe(ServiceSynchronize.this, new Observer() { + @Override + public void onChanged(Integer ops) { + Log.i("Pending ops=" + ops); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ServiceSynchronize.this); + boolean enabled = prefs.getBoolean("enabled", true); + serviceManager.quit(!enabled && ops == 0); + } + }); + JobDaily.schedule(this); } @@ -141,8 +150,6 @@ public class ServiceSynchronize extends LifecycleService { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); cm.unregisterNetworkCallback(serviceManager); - serviceManager.service_destroy(); - Widget.update(this, -1); JobDaily.cancel(this); @@ -168,8 +175,8 @@ public class ServiceSynchronize extends LifecycleService { if (action != null) try { switch (action) { - case "init": - serviceManager.service_init(); + case "start": + serviceManager.service_start(); break; case "alarm": @@ -238,6 +245,7 @@ public class ServiceSynchronize extends LifecycleService { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ServiceSynchronize.this); boolean debug = (prefs.getBoolean("debug", false) || BuildConfig.BETA_RELEASE); //System.setProperty("mail.socket.debug", Boolean.toString(debug)); + boolean enabled = prefs.getBoolean("enabled", true); // Create session Properties props = MessageHelper.getSessionProperties(account.auth_type, account.realm, account.insecure); @@ -581,7 +589,8 @@ public class ServiceSynchronize extends LifecycleService { idler.start(); idlers.add(idler); - EntityOperation.sync(this, folder.id); + if (enabled) + EntityOperation.sync(this, folder.id); } else folders.put(folder, null); @@ -868,6 +877,7 @@ public class ServiceSynchronize extends LifecycleService { private Core.State state; private boolean started = false; private int queued = 0; + private boolean quit = false; private long lastLost = 0; private ExecutorService queue = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory); @@ -927,17 +937,11 @@ public class ServiceSynchronize extends LifecycleService { EntityLog.log(ServiceSynchronize.this, "suitable=" + suitable + " metered=" + metered + " isMetered=" + isMetered); - // The connected state is deliberately ignored return suitable; } - private boolean isEnabled() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ServiceSynchronize.this); - return prefs.getBoolean("enabled", true); - } - - private void service_init() { - EntityLog.log(ServiceSynchronize.this, "Service init"); + private void service_start() { + EntityLog.log(ServiceSynchronize.this, "Service start"); // Network events will manage the service } @@ -956,14 +960,6 @@ public class ServiceSynchronize extends LifecycleService { } } - private void service_destroy() { - synchronized (this) { - EntityLog.log(ServiceSynchronize.this, "Service destroy"); - if (started) - queue_reload(false, "service destroy"); - } - } - private void start() { EntityLog.log(ServiceSynchronize.this, "Main start"); @@ -979,6 +975,9 @@ public class ServiceSynchronize extends LifecycleService { try { wl.acquire(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ServiceSynchronize.this); + boolean enabled = prefs.getBoolean("enabled", true); + final DB db = DB.getInstance(ServiceSynchronize.this); long ago = new Date().getTime() - lastLost; @@ -993,7 +992,7 @@ public class ServiceSynchronize extends LifecycleService { } // Start monitoring accounts - List accounts = db.account().getSynchronizingAccounts(); + List accounts = db.account().getSynchronizingAccounts(enabled); for (final EntityAccount account : accounts) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) if (account.notify) @@ -1035,6 +1034,7 @@ public class ServiceSynchronize extends LifecycleService { astate.stop(); for (Core.State astate : threadState) astate.join(); + threadState.clear(); EntityLog.log(ServiceSynchronize.this, "Main exited"); @@ -1063,7 +1063,7 @@ public class ServiceSynchronize extends LifecycleService { private void queue_reload(final boolean start, final String reason) { final boolean doStop = started; - final boolean doStart = (start && isEnabled() && suitableNetwork()); + final boolean doStart = (start && suitableNetwork()); EntityLog.log(ServiceSynchronize.this, "Queue reload" + " doStop=" + doStop + " doStart=" + doStart + " queued=" + queued + " " + reason); @@ -1108,23 +1108,19 @@ public class ServiceSynchronize extends LifecycleService { } finally { queued--; EntityLog.log(ServiceSynchronize.this, "Reload done queued=" + queued); - - if (queued == 0 && !isEnabled()) { - try { - Thread.sleep(STOP_DELAY); - } catch (InterruptedException ignored) { - } - if (queued == 0 && !isEnabled()) { - EntityLog.log(ServiceSynchronize.this, "Service stop"); - stopSelf(); - } - } - + if (queued == 0 && quit) + stopSelf(); wl.release(); } } }); } + + private void quit(boolean quit) { + this.quit = quit; + if (quit && queued == 0) + queue_reload(false, "quit"); + } } private static void schedule(Context context) { @@ -1198,16 +1194,9 @@ public class ServiceSynchronize extends LifecycleService { // Restore schedule schedule(context); - // Conditionally init service - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean enabled = prefs.getBoolean("enabled", true); + // Start service + start(context); - int accounts = db.account().getSynchronizingAccounts().size(); - - if (enabled && accounts > 0) - ContextCompat.startForegroundService(context, - new Intent(context, ServiceSynchronize.class) - .setAction("init")); } catch (Throwable ex) { Log.e(ex); } @@ -1217,6 +1206,12 @@ public class ServiceSynchronize extends LifecycleService { } } + static void start(Context context) { + ContextCompat.startForegroundService(context, + new Intent(context, ServiceSynchronize.class) + .setAction("start")); + } + static void reschedule(Context context) { ContextCompat.startForegroundService(context, new Intent(context, ServiceSynchronize.class) diff --git a/app/src/main/java/eu/faircode/email/ServiceUI.java b/app/src/main/java/eu/faircode/email/ServiceUI.java index d9a8d15796..6b3205954b 100644 --- a/app/src/main/java/eu/faircode/email/ServiceUI.java +++ b/app/src/main/java/eu/faircode/email/ServiceUI.java @@ -9,21 +9,10 @@ import android.net.Uri; import android.os.PowerManager; import android.preference.PreferenceManager; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -import javax.mail.Folder; -import javax.mail.MessagingException; -import javax.mail.Session; -import javax.mail.Store; - import androidx.annotation.Nullable; public class ServiceUI extends IntentService { private PowerManager.WakeLock wl; - private Map accountStore = new HashMap<>(); static final int PI_WHY = 1; static final int PI_SUMMARY = 2; @@ -54,31 +43,7 @@ public class ServiceUI extends IntentService { @Override public void onDestroy() { Log.i("Service UI destroy"); - - final DB db = DB.getInstance(this); - - new Thread(new Runnable() { - @Override - public void run() { - try { - for (EntityAccount account : accountStore.keySet()) - try { - Log.i(account.name + " closing"); - db.account().setAccountState(account.id, "closing"); - accountStore.get(account).close(); - } catch (Throwable ex) { - Log.w(ex); - } finally { - Log.i(account.name + " closed"); - db.account().setAccountState(account.id, null); - } - accountStore.clear(); - } finally { - wl.release(); - } - } - }).start(); - + wl.release(); super.onDestroy(); } @@ -130,14 +95,6 @@ public class ServiceUI extends IntentService { onSnooze(id); break; - case "process": - onProcessOperations(id); - break; - - case "fsync": - onFolderSync(id); - break; - default: Log.w("Unknown action: " + parts[0]); } @@ -267,136 +224,4 @@ public class ServiceUI extends IntentService { db.endTransaction(); } } - - private void onProcessOperations(long fid) { - DB db = DB.getInstance(this); - - EntityFolder folder = db.folder().getFolder(fid); - if (folder == null) - return; - EntityAccount account = db.account().getAccount(folder.account); - if (account == null) - return; - - Folder ifolder = null; - try { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean debug = (prefs.getBoolean("debug", false) || BuildConfig.BETA_RELEASE); - - // Create session - Properties props = MessageHelper.getSessionProperties(account.auth_type, account.realm, account.insecure); - final Session isession = Session.getInstance(props, null); - isession.setDebug(debug); - - Store istore = accountStore.get(account.id); - if (istore == null || !istore.isConnected()) { - // Connect account - Log.i(account.name + " connecting"); - db.account().setAccountState(account.id, "connecting"); - istore = isession.getStore(account.getProtocol()); - Helper.connect(this, istore, account); - db.account().setAccountState(account.id, "connected"); - db.account().setAccountConnected(account.id, new Date().getTime()); - db.account().setAccountError(account.id, null); - Log.i(account.name + " connected"); - - accountStore.put(account, istore); - } else - Log.i(account + " reusing connection"); - - // Connect folder - Log.i(folder.name + " connecting"); - db.folder().setFolderState(folder.id, "connecting"); - ifolder = istore.getFolder(folder.name); - ifolder.open(Folder.READ_WRITE); - db.folder().setFolderState(folder.id, "connected"); - db.folder().setFolderError(folder.id, null); - Log.i(folder.name + " connected"); - - // Process operations - Core.processOperations(this, account, folder, isession, istore, ifolder, new Core.State()); - - } catch (Throwable ex) { - Log.w(ex); - Core.reportError(this, account, folder, ex); - db.account().setAccountError(account.id, Helper.formatThrowable(ex)); - db.folder().setFolderError(folder.id, Helper.formatThrowable(ex, false)); - } finally { - if (ifolder != null) - try { - Log.i(folder.name + " closing"); - db.folder().setFolderState(folder.id, "closing"); - ifolder.close(); - } catch (MessagingException ex) { - Log.w(ex); - } finally { - Log.i(folder.name + " closed"); - db.folder().setFolderState(folder.id, null); - db.folder().setFolderSyncState(folder.id, null); - } - } - } - - private void onFolderSync(long aid) { - DB db = DB.getInstance(this); - EntityAccount account = db.account().getAccount(aid); - if (account == null) - return; - - Store istore = null; - try { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean debug = (prefs.getBoolean("debug", false) || BuildConfig.BETA_RELEASE); - - // Create session - Properties props = MessageHelper.getSessionProperties(account.auth_type, account.realm, account.insecure); - final Session isession = Session.getInstance(props, null); - isession.setDebug(debug); - - // Connect account - Log.i(account.name + " connecting"); - db.account().setAccountState(account.id, "connecting"); - istore = isession.getStore(account.getProtocol()); - Helper.connect(this, istore, account); - db.account().setAccountState(account.id, "connected"); - db.account().setAccountConnected(account.id, new Date().getTime()); - db.account().setAccountError(account.id, null); - Log.i(account.name + " connected"); - - // Synchronize folders - Core.onSynchronizeFolders(this, account, istore, new Core.State()); - - } catch (Throwable ex) { - Log.w(ex); - Core.reportError(this, account, null, ex); - db.account().setAccountError(account.id, Helper.formatThrowable(ex)); - } finally { - if (istore != null) { - Log.i(account.name + " closing"); - db.account().setAccountState(account.id, "closing"); - - try { - istore.close(); - } catch (MessagingException ex) { - Log.e(ex); - } - - Log.i(account.name + " closed"); - } - - db.account().setAccountState(account.id, null); - } - } - - public static void process(Context context, long folder) { - context.startService( - new Intent(context, ServiceUI.class) - .setAction("process:" + folder)); - } - - public static void fsync(Context context, long account) { - context.startService( - new Intent(context, ServiceUI.class) - .setAction("fsync:" + account)); - } }