diff --git a/app/src/main/java/com/nutomic/syncthingandroid/http/PostRequest.java b/app/src/main/java/com/nutomic/syncthingandroid/http/PostRequest.java new file mode 100644 index 00000000..435842cf --- /dev/null +++ b/app/src/main/java/com/nutomic/syncthingandroid/http/PostRequest.java @@ -0,0 +1,26 @@ +package com.nutomic.syncthingandroid.http; + +import android.content.Context; +import android.net.Uri; +import android.support.annotation.Nullable; + +import com.android.volley.Request; +import com.google.common.base.Optional; + +import java.net.URL; +import java.util.Collections; +import java.util.Map; + +public class PostRequest extends ApiRequest { + + public static final String URI_DB_OVERRIDE = "/rest/db/override"; + + public PostRequest(Context context, URL url, String path, String apiKey, + @Nullable Map params, OnSuccessListener listener) { + super(context, url, path, apiKey); + Map safeParams = Optional.fromNullable(params).or(Collections.emptyMap()); + Uri uri = buildUri(safeParams); + connect(Request.Method.POST, uri, null, listener, null); + } + +} diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java b/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java index 2720a2a7..19d5d42b 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java @@ -20,6 +20,7 @@ import com.nutomic.syncthingandroid.BuildConfig; import com.nutomic.syncthingandroid.SyncthingApp; import com.nutomic.syncthingandroid.activities.ShareActivity; import com.nutomic.syncthingandroid.http.GetRequest; +import com.nutomic.syncthingandroid.http.PostRequest; import com.nutomic.syncthingandroid.http.PostConfigRequest; import com.nutomic.syncthingandroid.model.Config; import com.nutomic.syncthingandroid.model.Completion; @@ -268,6 +269,16 @@ public class RestApi { } } + /** + * Override folder changes. This is the same as hitting + * the "override changes" button from the web UI. + */ + public void overrideChanges(String folderId) { + Log.d(TAG, "overrideChanges '" + folderId + "'"); + new PostRequest(mContext, mUrl, PostRequest.URI_DB_OVERRIDE, mApiKey, + ImmutableMap.of("folder", folderId), null); + } + /** * Sends current config to Syncthing. * Will result in a "ConfigSaved" event. diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java b/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java index cfd7f1d3..10a7ad3e 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java @@ -71,6 +71,12 @@ public class SyncthingService extends Service { public static final String ACTION_IGNORE_FOLDER = "com.nutomic.syncthingandroid.service.SyncthingService.IGNORE_FOLDER"; + /** + * Intent action to override folder changes. + */ + public static final String ACTION_OVERRIDE_CHANGES = + "com.nutomic.syncthingandroid.service.SyncthingService.OVERRIDE_CHANGES"; + /** * Extra used together with ACTION_IGNORE_DEVICE, ACTION_IGNORE_FOLDER. */ @@ -242,6 +248,8 @@ public class SyncthingService extends Service { // mApi is not null due to State.ACTIVE mApi.ignoreFolder(intent.getStringExtra(EXTRA_FOLDER_ID)); mNotificationHandler.cancelConsentNotification(intent.getIntExtra(EXTRA_NOTIFICATION_ID, 0)); + } else if (ACTION_OVERRIDE_CHANGES.equals(intent.getAction()) && mCurrentState == State.ACTIVE) { + mApi.overrideChanges(intent.getStringExtra(EXTRA_FOLDER_ID)); } return START_STICKY; } diff --git a/app/src/main/java/com/nutomic/syncthingandroid/views/FoldersAdapter.java b/app/src/main/java/com/nutomic/syncthingandroid/views/FoldersAdapter.java index 2bf3a228..0c262023 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/views/FoldersAdapter.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/views/FoldersAdapter.java @@ -19,7 +19,9 @@ import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.databinding.ItemFolderListBinding; import com.nutomic.syncthingandroid.model.Folder; import com.nutomic.syncthingandroid.model.FolderStatus; +import com.nutomic.syncthingandroid.service.Constants; import com.nutomic.syncthingandroid.service.RestApi; +import com.nutomic.syncthingandroid.service.SyncthingService; import com.nutomic.syncthingandroid.util.Util; import java.io.File; @@ -37,36 +39,46 @@ public class FoldersAdapter extends ArrayAdapter { private final HashMap mLocalFolderStatuses = new HashMap<>(); + private final Context mContext; + public FoldersAdapter(Context context) { super(context, 0); + mContext = context; } @Override @NonNull public View getView(int position, View convertView, @NonNull ViewGroup parent) { ItemFolderListBinding binding = (convertView == null) - ? DataBindingUtil.inflate(LayoutInflater.from(getContext()), R.layout.item_folder_list, parent, false) + ? DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.item_folder_list, parent, false) : DataBindingUtil.bind(convertView); Folder folder = getItem(position); binding.label.setText(TextUtils.isEmpty(folder.label) ? folder.id : folder.label); binding.directory.setText(folder.path); + binding.override.setOnClickListener(v -> { + // Send "Override changes" through our service to the REST API. + Intent intent = new Intent(mContext, SyncthingService.class) + .putExtra(SyncthingService.EXTRA_FOLDER_ID, folder.id); + intent.setAction(SyncthingService.ACTION_OVERRIDE_CHANGES); + mContext.startService(intent); + }); binding.openFolder.setOnClickListener(v -> { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(new File(folder.path)), "resource/folder"); intent.putExtra("org.openintents.extra.ABSOLUTE_PATH", folder.path); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK); - if (intent.resolveActivity(getContext().getPackageManager()) != null) { - getContext().startActivity(intent); + if (intent.resolveActivity(mContext.getPackageManager()) != null) { + mContext.startActivity(intent); } else { // Try a second way to find a compatible file explorer app. Log.v(TAG, "openFolder: Fallback to application chooser to open folder."); intent.setDataAndType(Uri.parse(folder.path), "application/*"); - Intent chooserIntent = Intent.createChooser(intent, getContext().getString(R.string.open_file_manager)); + Intent chooserIntent = Intent.createChooser(intent, mContext.getString(R.string.open_file_manager)); if (chooserIntent != null) { - getContext().startActivity(chooserIntent); + mContext.startActivity(chooserIntent); } else { - Toast.makeText(getContext(), R.string.toast_no_file_manager, Toast.LENGTH_SHORT).show(); + Toast.makeText(mContext, R.string.toast_no_file_manager, Toast.LENGTH_SHORT).show(); } } }); @@ -79,6 +91,7 @@ public class FoldersAdapter extends ArrayAdapter { FolderStatus folderStatus = mLocalFolderStatuses.get(folder.id); if (folderStatus == null) { binding.items.setVisibility(GONE); + binding.override.setVisibility(GONE); binding.size.setVisibility(GONE); setTextOrHide(binding.invalid, folder.invalid); return; @@ -88,35 +101,38 @@ public class FoldersAdapter extends ArrayAdapter { ? Math.round(100 * folderStatus.inSyncBytes / folderStatus.globalBytes) : 100; long neededItems = folderStatus.needFiles + folderStatus.needDirectories + folderStatus.needSymlinks + folderStatus.needDeletes; - if (folderStatus.state.equals("idle") && neededItems > 0) { - binding.state.setText(getContext().getString(R.string.status_outofsync)); - binding.state.setTextColor(ContextCompat.getColor(getContext(), R.color.text_red)); + boolean outOfSync = folderStatus.state.equals("idle") && neededItems > 0; + boolean overrideButtonVisible = (folder.type == Constants.FOLDER_TYPE_SEND_ONLY) && outOfSync; + binding.override.setVisibility(overrideButtonVisible ? VISIBLE : GONE); + if (outOfSync) { + binding.state.setText(mContext.getString(R.string.status_outofsync)); + binding.state.setTextColor(ContextCompat.getColor(mContext, R.color.text_red)); } else { if (folder.paused) { - binding.state.setText(getContext().getString(R.string.state_paused)); - binding.state.setTextColor(ContextCompat.getColor(getContext(), R.color.text_black)); + binding.state.setText(mContext.getString(R.string.state_paused)); + binding.state.setTextColor(ContextCompat.getColor(mContext, R.color.text_black)); } else { - binding.state.setText(getLocalizedState(getContext(), folderStatus.state, percentage)); + binding.state.setText(getLocalizedState(mContext, folderStatus.state, percentage)); switch(folderStatus.state) { case "idle": - binding.state.setTextColor(ContextCompat.getColor(getContext(), R.color.text_green)); + binding.state.setTextColor(ContextCompat.getColor(mContext, R.color.text_green)); break; case "scanning": case "syncing": - binding.state.setTextColor(ContextCompat.getColor(getContext(), R.color.text_blue)); + binding.state.setTextColor(ContextCompat.getColor(mContext, R.color.text_blue)); break; default: - binding.state.setTextColor(ContextCompat.getColor(getContext(), R.color.text_red)); + binding.state.setTextColor(ContextCompat.getColor(mContext, R.color.text_red)); } } } binding.items.setVisibility(VISIBLE); - binding.items.setText(getContext().getResources() + binding.items.setText(mContext.getResources() .getQuantityString(R.plurals.files, (int) folderStatus.inSyncFiles, folderStatus.inSyncFiles, folderStatus.globalFiles)); binding.size.setVisibility(VISIBLE); - binding.size.setText(getContext().getString(R.string.folder_size_format, - Util.readableFileSize(getContext(), folderStatus.inSyncBytes), - Util.readableFileSize(getContext(), folderStatus.globalBytes))); + binding.size.setText(mContext.getString(R.string.folder_size_format, + Util.readableFileSize(mContext, folderStatus.inSyncBytes), + Util.readableFileSize(mContext, folderStatus.globalBytes))); setTextOrHide(binding.invalid, folderStatus.invalid); } diff --git a/app/src/main/res/layout/item_folder_list.xml b/app/src/main/res/layout/item_folder_list.xml index 4c40a0ec..0856e370 100644 --- a/app/src/main/res/layout/item_folder_list.xml +++ b/app/src/main/res/layout/item_folder_list.xml @@ -45,11 +45,26 @@ android:ellipsize="end" android:textAppearance="?textAppearanceListItemSecondary" /> +