mirror of
https://github.com/syncthing/syncthing-android.git
synced 2024-11-26 06:11:19 +00:00
Improve building wrapper and native binaries on Windows and Linux
* Do not ask for root if root is disabled in settings * Show error in UI when libSyncthing.so is missing * build-syncthing - Install Go on demand on windows * build-syncthing - Install Android NDK on demand on windows * Update README.md * Update APK version to 0.14.51.rc3.6 / 4162
This commit is contained in:
parent
0203aebc40
commit
73775a116d
11 changed files with 377 additions and 199 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -32,6 +32,9 @@ gradle/wrapper/gradlew*
|
||||||
# Prebuilt-go
|
# Prebuilt-go
|
||||||
syncthing/go
|
syncthing/go
|
||||||
syncthing/go.tgz
|
syncthing/go.tgz
|
||||||
|
syncthing/go.zip
|
||||||
|
syncthing/android-ndk-r16b
|
||||||
|
syncthing/ndk.zip
|
||||||
|
|
||||||
# External build artifacts
|
# External build artifacts
|
||||||
ext/
|
ext/
|
||||||
|
|
30
README.md
30
README.md
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
A wrapper of [Syncthing](https://github.com/syncthing/syncthing) for Android. Head to the "releases" section for builds. Please open an issue under this fork if you need help. Important: Please don't file bugs at the upstream repository "syncthing-android" if you are using this fork.
|
A wrapper of [Syncthing](https://github.com/syncthing/syncthing) for Android. Head to the "releases" section for builds. Please open an issue under this fork if you need help. Important: Please don't file bugs at the upstream repository "syncthing-android" if you are using this fork.
|
||||||
|
|
||||||
<img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_1.png" alt="screenshot 1" width="200" /> <img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_2.png" alt="screenshot 2" width="200" /> <img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_3.png" alt="screenshot 3" width="200" />
|
<img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_12.png" alt="screenshot 1" width="200" /> <img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_11.png" alt="screenshot 2" width="200" /> <img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_09.png" alt="screenshot 3" width="200" />
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
|
|
||||||
|
@ -22,21 +22,33 @@ The project is translated on [Transifex](https://www.transifex.com/projects/p/sy
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
### Dependencies
|
### Prerequisites
|
||||||
- Android SDK (you can skip this if you are using Android Studio)
|
- Android SDK
|
||||||
- Android NDK (`$ANDROID_NDK_HOME` should point at the root directory of your NDK)
|
`You can skip this if you are using Android Studio.`
|
||||||
- Go (see [here](https://docs.syncthing.net/dev/building.html#prerequisites) for the required version)
|
- Android NDK r16b
|
||||||
|
`$ANDROID_NDK_HOME environment variable should point at the root directory of your NDK. If the variable is not set, build-syncthing.py will automatically try to download and setup the NDK.`
|
||||||
|
- Go 1.9.7
|
||||||
|
`Make sure, Go is installed and available on the PATH environment variable. If Go is not found on the PATH environment variable, build-syncthing.py will automatically try to download and setup GO on the PATH.`
|
||||||
|
- Python 2.7
|
||||||
|
`Make sure, Python is installed and available on the PATH environment variable.`
|
||||||
|
|
||||||
### Build instructions
|
### Build instructions
|
||||||
|
|
||||||
Make sure you clone the project with
|
Make sure you clone the project with
|
||||||
`git clone https://github.com/Catfriend1/syncthing-android.git --recursive`. Alternatively, run
|
`git clone https://github.com/Catfriend1/syncthing-android.git --recursive`.
|
||||||
`git submodule init && git submodule update` in the project folder.
|
Alternatively, run `git submodule init && git submodule update` in the project folder.
|
||||||
|
|
||||||
A Linux VM, for example running Debian, is recommended to build this.
|
A Linux VM, for example running Debian, is recommended to build this.
|
||||||
|
|
||||||
Build Syncthing using `./gradlew cleanNative buildNative`. Then use `./gradlew assembleDebug` or
|
Build Syncthing and the Syncthing-Android wrapper using the following commands:
|
||||||
Android Studio to build the apk.
|
`./gradlew buildNative`
|
||||||
|
`./gradlew lint assembleDebug`
|
||||||
|
|
||||||
|
You can also use Android Studio to build the apk after you manually ran the `./gradlew buildNative` command in the repository root.
|
||||||
|
|
||||||
|
To clean up all files generated during build, use the following commands:
|
||||||
|
`./gradlew cleanNative`
|
||||||
|
`./gradlew clean`
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,8 @@ android {
|
||||||
applicationId "com.github.catfriend1.syncthingandroid"
|
applicationId "com.github.catfriend1.syncthingandroid"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 4161
|
versionCode 4162
|
||||||
versionName "0.14.51.rc3.5"
|
versionName "0.14.51.rc3.6"
|
||||||
testApplicationId 'com.github.catfriend1.syncthingandroid.test'
|
testApplicationId 'com.github.catfriend1.syncthingandroid.test'
|
||||||
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
|
||||||
playAccountConfig = playAccountConfigs.defaultAccountConfig
|
playAccountConfig = playAccountConfigs.defaultAccountConfig
|
||||||
|
|
|
@ -39,9 +39,11 @@ import android.widget.Toast;
|
||||||
import com.nutomic.syncthingandroid.R;
|
import com.nutomic.syncthingandroid.R;
|
||||||
import com.nutomic.syncthingandroid.SyncthingApp;
|
import com.nutomic.syncthingandroid.SyncthingApp;
|
||||||
import com.nutomic.syncthingandroid.service.Constants;
|
import com.nutomic.syncthingandroid.service.Constants;
|
||||||
|
import com.nutomic.syncthingandroid.service.SyncthingRunnable.ExecutableNotFoundException;
|
||||||
import com.nutomic.syncthingandroid.util.ConfigXml;
|
import com.nutomic.syncthingandroid.util.ConfigXml;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
public class FirstStartActivity extends Activity {
|
public class FirstStartActivity extends Activity {
|
||||||
|
@ -79,7 +81,8 @@ public class FirstStartActivity extends Activity {
|
||||||
private Button mBackButton;
|
private Button mBackButton;
|
||||||
private Button mNextButton;
|
private Button mNextButton;
|
||||||
|
|
||||||
@Inject SharedPreferences mPreferences;
|
@Inject
|
||||||
|
SharedPreferences mPreferences;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles activity behaviour depending on prerequisites.
|
* Handles activity behaviour depending on prerequisites.
|
||||||
|
@ -479,6 +482,9 @@ public class FirstStartActivity extends Activity {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
configXml = new ConfigXml(firstStartActivity);
|
configXml = new ConfigXml(firstStartActivity);
|
||||||
|
} catch (ExecutableNotFoundException e) {
|
||||||
|
publishProgress(firstStartActivity.getString(R.string.executable_not_found, e.getMessage()));
|
||||||
|
cancel(true);
|
||||||
} catch (ConfigXml.OpenConfigException e) {
|
} catch (ConfigXml.OpenConfigException e) {
|
||||||
publishProgress(firstStartActivity.getString(R.string.config_create_failed));
|
publishProgress(firstStartActivity.getString(R.string.config_create_failed));
|
||||||
cancel(true);
|
cancel(true);
|
||||||
|
|
|
@ -123,7 +123,6 @@ public class WebGuiActivity extends SyncthingActivity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize WebView.
|
* Initialize WebView.
|
||||||
*
|
|
||||||
* Ignore lint javascript warning as js is loaded only from our known, local service.
|
* Ignore lint javascript warning as js is loaded only from our known, local service.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -134,7 +133,11 @@ public class WebGuiActivity extends SyncthingActivity
|
||||||
setContentView(R.layout.activity_web_gui);
|
setContentView(R.layout.activity_web_gui);
|
||||||
|
|
||||||
mLoadingView = findViewById(R.id.loading);
|
mLoadingView = findViewById(R.id.loading);
|
||||||
|
try {
|
||||||
mConfig = new ConfigXml(this);
|
mConfig = new ConfigXml(this);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e.getMessage());
|
||||||
|
}
|
||||||
loadCaCert();
|
loadCaCert();
|
||||||
|
|
||||||
mWebView = findViewById(R.id.webview);
|
mWebView = findViewById(R.id.webview);
|
||||||
|
|
|
@ -56,9 +56,13 @@ public class SyncthingRunnable implements Runnable {
|
||||||
private final File mSyncthingBinary;
|
private final File mSyncthingBinary;
|
||||||
private String[] mCommand;
|
private String[] mCommand;
|
||||||
private final File mLogFile;
|
private final File mLogFile;
|
||||||
@Inject SharedPreferences mPreferences;
|
|
||||||
private final boolean mUseRoot;
|
private final boolean mUseRoot;
|
||||||
@Inject NotificationHandler mNotificationHandler;
|
|
||||||
|
@Inject
|
||||||
|
SharedPreferences mPreferences;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
NotificationHandler mNotificationHandler;
|
||||||
|
|
||||||
public enum Command {
|
public enum Command {
|
||||||
deviceid, // Output the device ID to the command line.
|
deviceid, // Output the device ID to the command line.
|
||||||
|
@ -104,11 +108,15 @@ public class SyncthingRunnable implements Runnable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
try {
|
||||||
run(false);
|
run(false);
|
||||||
|
} catch (ExecutableNotFoundException e) {
|
||||||
|
throw new RuntimeException(e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("WakelockTimeout")
|
@SuppressLint("WakelockTimeout")
|
||||||
public String run(boolean returnStdOut) {
|
public String run(boolean returnStdOut) throws ExecutableNotFoundException {
|
||||||
Boolean sendStopToService = false;
|
Boolean sendStopToService = false;
|
||||||
Boolean restartSyncthingNative = false;
|
Boolean restartSyncthingNative = false;
|
||||||
int exitCode;
|
int exitCode;
|
||||||
|
@ -315,7 +323,11 @@ public class SyncthingRunnable implements Runnable {
|
||||||
* Manually run "sysctl fs.inotify" in a root shell terminal to check current limit.
|
* Manually run "sysctl fs.inotify" in a root shell terminal to check current limit.
|
||||||
*/
|
*/
|
||||||
private void increaseInotifyWatches() {
|
private void increaseInotifyWatches() {
|
||||||
if (!mUseRoot || !Shell.SU.available()) {
|
if (!mUseRoot) {
|
||||||
|
// Settings prohibit using root privileges. Cannot increase inotify limit.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Shell.SU.available()) {
|
||||||
Log.i(TAG, "increaseInotifyWatches: Root is not available. Cannot increase inotify limit.");
|
Log.i(TAG, "increaseInotifyWatches: Root is not available. Cannot increase inotify limit.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -327,7 +339,11 @@ public class SyncthingRunnable implements Runnable {
|
||||||
* Look for a running libsyncthing.so process and nice its IO.
|
* Look for a running libsyncthing.so process and nice its IO.
|
||||||
*/
|
*/
|
||||||
private void niceSyncthing() {
|
private void niceSyncthing() {
|
||||||
if (!mUseRoot || !Shell.SU.available()) {
|
if (!mUseRoot) {
|
||||||
|
// Settings prohibit using root privileges. Cannot nice syncthing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Shell.SU.available()) {
|
||||||
Log.i(TAG_NICE, "Root is not available. Cannot nice syncthing.");
|
Log.i(TAG_NICE, "Root is not available. Cannot nice syncthing.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -482,7 +498,16 @@ public class SyncthingRunnable implements Runnable {
|
||||||
return targetEnv;
|
return targetEnv;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Process setupAndLaunch(HashMap<String, String> env) throws IOException {
|
private Process setupAndLaunch(HashMap<String, String> env) throws IOException, ExecutableNotFoundException {
|
||||||
|
// Check if "libSyncthing.so" exists.
|
||||||
|
if (mCommand.length > 0) {
|
||||||
|
File libSyncthing = new File(mCommand[0]);
|
||||||
|
if (!libSyncthing.exists()) {
|
||||||
|
Log.e(TAG, "CRITICAL - Syncthing core binary is missing in APK package location " + mCommand[0]);
|
||||||
|
throw new ExecutableNotFoundException(mCommand[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mUseRoot) {
|
if (mUseRoot) {
|
||||||
ProcessBuilder pb = new ProcessBuilder("su");
|
ProcessBuilder pb = new ProcessBuilder("su");
|
||||||
Process process = pb.start();
|
Process process = pb.start();
|
||||||
|
@ -508,4 +533,16 @@ public class SyncthingRunnable implements Runnable {
|
||||||
return pb.start();
|
return pb.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ExecutableNotFoundException extends Exception {
|
||||||
|
|
||||||
|
public ExecutableNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExecutableNotFoundException(String message, Throwable throwable) {
|
||||||
|
super(message, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,18 +120,27 @@ public class SyncthingService extends Service {
|
||||||
* Indicates the current state of SyncthingService and of Syncthing itself.
|
* Indicates the current state of SyncthingService and of Syncthing itself.
|
||||||
*/
|
*/
|
||||||
public enum State {
|
public enum State {
|
||||||
/** Service is initializing, Syncthing was not started yet. */
|
/**
|
||||||
|
* Service is initializing, Syncthing was not started yet.
|
||||||
|
*/
|
||||||
INIT,
|
INIT,
|
||||||
/** Syncthing binary is starting. */
|
/**
|
||||||
|
* Syncthing binary is starting.
|
||||||
|
*/
|
||||||
STARTING,
|
STARTING,
|
||||||
/** Syncthing binary is running,
|
/**
|
||||||
|
* Syncthing binary is running,
|
||||||
* Rest API is available,
|
* Rest API is available,
|
||||||
* RestApi class read the config and is fully initialized.
|
* RestApi class read the config and is fully initialized.
|
||||||
*/
|
*/
|
||||||
ACTIVE,
|
ACTIVE,
|
||||||
/** Syncthing binary is shutting down. */
|
/**
|
||||||
|
* Syncthing binary is shutting down.
|
||||||
|
*/
|
||||||
DISABLED,
|
DISABLED,
|
||||||
/** There is some problem that prevents Syncthing from running. */
|
/**
|
||||||
|
* There is some problem that prevents Syncthing from running.
|
||||||
|
*/
|
||||||
ERROR,
|
ERROR,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,13 +150,7 @@ public class SyncthingService extends Service {
|
||||||
* {@link onStartCommand}.
|
* {@link onStartCommand}.
|
||||||
*/
|
*/
|
||||||
private State mCurrentState = State.DISABLED;
|
private State mCurrentState = State.DISABLED;
|
||||||
|
|
||||||
private ConfigXml mConfig;
|
private ConfigXml mConfig;
|
||||||
private @Nullable PollWebGuiAvailableTask mPollWebGuiAvailableTask = null;
|
|
||||||
private @Nullable RestApi mApi = null;
|
|
||||||
private @Nullable EventProcessor mEventProcessor = null;
|
|
||||||
private @Nullable RunConditionMonitor mRunConditionMonitor = null;
|
|
||||||
private @Nullable SyncthingRunnable mSyncthingRunnable = null;
|
|
||||||
private StartupTask mStartupTask = null;
|
private StartupTask mStartupTask = null;
|
||||||
private Thread mSyncthingRunnableThread = null;
|
private Thread mSyncthingRunnableThread = null;
|
||||||
private Handler mHandler;
|
private Handler mHandler;
|
||||||
|
@ -155,8 +158,26 @@ public class SyncthingService extends Service {
|
||||||
private final HashSet<OnServiceStateChangeListener> mOnServiceStateChangeListeners = new HashSet<>();
|
private final HashSet<OnServiceStateChangeListener> mOnServiceStateChangeListeners = new HashSet<>();
|
||||||
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this);
|
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this);
|
||||||
|
|
||||||
@Inject NotificationHandler mNotificationHandler;
|
private @Nullable
|
||||||
@Inject SharedPreferences mPreferences;
|
PollWebGuiAvailableTask mPollWebGuiAvailableTask = null;
|
||||||
|
|
||||||
|
private @Nullable
|
||||||
|
RestApi mApi = null;
|
||||||
|
|
||||||
|
private @Nullable
|
||||||
|
EventProcessor mEventProcessor = null;
|
||||||
|
|
||||||
|
private @Nullable
|
||||||
|
RunConditionMonitor mRunConditionMonitor = null;
|
||||||
|
|
||||||
|
private @Nullable
|
||||||
|
SyncthingRunnable mSyncthingRunnable = null;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
NotificationHandler mNotificationHandler;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SharedPreferences mPreferences;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object that must be locked upon accessing mCurrentState
|
* Object that must be locked upon accessing mCurrentState
|
||||||
|
@ -245,7 +266,8 @@ public class SyncthingService extends Service {
|
||||||
if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
||||||
shutdown(State.INIT, () -> launchStartupTask(SyncthingRunnable.Command.main));
|
shutdown(State.INIT, () -> launchStartupTask(SyncthingRunnable.Command.main));
|
||||||
} else if (ACTION_STOP.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
} else if (ACTION_STOP.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
||||||
shutdown(State.DISABLED, () -> {});
|
shutdown(State.DISABLED, () -> {
|
||||||
|
});
|
||||||
} else if (ACTION_RESET_DATABASE.equals(intent.getAction())) {
|
} else if (ACTION_RESET_DATABASE.equals(intent.getAction())) {
|
||||||
Log.i(TAG, "Invoking reset of database");
|
Log.i(TAG, "Invoking reset of database");
|
||||||
shutdown(State.INIT, () -> {
|
shutdown(State.INIT, () -> {
|
||||||
|
@ -305,7 +327,8 @@ public class SyncthingService extends Service {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.v(TAG, "Stopping syncthing");
|
Log.v(TAG, "Stopping syncthing");
|
||||||
shutdown(State.DISABLED, () -> {});
|
shutdown(State.DISABLED, () -> {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -355,6 +378,12 @@ public class SyncthingService extends Service {
|
||||||
try {
|
try {
|
||||||
syncthingService.mConfig = new ConfigXml(syncthingService);
|
syncthingService.mConfig = new ConfigXml(syncthingService);
|
||||||
syncthingService.mConfig.updateIfNeeded();
|
syncthingService.mConfig.updateIfNeeded();
|
||||||
|
} catch (SyncthingRunnable.ExecutableNotFoundException e) {
|
||||||
|
syncthingService.mNotificationHandler.showCrashedNotification(R.string.config_read_failed, "SycnthingRunnable.ExecutableNotFoundException");
|
||||||
|
synchronized (syncthingService.mStateLock) {
|
||||||
|
syncthingService.onServiceStateChange(State.ERROR);
|
||||||
|
}
|
||||||
|
cancel(true);
|
||||||
} catch (ConfigXml.OpenConfigException e) {
|
} catch (ConfigXml.OpenConfigException e) {
|
||||||
syncthingService.mNotificationHandler.showCrashedNotification(R.string.config_read_failed, "ConfigXml.OpenConfigException");
|
syncthingService.mNotificationHandler.showCrashedNotification(R.string.config_read_failed, "ConfigXml.OpenConfigException");
|
||||||
synchronized (syncthingService.mStateLock) {
|
synchronized (syncthingService.mStateLock) {
|
||||||
|
@ -483,21 +512,22 @@ public class SyncthingService extends Service {
|
||||||
mDestroyScheduled = true;
|
mDestroyScheduled = true;
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Shutting down syncthing binary immediately");
|
Log.i(TAG, "Shutting down syncthing binary immediately");
|
||||||
shutdown(State.DISABLED, () -> {});
|
shutdown(State.DISABLED, () -> {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the storage permission got revoked, we did not start the binary and
|
// If the storage permission got revoked, we did not start the binary and
|
||||||
// are in State.INIT requiring an immediate shutdown of this service class.
|
// are in State.INIT requiring an immediate shutdown of this service class.
|
||||||
Log.i(TAG, "Shutting down syncthing binary due to missing storage permission.");
|
Log.i(TAG, "Shutting down syncthing binary due to missing storage permission.");
|
||||||
shutdown(State.DISABLED, () -> {});
|
shutdown(State.DISABLED, () -> {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop Syncthing and all helpers like event processor and api handler.
|
* Stop Syncthing and all helpers like event processor and api handler.
|
||||||
*
|
|
||||||
* Sets {@link #mCurrentState} to newState, and calls onKilledListener once Syncthing is killed.
|
* Sets {@link #mCurrentState} to newState, and calls onKilledListener once Syncthing is killed.
|
||||||
*/
|
*/
|
||||||
private void shutdown(State newState, OnSyncthingKilled onKilledListener) {
|
private void shutdown(State newState, OnSyncthingKilled onKilledListener) {
|
||||||
|
@ -550,7 +580,8 @@ public class SyncthingService extends Service {
|
||||||
onKilledListener.onKilled();
|
onKilledListener.onKilled();
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable RestApi getApi() {
|
public @Nullable
|
||||||
|
RestApi getApi() {
|
||||||
return mApi;
|
return mApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,7 +599,6 @@ public class SyncthingService extends Service {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a listener for the syncthing API state changing.
|
* Register a listener for the syncthing API state changing.
|
||||||
*
|
|
||||||
* The listener is called immediately with the current state, and again whenever the state
|
* The listener is called immediately with the current state, and again whenever the state
|
||||||
* changes. The call is always from the GUI thread.
|
* changes. The call is always from the GUI thread.
|
||||||
*
|
*
|
||||||
|
@ -692,7 +722,8 @@ public class SyncthingService extends Service {
|
||||||
Log.v(TAG, "importConfig BEGIN");
|
Log.v(TAG, "importConfig BEGIN");
|
||||||
|
|
||||||
// Shutdown synchronously.
|
// Shutdown synchronously.
|
||||||
shutdown(State.DISABLED, () -> {});
|
shutdown(State.DISABLED, () -> {
|
||||||
|
});
|
||||||
|
|
||||||
// Import config, privateKey and/or publicKey.
|
// Import config, privateKey and/or publicKey.
|
||||||
try {
|
try {
|
||||||
|
@ -736,11 +767,13 @@ public class SyncthingService extends Service {
|
||||||
// Preferences that are no longer used and left-overs from previous versions of the app.
|
// Preferences that are no longer used and left-overs from previous versions of the app.
|
||||||
case "first_start":
|
case "first_start":
|
||||||
case "notify_crashes":
|
case "notify_crashes":
|
||||||
|
Log.v(TAG, "importConfig: Ignoring deprecated pref \"" + prefKey + "\".");
|
||||||
|
break;
|
||||||
// Cached information which is not available on SettingsActivity.
|
// Cached information which is not available on SettingsActivity.
|
||||||
case Constants.PREF_DEBUG_FACILITIES_AVAILABLE:
|
case Constants.PREF_DEBUG_FACILITIES_AVAILABLE:
|
||||||
case Constants.PREF_EVENT_PROCESSOR_LAST_SYNC_ID:
|
case Constants.PREF_EVENT_PROCESSOR_LAST_SYNC_ID:
|
||||||
case Constants.PREF_LAST_BINARY_VERSION:
|
case Constants.PREF_LAST_BINARY_VERSION:
|
||||||
Log.v(TAG, "importConfig: Ignoring pref \"" + prefKey + "\".");
|
Log.v(TAG, "importConfig: Ignoring cache pref \"" + prefKey + "\".");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Log.v(TAG, "importConfig: Adding pref \"" + prefKey + "\" to commit ...");
|
Log.v(TAG, "importConfig: Adding pref \"" + prefKey + "\" to commit ...");
|
||||||
|
|
|
@ -41,7 +41,6 @@ import javax.xml.transform.stream.StreamResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides direct access to the config.xml file in the file system.
|
* 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).
|
* This class should only be used if the syncthing API is not available (usually during startup).
|
||||||
*/
|
*/
|
||||||
public class ConfigXml {
|
public class ConfigXml {
|
||||||
|
@ -53,19 +52,21 @@ public class ConfigXml {
|
||||||
private static final int FOLDER_ID_APPENDIX_LENGTH = 4;
|
private static final int FOLDER_ID_APPENDIX_LENGTH = 4;
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
@Inject SharedPreferences mPreferences;
|
|
||||||
|
@Inject
|
||||||
|
SharedPreferences mPreferences;
|
||||||
|
|
||||||
private final File mConfigFile;
|
private final File mConfigFile;
|
||||||
|
|
||||||
private Document mConfig;
|
private Document mConfig;
|
||||||
|
|
||||||
public ConfigXml(Context context) throws OpenConfigException {
|
public ConfigXml(Context context) throws OpenConfigException, SyncthingRunnable.ExecutableNotFoundException {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mConfigFile = Constants.getConfigFile(mContext);
|
mConfigFile = Constants.getConfigFile(mContext);
|
||||||
boolean isFirstStart = !mConfigFile.exists();
|
boolean isFirstStart = !mConfigFile.exists();
|
||||||
if (isFirstStart) {
|
if (isFirstStart) {
|
||||||
Log.i(TAG, "App started for the first time. Generating keys and config.");
|
Log.i(TAG, "App started for the first time. Generating keys and config.");
|
||||||
new SyncthingRunnable(context, SyncthingRunnable.Command.generate).run();
|
new SyncthingRunnable(context, SyncthingRunnable.Command.generate).run(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
readConfig();
|
readConfig();
|
||||||
|
@ -122,7 +123,6 @@ public class ConfigXml {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the config file.
|
* Updates the config file.
|
||||||
*
|
|
||||||
* Sets ignorePerms flag to true on every folder, force enables TLS, sets the
|
* Sets ignorePerms flag to true on every folder, force enables TLS, sets the
|
||||||
* username/password, and disables weak hash checking.
|
* username/password, and disables weak hash checking.
|
||||||
*/
|
*/
|
||||||
|
@ -210,7 +210,6 @@ public class ConfigXml {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates syncthing options to a version specific target setting in the config file.
|
* Updates syncthing options to a version specific target setting in the config file.
|
||||||
*
|
|
||||||
* Used for one-time config migration from a lower syncthing version to the current version.
|
* Used for one-time config migration from a lower syncthing version to the current version.
|
||||||
* Enables filesystem watcher.
|
* Enables filesystem watcher.
|
||||||
* Returns if changes to the config have been made.
|
* Returns if changes to the config have been made.
|
||||||
|
@ -273,7 +272,6 @@ public class ConfigXml {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set device model name as device name for Syncthing.
|
* Set device model name as device name for Syncthing.
|
||||||
*
|
|
||||||
* We need to iterate through XML nodes manually, as mConfig.getDocumentElement() will also
|
* We need to iterate through XML nodes manually, as mConfig.getDocumentElement() will also
|
||||||
* return nested elements inside folder element. We have to check that we only rename the
|
* return nested elements inside folder element. We have to check that we only rename the
|
||||||
* device corresponding to the local device ID.
|
* device corresponding to the local device ID.
|
||||||
|
|
|
@ -652,6 +652,7 @@ Please report any problems you encounter via Github.</string>
|
||||||
<string name="syncthing_terminated">Syncthing was terminated</string>
|
<string name="syncthing_terminated">Syncthing was terminated</string>
|
||||||
|
|
||||||
<!-- Toast shown if syncthing failed to create or read the config -->
|
<!-- Toast shown if syncthing failed to create or read the config -->
|
||||||
|
<string name="executable_not_found">Core executable \"%s\" is missing. Check build and logcat output.</string>
|
||||||
<string name="config_create_failed">Failed to create configuration. Check logcat output.</string>
|
<string name="config_create_failed">Failed to create configuration. Check logcat output.</string>
|
||||||
<string name="config_read_failed">Failed to read configuration. Consider backing up data from your sync folders, then clear this app\'s data from Android settings and launch it again.</string>
|
<string name="config_read_failed">Failed to read configuration. Consider backing up data from your sync folders, then clear this app\'s data from Android settings and launch it again.</string>
|
||||||
|
|
||||||
|
|
|
@ -44,17 +44,13 @@ def get_min_sdk(project_dir):
|
||||||
|
|
||||||
fail('Failed to find minSdkVersion')
|
fail('Failed to find minSdkVersion')
|
||||||
|
|
||||||
|
|
||||||
def get_ndk_home():
|
|
||||||
if not os.environ.get('ANDROID_NDK_HOME', ''):
|
|
||||||
fail('Error: ANDROID_NDK_HOME environment variable not defined')
|
|
||||||
return os.environ['ANDROID_NDK_HOME']
|
|
||||||
|
|
||||||
def which(program):
|
def which(program):
|
||||||
import os
|
import os
|
||||||
def is_exe(fpath):
|
def is_exe(fpath):
|
||||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
||||||
|
|
||||||
|
if (sys.platform == 'win32'):
|
||||||
|
program += ".exe"
|
||||||
fpath, fname = os.path.split(program)
|
fpath, fname = os.path.split(program)
|
||||||
if fpath:
|
if fpath:
|
||||||
if is_exe(program):
|
if is_exe(program):
|
||||||
|
@ -67,26 +63,40 @@ def which(program):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def change_permissions_recursive(path, mode):
|
||||||
|
import os
|
||||||
|
for root, dirs, files in os.walk(path, topdown=False):
|
||||||
|
for dir in [os.path.join(root,d) for d in dirs]:
|
||||||
|
os.chmod(dir, mode)
|
||||||
|
for file in [os.path.join(root, f) for f in files]:
|
||||||
|
os.chmod(file, mode)
|
||||||
|
|
||||||
def install_go():
|
def install_go():
|
||||||
import os
|
import os
|
||||||
import tarfile
|
import tarfile
|
||||||
|
import zipfile
|
||||||
import urllib
|
import urllib
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
# Consts.
|
# Consts.
|
||||||
pwd_path = os.path.dirname(os.path.realpath(__file__))
|
pwd_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
url = 'https://dl.google.com/go/go1.9.7.windows-amd64.zip'
|
||||||
|
expected_shasum = '8db4b21916a3bc79f48d0611202ee5814c82f671b36d5d2efcb446879456cd28'
|
||||||
|
tar_gz_fullfn = pwd_path + os.path.sep + 'go.zip';
|
||||||
|
else:
|
||||||
url = 'https://dl.google.com/go/go1.9.7.linux-amd64.tar.gz'
|
url = 'https://dl.google.com/go/go1.9.7.linux-amd64.tar.gz'
|
||||||
expected_shasum = '88573008f4f6233b81f81d8ccf92234b4f67238df0f0ab173d75a302a1f3d6ee'
|
expected_shasum = '88573008f4f6233b81f81d8ccf92234b4f67238df0f0ab173d75a302a1f3d6ee'
|
||||||
|
tar_gz_fullfn = pwd_path + os.path.sep + 'go.tgz';
|
||||||
|
|
||||||
# Download prebuilt-go.
|
# Download prebuilt-go.
|
||||||
url_base_name = os.path.basename(url)
|
url_base_name = os.path.basename(url)
|
||||||
tar_gz_fullfn = pwd_path + os.path.sep + 'go.tgz';
|
|
||||||
if not os.path.isfile(tar_gz_fullfn):
|
if not os.path.isfile(tar_gz_fullfn):
|
||||||
print('Downloading prebuilt-go tar to:', tar_gz_fullfn)
|
print('Downloading prebuilt-go to:', tar_gz_fullfn)
|
||||||
tar_gz_fullfn = urllib.urlretrieve(url, tar_gz_fullfn)[0]
|
tar_gz_fullfn = urllib.urlretrieve(url, tar_gz_fullfn)[0]
|
||||||
print('Downloaded prebuilt-go tar to:', tar_gz_fullfn)
|
print('Downloaded prebuilt-go to:', tar_gz_fullfn)
|
||||||
|
|
||||||
# Verfiy SHA-1 checksum of downloaded files.
|
# Verfiy SHA-256 checksum of downloaded files.
|
||||||
with open(tar_gz_fullfn, 'rb') as f:
|
with open(tar_gz_fullfn, 'rb') as f:
|
||||||
contents = f.read()
|
contents = f.read()
|
||||||
found_shasum = hashlib.sha256(contents).hexdigest()
|
found_shasum = hashlib.sha256(contents).hexdigest()
|
||||||
|
@ -96,11 +106,18 @@ def install_go():
|
||||||
print("[ok] Checksum of", tar_gz_fullfn, "matches expected value.")
|
print("[ok] Checksum of", tar_gz_fullfn, "matches expected value.")
|
||||||
|
|
||||||
# Proceed with extraction of the prebuilt go.
|
# Proceed with extraction of the prebuilt go.
|
||||||
# This will go to a subfolder "go" in the current path.
|
if not os.path.isfile(pwd_path + os.path.sep + 'go' + os.path.sep + 'LICENSE'):
|
||||||
print("Extracting prebuilt-go ...")
|
print("Extracting prebuilt-go ...")
|
||||||
|
# This will go to a subfolder "go" in the current path.
|
||||||
file_name, file_extension = os.path.splitext(url_base_name)
|
file_name, file_extension = os.path.splitext(url_base_name)
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
zip = zipfile.ZipFile(tar_gz_fullfn, 'r')
|
||||||
|
zip.extractall(pwd_path)
|
||||||
|
zip.close()
|
||||||
|
else:
|
||||||
tar = tarfile.open(tar_gz_fullfn)
|
tar = tarfile.open(tar_gz_fullfn)
|
||||||
tar.extractall(pwd_path)
|
tar.extractall(pwd_path)
|
||||||
|
tar.close()
|
||||||
|
|
||||||
# Add (...).tar/go/bin" to the PATH.
|
# Add (...).tar/go/bin" to the PATH.
|
||||||
go_bin_path = pwd_path + os.path.sep + 'go' + os.path.sep + 'bin'
|
go_bin_path = pwd_path + os.path.sep + 'go' + os.path.sep + 'bin'
|
||||||
|
@ -108,6 +125,65 @@ def install_go():
|
||||||
os.environ["PATH"] += os.pathsep + go_bin_path
|
os.environ["PATH"] += os.pathsep + go_bin_path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def install_ndk():
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
import urllib
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
# Consts.
|
||||||
|
pwd_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
url = 'https://dl.google.com/android/repository/android-ndk-r16b-windows-x86_64.zip'
|
||||||
|
expected_shasum = 'f3f1909ed1052e98dda2c79d11c22f3da28daf25'
|
||||||
|
|
||||||
|
else:
|
||||||
|
url = 'https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip'
|
||||||
|
expected_shasum = '42aa43aae89a50d1c66c3f9fdecd676936da6128'
|
||||||
|
|
||||||
|
zip_fullfn = pwd_path + os.path.sep + 'ndk.zip';
|
||||||
|
# Download NDK.
|
||||||
|
url_base_name = os.path.basename(url)
|
||||||
|
if not os.path.isfile(zip_fullfn):
|
||||||
|
print('Downloading NDK to:', zip_fullfn)
|
||||||
|
zip_fullfn = urllib.urlretrieve(url, zip_fullfn)[0]
|
||||||
|
print('Downloaded NDK to:', zip_fullfn)
|
||||||
|
|
||||||
|
# Verfiy SHA-1 checksum of downloaded files.
|
||||||
|
with open(zip_fullfn, 'rb') as f:
|
||||||
|
contents = f.read()
|
||||||
|
found_shasum = hashlib.sha1(contents).hexdigest()
|
||||||
|
print("SHA-1:", zip_fullfn, "%s" % found_shasum)
|
||||||
|
if found_shasum != expected_shasum:
|
||||||
|
fail('Error: SHA-1 checksum', found_shasum, 'of downloaded file does not match expected checksum', expected_shasum)
|
||||||
|
print("[ok] Checksum of", zip_fullfn, "matches expected value.")
|
||||||
|
|
||||||
|
# Proceed with extraction of the NDK if necessary.
|
||||||
|
ndk_home_path = pwd_path + os.path.sep + 'android-ndk-r16b'
|
||||||
|
if not os.path.isfile(ndk_home_path + os.path.sep + "sysroot" + os.path.sep + "NOTICE"):
|
||||||
|
print("Extracting NDK ...")
|
||||||
|
# This will go to a subfolder "android-ndk-r16b" in the current path.
|
||||||
|
file_name, file_extension = os.path.splitext(url_base_name)
|
||||||
|
zip = zipfile.ZipFile(zip_fullfn, 'r')
|
||||||
|
zip.extractall(pwd_path)
|
||||||
|
zip.close()
|
||||||
|
|
||||||
|
# Linux only - Set executable permission on files.
|
||||||
|
if platform.system() == 'Linux':
|
||||||
|
print("Setting permissions on NDK executables ...")
|
||||||
|
change_permissions_recursive(ndk_home_path, 0o755);
|
||||||
|
|
||||||
|
# Add "ANDROID_NDK_HOME" environment variable.
|
||||||
|
print('Adding ANDROID_NDK_HOME=\'' + ndk_home_path + '\'')
|
||||||
|
os.environ["ANDROID_NDK_HOME"] = ndk_home_path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# BUILD SCRIPT MAIN.
|
||||||
|
#
|
||||||
if platform.system() not in SUPPORTED_PYTHON_PLATFORMS:
|
if platform.system() not in SUPPORTED_PYTHON_PLATFORMS:
|
||||||
fail('Unsupported python platform %s. Supported platforms: %s', platform.system(),
|
fail('Unsupported python platform %s. Supported platforms: %s', platform.system(),
|
||||||
', '.join(SUPPORTED_PYTHON_PLATFORMS))
|
', '.join(SUPPORTED_PYTHON_PLATFORMS))
|
||||||
|
@ -125,12 +201,20 @@ go_bin = which("go");
|
||||||
if not go_bin:
|
if not go_bin:
|
||||||
print('Warning: go is not available on the PATH.')
|
print('Warning: go is not available on the PATH.')
|
||||||
install_go();
|
install_go();
|
||||||
|
|
||||||
# Retry: Check if go is available.
|
# Retry: Check if go is available.
|
||||||
go_bin = which("go");
|
go_bin = which("go");
|
||||||
if not go_bin:
|
if not go_bin:
|
||||||
fail('Error: go is not available on the PATH.')
|
fail('Error: go is not available on the PATH.')
|
||||||
print ('go_bin [', go_bin, ']')
|
print ('go_bin=\'' + go_bin + '\'')
|
||||||
|
|
||||||
|
# Check if ANDROID_NDK_HOME variable is set.
|
||||||
|
if not os.environ.get('ANDROID_NDK_HOME', ''):
|
||||||
|
print('Warning: ANDROID_NDK_HOME environment variable not defined.')
|
||||||
|
install_ndk();
|
||||||
|
# Retry: Check if ANDROID_NDK_HOME variable is set.
|
||||||
|
if not os.environ.get('ANDROID_NDK_HOME', ''):
|
||||||
|
fail('Error: ANDROID_NDK_HOME environment variable not defined')
|
||||||
|
print ('ANDROID_NDK_HOME=\'' + os.environ.get('ANDROID_NDK_HOME', '') + '\'')
|
||||||
|
|
||||||
# Make sure all tags are available for git describe
|
# Make sure all tags are available for git describe
|
||||||
# https://github.com/syncthing/syncthing-android/issues/872
|
# https://github.com/syncthing/syncthing-android/issues/872
|
||||||
|
@ -148,7 +232,7 @@ for target in BUILD_TARGETS:
|
||||||
|
|
||||||
if os.environ.get('SYNCTHING_ANDROID_PREBUILT', ''):
|
if os.environ.get('SYNCTHING_ANDROID_PREBUILT', ''):
|
||||||
# The environment variable indicates the SDK and stdlib was prebuilt, set a custom paths.
|
# The environment variable indicates the SDK and stdlib was prebuilt, set a custom paths.
|
||||||
standalone_ndk_dir = get_ndk_home() + os.path.sep + 'standalone-ndk' + os.path.sep + 'android-' + target_min_sdk + '-' + target['goarch']
|
standalone_ndk_dir = os.environ['ANDROID_NDK_HOME'] + os.path.sep + 'standalone-ndk' + os.path.sep + 'android-' + target_min_sdk + '-' + target['goarch']
|
||||||
pkg_argument = []
|
pkg_argument = []
|
||||||
else:
|
else:
|
||||||
# Build standalone NDK toolchain if it doesn't exist.
|
# Build standalone NDK toolchain if it doesn't exist.
|
||||||
|
@ -160,7 +244,7 @@ for target in BUILD_TARGETS:
|
||||||
print('Building standalone NDK for', target['arch'], 'API level', target_min_sdk, 'to', standalone_ndk_dir)
|
print('Building standalone NDK for', target['arch'], 'API level', target_min_sdk, 'to', standalone_ndk_dir)
|
||||||
subprocess.check_call([
|
subprocess.check_call([
|
||||||
sys.executable,
|
sys.executable,
|
||||||
os.path.join(get_ndk_home(), 'build', 'tools', 'make_standalone_toolchain.py'),
|
os.path.join(os.environ['ANDROID_NDK_HOME'], 'build', 'tools', 'make_standalone_toolchain.py'),
|
||||||
'--arch',
|
'--arch',
|
||||||
target['arch'],
|
target['arch'],
|
||||||
'--api',
|
'--api',
|
||||||
|
|
|
@ -10,6 +10,7 @@ task buildNative(type: Exec) {
|
||||||
*/
|
*/
|
||||||
task cleanNative(type: Delete) {
|
task cleanNative(type: Delete) {
|
||||||
delete "$projectDir/../app/src/main/jniLibs/"
|
delete "$projectDir/../app/src/main/jniLibs/"
|
||||||
|
delete "android-ndk-r16b"
|
||||||
delete "gobuild"
|
delete "gobuild"
|
||||||
delete "go"
|
delete "go"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue