create testable LocalHTTPDManager for controlling the webserver
The RxJava tricks were a nightmare...
This commit is contained in:
parent
5b610798c2
commit
79e7e78e7f
372
app/src/androidTest/java/org/fdroid/fdroid/Netstat.java
Normal file
372
app/src/androidTest/java/org/fdroid/fdroid/Netstat.java
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replacer for the netstat utility, by reading the /proc filesystem it can find out the
|
||||||
|
* open connections of the system
|
||||||
|
* From http://www.ussg.iu.edu/hypermail/linux/kernel/0409.1/2166.html :
|
||||||
|
* It will first list all listening TCP sockets, and next list all established
|
||||||
|
* TCP connections. A typical entry of /proc/net/tcp would look like this (split
|
||||||
|
* up into 3 parts because of the length of the line):
|
||||||
|
* <p>
|
||||||
|
* 46: 010310AC:9C4C 030310AC:1770 01
|
||||||
|
* | | | | | |--> connection state
|
||||||
|
* | | | | |------> remote TCP port number
|
||||||
|
* | | | |-------------> remote IPv4 address
|
||||||
|
* | | |--------------------> local TCP port number
|
||||||
|
* | |---------------------------> local IPv4 address
|
||||||
|
* |----------------------------------> number of entry
|
||||||
|
* <p>
|
||||||
|
* 00000150:00000000 01:00000019 00000000
|
||||||
|
* | | | | |--> number of unrecovered RTO timeouts
|
||||||
|
* | | | |----------> number of jiffies until timer expires
|
||||||
|
* | | |----------------> timer_active (see below)
|
||||||
|
* | |----------------------> receive-queue
|
||||||
|
* |-------------------------------> transmit-queue
|
||||||
|
* <p>
|
||||||
|
* 1000 0 54165785 4 cd1e6040 25 4 27 3 -1
|
||||||
|
* | | | | | | | | | |--> slow start size threshold,
|
||||||
|
* | | | | | | | | | or -1 if the treshold
|
||||||
|
* | | | | | | | | | is >= 0xFFFF
|
||||||
|
* | | | | | | | | |----> sending congestion window
|
||||||
|
* | | | | | | | |-------> (ack.quick<<1)|ack.pingpong
|
||||||
|
* | | | | | | |---------> Predicted tick of soft clock
|
||||||
|
* | | | | | | (delayed ACK control data)
|
||||||
|
* | | | | | |------------> retransmit timeout
|
||||||
|
* | | | | |------------------> location of socket in memory
|
||||||
|
* | | | |-----------------------> socket reference count
|
||||||
|
* | | |-----------------------------> inode
|
||||||
|
* | |----------------------------------> unanswered 0-window probes
|
||||||
|
* |---------------------------------------------> uid
|
||||||
|
*
|
||||||
|
* @author Ciprian Dobre
|
||||||
|
*/
|
||||||
|
public class Netstat {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Possible values for states in /proc/net/tcp
|
||||||
|
*/
|
||||||
|
private static final String[] STATES = {
|
||||||
|
"ESTBLSH", "SYNSENT", "SYNRECV", "FWAIT1", "FWAIT2", "TMEWAIT",
|
||||||
|
"CLOSED", "CLSWAIT", "LASTACK", "LISTEN", "CLOSING", "UNKNOWN",
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Pattern used when parsing through /proc/net/tcp
|
||||||
|
*/
|
||||||
|
private static final Pattern NET_PATTERN = Pattern.compile(
|
||||||
|
"\\d+:\\s+([\\dA-F]+):([\\dA-F]+)\\s+([\\dA-F]+):([\\dA-F]+)\\s+([\\dA-F]+)\\s+" +
|
||||||
|
"[\\dA-F]+:[\\dA-F]+\\s+[\\dA-F]+:[\\dA-F]+\\s+[\\dA-F]+\\s+([\\d]+)\\s+[\\d]+\\s+([\\d]+)");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method that converts an address from a hex representation as founded in /proc to String representation
|
||||||
|
*/
|
||||||
|
private static String getAddress(final String hexa) {
|
||||||
|
try {
|
||||||
|
// first let's convert the address to Integer
|
||||||
|
final long v = Long.parseLong(hexa, 16);
|
||||||
|
// in /proc the order is little endian and java uses big endian order we also need to invert the order
|
||||||
|
final long adr = (v >>> 24) | (v << 24) |
|
||||||
|
((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00);
|
||||||
|
// and now it's time to output the result
|
||||||
|
return ((adr >> 24) & 0xff) + "." + ((adr >> 16) & 0xff) + "." + ((adr >> 8) & 0xff) + "." + (adr & 0xff);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return "0.0.0.0"; // NOPMD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt16(final String hexa) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(hexa, 16);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
private static String getPName(final int pid) {
|
||||||
|
final Pattern pattern = Pattern.compile("Name:\\s*(\\S+)");
|
||||||
|
try {
|
||||||
|
BufferedReader in = new BufferedReader(new FileReader("/proc/" + pid + "/status"));
|
||||||
|
String line;
|
||||||
|
while ((line = in.readLine()) != null) {
|
||||||
|
final Matcher matcher = pattern.matcher(line);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used to question for the connections currently openned
|
||||||
|
*
|
||||||
|
* @return The list of connections (as Connection objects)
|
||||||
|
*/
|
||||||
|
public static List<Connection> getConnections() {
|
||||||
|
|
||||||
|
final ArrayList<Connection> net = new ArrayList<>();
|
||||||
|
|
||||||
|
// read from /proc/net/tcp the list of currently openned socket connections
|
||||||
|
try {
|
||||||
|
BufferedReader in = new BufferedReader(new FileReader("/proc/net/tcp"));
|
||||||
|
String line;
|
||||||
|
while ((line = in.readLine()) != null) {
|
||||||
|
Matcher matcher = NET_PATTERN.matcher(line);
|
||||||
|
if (matcher.find()) {
|
||||||
|
final Connection c = new Connection();
|
||||||
|
c.setProtocol(Connection.TCP_CONNECTION);
|
||||||
|
net.add(c);
|
||||||
|
final String localPortHexa = matcher.group(2);
|
||||||
|
final String remoteAddressHexa = matcher.group(3);
|
||||||
|
final String remotePortHexa = matcher.group(4);
|
||||||
|
final String statusHexa = matcher.group(5);
|
||||||
|
//final String uid = matcher.group(6);
|
||||||
|
//final String inode = matcher.group(7);
|
||||||
|
c.setLocalPort(getInt16(localPortHexa));
|
||||||
|
c.setRemoteAddress(getAddress(remoteAddressHexa));
|
||||||
|
c.setRemotePort(getInt16(remotePortHexa));
|
||||||
|
try {
|
||||||
|
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
c.setStatus(STATES[11]); // unknwon
|
||||||
|
}
|
||||||
|
c.setPID(-1); // unknown
|
||||||
|
c.setPName("UNKNOWN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
} catch (Throwable t) { // NOPMD
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
// read from /proc/net/udp the list of currently openned socket connections
|
||||||
|
try {
|
||||||
|
BufferedReader in = new BufferedReader(new FileReader("/proc/net/udp"));
|
||||||
|
String line;
|
||||||
|
while ((line = in.readLine()) != null) {
|
||||||
|
Matcher matcher = NET_PATTERN.matcher(line);
|
||||||
|
if (matcher.find()) {
|
||||||
|
final Connection c = new Connection();
|
||||||
|
c.setProtocol(Connection.UDP_CONNECTION);
|
||||||
|
net.add(c);
|
||||||
|
final String localPortHexa = matcher.group(2);
|
||||||
|
final String remoteAddressHexa = matcher.group(3);
|
||||||
|
final String remotePortHexa = matcher.group(4);
|
||||||
|
final String statusHexa = matcher.group(5);
|
||||||
|
//final String uid = matcher.group(6);
|
||||||
|
//final String inode = matcher.group(7);
|
||||||
|
c.setLocalPort(getInt16(localPortHexa));
|
||||||
|
c.setRemoteAddress(getAddress(remoteAddressHexa));
|
||||||
|
c.setRemotePort(getInt16(remotePortHexa));
|
||||||
|
try {
|
||||||
|
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
c.setStatus(STATES[11]); // unknwon
|
||||||
|
}
|
||||||
|
c.setPID(-1); // unknown
|
||||||
|
c.setPName("UNKNOWN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
} catch (Throwable t) { // NOPMD
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
// read from /proc/net/raw the list of currently openned socket connections
|
||||||
|
try {
|
||||||
|
BufferedReader in = new BufferedReader(new FileReader("/proc/net/raw"));
|
||||||
|
String line;
|
||||||
|
while ((line = in.readLine()) != null) {
|
||||||
|
Matcher matcher = NET_PATTERN.matcher(line);
|
||||||
|
if (matcher.find()) {
|
||||||
|
final Connection c = new Connection();
|
||||||
|
c.setProtocol(Connection.RAW_CONNECTION);
|
||||||
|
net.add(c);
|
||||||
|
//final String localAddressHexa = matcher.group(1);
|
||||||
|
final String localPortHexa = matcher.group(2);
|
||||||
|
final String remoteAddressHexa = matcher.group(3);
|
||||||
|
final String remotePortHexa = matcher.group(4);
|
||||||
|
final String statusHexa = matcher.group(5);
|
||||||
|
//final String uid = matcher.group(6);
|
||||||
|
//final String inode = matcher.group(7);
|
||||||
|
c.setLocalPort(getInt16(localPortHexa));
|
||||||
|
c.setRemoteAddress(getAddress(remoteAddressHexa));
|
||||||
|
c.setRemotePort(getInt16(remotePortHexa));
|
||||||
|
try {
|
||||||
|
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
c.setStatus(STATES[11]); // unknwon
|
||||||
|
}
|
||||||
|
c.setPID(-1); // unknown
|
||||||
|
c.setPName("UNKNOWN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
} catch (Throwable t) { // NOPMD
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
return net;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informations about a given connection
|
||||||
|
*
|
||||||
|
* @author Ciprian Dobre
|
||||||
|
*/
|
||||||
|
public static class Connection {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of connection protocol
|
||||||
|
***/
|
||||||
|
public static final byte TCP_CONNECTION = 0;
|
||||||
|
public static final byte UDP_CONNECTION = 1;
|
||||||
|
public static final byte RAW_CONNECTION = 2;
|
||||||
|
/**
|
||||||
|
* <code>serialVersionUID</code>
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 1988671591829311032L;
|
||||||
|
/**
|
||||||
|
* The protocol of the connection (can be tcp, udp or raw)
|
||||||
|
*/
|
||||||
|
protected byte protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The owner of the connection (username)
|
||||||
|
*/
|
||||||
|
protected String powner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pid of the owner process
|
||||||
|
*/
|
||||||
|
protected int pid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the program owning the connection
|
||||||
|
*/
|
||||||
|
protected String pname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local port
|
||||||
|
*/
|
||||||
|
protected int localPort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remote address of the connection
|
||||||
|
*/
|
||||||
|
protected String remoteAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remote port
|
||||||
|
*/
|
||||||
|
protected int remotePort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status of the connection
|
||||||
|
*/
|
||||||
|
protected String status;
|
||||||
|
|
||||||
|
public final byte getProtocol() {
|
||||||
|
return protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setProtocol(final byte protocol) {
|
||||||
|
this.protocol = protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getProtocolAsString() {
|
||||||
|
switch (protocol) {
|
||||||
|
case TCP_CONNECTION:
|
||||||
|
return "TCP";
|
||||||
|
case UDP_CONNECTION:
|
||||||
|
return "UDP";
|
||||||
|
case RAW_CONNECTION:
|
||||||
|
return "RAW";
|
||||||
|
}
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getPOwner() {
|
||||||
|
return powner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setPOwner(final String owner) {
|
||||||
|
this.powner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getPID() {
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setPID(final int pid) {
|
||||||
|
this.pid = pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getPName() {
|
||||||
|
return pname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setPName(final String pname) {
|
||||||
|
this.pname = pname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getLocalPort() {
|
||||||
|
return localPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setLocalPort(final int localPort) {
|
||||||
|
this.localPort = localPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getRemoteAddress() {
|
||||||
|
return remoteAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setRemoteAddress(final String remoteAddress) {
|
||||||
|
this.remoteAddress = remoteAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getRemotePort() {
|
||||||
|
return remotePort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setRemotePort(final int remotePort) {
|
||||||
|
this.remotePort = remotePort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setStatus(final String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
buf.append("[Prot=").append(getProtocolAsString());
|
||||||
|
buf.append(",POwner=").append(powner);
|
||||||
|
buf.append(",PID=").append(pid);
|
||||||
|
buf.append(",PName=").append(pname);
|
||||||
|
buf.append(",LPort=").append(localPort);
|
||||||
|
buf.append(",RAddress=").append(remoteAddress);
|
||||||
|
buf.append(",RPort=").append(remotePort);
|
||||||
|
buf.append(",Status=").append(status);
|
||||||
|
buf.append("]");
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,187 @@
|
|||||||
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
|
import org.fdroid.fdroid.Netstat;
|
||||||
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class LocalHTTPDManagerTest {
|
||||||
|
private static final String TAG = "LocalHTTPDManagerTest";
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private LocalBroadcastManager lbm;
|
||||||
|
|
||||||
|
private static final String LOCALHOST = "localhost";
|
||||||
|
private static final int PORT = 8888;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
context = InstrumentationRegistry.getTargetContext();
|
||||||
|
lbm = LocalBroadcastManager.getInstance(context);
|
||||||
|
|
||||||
|
FDroidApp.ipAddressString = LOCALHOST;
|
||||||
|
FDroidApp.port = PORT;
|
||||||
|
|
||||||
|
for (Netstat.Connection connection : Netstat.getConnections()) { // NOPMD
|
||||||
|
Log.i("LocalHTTPDManagerTest", "connection: " + connection.toString());
|
||||||
|
}
|
||||||
|
assertFalse(Utils.isServerSocketInUse(PORT));
|
||||||
|
LocalHTTPDManager.stop(context);
|
||||||
|
|
||||||
|
for (Netstat.Connection connection : Netstat.getConnections()) { // NOPMD
|
||||||
|
Log.i("LocalHTTPDManagerTest", "connection: " + connection.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
lbm.unregisterReceiver(startedReceiver);
|
||||||
|
lbm.unregisterReceiver(stoppedReceiver);
|
||||||
|
lbm.unregisterReceiver(errorReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStartStop() throws InterruptedException {
|
||||||
|
Log.i(TAG, "testStartStop");
|
||||||
|
|
||||||
|
final CountDownLatch startLatch = new CountDownLatch(1);
|
||||||
|
BroadcastReceiver latchReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
startLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
|
||||||
|
lbm.registerReceiver(stoppedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
|
||||||
|
lbm.registerReceiver(errorReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
|
||||||
|
LocalHTTPDManager.start(context, false);
|
||||||
|
assertTrue(startLatch.await(30, TimeUnit.SECONDS));
|
||||||
|
assertTrue(Utils.isServerSocketInUse(PORT));
|
||||||
|
assertTrue(Utils.canConnectToSocket(LOCALHOST, PORT));
|
||||||
|
lbm.unregisterReceiver(latchReceiver);
|
||||||
|
lbm.unregisterReceiver(stoppedReceiver);
|
||||||
|
lbm.unregisterReceiver(errorReceiver);
|
||||||
|
|
||||||
|
final CountDownLatch stopLatch = new CountDownLatch(1);
|
||||||
|
latchReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
stopLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
lbm.registerReceiver(startedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
|
||||||
|
lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
|
||||||
|
lbm.registerReceiver(errorReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
|
||||||
|
LocalHTTPDManager.stop(context);
|
||||||
|
assertTrue(stopLatch.await(30, TimeUnit.SECONDS));
|
||||||
|
assertFalse(Utils.isServerSocketInUse(PORT));
|
||||||
|
assertFalse(Utils.canConnectToSocket(LOCALHOST, PORT)); // if this is flaky, just remove it
|
||||||
|
lbm.unregisterReceiver(latchReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testError() throws InterruptedException, IOException {
|
||||||
|
Log.i("LocalHTTPDManagerTest", "testError");
|
||||||
|
ServerSocket blockerSocket = new ServerSocket(PORT);
|
||||||
|
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
BroadcastReceiver latchReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
lbm.registerReceiver(startedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
|
||||||
|
lbm.registerReceiver(stoppedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
|
||||||
|
lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
|
||||||
|
LocalHTTPDManager.start(context, false);
|
||||||
|
assertTrue(latch.await(30, TimeUnit.SECONDS));
|
||||||
|
assertTrue(Utils.isServerSocketInUse(PORT));
|
||||||
|
assertNotEquals(PORT, FDroidApp.port);
|
||||||
|
assertFalse(Utils.isServerSocketInUse(FDroidApp.port));
|
||||||
|
lbm.unregisterReceiver(latchReceiver);
|
||||||
|
blockerSocket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRestart() throws InterruptedException, IOException {
|
||||||
|
Log.i("LocalHTTPDManagerTest", "testRestart");
|
||||||
|
assertFalse(Utils.isServerSocketInUse(PORT));
|
||||||
|
final CountDownLatch startLatch = new CountDownLatch(1);
|
||||||
|
BroadcastReceiver latchReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
startLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
|
||||||
|
lbm.registerReceiver(stoppedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
|
||||||
|
lbm.registerReceiver(errorReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
|
||||||
|
LocalHTTPDManager.start(context, false);
|
||||||
|
assertTrue(startLatch.await(30, TimeUnit.SECONDS));
|
||||||
|
assertTrue(Utils.isServerSocketInUse(PORT));
|
||||||
|
lbm.unregisterReceiver(latchReceiver);
|
||||||
|
lbm.unregisterReceiver(stoppedReceiver);
|
||||||
|
|
||||||
|
final CountDownLatch restartLatch = new CountDownLatch(1);
|
||||||
|
latchReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
restartLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
|
||||||
|
LocalHTTPDManager.restart(context, false);
|
||||||
|
assertTrue(restartLatch.await(30, TimeUnit.SECONDS));
|
||||||
|
lbm.unregisterReceiver(latchReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final BroadcastReceiver startedReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String message = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
|
Log.i(TAG, "startedReceiver: " + message);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final BroadcastReceiver stoppedReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String message = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
|
Log.i(TAG, "stoppedReceiver: " + message);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final BroadcastReceiver errorReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String message = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
|
Log.i(TAG, "errorReceiver: " + message);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -78,6 +78,7 @@
|
|||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
<service android:name=".localrepo.SwapService"/>
|
<service android:name=".localrepo.SwapService"/>
|
||||||
|
|
||||||
|
<service android:name=".localrepo.LocalHTTPDManager"/>
|
||||||
<service
|
<service
|
||||||
android:name=".localrepo.LocalRepoService"
|
android:name=".localrepo.LocalRepoService"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
|
import org.fdroid.fdroid.Preferences;
|
||||||
|
import org.fdroid.fdroid.net.LocalHTTPD;
|
||||||
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.BindException;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage {@link LocalHTTPD} in a {@link HandlerThread};
|
||||||
|
*/
|
||||||
|
public class LocalHTTPDManager {
|
||||||
|
private static final String TAG = "LocalHTTPDManager";
|
||||||
|
|
||||||
|
public static final String ACTION_STARTED = "LocalHTTPDStarted";
|
||||||
|
public static final String ACTION_STOPPED = "LocalHTTPDStopped";
|
||||||
|
public static final String ACTION_ERROR = "LocalHTTPDError";
|
||||||
|
|
||||||
|
private static final int STOP = 5709;
|
||||||
|
|
||||||
|
private static Handler handler;
|
||||||
|
private static volatile HandlerThread handlerThread;
|
||||||
|
private static LocalHTTPD localHttpd;
|
||||||
|
|
||||||
|
public static void start(Context context) {
|
||||||
|
start(context, Preferences.get().isLocalRepoHttpsEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testable version, not for regular use.
|
||||||
|
*
|
||||||
|
* @see #start(Context)
|
||||||
|
*/
|
||||||
|
static void start(final Context context, final boolean useHttps) {
|
||||||
|
if (handlerThread != null && handlerThread.isAlive()) {
|
||||||
|
Log.w(TAG, "handlerThread is already running, doing nothing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlerThread = new HandlerThread("LocalHTTPD", Process.THREAD_PRIORITY_LESS_FAVORABLE) {
|
||||||
|
@Override
|
||||||
|
protected void onLooperPrepared() {
|
||||||
|
localHttpd = new LocalHTTPD(
|
||||||
|
context,
|
||||||
|
FDroidApp.ipAddressString,
|
||||||
|
FDroidApp.port,
|
||||||
|
context.getFilesDir(),
|
||||||
|
useHttps);
|
||||||
|
try {
|
||||||
|
localHttpd.start();
|
||||||
|
Intent intent = new Intent(ACTION_STARTED);
|
||||||
|
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||||
|
} catch (BindException e) {
|
||||||
|
int prev = FDroidApp.port;
|
||||||
|
FDroidApp.port = FDroidApp.port + new Random().nextInt(1111);
|
||||||
|
WifiStateChangeService.start(context, null);
|
||||||
|
Intent intent = new Intent(ACTION_ERROR);
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT,
|
||||||
|
"port " + prev + " occupied, trying on " + FDroidApp.port + ": ("
|
||||||
|
+ e.getLocalizedMessage() + ")");
|
||||||
|
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Intent intent = new Intent(ACTION_ERROR);
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, e.getLocalizedMessage());
|
||||||
|
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
handlerThread.start();
|
||||||
|
handler = new Handler(handlerThread.getLooper()) {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
localHttpd.stop();
|
||||||
|
handlerThread.quit();
|
||||||
|
handlerThread = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stop(Context context) {
|
||||||
|
if (handler == null || handlerThread == null || !handlerThread.isAlive()) {
|
||||||
|
Log.w(TAG, "handlerThread is already stopped, doing nothing!");
|
||||||
|
handlerThread = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handler.sendEmptyMessage(STOP);
|
||||||
|
Intent stoppedIntent = new Intent(ACTION_STOPPED);
|
||||||
|
LocalBroadcastManager.getInstance(context).sendBroadcast(stoppedIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run {@link #stop(Context)}, wait for it to actually stop, then run
|
||||||
|
* {@link #start(Context)}.
|
||||||
|
*/
|
||||||
|
public static void restart(Context context) {
|
||||||
|
restart(context, Preferences.get().isLocalRepoHttpsEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testable version, not for regular use.
|
||||||
|
*
|
||||||
|
* @see #restart(Context)
|
||||||
|
*/
|
||||||
|
static void restart(Context context, boolean useHttps) {
|
||||||
|
stop(context);
|
||||||
|
try {
|
||||||
|
handlerThread.join(10000);
|
||||||
|
} catch (InterruptedException | NullPointerException e) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
start(context, useHttps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAlive() {
|
||||||
|
return handlerThread != null && handlerThread.isAlive();
|
||||||
|
}
|
||||||
|
}
|
@ -133,23 +133,17 @@ public class SwapService extends Service {
|
|||||||
if (getPeer() == null) {
|
if (getPeer() == null) {
|
||||||
throw new IllegalStateException("Cannot connect to peer, no peer has been selected.");
|
throw new IllegalStateException("Cannot connect to peer, no peer has been selected.");
|
||||||
}
|
}
|
||||||
connectTo(getPeer(), getPeer().shouldPromptForSwapBack());
|
connectTo(getPeer());
|
||||||
|
if (isEnabled() && getPeer().shouldPromptForSwapBack()) {
|
||||||
|
askServerToSwapWithUs(peerRepo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connectTo(@NonNull Peer peer, boolean requestSwapBack) {
|
public void connectTo(@NonNull Peer peer) {
|
||||||
if (peer != this.peer) {
|
if (peer != this.peer) {
|
||||||
Log.e(TAG, "Oops, got a different peer to swap with than initially planned.");
|
Log.e(TAG, "Oops, got a different peer to swap with than initially planned.");
|
||||||
}
|
}
|
||||||
|
|
||||||
peerRepo = ensureRepoExists(peer);
|
peerRepo = ensureRepoExists(peer);
|
||||||
|
|
||||||
// Only ask server to swap with us, if we are actually running a local repo service.
|
|
||||||
// It is possible to have a swap initiated without first starting a swap, in which
|
|
||||||
// case swapping back is pointless.
|
|
||||||
if (isEnabled() && requestSwapBack) {
|
|
||||||
askServerToSwapWithUs(peerRepo);
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateService.updateRepoNow(this, peer.getRepoAddress());
|
UpdateService.updateRepoNow(this, peer.getRepoAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +178,9 @@ public class SwapService extends Service {
|
|||||||
intent.putExtra(Downloader.EXTRA_ERROR_MESSAGE, e.getLocalizedMessage());
|
intent.putExtra(Downloader.EXTRA_ERROR_MESSAGE, e.getLocalizedMessage());
|
||||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
|
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
|
||||||
} finally {
|
} finally {
|
||||||
conn.disconnect();
|
if (conn != null) {
|
||||||
|
conn.disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -362,7 +358,7 @@ public class SwapService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return bluetoothSwap.isConnected() || wifiSwap.isConnected();
|
return bluetoothSwap.isConnected() || LocalHTTPDManager.isAlive();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@ -492,6 +488,7 @@ public class SwapService extends Service {
|
|||||||
bluetoothAdapter.disable();
|
bluetoothAdapter.disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocalHTTPDManager.stop(this);
|
||||||
if (wifiManager != null && !wasWifiEnabledBeforeSwap()) {
|
if (wifiManager != null && !wasWifiEnabledBeforeSwap()) {
|
||||||
wifiManager.setWifiEnabled(false);
|
wifiManager.setWifiEnabled(false);
|
||||||
}
|
}
|
||||||
@ -548,7 +545,7 @@ public class SwapService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (peer != null) {
|
if (peer != null) {
|
||||||
connectTo(peer, false);
|
connectTo(peer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
package org.fdroid.fdroid.localrepo.type;
|
package org.fdroid.fdroid.localrepo.type;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.Preferences;
|
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalHTTPDManager;
|
||||||
import org.fdroid.fdroid.localrepo.SwapService;
|
import org.fdroid.fdroid.localrepo.SwapService;
|
||||||
import org.fdroid.fdroid.net.LocalHTTPD;
|
|
||||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
|
||||||
import rx.Single;
|
import rx.Single;
|
||||||
import rx.SingleSubscriber;
|
import rx.SingleSubscriber;
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
@ -20,17 +14,11 @@ import rx.functions.Action1;
|
|||||||
import rx.functions.Func2;
|
import rx.functions.Func2;
|
||||||
import rx.schedulers.Schedulers;
|
import rx.schedulers.Schedulers;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.BindException;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
@SuppressWarnings("LineLength")
|
@SuppressWarnings("LineLength")
|
||||||
public class WifiSwap extends SwapType {
|
public class WifiSwap extends SwapType {
|
||||||
|
|
||||||
private static final String TAG = "WifiSwap";
|
private static final String TAG = "WifiSwap";
|
||||||
|
|
||||||
private Handler webServerThreadHandler;
|
|
||||||
private LocalHTTPD localHttpd;
|
|
||||||
private final BonjourBroadcast bonjourBroadcast;
|
private final BonjourBroadcast bonjourBroadcast;
|
||||||
private final WifiManager wifiManager;
|
private final WifiManager wifiManager;
|
||||||
|
|
||||||
@ -50,10 +38,10 @@ public class WifiSwap extends SwapType {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
|
sendBroadcast(SwapService.EXTRA_STARTING);
|
||||||
wifiManager.setWifiEnabled(true);
|
wifiManager.setWifiEnabled(true);
|
||||||
|
|
||||||
Utils.debugLog(TAG, "Preparing swap webserver.");
|
LocalHTTPDManager.start(context);
|
||||||
sendBroadcast(SwapService.EXTRA_STARTING);
|
|
||||||
|
|
||||||
if (FDroidApp.ipAddressString == null) {
|
if (FDroidApp.ipAddressString == null) {
|
||||||
Log.e(TAG, "Not starting swap webserver, because we don't seem to be connected to a network.");
|
Log.e(TAG, "Not starting swap webserver, because we don't seem to be connected to a network.");
|
||||||
@ -110,65 +98,17 @@ public class WifiSwap extends SwapType {
|
|||||||
private Single.OnSubscribe<Boolean> getWebServerTask() {
|
private Single.OnSubscribe<Boolean> getWebServerTask() {
|
||||||
return new Single.OnSubscribe<Boolean>() {
|
return new Single.OnSubscribe<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public void call(final SingleSubscriber<? super Boolean> singleSubscriber) {
|
public void call(SingleSubscriber<? super Boolean> singleSubscriber) {
|
||||||
new Thread(new Runnable() {
|
singleSubscriber.onSuccess(true);
|
||||||
// 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);
|
|
||||||
WifiStateChangeService.start(context, null);
|
|
||||||
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
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
sendBroadcast(SwapService.EXTRA_STOPPING);
|
sendBroadcast(SwapService.EXTRA_STOPPING); // This needs to be per-SwapType
|
||||||
if (webServerThreadHandler == null) {
|
Utils.debugLog(TAG, "Sending message to swap webserver to stop it.");
|
||||||
Log.i(TAG, "null handler in stopWebServer");
|
LocalHTTPDManager.stop(context);
|
||||||
} 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
|
// 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
|
// order, but it helps. In practice, the Bonjour stuff takes a second or two to stop. This
|
||||||
|
@ -49,6 +49,7 @@ import java.io.FilenameFilter;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@ -80,7 +81,7 @@ public class LocalHTTPD extends NanoHTTPD {
|
|||||||
*/
|
*/
|
||||||
public static final String[] INDEX_FILE_NAMES = {"index.html"};
|
public static final String[] INDEX_FILE_NAMES = {"index.html"};
|
||||||
|
|
||||||
private final Context context;
|
private final WeakReference<Context> context;
|
||||||
|
|
||||||
protected List<File> rootDirs;
|
protected List<File> rootDirs;
|
||||||
|
|
||||||
@ -101,7 +102,7 @@ public class LocalHTTPD extends NanoHTTPD {
|
|||||||
public LocalHTTPD(Context context, String hostname, int port, File webRoot, boolean useHttps) {
|
public LocalHTTPD(Context context, String hostname, int port, File webRoot, boolean useHttps) {
|
||||||
super(hostname, port);
|
super(hostname, port);
|
||||||
rootDirs = Collections.singletonList(webRoot);
|
rootDirs = Collections.singletonList(webRoot);
|
||||||
this.context = context.getApplicationContext();
|
this.context = new WeakReference<>(context.getApplicationContext());
|
||||||
if (useHttps) {
|
if (useHttps) {
|
||||||
enableHTTPS();
|
enableHTTPS();
|
||||||
}
|
}
|
||||||
@ -370,7 +371,7 @@ public class LocalHTTPD extends NanoHTTPD {
|
|||||||
return newFixedLengthResponse(Response.Status.BAD_REQUEST, MIME_PLAINTEXT,
|
return newFixedLengthResponse(Response.Status.BAD_REQUEST, MIME_PLAINTEXT,
|
||||||
"Requires 'repo' parameter to be posted.");
|
"Requires 'repo' parameter to be posted.");
|
||||||
}
|
}
|
||||||
SwapWorkflowActivity.requestSwap(context, session.getParms().get("repo"));
|
SwapWorkflowActivity.requestSwap(context.get(), session.getParms().get("repo"));
|
||||||
return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, "Swap request received.");
|
return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, "Swap request received.");
|
||||||
}
|
}
|
||||||
return newFixedLengthResponse("");
|
return newFixedLengthResponse("");
|
||||||
@ -491,7 +492,7 @@ public class LocalHTTPD extends NanoHTTPD {
|
|||||||
|
|
||||||
private void enableHTTPS() {
|
private void enableHTTPS() {
|
||||||
try {
|
try {
|
||||||
LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context);
|
LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context.get());
|
||||||
SSLServerSocketFactory factory = NanoHTTPD.makeSSLSocketFactory(
|
SSLServerSocketFactory factory = NanoHTTPD.makeSSLSocketFactory(
|
||||||
localRepoKeyStore.getKeyStore(),
|
localRepoKeyStore.getKeyStore(),
|
||||||
localRepoKeyStore.getKeyManagers());
|
localRepoKeyStore.getKeyManagers());
|
||||||
|
@ -63,6 +63,9 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
@ -887,4 +890,26 @@ public final class Utils {
|
|||||||
theme.resolveAttribute(R.attr.colorPrimary, typedValue, true);
|
theme.resolveAttribute(R.attr.colorPrimary, typedValue, true);
|
||||||
swipeLayout.setColorSchemeColors(typedValue.data);
|
swipeLayout.setColorSchemeColors(typedValue.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean canConnectToSocket(String host, int port) {
|
||||||
|
try {
|
||||||
|
Socket socket = new Socket();
|
||||||
|
socket.connect(new InetSocketAddress(host, port), 5);
|
||||||
|
socket.close();
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Could not connect.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isServerSocketInUse(int port) {
|
||||||
|
try {
|
||||||
|
(new ServerSocket(port)).close();
|
||||||
|
return false;
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Could not connect.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
import org.robolectric.shadows.ShadowLog;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class LocalHTTPDManagerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStartStop() throws InterruptedException {
|
||||||
|
ShadowLog.stream = System.out;
|
||||||
|
Context context = RuntimeEnvironment.application;
|
||||||
|
|
||||||
|
final String host = "localhost";
|
||||||
|
final int port = 8888;
|
||||||
|
assertFalse(Utils.isServerSocketInUse(port));
|
||||||
|
LocalHTTPDManager.stop(context);
|
||||||
|
|
||||||
|
FDroidApp.ipAddressString = host;
|
||||||
|
FDroidApp.port = port;
|
||||||
|
|
||||||
|
LocalHTTPDManager.start(context, false);
|
||||||
|
final CountDownLatch startLatch = new CountDownLatch(1);
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (!Utils.isServerSocketInUse(port)) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startLatch.countDown();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
assertTrue(startLatch.await(30, TimeUnit.SECONDS));
|
||||||
|
assertTrue(Utils.isServerSocketInUse(port));
|
||||||
|
assertTrue(Utils.canConnectToSocket(host, port));
|
||||||
|
|
||||||
|
LocalHTTPDManager.stop(context);
|
||||||
|
final CountDownLatch stopLatch = new CountDownLatch(1);
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (!Utils.isServerSocketInUse(port)) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stopLatch.countDown();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
assertTrue(stopLatch.await(10, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user