// Copyright (C) 2014 The Syncthing Authors. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package syncthing.android.service; import android.content.Context; import android.os.Build; import android.os.Environment; import android.support.annotation.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import java.io.File; import java.io.IOException; 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; import timber.log.Timber; /** * 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 { public static final String CONFIG_FILE = "config.xml"; public static final String LOCALHOST_IP = "127.0.0.1"; public static final String DEFAULT_PORT = "8385"; private File mConfigFile; private Document mConfig; private ConfigXml(File configFile) { mConfigFile = configFile; try { DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); mConfig = db.parse(mConfigFile); } catch (SAXException | ParserConfigurationException | IOException e) { throw new RuntimeException(e); } } public @Nullable static ConfigXml get(Context context) { File configfile = getConfigFile(context); if (!configfile.exists()) { return null; } else { return new ConfigXml(configfile); } } public static File getConfigFile(Context context) { return new File(SyncthingUtils.getConfigDirectory(context), CONFIG_FILE); } /** * Updates the config file. */ public void updateIfNeeded() { Timber.d("Checking for needed config updates"); boolean changed = false; Element options = (Element) mConfig.getDocumentElement() .getElementsByTagName("options").item(0); //disable start browser NodeList startBrowser = options.getElementsByTagName("startBrowser"); if (startBrowser != null && startBrowser.getLength() == 1) { if (Boolean.parseBoolean(startBrowser.item(0).getTextContent())) { Timber.d("Set 'startBrowser' to false"); startBrowser.item(0).setTextContent(Boolean.toString(false)); changed = true; } } NodeList folders = mConfig.getDocumentElement().getElementsByTagName("folder"); for (int i = 0; i < folders.getLength(); i++) { Element r = (Element) folders.item(i); // Set ignorePerms attribute. if (!r.hasAttribute("ignorePerms") || !Boolean.parseBoolean(r.getAttribute("ignorePerms"))) { Timber.d("Set 'ignorePerms' on folder " + r.getAttribute("id")); r.setAttribute("ignorePerms", Boolean.toString(true)); changed = true; } // Set rescanIntervalS attribute. if (Integer.parseInt(r.getAttribute("rescanIntervalS")) == 60) { Timber.d("Set 'rescanIntervalS' on folder " + r.getAttribute("id")); r.setAttribute("rescanIntervalS", "86400"); changed = true; } } if (changed) { saveChanges(); } } /** * Change default GUI address to 127.0.0.1:8385 */ public void changeDefaultGUIAddress() { Element options = (Element) mConfig.getDocumentElement() .getElementsByTagName("gui").item(0); // Enable TLS options.setAttribute("tls", Boolean.toString(true)); Element address = (Element) options.getElementsByTagName("address").item(0); address.setTextContent(LOCALHOST_IP + ":" + DEFAULT_PORT); saveChanges(); } /** * Change default folder id to camera and path to camera folder path. */ public void changeDefaultFolder() { Element folder = (Element) mConfig.getDocumentElement() .getElementsByTagName("folder").item(0); folder.setAttribute("id", SyncthingUtils.generateDeviceName(true) + "-Camera"); folder.setAttribute("path", Environment .getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()); folder.setAttribute("ro", "true"); saveChanges(); } /** * Change default device name from localhost to the actual device name */ public void changeDefaultDeviceName() { NodeList devices = mConfig.getDocumentElement() .getElementsByTagName("device"); for (int i = 0; i < devices.getLength(); i++) { Element d = (Element) devices.item(i); if (!d.hasAttribute("name") || d.getAttribute("name").equals("localhost")) { d.setAttribute("name", SyncthingUtils.generateDeviceName(false)); } } saveChanges(); } /** * Retrieve API key */ public String getApiKey() { Element options = (Element) mConfig.getDocumentElement() .getElementsByTagName("gui").item(0); Element apiKey = (Element) options.getElementsByTagName("apikey").item(0); return apiKey.getTextContent(); } /** * Retrieve instance url */ public String getUrl() { Element options = (Element) mConfig.getDocumentElement() .getElementsByTagName("gui").item(0); boolean tls = false; String tlsattr = options.getAttribute("tls"); if (tlsattr != null) { tls = Boolean.parseBoolean(tlsattr); } Element address = (Element) options.getElementsByTagName("address").item(0); String addr = address.getTextContent(); return tls ? "https://" + addr : "http://" + addr; } /** * Writes updated mConfig back to file. */ private void saveChanges() { try { Timber.d("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) { throw new RuntimeException(e); } } }