mirror of
https://github.com/syncthing/syncthing-android.git
synced 2024-11-26 14:21:16 +00:00
Merge pull request #394 from syncthing/sync-root
Added option to run syncthing as root (fixes #48).
This commit is contained in:
commit
1e2c4196a8
6 changed files with 76 additions and 31 deletions
|
@ -19,13 +19,15 @@ apply plugin: 'com.github.ben-manes.versions'
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {
|
maven {
|
||||||
url {
|
url 'https://raw.github.com/kolavar/android-support-v4-preferencefragment/master/maven-repository/'
|
||||||
'https://raw.github.com/kolavar/android-support-v4-preferencefragment/master/maven-repository/'
|
}
|
||||||
}
|
maven {
|
||||||
|
url 'http://jcenter.bintray.com'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compile 'eu.chainfire:libsuperuser:1.0.0.201504231659'
|
||||||
compile 'com.android.support:appcompat-v7:22.0.0'
|
compile 'com.android.support:appcompat-v7:22.0.0'
|
||||||
compile 'com.android.support:support-v4-preferencefragment:1.0.0@aar'
|
compile 'com.android.support:support-v4-preferencefragment:1.0.0@aar'
|
||||||
androidTestCompile 'com.squareup.okhttp:mockwebserver:2.3.0'
|
androidTestCompile 'com.squareup.okhttp:mockwebserver:2.3.0'
|
||||||
|
|
|
@ -25,6 +25,8 @@ import com.nutomic.syncthingandroid.activities.SyncthingActivity;
|
||||||
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||||
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
||||||
|
|
||||||
|
import eu.chainfire.libsuperuser.Shell;
|
||||||
|
|
||||||
public class SettingsFragment extends PreferenceFragment
|
public class SettingsFragment extends PreferenceFragment
|
||||||
implements SyncthingActivity.OnServiceConnectedListener,
|
implements SyncthingActivity.OnServiceConnectedListener,
|
||||||
SyncthingService.OnApiChangeListener, Preference.OnPreferenceChangeListener,
|
SyncthingService.OnApiChangeListener, Preference.OnPreferenceChangeListener,
|
||||||
|
@ -126,6 +128,7 @@ public class SettingsFragment extends PreferenceFragment
|
||||||
mSyncOnlyCharging = (CheckBoxPreference)
|
mSyncOnlyCharging = (CheckBoxPreference)
|
||||||
findPreference(SyncthingService.PREF_SYNC_ONLY_CHARGING);
|
findPreference(SyncthingService.PREF_SYNC_ONLY_CHARGING);
|
||||||
mSyncOnlyWifi = (CheckBoxPreference) findPreference(SyncthingService.PREF_SYNC_ONLY_WIFI);
|
mSyncOnlyWifi = (CheckBoxPreference) findPreference(SyncthingService.PREF_SYNC_ONLY_WIFI);
|
||||||
|
Preference useRoot = findPreference(SyncthingService.PREF_USE_ROOT);
|
||||||
Preference appVersion = screen.findPreference(APP_VERSION_KEY);
|
Preference appVersion = screen.findPreference(APP_VERSION_KEY);
|
||||||
mOptionsScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_OPTIONS_KEY);
|
mOptionsScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_OPTIONS_KEY);
|
||||||
mGuiScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_GUI_KEY);
|
mGuiScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_GUI_KEY);
|
||||||
|
@ -143,6 +146,10 @@ public class SettingsFragment extends PreferenceFragment
|
||||||
mAlwaysRunInBackground.setOnPreferenceChangeListener(this);
|
mAlwaysRunInBackground.setOnPreferenceChangeListener(this);
|
||||||
mSyncOnlyCharging.setOnPreferenceChangeListener(this);
|
mSyncOnlyCharging.setOnPreferenceChangeListener(this);
|
||||||
mSyncOnlyWifi.setOnPreferenceChangeListener(this);
|
mSyncOnlyWifi.setOnPreferenceChangeListener(this);
|
||||||
|
if (!Shell.SU.available()) {
|
||||||
|
screen.removePreference(useRoot);
|
||||||
|
}
|
||||||
|
useRoot.setOnPreferenceChangeListener(this);
|
||||||
screen.findPreference(EXPORT_CONFIG).setOnPreferenceClickListener(this);
|
screen.findPreference(EXPORT_CONFIG).setOnPreferenceClickListener(this);
|
||||||
screen.findPreference(IMPORT_CONFIG).setOnPreferenceClickListener(this);
|
screen.findPreference(IMPORT_CONFIG).setOnPreferenceClickListener(this);
|
||||||
screen.findPreference(SYNCTHING_RESET).setOnPreferenceClickListener(this);
|
screen.findPreference(SYNCTHING_RESET).setOnPreferenceClickListener(this);
|
||||||
|
@ -234,23 +241,25 @@ public class SettingsFragment extends PreferenceFragment
|
||||||
RestApi.TYPE_GUI, preference.getKey(), o, false, getActivity());
|
RestApi.TYPE_GUI, preference.getKey(), o, false, getActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean requireRestart = false;
|
||||||
|
|
||||||
// Avoid any code injection.
|
// Avoid any code injection.
|
||||||
int error = 0;
|
int error = 0;
|
||||||
if (preference.getKey().equals(STTRACE)) {
|
if (preference.getKey().equals(STTRACE)) {
|
||||||
if (((String) o).matches("[a-z, ]*"))
|
if (((String) o).matches("[a-z, ]*"))
|
||||||
mSyncthingService.getApi().requireRestart(getActivity());
|
requireRestart = true;
|
||||||
else
|
else
|
||||||
error = R.string.toast_invalid_sttrace;
|
error = R.string.toast_invalid_sttrace;
|
||||||
} else if (preference.getKey().equals(GUI_USER)) {
|
} else if (preference.getKey().equals(GUI_USER)) {
|
||||||
String s = (String) o;
|
String s = (String) o;
|
||||||
if (!s.contains(":") && !s.contains("'"))
|
if (!s.contains(":") && !s.contains("'"))
|
||||||
mSyncthingService.getApi().requireRestart(getActivity());
|
requireRestart = true;
|
||||||
else
|
else
|
||||||
error = R.string.toast_invalid_username;
|
error = R.string.toast_invalid_username;
|
||||||
} else if (preference.getKey().equals(GUI_PASSWORD)) {
|
} else if (preference.getKey().equals(GUI_PASSWORD)) {
|
||||||
String s = (String) o;
|
String s = (String) o;
|
||||||
if (!s.contains(":") && !s.contains("'"))
|
if (!s.contains(":") && !s.contains("'"))
|
||||||
mSyncthingService.getApi().requireRestart(getActivity());
|
requireRestart = true;
|
||||||
else
|
else
|
||||||
error = R.string.toast_invalid_password;
|
error = R.string.toast_invalid_password;
|
||||||
}
|
}
|
||||||
|
@ -259,6 +268,13 @@ public class SettingsFragment extends PreferenceFragment
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preference.getKey().equals(SyncthingService.PREF_USE_ROOT))
|
||||||
|
requireRestart = true;
|
||||||
|
|
||||||
|
if (requireRestart)
|
||||||
|
mSyncthingService.getApi().requireRestart(getActivity());
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -267,7 +267,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops syncthing. You should probably use SyncthingService.stopService() instead.
|
* Stops syncthing and cancels notification. For use by {@link SyncthingService}.
|
||||||
*/
|
*/
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
// Happens in unit tests.
|
// Happens in unit tests.
|
||||||
|
@ -277,7 +277,6 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
NotificationManager nm = (NotificationManager)
|
NotificationManager nm = (NotificationManager)
|
||||||
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
nm.cancel(NOTIFICATION_RESTART);
|
nm.cancel(NOTIFICATION_RESTART);
|
||||||
SyncthingRunnable.killSyncthing();
|
|
||||||
mRestartPostponed = false;
|
mRestartPostponed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -740,7 +739,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
case "": return c.getString(R.string.state_unknown);
|
case "": return c.getString(R.string.state_unknown);
|
||||||
}
|
}
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
throw new AssertionError("Unexpected folder state");
|
throw new AssertionError("Unexpected folder state " + state);
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
@ -15,6 +16,8 @@ import java.io.InputStreamReader;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import eu.chainfire.libsuperuser.Shell;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the syncthing binary from command line, and prints its output to logcat.
|
* Runs the syncthing binary from command line, and prints its output to logcat.
|
||||||
*/
|
*/
|
||||||
|
@ -88,8 +91,7 @@ public class SyncthingRunnable implements Runnable {
|
||||||
try {
|
try {
|
||||||
ProcessBuilder pb = new ProcessBuilder("chmod", "+x", mSyncthingBinary);
|
ProcessBuilder pb = new ProcessBuilder("chmod", "+x", mSyncthingBinary);
|
||||||
Process p = pb.start();
|
Process p = pb.start();
|
||||||
if (p != null)
|
p.waitFor();
|
||||||
p.waitFor();
|
|
||||||
} catch (IOException|InterruptedException e) {
|
} catch (IOException|InterruptedException e) {
|
||||||
Log.w(TAG, "Failed to chmod Syncthing", e);
|
Log.w(TAG, "Failed to chmod Syncthing", e);
|
||||||
}
|
}
|
||||||
|
@ -98,7 +100,10 @@ public class SyncthingRunnable implements Runnable {
|
||||||
try {
|
try {
|
||||||
// Loop to handle Syncthing restarts (these always have an error code of 3).
|
// Loop to handle Syncthing restarts (these always have an error code of 3).
|
||||||
do {
|
do {
|
||||||
ProcessBuilder pb = new ProcessBuilder(mCommand);
|
ProcessBuilder pb = (useRoot())
|
||||||
|
? new ProcessBuilder("su", "-c", TextUtils.join(" ", mCommand))
|
||||||
|
: new ProcessBuilder(mCommand);
|
||||||
|
|
||||||
Map<String, String> env = pb.environment();
|
Map<String, String> env = pb.environment();
|
||||||
// Set home directory to data folder for web GUI folder picker.
|
// Set home directory to data folder for web GUI folder picker.
|
||||||
env.put("HOME", Environment.getExternalStorageDirectory().getAbsolutePath());
|
env.put("HOME", Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||||
|
@ -138,6 +143,14 @@ public class SyncthingRunnable implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if root is available and enabled in settings.
|
||||||
|
*/
|
||||||
|
private boolean useRoot() {
|
||||||
|
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||||
|
return sp.getBoolean(SyncthingService.PREF_USE_ROOT, false) && Shell.SU.available();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look for a running libsyncthing.so process and nice its IO.
|
* Look for a running libsyncthing.so process and nice its IO.
|
||||||
*/
|
*/
|
||||||
|
@ -149,7 +162,7 @@ public class SyncthingRunnable implements Runnable {
|
||||||
int ret = 1;
|
int ret = 1;
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1000); // Wait a second before getting the pid
|
Thread.sleep(1000); // Wait a second before getting the pid
|
||||||
nice = Runtime.getRuntime().exec("sh");
|
nice = Runtime.getRuntime().exec((useRoot()) ? "su" : "sh");
|
||||||
niceOut = new DataOutputStream(nice.getOutputStream());
|
niceOut = new DataOutputStream(nice.getOutputStream());
|
||||||
niceOut.writeBytes("set `ps | grep libsyncthing.so`\n");
|
niceOut.writeBytes("set `ps | grep libsyncthing.so`\n");
|
||||||
niceOut.writeBytes("ionice $2 be 7\n"); // best-effort, low priority
|
niceOut.writeBytes("ionice $2 be 7\n"); // best-effort, low priority
|
||||||
|
@ -182,9 +195,8 @@ public class SyncthingRunnable implements Runnable {
|
||||||
/**
|
/**
|
||||||
* Look for running libsyncthing.so processes and kill them.
|
* Look for running libsyncthing.so processes and kill them.
|
||||||
* Try a SIGTERM once, then try again (twice) with SIGKILL.
|
* Try a SIGTERM once, then try again (twice) with SIGKILL.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public static void killSyncthing() {
|
public void killSyncthing() {
|
||||||
final Process p = mSyncthing.get();
|
final Process p = mSyncthing.get();
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
mSyncthing.set(null);
|
mSyncthing.set(null);
|
||||||
|
@ -201,7 +213,7 @@ public class SyncthingRunnable implements Runnable {
|
||||||
Process ps = null;
|
Process ps = null;
|
||||||
DataOutputStream psOut = null;
|
DataOutputStream psOut = null;
|
||||||
try {
|
try {
|
||||||
ps = Runtime.getRuntime().exec("sh");
|
ps = Runtime.getRuntime().exec((useRoot()) ? "su" : "sh");
|
||||||
psOut = new DataOutputStream(ps.getOutputStream());
|
psOut = new DataOutputStream(ps.getOutputStream());
|
||||||
psOut.writeBytes("ps | grep libsyncthing.so\n");
|
psOut.writeBytes("ps | grep libsyncthing.so\n");
|
||||||
psOut.writeBytes("exit\n");
|
psOut.writeBytes("exit\n");
|
||||||
|
|
|
@ -92,6 +92,8 @@ public class SyncthingService extends Service {
|
||||||
|
|
||||||
public static final String PREF_SYNC_ONLY_CHARGING = "sync_only_charging";
|
public static final String PREF_SYNC_ONLY_CHARGING = "sync_only_charging";
|
||||||
|
|
||||||
|
public static final String PREF_USE_ROOT = "use_root";
|
||||||
|
|
||||||
private static final int NOTIFICATION_ACTIVE = 1;
|
private static final int NOTIFICATION_ACTIVE = 1;
|
||||||
|
|
||||||
private ConfigXml mConfig;
|
private ConfigXml mConfig;
|
||||||
|
@ -142,6 +144,8 @@ public class SyncthingService extends Service {
|
||||||
|
|
||||||
private DeviceStateHolder mDeviceStateHolder;
|
private DeviceStateHolder mDeviceStateHolder;
|
||||||
|
|
||||||
|
private SyncthingRunnable mRunnable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles intents, either {@link #ACTION_RESTART}, or intents having
|
* 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}
|
||||||
|
@ -153,11 +157,11 @@ public class SyncthingService extends Service {
|
||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
|
|
||||||
if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
||||||
mApi.shutdown();
|
shutdown();
|
||||||
mCurrentState = State.INIT;
|
mCurrentState = State.INIT;
|
||||||
updateState();
|
updateState();
|
||||||
} else if (ACTION_RESET.equals(intent.getAction())) {
|
} else if (ACTION_RESET.equals(intent.getAction())) {
|
||||||
mApi.shutdown();
|
shutdown();
|
||||||
new SyncthingRunnable(this, SyncthingRunnable.Command.reset).run();
|
new SyncthingRunnable(this, SyncthingRunnable.Command.reset).run();
|
||||||
mCurrentState = State.INIT;
|
mCurrentState = State.INIT;
|
||||||
updateState();
|
updateState();
|
||||||
|
@ -203,14 +207,15 @@ public class SyncthingService extends Service {
|
||||||
// HACK: Make sure there is no syncthing binary left running from an improper
|
// HACK: Make sure there is no syncthing binary left running from an improper
|
||||||
// shutdown (eg Play Store update).
|
// shutdown (eg Play Store update).
|
||||||
// NOTE: This will log an exception if syncthing is not actually running.
|
// NOTE: This will log an exception if syncthing is not actually running.
|
||||||
mApi.shutdown();
|
shutdown();
|
||||||
|
|
||||||
Log.i(TAG, "Starting syncthing according to current state and preferences");
|
Log.i(TAG, "Starting syncthing according to current state and preferences");
|
||||||
mConfig = new ConfigXml(SyncthingService.this);
|
mConfig = new ConfigXml(SyncthingService.this);
|
||||||
mCurrentState = State.STARTING;
|
mCurrentState = State.STARTING;
|
||||||
registerOnWebGuiAvailableListener(mApi);
|
registerOnWebGuiAvailableListener(mApi);
|
||||||
new PollWebGuiAvailableTaskImpl(getFilesDir() + "/" + HTTPS_CERT_FILE).execute(mConfig.getWebGuiUrl());
|
new PollWebGuiAvailableTaskImpl(getFilesDir() + "/" + HTTPS_CERT_FILE).execute(mConfig.getWebGuiUrl());
|
||||||
new Thread(new SyncthingRunnable(this, SyncthingRunnable.Command.main)).start();
|
mRunnable = new SyncthingRunnable(this, SyncthingRunnable.Command.main);
|
||||||
|
new Thread(mRunnable).start();
|
||||||
Notification n = new NotificationCompat.Builder(this)
|
Notification n = new NotificationCompat.Builder(this)
|
||||||
.setContentTitle(getString(R.string.syncthing_active))
|
.setContentTitle(getString(R.string.syncthing_active))
|
||||||
.setSmallIcon(R.drawable.ic_stat_notify)
|
.setSmallIcon(R.drawable.ic_stat_notify)
|
||||||
|
@ -229,14 +234,7 @@ public class SyncthingService extends Service {
|
||||||
Log.i(TAG, "Stopping syncthing according to current state and preferences");
|
Log.i(TAG, "Stopping syncthing according to current state and preferences");
|
||||||
mCurrentState = State.DISABLED;
|
mCurrentState = State.DISABLED;
|
||||||
|
|
||||||
if (mApi != null) {
|
shutdown();
|
||||||
mApi.shutdown();
|
|
||||||
for (FolderObserver ro : mObservers) {
|
|
||||||
ro.stopWatching();
|
|
||||||
}
|
|
||||||
mObservers.clear();
|
|
||||||
}
|
|
||||||
nm.cancel(NOTIFICATION_ACTIVE);
|
|
||||||
}
|
}
|
||||||
onApiChange();
|
onApiChange();
|
||||||
}
|
}
|
||||||
|
@ -331,11 +329,23 @@ public class SyncthingService extends Service {
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
Log.i(TAG, "Shutting down service");
|
Log.i(TAG, "Shutting down service");
|
||||||
if (mApi != null) {
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdown() {
|
||||||
|
if (mRunnable != null)
|
||||||
|
mRunnable.killSyncthing();
|
||||||
|
|
||||||
|
if (mApi != null)
|
||||||
mApi.shutdown();
|
mApi.shutdown();
|
||||||
}
|
|
||||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
nm.cancel(NOTIFICATION_ACTIVE);
|
nm.cancel(NOTIFICATION_ACTIVE);
|
||||||
|
|
||||||
|
for (FolderObserver ro : mObservers) {
|
||||||
|
ro.stopWatching();
|
||||||
|
}
|
||||||
|
mObservers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -400,7 +410,7 @@ public class SyncthingService extends Service {
|
||||||
if (mStopScheduled) {
|
if (mStopScheduled) {
|
||||||
mCurrentState = State.DISABLED;
|
mCurrentState = State.DISABLED;
|
||||||
onApiChange();
|
onApiChange();
|
||||||
mApi.shutdown();
|
shutdown();
|
||||||
mStopScheduled = false;
|
mStopScheduled = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,12 @@
|
||||||
android:summary="@string/advanced_folder_picker_summary"
|
android:summary="@string/advanced_folder_picker_summary"
|
||||||
android:defaultValue="false" />
|
android:defaultValue="false" />
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="use_root"
|
||||||
|
android:title="Sync as root"
|
||||||
|
android:summary="Run syncthing as superuser"
|
||||||
|
android:defaultValue="false" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
|
|
Loading…
Reference in a new issue