mirror of
https://github.com/syncthing/syncthing-android.git
synced 2025-01-11 04:25:53 +00:00
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
12 changed files with 561 additions and 149 deletions
|
@ -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.
|
||||
* <p/>
|
||||
*
|
||||
* Ignore lint javascript warning as js is loaded only from our known, local service.
|
||||
*/
|
||||
@Override
|
||||
|
@ -66,7 +66,7 @@ public class WebGuiActivity extends SyncthingActivity implements SyncthingServic
|
|||
*/
|
||||
@Override
|
||||
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].
|
||||
*/
|
||||
public class PostTask extends AsyncTask<String, Void, Void> {
|
||||
public class PostTask extends AsyncTask<String, Void, Boolean> {
|
||||
|
||||
private static final String TAG = "PostTask";
|
||||
|
||||
|
@ -31,7 +31,7 @@ public class PostTask extends AsyncTask<String, Void, Void> {
|
|||
* params[3] The request content (optional)
|
||||
*/
|
||||
@Override
|
||||
protected Void doInBackground(String... params) {
|
||||
protected Boolean doInBackground(String... params) {
|
||||
String fullUri = params[0] + params[1];
|
||||
HttpClient httpclient = new DefaultHttpClient();
|
||||
HttpPost post = new HttpPost(fullUri);
|
||||
|
@ -44,8 +44,9 @@ public class PostTask extends AsyncTask<String, Void, Void> {
|
|||
httpclient.execute(post);
|
||||
} catch (IOException 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 final SyncthingService mSyncthingService;
|
||||
private final Context mContext;
|
||||
|
||||
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,
|
||||
* 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) {
|
||||
mSyncthingService = syncthingService;
|
||||
public RestApi(Context context, String url, String apiKey, OnApiAvailableListener listener) {
|
||||
mContext = context;
|
||||
mUrl = url;
|
||||
mApiKey = apiKey;
|
||||
mNotificationManager = (NotificationManager)
|
||||
mSyncthingService.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mOnApiAvailableListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -199,6 +186,12 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -243,7 +236,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
throw new AssertionError("Too many startup calls");
|
||||
}
|
||||
if (value == TOTAL_STARTUP_CALLS) {
|
||||
mSyncthingService.onApiChange();
|
||||
mOnApiAvailableListener.onApiAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,7 +320,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
if (mRestartPostponed)
|
||||
return;
|
||||
|
||||
final Intent intent = new Intent(mSyncthingService, SyncthingService.class)
|
||||
final Intent intent = new Intent(mContext, SyncthingService.class)
|
||||
.setAction(SyncthingService.ACTION_RESTART);
|
||||
|
||||
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() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
mSyncthingService.startService(intent);
|
||||
mContext.startService(intent);
|
||||
}
|
||||
})
|
||||
.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.
|
||||
*/
|
||||
private void createRestartNotification() {
|
||||
Intent intent = new Intent(mSyncthingService, SyncthingService.class)
|
||||
Intent intent = new Intent(mContext, SyncthingService.class)
|
||||
.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)
|
||||
.setContentTitle(mSyncthingService.getString(R.string.restart_title))
|
||||
.setContentText(mSyncthingService.getString(R.string.restart_notification_text))
|
||||
Notification n = new NotificationCompat.Builder(mContext)
|
||||
.setContentTitle(mContext.getString(R.string.restart_title))
|
||||
.setContentText(mContext.getString(R.string.restart_notification_text))
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentIntent(pi)
|
||||
.build();
|
||||
|
@ -381,13 +374,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
*/
|
||||
public List<Node> getNodes() {
|
||||
if (mConfig == null)
|
||||
return new ArrayList<Node>();
|
||||
return null;
|
||||
|
||||
try {
|
||||
return getNodes(mConfig.getJSONArray("Nodes"));
|
||||
} catch (JSONException 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 {
|
||||
List<Node> ret;
|
||||
ret = new ArrayList<Node>(nodes.length());
|
||||
ret = new ArrayList<>(nodes.length());
|
||||
for (int i = 0; i < nodes.length(); i++) {
|
||||
JSONObject json = nodes.getJSONObject(i);
|
||||
Node n = new Node();
|
||||
|
@ -453,12 +446,12 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
*/
|
||||
public List<Repo> getRepos() {
|
||||
if (mConfig == null)
|
||||
return new ArrayList<Repo>();
|
||||
return new ArrayList<>();
|
||||
|
||||
List<Repo> ret = null;
|
||||
List<Repo> ret;
|
||||
try {
|
||||
JSONArray repos = mConfig.getJSONArray("Repositories");
|
||||
ret = new ArrayList<Repo>(repos.length());
|
||||
ret = new ArrayList<>(repos.length());
|
||||
for (int i = 0; i < repos.length(); i++) {
|
||||
JSONObject json = repos.getJSONObject(i);
|
||||
Repo r = new Repo();
|
||||
|
@ -482,6 +475,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
}
|
||||
} catch (JSONException e) {
|
||||
Log.w(TAG, "Failed to read nodes", e);
|
||||
return null;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -661,7 +655,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
*/
|
||||
public void editNode(final Node node,
|
||||
final OnNodeIdNormalizedListener listener) {
|
||||
mSyncthingService.getApi().normalizeNodeId(node.NodeID,
|
||||
normalizeNodeId(node.NodeID,
|
||||
new RestApi.OnNodeIdNormalizedListener() {
|
||||
@Override
|
||||
public void onNodeIdNormalized(String normalizedId, String error) {
|
||||
|
@ -696,7 +690,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
n.put("NodeID", node.NodeID);
|
||||
n.put("Name", node.Name);
|
||||
n.put("Addresses", listToJson(node.Addresses.split(" ")));
|
||||
configUpdated(mSyncthingService);
|
||||
configUpdated(mContext);
|
||||
} catch (JSONException 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.
|
||||
*/
|
||||
public void deleteNode(Node node, Activity activity) {
|
||||
public boolean deleteNode(Node node, Context context) {
|
||||
try {
|
||||
JSONArray nodes = mConfig.getJSONArray("Nodes");
|
||||
|
||||
|
@ -720,16 +714,18 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
break;
|
||||
}
|
||||
}
|
||||
configUpdated(activity);
|
||||
configUpdated(context);
|
||||
} catch (JSONException e) {
|
||||
Log.w(TAG, "Failed to edit repo", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
JSONArray repos = mConfig.getJSONArray("Repositories");
|
||||
JSONObject r = null;
|
||||
|
@ -769,13 +765,15 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
configUpdated(context);
|
||||
} catch (JSONException e) {
|
||||
Log.w(TAG, "Failed to edit repo " + repo.ID + " at " + repo.Directory, e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given repository from syncthing.
|
||||
*/
|
||||
public void deleteRepo(Repo repo, Activity activity) {
|
||||
public boolean deleteRepo(Repo repo, Context context) {
|
||||
try {
|
||||
JSONArray repos = mConfig.getJSONArray("Repositories");
|
||||
|
||||
|
@ -787,10 +785,12 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
break;
|
||||
}
|
||||
}
|
||||
configUpdated(activity);
|
||||
configUpdated(context);
|
||||
} catch (JSONException 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.
|
||||
*/
|
||||
public static void shareNodeId(Activity activity, String id) {
|
||||
public static void shareNodeId(Context context, String id) {
|
||||
Intent shareIntent = new Intent();
|
||||
shareIntent.setAction(Intent.ACTION_SEND);
|
||||
shareIntent.setType("text/plain");
|
||||
shareIntent.putExtra(android.content.Intent.EXTRA_TEXT, id);
|
||||
activity.startActivity(Intent.createChooser(
|
||||
shareIntent, activity.getString(R.string.send_node_id_to)));
|
||||
context.startActivity(Intent.createChooser(
|
||||
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;
|
||||
if (sdk < android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager)
|
||||
mSyncthingService.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(id);
|
||||
} else {
|
||||
ClipboardManager clipboard = (ClipboardManager)
|
||||
mSyncthingService.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(mSyncthingService.getString(R.string.node_id), id);
|
||||
mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(mContext.getString(R.string.node_id), id);
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -54,12 +54,6 @@ public class SyncthingService extends Service {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -73,7 +67,9 @@ public class SyncthingService extends Service {
|
|||
/**
|
||||
* 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;
|
||||
|
||||
|
@ -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
|
||||
* {@link PollWebGuiAvailableTask}.
|
||||
* {@link PollWebGuiAvailableTaskImpl}.
|
||||
*/
|
||||
private boolean mStopScheduled = false;
|
||||
|
||||
|
@ -121,7 +117,7 @@ public class SyncthingService extends Service {
|
|||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
@Override
|
||||
|
@ -131,15 +127,10 @@ public class SyncthingService extends Service {
|
|||
} else if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
||||
new PostTask() {
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
ConfigXml config = new ConfigXml(SyncthingService.this);
|
||||
mApi = new RestApi(SyncthingService.this,
|
||||
config.getWebGuiUrl(), config.getApiKey());
|
||||
mCurrentState = State.STARTING;
|
||||
registerOnWebGuiAvailableListener(mApi);
|
||||
new PollWebGuiAvailableTask().execute();
|
||||
protected void onPostExecute(Boolean aBoolean) {
|
||||
new StartupTask().execute();
|
||||
}
|
||||
}.execute(mApi.getUrl(), PostTask.URI_RESTART, mApi.getApiKey());
|
||||
}.execute(mConfig.getWebGuiUrl(), PostTask.URI_RESTART, mConfig.getApiKey());
|
||||
} else if (mCurrentState != State.INIT) {
|
||||
mDeviceStateHolder.update(intent);
|
||||
updateState();
|
||||
|
@ -169,7 +160,7 @@ public class SyncthingService extends Service {
|
|||
|
||||
mCurrentState = State.STARTING;
|
||||
registerOnWebGuiAvailableListener(mApi);
|
||||
new PollWebGuiAvailableTask().execute();
|
||||
new PollWebGuiAvailableTaskImpl().execute(mConfig.getWebGuiUrl());
|
||||
new Thread(new SyncthingRunnable(
|
||||
this, getApplicationInfo().dataDir + "/" + BINARY_NAME)).start();
|
||||
}
|
||||
|
@ -190,53 +181,6 @@ public class SyncthingService extends Service {
|
|||
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
|
||||
* <p/>
|
||||
|
@ -299,22 +243,29 @@ public class SyncthingService extends Service {
|
|||
@Override
|
||||
protected Pair<String, String> doInBackground(Void... voids) {
|
||||
moveConfigFiles();
|
||||
ConfigXml config = new ConfigXml(SyncthingService.this);
|
||||
config.updateIfNeeded();
|
||||
mConfig = new ConfigXml(SyncthingService.this);
|
||||
mConfig.updateIfNeeded();
|
||||
|
||||
if (isFirstStart()) {
|
||||
Log.i(TAG, "App started for the first time. " +
|
||||
"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
|
||||
protected void onPostExecute(Pair<String, String> urlAndKey) {
|
||||
mApi = new RestApi(SyncthingService.this, urlAndKey.first, urlAndKey.second);
|
||||
Log.i(TAG, "Web GUI will be available at " + mApi.getUrl());
|
||||
mApi = new RestApi(SyncthingService.this, urlAndKey.first, urlAndKey.second,
|
||||
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
|
||||
// shutdown (eg Play Store updateIfNeeded).
|
||||
|
@ -379,12 +330,31 @@ public class SyncthingService extends Service {
|
|||
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.
|
||||
* <p/>
|
||||
*
|
||||
* Must only be called from SyncthingService or {@link RestApi}.
|
||||
*/
|
||||
public void onApiChange() {
|
||||
private void onApiChange() {
|
||||
for (Iterator<WeakReference<OnApiChangeListener>> i = mOnApiChangeListeners.iterator();
|
||||
i.hasNext(); ) {
|
||||
WeakReference<OnApiChangeListener> listener = i.next();
|
||||
|
@ -427,4 +397,8 @@ public class SyncthingService extends Service {
|
|||
.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 upload = (TextView) convertView.findViewById(R.id.upload);
|
||||
|
||||
name.setText(getItem(position).Name);
|
||||
final String nodeId = getItem(position).NodeID;
|
||||
|
||||
String nodeId = getItem(position).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.Completion == 100) {
|
||||
status.setText(res.getString(R.string.node_up_to_date));
|
||||
status.setTextColor(res.getColor(R.color.text_green));
|
||||
} else {
|
||||
status.setText(res.getString(R.string.node_syncing, conn.Completion));
|
||||
status.setTextColor(res.getColor(R.color.text_blue));
|
||||
status.setText(r.getString(R.string.node_up_to_date));
|
||||
status.setTextColor(r.getColor(R.color.text_green));
|
||||
}
|
||||
else {
|
||||
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));
|
||||
upload.setText(RestApi.readableTransferRate(getContext(), conn.OutBits));
|
||||
} else {
|
||||
download.setText("0 " + res.getStringArray(R.array.transfer_rate_units)[0]);
|
||||
upload.setText("0 " + res.getStringArray(R.array.transfer_rate_units)[0]);
|
||||
status.setText(res.getString(R.string.node_disconnected));
|
||||
status.setTextColor(res.getColor(R.color.text_red));
|
||||
}
|
||||
else {
|
||||
download.setText("0 " + r.getStringArray(R.array.transfer_rate_units)[0]);
|
||||
upload.setText("0 " + r.getStringArray(R.array.transfer_rate_units)[0]);
|
||||
status.setText(r.getString(R.string.node_disconnected));
|
||||
status.setTextColor(r.getColor(R.color.text_red));
|
||||
}
|
||||
|
||||
return convertView;
|
||||
|
|
|
@ -35,17 +35,17 @@ public class ReposAdapter extends ArrayAdapter<RestApi.Repo>
|
|||
}
|
||||
|
||||
TextView id = (TextView) convertView.findViewById(R.id.id);
|
||||
|
||||
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 size = (TextView) convertView.findViewById(R.id.size);
|
||||
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));
|
||||
folder.setText((getItem(position).Directory));
|
||||
RestApi.Model model = mModels.get(getItem(position).ID);
|
||||
directory.setText((repo.Directory));
|
||||
if (model != null) {
|
||||
state.setText(getContext().getString(R.string.repo_progress_format, model.state,
|
||||
(model.globalBytes <= 0)
|
||||
|
@ -56,10 +56,13 @@ public class ReposAdapter extends ArrayAdapter<RestApi.Repo>
|
|||
.getString(R.string.files, model.localFiles, model.globalFiles));
|
||||
size.setText(RestApi.readableFileSize(getContext(), model.localBytes) + " / " +
|
||||
RestApi.readableFileSize(getContext(), model.globalBytes));
|
||||
invalid.setText(model.invalid);
|
||||
invalid.setVisibility((model.invalid.equals("")) ? View.GONE : View.VISIBLE);
|
||||
if (repo.Invalid.equals("")) {
|
||||
invalid.setText(model.invalid);
|
||||
invalid.setVisibility((model.invalid.equals("")) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
invalid.setVisibility(View.GONE);
|
||||
invalid.setText(repo.Invalid);
|
||||
invalid.setVisibility((repo.Invalid.equals("")) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
return convertView;
|
||||
|
@ -91,4 +94,5 @@ public class ReposAdapter extends ArrayAdapter<RestApi.Repo>
|
|||
mModels.put(repoId, model);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/folder"
|
||||
android:id="@+id/directory"
|
||||
android:layout_below="@id/state"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -33,7 +33,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/items"
|
||||
android:layout_below="@id/folder"
|
||||
android:layout_below="@id/directory"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
|
Loading…
Reference in a new issue