From dff9156d779cb60997da5fc069144fc77855c9de Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Sun, 22 Jun 2014 22:36:35 +0200 Subject: [PATCH] Added proper restart handling (new config data is automatically loaded after restart). --- .../gui/LoadingListFragment.java | 8 +- .../gui/LocalNodeInfoFragment.java | 2 +- .../syncthingandroid/gui/MainActivity.java | 18 +- .../gui/NodeSettingsActivity.java | 5 +- .../syncthingandroid/gui/NodesFragment.java | 2 +- .../gui/RepoSettingsActivity.java | 5 +- .../syncthingandroid/gui/ReposFragment.java | 2 +- .../syncthingandroid/syncthing/RestApi.java | 69 +++---- .../syncthing/SyncthingService.java | 176 ++++++------------ .../syncthingandroid/util/ConfigXml.java | 134 +++++++++++++ 10 files changed, 233 insertions(+), 188 deletions(-) create mode 100644 src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/LoadingListFragment.java b/src/main/java/com/nutomic/syncthingandroid/gui/LoadingListFragment.java index bc74b7e9..dccbb0f5 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/LoadingListFragment.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/LoadingListFragment.java @@ -34,9 +34,8 @@ import com.nutomic.syncthingandroid.syncthing.SyncthingService; /** * {@link android.support.v4.app.ListFragment} that shows a configurable loading text. */ -public abstract class LoadingListFragment extends Fragment implements RestApi.OnApiAvailableListener, AdapterView.OnItemClickListener { - - private boolean mInitialized = false; +public abstract class LoadingListFragment extends Fragment implements + SyncthingService.OnApiAvailableListener, AdapterView.OnItemClickListener { private ListFragment mListFragment; @@ -109,11 +108,10 @@ public abstract class LoadingListFragment extends Fragment implements RestApi.On @Override public void onApiAvailable() { MainActivity activity = (MainActivity) getActivity(); - if (!mInitialized && getActivity() != null && + if (getActivity() != null && activity.getApi() != null && mListFragment != null) { onInitAdapter(activity); getListView().setOnItemClickListener(this); - mInitialized = true; } } diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/LocalNodeInfoFragment.java b/src/main/java/com/nutomic/syncthingandroid/gui/LocalNodeInfoFragment.java index 17a366b7..0914febc 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/LocalNodeInfoFragment.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/LocalNodeInfoFragment.java @@ -26,7 +26,7 @@ import java.util.TimerTask; */ public class LocalNodeInfoFragment extends Fragment implements RestApi.OnReceiveSystemInfoListener, RestApi.OnReceiveConnectionsListener, - RestApi.OnApiAvailableListener { + SyncthingService.OnApiAvailableListener { private TextView mNodeId; diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java index fd44b0ac..8dbd474a 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java @@ -54,9 +54,9 @@ public class MainActivity extends ActionBarActivity */ @Override public void onWebGuiAvailable() { - mSyncthingService.getApi().registerOnApiAvailableListener(mRepositoriesFragment); - mSyncthingService.getApi().registerOnApiAvailableListener(mNodesFragment); - mSyncthingService.getApi().registerOnApiAvailableListener(mLocalNodeInfoFragment); + mSyncthingService.registerOnApiAvailableListener(mRepositoriesFragment); + mSyncthingService.registerOnApiAvailableListener(mNodesFragment); + mSyncthingService.registerOnApiAvailableListener(mLocalNodeInfoFragment); mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); mDrawerLayout.setDrawerListener(mDrawerToggle); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -220,14 +220,14 @@ public class MainActivity extends ActionBarActivity } switch (item.getItemId()) { - case R.id.add_node: - Intent intent = new Intent(this, NodeSettingsActivity.class); - intent.setAction(NodeSettingsActivity.ACTION_CREATE); + case R.id.add_repository: + Intent intent = new Intent(this, RepoSettingsActivity.class); + intent.setAction(RepoSettingsActivity.ACTION_CREATE); startActivity(intent); return true; - case R.id.add_repository: - intent = new Intent(this, RepoSettingsActivity.class); - intent.setAction(RepoSettingsActivity.ACTION_CREATE); + case R.id.add_node: + intent = new Intent(this, NodeSettingsActivity.class); + intent.setAction(NodeSettingsActivity.ACTION_CREATE); startActivity(intent); return true; case R.id.web_gui: diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java index 56a55426..4ffcd1c9 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java @@ -28,7 +28,7 @@ import java.util.Map; */ public class NodeSettingsActivity extends PreferenceActivity implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener, - RestApi.OnReceiveConnectionsListener, RestApi.OnApiAvailableListener { + RestApi.OnReceiveConnectionsListener, SyncthingService.OnApiAvailableListener { public static final String ACTION_CREATE = "create"; @@ -43,8 +43,7 @@ public class NodeSettingsActivity extends PreferenceActivity implements public void onServiceConnected(ComponentName className, IBinder service) { SyncthingServiceBinder binder = (SyncthingServiceBinder) service; mSyncthingService = binder.getService(); - mSyncthingService.getApi() - .registerOnApiAvailableListener(NodeSettingsActivity.this); + mSyncthingService.registerOnApiAvailableListener(NodeSettingsActivity.this); } public void onServiceDisconnected(ComponentName className) { diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/NodesFragment.java b/src/main/java/com/nutomic/syncthingandroid/gui/NodesFragment.java index e6344284..be29edc4 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/NodesFragment.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/NodesFragment.java @@ -17,7 +17,7 @@ import java.util.TimerTask; * Displays a list of all existing nodes. */ public class NodesFragment extends LoadingListFragment implements - RestApi.OnApiAvailableListener, ListView.OnItemClickListener { + SyncthingService.OnApiAvailableListener, ListView.OnItemClickListener { private NodeAdapter mAdapter; diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java index acb1c148..ed86a237 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java @@ -31,7 +31,7 @@ import java.util.List; */ public class RepoSettingsActivity extends PreferenceActivity implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener, - RestApi.OnApiAvailableListener { + SyncthingService.OnApiAvailableListener { public static final String ACTION_CREATE = "create"; @@ -48,8 +48,7 @@ public class RepoSettingsActivity extends PreferenceActivity public void onServiceConnected(ComponentName className, IBinder service) { SyncthingServiceBinder binder = (SyncthingServiceBinder) service; mSyncthingService = binder.getService(); - mSyncthingService.getApi() - .registerOnApiAvailableListener(RepoSettingsActivity.this); + mSyncthingService.registerOnApiAvailableListener(RepoSettingsActivity.this); } public void onServiceDisconnected(ComponentName className) { diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/ReposFragment.java b/src/main/java/com/nutomic/syncthingandroid/gui/ReposFragment.java index 3759c203..655c99d4 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/ReposFragment.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/ReposFragment.java @@ -16,7 +16,7 @@ import java.util.TimerTask; * Displays a list of all existing repositories. */ public class ReposFragment extends LoadingListFragment implements - RestApi.OnApiAvailableListener, AdapterView.OnItemClickListener { + SyncthingService.OnApiAvailableListener, AdapterView.OnItemClickListener { private ReposAdapter mAdapter; diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java index e05d1bcf..d1e7c9c4 100644 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java +++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java @@ -14,6 +14,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.lang.ref.WeakReference; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashMap; @@ -115,16 +116,9 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { public String invalid; } - public interface OnApiAvailableListener { - public void onApiAvailable(); - } - - private final LinkedList mOnApiAvailableListeners = - new LinkedList(); - private static final int NOTIFICATION_RESTART = 2; - private final Context mContext; + private final SyncthingService mSyncthingService; private String mVersion; @@ -149,12 +143,12 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { */ private long mPreviousConnectionTime = 0; - public RestApi(Context context, String url, String apiKey) { - mContext = context; + public RestApi(SyncthingService syncthingService, String url, String apiKey) { + mSyncthingService = syncthingService; mUrl = url; mApiKey = apiKey; mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); + mSyncthingService.getSystemService(Context.NOTIFICATION_SERVICE); } /** @@ -164,6 +158,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { return mUrl; } + /** + * Returns the API key needed to access the Rest API. + */ + public String getApiKey() { + return mApiKey; + } + /** * Number of previous calls to {@link #tryIsAvailable()}. */ @@ -208,18 +209,14 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { }); } - /** - * Increments mAvailableCount by one, and, if it reached TOTAL_STARTUP_CALLS, notifies - * all registered {@link OnApiAvailableListener} listeners. + * Increments mAvailableCount by one, and, if it reached TOTAL_STARTUP_CALLS, + * calls {@link SyncthingService#onApiAvailable()}. */ private void tryIsAvailable() { int value = mAvailableCount.incrementAndGet(); if (value == TOTAL_STARTUP_CALLS) { - for (OnApiAvailableListener listener : mOnApiAvailableListeners) { - listener.onApiAvailable(); - } - mOnApiAvailableListeners.clear(); + mSyncthingService.onApiAvailable(); } } @@ -238,13 +235,6 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { new PostTask().execute(mUrl, PostTask.URI_SHUTDOWN, mApiKey); } - /** - * Restarts the syncthing binary. - */ - public void restart() { - new PostTask().execute(mUrl, PostTask.URI_RESTART, mApiKey); - } - /** * Gets a value from config, * @@ -308,13 +298,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { private void configUpdated() { new PostTask().execute(mUrl, PostTask.URI_CONFIG, mApiKey, mConfig.toString()); - Intent i = new Intent(mContext, SyncthingService.class) + Intent i = new Intent(mSyncthingService, SyncthingService.class) .setAction(SyncthingService.ACTION_RESTART); - PendingIntent pi = PendingIntent.getService(mContext, 0, i, 0); + PendingIntent pi = PendingIntent.getService(mSyncthingService, 0, i, 0); - Notification n = new NotificationCompat.Builder(mContext) - .setContentTitle(mContext.getString(R.string.restart_notif_title)) - .setContentText(mContext.getString(R.string.restart_notif_text)) + Notification n = new NotificationCompat.Builder(mSyncthingService) + .setContentTitle(mSyncthingService.getString(R.string.restart_notif_title)) + .setContentText(mSyncthingService.getString(R.string.restart_notif_text)) .setSmallIcon(R.drawable.ic_launcher) .setContentIntent(pi) .build(); @@ -433,21 +423,6 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { return ret; } - /** - * Register a listener for the web gui becoming available.. - * - * If the web gui is already available, listener will be called immediately. - * Listeners are unregistered automatically after being called. - */ - public void registerOnApiAvailableListener(OnApiAvailableListener listener) { - if (mConfig != null) { - listener.onApiAvailable(); - } - else { - mOnApiAvailableListeners.addLast(listener); - } - } - /** * Converts a number of bytes to a human readable file size (eg 3.5 GB). */ @@ -715,4 +690,8 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { return newArray; } + public boolean isApiAvailable() { + return mAvailableCount.get() == TOTAL_STARTUP_CALLS; + } + } diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java index 886b1649..1da5e9ac 100644 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java +++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java @@ -6,7 +6,6 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; -import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.support.v4.app.NotificationCompat; @@ -15,6 +14,7 @@ import android.util.Pair; import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.gui.MainActivity; +import com.nutomic.syncthingandroid.util.ConfigXml; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; @@ -22,10 +22,6 @@ 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 org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; import java.io.BufferedReader; import java.io.DataOutputStream; @@ -35,20 +31,10 @@ import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.lang.ref.WeakReference; import java.util.LinkedList; -import java.util.Random; import java.util.concurrent.locks.ReentrantLock; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; - /** * Holds the native syncthing instance and provides an API to access it. */ @@ -114,10 +100,27 @@ public class SyncthingService extends Service { private boolean mIsWebGuiAvailable = false; + public interface OnApiAvailableListener { + public void onApiAvailable(); + } + + private final LinkedList> mOnApiAvailableListeners = + new LinkedList>(); + @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && ACTION_RESTART.equals(intent.getAction())) { - mApi.restart(); + mIsWebGuiAvailable = false; + new PostTask() { + @Override + protected void onPostExecute(Void aVoid) { + ConfigXml config = new ConfigXml(getConfigFile()); + mApi = new RestApi(SyncthingService.this, + config.getWebGuiUrl(), config.getApiKey()); + registerOnWebGuiAvailableListener(mApi); + new PollWebGuiAvailableTask().execute(); + } + }.execute(mApi.getUrl(), PostTask.URI_RESTART, mApi.getApiKey()); } return START_STICKY; } @@ -325,30 +328,9 @@ public class SyncthingService extends Service { copyDefaultConfig(); } moveConfigFiles(); - updateConfig(); - - String syncthingUrl = null; - String apiKey = null; - try { - DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - Document d = db.parse(getConfigFile()); - Element gui = (Element) d.getDocumentElement() - .getElementsByTagName("gui").item(0); - syncthingUrl = gui.getElementsByTagName("address").item(0).getTextContent(); - apiKey = gui.getElementsByTagName("apikey").item(0).getTextContent(); - } - catch (SAXException e) { - throw new RuntimeException("Failed to read gui url, aborting", e); - } - catch (ParserConfigurationException e) { - throw new RuntimeException("Failed to read gui url, aborting", e); - } - catch (IOException e) { - throw new RuntimeException("Failed to read gui url, aborting", e); - } - finally { - return new Pair("http://" + syncthingUrl, apiKey); - } + ConfigXml config = new ConfigXml(getConfigFile()); + config.update(); + return new Pair(config.getWebGuiUrl(), config.getApiKey()); } @Override @@ -403,85 +385,6 @@ public class SyncthingService extends Service { return new File(getFilesDir(), CONFIG_FILE); } - /** - * Updates the config file. - * - * Coming from 0.2.0 and earlier, globalAnnounceServer value "announce.syncthing.net:22025" is - * replaced with "194.126.249.5:22025" (as domain resolve is broken). - * - * Coming from 0.3.0 and earlier, the ignorePerms flag is set to true on every repository. - */ - private void updateConfig() { - try { - Log.i(TAG, "Checking for needed config updates"); - boolean changed = false; - DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - Document doc = db.parse(getConfigFile()); - Element options = (Element) doc.getDocumentElement() - .getElementsByTagName("options").item(0); - Element gui = (Element) doc.getDocumentElement() - .getElementsByTagName("gui").item(0); - - // Create an API key if it does not exist. - if (gui.getElementsByTagName("apikey").getLength() == 0) { - Log.i(TAG, "Initializing API key with random string"); - char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray(); - StringBuilder sb = new StringBuilder(); - Random random = new Random(); - for (int i = 0; i < 20; i++) { - sb.append(chars[random.nextInt(chars.length)]); - } - Element apiKey = doc.createElement("apikey"); - apiKey.setTextContent(sb.toString()); - gui.appendChild(apiKey); - changed = true; - } - - // Hardcode default globalAnnounceServer ip. - Element globalAnnounceServer = (Element) - options.getElementsByTagName("globalAnnounceServer").item(0); - if (globalAnnounceServer.getTextContent().equals("announce.syncthing.net:22025")) { - Log.i(TAG, "Replacing globalAnnounceServer host with ip"); - globalAnnounceServer.setTextContent("194.126.249.5:22025"); - changed = true; - } - - // Set ignorePerms attribute. - NodeList repos = doc.getDocumentElement().getElementsByTagName("repository"); - for (int i = 0; i < repos.getLength(); i++) { - Element r = (Element) repos.item(i); - if (!r.hasAttribute("ignorePerms") || - !Boolean.parseBoolean(r.getAttribute("ignorePerms"))) { - Log.i(TAG, "Set 'ignorePerms' on repository " + r.getAttribute("id")); - r.setAttribute("ignorePerms", Boolean.toString(true)); - changed = true; - } - } - - // Write the changes back to file. - if (changed) { - Log.i(TAG, "Writing updated config back to file"); - TransformerFactory transformerFactory = TransformerFactory.newInstance(); - Transformer transformer = transformerFactory.newTransformer(); - DOMSource domSource = new DOMSource(doc); - StreamResult streamResult = new StreamResult(getConfigFile()); - transformer.transform(domSource, streamResult); - } - } - catch (ParserConfigurationException e) { - Log.w(TAG, "Failed to parse config", e); - } - catch (IOException e) { - Log.w(TAG, "Failed to parse config", e); - } - catch (SAXException e) { - Log.w(TAG, "Failed to parse config", e); - } - catch (TransformerException e) { - Log.w(TAG, "Failed to save updated config", e); - } - } - /** * Returns true if this service has not been started before (ie config.xml does not exist). * @@ -525,4 +428,37 @@ public class SyncthingService extends Service { return mApi; } + /** + * Register a listener for the syncthing API becoming available.. + * + * If the API is already available, listener will be called immediately. + * + * Listeners are kept around (as weak reference) and called again after any configuration + * changes to allow a data refresh. + */ + public void registerOnApiAvailableListener(OnApiAvailableListener listener) { + if (mApi.isApiAvailable()) { + listener.onApiAvailable(); + } + else { + mOnApiAvailableListeners.addLast(new WeakReference(listener)); + } + } + + /** + * Called by {@link RestApi} once it is fully initialized. + * + * Must not be called from anywhere else. + */ + public void onApiAvailable() { + for (WeakReference listener : mOnApiAvailableListeners) { + if (listener.get() != null) { + listener.get().onApiAvailable(); + } + else { + mOnApiAvailableListeners.remove(listener); + } + } + } + } diff --git a/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java b/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java new file mode 100644 index 00000000..10d8cead --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java @@ -0,0 +1,134 @@ +package com.nutomic.syncthingandroid.util; + +import android.util.Log; +import android.util.Pair; + +import com.nutomic.syncthingandroid.syncthing.SyncthingService; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.IOException; +import java.util.Random; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +/** + * Provides direct access to the config.xml file in the file system. + * + * This class should only be used if the syncthing API is not available (usually during startup). + */ +public class ConfigXml { + + private static final String TAG = "ConfigXml"; + + private File mConfigFile; + + private Document mConfig; + + public ConfigXml(File configFile) { + mConfigFile = configFile; + try { + DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + mConfig = db.parse(configFile); + } catch (SAXException e) { + throw new RuntimeException("Failed to parse config file", e); + } catch (ParserConfigurationException e) { + throw new RuntimeException("Failed to parse config file", e); + } catch (IOException e) { + throw new RuntimeException("Failed to open config file", e); + } + } + + public String getWebGuiUrl() { + return "http://" + getGuiElement().getElementsByTagName("address").item(0).getTextContent(); + } + + public String getApiKey() { + return getGuiElement().getElementsByTagName("apikey").item(0).getTextContent(); + } + + /** + * Updates the config file. + * + * Coming from 0.2.0 and earlier, globalAnnounceServer value "announce.syncthing.net:22025" is + * replaced with "194.126.249.5:22025" (as domain resolve is broken). + * + * Coming from 0.3.0 and earlier, the ignorePerms flag is set to true on every repository. + */ + public void update() { + try { + Log.i(TAG, "Checking for needed config updates"); + boolean changed = false; + Element options = (Element) mConfig.getDocumentElement() + .getElementsByTagName("options").item(0); + Element gui = (Element) mConfig.getDocumentElement() + .getElementsByTagName("gui").item(0); + + // Create an API key if it does not exist. + if (gui.getElementsByTagName("apikey").getLength() == 0) { + Log.i(TAG, "Initializing API key with random string"); + char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray(); + StringBuilder sb = new StringBuilder(); + Random random = new Random(); + for (int i = 0; i < 20; i++) { + sb.append(chars[random.nextInt(chars.length)]); + } + Element apiKey = mConfig.createElement("apikey"); + apiKey.setTextContent(sb.toString()); + gui.appendChild(apiKey); + changed = true; + } + + // Hardcode default globalAnnounceServer ip. + Element globalAnnounceServer = (Element) + options.getElementsByTagName("globalAnnounceServer").item(0); + if (globalAnnounceServer.getTextContent().equals("announce.syncthing.net:22025")) { + Log.i(TAG, "Replacing globalAnnounceServer host with ip"); + globalAnnounceServer.setTextContent("194.126.249.5:22025"); + changed = true; + } + + // Set ignorePerms attribute. + NodeList repos = mConfig.getDocumentElement().getElementsByTagName("repository"); + for (int i = 0; i < repos.getLength(); i++) { + Element r = (Element) repos.item(i); + if (!r.hasAttribute("ignorePerms") || + !Boolean.parseBoolean(r.getAttribute("ignorePerms"))) { + Log.i(TAG, "Set 'ignorePerms' on repository " + r.getAttribute("id")); + r.setAttribute("ignorePerms", Boolean.toString(true)); + changed = true; + } + } + + // Write the changes back to file. + if (changed) { + Log.i(TAG, "Writing updated config back to file"); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + DOMSource domSource = new DOMSource(mConfig); + StreamResult streamResult = new StreamResult(mConfigFile); + transformer.transform(domSource, streamResult); + } + } + catch (TransformerException e) { + Log.w(TAG, "Failed to save updated config", e); + } + } + + private Element getGuiElement() { + return (Element) mConfig.getDocumentElement() + .getElementsByTagName("gui").item(0); + } + +}