Lots of new unit tests, refactoring.
New tests: RestApiTest NodesAdapterTest ReposAdapterTest Refactored: extracted PollWebGuiAvailableTask from SyncthingService some changes in return values/calling behaviour for easier/better testing
This commit is contained in:
parent
7b3d1b4052
commit
b5f38c5c19
|
@ -0,0 +1,53 @@
|
||||||
|
package com.nutomic.syncthingandroid.test.syncthing;
|
||||||
|
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.PollWebGuiAvailableTask;
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.PostTask;
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.SyncthingRunnable;
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
||||||
|
import com.nutomic.syncthingandroid.test.TestContext;
|
||||||
|
import com.nutomic.syncthingandroid.util.ConfigXml;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class PollWebGuiAvailableTaskTest extends AndroidTestCase {
|
||||||
|
|
||||||
|
private SyncthingRunnable mSyncthing;
|
||||||
|
|
||||||
|
private ConfigXml mConfig;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
mConfig = new ConfigXml(new TestContext(getContext()));
|
||||||
|
mConfig.updateIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void tearDown() throws Exception {
|
||||||
|
super.tearDown();
|
||||||
|
|
||||||
|
ConfigXml.getConfigFile(new TestContext(getContext())).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPolling() throws InterruptedException {
|
||||||
|
mSyncthing = new SyncthingRunnable(new TestContext(null),
|
||||||
|
getContext().getApplicationInfo().dataDir + "/" + SyncthingService.BINARY_NAME);
|
||||||
|
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
new PollWebGuiAvailableTask() {
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void aVoid) {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}.execute(mConfig.getWebGuiUrl());
|
||||||
|
latch.await(1, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
new PostTask().execute(mConfig.getWebGuiUrl(), PostTask.URI_SHUTDOWN, mConfig.getApiKey());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
package com.nutomic.syncthingandroid.test.syncthing;
|
||||||
|
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
|
import android.test.suitebuilder.annotation.MediumTest;
|
||||||
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.PollWebGuiAvailableTask;
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.PostTask;
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.SyncthingRunnable;
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
||||||
|
import com.nutomic.syncthingandroid.test.TestContext;
|
||||||
|
import com.nutomic.syncthingandroid.util.ConfigXml;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class RestApiTest extends AndroidTestCase {
|
||||||
|
|
||||||
|
private SyncthingRunnable mSyncthing;
|
||||||
|
|
||||||
|
private ConfigXml mConfig;
|
||||||
|
|
||||||
|
private RestApi mApi;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
mSyncthing = new SyncthingRunnable(new TestContext(null),
|
||||||
|
getContext().getApplicationInfo().dataDir + "/" + SyncthingService.BINARY_NAME);
|
||||||
|
|
||||||
|
mConfig = new ConfigXml(new TestContext(getContext()));
|
||||||
|
mConfig.createCameraRepo();
|
||||||
|
mConfig.updateIfNeeded();
|
||||||
|
|
||||||
|
final CountDownLatch latch = new CountDownLatch(2);
|
||||||
|
new PollWebGuiAvailableTask() {
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void aVoid) {
|
||||||
|
mApi.onWebGuiAvailable();
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}.execute(mConfig.getWebGuiUrl());
|
||||||
|
mApi = new RestApi(getContext(), mConfig.getWebGuiUrl(), mConfig.getApiKey(),
|
||||||
|
new RestApi.OnApiAvailableListener() {
|
||||||
|
@Override
|
||||||
|
public void onApiAvailable() {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
latch.await(1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void tearDown() throws Exception {
|
||||||
|
super.tearDown();
|
||||||
|
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
new PostTask() {
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean aBoolean) {
|
||||||
|
assertTrue(aBoolean);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}.execute(mConfig.getWebGuiUrl(), PostTask.URI_SHUTDOWN, mConfig.getApiKey());
|
||||||
|
latch.await(1, TimeUnit.SECONDS);
|
||||||
|
ConfigXml.getConfigFile(new TestContext(getContext())).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testGetVersion() {
|
||||||
|
assertNotNull(mApi.getVersion());
|
||||||
|
assertFalse(mApi.getVersion().equals(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testGetNodes() {
|
||||||
|
assertNotNull(mApi.getNodes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testGetSystemInfo() throws InterruptedException {
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
mApi.getSystemInfo(new RestApi.OnReceiveSystemInfoListener() {
|
||||||
|
@Override
|
||||||
|
public void onReceiveSystemInfo(RestApi.SystemInfo info) {
|
||||||
|
assertNotNull(info);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
latch.await(1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testGetRepos() {
|
||||||
|
assertNotNull(mApi.getRepos());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testReadableFileSize() {
|
||||||
|
assertEquals("1 MB", RestApi.readableFileSize(getContext(), 1048576));
|
||||||
|
assertEquals("1 GB", RestApi.readableFileSize(getContext(), 1073741824));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testGetReadableTransferRate() {
|
||||||
|
assertEquals("1 Mb/s", RestApi.readableTransferRate(getContext(), 1048576));
|
||||||
|
assertEquals("1 Gb/s", RestApi.readableTransferRate(getContext(), 1073741824));
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testGetConnections() throws InterruptedException {
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
mApi.getConnections(new RestApi.OnReceiveConnectionsListener() {
|
||||||
|
@Override
|
||||||
|
public void onReceiveConnections(Map<String, RestApi.Connection> connections) {
|
||||||
|
assertNotNull(connections);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
latch.await(1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testGetModel() throws InterruptedException {
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
mApi.getModel("camera", new RestApi.OnReceiveModelListener() {
|
||||||
|
@Override
|
||||||
|
public void onReceiveModel(String repoId, RestApi.Model model) {
|
||||||
|
assertNotNull(model);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
latch.await(1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
public void testModifyNode() throws InterruptedException {
|
||||||
|
final RestApi.Node node = new RestApi.Node();
|
||||||
|
node.NodeID = "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2";
|
||||||
|
node.Addresses = "dynamic";
|
||||||
|
node.Name = "my node";
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
mApi.editNode(node, new RestApi.OnNodeIdNormalizedListener() {
|
||||||
|
@Override
|
||||||
|
public void onNodeIdNormalized(String normalizedId, String error) {
|
||||||
|
assertEquals(node.NodeID, normalizedId);
|
||||||
|
assertEquals(null, error);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
latch.await(10, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
assertTrue(mApi.deleteNode(node, getContext()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testModifyRepo() {
|
||||||
|
RestApi.Repo repo = new RestApi.Repo();
|
||||||
|
repo.Directory = "/my/dir";
|
||||||
|
repo.ID = "my-repo";
|
||||||
|
repo.Nodes = new ArrayList<>();
|
||||||
|
repo.ReadOnly = false;
|
||||||
|
repo.Versioning = new RestApi.Versioning();
|
||||||
|
assertTrue(mApi.editRepo(repo, true, getContext()));
|
||||||
|
|
||||||
|
assertTrue(mApi.deleteRepo(repo, getContext()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testNormalizeNodeId() throws InterruptedException {
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
mApi.normalizeNodeId("p56ioi7m--zjnu2iq-gdr-eydm-2mgtmgl3bxnpq6w5btbbz4tjxzwicq",
|
||||||
|
new RestApi.OnNodeIdNormalizedListener() {
|
||||||
|
@Override
|
||||||
|
public void onNodeIdNormalized(String normalizedId, String error) {
|
||||||
|
assertEquals("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2",
|
||||||
|
normalizedId);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
latch.await(1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.nutomic.syncthingandroid.test.util;
|
||||||
|
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
import android.test.suitebuilder.annotation.MediumTest;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.nutomic.syncthingandroid.R;
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||||
|
import com.nutomic.syncthingandroid.test.TestContext;
|
||||||
|
import com.nutomic.syncthingandroid.util.NodesAdapter;
|
||||||
|
import com.nutomic.syncthingandroid.util.ReposAdapter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class NodesAdapterTest extends AndroidTestCase {
|
||||||
|
|
||||||
|
private NodesAdapter mAdapter;
|
||||||
|
|
||||||
|
private RestApi.Node mNode = new RestApi.Node();
|
||||||
|
|
||||||
|
private RestApi.Connection mConnection = new RestApi.Connection();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
mAdapter = new NodesAdapter(getContext());
|
||||||
|
mNode.Addresses = "127.0.0.1:12345";
|
||||||
|
mNode.Name = "the node";
|
||||||
|
mNode.NodeID = "123-456-789";
|
||||||
|
|
||||||
|
mConnection.Completion = 100;
|
||||||
|
mConnection.InBits = 1048576;
|
||||||
|
mConnection.OutBits = 1073741824;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testGetViewNoConnections() {
|
||||||
|
mAdapter.add(Arrays.asList(mNode));
|
||||||
|
View v = mAdapter.getView(0, null, null);
|
||||||
|
|
||||||
|
assertEquals(mNode.Name, ((TextView) v.findViewById(R.id.name)).getText());
|
||||||
|
assertEquals(getContext().getString(R.string.node_disconnected),
|
||||||
|
((TextView) v.findViewById(R.id.status)).getText().toString());
|
||||||
|
assertFalse(((TextView) v.findViewById(R.id.status)).getText().equals(""));
|
||||||
|
assertFalse(((TextView) v.findViewById(R.id.download)).getText().equals(""));
|
||||||
|
assertFalse(((TextView) v.findViewById(R.id.upload)).getText().equals(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testGetViewConnections() {
|
||||||
|
mAdapter.add(Arrays.asList(mNode));
|
||||||
|
mAdapter.onReceiveConnections(
|
||||||
|
new HashMap<String, RestApi.Connection>() {{ put(mNode.NodeID, mConnection); }});
|
||||||
|
View v = mAdapter.getView(0, null, null);
|
||||||
|
|
||||||
|
assertEquals(getContext().getString(R.string.node_up_to_date),
|
||||||
|
((TextView) v.findViewById(R.id.status)).getText().toString());
|
||||||
|
assertEquals("1 Mb/s", ((TextView) v.findViewById(R.id.download)).getText().toString());
|
||||||
|
assertEquals("1 Gb/s", ((TextView) v.findViewById(R.id.upload)).getText().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.nutomic.syncthingandroid.test.util;
|
||||||
|
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
import android.test.suitebuilder.annotation.MediumTest;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.nutomic.syncthingandroid.R;
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||||
|
import com.nutomic.syncthingandroid.test.TestContext;
|
||||||
|
import com.nutomic.syncthingandroid.util.ReposAdapter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class ReposAdapterTest extends AndroidTestCase {
|
||||||
|
|
||||||
|
private ReposAdapter mAdapter;
|
||||||
|
|
||||||
|
private RestApi.Repo mRepo = new RestApi.Repo();
|
||||||
|
|
||||||
|
private RestApi.Model mModel = new RestApi.Model();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
mAdapter = new ReposAdapter(getContext());
|
||||||
|
mRepo.Directory = "/my/dir/";
|
||||||
|
mRepo.ID = "id 123";
|
||||||
|
mRepo.Invalid = "all good";
|
||||||
|
mRepo.Nodes = new ArrayList<>();
|
||||||
|
mRepo.ReadOnly = false;
|
||||||
|
mRepo.Versioning = new RestApi.Versioning();
|
||||||
|
|
||||||
|
mModel.localFiles = 50;
|
||||||
|
mModel.globalFiles = 500;
|
||||||
|
mModel.localBytes = 1048576;
|
||||||
|
mModel.globalBytes = 1073741824;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testGetViewNoModel() {
|
||||||
|
mAdapter.add(Arrays.asList(mRepo));
|
||||||
|
View v = mAdapter.getView(0, null, null);
|
||||||
|
assertEquals(mRepo.ID, ((TextView) v.findViewById(R.id.id)).getText());
|
||||||
|
assertEquals(mRepo.Directory, ((TextView) v.findViewById(R.id.directory)).getText());
|
||||||
|
assertEquals(mRepo.Invalid, ((TextView) v.findViewById(R.id.invalid)).getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testGetViewModel() {
|
||||||
|
mAdapter.add(Arrays.asList(mRepo));
|
||||||
|
mAdapter.onReceiveModel(mRepo.ID, mModel);
|
||||||
|
View v = mAdapter.getView(0, null, null);
|
||||||
|
assertFalse(((TextView) v.findViewById(R.id.state)).getText().toString().equals(""));
|
||||||
|
String items = ((TextView) v.findViewById(R.id.items)).getText().toString();
|
||||||
|
assertTrue(items.contains(Long.toString(mModel.localFiles)));
|
||||||
|
assertTrue(items.contains(Long.toString(mModel.localFiles)));
|
||||||
|
String size = ((TextView) v.findViewById(R.id.size)).getText().toString();
|
||||||
|
assertTrue(size.contains("1 MB"));
|
||||||
|
assertTrue(size.contains("1 GB"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ public class WebGuiActivity extends SyncthingActivity implements SyncthingServic
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize WebView.
|
* Initialize WebView.
|
||||||
* <p/>
|
*
|
||||||
* Ignore lint javascript warning as js is loaded only from our known, local service.
|
* Ignore lint javascript warning as js is loaded only from our known, local service.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -66,7 +66,7 @@ public class WebGuiActivity extends SyncthingActivity implements SyncthingServic
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onWebGuiAvailable() {
|
public void onWebGuiAvailable() {
|
||||||
mWebView.loadUrl(getApi().getUrl());
|
mWebView.loadUrl(getService().getWebGuiUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.nutomic.syncthingandroid.syncthing;
|
||||||
|
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpHead;
|
||||||
|
import org.apache.http.conn.HttpHostConnectException;
|
||||||
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polls SYNCTHING_URL until it returns HTTP status OK, then calls all listeners
|
||||||
|
* in mOnWebGuiAvailableListeners and clears it.
|
||||||
|
*/
|
||||||
|
public abstract class PollWebGuiAvailableTask extends AsyncTask<String, Void, Void> {
|
||||||
|
|
||||||
|
private static final String TAG = "PollWebGuiAvailableTask";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interval in ms, at which connections to the web gui are performed on first start
|
||||||
|
* to find out if it's online.
|
||||||
|
*/
|
||||||
|
private static final long WEB_GUI_POLL_INTERVAL = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param url The URL of the web GUI (eg 127.0.0.1:8080).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(String... url) {
|
||||||
|
int status = 0;
|
||||||
|
HttpClient httpclient = new DefaultHttpClient();
|
||||||
|
HttpHead head = new HttpHead(url[0]);
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
Thread.sleep(WEB_GUI_POLL_INTERVAL);
|
||||||
|
HttpResponse response = httpclient.execute(head);
|
||||||
|
// NOTE: status is not really needed, as HttpHostConnectException is thrown
|
||||||
|
// earlier.
|
||||||
|
status = response.getStatusLine().getStatusCode();
|
||||||
|
} catch (HttpHostConnectException e) {
|
||||||
|
// We catch this in every call, as long as the service is not online,
|
||||||
|
// so we ignore and continue.
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Failed to poll for web interface", e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.w(TAG, "Failed to poll for web interface", e);
|
||||||
|
}
|
||||||
|
} while (status != HttpStatus.SC_OK);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ import java.io.IOException;
|
||||||
/**
|
/**
|
||||||
* Performs a POST request with no parameters to the URL in uri[0] with the path in uri[1].
|
* Performs a POST request with no parameters to the URL in uri[0] with the path in uri[1].
|
||||||
*/
|
*/
|
||||||
public class PostTask extends AsyncTask<String, Void, Void> {
|
public class PostTask extends AsyncTask<String, Void, Boolean> {
|
||||||
|
|
||||||
private static final String TAG = "PostTask";
|
private static final String TAG = "PostTask";
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ public class PostTask extends AsyncTask<String, Void, Void> {
|
||||||
* params[3] The request content (optional)
|
* params[3] The request content (optional)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(String... params) {
|
protected Boolean doInBackground(String... params) {
|
||||||
String fullUri = params[0] + params[1];
|
String fullUri = params[0] + params[1];
|
||||||
HttpClient httpclient = new DefaultHttpClient();
|
HttpClient httpclient = new DefaultHttpClient();
|
||||||
HttpPost post = new HttpPost(fullUri);
|
HttpPost post = new HttpPost(fullUri);
|
||||||
|
@ -44,8 +44,9 @@ public class PostTask extends AsyncTask<String, Void, Void> {
|
||||||
httpclient.execute(post);
|
httpclient.execute(post);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, "Failed to call Rest API at " + fullUri, e);
|
Log.w(TAG, "Failed to call Rest API at " + fullUri, e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
|
|
||||||
private static final int NOTIFICATION_RESTART = 2;
|
private static final int NOTIFICATION_RESTART = 2;
|
||||||
|
|
||||||
private final SyncthingService mSyncthingService;
|
private final Context mContext;
|
||||||
|
|
||||||
private String mVersion;
|
private String mVersion;
|
||||||
|
|
||||||
|
@ -165,28 +165,15 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
* Stores the latest result of {@link #getModel(String, OnReceiveModelListener)} for each repo,
|
* Stores the latest result of {@link #getModel(String, OnReceiveModelListener)} for each repo,
|
||||||
* for calculating node percentage in {@link #getConnections(OnReceiveConnectionsListener)}.
|
* for calculating node percentage in {@link #getConnections(OnReceiveConnectionsListener)}.
|
||||||
*/
|
*/
|
||||||
private HashMap<String, Model> mCachedModelInfo = new HashMap<String, Model>();
|
private HashMap<String, Model> mCachedModelInfo = new HashMap<>();
|
||||||
|
|
||||||
public RestApi(SyncthingService syncthingService, String url, String apiKey) {
|
public RestApi(Context context, String url, String apiKey, OnApiAvailableListener listener) {
|
||||||
mSyncthingService = syncthingService;
|
mContext = context;
|
||||||
mUrl = url;
|
mUrl = url;
|
||||||
mApiKey = apiKey;
|
mApiKey = apiKey;
|
||||||
mNotificationManager = (NotificationManager)
|
mNotificationManager = (NotificationManager)
|
||||||
mSyncthingService.getSystemService(Context.NOTIFICATION_SERVICE);
|
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
}
|
mOnApiAvailableListener = listener;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the full URL of the web gui.
|
|
||||||
*/
|
|
||||||
public String getUrl() {
|
|
||||||
return mUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the API key needed to access the Rest API.
|
|
||||||
*/
|
|
||||||
public String getApiKey() {
|
|
||||||
return mApiKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -199,6 +186,12 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
*/
|
*/
|
||||||
private static final int TOTAL_STARTUP_CALLS = 3;
|
private static final int TOTAL_STARTUP_CALLS = 3;
|
||||||
|
|
||||||
|
public interface OnApiAvailableListener {
|
||||||
|
public void onApiAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnApiAvailableListener mOnApiAvailableListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets local node id, syncthing version and config, then calls all OnApiAvailableListeners.
|
* Gets local node id, syncthing version and config, then calls all OnApiAvailableListeners.
|
||||||
*/
|
*/
|
||||||
|
@ -243,7 +236,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
throw new AssertionError("Too many startup calls");
|
throw new AssertionError("Too many startup calls");
|
||||||
}
|
}
|
||||||
if (value == TOTAL_STARTUP_CALLS) {
|
if (value == TOTAL_STARTUP_CALLS) {
|
||||||
mSyncthingService.onApiChange();
|
mOnApiAvailableListener.onApiAvailable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +320,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
if (mRestartPostponed)
|
if (mRestartPostponed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
final Intent intent = new Intent(mSyncthingService, SyncthingService.class)
|
final Intent intent = new Intent(mContext, SyncthingService.class)
|
||||||
.setAction(SyncthingService.ACTION_RESTART);
|
.setAction(SyncthingService.ACTION_RESTART);
|
||||||
|
|
||||||
AlertDialog.Builder builder = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
|
AlertDialog.Builder builder = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
@ -337,7 +330,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
mSyncthingService.startService(intent);
|
mContext.startService(intent);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.restart_later, new DialogInterface.OnClickListener() {
|
.setNegativeButton(R.string.restart_later, new DialogInterface.OnClickListener() {
|
||||||
|
@ -361,13 +354,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
* Creates a notification prompting the user to restart the app.
|
* Creates a notification prompting the user to restart the app.
|
||||||
*/
|
*/
|
||||||
private void createRestartNotification() {
|
private void createRestartNotification() {
|
||||||
Intent intent = new Intent(mSyncthingService, SyncthingService.class)
|
Intent intent = new Intent(mContext, SyncthingService.class)
|
||||||
.setAction(SyncthingService.ACTION_RESTART);
|
.setAction(SyncthingService.ACTION_RESTART);
|
||||||
PendingIntent pi = PendingIntent.getService(mSyncthingService, 0, intent, 0);
|
PendingIntent pi = PendingIntent.getService(mContext, 0, intent, 0);
|
||||||
|
|
||||||
Notification n = new NotificationCompat.Builder(mSyncthingService)
|
Notification n = new NotificationCompat.Builder(mContext)
|
||||||
.setContentTitle(mSyncthingService.getString(R.string.restart_title))
|
.setContentTitle(mContext.getString(R.string.restart_title))
|
||||||
.setContentText(mSyncthingService.getString(R.string.restart_notification_text))
|
.setContentText(mContext.getString(R.string.restart_notification_text))
|
||||||
.setSmallIcon(R.drawable.ic_launcher)
|
.setSmallIcon(R.drawable.ic_launcher)
|
||||||
.setContentIntent(pi)
|
.setContentIntent(pi)
|
||||||
.build();
|
.build();
|
||||||
|
@ -381,13 +374,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
*/
|
*/
|
||||||
public List<Node> getNodes() {
|
public List<Node> getNodes() {
|
||||||
if (mConfig == null)
|
if (mConfig == null)
|
||||||
return new ArrayList<Node>();
|
return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return getNodes(mConfig.getJSONArray("Nodes"));
|
return getNodes(mConfig.getJSONArray("Nodes"));
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.w(TAG, "Failed to read nodes", e);
|
Log.w(TAG, "Failed to read nodes", e);
|
||||||
return new ArrayList<Node>();
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,7 +425,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
*/
|
*/
|
||||||
private List<Node> getNodes(JSONArray nodes) throws JSONException {
|
private List<Node> getNodes(JSONArray nodes) throws JSONException {
|
||||||
List<Node> ret;
|
List<Node> ret;
|
||||||
ret = new ArrayList<Node>(nodes.length());
|
ret = new ArrayList<>(nodes.length());
|
||||||
for (int i = 0; i < nodes.length(); i++) {
|
for (int i = 0; i < nodes.length(); i++) {
|
||||||
JSONObject json = nodes.getJSONObject(i);
|
JSONObject json = nodes.getJSONObject(i);
|
||||||
Node n = new Node();
|
Node n = new Node();
|
||||||
|
@ -453,12 +446,12 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
*/
|
*/
|
||||||
public List<Repo> getRepos() {
|
public List<Repo> getRepos() {
|
||||||
if (mConfig == null)
|
if (mConfig == null)
|
||||||
return new ArrayList<Repo>();
|
return new ArrayList<>();
|
||||||
|
|
||||||
List<Repo> ret = null;
|
List<Repo> ret;
|
||||||
try {
|
try {
|
||||||
JSONArray repos = mConfig.getJSONArray("Repositories");
|
JSONArray repos = mConfig.getJSONArray("Repositories");
|
||||||
ret = new ArrayList<Repo>(repos.length());
|
ret = new ArrayList<>(repos.length());
|
||||||
for (int i = 0; i < repos.length(); i++) {
|
for (int i = 0; i < repos.length(); i++) {
|
||||||
JSONObject json = repos.getJSONObject(i);
|
JSONObject json = repos.getJSONObject(i);
|
||||||
Repo r = new Repo();
|
Repo r = new Repo();
|
||||||
|
@ -482,6 +475,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
}
|
}
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.w(TAG, "Failed to read nodes", e);
|
Log.w(TAG, "Failed to read nodes", e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -661,7 +655,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
*/
|
*/
|
||||||
public void editNode(final Node node,
|
public void editNode(final Node node,
|
||||||
final OnNodeIdNormalizedListener listener) {
|
final OnNodeIdNormalizedListener listener) {
|
||||||
mSyncthingService.getApi().normalizeNodeId(node.NodeID,
|
normalizeNodeId(node.NodeID,
|
||||||
new RestApi.OnNodeIdNormalizedListener() {
|
new RestApi.OnNodeIdNormalizedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onNodeIdNormalized(String normalizedId, String error) {
|
public void onNodeIdNormalized(String normalizedId, String error) {
|
||||||
|
@ -696,7 +690,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
n.put("NodeID", node.NodeID);
|
n.put("NodeID", node.NodeID);
|
||||||
n.put("Name", node.Name);
|
n.put("Name", node.Name);
|
||||||
n.put("Addresses", listToJson(node.Addresses.split(" ")));
|
n.put("Addresses", listToJson(node.Addresses.split(" ")));
|
||||||
configUpdated(mSyncthingService);
|
configUpdated(mContext);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.w(TAG, "Failed to read nodes", e);
|
Log.w(TAG, "Failed to read nodes", e);
|
||||||
}
|
}
|
||||||
|
@ -708,7 +702,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
/**
|
/**
|
||||||
* Deletes the given node from syncthing.
|
* Deletes the given node from syncthing.
|
||||||
*/
|
*/
|
||||||
public void deleteNode(Node node, Activity activity) {
|
public boolean deleteNode(Node node, Context context) {
|
||||||
try {
|
try {
|
||||||
JSONArray nodes = mConfig.getJSONArray("Nodes");
|
JSONArray nodes = mConfig.getJSONArray("Nodes");
|
||||||
|
|
||||||
|
@ -720,16 +714,18 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
configUpdated(activity);
|
configUpdated(context);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.w(TAG, "Failed to edit repo", e);
|
Log.w(TAG, "Failed to edit repo", e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates or creates the given node.
|
* Updates or creates the given node.
|
||||||
*/
|
*/
|
||||||
public void editRepo(Repo repo, boolean create, Context context) {
|
public boolean editRepo(Repo repo, boolean create, Context context) {
|
||||||
try {
|
try {
|
||||||
JSONArray repos = mConfig.getJSONArray("Repositories");
|
JSONArray repos = mConfig.getJSONArray("Repositories");
|
||||||
JSONObject r = null;
|
JSONObject r = null;
|
||||||
|
@ -769,13 +765,15 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
configUpdated(context);
|
configUpdated(context);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.w(TAG, "Failed to edit repo " + repo.ID + " at " + repo.Directory, e);
|
Log.w(TAG, "Failed to edit repo " + repo.ID + " at " + repo.Directory, e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the given repository from syncthing.
|
* Deletes the given repository from syncthing.
|
||||||
*/
|
*/
|
||||||
public void deleteRepo(Repo repo, Activity activity) {
|
public boolean deleteRepo(Repo repo, Context context) {
|
||||||
try {
|
try {
|
||||||
JSONArray repos = mConfig.getJSONArray("Repositories");
|
JSONArray repos = mConfig.getJSONArray("Repositories");
|
||||||
|
|
||||||
|
@ -787,10 +785,12 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
configUpdated(activity);
|
configUpdated(context);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.w(TAG, "Failed to edit repo", e);
|
Log.w(TAG, "Failed to edit repo", e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -844,13 +844,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
/**
|
/**
|
||||||
* Shares the given node id via Intent. Must be called from an Activity.
|
* Shares the given node id via Intent. Must be called from an Activity.
|
||||||
*/
|
*/
|
||||||
public static void shareNodeId(Activity activity, String id) {
|
public static void shareNodeId(Context context, String id) {
|
||||||
Intent shareIntent = new Intent();
|
Intent shareIntent = new Intent();
|
||||||
shareIntent.setAction(Intent.ACTION_SEND);
|
shareIntent.setAction(Intent.ACTION_SEND);
|
||||||
shareIntent.setType("text/plain");
|
shareIntent.setType("text/plain");
|
||||||
shareIntent.putExtra(android.content.Intent.EXTRA_TEXT, id);
|
shareIntent.putExtra(android.content.Intent.EXTRA_TEXT, id);
|
||||||
activity.startActivity(Intent.createChooser(
|
context.startActivity(Intent.createChooser(
|
||||||
shareIntent, activity.getString(R.string.send_node_id_to)));
|
shareIntent, context.getString(R.string.send_node_id_to)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -863,15 +863,15 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
int sdk = android.os.Build.VERSION.SDK_INT;
|
int sdk = android.os.Build.VERSION.SDK_INT;
|
||||||
if (sdk < android.os.Build.VERSION_CODES.HONEYCOMB) {
|
if (sdk < android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager)
|
android.text.ClipboardManager clipboard = (android.text.ClipboardManager)
|
||||||
mSyncthingService.getSystemService(Context.CLIPBOARD_SERVICE);
|
mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
clipboard.setText(id);
|
clipboard.setText(id);
|
||||||
} else {
|
} else {
|
||||||
ClipboardManager clipboard = (ClipboardManager)
|
ClipboardManager clipboard = (ClipboardManager)
|
||||||
mSyncthingService.getSystemService(Context.CLIPBOARD_SERVICE);
|
mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
ClipData clip = ClipData.newPlainText(mSyncthingService.getString(R.string.node_id), id);
|
ClipData clip = ClipData.newPlainText(mContext.getString(R.string.node_id), id);
|
||||||
clipboard.setPrimaryClip(clip);
|
clipboard.setPrimaryClip(clip);
|
||||||
}
|
}
|
||||||
Toast.makeText(mSyncthingService, R.string.node_id_copied_to_clipboard, Toast.LENGTH_SHORT)
|
Toast.makeText(mContext, R.string.node_id_copied_to_clipboard, Toast.LENGTH_SHORT)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,12 +54,6 @@ public class SyncthingService extends Service {
|
||||||
*/
|
*/
|
||||||
public static final int GUI_UPDATE_INTERVAL = 1000;
|
public static final int GUI_UPDATE_INTERVAL = 1000;
|
||||||
|
|
||||||
/**
|
|
||||||
* Interval in ms, at which connections to the web gui are performed on first start
|
|
||||||
* to find out if it's online.
|
|
||||||
*/
|
|
||||||
private static final long WEB_GUI_POLL_INTERVAL = 100;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the public key file in the data directory.
|
* Name of the public key file in the data directory.
|
||||||
*/
|
*/
|
||||||
|
@ -73,7 +67,9 @@ public class SyncthingService extends Service {
|
||||||
/**
|
/**
|
||||||
* Path to the native, integrated syncthing binary, relative to the data folder
|
* Path to the native, integrated syncthing binary, relative to the data folder
|
||||||
*/
|
*/
|
||||||
private static final String BINARY_NAME = "lib/libsyncthing.so";
|
public static final String BINARY_NAME = "lib/libsyncthing.so";
|
||||||
|
|
||||||
|
private ConfigXml mConfig;
|
||||||
|
|
||||||
private RestApi mApi;
|
private RestApi mApi;
|
||||||
|
|
||||||
|
@ -113,7 +109,7 @@ public class SyncthingService extends Service {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if a stop was requested while syncthing is starting, in that case, perform stop in
|
* True if a stop was requested while syncthing is starting, in that case, perform stop in
|
||||||
* {@link PollWebGuiAvailableTask}.
|
* {@link PollWebGuiAvailableTaskImpl}.
|
||||||
*/
|
*/
|
||||||
private boolean mStopScheduled = false;
|
private boolean mStopScheduled = false;
|
||||||
|
|
||||||
|
@ -121,7 +117,7 @@ public class SyncthingService extends Service {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles intents, either {@link #ACTION_RESTART}, or intents having
|
* Handles intents, either {@link #ACTION_RESTART}, or intents having
|
||||||
* {@link DeviceStateHolder.EXTRA_HAS_WIFI} or {@link DeviceStateHolder.EXTRA_IS_CHARGING}
|
* {@link DeviceStateHolder#EXTRA_HAS_WIFI} or {@link DeviceStateHolder#EXTRA_IS_CHARGING}
|
||||||
* (which are handled by {@link DeviceStateHolder}.
|
* (which are handled by {@link DeviceStateHolder}.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -131,15 +127,10 @@ public class SyncthingService extends Service {
|
||||||
} else if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
} else if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
||||||
new PostTask() {
|
new PostTask() {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Void aVoid) {
|
protected void onPostExecute(Boolean aBoolean) {
|
||||||
ConfigXml config = new ConfigXml(SyncthingService.this);
|
new StartupTask().execute();
|
||||||
mApi = new RestApi(SyncthingService.this,
|
|
||||||
config.getWebGuiUrl(), config.getApiKey());
|
|
||||||
mCurrentState = State.STARTING;
|
|
||||||
registerOnWebGuiAvailableListener(mApi);
|
|
||||||
new PollWebGuiAvailableTask().execute();
|
|
||||||
}
|
}
|
||||||
}.execute(mApi.getUrl(), PostTask.URI_RESTART, mApi.getApiKey());
|
}.execute(mConfig.getWebGuiUrl(), PostTask.URI_RESTART, mConfig.getApiKey());
|
||||||
} else if (mCurrentState != State.INIT) {
|
} else if (mCurrentState != State.INIT) {
|
||||||
mDeviceStateHolder.update(intent);
|
mDeviceStateHolder.update(intent);
|
||||||
updateState();
|
updateState();
|
||||||
|
@ -169,7 +160,7 @@ public class SyncthingService extends Service {
|
||||||
|
|
||||||
mCurrentState = State.STARTING;
|
mCurrentState = State.STARTING;
|
||||||
registerOnWebGuiAvailableListener(mApi);
|
registerOnWebGuiAvailableListener(mApi);
|
||||||
new PollWebGuiAvailableTask().execute();
|
new PollWebGuiAvailableTaskImpl().execute(mConfig.getWebGuiUrl());
|
||||||
new Thread(new SyncthingRunnable(
|
new Thread(new SyncthingRunnable(
|
||||||
this, getApplicationInfo().dataDir + "/" + BINARY_NAME)).start();
|
this, getApplicationInfo().dataDir + "/" + BINARY_NAME)).start();
|
||||||
}
|
}
|
||||||
|
@ -190,53 +181,6 @@ public class SyncthingService extends Service {
|
||||||
onApiChange();
|
onApiChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Polls SYNCTHING_URL until it returns HTTP status OK, then calls all listeners
|
|
||||||
* in mOnWebGuiAvailableListeners and clears it.
|
|
||||||
*/
|
|
||||||
private class PollWebGuiAvailableTask extends AsyncTask<Void, Void, Void> {
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
int status = 0;
|
|
||||||
HttpClient httpclient = new DefaultHttpClient();
|
|
||||||
HttpHead head = new HttpHead(mApi.getUrl());
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
Thread.sleep(WEB_GUI_POLL_INTERVAL);
|
|
||||||
HttpResponse response = httpclient.execute(head);
|
|
||||||
// NOTE: status is not really needed, as HttpHostConnectException is thrown
|
|
||||||
// earlier.
|
|
||||||
status = response.getStatusLine().getStatusCode();
|
|
||||||
} catch (HttpHostConnectException e) {
|
|
||||||
// We catch this in every call, as long as the service is not online,
|
|
||||||
// so we ignore and continue.
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, "Failed to poll for web interface", e);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.w(TAG, "Failed to poll for web interface", e);
|
|
||||||
}
|
|
||||||
} while (status != HttpStatus.SC_OK);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void aVoid) {
|
|
||||||
if (mStopScheduled) {
|
|
||||||
mCurrentState = State.DISABLED;
|
|
||||||
onApiChange();
|
|
||||||
mApi.shutdown();
|
|
||||||
mStopScheduled = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Log.i(TAG, "Web GUI has come online at " + mApi.getUrl());
|
|
||||||
mCurrentState = State.ACTIVE;
|
|
||||||
for (OnWebGuiAvailableListener listener : mOnWebGuiAvailableListeners) {
|
|
||||||
listener.onWebGuiAvailable();
|
|
||||||
}
|
|
||||||
mOnWebGuiAvailableListeners.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move config file, keys, and index files to "official" folder
|
* Move config file, keys, and index files to "official" folder
|
||||||
* <p/>
|
* <p/>
|
||||||
|
@ -299,22 +243,29 @@ public class SyncthingService extends Service {
|
||||||
@Override
|
@Override
|
||||||
protected Pair<String, String> doInBackground(Void... voids) {
|
protected Pair<String, String> doInBackground(Void... voids) {
|
||||||
moveConfigFiles();
|
moveConfigFiles();
|
||||||
ConfigXml config = new ConfigXml(SyncthingService.this);
|
mConfig = new ConfigXml(SyncthingService.this);
|
||||||
config.updateIfNeeded();
|
mConfig.updateIfNeeded();
|
||||||
|
|
||||||
if (isFirstStart()) {
|
if (isFirstStart()) {
|
||||||
Log.i(TAG, "App started for the first time. " +
|
Log.i(TAG, "App started for the first time. " +
|
||||||
"Copying default config, keys will be generated automatically");
|
"Copying default config, keys will be generated automatically");
|
||||||
config.createCameraRepo();
|
mConfig.createCameraRepo();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Pair<>(config.getWebGuiUrl(), config.getApiKey());
|
return new Pair<>(mConfig.getWebGuiUrl(), mConfig.getApiKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Pair<String, String> urlAndKey) {
|
protected void onPostExecute(Pair<String, String> urlAndKey) {
|
||||||
mApi = new RestApi(SyncthingService.this, urlAndKey.first, urlAndKey.second);
|
mApi = new RestApi(SyncthingService.this, urlAndKey.first, urlAndKey.second,
|
||||||
Log.i(TAG, "Web GUI will be available at " + mApi.getUrl());
|
new RestApi.OnApiAvailableListener() {
|
||||||
|
@Override
|
||||||
|
public void onApiAvailable() {
|
||||||
|
onApiChange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
registerOnWebGuiAvailableListener(mApi);
|
||||||
|
Log.i(TAG, "Web GUI will be available at " + mConfig.getWebGuiUrl());
|
||||||
|
|
||||||
// HACK: Make sure there is no syncthing binary left running from an improper
|
// HACK: Make sure there is no syncthing binary left running from an improper
|
||||||
// shutdown (eg Play Store updateIfNeeded).
|
// shutdown (eg Play Store updateIfNeeded).
|
||||||
|
@ -379,12 +330,31 @@ public class SyncthingService extends Service {
|
||||||
mOnApiChangeListeners.add(new WeakReference<OnApiChangeListener>(listener));
|
mOnApiChangeListeners.add(new WeakReference<OnApiChangeListener>(listener));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class PollWebGuiAvailableTaskImpl extends PollWebGuiAvailableTask {
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void aVoid) {
|
||||||
|
if (mStopScheduled) {
|
||||||
|
mCurrentState = State.DISABLED;
|
||||||
|
onApiChange();
|
||||||
|
mApi.shutdown();
|
||||||
|
mStopScheduled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.i(TAG, "Web GUI has come online at " + mConfig.getWebGuiUrl());
|
||||||
|
mCurrentState = State.ACTIVE;
|
||||||
|
for (OnWebGuiAvailableListener listener : mOnWebGuiAvailableListeners) {
|
||||||
|
listener.onWebGuiAvailable();
|
||||||
|
}
|
||||||
|
mOnWebGuiAvailableListeners.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to notifiy listeners of an API change.
|
* Called to notifiy listeners of an API change.
|
||||||
* <p/>
|
*
|
||||||
* Must only be called from SyncthingService or {@link RestApi}.
|
* Must only be called from SyncthingService or {@link RestApi}.
|
||||||
*/
|
*/
|
||||||
public void onApiChange() {
|
private void onApiChange() {
|
||||||
for (Iterator<WeakReference<OnApiChangeListener>> i = mOnApiChangeListeners.iterator();
|
for (Iterator<WeakReference<OnApiChangeListener>> i = mOnApiChangeListeners.iterator();
|
||||||
i.hasNext(); ) {
|
i.hasNext(); ) {
|
||||||
WeakReference<OnApiChangeListener> listener = i.next();
|
WeakReference<OnApiChangeListener> listener = i.next();
|
||||||
|
@ -427,4 +397,8 @@ public class SyncthingService extends Service {
|
||||||
.setCancelable(false);
|
.setCancelable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getWebGuiUrl() {
|
||||||
|
return mConfig.getWebGuiUrl();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,26 +42,28 @@ public class NodesAdapter extends ArrayAdapter<RestApi.Node>
|
||||||
TextView download = (TextView) convertView.findViewById(R.id.download);
|
TextView download = (TextView) convertView.findViewById(R.id.download);
|
||||||
TextView upload = (TextView) convertView.findViewById(R.id.upload);
|
TextView upload = (TextView) convertView.findViewById(R.id.upload);
|
||||||
|
|
||||||
name.setText(getItem(position).Name);
|
String nodeId = getItem(position).NodeID;
|
||||||
final String nodeId = getItem(position).NodeID;
|
|
||||||
|
|
||||||
RestApi.Connection conn = mConnections.get(nodeId);
|
RestApi.Connection conn = mConnections.get(nodeId);
|
||||||
Resources res = getContext().getResources();
|
|
||||||
|
name.setText(getItem(position).Name);
|
||||||
|
Resources r = getContext().getResources();
|
||||||
if (conn != null) {
|
if (conn != null) {
|
||||||
if (conn.Completion == 100) {
|
if (conn.Completion == 100) {
|
||||||
status.setText(res.getString(R.string.node_up_to_date));
|
status.setText(r.getString(R.string.node_up_to_date));
|
||||||
status.setTextColor(res.getColor(R.color.text_green));
|
status.setTextColor(r.getColor(R.color.text_green));
|
||||||
} else {
|
}
|
||||||
status.setText(res.getString(R.string.node_syncing, conn.Completion));
|
else {
|
||||||
status.setTextColor(res.getColor(R.color.text_blue));
|
status.setText(r.getString(R.string.node_syncing, conn.Completion));
|
||||||
|
status.setTextColor(r.getColor(R.color.text_blue));
|
||||||
}
|
}
|
||||||
download.setText(RestApi.readableTransferRate(getContext(), conn.InBits));
|
download.setText(RestApi.readableTransferRate(getContext(), conn.InBits));
|
||||||
upload.setText(RestApi.readableTransferRate(getContext(), conn.OutBits));
|
upload.setText(RestApi.readableTransferRate(getContext(), conn.OutBits));
|
||||||
} else {
|
}
|
||||||
download.setText("0 " + res.getStringArray(R.array.transfer_rate_units)[0]);
|
else {
|
||||||
upload.setText("0 " + res.getStringArray(R.array.transfer_rate_units)[0]);
|
download.setText("0 " + r.getStringArray(R.array.transfer_rate_units)[0]);
|
||||||
status.setText(res.getString(R.string.node_disconnected));
|
upload.setText("0 " + r.getStringArray(R.array.transfer_rate_units)[0]);
|
||||||
status.setTextColor(res.getColor(R.color.text_red));
|
status.setText(r.getString(R.string.node_disconnected));
|
||||||
|
status.setTextColor(r.getColor(R.color.text_red));
|
||||||
}
|
}
|
||||||
|
|
||||||
return convertView;
|
return convertView;
|
||||||
|
|
|
@ -35,17 +35,17 @@ public class ReposAdapter extends ArrayAdapter<RestApi.Repo>
|
||||||
}
|
}
|
||||||
|
|
||||||
TextView id = (TextView) convertView.findViewById(R.id.id);
|
TextView id = (TextView) convertView.findViewById(R.id.id);
|
||||||
|
|
||||||
TextView state = (TextView) convertView.findViewById(R.id.state);
|
TextView state = (TextView) convertView.findViewById(R.id.state);
|
||||||
TextView folder = (TextView) convertView.findViewById(R.id.folder);
|
TextView directory = (TextView) convertView.findViewById(R.id.directory);
|
||||||
TextView items = (TextView) convertView.findViewById(R.id.items);
|
TextView items = (TextView) convertView.findViewById(R.id.items);
|
||||||
TextView size = (TextView) convertView.findViewById(R.id.size);
|
TextView size = (TextView) convertView.findViewById(R.id.size);
|
||||||
TextView invalid = (TextView) convertView.findViewById(R.id.invalid);
|
TextView invalid = (TextView) convertView.findViewById(R.id.invalid);
|
||||||
|
|
||||||
id.setText(getItem(position).ID);
|
RestApi.Repo repo = getItem(position);
|
||||||
|
RestApi.Model model = mModels.get(repo.ID);
|
||||||
|
id.setText(repo.ID);
|
||||||
state.setTextColor(getContext().getResources().getColor(R.color.text_green));
|
state.setTextColor(getContext().getResources().getColor(R.color.text_green));
|
||||||
folder.setText((getItem(position).Directory));
|
directory.setText((repo.Directory));
|
||||||
RestApi.Model model = mModels.get(getItem(position).ID);
|
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
state.setText(getContext().getString(R.string.repo_progress_format, model.state,
|
state.setText(getContext().getString(R.string.repo_progress_format, model.state,
|
||||||
(model.globalBytes <= 0)
|
(model.globalBytes <= 0)
|
||||||
|
@ -56,10 +56,13 @@ public class ReposAdapter extends ArrayAdapter<RestApi.Repo>
|
||||||
.getString(R.string.files, model.localFiles, model.globalFiles));
|
.getString(R.string.files, model.localFiles, model.globalFiles));
|
||||||
size.setText(RestApi.readableFileSize(getContext(), model.localBytes) + " / " +
|
size.setText(RestApi.readableFileSize(getContext(), model.localBytes) + " / " +
|
||||||
RestApi.readableFileSize(getContext(), model.globalBytes));
|
RestApi.readableFileSize(getContext(), model.globalBytes));
|
||||||
invalid.setText(model.invalid);
|
if (repo.Invalid.equals("")) {
|
||||||
invalid.setVisibility((model.invalid.equals("")) ? View.GONE : View.VISIBLE);
|
invalid.setText(model.invalid);
|
||||||
|
invalid.setVisibility((model.invalid.equals("")) ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
invalid.setVisibility(View.GONE);
|
invalid.setText(repo.Invalid);
|
||||||
|
invalid.setVisibility((repo.Invalid.equals("")) ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return convertView;
|
return convertView;
|
||||||
|
@ -91,4 +94,5 @@ public class ReposAdapter extends ArrayAdapter<RestApi.Repo>
|
||||||
mModels.put(repoId, model);
|
mModels.put(repoId, model);
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/folder"
|
android:id="@+id/directory"
|
||||||
android:layout_below="@id/state"
|
android:layout_below="@id/state"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/items"
|
android:id="@+id/items"
|
||||||
android:layout_below="@id/folder"
|
android:layout_below="@id/directory"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
Loading…
Reference in New Issue