package org.fdroid.fdroid.localrepo.type;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.net.LocalHTTPD;
import org.fdroid.fdroid.net.WifiStateChangeService;
import java.io.IOException;
import java.net.BindException;
import java.util.Random;
import rx.Single;
import rx.SingleSubscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Func2;
import rx.schedulers.Schedulers;
public class WifiSwap extends SwapType {
private static final String TAG = "WifiSwap";
private Handler webServerThreadHandler;
private LocalHTTPD localHttpd;
private final BonjourBroadcast bonjourBroadcast;
public WifiSwap(Context context) {
super(context);
bonjourBroadcast = new BonjourBroadcast(context);
}
protected String getBroadcastAction() {
return SwapService.WIFI_STATE_CHANGE;
}
public BonjourBroadcast getBonjour() {
return bonjourBroadcast;
}
@Override
public void start() {
Utils.debugLog(TAG, "Preparing swap webserver.");
sendBroadcast(SwapService.EXTRA_STARTING);
if (FDroidApp.ipAddressString == null) {
Log.e(TAG, "Not starting swap webserver, because we don't seem to be connected to a network.");
setConnected(false);
}
Single.zip(
Single.create(getWebServerTask()),
Single.create(getBonjourTask()),
new Func2<Boolean, Boolean, Boolean>() {
@Override
public Boolean call(Boolean webServerTask, Boolean bonjourServiceTask) {
return bonjourServiceTask && webServerTask;
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.subscribe(new Action1<Boolean>() {
@Override
public void call(Boolean success) {
setConnected(success);
}
},
new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
setConnected(false);
}
});
}
/**
* A task which starts the {@link WifiSwap#bonjourBroadcast} and then emits a `true` value at
* the end.
*/
private Single.OnSubscribe<Boolean> getBonjourTask() {
return new Single.OnSubscribe<Boolean>() {
@Override
public void call(SingleSubscriber<? super Boolean> singleSubscriber) {
bonjourBroadcast.start();
// TODO: Be more intelligent about failures here so that we can invoke
// singleSubscriber.onError() in the appropriate circumstances.
singleSubscriber.onSuccess(true);
}
};
}
/**
* Constructs a new {@link Thread} for the webserver to run on. If successful, it will also
* populate the webServerThreadHandler property and bind it to that particular thread. This
* allows messages to be sent to the webserver thread by posting messages to that handler.
*/
private Single.OnSubscribe<Boolean> getWebServerTask() {
return new Single.OnSubscribe<Boolean>() {
@Override
public void call(final SingleSubscriber<? super Boolean> singleSubscriber) {
new Thread(new Runnable() {
// Tell Eclipse this is not a leak because of Looper use.
@SuppressLint("HandlerLeak")
@Override
public void run() {
localHttpd = new LocalHTTPD(
context,
FDroidApp.ipAddressString,
FDroidApp.port,
context.getFilesDir(),
Preferences.get().isLocalRepoHttpsEnabled());
Looper.prepare(); // must be run before creating a Handler
webServerThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.i(TAG, "we've been asked to stop the webserver: " + msg.obj);
localHttpd.stop();
Looper looper = Looper.myLooper();
if (looper == null) {
Log.e(TAG, "Looper.myLooper() was null for sum reason while shutting down the swap webserver.");
} else {
looper.quit();
}
}
};
try {
Utils.debugLog(TAG, "Starting swap webserver...");
localHttpd.start();
Utils.debugLog(TAG, "Swap webserver started.");
singleSubscriber.onSuccess(true);
} catch (BindException e) {
int prev = FDroidApp.port;
FDroidApp.port = FDroidApp.port + new Random().nextInt(1111);
context.startService(new Intent(context, WifiStateChangeService.class));
singleSubscriber.onError(new Exception("port " + prev + " occupied, trying on " + FDroidApp.port + "!"));
} catch (IOException e) {
Log.e(TAG, "Could not start local repo HTTP server", e);
singleSubscriber.onError(e);
}
Looper.loop(); // start the message receiving loop
}
}).start();
}
};
}
@Override
public void stop() {
sendBroadcast(SwapService.EXTRA_STOPPING);
if (webServerThreadHandler == null) {
Log.i(TAG, "null handler in stopWebServer");
} else {
Utils.debugLog(TAG, "Sending message to swap webserver to stop it.");
Message msg = webServerThreadHandler.obtainMessage();
msg.obj = webServerThreadHandler.getLooper().getThread().getName() + " says stop";
webServerThreadHandler.sendMessage(msg);
}
// Stop the Bonjour stuff after asking the webserver to stop. This is not required in this
// order, but it helps. In practice, the Bonjour stuff takes a second or two to stop. This
// should give enough time for the message we posted above to reach the web server thread
// and for the webserver to thus be stopped.
bonjourBroadcast.stop();
setConnected(false);
}
}