mirror of
https://github.com/M66B/FairEmail.git
synced 2025-02-27 16:33:47 +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.*" +
|
||||
", 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" +
|
||||
" LEFT JOIN folder ON folder.account = account.id" +
|
||||
" LEFT JOIN operation ON operation.folder = folder.id" +
|
||||
" GROUP BY account.id" +
|
||||
" ORDER BY account.id")
|
||||
LiveData<List<TupleAccountState>> liveAccountState();
|
||||
|
|
|
@ -51,11 +51,13 @@ import com.sun.mail.imap.IMAPFolder;
|
|||
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
@ -84,7 +86,7 @@ import me.leolin.shortcutbadger.ShortcutBadger;
|
|||
|
||||
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 Core.State state = null;
|
||||
private int lastStartId = -1;
|
||||
|
@ -120,21 +122,25 @@ public class ServiceSynchronize extends ServiceBase {
|
|||
private ConnectionHelper.NetworkState lastNetworkState = null;
|
||||
private List<TupleAccountState> lastAccountStates = null;
|
||||
|
||||
private void post(boolean reload) {
|
||||
post(reload, lastNetworkState, lastAccountStates);
|
||||
}
|
||||
|
||||
private void post(ConnectionHelper.NetworkState networkState) {
|
||||
lastNetworkState = networkState;
|
||||
post(lastNetworkState, lastAccountStates);
|
||||
post(false, lastNetworkState, lastAccountStates);
|
||||
}
|
||||
|
||||
private void post(List<TupleAccountState> 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) {
|
||||
List<TupleAccountNetworkState> result = new ArrayList<>();
|
||||
for (TupleAccountState accountState : accountStates)
|
||||
result.add(new TupleAccountNetworkState(networkState, accountState));
|
||||
result.add(new TupleAccountNetworkState(reload, networkState, accountState));
|
||||
postValue(result);
|
||||
}
|
||||
}
|
||||
|
@ -162,12 +168,13 @@ public class ServiceSynchronize extends ServiceBase {
|
|||
|
||||
DB db = DB.getInstance(this);
|
||||
|
||||
db.account().liveAccountState().observe(this, new Observer<List<TupleAccountState>>() {
|
||||
@Override
|
||||
public void onChanged(List<TupleAccountState> accountStates) {
|
||||
liveAccountState.postValue(accountStates);
|
||||
}
|
||||
});
|
||||
if (BuildConfig.DEBUG)
|
||||
db.account().liveAccountState().observe(this, new Observer<List<TupleAccountState>>() {
|
||||
@Override
|
||||
public void onChanged(List<TupleAccountState> accountStates) {
|
||||
liveAccountState.postValue(accountStates);
|
||||
}
|
||||
});
|
||||
|
||||
liveAccountNetworkState.addSource(liveNetworkState, new Observer<ConnectionHelper.NetworkState>() {
|
||||
@Override
|
||||
|
@ -184,9 +191,96 @@ public class ServiceSynchronize extends ServiceBase {
|
|||
});
|
||||
|
||||
liveAccountNetworkState.observe(this, new Observer<List<TupleAccountNetworkState>>() {
|
||||
private List<TupleAccountNetworkState> accountStates = new ArrayList<>();
|
||||
private Map<TupleAccountNetworkState, Core.State> serviceStates = new Hashtable<>();
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
public void onDestroy() {
|
||||
EntityLog.log(this, "Service destroy");
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
|
||||
unregisterReceiver(onScreenOff);
|
||||
unregisterReceiver(connectionChangedReceiver);
|
||||
|
||||
|
@ -661,29 +773,31 @@ public class ServiceSynchronize extends ServiceBase {
|
|||
}
|
||||
|
||||
// Start monitoring accounts
|
||||
List<EntityAccount> accounts = db.account().getSynchronizingAccounts();
|
||||
for (final EntityAccount account : accounts) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (account.notify)
|
||||
account.createNotificationChannel(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);
|
||||
}
|
||||
if (!BuildConfig.DEBUG) {
|
||||
List<EntityAccount> accounts = db.account().getSynchronizingAccounts();
|
||||
for (final EntityAccount account : accounts) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (account.notify)
|
||||
account.createNotificationChannel(ServiceSynchronize.this);
|
||||
else
|
||||
account.deleteNotificationChannel(ServiceSynchronize.this);
|
||||
}
|
||||
}, "sync.account." + account.id);
|
||||
astate.start();
|
||||
state.childs.add(astate);
|
||||
|
||||
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();
|
||||
state.childs.add(astate);
|
||||
}
|
||||
}
|
||||
|
||||
EntityLog.log(ServiceSynchronize.this, "Main started");
|
||||
|
@ -964,6 +1078,8 @@ public class ServiceSynchronize extends ServiceBase {
|
|||
|
||||
if (ex.getCause() instanceof BadCommandException)
|
||||
throw ex;
|
||||
if ("connection failure".equals(ex.getMessage()))
|
||||
throw ex;
|
||||
|
||||
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
|
||||
continue;
|
||||
|
@ -1475,7 +1591,8 @@ public class ServiceSynchronize extends ServiceBase {
|
|||
|
||||
private void updateState() {
|
||||
ConnectionHelper.NetworkState ns = ConnectionHelper.getNetworkState(ServiceSynchronize.this);
|
||||
liveNetworkState.postValue(ns);
|
||||
if (BuildConfig.DEBUG)
|
||||
liveNetworkState.postValue(ns);
|
||||
networkState.update(ns);
|
||||
|
||||
if (lastSuitable == null || lastSuitable != networkState.isSuitable()) {
|
||||
|
|
|
@ -19,13 +19,43 @@ package eu.faircode.email;
|
|||
Copyright 2018-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class TupleAccountNetworkState {
|
||||
public boolean reload;
|
||||
public ConnectionHelper.NetworkState networkState;
|
||||
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.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)
|
||||
*/
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class TupleAccountState extends EntityAccount {
|
||||
// TODO: folder property changes (name, synchronize, poll)
|
||||
public int folders;
|
||||
public int operations;
|
||||
|
||||
|
@ -27,10 +30,25 @@ public class TupleAccountState extends EntityAccount {
|
|||
public boolean equals(Object obj) {
|
||||
if (obj instanceof TupleAccountState) {
|
||||
TupleAccountState other = (TupleAccountState) obj;
|
||||
return (super.equals(obj) && // TODO selected attributes
|
||||
this.folders == other.folders &&
|
||||
this.operations == other.operations);
|
||||
return (this.host.equals(other.host) &&
|
||||
this.starttls == other.starttls &&
|
||||
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
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean shouldRun() {
|
||||
return (synchronize && (folders > 0 || operations > 0));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue