mirror of https://github.com/M66B/FairEmail.git
Single sync service
This commit is contained in:
parent
67ec1908aa
commit
c0983d24f5
|
@ -49,7 +49,7 @@ public class ActivityMain extends AppCompatActivity implements FragmentManager.O
|
|||
new SimpleTask<List<EntityAccount>>() {
|
||||
@Override
|
||||
protected List<EntityAccount> onExecute(Context context, Bundle args) {
|
||||
return DB.getInstance(context).account().getSynchronizingAccounts();
|
||||
return DB.getInstance(context).account().getSynchronizingAccounts(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -32,8 +32,13 @@ public interface DaoAccount {
|
|||
@Query("SELECT * FROM account")
|
||||
List<EntityAccount> getAccounts();
|
||||
|
||||
@Query("SELECT * FROM account WHERE synchronize")
|
||||
List<EntityAccount> 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<EntityAccount> getSynchronizingAccounts(boolean all);
|
||||
|
||||
@Query("SELECT * FROM account WHERE tbd = 1")
|
||||
List<EntityAccount> getAccountsTbd();
|
||||
|
|
|
@ -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<Integer> livePendingOperationsCount();
|
||||
|
||||
@Query("UPDATE operation SET error = :error WHERE id = :id")
|
||||
int setOperationError(long id, String error);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Integer>() {
|
||||
@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<EntityAccount> accounts = db.account().getSynchronizingAccounts();
|
||||
List<EntityAccount> 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)
|
||||
|
|
|
@ -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<EntityAccount, Store> 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));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue