mirror of
https://github.com/syncthing/syncthing-android.git
synced 2025-01-23 10:25:54 +00:00
Device create/edit dialog: Check address input, fix losing changes on screen rotation (#270)
* Add comments * Add model/Device#checkDeviceAddresses * Add checkDeviceAddresses to device create dialog * FolderActivity: Consolidate createDiscardDialog into showDiscardDialog * folder_settings: R.id.create => R.id.save * device_settings: R.id.create => R.id.save * Edit device dialog: Offer back+discard and save action * Add deviceNeedsToUpdate to savedInstanceState (fixes #271) Remove dependency SyncthingService.OnServiceStateChangeListener * Fix folder settings validation doesn't take place when editing folder (fixes #273) (fixes #265) * Conditional label of the create/save button * Remove workaround for rotation: mVersioning * Fix folder.type is reset from SendOnly to Send&Receive on recreation of FolderActivity while creating a new folder (fixes #274) * Fix typo * Fix typo * Fix typo * Review - Relocate code * Move null checks to the beginning of onSave * Updated de translation * Add ListenAddressesChanged to EventProcessor as unhandled event.
This commit is contained in:
parent
b2864cc3c5
commit
56455fc89d
8 changed files with 327 additions and 243 deletions
|
@ -53,9 +53,7 @@ import static com.nutomic.syncthingandroid.util.Compression.METADATA;
|
||||||
/**
|
/**
|
||||||
* Shows device details and allows changing them.
|
* Shows device details and allows changing them.
|
||||||
*/
|
*/
|
||||||
public class DeviceActivity extends SyncthingActivity
|
public class DeviceActivity extends SyncthingActivity {
|
||||||
implements
|
|
||||||
SyncthingService.OnServiceStateChangeListener {
|
|
||||||
|
|
||||||
public static final String EXTRA_NOTIFICATION_ID =
|
public static final String EXTRA_NOTIFICATION_ID =
|
||||||
"com.github.catfriend1.syncthingandroid.activities.DeviceActivity.NOTIFICATION_ID";
|
"com.github.catfriend1.syncthingandroid.activities.DeviceActivity.NOTIFICATION_ID";
|
||||||
|
@ -207,19 +205,47 @@ public class DeviceActivity extends SyncthingActivity
|
||||||
mCompressionContainer.setOnClickListener(view -> onCompressionContainerClick());
|
mCompressionContainer.setOnClickListener(view -> onCompressionContainerClick());
|
||||||
mCustomSyncConditionsDialog.setOnClickListener(view -> onCustomSyncConditionsDialogClick());
|
mCustomSyncConditionsDialog.setOnClickListener(view -> onCustomSyncConditionsDialogClick());
|
||||||
|
|
||||||
if (savedInstanceState != null){
|
|
||||||
if (mDevice == null) {
|
|
||||||
mDevice = new Gson().fromJson(savedInstanceState.getString("device"), Device.class);
|
|
||||||
}
|
|
||||||
restoreDialogStates(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
findViewById(R.id.editDeviceIdContainer).setVisibility(mIsCreateMode ? View.VISIBLE : View.GONE);
|
findViewById(R.id.editDeviceIdContainer).setVisibility(mIsCreateMode ? View.VISIBLE : View.GONE);
|
||||||
mShowDeviceIdContainer.setVisibility(!mIsCreateMode ? View.VISIBLE : View.GONE);
|
mShowDeviceIdContainer.setVisibility(!mIsCreateMode ? View.VISIBLE : View.GONE);
|
||||||
if (mIsCreateMode) {
|
|
||||||
if (mDevice == null) {
|
if (savedInstanceState != null) {
|
||||||
|
Log.d(TAG, "Retrieving state from savedInstanceState ...");
|
||||||
|
mDevice = new Gson().fromJson(savedInstanceState.getString("device"), Device.class);
|
||||||
|
mDeviceNeedsToUpdate = savedInstanceState.getBoolean("deviceNeedsToUpdate");
|
||||||
|
restoreDialogStates(savedInstanceState);
|
||||||
|
} else {
|
||||||
|
// Fresh init of the edit or create mode.
|
||||||
|
if (mIsCreateMode) {
|
||||||
|
Log.d(TAG, "Initializing create mode ...");
|
||||||
initDevice();
|
initDevice();
|
||||||
|
mDeviceNeedsToUpdate = true;
|
||||||
|
} else {
|
||||||
|
// Edit mode.
|
||||||
|
String passedId = getIntent().getStringExtra(EXTRA_DEVICE_ID);
|
||||||
|
Log.d(TAG, "Initializing edit mode: deviceID=" + passedId);
|
||||||
|
RestApi restApi = getApi();
|
||||||
|
List<Device> devices = mConfig.getDevices(restApi, false);
|
||||||
|
mDevice = null;
|
||||||
|
for (Device currentDevice : devices) {
|
||||||
|
if (currentDevice.deviceID.equals(passedId)) {
|
||||||
|
mDevice = currentDevice;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mDevice == null) {
|
||||||
|
Log.w(TAG, "Device not found in API update, maybe it was deleted?");
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (restApi != null) {
|
||||||
|
restApi.getConnections(this::onReceiveConnections);
|
||||||
|
}
|
||||||
|
mDeviceNeedsToUpdate = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
updateViewsAndSetListeners();
|
||||||
|
|
||||||
|
if (mIsCreateMode) {
|
||||||
mEditDeviceId.requestFocus();
|
mEditDeviceId.requestFocus();
|
||||||
} else {
|
} else {
|
||||||
getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||||
|
@ -236,10 +262,8 @@ public class DeviceActivity extends SyncthingActivity
|
||||||
showDeleteDialog();
|
showDeleteDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mIsCreateMode){
|
if (savedInstanceState.getBoolean(IS_SHOWING_DISCARD_DIALOG)){
|
||||||
if (savedInstanceState.getBoolean(IS_SHOWING_DISCARD_DIALOG)){
|
showDiscardDialog();
|
||||||
showDiscardDialog();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,37 +276,11 @@ public class DeviceActivity extends SyncthingActivity
|
||||||
SyncthingServiceBinder syncthingServiceBinder = (SyncthingServiceBinder) iBinder;
|
SyncthingServiceBinder syncthingServiceBinder = (SyncthingServiceBinder) iBinder;
|
||||||
SyncthingService syncthingService = (SyncthingService) syncthingServiceBinder.getService();
|
SyncthingService syncthingService = (SyncthingService) syncthingServiceBinder.getService();
|
||||||
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
||||||
syncthingService.registerOnServiceStateChangeListener(DeviceActivity.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceStateChange(SyncthingService.State currentState) {
|
|
||||||
if (!mIsCreateMode) {
|
|
||||||
RestApi restApi = getApi();
|
|
||||||
List<Device> devices = mConfig.getDevices(restApi, false);
|
|
||||||
String passedId = getIntent().getStringExtra(EXTRA_DEVICE_ID);
|
|
||||||
mDevice = null;
|
|
||||||
for (Device currentDevice : devices) {
|
|
||||||
if (currentDevice.deviceID.equals(passedId)) {
|
|
||||||
mDevice = currentDevice;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mDevice == null) {
|
|
||||||
Log.w(TAG, "Device not found in API update, maybe it was deleted?");
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (restApi != null) {
|
|
||||||
restApi.getConnections(this::onReceiveConnections);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateViewsAndSetListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (mIsCreateMode) {
|
if (mDeviceNeedsToUpdate) {
|
||||||
showDiscardDialog();
|
showDiscardDialog();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -290,24 +288,12 @@ public class DeviceActivity extends SyncthingActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
|
|
||||||
// We don't want to update every time a TextView's character changes,
|
|
||||||
// so we hold off until the view stops being visible to the user.
|
|
||||||
if (mDeviceNeedsToUpdate) {
|
|
||||||
updateDevice();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
SyncthingService syncthingService = getService();
|
SyncthingService syncthingService = getService();
|
||||||
if (syncthingService != null) {
|
if (syncthingService != null) {
|
||||||
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
||||||
syncthingService.unregisterOnServiceStateChangeListener(DeviceActivity.this);
|
|
||||||
}
|
}
|
||||||
mEditDeviceId.removeTextChangedListener(mIdTextWatcher);
|
mEditDeviceId.removeTextChangedListener(mIdTextWatcher);
|
||||||
mNameView.removeTextChangedListener(mNameTextWatcher);
|
mNameView.removeTextChangedListener(mNameTextWatcher);
|
||||||
|
@ -321,10 +307,10 @@ public class DeviceActivity extends SyncthingActivity
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putString("device", new Gson().toJson(mDevice));
|
outState.putString("device", new Gson().toJson(mDevice));
|
||||||
if (mIsCreateMode){
|
outState.putBoolean("deviceNeedsToUpdate", mDeviceNeedsToUpdate);
|
||||||
outState.putBoolean(IS_SHOWING_DISCARD_DIALOG, mDiscardDialog != null && mDiscardDialog.isShowing());
|
|
||||||
Util.dismissDialogSafe(mDiscardDialog, this);
|
outState.putBoolean(IS_SHOWING_DISCARD_DIALOG, mDiscardDialog != null && mDiscardDialog.isShowing());
|
||||||
}
|
Util.dismissDialogSafe(mDiscardDialog, this);
|
||||||
|
|
||||||
outState.putBoolean(IS_SHOWING_COMPRESSION_DIALOG, mCompressionDialog != null && mCompressionDialog.isShowing());
|
outState.putBoolean(IS_SHOWING_COMPRESSION_DIALOG, mCompressionDialog != null && mCompressionDialog.isShowing());
|
||||||
Util.dismissDialogSafe(mCompressionDialog, this);
|
Util.dismissDialogSafe(mCompressionDialog, this);
|
||||||
|
@ -405,7 +391,7 @@ public class DeviceActivity extends SyncthingActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
menu.findItem(R.id.create).setVisible(mIsCreateMode);
|
menu.findItem(R.id.save).setTitle(mIsCreateMode ? R.string.create : R.string.save_title);
|
||||||
menu.findItem(R.id.share_device_id).setVisible(!mIsCreateMode);
|
menu.findItem(R.id.share_device_id).setVisible(!mIsCreateMode);
|
||||||
menu.findItem(R.id.remove).setVisible(!mIsCreateMode);
|
menu.findItem(R.id.remove).setVisible(!mIsCreateMode);
|
||||||
return true;
|
return true;
|
||||||
|
@ -414,24 +400,8 @@ public class DeviceActivity extends SyncthingActivity
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.create:
|
case R.id.save:
|
||||||
if (isEmpty(mDevice.deviceID)) {
|
onSave();
|
||||||
Toast.makeText(this, R.string.device_id_required, Toast.LENGTH_LONG)
|
|
||||||
.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!mDevice.checkDeviceID()) {
|
|
||||||
Toast.makeText(this, R.string.device_id_invalid, Toast.LENGTH_LONG)
|
|
||||||
.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (isEmpty(mDevice.name)) {
|
|
||||||
Toast.makeText(this, R.string.device_name_required, Toast.LENGTH_LONG)
|
|
||||||
.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
mConfig.addDevice(getApi(), mDevice);
|
|
||||||
finish();
|
|
||||||
return true;
|
return true;
|
||||||
case R.id.share_device_id:
|
case R.id.share_device_id:
|
||||||
shareDeviceId(this, mDevice.deviceID);
|
shareDeviceId(this, mDevice.deviceID);
|
||||||
|
@ -491,23 +461,51 @@ public class DeviceActivity extends SyncthingActivity
|
||||||
mDevice.introducedBy = "";
|
mDevice.introducedBy = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void onSave() {
|
||||||
* Sends the updated device info if in edit mode.
|
if (mDevice == null) {
|
||||||
* Preconditions: mDeviceNeedsToUpdate == true
|
Log.e(TAG, "onSave: mDevice == null");
|
||||||
*/
|
|
||||||
private void updateDevice() {
|
|
||||||
if (mIsCreateMode) {
|
|
||||||
// If we are about to create this device, we cannot update via restApi.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mDevice == null) {
|
|
||||||
Log.e(TAG, "updateDevice: mDevice == null");
|
// Validate fields.
|
||||||
|
if (isEmpty(mDevice.deviceID)) {
|
||||||
|
Toast.makeText(this, R.string.device_id_required, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mDevice.checkDeviceID()) {
|
||||||
|
Toast.makeText(this, R.string.device_id_invalid, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isEmpty(mDevice.name)) {
|
||||||
|
Toast.makeText(this, R.string.device_name_required, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mDevice.checkDeviceAddresses()) {
|
||||||
|
Toast.makeText(this, R.string.device_addresses_invalid, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsCreateMode) {
|
||||||
|
Log.v(TAG, "onSave: Adding device with ID = \'" + mDevice.deviceID + "\'");
|
||||||
|
mConfig.addDevice(getApi(), mDevice);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit mode.
|
||||||
|
if (!mDeviceNeedsToUpdate) {
|
||||||
|
// We've got nothing to save.
|
||||||
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Log.v(TAG, "deviceID=" + mDevice.deviceID + ", introducedBy=" + mDevice.introducedBy);
|
// Log.v(TAG, "deviceID=" + mDevice.deviceID + ", introducedBy=" + mDevice.introducedBy);
|
||||||
|
|
||||||
// Save device specific preferences.
|
// Save device specific preferences.
|
||||||
Log.v(TAG, "updateDevice: mDevice.deviceID = \'" + mDevice.deviceID + "\'");
|
Log.v(TAG, "onSave: Updating device with ID = \'" + mDevice.deviceID + "\'");
|
||||||
SharedPreferences.Editor editor = mPreferences.edit();
|
SharedPreferences.Editor editor = mPreferences.edit();
|
||||||
editor.putBoolean(
|
editor.putBoolean(
|
||||||
Constants.DYN_PREF_OBJECT_CUSTOM_SYNC_CONDITIONS(Constants.PREF_OBJECT_PREFIX_DEVICE + mDevice.deviceID),
|
Constants.DYN_PREF_OBJECT_CUSTOM_SYNC_CONDITIONS(Constants.PREF_OBJECT_PREFIX_DEVICE + mDevice.deviceID),
|
||||||
|
@ -517,8 +515,13 @@ public class DeviceActivity extends SyncthingActivity
|
||||||
|
|
||||||
// Update device using RestApi or ConfigXml.
|
// Update device using RestApi or ConfigXml.
|
||||||
mConfig.updateDevice(getApi(), mDevice);
|
mConfig.updateDevice(getApi(), mDevice);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts text line to addresses array.
|
||||||
|
*/
|
||||||
private List<String> persistableAddresses(CharSequence userInput) {
|
private List<String> persistableAddresses(CharSequence userInput) {
|
||||||
if (isEmpty(userInput)) {
|
if (isEmpty(userInput)) {
|
||||||
return DYNAMIC_ADDRESS;
|
return DYNAMIC_ADDRESS;
|
||||||
|
@ -543,6 +546,9 @@ public class DeviceActivity extends SyncthingActivity
|
||||||
return Arrays.asList(input.split(", "));
|
return Arrays.asList(input.split(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts addresses array to a text line.
|
||||||
|
*/
|
||||||
private String displayableAddresses() {
|
private String displayableAddresses() {
|
||||||
if (mDevice.addresses == null) {
|
if (mDevice.addresses == null) {
|
||||||
return "";
|
return "";
|
||||||
|
|
|
@ -61,8 +61,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||||
/**
|
/**
|
||||||
* Shows folder details and allows changing them.
|
* Shows folder details and allows changing them.
|
||||||
*/
|
*/
|
||||||
public class FolderActivity extends SyncthingActivity
|
public class FolderActivity extends SyncthingActivity {
|
||||||
implements SyncthingService.OnServiceStateChangeListener {
|
|
||||||
|
|
||||||
public static final String EXTRA_NOTIFICATION_ID =
|
public static final String EXTRA_NOTIFICATION_ID =
|
||||||
"com.github.catfriend1.syncthingandroid.activities.FolderActivity.NOTIFICATION_ID";
|
"com.github.catfriend1.syncthingandroid.activities.FolderActivity.NOTIFICATION_ID";
|
||||||
|
@ -125,8 +124,6 @@ public class FolderActivity extends SyncthingActivity
|
||||||
private Dialog mDeleteDialog;
|
private Dialog mDeleteDialog;
|
||||||
private Dialog mDiscardDialog;
|
private Dialog mDiscardDialog;
|
||||||
|
|
||||||
private Folder.Versioning mVersioning;
|
|
||||||
|
|
||||||
private final TextWatcher mTextWatcher = new TextWatcherAdapter() {
|
private final TextWatcher mTextWatcher = new TextWatcherAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
|
@ -221,34 +218,72 @@ public class FolderActivity extends SyncthingActivity
|
||||||
findViewById(R.id.pullOrderContainer).setOnClickListener(v -> showPullOrderDialog());
|
findViewById(R.id.pullOrderContainer).setOnClickListener(v -> showPullOrderDialog());
|
||||||
findViewById(R.id.versioningContainer).setOnClickListener(v -> showVersioningDialog());
|
findViewById(R.id.versioningContainer).setOnClickListener(v -> showVersioningDialog());
|
||||||
|
|
||||||
if (mIsCreateMode) {
|
if (savedInstanceState != null) {
|
||||||
if (savedInstanceState != null) {
|
Log.d(TAG, "Retrieving state from savedInstanceState ...");
|
||||||
mFolder = new Gson().fromJson(savedInstanceState.getString("mFolder"), Folder.class);
|
mFolder = new Gson().fromJson(savedInstanceState.getString("mFolder"), Folder.class);
|
||||||
mFolderUri = savedInstanceState.getParcelable("mFolderUri");
|
mFolderNeedsToUpdate = savedInstanceState.getBoolean("mFolderNeedsToUpdate");
|
||||||
}
|
mIgnoreListNeedsToUpdate = savedInstanceState.getBoolean("mIgnoreListNeedsToUpdate");
|
||||||
if (mFolder == null) {
|
mFolderUri = savedInstanceState.getParcelable("mFolderUri");
|
||||||
|
restoreDialogStates(savedInstanceState);
|
||||||
|
} else {
|
||||||
|
// Fresh init of the edit or create mode.
|
||||||
|
if (mIsCreateMode) {
|
||||||
|
Log.d(TAG, "Initializing create mode ...");
|
||||||
initFolder();
|
initFolder();
|
||||||
|
mFolderNeedsToUpdate = true;
|
||||||
|
} else {
|
||||||
|
// Edit mode.
|
||||||
|
String passedId = getIntent().getStringExtra(EXTRA_FOLDER_ID);
|
||||||
|
Log.d(TAG, "Initializing edit mode: folder.id=" + passedId);
|
||||||
|
RestApi restApi = getApi();
|
||||||
|
List<Folder> folders = mConfig.getFolders(restApi);
|
||||||
|
mFolder = null;
|
||||||
|
for (Folder currentFolder : folders) {
|
||||||
|
if (currentFolder.id.equals(passedId)) {
|
||||||
|
mFolder = currentFolder;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mFolder == null) {
|
||||||
|
Log.w(TAG, "Folder not found in API update, maybe it was deleted?");
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mConfig.getFolderIgnoreList(restApi, mFolder, this::onReceiveFolderIgnoreList);
|
||||||
|
mFolderNeedsToUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the extra is set, we should automatically share the current folder with the given device.
|
||||||
|
if (getIntent().hasExtra(EXTRA_DEVICE_ID)) {
|
||||||
|
Device device = new Device();
|
||||||
|
device.deviceID = getIntent().getStringExtra(EXTRA_DEVICE_ID);
|
||||||
|
mFolder.addDevice(device);
|
||||||
|
mFolderNeedsToUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsCreateMode) {
|
||||||
mEditIgnoreListTitle.setEnabled(false);
|
mEditIgnoreListTitle.setEnabled(false);
|
||||||
mEditIgnoreListContent.setEnabled(false);
|
mEditIgnoreListContent.setEnabled(false);
|
||||||
}
|
} else {
|
||||||
else {
|
// Edit mode.
|
||||||
// Prepare edit mode.
|
|
||||||
mIdView.setFocusable(false);
|
mIdView.setFocusable(false);
|
||||||
mIdView.setEnabled(false);
|
mIdView.setEnabled(false);
|
||||||
mPathView.setFocusable(false);
|
mPathView.setFocusable(false);
|
||||||
mPathView.setEnabled(false);
|
mPathView.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
checkWriteAndUpdateUI();
|
||||||
|
updateViewsAndSetListeners();
|
||||||
|
|
||||||
// Open keyboard on label view in edit mode.
|
// Open keyboard on label view in edit mode.
|
||||||
mLabelView.requestFocus();
|
mLabelView.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
private void restoreDialogStates(Bundle savedInstanceState) {
|
||||||
if (savedInstanceState.getBoolean(IS_SHOWING_DELETE_DIALOG)) {
|
if (savedInstanceState.getBoolean(IS_SHOWING_DELETE_DIALOG)) {
|
||||||
showDeleteDialog();
|
showDeleteDialog();
|
||||||
} else if (savedInstanceState.getBoolean(IS_SHOW_DISCARD_DIALOG)) {
|
} else if (savedInstanceState.getBoolean(IS_SHOW_DISCARD_DIALOG)) {
|
||||||
showDiscardDialog();
|
showDiscardDialog();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,7 +380,7 @@ public class FolderActivity extends SyncthingActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (mIsCreateMode) {
|
if (mFolderNeedsToUpdate) {
|
||||||
showDiscardDialog();
|
showDiscardDialog();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -353,25 +388,12 @@ public class FolderActivity extends SyncthingActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
|
|
||||||
// We don't want to update every time a TextView's character changes,
|
|
||||||
// so we hold off until the view stops being visible to the user.
|
|
||||||
if (mFolderNeedsToUpdate) {
|
|
||||||
Log.v(TAG, "onPause: mFolderNeedsToUpdate=true, mIgnoreListNeedsToUpdate=" + Boolean.toString(mIgnoreListNeedsToUpdate));
|
|
||||||
updateFolder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
SyncthingService syncthingService = getService();
|
SyncthingService syncthingService = getService();
|
||||||
if (syncthingService != null) {
|
if (syncthingService != null) {
|
||||||
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
||||||
syncthingService.unregisterOnServiceStateChangeListener(FolderActivity.this);
|
|
||||||
}
|
}
|
||||||
mLabelView.removeTextChangedListener(mTextWatcher);
|
mLabelView.removeTextChangedListener(mTextWatcher);
|
||||||
mIdView.removeTextChangedListener(mTextWatcher);
|
mIdView.removeTextChangedListener(mTextWatcher);
|
||||||
|
@ -381,17 +403,16 @@ public class FolderActivity extends SyncthingActivity
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putString("mFolder", new Gson().toJson(mFolder));
|
||||||
|
outState.putBoolean("mFolderNeedsToUpdate", mFolderNeedsToUpdate);
|
||||||
|
outState.putBoolean("mIgnoreListNeedsToUpdate", mIgnoreListNeedsToUpdate);
|
||||||
|
outState.putParcelable("mFolderUri", mFolderUri);
|
||||||
|
|
||||||
outState.putBoolean(IS_SHOWING_DELETE_DIALOG, mDeleteDialog != null && mDeleteDialog.isShowing());
|
outState.putBoolean(IS_SHOWING_DELETE_DIALOG, mDeleteDialog != null && mDeleteDialog.isShowing());
|
||||||
Util.dismissDialogSafe(mDeleteDialog, this);
|
Util.dismissDialogSafe(mDeleteDialog, this);
|
||||||
|
|
||||||
outState.putBoolean(IS_SHOW_DISCARD_DIALOG, mDiscardDialog != null && mDiscardDialog.isShowing());
|
outState.putBoolean(IS_SHOW_DISCARD_DIALOG, mDiscardDialog != null && mDiscardDialog.isShowing());
|
||||||
Util.dismissDialogSafe(mDiscardDialog, this);
|
Util.dismissDialogSafe(mDiscardDialog, this);
|
||||||
|
|
||||||
if (mIsCreateMode) {
|
|
||||||
outState.putString("mFolder", new Gson().toJson(mFolder));
|
|
||||||
outState.putParcelable("mFolderUri", mFolderUri);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -403,47 +424,6 @@ public class FolderActivity extends SyncthingActivity
|
||||||
SyncthingServiceBinder syncthingServiceBinder = (SyncthingServiceBinder) iBinder;
|
SyncthingServiceBinder syncthingServiceBinder = (SyncthingServiceBinder) iBinder;
|
||||||
SyncthingService syncthingService = (SyncthingService) syncthingServiceBinder.getService();
|
SyncthingService syncthingService = (SyncthingService) syncthingServiceBinder.getService();
|
||||||
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
||||||
syncthingService.registerOnServiceStateChangeListener(FolderActivity.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceStateChange(SyncthingService.State currentState) {
|
|
||||||
if (mFolderNeedsToUpdate) {
|
|
||||||
Log.d(TAG, "onServiceStateChange: Suppressing reload of folder config as changes were made to that folder in the meantime.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mIsCreateMode) {
|
|
||||||
Log.d(TAG, "onServiceStateChange: (Re)loading folder config ...");
|
|
||||||
RestApi restApi = getApi();
|
|
||||||
List<Folder> folders = mConfig.getFolders(restApi);
|
|
||||||
String passedId = getIntent().getStringExtra(EXTRA_FOLDER_ID);
|
|
||||||
mFolder = null;
|
|
||||||
for (Folder currentFolder : folders) {
|
|
||||||
if (currentFolder.id.equals(passedId)) {
|
|
||||||
mFolder = currentFolder;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mFolder == null) {
|
|
||||||
Log.w(TAG, "Folder not found in API update, maybe it was deleted?");
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mConfig.getFolderIgnoreList(restApi, mFolder, this::onReceiveFolderIgnoreList);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the extra is set, we should automatically share the current folder with the given device.
|
|
||||||
if (getIntent().hasExtra(EXTRA_DEVICE_ID)) {
|
|
||||||
Device device = new Device();
|
|
||||||
device.deviceID = getIntent().getStringExtra(EXTRA_DEVICE_ID);
|
|
||||||
mFolder.addDevice(device);
|
|
||||||
mFolderNeedsToUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkWriteAndUpdateUI();
|
|
||||||
attemptToApplyVersioningConfig();
|
|
||||||
updateViewsAndSetListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onReceiveFolderIgnoreList(FolderIgnoreList folderIgnoreList) {
|
private void onReceiveFolderIgnoreList(FolderIgnoreList folderIgnoreList) {
|
||||||
|
@ -456,16 +436,6 @@ public class FolderActivity extends SyncthingActivity
|
||||||
mEditIgnoreListContent.addTextChangedListener(mIgnoreListContentTextWatcher);
|
mEditIgnoreListContent.addTextChangedListener(mIgnoreListContentTextWatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the FolderActivity gets recreated after the VersioningDialogActivity is closed, then the result from the VersioningDialogActivity will be received before
|
|
||||||
// the mFolder variable has been recreated, so the versioning config will be stored in the mVersioning variable until the mFolder variable has been
|
|
||||||
// recreated in the onServiceStateChange(). This has been observed to happen after the screen orientation has changed while the VersioningDialogActivity was open.
|
|
||||||
private void attemptToApplyVersioningConfig() {
|
|
||||||
if (mFolder != null && mVersioning != null){
|
|
||||||
mFolder.versioning = mVersioning;
|
|
||||||
mVersioning = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateViewsAndSetListeners() {
|
private void updateViewsAndSetListeners() {
|
||||||
mLabelView.removeTextChangedListener(mTextWatcher);
|
mLabelView.removeTextChangedListener(mTextWatcher);
|
||||||
mIdView.removeTextChangedListener(mTextWatcher);
|
mIdView.removeTextChangedListener(mTextWatcher);
|
||||||
|
@ -528,7 +498,7 @@ public class FolderActivity extends SyncthingActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
menu.findItem(R.id.create).setVisible(mIsCreateMode);
|
menu.findItem(R.id.save).setTitle(mIsCreateMode ? R.string.create : R.string.save_title);
|
||||||
menu.findItem(R.id.remove).setVisible(!mIsCreateMode);
|
menu.findItem(R.id.remove).setVisible(!mIsCreateMode);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -536,39 +506,8 @@ public class FolderActivity extends SyncthingActivity
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.create:
|
case R.id.save:
|
||||||
if (TextUtils.isEmpty(mFolder.id)) {
|
onSave();
|
||||||
Toast.makeText(this, R.string.folder_id_required, Toast.LENGTH_LONG)
|
|
||||||
.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();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
|
||||||
mFolderUri != null &&
|
|
||||||
mFolder.type.equals(Constants.FOLDER_TYPE_SEND_ONLY)) {
|
|
||||||
/**
|
|
||||||
* Normally, syncthing takes care of creating the ".stfolder" marker.
|
|
||||||
* This fails on newer android versions if the syncthing binary only has
|
|
||||||
* readonly access on the path and the user tries to configure a
|
|
||||||
* sendonly folder. To fix this, we'll precreate the marker using java code.
|
|
||||||
*/
|
|
||||||
DocumentFile dfFolder = DocumentFile.fromTreeUri(this, mFolderUri);
|
|
||||||
if (dfFolder != null) {
|
|
||||||
Log.v(TAG, "Creating new directory " + mFolder.path + File.separator + FOLDER_MARKER_NAME);
|
|
||||||
dfFolder.createDirectory(FOLDER_MARKER_NAME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mConfig.addFolder(getApi(), mFolder);
|
|
||||||
finish();
|
|
||||||
return true;
|
return true;
|
||||||
case R.id.remove:
|
case R.id.remove:
|
||||||
showDeleteDialog();
|
showDeleteDialog();
|
||||||
|
@ -662,8 +601,12 @@ public class FolderActivity extends SyncthingActivity
|
||||||
* because the user most probably intentionally chose a special folder like
|
* because the user most probably intentionally chose a special folder like
|
||||||
* "[storage]/Android/data/com.nutomic.syncthingandroid/files"
|
* "[storage]/Android/data/com.nutomic.syncthingandroid/files"
|
||||||
* or enabled root mode thus having write access.
|
* or enabled root mode thus having write access.
|
||||||
|
* Default from {@link #initFolder} was already set in {@link #onCreate}.
|
||||||
|
* mFolder.type = Constants.FOLDER_TYPE_SEND_RECEIVE;
|
||||||
|
* We won't set it again here as this would cause user selection to be reset on
|
||||||
|
* screen rotation - as we don't know if we restored the activity or created
|
||||||
|
* a fresh one.
|
||||||
*/
|
*/
|
||||||
mFolder.type = Constants.FOLDER_TYPE_SEND_RECEIVE;
|
|
||||||
updateFolderTypeDescription();
|
updateFolderTypeDescription();
|
||||||
} else {
|
} else {
|
||||||
mEditIgnoreListTitle.setEnabled(true);
|
mEditIgnoreListTitle.setEnabled(true);
|
||||||
|
@ -694,6 +637,9 @@ public class FolderActivity extends SyncthingActivity
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init a new folder in mIsCreateMode, used in {@link #onCreate}.
|
||||||
|
*/
|
||||||
private void initFolder() {
|
private void initFolder() {
|
||||||
mFolder = new Folder();
|
mFolder = new Folder();
|
||||||
mFolder.id = (getIntent().hasExtra(EXTRA_FOLDER_ID))
|
mFolder.id = (getIntent().hasExtra(EXTRA_FOLDER_ID))
|
||||||
|
@ -708,7 +654,7 @@ public class FolderActivity extends SyncthingActivity
|
||||||
*/
|
*/
|
||||||
mFolder.rescanIntervalS = 3600;
|
mFolder.rescanIntervalS = 3600;
|
||||||
mFolder.paused = false;
|
mFolder.paused = false;
|
||||||
mFolder.type = Constants.FOLDER_TYPE_SEND_RECEIVE;
|
mFolder.type = Constants.FOLDER_TYPE_SEND_RECEIVE; // Default for {@link #checkWriteAndUpdateUI}.
|
||||||
mFolder.versioning = new Folder.Versioning();
|
mFolder.versioning = new Folder.Versioning();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -735,24 +681,60 @@ public class FolderActivity extends SyncthingActivity
|
||||||
deviceView.setOnCheckedChangeListener(mCheckedListener);
|
deviceView.setOnCheckedChangeListener(mCheckedListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void onSave() {
|
||||||
* Sends the updated folder info if in edit mode.
|
if (mFolder == null) {
|
||||||
* Preconditions:
|
Log.e(TAG, "onSave: mFolder == null");
|
||||||
* mFolderNeedsToUpdate == true
|
|
||||||
* mIgnoreListNeedsToUpdate == true (Optional)
|
|
||||||
*/
|
|
||||||
private void updateFolder() {
|
|
||||||
if (mIsCreateMode) {
|
|
||||||
// If we are about to create this folder, we cannot update via restApi.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mFolder == null) {
|
|
||||||
Log.e(TAG, "updateFolder: mFolder == null");
|
// Validate fields.
|
||||||
|
if (TextUtils.isEmpty(mFolder.id)) {
|
||||||
|
Toast.makeText(this, R.string.folder_id_required, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(mFolder.label)) {
|
||||||
|
Toast.makeText(this, R.string.folder_label_required, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(mFolder.path)) {
|
||||||
|
Toast.makeText(this, R.string.folder_path_required, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsCreateMode) {
|
||||||
|
Log.v(TAG, "onSave: Adding folder with ID = \'" + mFolder.id + "\'");
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
||||||
|
mFolderUri != null &&
|
||||||
|
mFolder.type.equals(Constants.FOLDER_TYPE_SEND_ONLY)) {
|
||||||
|
/**
|
||||||
|
* Normally, syncthing takes care of creating the ".stfolder" marker.
|
||||||
|
* This fails on newer android versions if the syncthing binary only has
|
||||||
|
* readonly access on the path and the user tries to configure a
|
||||||
|
* sendonly folder. To fix this, we'll precreate the marker using java code.
|
||||||
|
*/
|
||||||
|
DocumentFile dfFolder = DocumentFile.fromTreeUri(this, mFolderUri);
|
||||||
|
if (dfFolder != null) {
|
||||||
|
Log.v(TAG, "onSave: Creating new directory " + mFolder.path + File.separator + FOLDER_MARKER_NAME);
|
||||||
|
dfFolder.createDirectory(FOLDER_MARKER_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mConfig.addFolder(getApi(), mFolder);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit mode.
|
||||||
|
if (!mFolderNeedsToUpdate) {
|
||||||
|
// We've got nothing to save.
|
||||||
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save folder specific preferences.
|
// Save folder specific preferences.
|
||||||
Log.v(TAG, "updateFolder: mFolder.id = \'" + mFolder.id + "\'");
|
Log.v(TAG, "onSave: Updating folder with ID = \'" + mFolder.id + "\'");
|
||||||
SharedPreferences.Editor editor = mPreferences.edit();
|
SharedPreferences.Editor editor = mPreferences.edit();
|
||||||
editor.putBoolean(
|
editor.putBoolean(
|
||||||
Constants.DYN_PREF_OBJECT_CUSTOM_SYNC_CONDITIONS(Constants.PREF_OBJECT_PREFIX_FOLDER + mFolder.id),
|
Constants.DYN_PREF_OBJECT_CUSTOM_SYNC_CONDITIONS(Constants.PREF_OBJECT_PREFIX_FOLDER + mFolder.id),
|
||||||
|
@ -770,41 +752,39 @@ public class FolderActivity extends SyncthingActivity
|
||||||
|
|
||||||
// Update folder using RestApi or ConfigXml.
|
// Update folder using RestApi or ConfigXml.
|
||||||
mConfig.updateFolder(restApi, mFolder);
|
mConfig.updateFolder(restApi, mFolder);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showDiscardDialog(){
|
private void showDiscardDialog(){
|
||||||
mDiscardDialog = createDiscardDialog();
|
mDiscardDialog = new AlertDialog.Builder(this)
|
||||||
mDiscardDialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dialog createDiscardDialog() {
|
|
||||||
return new AlertDialog.Builder(this)
|
|
||||||
.setMessage(R.string.dialog_discard_changes)
|
.setMessage(R.string.dialog_discard_changes)
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> finish())
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> finish())
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.create();
|
.create();
|
||||||
|
mDiscardDialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateVersioning(Bundle arguments) {
|
private void updateVersioning(Bundle arguments) {
|
||||||
if (mFolder != null){
|
if (mFolder == null) {
|
||||||
mVersioning = mFolder.versioning;
|
Log.e(TAG, "updateVersioning: mFolder == null");
|
||||||
} else {
|
return;
|
||||||
mVersioning = new Folder.Versioning();
|
}
|
||||||
|
if (mFolder.versioning == null) {
|
||||||
|
mFolder.versioning = new Folder.Versioning();
|
||||||
}
|
}
|
||||||
|
|
||||||
String type = arguments.getString("type");
|
String type = arguments.getString("type");
|
||||||
arguments.remove("type");
|
arguments.remove("type");
|
||||||
|
|
||||||
if (type.equals("none")){
|
if (type.equals("none")) {
|
||||||
mVersioning = new Folder.Versioning();
|
mFolder.versioning = new Folder.Versioning();
|
||||||
} else {
|
} else {
|
||||||
for (String key : arguments.keySet()) {
|
for (String key : arguments.keySet()) {
|
||||||
mVersioning.params.put(key, arguments.getString(key));
|
mFolder.versioning.params.put(key, arguments.getString(key));
|
||||||
}
|
}
|
||||||
mVersioning.type = type;
|
mFolder.versioning.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
attemptToApplyVersioningConfig();
|
|
||||||
updateVersioningDescription();
|
updateVersioningDescription();
|
||||||
mFolderNeedsToUpdate = true;
|
mFolderNeedsToUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ public class Device {
|
||||||
public List<PendingFolder> pendingFolders;
|
public List<PendingFolder> pendingFolders;
|
||||||
public List<IgnoredFolder> ignoredFolders;
|
public List<IgnoredFolder> ignoredFolders;
|
||||||
|
|
||||||
|
// private static final String TAG = "Device";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relevant fields for Folder.List<Device> "shared-with-device" model,
|
* Relevant fields for Folder.List<Device> "shared-with-device" model,
|
||||||
* handled by {@link ConfigRouter#updateFolder and ConfigXml#updateFolder}
|
* handled by {@link ConfigRouter#updateFolder and ConfigXml#updateFolder}
|
||||||
|
@ -112,4 +114,93 @@ public class Device {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if device.addresses elements are correctly formatted.
|
||||||
|
* See https://docs.syncthing.net/users/config.html#device-element for what is correct.
|
||||||
|
* It can be improved in the future because it doesn't catch all mistakes a user can do.
|
||||||
|
* It catches the most common mistakes.
|
||||||
|
*/
|
||||||
|
public Boolean checkDeviceAddresses() {
|
||||||
|
if (this.addresses == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (String address : this.addresses) {
|
||||||
|
// Log.v(TAG, "address=(" + address + ")");
|
||||||
|
if (address.equals("dynamic")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegEx documentation:
|
||||||
|
*
|
||||||
|
* - Matching
|
||||||
|
* tcp://127.0.0.1:4000
|
||||||
|
* tcp4://127.0.0.1:4000
|
||||||
|
* tcp6://127.0.0.1:4000
|
||||||
|
* tcp4://127.0.0.1
|
||||||
|
* tcp://[2001:db8::23:42]
|
||||||
|
* tcp://[2001:db8::23:42]:12345
|
||||||
|
* tcp://myserver
|
||||||
|
* tcp://myserver:12345
|
||||||
|
*
|
||||||
|
* - Not-Matching
|
||||||
|
* tcp8://127.0.0.1
|
||||||
|
* udp4://127.0.0.1
|
||||||
|
*/
|
||||||
|
if (!address.matches("^tcp([46])?://.*$")) {
|
||||||
|
// Log.v(TAG, "Invalid protocol.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate protocol from address and port.
|
||||||
|
String[] addressSplit = address.split("://");
|
||||||
|
if (addressSplit.length == 1) {
|
||||||
|
// There's only the protocol given, nothing more.
|
||||||
|
// Log.v(TAG, "There's only the protocol given, nothing more.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (addressSplit.length == 2) {
|
||||||
|
// Check if the address ends with ":" or "]:"
|
||||||
|
if (addressSplit[addressSplit.length-1].endsWith(":") ||
|
||||||
|
addressSplit[addressSplit.length-1].endsWith("]:")) {
|
||||||
|
// The address ends with ":". Will match "tcp://myserver:"
|
||||||
|
// Log.v(TAG, "address ends with \":\" or \"]:\". Will match \"tcp://myserver:\".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there's a "hostname:port" number given in the part after "://".
|
||||||
|
String[] hostnamePortSplit = addressSplit[addressSplit.length-1].split(":");
|
||||||
|
if (hostnamePortSplit.length > 1) {
|
||||||
|
// Check if the hostname or IP address given before the port is empty.
|
||||||
|
if (TextUtils.isEmpty(hostnamePortSplit[0])) {
|
||||||
|
// Empty hostname or IP address before the port. Will match "tcp://:4000"
|
||||||
|
// Log.v(TAG, "Empty hostname or IP address before the port.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there's a port number given in the last part.
|
||||||
|
String potentialPort = hostnamePortSplit[hostnamePortSplit.length-1];
|
||||||
|
if (!potentialPort.endsWith("]")) {
|
||||||
|
// It's not the end of an IPv6 address and likely a port number.
|
||||||
|
// Log.v(TAG, "... potentialPort=(" + potentialPort + ")");
|
||||||
|
Integer port = 0;
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(potentialPort);
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
if (port < 1 || port > 65535) {
|
||||||
|
// Invalid port number.
|
||||||
|
// Log.v(TAG, "Invalid port number.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Protocol is given more than one time. Will match "tcp://tcp://"
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,6 +162,7 @@ public class EventProcessor implements Runnable, RestApi.OnReceiveEventListener
|
||||||
case "FolderSummary":
|
case "FolderSummary":
|
||||||
case "FolderWatchStateChanged":
|
case "FolderWatchStateChanged":
|
||||||
case "ItemStarted":
|
case "ItemStarted":
|
||||||
|
case "ListenAddressesChanged":
|
||||||
case "LocalIndexUpdated":
|
case "LocalIndexUpdated":
|
||||||
case "LoginAttempt":
|
case "LoginAttempt":
|
||||||
case "RemoteDownloadProgress":
|
case "RemoteDownloadProgress":
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/create"
|
android:id="@+id/save"
|
||||||
android:title="@string/add"
|
android:title="@string/add"
|
||||||
android:icon="@drawable/ic_done_white_24dp"
|
android:icon="@drawable/ic_done_white_24dp"
|
||||||
app:showAsAction="always|withText" />
|
app:showAsAction="always|withText" />
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/create"
|
android:id="@+id/save"
|
||||||
android:icon="@drawable/ic_done_white_24dp"
|
android:icon="@drawable/ic_done_white_24dp"
|
||||||
android:title="@string/create"
|
android:title="@string/create"
|
||||||
app:showAsAction="always|withText" />
|
app:showAsAction="always|withText" />
|
||||||
|
|
|
@ -305,6 +305,9 @@ Bitte melden Sie auftretende Probleme via GitHub.</string>
|
||||||
<!-- Toast shown when trying to create a device with an empty name -->
|
<!-- Toast shown when trying to create a device with an empty name -->
|
||||||
<string name="device_name_required">Der Gerätename darf nicht leer sein</string>
|
<string name="device_name_required">Der Gerätename darf nicht leer sein</string>
|
||||||
|
|
||||||
|
<!-- Toast shown when trying to create a device with invalid addresses -->
|
||||||
|
<string name="device_addresses_invalid">Die Geräteadressen sind nicht im richtigen Format. Bitte versuche es erneut. Sieh Dir die Syncthing Doku für weitere Hilfe an.</string>
|
||||||
|
|
||||||
<!-- Content description for device ID qr code icon -->
|
<!-- Content description for device ID qr code icon -->
|
||||||
<string name="scan_qr_code_description">QR Code scannen</string>
|
<string name="scan_qr_code_description">QR Code scannen</string>
|
||||||
|
|
||||||
|
|
|
@ -308,6 +308,9 @@ Please report any problems you encounter via Github.</string>
|
||||||
<!-- Toast shown when trying to create a device with an empty name -->
|
<!-- Toast shown when trying to create a device with an empty name -->
|
||||||
<string name="device_name_required">The device name must not be empty</string>
|
<string name="device_name_required">The device name must not be empty</string>
|
||||||
|
|
||||||
|
<!-- Toast shown when trying to create a device with invalid addresses -->
|
||||||
|
<string name="device_addresses_invalid">The device addresses are not formatted correctly. Please retry. Look at the Syncthing docs for more info.</string>
|
||||||
|
|
||||||
<!-- Content description for device ID qr code icon -->
|
<!-- Content description for device ID qr code icon -->
|
||||||
<string name="scan_qr_code_description">Scan QR Code</string>
|
<string name="scan_qr_code_description">Scan QR Code</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue