From 393374021393292bdd83390a2c14af3f7082473a Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Sat, 14 Jun 2014 02:55:55 +0200 Subject: [PATCH] Added node and repo settings activities. --- src/main/AndroidManifest.xml | 16 + .../ExtendedCheckBoxPreference.java | 22 ++ .../gui/LoadingListFragment.java | 4 +- .../syncthingandroid/gui/MainActivity.java | 14 +- .../gui/NodeSettingsActivity.java | 228 ++++++++++++++ .../syncthingandroid/gui/NodesFragment.java | 16 +- .../gui/RepoSettingsActivity.java | 280 ++++++++++++++++++ .../gui/RepositoriesFragment.java | 13 +- .../gui/SettingsActivity.java | 4 +- .../syncthingandroid/syncthing/PostTask.java | 2 +- .../syncthingandroid/syncthing/RestApi.java | 150 +++++++++- src/main/res/menu/{menu.xml => main_menu.xml} | 12 + src/main/res/menu/node_settings_menu.xml | 8 + src/main/res/menu/repo_settings_menu.xml | 8 + src/main/res/values-large/styles.xml | 4 + src/main/res/values/strings.xml | 45 +++ src/main/res/values/styles.xml | 10 + .../xml/{settings.xml => app_settings.xml} | 0 src/main/res/xml/node_settings.xml | 34 +++ src/main/res/xml/repo_settings.xml | 36 +++ 20 files changed, 893 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/nutomic/syncthingandroid/ExtendedCheckBoxPreference.java create mode 100644 src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java create mode 100644 src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java rename src/main/res/menu/{menu.xml => main_menu.xml} (72%) create mode 100644 src/main/res/menu/node_settings_menu.xml create mode 100644 src/main/res/menu/repo_settings_menu.xml create mode 100644 src/main/res/values-large/styles.xml create mode 100644 src/main/res/values/styles.xml rename src/main/res/xml/{settings.xml => app_settings.xml} (100%) create mode 100644 src/main/res/xml/node_settings.xml create mode 100644 src/main/res/xml/repo_settings.xml diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 2dc7b4a7..d7af018d 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -45,5 +45,21 @@ + + + + + + + + diff --git a/src/main/java/com/nutomic/syncthingandroid/ExtendedCheckBoxPreference.java b/src/main/java/com/nutomic/syncthingandroid/ExtendedCheckBoxPreference.java new file mode 100644 index 00000000..8371a37d --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/ExtendedCheckBoxPreference.java @@ -0,0 +1,22 @@ +package com.nutomic.syncthingandroid; + +import android.content.Context; +import android.preference.CheckBoxPreference; + +/** + * Saves an extra object on construction, which can be retrieved later. + */ +public class ExtendedCheckBoxPreference extends CheckBoxPreference { + + private final Object mObject; + + public ExtendedCheckBoxPreference(Context context, Object object) { + super(context); + mObject = object; + } + + public Object getObject() { + return mObject; + } + +} diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/LoadingListFragment.java b/src/main/java/com/nutomic/syncthingandroid/gui/LoadingListFragment.java index 5c93ff2f..bc74b7e9 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/LoadingListFragment.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/LoadingListFragment.java @@ -22,6 +22,7 @@ import android.support.v4.app.ListFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.TextView; @@ -33,7 +34,7 @@ import com.nutomic.syncthingandroid.syncthing.SyncthingService; /** * {@link android.support.v4.app.ListFragment} that shows a configurable loading text. */ -public abstract class LoadingListFragment extends Fragment implements RestApi.OnApiAvailableListener { +public abstract class LoadingListFragment extends Fragment implements RestApi.OnApiAvailableListener, AdapterView.OnItemClickListener { private boolean mInitialized = false; @@ -111,6 +112,7 @@ public abstract class LoadingListFragment extends Fragment implements RestApi.On if (!mInitialized && getActivity() != null && activity.getApi() != null && mListFragment != null) { onInitAdapter(activity); + getListView().setOnItemClickListener(this); mInitialized = true; } } diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java index e90c5bd7..1a010114 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java @@ -32,7 +32,7 @@ import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder; * {@link LocalNodeInfoFragment} in the navigation drawer. */ public class MainActivity extends ActionBarActivity - implements SyncthingService.OnWebGuiAvailableListener{ + implements SyncthingService.OnWebGuiAvailableListener { private SyncthingService mSyncthingService; @@ -197,7 +197,7 @@ public class MainActivity extends ActionBarActivity @Override public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu, menu); + getMenuInflater().inflate(R.menu.main_menu, menu); return true; } @@ -220,6 +220,16 @@ public class MainActivity extends ActionBarActivity } switch (item.getItemId()) { + case R.id.add_node: + Intent intent = new Intent(this, NodeSettingsActivity.class); + intent.setAction(NodeSettingsActivity.ACTION_CREATE); + startActivity(intent); + return true; + case R.id.add_repository: + intent = new Intent(this, RepoSettingsActivity.class); + intent.setAction(RepoSettingsActivity.ACTION_CREATE); + startActivity(intent); + return true; case R.id.web_gui: startActivity(new Intent(this, WebGuiActivity.class)); return true; diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java new file mode 100644 index 00000000..33ab9cfd --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java @@ -0,0 +1,228 @@ +package com.nutomic.syncthingandroid.gui; + +import android.app.AlertDialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.preference.EditTextPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +import com.nutomic.syncthingandroid.R; +import com.nutomic.syncthingandroid.syncthing.RestApi; +import com.nutomic.syncthingandroid.syncthing.SyncthingService; +import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Shows node details and allows changing them. + */ +public class NodeSettingsActivity extends PreferenceActivity implements + Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener, + RestApi.OnReceiveConnectionsListener, RestApi.OnApiAvailableListener { + + public static final String ACTION_CREATE = "create"; + + public static final String ACTION_EDIT = "edit"; + + public static final String KEY_NODE_ID = "node_id"; + + private SyncthingService mSyncthingService; + + private final ServiceConnection mSyncthingServiceConnection = new ServiceConnection() { + + public void onServiceConnected(ComponentName className, IBinder service) { + SyncthingServiceBinder binder = (SyncthingServiceBinder) service; + mSyncthingService = binder.getService(); + mSyncthingService.getApi() + .registerOnApiAvailableListener(NodeSettingsActivity.this); + } + + public void onServiceDisconnected(ComponentName className) { + mSyncthingService = null; + } + }; + + private RestApi.Node mNode; + + private EditTextPreference mNodeId; + + private EditTextPreference mName; + + private EditTextPreference mAddresses; + + private Preference mVersion; + + private Preference mCurrentAddress; + + private Preference mDelete; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.node_settings); + + mNodeId = (EditTextPreference) findPreference("node_id"); + mNodeId.setOnPreferenceChangeListener(this); + mName = (EditTextPreference) findPreference("name"); + mName.setOnPreferenceChangeListener(this); + mAddresses = (EditTextPreference) findPreference("addresses"); + mAddresses.setOnPreferenceChangeListener(this); + mVersion = findPreference("version"); + mVersion.setSummary("?"); + mCurrentAddress = findPreference("current_address"); + mCurrentAddress.setSummary("?"); + mDelete = findPreference("delete"); + mDelete.setOnPreferenceClickListener(this); + + bindService(new Intent(this, SyncthingService.class), + mSyncthingServiceConnection, Context.BIND_AUTO_CREATE); + } + + @Override + public void onApiAvailable() { + if (getIntent().getAction().equals(ACTION_CREATE)) { + setTitle(R.string.create_node); + mNode = new RestApi.Node(); + mNode.Name = ""; + mNode.NodeID = ""; + mNode.Addresses = "dynamic"; + getPreferenceScreen().removePreference(mDelete); + } + else if (getIntent().getAction().equals(ACTION_EDIT)) { + setTitle(R.string.edit_node); + mNodeId.setEnabled(false); + List nodes = mSyncthingService.getApi().getNodes(); + for (int i = 0; i < nodes.size(); i++) { + if (nodes.get(i).NodeID.equals(getIntent().getStringExtra(KEY_NODE_ID))) { + mNode = nodes.get(i); + break; + } + } + } + mSyncthingService.getApi().getConnections(NodeSettingsActivity.this); + + mNodeId.setText(mNode.NodeID); + mNodeId.setSummary(mNode.NodeID); + mName.setText((mNode.Name)); + mName.setSummary(mNode.Name); + mAddresses.setText(mNode.Addresses); + mAddresses.setSummary(mNode.Addresses); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.node_settings_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + menu.findItem(R.id.create).setVisible(getIntent().getAction().equals(ACTION_CREATE)); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.create: + if (mNode.NodeID.equals("")) { + Toast.makeText(this, R.string.node_id_required, Toast.LENGTH_LONG).show(); + return true; + } + if (mNode.Name.equals("")) { + Toast.makeText(this, R.string.node_name_required, Toast.LENGTH_LONG).show(); + return true; + } + mSyncthingService.getApi().editNode(mNode, true); + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onDestroy() { + super.onDestroy(); + unbindService(mSyncthingServiceConnection); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object o) { + if (preference instanceof EditTextPreference) { + EditTextPreference pref = (EditTextPreference) preference; + pref.setSummary((String) o); + } + + if (preference.equals(mNodeId)) { + mNode.NodeID = (String) o; + nodeUpdated(); + return true; + } + else if (preference.equals(mName)) { + mNode.Name = (String) o; + nodeUpdated(); + return true; + } + else if (preference.equals(mAddresses)) { + mNode.Addresses = (String) o; + nodeUpdated(); + return true; + } + return false; + } + + @Override + public boolean onPreferenceClick(Preference preference) { + if (preference.equals(mDelete)) { + new AlertDialog.Builder(this) + .setMessage(R.string.delete_node_confirm) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + mSyncthingService.getApi().deleteNode(mNode); + finish(); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); + return true; + } + return false; + } + + /** + * Sets version and current address of the node. + * + * NOTE: This is only called once on startup, should be called more often to properly display + * version/address changes. + */ + @Override + public void onReceiveConnections(Map connections) { + if (connections.containsKey(mNode.NodeID)) { + mVersion.setSummary(connections.get(mNode.NodeID).ClientVersion); + mCurrentAddress.setSummary(connections.get(mNode.NodeID).Address); + } + } + + /** + * Sends the updated node info if in edit mode. + */ + private void nodeUpdated() { + if (getIntent().getAction().equals(ACTION_EDIT)) { + mSyncthingService.getApi().editNode(mNode, false); + } + } + +} diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/NodesFragment.java b/src/main/java/com/nutomic/syncthingandroid/gui/NodesFragment.java index aba153ec..a32354c4 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/NodesFragment.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/NodesFragment.java @@ -1,5 +1,11 @@ package com.nutomic.syncthingandroid.gui; +import android.content.Intent; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; + import com.nutomic.syncthingandroid.NodeAdapter; import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.syncthing.RestApi; @@ -12,7 +18,7 @@ import java.util.TimerTask; * Displays a list of all existing nodes. */ public class NodesFragment extends LoadingListFragment implements - RestApi.OnApiAvailableListener { + RestApi.OnApiAvailableListener, ListView.OnItemClickListener { private NodeAdapter mAdapter; @@ -58,4 +64,12 @@ public class NodesFragment extends LoadingListFragment implements } } + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + Intent intent = new Intent(getActivity(), NodeSettingsActivity.class); + intent.setAction(NodeSettingsActivity.ACTION_EDIT); + intent.putExtra(NodeSettingsActivity.KEY_NODE_ID, mAdapter.getItem(i).NodeID); + startActivity(intent); + } + } diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java new file mode 100644 index 00000000..b57ed711 --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java @@ -0,0 +1,280 @@ +package com.nutomic.syncthingandroid.gui; + +import android.app.AlertDialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +import com.nutomic.syncthingandroid.ExtendedCheckBoxPreference; +import com.nutomic.syncthingandroid.R; +import com.nutomic.syncthingandroid.syncthing.RestApi; +import com.nutomic.syncthingandroid.syncthing.SyncthingService; +import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder; + +import java.util.ArrayList; +import java.util.List; + +/** + * Shows repo details and allows changing them. + */ +public class RepoSettingsActivity extends PreferenceActivity + implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener, + RestApi.OnApiAvailableListener { + + public static final String ACTION_CREATE = "create"; + + public static final String ACTION_EDIT = "edit"; + + public static final String KEY_REPOSITORY_ID = "repository_id"; + + private static final String KEY_NODE_SHARED = "node_shared"; + + private SyncthingService mSyncthingService; + + private final ServiceConnection mSyncthingServiceConnection = new ServiceConnection() { + + public void onServiceConnected(ComponentName className, IBinder service) { + SyncthingServiceBinder binder = (SyncthingServiceBinder) service; + mSyncthingService = binder.getService(); + mSyncthingService.getApi() + .registerOnApiAvailableListener(RepoSettingsActivity.this); + } + + public void onServiceDisconnected(ComponentName className) { + mSyncthingService = null; + } + }; + + private RestApi.Repository mRepository; + + private EditTextPreference mRepositoryId; + + private EditTextPreference mDirectory; + + private CheckBoxPreference mRepositoryMaster; + + private PreferenceScreen mNodes; + + private CheckBoxPreference mVersioning; + + private EditTextPreference mVersioningKeep; + + private Preference mDelete; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.repo_settings); + + mRepositoryId = (EditTextPreference) findPreference("repository_id"); + mRepositoryId.setOnPreferenceChangeListener(this); + mDirectory = (EditTextPreference) findPreference("directory"); + mDirectory.setOnPreferenceChangeListener(this); + mRepositoryMaster = (CheckBoxPreference) findPreference("repository_master"); + mRepositoryMaster.setOnPreferenceChangeListener(this); + mNodes = (PreferenceScreen) findPreference("nodes"); + mVersioning = (CheckBoxPreference) findPreference("versioning"); + mVersioning.setOnPreferenceChangeListener(this); + mVersioningKeep = (EditTextPreference) findPreference("versioning_keep"); + mVersioningKeep.setOnPreferenceChangeListener(this); + mDelete = findPreference("delete"); + mDelete.setOnPreferenceClickListener(this); + + bindService(new Intent(this, SyncthingService.class), + mSyncthingServiceConnection, Context.BIND_AUTO_CREATE); + } + + @Override + public void onApiAvailable() { + if (getIntent().getAction().equals(ACTION_CREATE)) { + setTitle(R.string.create_repo); + mRepository = new RestApi.Repository(); + mRepository.ID = ""; + mRepository.Directory = ""; + mRepository.Nodes = new ArrayList(); + mRepository.Versioning = new RestApi.Versioning(); + getPreferenceScreen().removePreference(mDelete); + } + else if (getIntent().getAction().equals(ACTION_EDIT)) { + setTitle(R.string.edit_repo); + mRepositoryId.setEnabled(false); + List repos = mSyncthingService.getApi().getRepositories(); + for (int i = 0; i < repos.size(); i++) { + if (repos.get(i).ID.equals(getIntent().getStringExtra(KEY_REPOSITORY_ID))) { + mRepository = repos.get(i); + break; + } + } + } + + mRepositoryId.setText(mRepository.ID); + mRepositoryId.setSummary(mRepository.ID); + mDirectory.setText(mRepository.Directory); + mDirectory.setSummary(mRepository.Directory); + mRepositoryMaster.setChecked(mRepository.ReadOnly); + List nodesList = mSyncthingService.getApi().getNodes(); + for (RestApi.Node n : nodesList) { + ExtendedCheckBoxPreference cbp = + new ExtendedCheckBoxPreference(RepoSettingsActivity.this, n); + cbp.setTitle(n.Name); + cbp.setKey(KEY_NODE_SHARED); + cbp.setOnPreferenceChangeListener(RepoSettingsActivity.this); + cbp.setChecked(false); + for (RestApi.Node n2 : mRepository.Nodes) { + if (n2.NodeID.equals(n.NodeID)) { + cbp.setChecked(true); + } + } + mNodes.addPreference(cbp); + } + mVersioning.setChecked(mRepository.Versioning instanceof RestApi.SimpleVersioning); + if (mVersioning.isChecked()) { + mVersioningKeep.setText(mRepository.Versioning.getParams().get("keep")); + mVersioningKeep.setSummary(mRepository.Versioning.getParams().get("keep")); + mVersioningKeep.setEnabled(true); + } + else { + mVersioningKeep.setEnabled(false); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.repo_settings_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + menu.findItem(R.id.create).setVisible(getIntent().getAction().equals(ACTION_CREATE)); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.create: + if (mRepository.ID.equals("")) { + Toast.makeText(this, R.string.repo_id_required, Toast.LENGTH_LONG).show(); + return true; + } + if (mRepository.Directory.equals("")) { + Toast.makeText(this, R.string.repo_path_required, Toast.LENGTH_LONG).show(); + return true; + } + mSyncthingService.getApi().editRepository(mRepository, true); + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onDestroy() { + super.onDestroy(); + unbindService(mSyncthingServiceConnection); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object o) { + if (preference instanceof EditTextPreference) { + EditTextPreference pref = (EditTextPreference) preference; + pref.setSummary((String) o); + } + + if (preference.equals(mRepositoryId)) { + mRepository.ID = (String) o; + repositoryUpdated(); + return true; + } + else if (preference.equals(mDirectory)) { + mRepository.Directory = (String) o; + repositoryUpdated(); + return true; + } + else if (preference.equals(mRepositoryMaster)) { + mRepository.ReadOnly = (Boolean) o; + repositoryUpdated(); + return true; + } + else if (preference.getKey().equals(KEY_NODE_SHARED)) { + ExtendedCheckBoxPreference pref = (ExtendedCheckBoxPreference) preference; + RestApi.Node node = (RestApi.Node) pref.getObject(); + if ((Boolean) o) { + mRepository.Nodes.add(node); + } + else { + for (RestApi.Node n : mRepository.Nodes) { + if (n.NodeID.equals(node.NodeID)) { + mRepository.Nodes.remove(n); + } + } + } + repositoryUpdated(); + return true; + } + else if (preference.equals(mVersioning)) { + mVersioningKeep.setEnabled((Boolean) o); + if ((Boolean) o) { + RestApi.SimpleVersioning v = new RestApi.SimpleVersioning(); + mRepository.Versioning = v; + v.setParams(5); + mVersioningKeep.setText("5"); + mVersioningKeep.setSummary("5"); + } + else { + mRepository.Versioning = new RestApi.Versioning(); + } + repositoryUpdated(); + return true; + } + else if (preference.equals(mVersioningKeep)) { + ((RestApi.SimpleVersioning) mRepository.Versioning) + .setParams(Integer.parseInt((String) o)); + repositoryUpdated(); + return true; + } + + return false; + } + + @Override + public boolean onPreferenceClick(Preference preference) { + if (preference.equals(mDelete)) { + new AlertDialog.Builder(this) + .setMessage(R.string.delete_repo_confirm) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + mSyncthingService.getApi().deleteRepository(mRepository); + finish(); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); + return true; + } + return false; + } + + private void repositoryUpdated() { + if (getIntent().getAction().equals(ACTION_EDIT)) { + mSyncthingService.getApi().editRepository(mRepository, false); + } + } + +} diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/RepositoriesFragment.java b/src/main/java/com/nutomic/syncthingandroid/gui/RepositoriesFragment.java index dbe01fe5..2ca19695 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/RepositoriesFragment.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/RepositoriesFragment.java @@ -1,5 +1,9 @@ package com.nutomic.syncthingandroid.gui; +import android.content.Intent; +import android.view.View; +import android.widget.AdapterView; + import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.RepositoryAdapter; import com.nutomic.syncthingandroid.syncthing.RestApi; @@ -12,7 +16,7 @@ import java.util.TimerTask; * Displays a list of all existing repositories. */ public class RepositoriesFragment extends LoadingListFragment implements - RestApi.OnApiAvailableListener { + RestApi.OnApiAvailableListener, AdapterView.OnItemClickListener { private RepositoryAdapter mAdapter; @@ -58,4 +62,11 @@ public class RepositoriesFragment extends LoadingListFragment implements } } + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + Intent intent = new Intent(getActivity(), RepoSettingsActivity.class); + intent.setAction(RepoSettingsActivity.ACTION_EDIT); + intent.putExtra(RepoSettingsActivity.KEY_REPOSITORY_ID, mAdapter.getItem(i).ID); + startActivity(intent); + } } diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/SettingsActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/SettingsActivity.java index 33bb0051..47e9d884 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/SettingsActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/SettingsActivity.java @@ -1,5 +1,6 @@ package com.nutomic.syncthingandroid.gui; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.ComponentName; import android.content.Context; @@ -93,6 +94,7 @@ public class SettingsActivity extends PreferenceActivity * Manual target API as we manually check if ActionBar is available (for ActionBar back button). */ @Override + @SuppressLint("AppCompatMethod") @TargetApi(11) public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -106,7 +108,7 @@ public class SettingsActivity extends PreferenceActivity bindService(new Intent(this, SyncthingService.class), mSyncthingServiceConnection, Context.BIND_AUTO_CREATE); - addPreferencesFromResource(R.xml.settings); + addPreferencesFromResource(R.xml.app_settings); PreferenceScreen screen = getPreferenceScreen(); mVersion = screen.findPreference(SYNCTHING_VERSION_KEY); mOptionsScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_OPTIONS_KEY); diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/PostTask.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/PostTask.java index a5c9606e..f593bdbd 100644 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/PostTask.java +++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/PostTask.java @@ -38,7 +38,7 @@ public class PostTask extends AsyncTask { post.addHeader(new BasicHeader("X-API-Key", params[2])); try { - if (params.length > 2) { + if (params.length > 3) { post.setEntity(new StringEntity(params[3])); } httpclient.execute(post); diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java index 99302588..40d37fbc 100644 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java +++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java @@ -5,9 +5,12 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; import android.support.v4.app.NotificationCompat; import android.util.Log; +import com.nutomic.syncthingandroid.BuildConfig; import com.nutomic.syncthingandroid.R; import org.json.JSONArray; @@ -63,7 +66,6 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { public static class Repository { public String Directory; public String ID; - public final boolean IgnorePerms = true; public String Invalid; public List Nodes; public boolean ReadOnly; @@ -236,14 +238,14 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { */ public void shutdown() { mNotificationManager.cancel(NOTIFICATION_RESTART); - new PostTask().execute(mUrl, PostTask.URI_SHUTDOWN, mApiKey, ""); + new PostTask().execute(mUrl, PostTask.URI_SHUTDOWN, mApiKey); } /** * Restarts the syncthing binary. */ public void restart() { - new PostTask().execute(mUrl, PostTask.URI_RESTART); + new PostTask().execute(mUrl, PostTask.URI_RESTART, mApiKey); } /** @@ -307,7 +309,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { * Sends the updated mConfig via Rest API to syncthing and displays a "restart" notification. */ private void configUpdated() { - new PostTask().execute(mUrl, PostTask.URI_CONFIG, mConfig.toString()); + new PostTask().execute(mUrl, PostTask.URI_CONFIG, mApiKey, mConfig.toString()); Intent i = new Intent(mContext, SyncthingService.class) .setAction(SyncthingService.ACTION_RESTART); @@ -352,6 +354,9 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { new GetTask() { @Override protected void onPostExecute(String s) { + if (s == null) + return; + try { JSONObject system = new JSONObject(s); SystemInfo si = new SystemInfo(); @@ -395,6 +400,9 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { * Returns a list of all existing repositores. */ public List getRepositories() { + if (mConfig == null) + return new ArrayList(); + List ret = null; try { JSONArray repos = mConfig.getJSONArray("Repositories"); @@ -404,16 +412,16 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { Repository r = new Repository(); r.Directory = json.getString("Directory"); r.ID = json.getString("ID"); - // Hardcoded to true because missing permissions support. - // r.IgnorePerms = json.getBoolean("IgnorePerms"); r.Invalid = json.getString("Invalid"); r.Nodes = getNodes(json.getJSONArray("Nodes")); r.ReadOnly = json.getBoolean("ReadOnly"); JSONObject versioning = json.getJSONObject("Versioning"); if (versioning.getString("Type").equals("simple")) { + Log.d(TAG, mConfig.toString()); SimpleVersioning sv = new SimpleVersioning(); JSONObject params = versioning.getJSONObject("Params"); + Log.d(TAG, params.toString()); sv.setParams(params.getInt("keep")); r.Versioning = sv; } @@ -486,6 +494,9 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { new GetTask() { @Override protected void onPostExecute(String s) { + if (s == null) + return; + Long now = System.currentTimeMillis(); Long difference = (now - mPreviousConnectionTime) / 1000; if (difference < 1) { @@ -547,6 +558,9 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { new GetTask() { @Override protected void onPostExecute(String s) { + if (s == null) + return; + try { JSONObject json = new JSONObject(s); Model m = new Model(); @@ -571,4 +585,128 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener { }.execute(mUrl, GetTask.URI_MODEL, mApiKey, "repo", repoId); } + /** + * Updates or creates the given node. + */ + public void editNode(Node node, boolean create) { + try { + JSONArray nodes = mConfig.getJSONArray("Nodes"); + JSONObject n = null; + if (create) { + n = new JSONObject(); + nodes.put(n); + } + else { + for (int i = 0; i < nodes.length(); i++) { + JSONObject json = nodes.getJSONObject(i); + if (node.NodeID.equals(json.getString("NodeID"))) { + n = nodes.getJSONObject(i); + break; + } + } + } + n.put("NodeID", node.NodeID); + n.put("Name", node.Name); + n.put("Addresses", listToJson(node.Addresses.split(" "))); + Log.d(TAG, n.toString()); + Log.d(TAG, nodes.toString()); + configUpdated(); + } + catch (JSONException e) { + Log.w(TAG, "Failed to read nodes", e); + } + } + + public void deleteNode(Node node) { + try { + JSONArray nodes = mConfig.getJSONArray("Nodes"); + + for (int i = 0; i < nodes.length(); i++) { + JSONObject json = nodes.getJSONObject(i); + if (node.NodeID.equals(json.getString("NodeID"))) { + mConfig.remove("Nodes"); + mConfig.put("Nodes", delete(nodes, nodes.getJSONObject(i))); + break; + } + } + configUpdated(); + } + catch (JSONException e) { + Log.w(TAG, "Failed to edit repo", e); + } + } + + public void editRepository(Repository repository, boolean create) { + try { + JSONArray repos = mConfig.getJSONArray("Repositories"); + JSONObject r = null; + if (create) { + r = new JSONObject(); + repos.put(r); + } + else { + for (int i = 0; i < repos.length(); i++) { + JSONObject json = repos.getJSONObject(i); + if (repository.ID.equals(json.getString("ID"))) { + r = repos.getJSONObject(i); + break; + } + } + } + r.put("Directory", repository.Directory); + r.put("ID", repository.ID); + r.put("ReadOnly", repository.ReadOnly); + JSONArray nodes = new JSONArray(); + for (Node n : repository.Nodes) { + JSONObject element = new JSONObject(); + element.put("Addresses", n.Addresses); + element.put("Name", n.Name); + element.put("NodeID", n.NodeID); + nodes.put(element); + } + r.put("Nodes", nodes); + JSONObject versioning = new JSONObject(); + versioning.put("Type", repository.Versioning.getType()); + JSONObject params = new JSONObject(); + versioning.put("Params", params); + for (String key : repository.Versioning.getParams().keySet()) { + params.put(key, repository.Versioning.getParams().get(key)); + } + r.put("Versioning", versioning); + configUpdated(); + } + catch (JSONException e) { + Log.w(TAG, "Failed to edit repo " + repository.ID + " at " + repository.Directory, e); + } + } + + public void deleteRepository(Repository repository) { + try { + JSONArray repos = mConfig.getJSONArray("Repositories"); + + for (int i = 0; i < repos.length(); i++) { + JSONObject json = repos.getJSONObject(i); + if (repository.ID.equals(json.getString("ID"))) { + mConfig.remove("Repositories"); + mConfig.put("Repositories", delete(repos, repos.getJSONObject(i))); + break; + } + } + configUpdated(); + } + catch (JSONException e) { + Log.w(TAG, "Failed to edit repo", e); + } + } + + private JSONArray delete(JSONArray array, JSONObject delete) throws JSONException { + JSONArray newArray = new JSONArray(); + for (int i = 0; i < array.length(); i++) { + if (!array.getJSONObject(i).equals(delete)) { + newArray.put(array.get(i)); + } + } + return newArray; + } + } diff --git a/src/main/res/menu/menu.xml b/src/main/res/menu/main_menu.xml similarity index 72% rename from src/main/res/menu/menu.xml rename to src/main/res/menu/main_menu.xml index 7b6b3e48..0f8e4697 100644 --- a/src/main/res/menu/menu.xml +++ b/src/main/res/menu/main_menu.xml @@ -8,6 +8,18 @@ android:icon="@android:drawable/ic_menu_share" android:title="@string/share_node_id" app:showAsAction="ifRoom" /> + + + + + + diff --git a/src/main/res/menu/node_settings_menu.xml b/src/main/res/menu/node_settings_menu.xml new file mode 100644 index 00000000..b73bbab2 --- /dev/null +++ b/src/main/res/menu/node_settings_menu.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/src/main/res/menu/repo_settings_menu.xml b/src/main/res/menu/repo_settings_menu.xml new file mode 100644 index 00000000..b73bbab2 --- /dev/null +++ b/src/main/res/menu/repo_settings_menu.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/src/main/res/values-large/styles.xml b/src/main/res/values-large/styles.xml new file mode 100644 index 00000000..44272522 --- /dev/null +++ b/src/main/res/values-large/styles.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/res/xml/settings.xml b/src/main/res/xml/app_settings.xml similarity index 100% rename from src/main/res/xml/settings.xml rename to src/main/res/xml/app_settings.xml diff --git a/src/main/res/xml/node_settings.xml b/src/main/res/xml/node_settings.xml new file mode 100644 index 00000000..05447692 --- /dev/null +++ b/src/main/res/xml/node_settings.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/xml/repo_settings.xml b/src/main/res/xml/repo_settings.xml new file mode 100644 index 00000000..1dcb91f6 --- /dev/null +++ b/src/main/res/xml/repo_settings.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file