mirror of
https://github.com/M66B/FairEmail.git
synced 2025-02-28 00:35:58 +00:00
Individual account management proof of concept
This commit is contained in:
parent
f7e522e042
commit
062d2e0deb
4 changed files with 206 additions and 41 deletions
|
@ -71,10 +71,10 @@ public interface DaoAccount {
|
||||||
|
|
||||||
@Query("SELECT account.*" +
|
@Query("SELECT account.*" +
|
||||||
", SUM(folder.synchronize) AS folders" +
|
", SUM(folder.synchronize) AS folders" +
|
||||||
", COUNT(operation.id) AS operations" +
|
", (SELECT COUNT(id) FROM operation" +
|
||||||
|
" WHERE operation.account = account.id AND operation.name <> '" + EntityOperation.SEND + "') AS operations" +
|
||||||
" FROM account" +
|
" FROM account" +
|
||||||
" LEFT JOIN folder ON folder.account = account.id" +
|
" LEFT JOIN folder ON folder.account = account.id" +
|
||||||
" LEFT JOIN operation ON operation.folder = folder.id" +
|
|
||||||
" GROUP BY account.id" +
|
" GROUP BY account.id" +
|
||||||
" ORDER BY account.id")
|
" ORDER BY account.id")
|
||||||
LiveData<List<TupleAccountState>> liveAccountState();
|
LiveData<List<TupleAccountState>> liveAccountState();
|
||||||
|
|
|
@ -51,11 +51,13 @@ import com.sun.mail.imap.IMAPFolder;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Hashtable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -84,7 +86,7 @@ import me.leolin.shortcutbadger.ShortcutBadger;
|
||||||
|
|
||||||
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
|
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
|
||||||
|
|
||||||
public class ServiceSynchronize extends ServiceBase {
|
public class ServiceSynchronize extends ServiceBase implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private ConnectionHelper.NetworkState networkState = new ConnectionHelper.NetworkState();
|
private ConnectionHelper.NetworkState networkState = new ConnectionHelper.NetworkState();
|
||||||
private Core.State state = null;
|
private Core.State state = null;
|
||||||
private int lastStartId = -1;
|
private int lastStartId = -1;
|
||||||
|
@ -120,21 +122,25 @@ public class ServiceSynchronize extends ServiceBase {
|
||||||
private ConnectionHelper.NetworkState lastNetworkState = null;
|
private ConnectionHelper.NetworkState lastNetworkState = null;
|
||||||
private List<TupleAccountState> lastAccountStates = null;
|
private List<TupleAccountState> lastAccountStates = null;
|
||||||
|
|
||||||
|
private void post(boolean reload) {
|
||||||
|
post(reload, lastNetworkState, lastAccountStates);
|
||||||
|
}
|
||||||
|
|
||||||
private void post(ConnectionHelper.NetworkState networkState) {
|
private void post(ConnectionHelper.NetworkState networkState) {
|
||||||
lastNetworkState = networkState;
|
lastNetworkState = networkState;
|
||||||
post(lastNetworkState, lastAccountStates);
|
post(false, lastNetworkState, lastAccountStates);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void post(List<TupleAccountState> accountStates) {
|
private void post(List<TupleAccountState> accountStates) {
|
||||||
lastAccountStates = accountStates;
|
lastAccountStates = accountStates;
|
||||||
post(lastNetworkState, lastAccountStates);
|
post(false, lastNetworkState, lastAccountStates);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void post(ConnectionHelper.NetworkState networkState, List<TupleAccountState> accountStates) {
|
private void post(boolean reload, ConnectionHelper.NetworkState networkState, List<TupleAccountState> accountStates) {
|
||||||
if (networkState != null && accountStates != null) {
|
if (networkState != null && accountStates != null) {
|
||||||
List<TupleAccountNetworkState> result = new ArrayList<>();
|
List<TupleAccountNetworkState> result = new ArrayList<>();
|
||||||
for (TupleAccountState accountState : accountStates)
|
for (TupleAccountState accountState : accountStates)
|
||||||
result.add(new TupleAccountNetworkState(networkState, accountState));
|
result.add(new TupleAccountNetworkState(reload, networkState, accountState));
|
||||||
postValue(result);
|
postValue(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,12 +168,13 @@ public class ServiceSynchronize extends ServiceBase {
|
||||||
|
|
||||||
DB db = DB.getInstance(this);
|
DB db = DB.getInstance(this);
|
||||||
|
|
||||||
db.account().liveAccountState().observe(this, new Observer<List<TupleAccountState>>() {
|
if (BuildConfig.DEBUG)
|
||||||
@Override
|
db.account().liveAccountState().observe(this, new Observer<List<TupleAccountState>>() {
|
||||||
public void onChanged(List<TupleAccountState> accountStates) {
|
@Override
|
||||||
liveAccountState.postValue(accountStates);
|
public void onChanged(List<TupleAccountState> accountStates) {
|
||||||
}
|
liveAccountState.postValue(accountStates);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
liveAccountNetworkState.addSource(liveNetworkState, new Observer<ConnectionHelper.NetworkState>() {
|
liveAccountNetworkState.addSource(liveNetworkState, new Observer<ConnectionHelper.NetworkState>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -184,9 +191,96 @@ public class ServiceSynchronize extends ServiceBase {
|
||||||
});
|
});
|
||||||
|
|
||||||
liveAccountNetworkState.observe(this, new Observer<List<TupleAccountNetworkState>>() {
|
liveAccountNetworkState.observe(this, new Observer<List<TupleAccountNetworkState>>() {
|
||||||
|
private List<TupleAccountNetworkState> accountStates = new ArrayList<>();
|
||||||
|
private Map<TupleAccountNetworkState, Core.State> serviceStates = new Hashtable<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChanged(List<TupleAccountNetworkState> accountNetworkStates) {
|
public void onChanged(List<TupleAccountNetworkState> accountNetworkStates) {
|
||||||
Log.i("Account network states=" + accountNetworkStates.size());
|
boolean running = false;
|
||||||
|
|
||||||
|
for (TupleAccountNetworkState current : accountNetworkStates) {
|
||||||
|
if (current.accountState.shouldRun())
|
||||||
|
running = true;
|
||||||
|
|
||||||
|
int index = accountStates.indexOf(current);
|
||||||
|
if (index < 0) {
|
||||||
|
if (current.shouldRun())
|
||||||
|
start(current);
|
||||||
|
} else {
|
||||||
|
TupleAccountNetworkState prev = accountStates.get(index);
|
||||||
|
accountStates.remove(index);
|
||||||
|
|
||||||
|
if (current.reload ||
|
||||||
|
!prev.accountState.equals(current.accountState) ||
|
||||||
|
prev.shouldRun() != current.shouldRun()) {
|
||||||
|
Log.i("XXX account=" + current +
|
||||||
|
" reload=" + current.reload + " change=" + !prev.accountState.equals(current.accountState));
|
||||||
|
if (prev.shouldRun())
|
||||||
|
stop(prev);
|
||||||
|
if (current.shouldRun())
|
||||||
|
start(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accountStates.add(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!running)
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start(final TupleAccountNetworkState accountNetworkState) {
|
||||||
|
queue.submit(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
long ago = new Date().getTime() - lastLost;
|
||||||
|
if (ago < RECONNECT_BACKOFF)
|
||||||
|
try {
|
||||||
|
long backoff = RECONNECT_BACKOFF - ago;
|
||||||
|
EntityLog.log(ServiceSynchronize.this, accountNetworkState + " backoff=" + (backoff / 1000));
|
||||||
|
Thread.sleep(backoff);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Log.w(accountNetworkState + " backoff " + ex.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
if (accountNetworkState.accountState.notify)
|
||||||
|
accountNetworkState.accountState.createNotificationChannel(ServiceSynchronize.this);
|
||||||
|
else
|
||||||
|
accountNetworkState.accountState.deleteNotificationChannel(ServiceSynchronize.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(accountNetworkState.accountState.host + "/" + accountNetworkState.accountState.user + " run");
|
||||||
|
final Core.State astate = new Core.State(state);
|
||||||
|
astate.runnable(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
monitorAccount(accountNetworkState.accountState, astate, sync);
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
Log.e(accountNetworkState.accountState.name, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "sync.account." + accountNetworkState.accountState.id);
|
||||||
|
astate.start();
|
||||||
|
Log.i("XXX started=" + accountNetworkState);
|
||||||
|
serviceStates.put(accountNetworkState, astate);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stop(final TupleAccountNetworkState accountNetworkState) {
|
||||||
|
final Core.State state = serviceStates.get(accountNetworkState);
|
||||||
|
serviceStates.remove(accountNetworkState);
|
||||||
|
queue.submit(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Log.i("XXX stop=" + accountNetworkState);
|
||||||
|
state.stop();
|
||||||
|
state.join();
|
||||||
|
Log.i("XXX stopped=" + accountNetworkState);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -329,12 +423,30 @@ public class ServiceSynchronize extends ServiceBase {
|
||||||
last = current;
|
last = current;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final List<String> POST_EVAL =
|
||||||
|
Collections.unmodifiableList(Arrays.asList("metered", "roaming", "rlah"));
|
||||||
|
private static final List<String> POST_RELOAD =
|
||||||
|
Collections.unmodifiableList(Arrays.asList("socks_enabled", "socks_proxy", "subscribed_only", "debug"));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||||
|
if (POST_EVAL.contains(key))
|
||||||
|
liveAccountNetworkState.post(true);
|
||||||
|
else if (POST_RELOAD.contains(key))
|
||||||
|
liveAccountNetworkState.post(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
EntityLog.log(this, "Service destroy");
|
EntityLog.log(this, "Service destroy");
|
||||||
|
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||||
|
|
||||||
unregisterReceiver(onScreenOff);
|
unregisterReceiver(onScreenOff);
|
||||||
unregisterReceiver(connectionChangedReceiver);
|
unregisterReceiver(connectionChangedReceiver);
|
||||||
|
|
||||||
|
@ -661,29 +773,31 @@ public class ServiceSynchronize extends ServiceBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start monitoring accounts
|
// Start monitoring accounts
|
||||||
List<EntityAccount> accounts = db.account().getSynchronizingAccounts();
|
if (!BuildConfig.DEBUG) {
|
||||||
for (final EntityAccount account : accounts) {
|
List<EntityAccount> accounts = db.account().getSynchronizingAccounts();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
for (final EntityAccount account : accounts) {
|
||||||
if (account.notify)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
account.createNotificationChannel(ServiceSynchronize.this);
|
if (account.notify)
|
||||||
else
|
account.createNotificationChannel(ServiceSynchronize.this);
|
||||||
account.deleteNotificationChannel(ServiceSynchronize.this);
|
else
|
||||||
}
|
account.deleteNotificationChannel(ServiceSynchronize.this);
|
||||||
|
|
||||||
Log.i(account.host + "/" + account.user + " run");
|
|
||||||
final Core.State astate = new Core.State(state);
|
|
||||||
astate.runnable(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
monitorAccount(account, astate, sync);
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
Log.e(account.name, ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, "sync.account." + account.id);
|
|
||||||
astate.start();
|
Log.i(account.host + "/" + account.user + " run");
|
||||||
state.childs.add(astate);
|
final Core.State astate = new Core.State(state);
|
||||||
|
astate.runnable(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
monitorAccount(account, astate, sync);
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
Log.e(account.name, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "sync.account." + account.id);
|
||||||
|
astate.start();
|
||||||
|
state.childs.add(astate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityLog.log(ServiceSynchronize.this, "Main started");
|
EntityLog.log(ServiceSynchronize.this, "Main started");
|
||||||
|
@ -964,6 +1078,8 @@ public class ServiceSynchronize extends ServiceBase {
|
||||||
|
|
||||||
if (ex.getCause() instanceof BadCommandException)
|
if (ex.getCause() instanceof BadCommandException)
|
||||||
throw ex;
|
throw ex;
|
||||||
|
if ("connection failure".equals(ex.getMessage()))
|
||||||
|
throw ex;
|
||||||
|
|
||||||
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
|
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
|
||||||
continue;
|
continue;
|
||||||
|
@ -1475,7 +1591,8 @@ public class ServiceSynchronize extends ServiceBase {
|
||||||
|
|
||||||
private void updateState() {
|
private void updateState() {
|
||||||
ConnectionHelper.NetworkState ns = ConnectionHelper.getNetworkState(ServiceSynchronize.this);
|
ConnectionHelper.NetworkState ns = ConnectionHelper.getNetworkState(ServiceSynchronize.this);
|
||||||
liveNetworkState.postValue(ns);
|
if (BuildConfig.DEBUG)
|
||||||
|
liveNetworkState.postValue(ns);
|
||||||
networkState.update(ns);
|
networkState.update(ns);
|
||||||
|
|
||||||
if (lastSuitable == null || lastSuitable != networkState.isSuitable()) {
|
if (lastSuitable == null || lastSuitable != networkState.isSuitable()) {
|
||||||
|
|
|
@ -19,13 +19,43 @@ package eu.faircode.email;
|
||||||
Copyright 2018-2019 by Marcel Bokhorst (M66B)
|
Copyright 2018-2019 by Marcel Bokhorst (M66B)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
public class TupleAccountNetworkState {
|
public class TupleAccountNetworkState {
|
||||||
|
public boolean reload;
|
||||||
public ConnectionHelper.NetworkState networkState;
|
public ConnectionHelper.NetworkState networkState;
|
||||||
public TupleAccountState accountState;
|
public TupleAccountState accountState;
|
||||||
|
|
||||||
public TupleAccountNetworkState(ConnectionHelper.NetworkState networkState, TupleAccountState accountState) {
|
public TupleAccountNetworkState(boolean reload, ConnectionHelper.NetworkState networkState, TupleAccountState accountState) {
|
||||||
|
this.reload = reload;
|
||||||
this.networkState = networkState;
|
this.networkState = networkState;
|
||||||
this.accountState = accountState;
|
this.accountState = accountState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean shouldRun() {
|
||||||
|
return (this.networkState.isSuitable() &&
|
||||||
|
this.accountState.shouldRun());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (obj instanceof TupleAccountNetworkState) {
|
||||||
|
TupleAccountNetworkState other = (TupleAccountNetworkState) obj;
|
||||||
|
return this.accountState.id.equals(other.accountState.id);
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return accountState.id.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return accountState.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,10 @@ package eu.faircode.email;
|
||||||
Copyright 2018-2019 by Marcel Bokhorst (M66B)
|
Copyright 2018-2019 by Marcel Bokhorst (M66B)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class TupleAccountState extends EntityAccount {
|
public class TupleAccountState extends EntityAccount {
|
||||||
|
// TODO: folder property changes (name, synchronize, poll)
|
||||||
public int folders;
|
public int folders;
|
||||||
public int operations;
|
public int operations;
|
||||||
|
|
||||||
|
@ -27,10 +30,25 @@ public class TupleAccountState extends EntityAccount {
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof TupleAccountState) {
|
if (obj instanceof TupleAccountState) {
|
||||||
TupleAccountState other = (TupleAccountState) obj;
|
TupleAccountState other = (TupleAccountState) obj;
|
||||||
return (super.equals(obj) && // TODO selected attributes
|
return (this.host.equals(other.host) &&
|
||||||
this.folders == other.folders &&
|
this.starttls == other.starttls &&
|
||||||
this.operations == other.operations);
|
this.insecure == other.insecure &&
|
||||||
|
this.port.equals(other.port) &&
|
||||||
|
// auth_type
|
||||||
|
this.user.equals(other.user) &&
|
||||||
|
this.password.equals(other.password) &&
|
||||||
|
Objects.equals(this.realm, other.realm) &&
|
||||||
|
this.notify == other.notify &&
|
||||||
|
this.poll_interval.equals(other.poll_interval) &&
|
||||||
|
this.partial_fetch == other.partial_fetch &&
|
||||||
|
this.ignore_size == other.ignore_size &&
|
||||||
|
this.use_date == other.use_date &&
|
||||||
|
this.folders == other.folders);
|
||||||
} else
|
} else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean shouldRun() {
|
||||||
|
return (synchronize && (folders > 0 || operations > 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue