diff --git a/src/androidTest/java/com/nutomic/syncthingandroid/test/MockRestApi.java b/src/androidTest/java/com/nutomic/syncthingandroid/test/MockRestApi.java index 93d4a86f..f1677be4 100644 --- a/src/androidTest/java/com/nutomic/syncthingandroid/test/MockRestApi.java +++ b/src/androidTest/java/com/nutomic/syncthingandroid/test/MockRestApi.java @@ -1,10 +1,8 @@ package com.nutomic.syncthingandroid.test; -import android.app.Activity; import android.content.Context; -import android.support.annotation.NonNull; -import com.nutomic.syncthingandroid.model.Connection; +import com.nutomic.syncthingandroid.model.Connections; import com.nutomic.syncthingandroid.model.Device; import com.nutomic.syncthingandroid.model.Folder; import com.nutomic.syncthingandroid.model.Model; @@ -12,7 +10,6 @@ import com.nutomic.syncthingandroid.model.SystemInfo; import com.nutomic.syncthingandroid.service.RestApi; import java.net.URL; -import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -54,7 +51,7 @@ public class MockRestApi extends RestApi { } @Override - public void getConnections(OnResultListener1> listener) { + public void getConnections(OnResultListener1> listener) { throw new UnsupportedOperationException(); } diff --git a/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java b/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java index 6c2188ba..fadfdfba 100644 --- a/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java @@ -12,7 +12,9 @@ import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; -import android.view.*; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.TextView; @@ -22,7 +24,7 @@ import com.google.gson.Gson; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; import com.nutomic.syncthingandroid.R; -import com.nutomic.syncthingandroid.model.Connection; +import com.nutomic.syncthingandroid.model.Connections; import com.nutomic.syncthingandroid.model.Device; import com.nutomic.syncthingandroid.service.SyncthingService; import com.nutomic.syncthingandroid.util.Compression; @@ -32,7 +34,6 @@ import com.nutomic.syncthingandroid.util.Util; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Map; import static android.text.TextUtils.isEmpty; import static android.view.View.GONE; @@ -218,13 +219,13 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis * NOTE: This is only called once on startup, should be called more often to properly display * version/address changes. */ - public void onReceiveConnections(Map connections) { + public void onReceiveConnections(Connections connections) { boolean viewsExist = mSyncthingVersionView != null && mCurrentAddressView != null; - if (viewsExist && connections.containsKey(mDevice.deviceID)) { + if (viewsExist && connections.connections.containsKey(mDevice.deviceID)) { mCurrentAddressView.setVisibility(VISIBLE); mSyncthingVersionView.setVisibility(VISIBLE); - mCurrentAddressView.setText(connections.get(mDevice.deviceID).address); - mSyncthingVersionView.setText(connections.get(mDevice.deviceID).clientVersion); + mCurrentAddressView.setText(connections.connections.get(mDevice.deviceID).address); + mSyncthingVersionView.setText(connections.connections.get(mDevice.deviceID).clientVersion); } } diff --git a/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java b/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java index d12ea548..9c3f5dcf 100644 --- a/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java +++ b/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java @@ -15,7 +15,7 @@ import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.activities.MainActivity; import com.nutomic.syncthingandroid.activities.SettingsActivity; import com.nutomic.syncthingandroid.activities.WebGuiActivity; -import com.nutomic.syncthingandroid.model.Connection; +import com.nutomic.syncthingandroid.model.Connections; import com.nutomic.syncthingandroid.model.SystemInfo; import com.nutomic.syncthingandroid.model.SystemVersion; import com.nutomic.syncthingandroid.service.RestApi; @@ -187,8 +187,8 @@ public class DrawerFragment extends Fragment implements View.OnClickListener { /** * Populates views with status received via {@link RestApi#getConnections}. */ - private void onReceiveConnections(Map connections) { - Connection c = connections.get(RestApi.TOTAL_STATS); + private void onReceiveConnections(Connections connections) { + Connections.Connection c = connections.total; mDownload.setText(Util.readableTransferRate(mActivity, c.inBits)); mUpload.setText(Util.readableTransferRate(mActivity, c.outBits)); } diff --git a/src/main/java/com/nutomic/syncthingandroid/model/Connection.java b/src/main/java/com/nutomic/syncthingandroid/model/Connection.java deleted file mode 100644 index 7af69c40..00000000 --- a/src/main/java/com/nutomic/syncthingandroid/model/Connection.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.nutomic.syncthingandroid.model; - -public class Connection { - public String at; - public long inBytesTotal; - public long outBytesTotal; - public long inBits; - public long outBits; - public String address; - public String clientVersion; - public int completion; - public boolean connected; -} diff --git a/src/main/java/com/nutomic/syncthingandroid/model/Connections.java b/src/main/java/com/nutomic/syncthingandroid/model/Connections.java new file mode 100644 index 00000000..f88c31e7 --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/model/Connections.java @@ -0,0 +1,34 @@ +package com.nutomic.syncthingandroid.model; + +import java.util.Map; + +public class Connections { + + public Connection total; + public Map connections; + + public static class Connection { + public boolean paused; + public String clientVersion; + public String at; + public boolean connected; + public long inBytesTotal; + public long outBytesTotal; + public String type; + public String address; + + // These fields are not sent from Syncthing, but are populated on the client side. + public int completion; + public long inBits; + public long outBits; + + public void setTransferRate(Connection previous, long msElapsed) { + long secondsElapsed = msElapsed / 1000; + long inBytes = 8 * (inBytesTotal - previous.inBytesTotal) / secondsElapsed; + long outBytes = 8 * (outBytesTotal - previous.outBytesTotal) / secondsElapsed; + inBits = Math.max(0, inBytes); + outBits = Math.max(0, outBytes); + + } + } +} diff --git a/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java b/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java index b506fa43..498c1b9d 100644 --- a/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java +++ b/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.util.Log; import com.google.common.base.Objects; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; import com.google.gson.Gson; @@ -21,7 +22,7 @@ import com.nutomic.syncthingandroid.http.GetTask; import com.nutomic.syncthingandroid.http.PostConfigTask; import com.nutomic.syncthingandroid.http.PostScanTask; import com.nutomic.syncthingandroid.model.Config; -import com.nutomic.syncthingandroid.model.Connection; +import com.nutomic.syncthingandroid.model.Connections; import com.nutomic.syncthingandroid.model.Device; import com.nutomic.syncthingandroid.model.Event; import com.nutomic.syncthingandroid.model.Folder; @@ -31,10 +32,6 @@ import com.nutomic.syncthingandroid.model.SystemInfo; import com.nutomic.syncthingandroid.model.SystemVersion; import com.nutomic.syncthingandroid.util.FolderObserver; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import java.lang.reflect.Type; import java.net.URL; import java.util.HashMap; @@ -51,12 +48,6 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, private static final String TAG = "RestApi"; - /** - * Key of the map element containing connection info for the local device, in the return - * value of {@link #getConnections} - */ - public static final String TOTAL_STATS = "total"; - public interface OnConfigChangedListener { void onConfigChanged(); } @@ -83,7 +74,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, * Stores the result of the last successful request to {@link GetTask#URI_CONNECTIONS}, * or an empty Map. */ - private Map mPreviousConnections = new HashMap<>(); + private Optional mPreviousConnections = Optional.absent(); /** * Stores the timestamp of the last successful request to {@link GetTask#URI_CONNECTIONS}. @@ -342,62 +333,31 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, /** * Returns connection info for the local device and all connected devices. - *

- * Use the key {@link #TOTAL_STATS} to get connection info for the local device. - * - * The result is cached internally. Do not modify it or any of its contents. */ - public void getConnections(final OnResultListener1> listener) { + public void getConnections(final OnResultListener1 listener) { new GetTask(mUrl, GetTask.URI_CONNECTIONS, mHttpsCertPath, mApiKey, null, result -> { Long now = System.currentTimeMillis(); - Long timeElapsed = (now - mPreviousConnectionTime) / 1000; - if (timeElapsed < 1) { - listener.onResult(mPreviousConnections); + Long msElapsed = now - mPreviousConnectionTime; + if (msElapsed < SyncthingService.GUI_UPDATE_INTERVAL) { + listener.onResult(deepCopy(mPreviousConnections.get(), Connections.class)); return; } - try { - JSONObject json = new JSONObject(result); - Map jsonConnections = new HashMap<>(); - jsonConnections.put(TOTAL_STATS, json.getJSONObject(TOTAL_STATS)); - JSONArray extConnections = json.getJSONObject("connections").names(); - if (extConnections != null) { - for (int i = 0; i < extConnections.length(); i++) { - String deviceId = extConnections.get(i).toString(); - jsonConnections.put(deviceId, json.getJSONObject("connections").getJSONObject(deviceId)); - } - } - Map connections = new HashMap<>(); - for (Map.Entry jsonConnection : jsonConnections.entrySet()) { - String deviceId = jsonConnection.getKey(); - Connection c = new Connection(); - JSONObject conn = jsonConnection.getValue(); - c.address = deviceId; - c.at = conn.getString("at"); - c.inBytesTotal = conn.getLong("inBytesTotal"); - c.outBytesTotal = conn.getLong("outBytesTotal"); - c.address = conn.getString("address"); - c.clientVersion = conn.getString("clientVersion"); - c.completion = getDeviceCompletion(deviceId); - c.connected = conn.getBoolean("connected"); + mPreviousConnectionTime = now; + Connections connections = new Gson().fromJson(result, Connections.class); + for (Map.Entry e : connections.connections.entrySet()) { + e.getValue().completion = getDeviceCompletion(e.getKey()); - Connection prev = (mPreviousConnections.containsKey(deviceId)) - ? mPreviousConnections.get(deviceId) - : new Connection(); - mPreviousConnectionTime = now; - c.inBits = Math.max(0, 8 * - (conn.getLong("inBytesTotal") - prev.inBytesTotal) / timeElapsed); - c.outBits = Math.max(0, 8 * - (conn.getLong("outBytesTotal") - prev.outBytesTotal) / timeElapsed); - - connections.put(deviceId, c); - - } - mPreviousConnections = connections; - listener.onResult(mPreviousConnections); - } catch (JSONException e) { - Log.w(TAG, "Failed to parse connections", e); + Connections.Connection prev = mPreviousConnections + .transform(c -> c.connections.get(e.getKey())) + .or(new Connections.Connection()); + e.getValue().setTransferRate(prev, msElapsed); } + Connections.Connection prev = + mPreviousConnections.transform(c -> c.total).or(new Connections.Connection()); + connections.total.setTransferRate(prev, msElapsed); + mPreviousConnections = Optional.of(connections); + listener.onResult(deepCopy(connections, Connections.class)); }).execute(); } diff --git a/src/main/java/com/nutomic/syncthingandroid/views/DevicesAdapter.java b/src/main/java/com/nutomic/syncthingandroid/views/DevicesAdapter.java index 52d6fcf3..d47a5132 100644 --- a/src/main/java/com/nutomic/syncthingandroid/views/DevicesAdapter.java +++ b/src/main/java/com/nutomic/syncthingandroid/views/DevicesAdapter.java @@ -10,22 +10,19 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; +import com.google.common.base.Optional; import com.nutomic.syncthingandroid.R; -import com.nutomic.syncthingandroid.model.Connection; +import com.nutomic.syncthingandroid.model.Connections; import com.nutomic.syncthingandroid.model.Device; import com.nutomic.syncthingandroid.service.RestApi; import com.nutomic.syncthingandroid.util.Util; -import java.util.HashMap; -import java.util.Map; - /** * Generates item views for device items. */ public class DevicesAdapter extends ArrayAdapter { - private Map mConnections = - new HashMap<>(); + private Optional mConnections = Optional.absent(); public DevicesAdapter(Context context) { super(context, R.layout.item_device_list); @@ -46,21 +43,21 @@ public class DevicesAdapter extends ArrayAdapter { TextView upload = (TextView) convertView.findViewById(R.id.upload); String deviceId = getItem(position).deviceID; - Connection conn = mConnections.get(deviceId); + Optional conn = mConnections.transform(a -> a.connections.get(deviceId)); name.setText(getItem(position).getDisplayName()); Resources r = getContext().getResources(); - if (conn != null && conn.connected) { - if (conn.completion == 100) { + if (conn.isPresent() && conn.get().connected) { + if (conn.get().completion == 100) { status.setText(r.getString(R.string.device_up_to_date)); status.setTextColor(ContextCompat.getColor(getContext(), R.color.text_green)); } else { - status.setText(r.getString(R.string.device_syncing, conn.completion)); + status.setText(r.getString(R.string.device_syncing, conn.get().completion)); status.setTextColor(ContextCompat.getColor(getContext(), R.color.text_blue)); } - download.setText(Util.readableTransferRate(getContext(), conn.inBits)); - upload.setText(Util.readableTransferRate(getContext(), conn.outBits)); + download.setText(Util.readableTransferRate(getContext(), conn.get().inBits)); + upload.setText(Util.readableTransferRate(getContext(), conn.get().outBits)); } else { download.setText(Util.readableTransferRate(getContext(), 0)); @@ -81,8 +78,8 @@ public class DevicesAdapter extends ArrayAdapter { } } - public void onReceiveConnections(Map connections) { - mConnections = connections; + public void onReceiveConnections(Connections connections) { + mConnections = Optional.of(connections); notifyDataSetChanged(); } }