1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2024-12-23 11:21:29 +00:00

Migrate to built in fs notifications, add restart on wakeup toggle

* Added UI experimental option to enable/disable FolderObserver

* Disable android watcher by default.

* WIP - dismiss file watcher notification

todo - SyncthingRunnable has to run and migrate the config v27>28 before the file watcher notification can be dismissed.

* fix object type

* remove unackednotifications instead of altering it

* removed extra blank lines

* fix removeChild

* updated syncthing to v0.14.47-rc.2

* WIP - log remove of unackedNotificationID

* WIP - improved config migration 27>28

Prevent generating the unackedNotification fsWatcher in WebGUI by manual bumping the config version to #28.

* fix typo

* WIP - polish version bump code

* syncthing/imsodin-debugAndroidWatch

* update syncthing to v0.14.47-rc.3

* fix NPE on first app start

* fix pref condition

* add ConfigXML support for fsWatcher transition

Added UI experimental option to enable/disable FolderObserver

Disable android watcher by default.

WIP - dismiss file watcher notification

todo - SyncthingRunnable has to run and migrate the config v27>28 before the file watcher notification can be dismissed.

fix object type

remove unackednotifications instead of altering it

removed extra blank lines

fix removeChild

updated syncthing to v0.14.47-rc.2

WIP - log remove of unackedNotificationID

WIP - improved config migration 27>28

Prevent generating the unackedNotification fsWatcher in WebGUI by manual bumping the config version to #28.

fix typo

WIP - polish version bump code

syncthing/imsodin-debugAndroidWatch

update syncthing to v0.14.47-rc.3

fix NPE on first app start

fix pref condition

* updated syncthing to v0.14.47+3d02fcd notify-fix

* updated ConfigXml, FolderObs explanation, st 0.14.47-rc.4

* fixed string escape

* add restartOnWakeup setting to the UI

* restartOnWakeup = false by default on first app start and config generation

* improved logging of restartOnWakeup option to produce meaningful logs

* restartOnWakeup enabled by default
see discussion in https://github.com/syncthing/syncthing-android/issues/368

* PR improved according to review

* fix intendation - Atom Tab Indent = 4

* move configXml migration to separate function

* changeLocalDeviceName only affects "self device" - fixes https://github.com/syncthing/syncthing-android/issues/1059

* revert changeLocalDeviceName fix

* fixed changed result in migrateSyncthingOptions

* improved dismissing unackedNotificationID

* extend folder settings UI by fsWatcherEnabled checkbox

* remove FolderObserver

+ Constant.PREF_USE_TOR according to AudriusButkevicius' review added instead of static strings

* remove folderchange listener

* WIP - temporary - restApi logging

for better understanding whats going on

* prevent removingthe wrong unackedNotificationID

* fix build

* give me more log

* fix PostConfig REST request not working

due to missing fields in the folder model

* remove unused string

toast_folder_observer_stack_overflow using AndroidStudio

* fix typo

* trigger rebuild

* fix UI icon

* Revert to Syncthing to 0.14.46 and cherry-pick

* improved code according to review

* fix translation indent
This commit is contained in:
Catfriend1 2018-04-27 22:24:47 +02:00 committed by Audrius Butkevicius
parent 203dfc753f
commit 299b556923
40 changed files with 176 additions and 230 deletions

View file

@ -77,6 +77,7 @@ public class FolderActivity extends SyncthingActivity
private EditText mIdView;
private TextView mPathView;
private SwitchCompat mFolderMasterView;
private SwitchCompat mFolderFileWatcher;
private ViewGroup mDevicesContainer;
private TextView mVersioningDescriptionView;
private TextView mVersioningTypeView;
@ -109,6 +110,10 @@ public class FolderActivity extends SyncthingActivity
mFolder.type = (isChecked) ? "readonly" : "readwrite";
mFolderNeedsToUpdate = true;
break;
case R.id.fileWatcher:
mFolder.fsWatcherEnabled = isChecked;
mFolderNeedsToUpdate = true;
break;
case R.id.device_toggle:
Device device = (Device) view.getTag();
if (isChecked) {
@ -135,6 +140,7 @@ public class FolderActivity extends SyncthingActivity
mIdView = findViewById(R.id.id);
mPathView = findViewById(R.id.directoryTextView);
mFolderMasterView = findViewById(R.id.master);
mFolderFileWatcher = findViewById(R.id.fileWatcher);
mVersioningDescriptionView = findViewById(R.id.versioningDescription);
mVersioningTypeView = findViewById(R.id.versioningType);
mDevicesContainer = findViewById(R.id.devicesContainer);
@ -309,6 +315,7 @@ public class FolderActivity extends SyncthingActivity
mIdView.removeTextChangedListener(mTextWatcher);
mPathView.removeTextChangedListener(mTextWatcher);
mFolderMasterView.setOnCheckedChangeListener(null);
mFolderFileWatcher.setOnCheckedChangeListener(null);
// Update views
mLabelView.setText(mFolder.label);
@ -316,6 +323,7 @@ public class FolderActivity extends SyncthingActivity
mPathView.setText(mFolder.path);
updateVersioningDescription();
mFolderMasterView.setChecked(Objects.equal(mFolder.type, "readonly"));
mFolderFileWatcher.setChecked(mFolder.fsWatcherEnabled);
List<Device> devicesList = getApi().getDevices(false);
mDevicesContainer.removeAllViews();
@ -332,6 +340,7 @@ public class FolderActivity extends SyncthingActivity
mIdView.addTextChangedListener(mTextWatcher);
mPathView.addTextChangedListener(mTextWatcher);
mFolderMasterView.setOnCheckedChangeListener(mCheckedListener);
mFolderFileWatcher.setOnCheckedChangeListener(mCheckedListener);
}
@Override
@ -423,13 +432,13 @@ public class FolderActivity extends SyncthingActivity
: generateRandomFolderId();
mFolder.label = getIntent().getStringExtra(EXTRA_FOLDER_LABEL);
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.M) {
// Scan every 3 days (in case inotify dropped some changes)
mFolder.rescanIntervalS = 259200;
// Scan every hour (in case real time change detection failed)
mFolder.rescanIntervalS = 3600;
}
else {
// FileObserver is broken on Marshmallow.
// https://github.com/syncthing/syncthing-android/issues/787
mFolder.rescanIntervalS = 60;
mFolder.rescanIntervalS = 300;
}
mFolder.versioning = new Folder.Versioning();
}

View file

@ -102,6 +102,7 @@ public class SettingsActivity extends SyncthingActivity {
private CheckBoxPreference mRelaysEnabled;
private EditTextPreference mGlobalAnnounceServers;
private EditTextPreference mAddress;
private CheckBoxPreference mRestartOnWakeup;
private CheckBoxPreference mUrAccepted;
private Preference mCategoryBackup;
@ -177,6 +178,7 @@ public class SettingsActivity extends SyncthingActivity {
mRelaysEnabled = (CheckBoxPreference) findPreference("relaysEnabled");
mGlobalAnnounceServers = (EditTextPreference) findPreference("globalAnnounceServers");
mAddress = (EditTextPreference) findPreference("address");
mRestartOnWakeup = (CheckBoxPreference) findPreference("restartOnWakeup");
mUrAccepted = (CheckBoxPreference) findPreference("urAccepted");
mCategoryBackup = findPreference("category_backup");
@ -190,7 +192,7 @@ public class SettingsActivity extends SyncthingActivity {
mUseRoot = (CheckBoxPreference) findPreference(Constants.PREF_USE_ROOT);
Preference useWakelock = findPreference(Constants.PREF_USE_WAKE_LOCK);
Preference useTor = findPreference("use_tor");
Preference useTor = findPreference(Constants.PREF_USE_TOR);
mSyncthingVersion = findPreference("syncthing_version");
Preference appVersion = screen.findPreference("app_version");
@ -264,6 +266,7 @@ public class SettingsActivity extends SyncthingActivity {
mRelaysEnabled.setChecked(mOptions.relaysEnabled);
mGlobalAnnounceServers.setText(joiner.join(mOptions.globalAnnounceServers));
mAddress.setText(mGui.address);
mRestartOnWakeup.setChecked(mOptions.restartOnWakeup);
mApi.getSystemInfo(systemInfo ->
mUrAccepted.setChecked(mOptions.isUsageReportingAccepted(systemInfo.urVersionMax)));
}
@ -296,16 +299,33 @@ public class SettingsActivity extends SyncthingActivity {
case "listenAddresses":
mOptions.listenAddresses = Iterables.toArray(splitter.split((String) o), String.class);
break;
case "maxRecvKbps": mOptions.maxRecvKbps = Integer.parseInt((String) o); break;
case "maxSendKbps": mOptions.maxSendKbps = Integer.parseInt((String) o); break;
case "natEnabled": mOptions.natEnabled = (boolean) o; break;
case "localAnnounceEnabled": mOptions.localAnnounceEnabled = (boolean) o; break;
case "globalAnnounceEnabled": mOptions.globalAnnounceEnabled = (boolean) o; break;
case "relaysEnabled": mOptions.relaysEnabled = (boolean) o; break;
case "maxRecvKbps":
mOptions.maxRecvKbps = Integer.parseInt((String) o);
break;
case "maxSendKbps":
mOptions.maxSendKbps = Integer.parseInt((String) o);
break;
case "natEnabled":
mOptions.natEnabled = (boolean) o;
break;
case "localAnnounceEnabled":
mOptions.localAnnounceEnabled = (boolean) o;
break;
case "globalAnnounceEnabled":
mOptions.globalAnnounceEnabled = (boolean) o;
break;
case "relaysEnabled":
mOptions.relaysEnabled = (boolean) o;
break;
case "globalAnnounceServers":
mOptions.globalAnnounceServers = Iterables.toArray(splitter.split((String) o), String.class);
break;
case "address": mGui.address = (String) o; break;
case "address":
mGui.address = (String) o;
break;
case "restartOnWakeup":
mOptions.restartOnWakeup = (boolean) o;
break;
case "urAccepted":
mApi.getSystemInfo(systemInfo -> {
mOptions.urAccepted = ((boolean) o)

View file

@ -15,6 +15,8 @@ public class Folder {
public String label;
public String path;
public String type;
public boolean fsWatcherEnabled = true;
public int fsWatcherDelayS = 10;
private List<Device> devices = new ArrayList<>();
public int rescanIntervalS;
public final boolean ignorePerms = true;

View file

@ -16,6 +16,7 @@ public class Constants {
public static final String PREF_USE_ROOT = "use_root";
public static final String PREF_NOTIFICATION_TYPE = "notification_type";
public static final String PREF_USE_WAKE_LOCK = "wakelock_while_binary_running";
public static final String PREF_USE_TOR = "use_tor";
/**
* On Android 8.1, ACCESS_COARSE_LOCATION is required to access WiFi SSID.

View file

@ -32,7 +32,6 @@ import com.nutomic.syncthingandroid.model.Model;
import com.nutomic.syncthingandroid.model.Options;
import com.nutomic.syncthingandroid.model.SystemInfo;
import com.nutomic.syncthingandroid.model.SystemVersion;
import com.nutomic.syncthingandroid.util.FolderObserver;
import java.lang.reflect.Type;
import java.net.URL;
@ -49,8 +48,7 @@ import javax.inject.Inject;
/**
* Provides functions to interact with the syncthing REST API.
*/
public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
FolderObserver.OnFolderFileChangeListener {
public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
private static final String TAG = "RestApi";
@ -145,6 +143,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
tryIsAvailable();
});
new GetRequest(mContext, mUrl, GetRequest.URI_CONFIG, mApiKey, null, result -> {
Log.v(TAG, "onWebGuiAvailable: " + result);
mConfig = new Gson().fromJson(result, Config.class);
if (mConfig == null) {
throw new RuntimeException("config is null: " + result);
@ -485,14 +484,6 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
});
}
/**
* Force a rescan of the given subdirectory in folder.
*/
@Override
public void onFolderFileChange(String folderId, String relativePath) {
new PostScanRequest(mContext, mUrl, mApiKey, folderId, relativePath);
}
/**
* Returns prettyfied usage report.
*/

View file

@ -71,6 +71,8 @@ public class SyncthingRunnable implements Runnable {
mContext = context;
mSyncthingBinary = Constants.getSyncthingBinary(mContext);
mLogFile = Constants.getLogFile(mContext);
// Get preferences relevant to starting syncthing core.
mUseRoot = mPreferences.getBoolean(Constants.PREF_USE_ROOT, false) && Shell.SU.available();
switch (command) {
case generate:
@ -371,7 +373,7 @@ public class SyncthingRunnable implements Runnable {
// Disable hash benchmark for faster startup.
// https://github.com/syncthing/syncthing/issues/4348
targetEnv.put("STHASHING", "minio");
if (mPreferences.getBoolean("use_tor", false)) {
if (mPreferences.getBoolean(Constants.PREF_USE_TOR, false)) {
targetEnv.put("all_proxy", "socks5://localhost:9050");
targetEnv.put("ALL_PROXY_NO_FALLBACK", "1");
}

View file

@ -18,7 +18,6 @@ import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.http.PollWebGuiAvailableTask;
import com.nutomic.syncthingandroid.model.Folder;
import com.nutomic.syncthingandroid.util.ConfigXml;
import com.nutomic.syncthingandroid.util.FolderObserver;
import java.io.File;
import java.io.IOException;
@ -95,7 +94,6 @@ public class SyncthingService extends Service {
private DeviceStateHolder mDeviceStateHolder;
private SyncthingRunnable mSyncthingRunnable;
private final LinkedList<FolderObserver> mObservers = new LinkedList<>();
private final HashSet<OnApiChangeListener> mOnApiChangeListeners = new HashSet<>();
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this);
@ -232,22 +230,7 @@ public class SyncthingService extends Service {
private void onSyncthingStarted() {
onApiChange(State.ACTIVE);
Handler handler = new Handler();
new Thread(() -> {
for (Folder r : mApi.getFolders()) {
try {
mObservers.add(new FolderObserver(mApi, r, handler));
} catch (FolderObserver.FolderNotExistingException e) {
Log.w(TAG, "Failed to add observer for folder", e);
} catch (StackOverflowError e) {
Log.w(TAG, "Failed to add folder observer", e);
Toast.makeText(SyncthingService.this,
R.string.toast_folder_observer_stack_overflow,
Toast.LENGTH_LONG)
.show();
}
}
}).start();
Log.i(TAG, "onSyncthingStarted(): State.ACTIVE reached.");
}
@Override
@ -295,9 +278,6 @@ public class SyncthingService extends Service {
mNotificationHandler.cancelPersistentNotification(this);
Stream.of(mObservers).forEach(FolderObserver::stopWatching);
mObservers.clear();
if (mSyncthingRunnable != null) {
mSyncthingRunnable.killSyncthing(onKilledListener);
mSyncthingRunnable = null;

View file

@ -1,8 +1,10 @@
package com.nutomic.syncthingandroid.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
@ -23,6 +25,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.Locale;
import javax.inject.Inject;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@ -45,6 +48,7 @@ public class ConfigXml {
private static final String TAG = "ConfigXml";
private final Context mContext;
@Inject SharedPreferences mPreferences;
private final File mConfigFile;
@ -62,8 +66,18 @@ public class ConfigXml {
readConfig();
if (isFirstStart) {
boolean changed = false;
/* Synthing devices */
changeLocalDeviceName();
changeDefaultFolder();
/* Syncthing folders */
changed = changeDefaultFolder() || changed;
// Save changes if we made any.
if (changed) {
saveChanges();
}
}
}
@ -110,9 +124,15 @@ public class ConfigXml {
*/
@SuppressWarnings("SdCardPath")
public void updateIfNeeded() {
Log.i(TAG, "Checking for needed config updates");
boolean changed = false;
/* Perform one-time migration tasks on syncthing's config file when coming from an older config version. */
changed = migrateSyncthingOptions() || changed;
/* Get refs to important config objects */
NodeList folders = mConfig.getDocumentElement().getElementsByTagName("folder");
/* Section - folders */
for (int i = 0; i < folders.getLength(); i++) {
Element r = (Element) folders.item(i);
// Set ignorePerms attribute.
@ -128,6 +148,7 @@ public class ConfigXml {
changed = setConfigElement(r, "hashers", "1") || changed;
}
/* Section - GUI */
// Enforce TLS.
Element gui = getGuiElement();
changed = setConfigElement(gui, "tls", "true") || changed;
@ -156,17 +177,79 @@ public class ConfigXml {
changed = true;
}
/* Section - options */
// Disable weak hash benchmark for faster startup.
// https://github.com/syncthing/syncthing/issues/4348
Element options = (Element) mConfig.getDocumentElement()
.getElementsByTagName("options").item(0);
changed = setConfigElement(options, "weakHashSelectionMethod", "never") || changed;
/* Dismiss "fsWatcherNotification" according to https://github.com/syncthing/syncthing-android/pull/1051 */
NodeList childNodes = options.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if (node.getNodeName().equals("unackedNotificationID")) {
if (node.equals("fsWatcherNotification")) {
Log.i(TAG, "Remove found unackedNotificationID 'fsWatcherNotification'.");
options.removeChild(node);
changed = true;
break;
}
}
}
// Save changes if we made any.
if (changed) {
saveChanges();
}
}
/**
* 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 () {
/* Read existing config version */
int iConfigVersion = Integer.parseInt(mConfig.getDocumentElement().getAttribute("version"));
int iOldConfigVersion = iConfigVersion;
Log.i(TAG, "Found existing config version " + Integer.toString(iConfigVersion));
/* Check if we have to do manual migration from version X to Y */
if (iConfigVersion == 27) {
/* fsWatcher transition - https://github.com/syncthing/syncthing/issues/4882 */
Log.i(TAG, "Migrating config version " + Integer.toString(iConfigVersion) + " to 28 ...");
/* Enable fsWatcher for all folders */
NodeList folders = mConfig.getDocumentElement().getElementsByTagName("folder");
for (int i = 0; i < folders.getLength(); i++) {
Element r = (Element) folders.item(i);
// Enable "fsWatcherEnabled" attribute and set default delay.
Log.i(TAG, "Set 'fsWatcherEnabled', 'fsWatcherDelayS' on folder " + r.getAttribute("id"));
r.setAttribute("fsWatcherEnabled", "true");
r.setAttribute("fsWatcherDelayS", "10");
}
/**
* Set config version to 28 after manual config migration
* This prevents "unackedNotificationID" getting populated
* with the fsWatcher GUI notification.
*/
iConfigVersion = 28;
}
if (iConfigVersion != iOldConfigVersion) {
mConfig.getDocumentElement().setAttribute("version", Integer.toString(iConfigVersion));
Log.i(TAG, "New config version is " + Integer.toString(iConfigVersion));
return true;
} else {
return false;
}
}
private boolean setConfigElement(Element parent, String tagName, String textContent) {
Node element = parent.getElementsByTagName(tagName).item(0);
if (element == null) {
@ -203,8 +286,9 @@ public class ConfigXml {
/**
* Change default folder id to camera and path to camera folder path.
* Returns if changes to the config have been made.
*/
private void changeDefaultFolder() {
private boolean changeDefaultFolder() {
Element folder = (Element) mConfig.getDocumentElement()
.getElementsByTagName("folder").item(0);
String model = Build.MODEL
@ -216,7 +300,9 @@ public class ConfigXml {
folder.setAttribute("path", Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath());
folder.setAttribute("type", "readonly");
saveChanges();
folder.setAttribute("fsWatcherEnabled", "true");
folder.setAttribute("fsWatcherDelayS", "10");
return true;
}
/**

View file

@ -1,141 +0,0 @@
package com.nutomic.syncthingandroid.util;
import android.os.FileObserver;
import android.os.Handler;
import android.util.Log;
import com.annimon.stream.Stream;
import com.nutomic.syncthingandroid.model.Folder;
import java.io.File;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
/**
* Recursively watches a directory and all subfolders.
*/
public class FolderObserver extends FileObserver {
private static final String TAG = "FolderObserver";
private static final long SCAN_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(10);
private final OnFolderFileChangeListener mListener;
private final Folder mFolder;
private final String mPath;
private final ArrayList<FolderObserver> mChilds = new ArrayList<>();
private final Handler mHandler;
public interface OnFolderFileChangeListener {
void onFolderFileChange(String folderId, String relativePath);
}
public FolderObserver(OnFolderFileChangeListener listener, Folder folder, Handler mainHandler)
throws FolderNotExistingException {
this(listener, folder, "", mainHandler);
Log.i(TAG, "Observer created for (folder " + folder.id + ")");
}
public class FolderNotExistingException extends Exception {
private final String mPath;
public FolderNotExistingException(String path) {
mPath = path;
}
@Override
public String getMessage() {
return "path " + mPath + " does not exist, aborting file observer";
}
}
/**
* Constructs watcher and starts watching the given directory recursively.
*
* @param listener The listener where changes should be sent to.
* @param folder The folder where this folder belongs to.
* @param path path to the monitored folder, relative to folder root.
*/
private FolderObserver(OnFolderFileChangeListener listener, Folder folder, String path, Handler handler)
throws FolderNotExistingException {
super(folder.path + "/" + path,
ATTRIB | CLOSE_WRITE | CREATE | DELETE | DELETE_SELF | MOVED_FROM |
MOVED_TO | MOVE_SELF);
mListener = listener;
mFolder = folder;
mPath = path;
mHandler = handler;
Log.v(TAG, "Observer created for " + new File(mFolder.path, mPath).toString() + " (folder " + folder.id + ")");
startWatching();
File currentFolder = new File(folder.path, path);
if (!currentFolder.exists()) {
throw new FolderNotExistingException(currentFolder.getAbsolutePath());
}
File[] directories = currentFolder.listFiles((current, name) -> new File(current, name).isDirectory());
if (directories != null) {
for (File f : directories) {
mChilds.add(new FolderObserver(mListener, mFolder, path + "/" + f.getName(), mHandler));
}
}
}
/**
* Handles incoming events for changed files.
*/
@Override
public void onEvent(int event, String path) {
// Ignore some weird events that we may receive.
event &= FileObserver.ALL_EVENTS;
if (event == 0)
return;
File fullPath = (path != null)
? new File(mPath, path)
: new File(mPath);
Log.v(TAG, "Received inotify event " + Integer.toHexString(event) + " at " +
fullPath.getAbsolutePath());
switch (event) {
case MOVED_FROM:
// fall through
case DELETE_SELF:
// fall through
case DELETE:
for (FolderObserver c : mChilds) {
if (c.mPath.equals(path)) {
mChilds.remove(c);
break;
}
}
mHandler.postDelayed(() -> mListener.onFolderFileChange(mFolder.id, fullPath.getPath()),
SCAN_DELAY_MILLIS);
break;
case MOVED_TO:
// fall through
case CREATE:
if (fullPath.isDirectory()) {
try {
mChilds.add(new FolderObserver(mListener, mFolder, path, mHandler));
} catch (FolderNotExistingException e) {
Log.w(TAG, "Failed to add listener for nonexisting folder", e);
}
}
// fall through
default:
mHandler.postDelayed(() -> mListener.onFolderFileChange(mFolder.id, fullPath.getPath()),
SCAN_DELAY_MILLIS);
}
}
/**
* Recursively stops watching the directory.
*/
@Override
public void stopWatching() {
super.stopWatching();
Stream.of(mChilds).forEach(FolderObserver::stopWatching);
}
}

View file

@ -80,6 +80,16 @@
android:drawableStart="@drawable/ic_lock_black_24dp_active"
android:text="@string/folder_master" />
<android.support.v7.widget.SwitchCompat
android:id="@+id/fileWatcher"
style="@style/Widget.Syncthing.TextView.Label.Details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:drawableLeft="@drawable/ic_autorenew_black_24dp"
android:drawableStart="@drawable/ic_autorenew_black_24dp"
android:text="@string/folder_fileWatcher" />
<LinearLayout
android:id="@+id/versioningContainer"
android:layout_width="match_parent"

View file

@ -256,7 +256,6 @@
<string name="syncthing_active">Syncthing работи</string>
<string name="syncthing_disabled">Syncthing не работи</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Твърде много папки. Проверяване за cyclic symlinks</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<string name="default_folder_label">Камера</string>

View file

@ -318,7 +318,6 @@ Ens podeu informar dels problemes que trobeu a través de Github.</string>
<string name="syncthing_active">El Syncthing s\'està executant</string>
<string name="syncthing_disabled">El Syncthing està desactivat</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">L\'arbre de directoris és massa profund. Comproveu que no hi hagi enllaços simbòlics cíclics</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">No s\'ha pogut crear el fitxer de configuració</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -321,7 +321,6 @@ Všechny zaznamenané chyby prosím hlašte přes Github.</string>
<string name="syncthing_active">Syncthing běží</string>
<string name="syncthing_disabled">Syncthing je vypnutý</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Adresářová struktura je moc hluboká. Zkontrolujte zacyklené symlinky.</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">Vytváření konfiguračního souboru selhalo</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -313,7 +313,6 @@ Vær venlig at rapportere ethvert problem, du støder på, via Github. </string>
<string name="syncthing_active">Syncthing kører</string>
<string name="syncthing_disabled">Syncthing er slået fra</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Katalog-tree for dybt. Check for cyclic symlinks</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<string name="default_folder_label">Kamera</string>

View file

@ -317,7 +317,6 @@ Bitte melden Sie auftretende Probleme via Github.</string>
<string name="syncthing_active">Syncthing läuft</string>
<string name="syncthing_disabled">Syncthing ist deaktiviert</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Verzeichnisbaum zu verschachtelt. Auf zyklische symlinks prüfen</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">Erstellen der Konfigurationsdatei fehlgeschalgen</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -318,7 +318,6 @@
<string name="syncthing_active">Το Syncthing εκτελείται</string>
<string name="syncthing_disabled">Το Syncthing είναι απενεργοποιημένο</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Η ιεραρχία των φακέλων έχει υπερβολικά μεγάλο βάθος. Ελέγξτε αν υπάρχουν κυκλικοί συμβολικοί σύνδεσμοι</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">Αδυναμία δημιουργίας αρχείου ρυθμίσεων</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -236,7 +236,6 @@
<string name="syncthing_active">Syncthing está en ejecución</string>
<string name="syncthing_disabled">Syncthing está deshabilitado</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Árbol de carpetas muy profundo. Revise por enlaces simbólicos cíclicos.</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<!--ID of the default folder created on first start (camera folder). Must only contain 'a-z0-9_-'. Parameter is the device name-->

View file

@ -289,7 +289,6 @@
<string name="syncthing_active">Syncthing se está ejecutando</string>
<string name="syncthing_disabled">Syncthing está deshabilitado</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Árbol de directorios demasiado profundo. Comprueba enlaces simbólicos cíclicos</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<string name="default_folder_label">Cámara</string>

View file

@ -278,7 +278,6 @@ Ilmoitathan ystävällisesti kaikista havaitsemistasi ongelmista Githubin kautta
<string name="syncthing_active">Syncthing on käynnissä</string>
<string name="syncthing_disabled">Syncthing on poistettu käytöstä</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Kansiopolku on liian syvä. Tarkista toistuvat symlinkit.</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<string name="default_folder_label">Kamera</string>

View file

@ -318,7 +318,6 @@ S\'il vous plaît, soumettez les problèmes que vous rencontrez via Github.</str
<string name="syncthing_active">Syncthing fonctionne</string>
<string name="syncthing_disabled">Syncthing est désactivé</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Arborescence des répertoires trop profonde. Vérifiez les liens symboliques cycliques.</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">Echec de création du fichier de configuration</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -326,7 +326,6 @@ Biztosan törölni szeretnéd a Syncthing index adatbázisát?</string>
<string name="syncthing_active">A Syncthing fut</string>
<string name="syncthing_disabled">A Syncthing le van tiltva</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">A mappastruktúra túl mély. Ellenőrizd, nincsenek-e körkörös symlinkek.</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">Nem sikerült létrehozni a konfigurációs fájlt</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -318,7 +318,6 @@ Si prega di segnalare eventuali problemi che si incontrano via Github.</string>
<string name="syncthing_active">Syncthing è in esecuzione</string>
<string name="syncthing_disabled">Syncthing è disabilitato</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Albero delle cartelle troppo nidificato. Verificare la presenza di link simbolici ciclici</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">Impossibile creare il file di configurazione</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -313,7 +313,6 @@
<string name="syncthing_active">Syncthing は実行中です</string>
<string name="syncthing_disabled">Syncthing は無効になりました</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">ディレクトリーの階層が深すぎます。シンボリックリンクの循環を確認してください</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">設定ファイルの作成に失敗しました</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -309,7 +309,6 @@
<string name="syncthing_active">Syncthing이 작동 중입니다</string>
<string name="syncthing_disabled">Syncthing이 비활성화되었습니다</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">폴더의 계층이 너무 깊습니다. 심볼릭 링크의 순환을 확인해주세요.</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<string name="default_folder_label">카메라</string>

View file

@ -240,7 +240,6 @@
<string name="syncthing_active">Syncthing kjører</string>
<string name="syncthing_disabled">Syncthing er deaktivert</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Katalogtreet er for dypt. Se etter sykliske symlenker.</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<string name="default_folder_label">Kamera</string>

View file

@ -317,7 +317,6 @@ Als je problemen tegenkomt, meld ze dan via GitHub.</string>
<string name="syncthing_active">Syncthing draait</string>
<string name="syncthing_disabled">Syncthing is uitgeschakeld</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Mapstructuur te diep. Controleer op cyclische symlinks</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">Aanmaken van configuratiebestand mislukt</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -240,7 +240,6 @@
<string name="syncthing_active">Syncthing køyrer</string>
<string name="syncthing_disabled">Syncthing er ikkje aktivert</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Mappetreet er for djupt. Sjekk om det finst sykliske symbolske lenkjer</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<string name="default_folder_label">Kamera</string>

View file

@ -270,7 +270,6 @@ Proszę zgłaszać napotkane błędy programu za pośrednictwem serwisu Github.<
<string name="syncthing_active">Syncthing jest uruchomiony</string>
<string name="syncthing_disabled">Syncthing jest wyłączony</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Drzewo katalogów jest zbyt głębokie. Proszę je sprawdzić pod kątem obecności zapętlonych dowiązań symbolicznych.</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<string name="default_folder_label">Kamera</string>

View file

@ -314,7 +314,6 @@ Por favor, nos avise sobre quaisquer problemas que você encontrar via Github.</
<string name="syncthing_active">O Syncthing está rodando</string>
<string name="syncthing_disabled">O Syncthing está desabilitado</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Árvore de diretórios profunda demais. Verifique links simbólicos cíclicos.</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">Não foi possível criar o arquivo de configuração</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -262,7 +262,6 @@ Reporte, através do Github, quaisquer problemas que encontre, por favor.</strin
<string name="syncthing_active">O Syncthing está a correr</string>
<string name="syncthing_disabled">O Syncthing está desactivado</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Árvore de pastas demasiado profunda. Verifique se existem ligações simbólicas cíclicas</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<string name="default_folder_label">Câmera</string>

View file

@ -321,7 +321,6 @@ Vă rugăm să raportați orice problemă întâlniți, prin intermediul GitHub.
<string name="syncthing_active">Syncthing rulează</string>
<string name="syncthing_disabled">Syncthing este dezactivat</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Structura directorului este prea complexa. Verificați existența legăturilor simbolice</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">Creerea fișierului de configurare a eșuat</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -274,7 +274,6 @@
<string name="syncthing_active">Syncthing запущен</string>
<string name="syncthing_disabled">Syncthing выключен</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Слишком глубокая папка. Проверьте на цикличные симлинки</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<string name="default_folder_label">Камера</string>

View file

@ -222,7 +222,6 @@ Naozaj chcete resetovať databázu s indexom súborov?</string>
<string name="syncthing_active">Syncthing beží</string>
<string name="syncthing_disabled">Syncthing je zakázaný</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Strom adresárov je príliš hlboký. Skontrolujte, či neobsahuje zacyklené symlinky.</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<!--ID of the default folder created on first start (camera folder). Must only contain 'a-z0-9_-'. Parameter is the device name-->

View file

@ -317,7 +317,6 @@ Vänligen rapportera eventuella problem du stöter på via Github.</string>
<string name="syncthing_active">Syncthing körs</string>
<string name="syncthing_disabled">Syncthing är inaktiverad</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Katalogträdet är för djupt. Kontrollera efter cyclic symlinks</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">Misslyckades med att skapa konfigurationsfil</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -261,7 +261,6 @@ Eğer herhangi bir sorunla karşılaşırsan Github aracılığıyla bildir.</st
<string name="syncthing_active">Syncthing çalışıyor</string>
<string name="syncthing_disabled">Syncthing devre dışı</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Dizin ağacı çok derin. Döngüsel simgesel bağlantıların varlığını araştır</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<string name="default_folder_label">Kamera</string>

View file

@ -236,7 +236,6 @@
<string name="syncthing_active">Syncthing đang chạy</string>
<string name="syncthing_disabled">Đã tắt Syncthing</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">Cây th.mục quá sâu. Hãy k.tra các l.kết biểu trưng trùng lặp</string>
<!--Toast shown if syncthing failed to create a config-->
<!--Label of the default folder created of first start (camera folder).-->
<!--ID of the default folder created on first start (camera folder). Must only contain 'a-z0-9_-'. Parameter is the device name-->

View file

@ -315,7 +315,6 @@
<string name="syncthing_active">Syncthing 正在运行</string>
<string name="syncthing_disabled">Syncthing 已禁用</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">目录树层次过深,正在检查循环符号链接。</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">创建配置文件失败</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -314,7 +314,6 @@
<string name="syncthing_active">Syncthing 正在執行</string>
<string name="syncthing_disabled">Syncthing 已經停用</string>
<!--Toast shown if folder observer fails to traverse a folder-->
<string name="toast_folder_observer_stack_overflow">太深的樹狀資料夾。檢查是否有循環的連結。</string>
<!--Toast shown if syncthing failed to create a config-->
<string name="config_create_failed">無法建立設定檔</string>
<!--Label of the default folder created of first start (camera folder).-->

View file

@ -141,6 +141,9 @@ Please report any problems you encounter via Github.</string>
<!-- Setting title -->
<string name="folder_master">Send Only</string>
<!-- Setting title -->
<string name="folder_fileWatcher">Realtime Sync</string>
<!-- Setting title -->
<string name="devices">Devices</string>
@ -344,6 +347,14 @@ Please report any problems you encounter via Github.</string>
<string name="use_legacy_hashing_summary">Force Syncthing to use legacy hashing package for compatibility purposes</string>
<string name="use_folder_observer_title">Use deprecated change detection</string>
<string name="use_folder_observer_summary">Default: Disabled. Use deprecated android implementation to detect filesystem changes instead of SyncThing\'s new built-in feature.</string>
<string name="restart_on_wakeup_title">Restart on Wakeup</string>
<string name="restart_on_wakeup_summary">Default: Enabled. Disabling this feature may result in folder scans and device reconnects being delayed to save battery.</string>
<!-- Dialog shown before config export -->
<string name="dialog_confirm_export">Do you really want to export your configuration? Existing files will be overwritten.\n\nWARNING! Other applications may be able to read the private key from the backup location and use it to download/modify synchronized files.</string>
@ -536,9 +547,6 @@ Please report any problems you encounter via Github.</string>
<string name="syncthing_disabled">Syncthing is disabled</string>
<!-- Toast shown if folder observer fails to traverse a folder -->
<string name="toast_folder_observer_stack_overflow">Directory tree too deep. Check for cyclic symlinks</string>
<!-- Toast shown if syncthing failed to create a config -->
<string name="config_create_failed">Failed to create config file</string>

View file

@ -125,6 +125,12 @@
android:persistent="false"
android:inputType="textNoSuggestions" />
<CheckBoxPreference
android:key="restartOnWakeup"
android:title="@string/restart_on_wakeup_title"
android:summary="@string/restart_on_wakeup_summary"
android:defaultValue="false" />
<CheckBoxPreference
android:key="urAccepted"
android:title="@string/usage_reporting"