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
|
||||
syncthing/go
|
||||
syncthing/go.tgz
|
||||
syncthing/go.zip
|
||||
syncthing/android-ndk-r16b
|
||||
syncthing/ndk.zip
|
||||
|
||||
# External build artifacts
|
||||
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.
|
||||
|
||||
<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
|
||||
|
||||
|
@ -22,21 +22,33 @@ The project is translated on [Transifex](https://www.transifex.com/projects/p/sy
|
|||
|
||||
# Building
|
||||
|
||||
### Dependencies
|
||||
- Android SDK (you can skip this if you are using Android Studio)
|
||||
- Android NDK (`$ANDROID_NDK_HOME` should point at the root directory of your NDK)
|
||||
- Go (see [here](https://docs.syncthing.net/dev/building.html#prerequisites) for the required version)
|
||||
### Prerequisites
|
||||
- Android SDK
|
||||
`You can skip this if you are using Android Studio.`
|
||||
- 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
|
||||
|
||||
Make sure you clone the project with
|
||||
`git clone https://github.com/Catfriend1/syncthing-android.git --recursive`. Alternatively, run
|
||||
`git submodule init && git submodule update` in the project folder.
|
||||
`git clone https://github.com/Catfriend1/syncthing-android.git --recursive`.
|
||||
Alternatively, run `git submodule init && git submodule update` in the project folder.
|
||||
|
||||
A Linux VM, for example running Debian, is recommended to build this.
|
||||
|
||||
Build Syncthing using `./gradlew cleanNative buildNative`. Then use `./gradlew assembleDebug` or
|
||||
Android Studio to build the apk.
|
||||
Build Syncthing and the Syncthing-Android wrapper using the following commands:
|
||||
`./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
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ android {
|
|||
applicationId "com.github.catfriend1.syncthingandroid"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 26
|
||||
versionCode 4161
|
||||
versionName "0.14.51.rc3.5"
|
||||
versionCode 4162
|
||||
versionName "0.14.51.rc3.6"
|
||||
testApplicationId 'com.github.catfriend1.syncthingandroid.test'
|
||||
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
|
||||
playAccountConfig = playAccountConfigs.defaultAccountConfig
|
||||
|
|
|
@ -39,9 +39,11 @@ import android.widget.Toast;
|
|||
import com.nutomic.syncthingandroid.R;
|
||||
import com.nutomic.syncthingandroid.SyncthingApp;
|
||||
import com.nutomic.syncthingandroid.service.Constants;
|
||||
import com.nutomic.syncthingandroid.service.SyncthingRunnable.ExecutableNotFoundException;
|
||||
import com.nutomic.syncthingandroid.util.ConfigXml;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class FirstStartActivity extends Activity {
|
||||
|
@ -55,7 +57,7 @@ public class FirstStartActivity extends Activity {
|
|||
public int dotColorActive;
|
||||
public int dotColorInActive;
|
||||
|
||||
Slide (int layout, int dotColorActive, int dotColorInActive) {
|
||||
Slide(int layout, int dotColorActive, int dotColorInActive) {
|
||||
this.layout = layout;
|
||||
this.dotColorActive = dotColorActive;
|
||||
this.dotColorInActive = dotColorInActive;
|
||||
|
@ -79,7 +81,8 @@ public class FirstStartActivity extends Activity {
|
|||
private Button mBackButton;
|
||||
private Button mNextButton;
|
||||
|
||||
@Inject SharedPreferences mPreferences;
|
||||
@Inject
|
||||
SharedPreferences mPreferences;
|
||||
|
||||
/**
|
||||
* Handles activity behaviour depending on prerequisites.
|
||||
|
@ -113,7 +116,7 @@ public class FirstStartActivity extends Activity {
|
|||
// Make notification bar transparent (API level 21+)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||
}
|
||||
|
||||
// Show first start welcome wizard UI.
|
||||
|
@ -138,11 +141,11 @@ public class FirstStartActivity extends Activity {
|
|||
int slideIndex = 0;
|
||||
mSlides = new Slide[
|
||||
1 +
|
||||
(showSlideStoragePermission ? 1 : 0) +
|
||||
(showSlideIgnoreDozePermission ? 1 : 0) +
|
||||
(showSlideLocationPermission ? 1 : 0) +
|
||||
(showSlideKeyGeneration ? 1 : 0)
|
||||
];
|
||||
(showSlideStoragePermission ? 1 : 0) +
|
||||
(showSlideIgnoreDozePermission ? 1 : 0) +
|
||||
(showSlideLocationPermission ? 1 : 0) +
|
||||
(showSlideKeyGeneration ? 1 : 0)
|
||||
];
|
||||
mSlides[slideIndex++] = new Slide(R.layout.activity_firststart_intro, colorsActive[0], colorsInactive[0]);
|
||||
if (showSlideStoragePermission) {
|
||||
mSlidePosStoragePermission = slideIndex;
|
||||
|
@ -365,7 +368,7 @@ public class FirstStartActivity extends Activity {
|
|||
* so that back navigation works as expected.
|
||||
*/
|
||||
if (mPreferences.getBoolean(Constants.PREF_START_INTO_WEB_GUI, false)) {
|
||||
startActivities(new Intent[] {mainIntent, new Intent(this, WebGuiActivity.class)});
|
||||
startActivities(new Intent[]{mainIntent, new Intent(this, WebGuiActivity.class)});
|
||||
} else {
|
||||
startActivity(mainIntent);
|
||||
}
|
||||
|
@ -479,6 +482,9 @@ public class FirstStartActivity extends Activity {
|
|||
}
|
||||
try {
|
||||
configXml = new ConfigXml(firstStartActivity);
|
||||
} catch (ExecutableNotFoundException e) {
|
||||
publishProgress(firstStartActivity.getString(R.string.executable_not_found, e.getMessage()));
|
||||
cancel(true);
|
||||
} catch (ConfigXml.OpenConfigException e) {
|
||||
publishProgress(firstStartActivity.getString(R.string.config_create_failed));
|
||||
cancel(true);
|
||||
|
|
|
@ -83,7 +83,7 @@ public class WebGuiActivity extends SyncthingActivity
|
|||
SslCertificate sslCert = error.getCertificate();
|
||||
Field f = sslCert.getClass().getDeclaredField("mX509Certificate");
|
||||
f.setAccessible(true);
|
||||
X509Certificate cert = (X509Certificate)f.get(sslCert);
|
||||
X509Certificate cert = (X509Certificate) f.get(sslCert);
|
||||
if (cert == null) {
|
||||
Log.w(TAG, "X509Certificate reference invalid");
|
||||
handler.cancel();
|
||||
|
@ -91,8 +91,8 @@ public class WebGuiActivity extends SyncthingActivity
|
|||
}
|
||||
cert.verify(mCaCert.getPublicKey());
|
||||
handler.proceed();
|
||||
} catch (NoSuchFieldException|IllegalAccessException|CertificateException|
|
||||
NoSuchAlgorithmException|InvalidKeyException|NoSuchProviderException|
|
||||
} catch (NoSuchFieldException | IllegalAccessException | CertificateException |
|
||||
NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException |
|
||||
SignatureException e) {
|
||||
Log.w(TAG, e);
|
||||
handler.cancel();
|
||||
|
@ -106,7 +106,7 @@ public class WebGuiActivity extends SyncthingActivity
|
|||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
Uri uri = Uri.parse(url);
|
||||
if(uri.getHost().equals(getService().getWebGuiUrl().getHost())) {
|
||||
if (uri.getHost().equals(getService().getWebGuiUrl().getHost())) {
|
||||
return false;
|
||||
} else {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, uri));
|
||||
|
@ -123,7 +123,6 @@ public class WebGuiActivity extends SyncthingActivity
|
|||
|
||||
/**
|
||||
* Initialize WebView.
|
||||
*
|
||||
* Ignore lint javascript warning as js is loaded only from our known, local service.
|
||||
*/
|
||||
@Override
|
||||
|
@ -134,7 +133,11 @@ public class WebGuiActivity extends SyncthingActivity
|
|||
setContentView(R.layout.activity_web_gui);
|
||||
|
||||
mLoadingView = findViewById(R.id.loading);
|
||||
mConfig = new ConfigXml(this);
|
||||
try {
|
||||
mConfig = new ConfigXml(this);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
loadCaCert();
|
||||
|
||||
mWebView = findViewById(R.id.webview);
|
||||
|
@ -222,7 +225,7 @@ public class WebGuiActivity extends SyncthingActivity
|
|||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
mCaCert = (X509Certificate)
|
||||
cf.generateCertificate(inStream);
|
||||
} catch (FileNotFoundException|CertificateException e) {
|
||||
} catch (FileNotFoundException | CertificateException e) {
|
||||
throw new IllegalArgumentException("Untrusted Certificate", e);
|
||||
} finally {
|
||||
try {
|
||||
|
|
|
@ -56,9 +56,13 @@ public class SyncthingRunnable implements Runnable {
|
|||
private final File mSyncthingBinary;
|
||||
private String[] mCommand;
|
||||
private final File mLogFile;
|
||||
@Inject SharedPreferences mPreferences;
|
||||
private final boolean mUseRoot;
|
||||
@Inject NotificationHandler mNotificationHandler;
|
||||
|
||||
@Inject
|
||||
SharedPreferences mPreferences;
|
||||
|
||||
@Inject
|
||||
NotificationHandler mNotificationHandler;
|
||||
|
||||
public enum Command {
|
||||
deviceid, // Output the device ID to the command line.
|
||||
|
@ -83,19 +87,19 @@ public class SyncthingRunnable implements Runnable {
|
|||
mUseRoot = mPreferences.getBoolean(Constants.PREF_USE_ROOT, false) && Shell.SU.available();
|
||||
switch (command) {
|
||||
case deviceid:
|
||||
mCommand = new String[]{ mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "--device-id" };
|
||||
mCommand = new String[]{mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "--device-id"};
|
||||
break;
|
||||
case generate:
|
||||
mCommand = new String[]{ mSyncthingBinary.getPath(), "-generate", mContext.getFilesDir().toString(), "-logflags=0" };
|
||||
mCommand = new String[]{mSyncthingBinary.getPath(), "-generate", mContext.getFilesDir().toString(), "-logflags=0"};
|
||||
break;
|
||||
case main:
|
||||
mCommand = new String[]{ mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-no-browser", "-logflags=0" };
|
||||
mCommand = new String[]{mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-no-browser", "-logflags=0"};
|
||||
break;
|
||||
case resetdatabase:
|
||||
mCommand = new String[]{ mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-reset-database", "-logflags=0" };
|
||||
mCommand = new String[]{mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-reset-database", "-logflags=0"};
|
||||
break;
|
||||
case resetdeltas:
|
||||
mCommand = new String[]{ mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-reset-deltas", "-logflags=0" };
|
||||
mCommand = new String[]{mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-reset-deltas", "-logflags=0"};
|
||||
break;
|
||||
default:
|
||||
throw new InvalidParameterException("Unknown command option");
|
||||
|
@ -104,11 +108,15 @@ public class SyncthingRunnable implements Runnable {
|
|||
|
||||
@Override
|
||||
public void run() {
|
||||
run(false);
|
||||
try {
|
||||
run(false);
|
||||
} catch (ExecutableNotFoundException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("WakelockTimeout")
|
||||
public String run(boolean returnStdOut) {
|
||||
public String run(boolean returnStdOut) throws ExecutableNotFoundException {
|
||||
Boolean sendStopToService = false;
|
||||
Boolean restartSyncthingNative = false;
|
||||
int exitCode;
|
||||
|
@ -122,7 +130,7 @@ public class SyncthingRunnable implements Runnable {
|
|||
ProcessBuilder pb = new ProcessBuilder("chmod", "500", mSyncthingBinary.getPath());
|
||||
Process p = pb.start();
|
||||
p.waitFor();
|
||||
} catch (IOException|InterruptedException e) {
|
||||
} catch (IOException | InterruptedException e) {
|
||||
Log.w(TAG, "Failed to chmod Syncthing", e);
|
||||
}
|
||||
// Loop Syncthing
|
||||
|
@ -315,7 +323,11 @@ public class SyncthingRunnable implements Runnable {
|
|||
* Manually run "sysctl fs.inotify" in a root shell terminal to check current limit.
|
||||
*/
|
||||
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.");
|
||||
return;
|
||||
}
|
||||
|
@ -327,7 +339,11 @@ public class SyncthingRunnable implements Runnable {
|
|||
* Look for a running libsyncthing.so process and nice its IO.
|
||||
*/
|
||||
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.");
|
||||
return;
|
||||
}
|
||||
|
@ -343,7 +359,7 @@ public class SyncthingRunnable implements Runnable {
|
|||
// Set best-effort, low priority using ionice.
|
||||
int exitCode = Util.runShellCommand("/system/bin/ionice " + syncthingPID + " be 7\n", true);
|
||||
Log.i(TAG_NICE, "ionice returned " + Integer.toString(exitCode) +
|
||||
" on " + Constants.FILENAME_SYNCTHING_BINARY);
|
||||
" on " + Constants.FILENAME_SYNCTHING_BINARY);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,7 +379,7 @@ public class SyncthingRunnable implements Runnable {
|
|||
Log.d(TAG, "Sent kill SIGINT to process " + syncthingPID);
|
||||
} else {
|
||||
Log.w(TAG, "Failed to send kill SIGINT to process " + syncthingPID +
|
||||
" exit code " + Integer.toString(exitCode));
|
||||
" exit code " + Integer.toString(exitCode));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -380,9 +396,9 @@ public class SyncthingRunnable implements Runnable {
|
|||
/**
|
||||
* Logs the outputs of a stream to logcat and mNativeLog.
|
||||
*
|
||||
* @param is The stream to log.
|
||||
* @param is The stream to log.
|
||||
* @param priority The priority level.
|
||||
* @param saveLog True if the log should be stored to {@link #mLogFile}.
|
||||
* @param saveLog True if the log should be stored to {@link #mLogFile}.
|
||||
*/
|
||||
private Thread log(final InputStream is, final int priority, final boolean saveLog) {
|
||||
Thread t = new Thread(() -> {
|
||||
|
@ -452,7 +468,7 @@ public class SyncthingRunnable implements Runnable {
|
|||
// Set home directory to data folder for web GUI folder picker.
|
||||
targetEnv.put("HOME", Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||
targetEnv.put("STTRACE", TextUtils.join(" ",
|
||||
mPreferences.getStringSet(Constants.PREF_DEBUG_FACILITIES_ENABLED, new HashSet<>())));
|
||||
mPreferences.getStringSet(Constants.PREF_DEBUG_FACILITIES_ENABLED, new HashSet<>())));
|
||||
File externalFilesDir = mContext.getExternalFilesDir(null);
|
||||
if (externalFilesDir != null)
|
||||
targetEnv.put("STGUIASSETS", externalFilesDir.getAbsolutePath() + "/gui");
|
||||
|
@ -482,7 +498,16 @@ public class SyncthingRunnable implements Runnable {
|
|||
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) {
|
||||
ProcessBuilder pb = new ProcessBuilder("su");
|
||||
Process process = pb.start();
|
||||
|
@ -508,4 +533,16 @@ public class SyncthingRunnable implements Runnable {
|
|||
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.
|
||||
*/
|
||||
public enum State {
|
||||
/** Service is initializing, Syncthing was not started yet. */
|
||||
/**
|
||||
* Service is initializing, Syncthing was not started yet.
|
||||
*/
|
||||
INIT,
|
||||
/** Syncthing binary is starting. */
|
||||
/**
|
||||
* Syncthing binary is starting.
|
||||
*/
|
||||
STARTING,
|
||||
/** Syncthing binary is running,
|
||||
/**
|
||||
* Syncthing binary is running,
|
||||
* Rest API is available,
|
||||
* RestApi class read the config and is fully initialized.
|
||||
*/
|
||||
ACTIVE,
|
||||
/** Syncthing binary is shutting down. */
|
||||
/**
|
||||
* Syncthing binary is shutting down.
|
||||
*/
|
||||
DISABLED,
|
||||
/** There is some problem that prevents Syncthing from running. */
|
||||
/**
|
||||
* There is some problem that prevents Syncthing from running.
|
||||
*/
|
||||
ERROR,
|
||||
}
|
||||
|
||||
|
@ -141,13 +150,7 @@ public class SyncthingService extends Service {
|
|||
* {@link onStartCommand}.
|
||||
*/
|
||||
private State mCurrentState = State.DISABLED;
|
||||
|
||||
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 Thread mSyncthingRunnableThread = null;
|
||||
private Handler mHandler;
|
||||
|
@ -155,8 +158,26 @@ public class SyncthingService extends Service {
|
|||
private final HashSet<OnServiceStateChangeListener> mOnServiceStateChangeListeners = new HashSet<>();
|
||||
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this);
|
||||
|
||||
@Inject NotificationHandler mNotificationHandler;
|
||||
@Inject SharedPreferences mPreferences;
|
||||
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;
|
||||
|
||||
@Inject
|
||||
NotificationHandler mNotificationHandler;
|
||||
|
||||
@Inject
|
||||
SharedPreferences mPreferences;
|
||||
|
||||
/**
|
||||
* Object that must be locked upon accessing mCurrentState
|
||||
|
@ -196,8 +217,8 @@ public class SyncthingService extends Service {
|
|||
* We need to recheck if we still have the storage permission.
|
||||
*/
|
||||
mStoragePermissionGranted = (ContextCompat.checkSelfPermission(this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
|
||||
PackageManager.PERMISSION_GRANTED);
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
|
||||
PackageManager.PERMISSION_GRANTED);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -223,7 +244,7 @@ public class SyncthingService extends Service {
|
|||
* See {@link mLastDeterminedShouldRun} defaulting to "false".
|
||||
*/
|
||||
if (mCurrentState == State.DISABLED) {
|
||||
synchronized(mStateLock) {
|
||||
synchronized (mStateLock) {
|
||||
onServiceStateChange(mCurrentState);
|
||||
}
|
||||
}
|
||||
|
@ -245,7 +266,8 @@ public class SyncthingService extends Service {
|
|||
if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
||||
shutdown(State.INIT, () -> launchStartupTask(SyncthingRunnable.Command.main));
|
||||
} else if (ACTION_STOP.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
||||
shutdown(State.DISABLED, () -> {});
|
||||
shutdown(State.DISABLED, () -> {
|
||||
});
|
||||
} else if (ACTION_RESET_DATABASE.equals(intent.getAction())) {
|
||||
Log.i(TAG, "Invoking reset of database");
|
||||
shutdown(State.INIT, () -> {
|
||||
|
@ -305,7 +327,8 @@ public class SyncthingService extends Service {
|
|||
return;
|
||||
}
|
||||
Log.v(TAG, "Stopping syncthing");
|
||||
shutdown(State.DISABLED, () -> {});
|
||||
shutdown(State.DISABLED, () -> {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -313,9 +336,9 @@ public class SyncthingService extends Service {
|
|||
/**
|
||||
* Prepares to launch the syncthing binary.
|
||||
*/
|
||||
private void launchStartupTask (SyncthingRunnable.Command srCommand) {
|
||||
private void launchStartupTask(SyncthingRunnable.Command srCommand) {
|
||||
Log.v(TAG, "Starting syncthing");
|
||||
synchronized(mStateLock) {
|
||||
synchronized (mStateLock) {
|
||||
if (mCurrentState != State.DISABLED && mCurrentState != State.INIT) {
|
||||
Log.e(TAG, "launchStartupTask: Wrong state " + mCurrentState + " detected. Cancelling.");
|
||||
return;
|
||||
|
@ -336,90 +359,96 @@ public class SyncthingService extends Service {
|
|||
* Sets up the initial configuration, and updates the config when coming from an old
|
||||
* version.
|
||||
*/
|
||||
private static class StartupTask extends AsyncTask<Void, Void, Void> {
|
||||
private WeakReference<SyncthingService> refSyncthingService;
|
||||
private SyncthingRunnable.Command srCommand;
|
||||
private static class StartupTask extends AsyncTask<Void, Void, Void> {
|
||||
private WeakReference<SyncthingService> refSyncthingService;
|
||||
private SyncthingRunnable.Command srCommand;
|
||||
|
||||
StartupTask(SyncthingService context, SyncthingRunnable.Command srCommand) {
|
||||
refSyncthingService = new WeakReference<>(context);
|
||||
this.srCommand = srCommand;
|
||||
}
|
||||
StartupTask(SyncthingService context, SyncthingRunnable.Command srCommand) {
|
||||
refSyncthingService = new WeakReference<>(context);
|
||||
this.srCommand = srCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
SyncthingService syncthingService = refSyncthingService.get();
|
||||
if (syncthingService == null) {
|
||||
cancel(true);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
syncthingService.mConfig = new ConfigXml(syncthingService);
|
||||
syncthingService.mConfig.updateIfNeeded();
|
||||
} catch (ConfigXml.OpenConfigException e) {
|
||||
syncthingService.mNotificationHandler.showCrashedNotification(R.string.config_read_failed, "ConfigXml.OpenConfigException");
|
||||
synchronized (syncthingService.mStateLock) {
|
||||
syncthingService.onServiceStateChange(State.ERROR);
|
||||
}
|
||||
cancel(true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
// Get a reference to the service if it is still there.
|
||||
SyncthingService syncthingService = refSyncthingService.get();
|
||||
if (syncthingService != null) {
|
||||
syncthingService.onStartupTaskCompleteListener(srCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback on {@link StartupTask#onPostExecute}.
|
||||
*/
|
||||
private void onStartupTaskCompleteListener(SyncthingRunnable.Command srCommand) {
|
||||
if (mApi == null) {
|
||||
mApi = new RestApi(this, mConfig.getWebGuiUrl(), mConfig.getApiKey(),
|
||||
this::onApiAvailable, () -> onServiceStateChange(mCurrentState));
|
||||
Log.i(TAG, "Web GUI will be available at " + mConfig.getWebGuiUrl());
|
||||
}
|
||||
|
||||
// Check mSyncthingRunnable lifecycle and create singleton.
|
||||
if (mSyncthingRunnable != null || mSyncthingRunnableThread != null) {
|
||||
Log.e(TAG, "onStartupTaskCompleteListener: Syncthing binary lifecycle violated");
|
||||
return;
|
||||
}
|
||||
mSyncthingRunnable = new SyncthingRunnable(this, srCommand);
|
||||
|
||||
/**
|
||||
* Check if an old syncthing instance is still running.
|
||||
* This happens after an in-place app upgrade. If so, end it.
|
||||
*/
|
||||
mSyncthingRunnable.killSyncthing();
|
||||
|
||||
// Start the syncthing binary in a separate thread.
|
||||
mSyncthingRunnableThread = new Thread(mSyncthingRunnable);
|
||||
mSyncthingRunnableThread.start();
|
||||
|
||||
/**
|
||||
* Wait for the web-gui of the native syncthing binary to come online.
|
||||
*
|
||||
* In case the binary is to be stopped, also be aware that another thread could request
|
||||
* to stop the binary in the time while waiting for the GUI to become active. See the comment
|
||||
* for {@link SyncthingService#onDestroy} for details.
|
||||
*/
|
||||
if (mPollWebGuiAvailableTask == null) {
|
||||
mPollWebGuiAvailableTask = new PollWebGuiAvailableTask(
|
||||
this, getWebGuiUrl(), mConfig.getApiKey(), result -> {
|
||||
Log.i(TAG, "Web GUI has come online at " + mConfig.getWebGuiUrl());
|
||||
if (mApi != null) {
|
||||
mApi.readConfigFromRestApi();
|
||||
}
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
SyncthingService syncthingService = refSyncthingService.get();
|
||||
if (syncthingService == null) {
|
||||
cancel(true);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
syncthingService.mConfig = new ConfigXml(syncthingService);
|
||||
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) {
|
||||
syncthingService.mNotificationHandler.showCrashedNotification(R.string.config_read_failed, "ConfigXml.OpenConfigException");
|
||||
synchronized (syncthingService.mStateLock) {
|
||||
syncthingService.onServiceStateChange(State.ERROR);
|
||||
}
|
||||
cancel(true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
// Get a reference to the service if it is still there.
|
||||
SyncthingService syncthingService = refSyncthingService.get();
|
||||
if (syncthingService != null) {
|
||||
syncthingService.onStartupTaskCompleteListener(srCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback on {@link StartupTask#onPostExecute}.
|
||||
*/
|
||||
private void onStartupTaskCompleteListener(SyncthingRunnable.Command srCommand) {
|
||||
if (mApi == null) {
|
||||
mApi = new RestApi(this, mConfig.getWebGuiUrl(), mConfig.getApiKey(),
|
||||
this::onApiAvailable, () -> onServiceStateChange(mCurrentState));
|
||||
Log.i(TAG, "Web GUI will be available at " + mConfig.getWebGuiUrl());
|
||||
}
|
||||
|
||||
// Check mSyncthingRunnable lifecycle and create singleton.
|
||||
if (mSyncthingRunnable != null || mSyncthingRunnableThread != null) {
|
||||
Log.e(TAG, "onStartupTaskCompleteListener: Syncthing binary lifecycle violated");
|
||||
return;
|
||||
}
|
||||
mSyncthingRunnable = new SyncthingRunnable(this, srCommand);
|
||||
|
||||
/**
|
||||
* Check if an old syncthing instance is still running.
|
||||
* This happens after an in-place app upgrade. If so, end it.
|
||||
*/
|
||||
mSyncthingRunnable.killSyncthing();
|
||||
|
||||
// Start the syncthing binary in a separate thread.
|
||||
mSyncthingRunnableThread = new Thread(mSyncthingRunnable);
|
||||
mSyncthingRunnableThread.start();
|
||||
|
||||
/**
|
||||
* Wait for the web-gui of the native syncthing binary to come online.
|
||||
*
|
||||
* In case the binary is to be stopped, also be aware that another thread could request
|
||||
* to stop the binary in the time while waiting for the GUI to become active. See the comment
|
||||
* for {@link SyncthingService#onDestroy} for details.
|
||||
*/
|
||||
if (mPollWebGuiAvailableTask == null) {
|
||||
mPollWebGuiAvailableTask = new PollWebGuiAvailableTask(
|
||||
this, getWebGuiUrl(), mConfig.getApiKey(), result -> {
|
||||
Log.i(TAG, "Web GUI has come online at " + mConfig.getWebGuiUrl());
|
||||
if (mApi != null) {
|
||||
mApi.readConfigFromRestApi();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when {@link RestApi#checkReadConfigFromRestApiCompleted} detects
|
||||
|
@ -483,21 +512,22 @@ public class SyncthingService extends Service {
|
|||
mDestroyScheduled = true;
|
||||
} else {
|
||||
Log.i(TAG, "Shutting down syncthing binary immediately");
|
||||
shutdown(State.DISABLED, () -> {});
|
||||
shutdown(State.DISABLED, () -> {
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 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.
|
||||
Log.i(TAG, "Shutting down syncthing binary due to missing storage permission.");
|
||||
shutdown(State.DISABLED, () -> {});
|
||||
shutdown(State.DISABLED, () -> {
|
||||
});
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop Syncthing and all helpers like event processor and api handler.
|
||||
*
|
||||
* Sets {@link #mCurrentState} to newState, and calls onKilledListener once Syncthing is killed.
|
||||
*/
|
||||
private void shutdown(State newState, OnSyncthingKilled onKilledListener) {
|
||||
|
@ -510,7 +540,7 @@ public class SyncthingService extends Service {
|
|||
}
|
||||
|
||||
Log.i(TAG, "Shutting down");
|
||||
synchronized(mStateLock) {
|
||||
synchronized (mStateLock) {
|
||||
onServiceStateChange(newState);
|
||||
}
|
||||
|
||||
|
@ -550,7 +580,8 @@ public class SyncthingService extends Service {
|
|||
onKilledListener.onKilled();
|
||||
}
|
||||
|
||||
public @Nullable RestApi getApi() {
|
||||
public @Nullable
|
||||
RestApi getApi() {
|
||||
return mApi;
|
||||
}
|
||||
|
||||
|
@ -568,7 +599,6 @@ public class SyncthingService extends Service {
|
|||
|
||||
/**
|
||||
* Register a listener for the syncthing API state changing.
|
||||
*
|
||||
* The listener is called immediately with the current state, and again whenever the state
|
||||
* changes. The call is always from the GUI thread.
|
||||
*
|
||||
|
@ -658,12 +688,12 @@ public class SyncthingService extends Service {
|
|||
file = new File(Constants.EXPORT_PATH, Constants.SHARED_PREFS_EXPORT_FILE);
|
||||
fileOutputStream = new FileOutputStream(file);
|
||||
if (!file.exists()) {
|
||||
file.createNewFile();
|
||||
}
|
||||
file.createNewFile();
|
||||
}
|
||||
objectOutputStream = new ObjectOutputStream(fileOutputStream);
|
||||
objectOutputStream.writeObject(mPreferences.getAll());
|
||||
objectOutputStream.flush();
|
||||
fileOutputStream.flush();
|
||||
fileOutputStream.flush();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "exportConfig: Failed to export SharedPreferences #1", e);
|
||||
failSuccess = false;
|
||||
|
@ -672,12 +702,12 @@ public class SyncthingService extends Service {
|
|||
if (objectOutputStream != null) {
|
||||
objectOutputStream.close();
|
||||
}
|
||||
if (fileOutputStream != null) {
|
||||
fileOutputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "exportConfig: Failed to export SharedPreferences #2", e);
|
||||
}
|
||||
if (fileOutputStream != null) {
|
||||
fileOutputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "exportConfig: Failed to export SharedPreferences #2", e);
|
||||
}
|
||||
}
|
||||
return failSuccess;
|
||||
}
|
||||
|
@ -692,7 +722,8 @@ public class SyncthingService extends Service {
|
|||
Log.v(TAG, "importConfig BEGIN");
|
||||
|
||||
// Shutdown synchronously.
|
||||
shutdown(State.DISABLED, () -> {});
|
||||
shutdown(State.DISABLED, () -> {
|
||||
});
|
||||
|
||||
// Import config, privateKey and/or publicKey.
|
||||
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.
|
||||
case "first_start":
|
||||
case "notify_crashes":
|
||||
Log.v(TAG, "importConfig: Ignoring deprecated pref \"" + prefKey + "\".");
|
||||
break;
|
||||
// Cached information which is not available on SettingsActivity.
|
||||
case Constants.PREF_DEBUG_FACILITIES_AVAILABLE:
|
||||
case Constants.PREF_EVENT_PROCESSOR_LAST_SYNC_ID:
|
||||
case Constants.PREF_LAST_BINARY_VERSION:
|
||||
Log.v(TAG, "importConfig: Ignoring pref \"" + prefKey + "\".");
|
||||
Log.v(TAG, "importConfig: Ignoring cache pref \"" + prefKey + "\".");
|
||||
break;
|
||||
default:
|
||||
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.
|
||||
*
|
||||
* This class should only be used if the syncthing API is not available (usually during startup).
|
||||
*/
|
||||
public class ConfigXml {
|
||||
|
@ -53,19 +52,21 @@ public class ConfigXml {
|
|||
private static final int FOLDER_ID_APPENDIX_LENGTH = 4;
|
||||
|
||||
private final Context mContext;
|
||||
@Inject SharedPreferences mPreferences;
|
||||
|
||||
@Inject
|
||||
SharedPreferences mPreferences;
|
||||
|
||||
private final File mConfigFile;
|
||||
|
||||
private Document mConfig;
|
||||
|
||||
public ConfigXml(Context context) throws OpenConfigException {
|
||||
public ConfigXml(Context context) throws OpenConfigException, SyncthingRunnable.ExecutableNotFoundException {
|
||||
mContext = context;
|
||||
mConfigFile = Constants.getConfigFile(mContext);
|
||||
boolean isFirstStart = !mConfigFile.exists();
|
||||
if (isFirstStart) {
|
||||
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();
|
||||
|
@ -122,7 +123,6 @@ public class ConfigXml {
|
|||
|
||||
/**
|
||||
* Updates the config file.
|
||||
*
|
||||
* Sets ignorePerms flag to true on every folder, force enables TLS, sets the
|
||||
* username/password, and disables weak hash checking.
|
||||
*/
|
||||
|
@ -210,12 +210,11 @@ public class ConfigXml {
|
|||
|
||||
/**
|
||||
* 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.
|
||||
* Enables filesystem watcher.
|
||||
* Returns if changes to the config have been made.
|
||||
*/
|
||||
private boolean migrateSyncthingOptions () {
|
||||
private boolean migrateSyncthingOptions() {
|
||||
/* Read existing config version */
|
||||
int iConfigVersion = Integer.parseInt(mConfig.getDocumentElement().getAttribute("version"));
|
||||
int iOldConfigVersion = iConfigVersion;
|
||||
|
@ -238,10 +237,10 @@ public class ConfigXml {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set config version to 28 after manual config migration
|
||||
* This prevents "unackedNotificationID" getting populated
|
||||
* with the fsWatcher GUI notification.
|
||||
*/
|
||||
* Set config version to 28 after manual config migration
|
||||
* This prevents "unackedNotificationID" getting populated
|
||||
* with the fsWatcher GUI notification.
|
||||
*/
|
||||
iConfigVersion = 28;
|
||||
}
|
||||
|
||||
|
@ -273,7 +272,6 @@ public class ConfigXml {
|
|||
|
||||
/**
|
||||
* Set device model name as device name for Syncthing.
|
||||
*
|
||||
* 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
|
||||
* 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>
|
||||
|
||||
<!-- 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_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')
|
||||
|
||||
|
||||
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):
|
||||
import os
|
||||
def is_exe(fpath):
|
||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
||||
|
||||
if (sys.platform == 'win32'):
|
||||
program += ".exe"
|
||||
fpath, fname = os.path.split(program)
|
||||
if fpath:
|
||||
if is_exe(program):
|
||||
|
@ -67,26 +63,40 @@ def which(program):
|
|||
|
||||
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():
|
||||
import os
|
||||
import tarfile
|
||||
import zipfile
|
||||
import urllib
|
||||
import hashlib
|
||||
|
||||
# Consts.
|
||||
pwd_path = os.path.dirname(os.path.realpath(__file__))
|
||||
url = 'https://dl.google.com/go/go1.9.7.linux-amd64.tar.gz'
|
||||
expected_shasum = '88573008f4f6233b81f81d8ccf92234b4f67238df0f0ab173d75a302a1f3d6ee'
|
||||
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'
|
||||
expected_shasum = '88573008f4f6233b81f81d8ccf92234b4f67238df0f0ab173d75a302a1f3d6ee'
|
||||
tar_gz_fullfn = pwd_path + os.path.sep + 'go.tgz';
|
||||
|
||||
# Download prebuilt-go.
|
||||
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):
|
||||
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]
|
||||
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:
|
||||
contents = f.read()
|
||||
found_shasum = hashlib.sha256(contents).hexdigest()
|
||||
|
@ -96,11 +106,18 @@ def install_go():
|
|||
print("[ok] Checksum of", tar_gz_fullfn, "matches expected value.")
|
||||
|
||||
# Proceed with extraction of the prebuilt go.
|
||||
# This will go to a subfolder "go" in the current path.
|
||||
print("Extracting prebuilt-go ...")
|
||||
file_name, file_extension = os.path.splitext(url_base_name)
|
||||
tar = tarfile.open(tar_gz_fullfn)
|
||||
tar.extractall(pwd_path)
|
||||
if not os.path.isfile(pwd_path + os.path.sep + 'go' + os.path.sep + 'LICENSE'):
|
||||
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)
|
||||
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.extractall(pwd_path)
|
||||
tar.close()
|
||||
|
||||
# Add (...).tar/go/bin" to the PATH.
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
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:
|
||||
fail('Unsupported python platform %s. Supported platforms: %s', platform.system(),
|
||||
', '.join(SUPPORTED_PYTHON_PLATFORMS))
|
||||
|
@ -125,12 +201,20 @@ go_bin = which("go");
|
|||
if not go_bin:
|
||||
print('Warning: go is not available on the PATH.')
|
||||
install_go();
|
||||
# Retry: Check if go is available.
|
||||
go_bin = which("go");
|
||||
if not go_bin:
|
||||
fail('Error: go is not available on the PATH.')
|
||||
print ('go_bin=\'' + go_bin + '\'')
|
||||
|
||||
# Retry: Check if go is available.
|
||||
go_bin = which("go");
|
||||
if not go_bin:
|
||||
fail('Error: go is not available on the PATH.')
|
||||
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
|
||||
# https://github.com/syncthing/syncthing-android/issues/872
|
||||
|
@ -148,7 +232,7 @@ for target in BUILD_TARGETS:
|
|||
|
||||
if os.environ.get('SYNCTHING_ANDROID_PREBUILT', ''):
|
||||
# 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 = []
|
||||
else:
|
||||
# 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)
|
||||
subprocess.check_call([
|
||||
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',
|
||||
target['arch'],
|
||||
'--api',
|
||||
|
|
|
@ -10,6 +10,7 @@ task buildNative(type: Exec) {
|
|||
*/
|
||||
task cleanNative(type: Delete) {
|
||||
delete "$projectDir/../app/src/main/jniLibs/"
|
||||
delete "android-ndk-r16b"
|
||||
delete "gobuild"
|
||||
delete "go"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue