mirror of
https://github.com/syncthing/syncthing-android.git
synced 2024-11-26 06:11:19 +00:00
* Add Util#isTcpPortListening (fixes #193) * Move model/Config.Gui to model/Gui * Constants: Add default tcp ports * Add string: webui_tcp_port_unavailable * Add ConfigXml#getWebGuiBindPort * Check if webUI tcp port is available before launching native (fixes #193) Output failure notification if the port is allocated by someone else. * Settings UI - Valid tcp ports are from 1024 to 65535 (fixes #211) * Add tcp6 listen port detection, check on connState == LISTEN * Update translations de
This commit is contained in:
parent
1b37db6213
commit
7b82062f54
10 changed files with 108 additions and 35 deletions
|
@ -34,6 +34,7 @@ import com.nutomic.syncthingandroid.R;
|
||||||
import com.nutomic.syncthingandroid.SyncthingApp;
|
import com.nutomic.syncthingandroid.SyncthingApp;
|
||||||
import com.nutomic.syncthingandroid.model.Config;
|
import com.nutomic.syncthingandroid.model.Config;
|
||||||
import com.nutomic.syncthingandroid.model.Device;
|
import com.nutomic.syncthingandroid.model.Device;
|
||||||
|
import com.nutomic.syncthingandroid.model.Gui;
|
||||||
import com.nutomic.syncthingandroid.model.Options;
|
import com.nutomic.syncthingandroid.model.Options;
|
||||||
import com.nutomic.syncthingandroid.service.Constants;
|
import com.nutomic.syncthingandroid.service.Constants;
|
||||||
import com.nutomic.syncthingandroid.service.NotificationHandler;
|
import com.nutomic.syncthingandroid.service.NotificationHandler;
|
||||||
|
@ -169,7 +170,7 @@ public class SettingsActivity extends SyncthingActivity {
|
||||||
private RestApi mRestApi;
|
private RestApi mRestApi;
|
||||||
|
|
||||||
private Options mOptions;
|
private Options mOptions;
|
||||||
private Config.Gui mGui;
|
private Gui mGui;
|
||||||
|
|
||||||
private Boolean mPendingConfig = false;
|
private Boolean mPendingConfig = false;
|
||||||
|
|
||||||
|
@ -488,8 +489,8 @@ public class SettingsActivity extends SyncthingActivity {
|
||||||
webUITcpPort = Integer.parseInt((String) o);
|
webUITcpPort = Integer.parseInt((String) o);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
if (webUITcpPort < 1 || webUITcpPort > 65535) {
|
if (webUITcpPort < 1024 || webUITcpPort > 65535) {
|
||||||
Toast.makeText(getActivity(), getResources().getString(R.string.invalid_port_number, 1, 65535), Toast.LENGTH_LONG)
|
Toast.makeText(getActivity(), getResources().getString(R.string.invalid_port_number, 1024, 65535), Toast.LENGTH_LONG)
|
||||||
.show();
|
.show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,31 +10,4 @@ public class Config {
|
||||||
public Options options;
|
public Options options;
|
||||||
public List<PendingDevice> pendingDevices;
|
public List<PendingDevice> pendingDevices;
|
||||||
public List<RemoteIgnoredDevice> remoteIgnoredDevices;
|
public List<RemoteIgnoredDevice> remoteIgnoredDevices;
|
||||||
|
|
||||||
public class Gui {
|
|
||||||
public boolean enabled;
|
|
||||||
public String address;
|
|
||||||
public String user;
|
|
||||||
public String password;
|
|
||||||
public boolean useTLS;
|
|
||||||
public String apiKey;
|
|
||||||
public boolean insecureAdminAccess;
|
|
||||||
public String theme;
|
|
||||||
|
|
||||||
public String getBindAddress() {
|
|
||||||
if (address == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
String[] split = address.split(":");
|
|
||||||
return split.length < 1 ? "" : split[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBindPort() {
|
|
||||||
if (address == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
String[] split = address.split(":");
|
|
||||||
return split.length < 2 ? "" : split[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.nutomic.syncthingandroid.model;
|
||||||
|
|
||||||
|
public class Gui {
|
||||||
|
public boolean enabled;
|
||||||
|
public String address;
|
||||||
|
public String user;
|
||||||
|
public String password;
|
||||||
|
public boolean useTLS;
|
||||||
|
public String apiKey;
|
||||||
|
public boolean insecureAdminAccess;
|
||||||
|
public String theme;
|
||||||
|
|
||||||
|
public String getBindAddress() {
|
||||||
|
if (address == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String[] split = address.split(":");
|
||||||
|
return split.length < 1 ? "" : split[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBindPort() {
|
||||||
|
if (address == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String[] split = address.split(":");
|
||||||
|
return split.length < 2 ? "" : split[1];
|
||||||
|
}
|
||||||
|
}
|
|
@ -89,6 +89,12 @@ public class Constants {
|
||||||
public static final String FOLDER_TYPE_SEND_RECEIVE = "sendreceive";
|
public static final String FOLDER_TYPE_SEND_RECEIVE = "sendreceive";
|
||||||
public static final String FOLDER_TYPE_RECEIVE_ONLY = "receiveonly";
|
public static final String FOLDER_TYPE_RECEIVE_ONLY = "receiveonly";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default listening ports.
|
||||||
|
*/
|
||||||
|
public static final Integer DEFAULT_WEBGUI_TCP_PORT = 8384;
|
||||||
|
public static final Integer DEFAULT_DATA_TCP_PORT = 22000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On Android 8.1, ACCESS_COARSE_LOCATION is required to access WiFi SSID.
|
* On Android 8.1, ACCESS_COARSE_LOCATION is required to access WiFi SSID.
|
||||||
* This is the request code used when requesting the permission.
|
* This is the request code used when requesting the permission.
|
||||||
|
|
|
@ -32,6 +32,7 @@ import com.nutomic.syncthingandroid.model.Event;
|
||||||
import com.nutomic.syncthingandroid.model.Folder;
|
import com.nutomic.syncthingandroid.model.Folder;
|
||||||
import com.nutomic.syncthingandroid.model.FolderIgnoreList;
|
import com.nutomic.syncthingandroid.model.FolderIgnoreList;
|
||||||
import com.nutomic.syncthingandroid.model.FolderStatus;
|
import com.nutomic.syncthingandroid.model.FolderStatus;
|
||||||
|
import com.nutomic.syncthingandroid.model.Gui;
|
||||||
import com.nutomic.syncthingandroid.model.IgnoredFolder;
|
import com.nutomic.syncthingandroid.model.IgnoredFolder;
|
||||||
import com.nutomic.syncthingandroid.model.Options;
|
import com.nutomic.syncthingandroid.model.Options;
|
||||||
import com.nutomic.syncthingandroid.model.PendingDevice;
|
import com.nutomic.syncthingandroid.model.PendingDevice;
|
||||||
|
@ -573,13 +574,13 @@ public class RestApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Config.Gui getGui() {
|
public Gui getGui() {
|
||||||
synchronized (mConfigLock) {
|
synchronized (mConfigLock) {
|
||||||
return deepCopy(mConfig.gui, Config.Gui.class);
|
return deepCopy(mConfig.gui, Gui.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void editSettings(Config.Gui newGui, Options newOptions) {
|
public void editSettings(Gui newGui, Options newOptions) {
|
||||||
synchronized (mConfigLock) {
|
synchronized (mConfigLock) {
|
||||||
mConfig.gui = newGui;
|
mConfig.gui = newGui;
|
||||||
mConfig.options = newOptions;
|
mConfig.options = newOptions;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import com.nutomic.syncthingandroid.model.Device;
|
||||||
import com.nutomic.syncthingandroid.model.Folder;
|
import com.nutomic.syncthingandroid.model.Folder;
|
||||||
import com.nutomic.syncthingandroid.util.ConfigXml;
|
import com.nutomic.syncthingandroid.util.ConfigXml;
|
||||||
import com.nutomic.syncthingandroid.util.FileUtils;
|
import com.nutomic.syncthingandroid.util.FileUtils;
|
||||||
|
import com.nutomic.syncthingandroid.util.Util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
@ -506,8 +507,7 @@ public class SyncthingService extends Service {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.v(TAG, "Starting syncthing");
|
|
||||||
onServiceStateChange(State.STARTING);
|
|
||||||
mConfig = new ConfigXml(this);
|
mConfig = new ConfigXml(this);
|
||||||
try {
|
try {
|
||||||
mConfig.loadConfig();
|
mConfig.loadConfig();
|
||||||
|
@ -519,6 +519,19 @@ public class SyncthingService extends Service {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the SyncthingNative's configured webgui port is allocated by another app or process. (issue #193)
|
||||||
|
Integer webGuiTcpPort = mConfig.getWebGuiBindPort();
|
||||||
|
Boolean isWebUIPortListening = Util.isTcpPortListening(webGuiTcpPort);
|
||||||
|
if (isWebUIPortListening) {
|
||||||
|
// We shouldn't start SyncthingNative as we would wait forever for life signs on the configured port. (ANR)
|
||||||
|
Log.e(TAG, "launchStartupTask: WebUI tcp port " + Integer.toString(webGuiTcpPort) + " unavailable. Second instance?");
|
||||||
|
mNotificationHandler.showCrashedNotification(R.string.webui_tcp_port_unavailable, Integer.toString(webGuiTcpPort));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v(TAG, "Starting syncthing");
|
||||||
|
onServiceStateChange(State.STARTING);
|
||||||
|
|
||||||
if (mRestApi == null) {
|
if (mRestApi == null) {
|
||||||
mRestApi = new RestApi(this, mConfig.getWebGuiUrl(), mConfig.getApiKey(),
|
mRestApi = new RestApi(this, mConfig.getWebGuiUrl(), mConfig.getApiKey(),
|
||||||
this::onApiAvailable, () -> onServiceStateChange(mCurrentState));
|
this::onApiAvailable, () -> onServiceStateChange(mCurrentState));
|
||||||
|
|
|
@ -10,6 +10,7 @@ import android.util.Log;
|
||||||
import com.nutomic.syncthingandroid.model.Device;
|
import com.nutomic.syncthingandroid.model.Device;
|
||||||
import com.nutomic.syncthingandroid.model.Folder;
|
import com.nutomic.syncthingandroid.model.Folder;
|
||||||
import com.nutomic.syncthingandroid.model.FolderIgnoreList;
|
import com.nutomic.syncthingandroid.model.FolderIgnoreList;
|
||||||
|
import com.nutomic.syncthingandroid.model.Gui;
|
||||||
import com.nutomic.syncthingandroid.R;
|
import com.nutomic.syncthingandroid.R;
|
||||||
import com.nutomic.syncthingandroid.service.Constants;
|
import com.nutomic.syncthingandroid.service.Constants;
|
||||||
import com.nutomic.syncthingandroid.service.SyncthingRunnable;
|
import com.nutomic.syncthingandroid.service.SyncthingRunnable;
|
||||||
|
@ -204,6 +205,17 @@ public class ConfigXml {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getWebGuiBindPort() {
|
||||||
|
try {
|
||||||
|
Gui gui = new Gui();
|
||||||
|
gui.address = getGuiElement().getElementsByTagName("address").item(0).getTextContent();
|
||||||
|
return Integer.parseInt(gui.getBindPort());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, "getWebGuiBindPort: Failed with exception: ", e);
|
||||||
|
return Constants.DEFAULT_WEBGUI_TCP_PORT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String getApiKey() {
|
public String getApiKey() {
|
||||||
return getGuiElement().getElementsByTagName("apikey").item(0).getTextContent();
|
return getGuiElement().getElementsByTagName("apikey").item(0).getTextContent();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
@ -257,6 +258,38 @@ public class Util {
|
||||||
return capturedStdOut;
|
return capturedStdOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a TCP is listening on the local device on a specific port.
|
||||||
|
*/
|
||||||
|
public static Boolean isTcpPortListening(Integer port) {
|
||||||
|
// t: tcp, l: listening, n: numeric
|
||||||
|
String output = runShellCommandGetOutput("netstat -t -l -n", false);
|
||||||
|
if (TextUtils.isEmpty(output)) {
|
||||||
|
Log.w(TAG, "isTcpPortListening: Failed to run netstat. Returning false.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String[] results = output.split("\n");
|
||||||
|
for (String line : results) {
|
||||||
|
if (TextUtils.isEmpty(output)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String[] words = line.split("\\s+");
|
||||||
|
if (words.length > 5) {
|
||||||
|
String protocol = words[0];
|
||||||
|
String localAddress = words[3];
|
||||||
|
String connState = words[5];
|
||||||
|
if (protocol.equals("tcp") || protocol.equals("tcp6")) {
|
||||||
|
if (localAddress.endsWith(":" + Integer.toString(port)) &&
|
||||||
|
connState.equalsIgnoreCase("LISTEN")) {
|
||||||
|
// Port is listening.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make sure that dialog is showing and activity is valid before dismissing dialog, to prevent
|
* Make sure that dialog is showing and activity is valid before dismissing dialog, to prevent
|
||||||
* various crashes.
|
* various crashes.
|
||||||
|
|
|
@ -711,6 +711,9 @@ Bitte melden Sie auftretende Probleme via GitHub.</string>
|
||||||
<!-- Toast shown if a config file crucial to operation is missing -->
|
<!-- Toast shown if a config file crucial to operation is missing -->
|
||||||
<string name="config_file_missing">Eine für den Betrieb wichtige Konfigurationsdatei fehlt</string>
|
<string name="config_file_missing">Eine für den Betrieb wichtige Konfigurationsdatei fehlt</string>
|
||||||
|
|
||||||
|
<!-- Toast shown if a listening tcp port is unavailable -->
|
||||||
|
<string name="webui_tcp_port_unavailable">WebGUI TCP-Port %s belegt. Zweite Instanz?</string>
|
||||||
|
|
||||||
<!-- Label of the default folder created of first start (camera folder). -->
|
<!-- Label of the default folder created of first start (camera folder). -->
|
||||||
<string name="default_folder_label">Kamera</string>
|
<string name="default_folder_label">Kamera</string>
|
||||||
|
|
||||||
|
|
|
@ -720,6 +720,9 @@ Please report any problems you encounter via Github.</string>
|
||||||
<!-- Toast shown if a config file crucial to operation is missing -->
|
<!-- Toast shown if a config file crucial to operation is missing -->
|
||||||
<string name="config_file_missing">A config file crucial to operation is missing</string>
|
<string name="config_file_missing">A config file crucial to operation is missing</string>
|
||||||
|
|
||||||
|
<!-- Toast shown if a listening tcp port is unavailable -->
|
||||||
|
<string name="webui_tcp_port_unavailable">WebUI tcp port %s busy. Second instance?</string>
|
||||||
|
|
||||||
<!-- Label of the default folder created of first start (camera folder). -->
|
<!-- Label of the default folder created of first start (camera folder). -->
|
||||||
<string name="default_folder_label">Camera</string>
|
<string name="default_folder_label">Camera</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue