model: Proxy all async work through the TunnelManager
Signed-off-by: Samuel Holland <samuel@sholland.org>
This commit is contained in:
parent
2315a699fb
commit
22bdffcecd
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user