model: Proxy all async work through the TunnelManager

Signed-off-by: Samuel Holland <samuel@sholland.org>
This commit is contained in:
Samuel Holland 2018-01-06 06:30:41 -06:00
parent 2315a699fb
commit 22bdffcecd
3 changed files with 69 additions and 70 deletions

View File

@ -198,7 +198,7 @@ public class TunnelListFragment extends BaseFragment {
case R.id.menu_action_delete: case R.id.menu_action_delete:
final CompletableFuture[] futures = getCheckedPositions() final CompletableFuture[] futures = getCheckedPositions()
.mapToObj(pos -> (Tunnel) tunnelList.getItemAtPosition(pos)) .mapToObj(pos -> (Tunnel) tunnelList.getItemAtPosition(pos))
.map(tunnelManager::delete) .map(Tunnel::delete)
.toArray(CompletableFuture[]::new); .toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures) CompletableFuture.allOf(futures)
.thenApply(x -> futures.length) .thenApply(x -> futures.length)

View File

@ -6,13 +6,10 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.wireguard.android.BR; import com.wireguard.android.BR;
import com.wireguard.android.backend.Backend;
import com.wireguard.android.configStore.ConfigStore;
import com.wireguard.android.util.ExceptionLoggers; import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.util.Keyed; import com.wireguard.android.util.Keyed;
import com.wireguard.config.Config; import com.wireguard.config.Config;
import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java9.util.concurrent.CompletableFuture; import java9.util.concurrent.CompletableFuture;
@ -25,19 +22,16 @@ import java9.util.concurrent.CompletionStage;
public class Tunnel extends BaseObservable implements Keyed<String> { public class Tunnel extends BaseObservable implements Keyed<String> {
public static final int NAME_MAX_LENGTH = 16; public static final int NAME_MAX_LENGTH = 16;
private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,16}"); private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,16}");
private static final String TAG = Tunnel.class.getSimpleName();
private final Backend backend; private final TunnelManager manager;
private final ConfigStore configStore;
private final String name; private final String name;
private Config config; private Config config;
private State state; private State state;
private Statistics statistics; private Statistics statistics;
Tunnel(@NonNull final Backend backend, @NonNull final ConfigStore configStore, Tunnel(@NonNull final TunnelManager manager, @NonNull final String name,
@NonNull final String name, @Nullable final Config config, @NonNull final State state) { @Nullable final Config config, @NonNull final State state) {
this.backend = backend; this.manager = manager;
this.configStore = configStore;
this.name = name; this.name = name;
this.config = config; this.config = config;
this.state = state; this.state = state;
@ -47,16 +41,20 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
return name != null && NAME_PATTERN.matcher(name).matches(); return name != null && NAME_PATTERN.matcher(name).matches();
} }
public CompletionStage<Void> delete() {
return manager.delete(this);
}
@Bindable @Bindable
public Config getConfig() { public Config getConfig() {
if (config == null) if (config == null)
getConfigAsync().whenComplete(ExceptionLoggers.D); manager.getTunnelConfig(this).whenComplete(ExceptionLoggers.E);
return config; return config;
} }
public CompletionStage<Config> getConfigAsync() { public CompletionStage<Config> getConfigAsync() {
if (config == null) if (config == null)
return configStore.load(name).thenApply(this::setConfigInternal); return manager.getTunnelConfig(this);
return CompletableFuture.completedFuture(config); return CompletableFuture.completedFuture(config);
} }
@ -65,7 +63,6 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
return name; return name;
} }
@Bindable
public String getName() { public String getName() {
return name; return name;
} }
@ -73,13 +70,13 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
@Bindable @Bindable
public State getState() { public State getState() {
if (state == State.UNKNOWN) if (state == State.UNKNOWN)
getStateAsync().whenComplete(ExceptionLoggers.D); manager.getTunnelState(this).whenComplete(ExceptionLoggers.E);
return state; return state;
} }
public CompletionStage<State> getStateAsync() { public CompletionStage<State> getStateAsync() {
if (state == State.UNKNOWN) if (state == State.UNKNOWN)
return backend.getState(this).thenApply(this::setStateInternal); return manager.getTunnelState(this);
return CompletableFuture.completedFuture(state); return CompletableFuture.completedFuture(state);
} }
@ -87,63 +84,49 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
public Statistics getStatistics() { public Statistics getStatistics() {
// FIXME: Check age of statistics. // FIXME: Check age of statistics.
if (statistics == null) if (statistics == null)
getStatisticsAsync().whenComplete(ExceptionLoggers.D); manager.getTunnelStatistics(this).whenComplete(ExceptionLoggers.E);
return statistics; return statistics;
} }
public CompletionStage<Statistics> getStatisticsAsync() { public CompletionStage<Statistics> getStatisticsAsync() {
// FIXME: Check age of statistics. // FIXME: Check age of statistics.
if (statistics == null) if (statistics == null)
return backend.getStatistics(this).thenApply(this::setStatisticsInternal); return manager.getTunnelStatistics(this);
return CompletableFuture.completedFuture(statistics); return CompletableFuture.completedFuture(statistics);
} }
private void onStateChanged(final State oldState, final State newState) { Config onConfigChanged(final Config config) {
if (newState != State.UP)
setStatisticsInternal(null);
}
public CompletionStage<Config> setConfig(@NonNull final Config config) {
if (!config.equals(this.config)) {
return backend.applyConfig(this, config)
.thenCompose(cfg -> configStore.save(name, cfg))
.thenApply(this::setConfigInternal);
}
return CompletableFuture.completedFuture(this.config);
}
private Config setConfigInternal(final Config config) {
if (Objects.equals(this.config, config))
return config;
this.config = config; this.config = config;
notifyPropertyChanged(BR.config); notifyPropertyChanged(BR.config);
return config; return config;
} }
public CompletionStage<State> setState(@NonNull final State state) { State onStateChanged(final State state) {
if (state != this.state) if (state != State.UP)
return backend.setState(this, state) onStatisticsChanged(null);
.thenApply(this::setStateInternal);
return CompletableFuture.completedFuture(this.state);
}
private State setStateInternal(final State state) {
if (Objects.equals(this.state, state))
return state;
onStateChanged(this.state, state);
this.state = state; this.state = state;
notifyPropertyChanged(BR.state); notifyPropertyChanged(BR.state);
return state; return state;
} }
private Statistics setStatisticsInternal(final Statistics statistics) { Statistics onStatisticsChanged(final Statistics statistics) {
if (Objects.equals(this.statistics, statistics))
return statistics;
this.statistics = statistics; this.statistics = statistics;
notifyPropertyChanged(BR.statistics); notifyPropertyChanged(BR.statistics);
return statistics; return statistics;
} }
public CompletionStage<Config> setConfig(@NonNull final Config config) {
if (!config.equals(this.config))
return manager.setTunnelConfig(this, config);
return CompletableFuture.completedFuture(this.config);
}
public CompletionStage<State> setState(@NonNull final State state) {
if (state != this.state)
return manager.setTunnelState(this, state);
return CompletableFuture.completedFuture(this.state);
}
public enum State { public enum State {
DOWN, DOWN,
TOGGLE, TOGGLE,

View File

@ -1,12 +1,12 @@
package com.wireguard.android.model; package com.wireguard.android.model;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log;
import com.wireguard.android.Application.ApplicationScope; import com.wireguard.android.Application.ApplicationScope;
import com.wireguard.android.backend.Backend; import com.wireguard.android.backend.Backend;
import com.wireguard.android.configStore.ConfigStore; import com.wireguard.android.configStore.ConfigStore;
import com.wireguard.android.model.Tunnel.State; import com.wireguard.android.model.Tunnel.State;
import com.wireguard.android.model.Tunnel.Statistics;
import com.wireguard.android.util.ExceptionLoggers; import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.util.KeyedObservableList; import com.wireguard.android.util.KeyedObservableList;
import com.wireguard.android.util.SortedKeyedObservableArrayList; import com.wireguard.android.util.SortedKeyedObservableArrayList;
@ -49,36 +49,37 @@ public final class TunnelManager {
} }
private Tunnel add(final String name, final Config config, final State state) { private Tunnel add(final String name, final Config config, final State state) {
final Tunnel tunnel = new Tunnel(backend, configStore, name, config, state); final Tunnel tunnel = new Tunnel(this, name, config, state);
tunnels.add(tunnel); tunnels.add(tunnel);
return tunnel; return tunnel;
} }
private Tunnel add(final String name) {
return add(name, null, State.UNKNOWN);
}
public CompletionStage<Tunnel> create(final String name, final Config config) { public CompletionStage<Tunnel> create(final String name, final Config config) {
Log.v(TAG, "Requested create tunnel " + name + " with config\n" + config);
if (!Tunnel.isNameValid(name)) if (!Tunnel.isNameValid(name))
return CompletableFuture.failedFuture(new IllegalArgumentException("Invalid name")); return CompletableFuture.failedFuture(new IllegalArgumentException("Invalid name"));
if (tunnels.containsKey(name)) { if (tunnels.containsKey(name)) {
final String message = "Tunnel " + name + " already exists"; final String message = "Tunnel " + name + " already exists";
return CompletableFuture.failedFuture(new IllegalArgumentException(message)); return CompletableFuture.failedFuture(new IllegalArgumentException(message));
} }
return configStore.create(name, config) return configStore.create(name, config).thenApply(cfg -> add(name, cfg, State.DOWN));
.thenApply(savedConfig -> add(name, savedConfig, State.DOWN));
} }
public CompletionStage<Void> delete(final Tunnel tunnel) { CompletionStage<Void> delete(final Tunnel tunnel) {
Log.v(TAG, "Requested delete tunnel " + tunnel.getName() + " state=" + tunnel.getState()); return setTunnelState(tunnel, State.DOWN)
return backend.setState(tunnel, State.DOWN)
.thenCompose(x -> configStore.delete(tunnel.getName())) .thenCompose(x -> configStore.delete(tunnel.getName()))
.thenAccept(x -> { .thenAccept(x -> remove(tunnel));
tunnels.remove(tunnel); }
if (tunnel.getName().equals(preferences.getString(KEY_PRIMARY_TUNNEL, null)))
preferences.edit().remove(KEY_PRIMARY_TUNNEL).apply(); CompletionStage<Config> getTunnelConfig(final Tunnel tunnel) {
}); return configStore.load(tunnel.getName()).thenApply(tunnel::onConfigChanged);
}
CompletionStage<State> getTunnelState(final Tunnel tunnel) {
return backend.getState(tunnel).thenApply(tunnel::onStateChanged);
}
CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) {
return backend.getStatistics(tunnel).thenApply(tunnel::onStatisticsChanged);
} }
public KeyedObservableList<String, Tunnel> getTunnels() { public KeyedObservableList<String, Tunnel> getTunnels() {
@ -86,12 +87,16 @@ public final class TunnelManager {
} }
public void onCreate() { public void onCreate() {
Log.v(TAG, "onCreate triggered");
configStore.enumerate().thenAcceptBoth(backend.enumerate(), (names, running) -> { configStore.enumerate().thenAcceptBoth(backend.enumerate(), (names, running) -> {
for (final String name : names) { for (final String name : names)
add(name, null, running.contains(name) ? State.UP : State.DOWN); add(name, null, running.contains(name) ? State.UP : State.DOWN);
}).whenComplete(ExceptionLoggers.E);
} }
}).whenComplete(ExceptionLoggers.D);
private void remove(final Tunnel tunnel) {
if (tunnel.getName().equals(preferences.getString(KEY_PRIMARY_TUNNEL, null)))
preferences.edit().remove(KEY_PRIMARY_TUNNEL).apply();
tunnels.remove(tunnel);
} }
public CompletionStage<Void> restoreState() { public CompletionStage<Void> restoreState() {
@ -114,4 +119,15 @@ public final class TunnelManager {
preferences.edit().putStringSet(KEY_RUNNING_TUNNELS, runningTunnels).apply(); preferences.edit().putStringSet(KEY_RUNNING_TUNNELS, runningTunnels).apply();
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
CompletionStage<Config> setTunnelConfig(final Tunnel tunnel, final Config config) {
return backend.applyConfig(tunnel, config)
.thenCompose(cfg -> configStore.save(tunnel.getName(), cfg))
.thenApply(tunnel::onConfigChanged);
}
CompletionStage<State> setTunnelState(final Tunnel tunnel, final State state) {
return backend.setState(tunnel, state)
.thenApply(tunnel::onStateChanged);
}
} }