Add syncthing preferences to app settings.

This commit is contained in:
Felix Ableitner 2014-05-30 15:16:38 +02:00
parent 870c451477
commit edea290ea6
7 changed files with 260 additions and 8 deletions

View File

@ -9,34 +9,64 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.support.v4.app.NavUtils;
import android.text.InputType;
import android.util.Log;
import android.view.MenuItem;
import com.nutomic.syncthingandroid.syncthing.RestApi;
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder;
public class SettingsActivity extends PreferenceActivity {
public class SettingsActivity extends PreferenceActivity
implements Preference.OnPreferenceChangeListener {
private static final String REPORT_ISSUE_KEY = "report_issue";
private static final String SYNCTHING_OPTIONS_KEY = "syncthing_options";
private static final String SYNCTHING_GUI_KEY = "syncthing_gui";
private static final String SYNCTHING_VERSION_KEY = "syncthing_version";
private Preference mVersion;
private PreferenceScreen mOptionsScreen;
private PreferenceScreen mGuiScreen;
private SyncthingService mSyncthingService;
/**
* Binds to service and sets version name. The version name can not be retrieved if the service
* is just started (as we don't wait until the api is up).
* Binds to service and sets syncthing preferences from Rest API.
*/
private ServiceConnection mSyncthingServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
SyncthingServiceBinder binder = (SyncthingServiceBinder) service;
mSyncthingService = binder.getService();
Preference version = getPreferenceScreen().findPreference(SYNCTHING_VERSION_KEY);
version.setSummary(mSyncthingService.getApi().getVersion());
mVersion.setSummary(mSyncthingService.getApi().getVersion());
for (int i = 0; i < mOptionsScreen.getPreferenceCount(); i++) {
Preference pref = mOptionsScreen.getPreference(i);
pref.setOnPreferenceChangeListener(SettingsActivity.this);
String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_OPTIONS, pref.getKey());
applyPreference(pref, value);
}
for (int i = 0; i < mGuiScreen.getPreferenceCount(); i++) {
Preference pref = mGuiScreen.getPreference(i);
pref.setOnPreferenceChangeListener(SettingsActivity.this);
String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_GUI, pref.getKey());
applyPreference(pref, value);
}
}
public void onServiceDisconnected(ComponentName className) {
@ -44,6 +74,22 @@ public class SettingsActivity extends PreferenceActivity {
}
};
/**
* Applies the given value to the preference.
*
* If pref is an EditTextPreference, setText is used and the value shown as summary. If pref is
* a CheckBoxPreference, setChecked is used (by parsing value as Boolean).
*/
private void applyPreference(Preference pref, String value) {
if (pref instanceof EditTextPreference) {
((EditTextPreference) pref).setText(value);
pref.setSummary(value);
}
else if (pref instanceof CheckBoxPreference) {
((CheckBoxPreference) pref).setChecked(Boolean.parseBoolean(value));
}
}
/**
* Loads layout, sets version from Rest API.
*
@ -64,6 +110,10 @@ public class SettingsActivity extends PreferenceActivity {
mSyncthingServiceConnection, Context.BIND_AUTO_CREATE);
addPreferencesFromResource(R.xml.settings);
PreferenceScreen screen = getPreferenceScreen();
mVersion = screen.findPreference(SYNCTHING_VERSION_KEY);
mOptionsScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_OPTIONS_KEY);
mGuiScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_GUI_KEY);
}
@Override
@ -99,4 +149,30 @@ public class SettingsActivity extends PreferenceActivity {
}
}
/**
* Sends the updated value to {@link }RestApi}, and sets it as the summary
* for EditTextPreference.
*/
@Override
public boolean onPreferenceChange(Preference preference, Object o) {
if (preference instanceof EditTextPreference) {
String value = (String) o;
preference.setSummary(value);
EditTextPreference etp = (EditTextPreference) preference;
if (etp.getEditText().getInputType() == InputType.TYPE_CLASS_NUMBER) {
o = Integer.parseInt((String) o);
}
}
if (mOptionsScreen.findPreference(preference.getKey()) != null) {
mSyncthingService.getApi().setValue(RestApi.TYPE_OPTIONS, preference.getKey(), o,
preference.getKey().equals("ListenAddress"));
return true;
}
else if (mGuiScreen.findPreference(preference.getKey()) != null) {
mSyncthingService.getApi().setValue(RestApi.TYPE_GUI, preference.getKey(), o, false);
return true;
}
return false;
}
}

View File

@ -23,6 +23,8 @@ public class GetTask extends AsyncTask<String, Void, String> {
private static final String TAG = "GetTask";
public static final String URI_CONFIG = "/rest/config";
public static final String URI_VERSION = "/rest/version";
/**

View File

@ -6,6 +6,7 @@ import android.util.Log;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
@ -20,10 +21,13 @@ public class PostTask extends AsyncTask<String, Void, Void> {
public static final String URI_SHUTDOWN = "/rest/shutdown";
public static final String URI_CONFIG = "/rest/config";
/**
* params[0] Syncthing hostname
* params[1] URI to call
* params[2] Syncthing API key
* params[3] The request content
*/
@Override
protected Void doInBackground(String... params) {
@ -33,6 +37,7 @@ public class PostTask extends AsyncTask<String, Void, Void> {
HttpPost post = new HttpPost(fullUri);
post.addHeader(new BasicHeader("X-API-Key", params[2]));
try {
post.setEntity(new StringEntity(params[3]));
httpclient.execute(post);
}
catch (IOException e) {

View File

@ -5,6 +5,13 @@ import android.util.Log;
import com.nutomic.syncthingandroid.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* Provides functions to interact with the syncthing REST API.
*/
@ -12,12 +19,24 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
private static final String TAG = "RestApi";
/**
* Parameter for {@link #getValue} or {@link #setValue} referring to "options" config item.
*/
public static final String TYPE_OPTIONS = "Options";
/**
* Parameter for {@link #getValue} or {@link #setValue} referring to "gui" config item.
*/
public static final String TYPE_GUI = "GUI";
private String mVersion;
private String mUrl;
private String mApiKey;
private JSONObject mConfig;
public RestApi(String url, String apiKey) {
mUrl = url;
mApiKey = apiKey;
@ -39,6 +58,17 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
Log.i(TAG, "Syncthing version is " + mVersion);
}
}.execute(mUrl, GetTask.URI_VERSION, mApiKey);
new GetTask() {
@Override
protected void onPostExecute(String config) {
try {
mConfig = new JSONObject(config);
}
catch (JSONException e) {
Log.w(TAG, "Failed to parse config", e);
}
}
}.execute(mUrl, GetTask.URI_CONFIG, mApiKey);
}
/**
@ -52,7 +82,69 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
* Stops syncthing. You should probably use SyncthingService.stopService() instead.
*/
public void shutdown() {
new PostTask().execute(mUrl, PostTask.URI_SHUTDOWN, mApiKey);
new PostTask().execute(mUrl, PostTask.URI_SHUTDOWN, mApiKey, "");
}
/**
* Gets a value from config,
*
* Booleans are returned as {@link }Boolean#toString}, arrays as space seperated string.
*
* @param name {@link #TYPE_OPTIONS} or {@link #TYPE_GUI}
* @param key The key to read from.
* @return The value as a String, or null on failure.
*/
public String getValue(String name, String key) {
try {
Object value = mConfig.getJSONObject(name).get(key);
return (value instanceof JSONArray)
// TODO: also remove "
? ((JSONArray) value).join(" ").replace("\"", "")
: String.valueOf(value);
}
catch (JSONException e) {
Log.w(TAG, "Failed to get value for " + key, e);
return null;
}
}
/**
* Sets a value to config and sends it via Rest API.
*
* Booleans must be passed as {@link Boolean}, arrays as space seperated string
* with isArray true.
*
* @param name {@link #TYPE_OPTIONS} or {@link #TYPE_GUI}
* @param key The key to write to.
* @param value The new value to set, either String, Boolean or Integer.
* @param isArray True iff value is a space seperated String that should be converted to array.
*/
public <T> void setValue(String name, String key, T value, boolean isArray) {
try {
if (isArray) {
JSONArray json = new JSONArray();
for (String s : ((String) value).split(" ")) {
json.put(s);
}
mConfig.getJSONObject(name).put(key, json);
}
else {
mConfig.getJSONObject(name).put(key, value);
}
configUpdated();
}
catch (JSONException e) {
Log.w(TAG, "Failed to set value for " + key, e);
}
}
/**
* Sends the updated mConfig via Rest API to syncthing.
*
* TODO: Show a restart notification
*/
private void configUpdated() {
new PostTask().execute(mUrl, PostTask.URI_CONFIG, mConfig.toString());
}
}

View File

@ -347,7 +347,7 @@ public class SyncthingService extends Service {
// HACK: Make sure there is no syncthing binary left running from an improper
// shutdown (eg Play Store update).
// NOTE: This will log an exception if syncthing is not actually running.
new PostTask().execute(mApi.getUrl(), PostTask.URI_SHUTDOWN, urlAndKey.second);
new PostTask().execute(mApi.getUrl(), PostTask.URI_SHUTDOWN, urlAndKey.second, "");
registerOnWebGuiAvailableListener(mApi);
new PollWebGuiAvailableTask().execute();
new Thread(new SyncthingRunnable()).start();

View File

@ -3,7 +3,7 @@
<address>127.0.0.1:8080</address>
</gui>
<options>
<listenAddress>:22000</listenAddress>
<listenAddress>0.0.0.0:22000</listenAddress>
<globalAnnounceServer>194.126.249.5:22025</globalAnnounceServer>
<globalAnnounceEnabled>true</globalAnnounceEnabled>
<localAnnounceEnabled>true</localAnnounceEnabled>

View File

@ -1,6 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
android:key="syncthing_options"
android:title="Syncthing Options"
android:persistent="false" >
<EditTextPreference
android:key="ListenAddress"
android:title="Sync Protocol Listen Addresses"
android:defaultValue="0.0.0.0:22000" />
<EditTextPreference
android:key="MaxSendKbps"
android:title="Outgoing Rate Limit (KiB/s)"
android:numeric="integer" />
<EditTextPreference
android:key="RescanIntervalS"
android:title="Rescan Interval (s)"
android:numeric="integer" />
<EditTextPreference
android:key="ReconnectIntervalS"
android:title="Reconnect Interval (s)"
android:numeric="integer" />
<EditTextPreference
android:key="ParallelRequests"
android:title="Max Outstanding Requests"
android:numeric="integer" />
<EditTextPreference
android:key="MaxChangeKbps"
android:title="Max File Change Rate (KiB/s)"
android:numeric="integer" />
<CheckBoxPreference
android:key="GlobalAnnEnabled"
android:title="Global Discovery" />
<CheckBoxPreference
android:key="LocalAnnEnabled"
android:title="Local Discovery" />
<EditTextPreference
android:key="LocalAnnPort"
android:title="Local Discovery Port"
android:numeric="integer" />
<CheckBoxPreference
android:key="UPnPEnabled"
android:title="Enable UPnP" />
</PreferenceScreen>
<PreferenceScreen
android:key="syncthing_gui"
android:title="Syncthing GUI"
android:persistent="false" >
<EditTextPreference
android:key="Address"
android:title="GUI Listen Addresses" />
<EditTextPreference
android:key="User"
android:title="GUI Authentication User" />
<EditTextPreference
android:key="Password"
android:title="GUI Authentication Password" />
<CheckBoxPreference
android:key="UseTLS"
android:title="Use HTTPS for GUI" />
</PreferenceScreen>
<Preference
android:key="report_issue"
android:title="@string/report_issue_title"