Skip to content

Commit

Permalink
Refactor ConfigXml (#135)
Browse files Browse the repository at this point in the history
Changelog:
- "Use default folder path given in config.xml" (#101)
- "IllegalStateException: Fragment already added" (#108)
- "Enhancement request for per-folder(device) sync conditions" (#110)
- "NPE crash after key and config regeneration" (#141)
- "Adjust the folder icon to show if it's send/receive or both" (#143)
- "CPU percentage is not shown on the status tab" (#144)
- "Always make individual sync conditions UI available" (#145)
- "IntroducedBy deviceID lost on config change through wrapper UI" (#146)
- "Wrapper doesn't use the same syntax as syncthing core's web UI for device addresses" (#147)
- "Syncthing wrapper "emergency" shutdown on native binary crash doesn't work" (#148)

Commits:

* WIP

* WIP

* Get folder list and paused setting when syncthing is not running

Preparation to solve #110

* Fix NPE in DeviceListFragment#DEVICES_COMPARATOR

* Remove blank line

* Add ConfigXml#getDevices and comparator

Make ConfigXml#saveChanges public

* SyncthingService evaluates per folder/device

sync conditions when syncthing is not running via ConfigXml

* Fix typos and add stubs

* Fix build errors

* DEBUG - Always run syncthing binary

* Fix NPE at RunConditionMonitor pointer

* Add setFolderPause, setDevicePause

to ConfigXml

* Improve logging

* Remove test mode

* Better log levels

* Make ConfigXml#updateIfNeeded private

* Remove SyncthingService#mStartupTask

AsyncTask no longer needed

* Update model/Options (fixes #101)

* Fix NPE after config regeneration (fixes #140)

* Refactor key and config generation

Refactor ConfigXml public functions to allow checking if a valid config exists and trigger key and config (re)genration if something is corrupted.

* Fix crash on export/import (fixes #142)

* ApiRequest - Disable verbose log in release builds

* ConfigXml#updateIfNeeded - Disable "startBrowser"

because it applies to desktop environments and cannot start a mobile browser app

* MainActivity - Always show all tabs

* Show folder/device tab contents from config.xml

if syncthing is not running

* Update ConfigXml#getDevices return model

- compression
- introducer

* Device tab - Hide in/out rate if syncthing is not running

 or if the device is paused

* Update device item layout

* MainActivity/Devices - Prevent showing outdated status

after syncthing core transitioned from "active" to "disabled"

* MainActivity/Folders - Prevent showing outdated status

after syncthing core transitioned from "active" to "disabled"

* Add ConfigRouter class

Provides a transparent access to the config if ...
a) Syncthing is running and REST API is available.
b) Syncthing is NOT running and config.xml is accessed.

* Add pref - Cache local device ID

* Allow excluding self in ConfigRouter#getDevices

* Allow excluding self in ConfigRouter#getDevices (2)

* Update Folder model default values

* Update Folder model defaults (2)

- copiers
- hashers

* WIP - ConfigXml - FolderActivity

Remove unused pref inject code
Cache local device ID in pref
Reduce verbose logging in release builds
Extend ConfigXml#getFolders
Extend ConfigXml#getDevices
Fix ConfigXml#setDevicePause

ToDo ConfigXml#getFolderIgnoreList needs to be implemented

* Implemented ConfigXml#getFolderIgnoreList

* Extend ConfigXml#getDevices

- device.addresses

* WIP - DeviceActivity

Make it available when syncthing is not running

* Fix unsuccessful API bumps while syncthing is starting

* Fix space

* Adjust the folder icon to show if it's send/receive or both (fixes #143)

* Fix lint - item_device_list

* Preserve active tab when syncthing core transitions between running and not running

* Add xmlns:android to item_folder_list

* Remove unused reference from item_folder_list

* Add device icon to device tab

* Fix CPU percentage not showing (fixes #144)

* SyncthingService - Polish iterator code

* Fix MainActivity#updateViewPager (fixes #108)

* Add ConfigXml#updateFolder, updateDevice (1)

* Add ConfigRouter#updateFolder, updateDevice

* Add missing "final" to ConfigXml#updateDevice

* WIP - FolderActivity - Update updateFolder via ConfigRouter

ToDo: Implement ConfigRouter here.

* ConfigRouter - Fix missing return

* DeviceActivity - Update device via ConfigRouter

* Always make individual sync conditions UI available (fixes #145)

regardless if syncthing core is running or not.
Remove SyncthingService dependency from SyncConditionsActivity

* Fix incorrect folder type icon shown

when syncthing core is not running

* Add "introducedBy" to folder and device model (fixes #146)

* Add Folder#getDevices to model

* ConfigXml#updateFolder - Writeback devices sharing the folder

Support preserving the "introducedBy" model field of Folder.java (fixes #146)

* Add ConfigXml#updateFolder - Versioning

* Remove SyncthingService dependency from FolderPickerActivity

because it is no longer required.

* Update ToDo remarks

* Add ConfigXml#updateDevice - Addresses

* Fix DeviceActivity#persistableAddresses to be more graceful (fixes #147)

and accept the same address syntax as syncthing core web UI does.

* Add ConfigXml#removeFolder, removeDevice

* Add ConfigXml#addDevice, addFolder

- Add ConfigXml#isDeviceIdValid
- Do not allow adding empty folder labels or empty device names.
- Update model Folder.java so ConfigXml can handle the ignorePerms XML attribute

* Fix Syncthing wrapper "emergency" shutdown on native binary crash (fixes #148)

* Update translation de

* Add ConfigXml#postFolderIgnoreList

* Update APK version to 0.14.54.3 / 4182

* Revert DEBUG - Always run syncthing binary

* Update whatsnew
  • Loading branch information
Catfriend1 authored Dec 22, 2018
1 parent 7791787 commit 190826e
Show file tree
Hide file tree
Showing 41 changed files with 1,231 additions and 373 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ android {
applicationId "com.github.catfriend1.syncthingandroid"
minSdkVersion 16
targetSdkVersion 26
versionCode 4181
versionName "0.14.54.2"
versionCode 4182
versionName "0.14.54.3"
testApplicationId 'com.github.catfriend1.syncthingandroid.test'
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
playAccountConfig = playAccountConfigs.defaultAccountConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.nutomic.syncthingandroid.service.SyncthingService;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.util.Compression;
import com.nutomic.syncthingandroid.util.ConfigRouter;
import com.nutomic.syncthingandroid.util.TextWatcherAdapter;
import com.nutomic.syncthingandroid.util.Util;

Expand Down Expand Up @@ -68,13 +69,15 @@ public class DeviceActivity extends SyncthingActivity
public static final String EXTRA_IS_CREATE =
"com.nutomic.syncthingandroid.activities.DeviceActivity.IS_CREATE";

private static final String TAG = "DeviceSettingsFragment";
private static final String TAG = "DeviceActivity";
private static final String IS_SHOWING_DISCARD_DIALOG = "DISCARD_FOLDER_DIALOG_STATE";
private static final String IS_SHOWING_COMPRESSION_DIALOG = "COMPRESSION_FOLDER_DIALOG_STATE";
private static final String IS_SHOWING_DELETE_DIALOG = "DELETE_FOLDER_DIALOG_STATE";

private static final List<String> DYNAMIC_ADDRESS = Collections.singletonList("dynamic");

private ConfigRouter mConfig;

private Device mDevice;

private View mIdContainer;
Expand Down Expand Up @@ -186,6 +189,8 @@ public void onCheckedChanged(CompoundButton view, boolean isChecked) {

@Override
public void onCreate(Bundle savedInstanceState) {
mConfig = new ConfigRouter(DeviceActivity.this);

super.onCreate(savedInstanceState);
((SyncthingApp) getApplication()).component().inject(this);
setContentView(R.layout.fragment_device);
Expand Down Expand Up @@ -338,14 +343,9 @@ private void onReceiveConnections(Connections connections) {

@Override
public void onServiceStateChange(SyncthingService.State currentState) {
if (currentState != ACTIVE) {
finish();
return;
}

if (!mIsCreateMode) {
RestApi restApi = getApi(); // restApi != null because of State.ACTIVE
List<Device> devices = restApi.getDevices(false);
RestApi restApi = getApi();
List<Device> devices = mConfig.getDevices(restApi, false);
String passedId = getIntent().getStringExtra(EXTRA_DEVICE_ID);
mDevice = null;
for (Device currentDevice : devices) {
Expand Down Expand Up @@ -427,8 +427,16 @@ public boolean onOptionsItemSelected(MenuItem item) {
.show();
return true;
}
getApi().addDevice(mDevice, error ->
Toast.makeText(this, error, Toast.LENGTH_LONG).show());
if (isEmpty(mDevice.name)) {
Toast.makeText(this, R.string.device_name_required, Toast.LENGTH_LONG)
.show();
return true;
}
mConfig.addDevice(
getApi(),
mDevice,
error -> Toast.makeText(this, error, Toast.LENGTH_LONG).show()
);
finish();
return true;
case R.id.share_device_id:
Expand All @@ -455,7 +463,8 @@ private Dialog createDeleteDialog(){
return new android.app.AlertDialog.Builder(this)
.setMessage(R.string.remove_device_confirm)
.setPositiveButton(android.R.string.yes, (dialogInterface, i) -> {
getApi().removeDevice(mDevice.deviceID);
mConfig.removeDevice(getApi(), mDevice.deviceID);
mDeviceNeedsToUpdate = false;
finish();
})
.setNegativeButton(android.R.string.no, null)
Expand All @@ -474,6 +483,9 @@ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
}
}

/**
* Used in mIsCreateMode.
*/
private void initDevice() {
mDevice = new Device();
mDevice.name = getIntent().getStringExtra(EXTRA_DEVICE_NAME);
Expand All @@ -482,6 +494,7 @@ private void initDevice() {
mDevice.compression = METADATA.getValue(this);
mDevice.introducer = false;
mDevice.paused = false;
mDevice.introducedBy = "";
}

private void prepareEditMode() {
Expand All @@ -501,13 +514,14 @@ private void prepareEditMode() {
*/
private void updateDevice() {
if (mIsCreateMode) {
// If we are about to create this folder, we cannot update via restApi.
// If we are about to create this device, we cannot update via restApi.
return;
}
if (mDevice == null) {
Log.e(TAG, "updateDevice: mDevice == null");
return;
}
// Log.v(TAG, "deviceID=" + mDevice.deviceID + ", introducedBy=" + mDevice.introducedBy);

// Save device specific preferences.
Log.v(TAG, "updateDevice: mDevice.deviceID = \'" + mDevice.deviceID + "\'");
Expand All @@ -518,26 +532,42 @@ private void updateDevice() {
);
editor.apply();

// Update device via restApi and send the config to REST endpoint.
RestApi restApi = getApi();
if (restApi == null) {
Log.e(TAG, "updateDevice: restApi == null");
return;
}
restApi.updateDevice(mDevice);
// Update device using RestApi or ConfigXml.
mConfig.updateDevice(getApi(), mDevice);
}

private List<String> persistableAddresses(CharSequence userInput) {
return isEmpty(userInput)
? DYNAMIC_ADDRESS
: Arrays.asList(userInput.toString().split(" "));
if (isEmpty(userInput)) {
return DYNAMIC_ADDRESS;
}

/**
* Be fault-tolerant here.
* The user can write like this:
* tcp4://192.168.1.67:2222, dynamic
* tcp4://192.168.1.67:2222; dynamic
* tcp4://192.168.1.67:2222,dynamic
* tcp4://192.168.1.67:2222;dynamic
* tcp4://192.168.1.67:2222 dynamic
*/
String input = userInput.toString();
input = input.replace(",", " ");
input = input.replace(";", " ");
input = input.replaceAll("\\s+", ", ");
// Log.v(TAG, "persistableAddresses: Cleaned user input=" + input);

// Split and return the addresses as String[].
return Arrays.asList(input.split(", "));
}

private String displayableAddresses() {
if (mDevice.addresses == null) {
return "";
}
List<String> list = DYNAMIC_ADDRESS.equals(mDevice.addresses)
? DYNAMIC_ADDRESS
: mDevice.addresses;
return TextUtils.join(" ", list);
return TextUtils.join(", ", list);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,29 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((SyncthingApp) getApplication()).component().inject(this);

/**
* Check if a valid config exists that can be read and parsed.
*/
Boolean configParseable = false;
Boolean configExists = Constants.getConfigFile(this).exists();
if (configExists) {
ConfigXml configParseTest = new ConfigXml(this);
try {
configParseTest.loadConfig();
configParseable = true;
} catch (ConfigXml.OpenConfigException e) {
Log.d(TAG, "Failed to parse existing config. Will show key generation slide ...");
}
}

/**
* Check if prerequisites to run the app are still in place.
* If anything mandatory is missing, the according welcome slide(s) will be shown.
*/
Boolean showSlideStoragePermission = !haveStoragePermission();
Boolean showSlideIgnoreDozePermission = !haveIgnoreDozePermission();
Boolean showSlideLocationPermission = !haveLocationPermission();
Boolean showSlideKeyGeneration = !Constants.getConfigFile(this).exists();
Boolean showSlideKeyGeneration = !configExists || !configParseable;

/**
* If we don't have to show slides for mandatory prerequisites,
Expand Down Expand Up @@ -480,8 +495,10 @@ protected Void doInBackground(Void... voids) {
cancel(true);
return null;
}
configXml = new ConfigXml(firstStartActivity);
try {
configXml = new ConfigXml(firstStartActivity);
// Create new secure keys and config.
configXml.generateConfig();
} catch (ExecutableNotFoundException e) {
publishProgress(firstStartActivity.getString(R.string.executable_not_found, e.getMessage()));
cancel(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.nutomic.syncthingandroid.service.RestApi;
import com.nutomic.syncthingandroid.service.SyncthingService;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.util.ConfigRouter;
import com.nutomic.syncthingandroid.util.FileUtils;
import com.nutomic.syncthingandroid.util.TextWatcherAdapter;
import com.nutomic.syncthingandroid.util.Util;
Expand Down Expand Up @@ -85,6 +86,7 @@ public class FolderActivity extends SyncthingActivity
private static final String FOLDER_MARKER_NAME = ".stfolder";
// private static final String IGNORE_FILE_NAME = ".stignore";

private ConfigRouter mConfig;
private Folder mFolder;
// Contains SAF readwrite access URI on API level >= Build.VERSION_CODES.LOLLIPOP (21)
private Uri mFolderUri = null;
Expand Down Expand Up @@ -154,7 +156,7 @@ public void onCheckedChanged(CompoundButton view, boolean isChecked) {
case R.id.device_toggle:
Device device = (Device) view.getTag();
if (isChecked) {
mFolder.addDevice(device.deviceID);
mFolder.addDevice(device);
} else {
mFolder.removeDevice(device.deviceID);
}
Expand All @@ -166,6 +168,8 @@ public void onCheckedChanged(CompoundButton view, boolean isChecked) {

@Override
public void onCreate(Bundle savedInstanceState) {
mConfig = new ConfigRouter(FolderActivity.this);

super.onCreate(savedInstanceState);
((SyncthingApp) getApplication()).component().inject(this);
setContentView(R.layout.fragment_folder);
Expand Down Expand Up @@ -384,14 +388,9 @@ public void onServiceConnected() {

@Override
public void onServiceStateChange(SyncthingService.State currentState) {
if (currentState != ACTIVE) {
finish();
return;
}

if (!mIsCreateMode) {
RestApi restApi = getApi(); // restApi != null because of State.ACTIVE
List<Folder> folders = restApi.getFolders();
RestApi restApi = getApi();
List<Folder> folders = mConfig.getFolders(restApi);
String passedId = getIntent().getStringExtra(EXTRA_FOLDER_ID);
mFolder = null;
for (Folder currentFolder : folders) {
Expand All @@ -405,11 +404,15 @@ public void onServiceStateChange(SyncthingService.State currentState) {
finish();
return;
}
restApi.getFolderIgnoreList(mFolder.id, this::onReceiveFolderIgnoreList);
mConfig.getFolderIgnoreList(restApi, mFolder, this::onReceiveFolderIgnoreList);
checkWriteAndUpdateUI();
}

// If the extra is set, we should automatically share the current folder with the given device.
if (getIntent().hasExtra(EXTRA_DEVICE_ID)) {
mFolder.addDevice(getIntent().getStringExtra(EXTRA_DEVICE_ID));
Device device = new Device();
device.deviceID = getIntent().getStringExtra(EXTRA_DEVICE_ID);
mFolder.addDevice(device);
mFolderNeedsToUpdate = true;
}

Expand Down Expand Up @@ -471,13 +474,14 @@ private void updateViewsAndSetListeners() {
mCustomSyncConditionsDialog.setEnabled(mCustomSyncConditionsSwitch.isChecked());

// Populate devicesList.
List<Device> devicesList = getApi().getDevices(false);
RestApi restApi = getApi();
List<Device> devicesList = mConfig.getDevices(restApi, false);
mDevicesContainer.removeAllViews();
if (devicesList.isEmpty()) {
addEmptyDeviceListView();
} else {
for (Device n : devicesList) {
addDeviceViewAndSetListener(n, getLayoutInflater());
for (Device device : devicesList) {
addDeviceViewAndSetListener(device, getLayoutInflater());
}
}

Expand Down Expand Up @@ -511,6 +515,11 @@ public boolean onOptionsItemSelected(MenuItem item) {
.show();
return true;
}
if (TextUtils.isEmpty(mFolder.label)) {
Toast.makeText(this, R.string.folder_label_required, Toast.LENGTH_LONG)
.show();
return true;
}
if (TextUtils.isEmpty(mFolder.path)) {
Toast.makeText(this, R.string.folder_path_required, Toast.LENGTH_LONG)
.show();
Expand All @@ -531,7 +540,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
dfFolder.createDirectory(FOLDER_MARKER_NAME);
}
}
getApi().createFolder(mFolder);
mConfig.addFolder(getApi(), mFolder);
finish();
return true;
case R.id.remove:
Expand All @@ -554,10 +563,7 @@ private Dialog createDeleteDialog(){
return new AlertDialog.Builder(this)
.setMessage(R.string.remove_folder_confirm)
.setPositiveButton(android.R.string.yes, (dialogInterface, i) -> {
RestApi restApi = getApi();
if (restApi != null) {
restApi.removeFolder(mFolder.id);
}
mConfig.removeFolder(getApi(), mFolder.id);
mFolderNeedsToUpdate = false;
finish();
})
Expand Down Expand Up @@ -731,17 +737,13 @@ private void updateFolder() {

// Update folder via restApi and send the config to REST endpoint.
RestApi restApi = getApi();
if (restApi == null) {
Log.e(TAG, "updateFolder: restApi == null");
return;
}

// Update ignore list.
String[] ignore = mEditIgnoreListContent.getText().toString().split("\n");
restApi.postFolderIgnoreList(mFolder.id, ignore);
mConfig.postFolderIgnoreList(restApi, mFolder, ignore);

// Update model and send the config to REST endpoint.
restApi.updateFolder(mFolder);
// Update folder using RestApi or ConfigXml.
mConfig.updateFolder(restApi, mFolder);
}

@Override
Expand Down
Loading

0 comments on commit 190826e

Please sign in to comment.