package de.luhmer.owncloudnewsreader; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.util.Log; import android.util.Xml; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; import com.nbsp.materialfilepicker.ui.FilePickerActivity; import org.json.JSONArray; import org.json.JSONObject; import org.xmlpull.v1.XmlPullParser; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.URL; import java.util.HashMap; import java.util.List; import butterknife.Bind; import butterknife.ButterKnife; import butterknife.OnClick; import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm; import de.luhmer.owncloudnewsreader.database.model.Folder; import de.luhmer.owncloudnewsreader.helper.AsyncTaskHelper; import de.luhmer.owncloudnewsreader.helper.FileUtils; import de.luhmer.owncloudnewsreader.helper.OpmlXmlParser; import de.luhmer.owncloudnewsreader.helper.ThemeChooser; import de.luhmer.owncloudnewsreader.helper.URLConnectionReader; import de.luhmer.owncloudnewsreader.model.Tuple; import de.luhmer.owncloudnewsreader.reader.HttpJsonRequest; import de.luhmer.owncloudnewsreader.reader.owncloud.API; import de.luhmer.owncloudnewsreader.reader.owncloud.apiv2.APIv2; public class NewFeedActivity extends AppCompatActivity { /** * Keep track of the login task to ensure we can cancel it if requested. */ private AddNewFeedTask mAddFeedTask = null; // UI references. @Bind(R.id.et_feed_url) EditText mFeedUrlView; @Bind(R.id.sp_folder) Spinner mFolderView; @Bind(R.id.new_feed_progress) View mProgressView; @Bind(R.id.new_feed_form) View mLoginFormView; @Bind(R.id.btn_addFeed) Button mAddFeedButton; @Bind(R.id.toolbar) Toolbar toolbar; List<Folder> folders; @Override protected void onCreate(Bundle savedInstanceState) { ThemeChooser.chooseTheme(this); super.onCreate(savedInstanceState); setContentView(R.layout.activity_new_feed); ButterKnife.bind(this); if (toolbar != null) { setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(this); folders = dbConn.getListOfFolders(); folders.add(0, new Folder(0, "No folder")); String[] folderNames = new String[folders.size()]; for(int i = 0; i < folders.size(); i++) { folderNames[i] = folders.get(i).getLabel(); } ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, folderNames); mFolderView.setAdapter(spinnerArrayAdapter); Intent intent = getIntent(); String action = intent.getAction(); if (action != null) { String url = ""; if(action.equals(Intent.ACTION_VIEW)) { url = intent.getDataString(); } else if(action.equals(Intent.ACTION_SEND)) { url = intent.getStringExtra(Intent.EXTRA_TEXT); } if(url.endsWith(".opml")) { AsyncTaskHelper.StartAsyncTask(new ImportOpmlSubscriptionsTask(url, NewFeedActivity.this)); } //String scheme = intent.getScheme(); //ContentResolver resolver = getContentResolver(); //Uri uri = intent.getData(); Log.v("tag" , "Content intent detected: " + action + " : " + url); mFeedUrlView.setText(url); } } @OnClick(R.id.btn_addFeed) public void btnAddFeedClick() { //Hide keyboard InputMethodManager imm = (InputMethodManager)getSystemService( Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mFeedUrlView.getWindowToken(), 0); attemptAddNewFeed(); } @OnClick(R.id.btn_import_opml) public void importOpml() { Intent intentFilePicker = new Intent(this, FilePickerActivity.class); startActivityForResult(intentFilePicker, 1); } @OnClick(R.id.btn_export_opml) public void exportOpml() { String xml = OpmlXmlParser.GenerateOPML(this); String path = FileUtils.getPath(this) + "/../subscriptions.opml"; try { FileOutputStream fos = new FileOutputStream(new File(path)); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos); outputStreamWriter.write(xml); outputStreamWriter.close(); fos.close(); new AlertDialog.Builder(this) .setMessage("Successfully exported to: " + path) .setTitle("OPML Export") .setNeutralButton("Ok", null) .create() .show(); } catch (IOException e) { Log.e("Exception", "File write failed: " + e.toString()); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 1 && resultCode == RESULT_OK) { String filePath = data.getStringExtra(FilePickerActivity.RESULT_FILE_PATH); AsyncTaskHelper.StartAsyncTask(new ImportOpmlSubscriptionsTask(filePath, NewFeedActivity.this)); } } public static class ImportOpmlSubscriptionsTask extends AsyncTask<Void, Void, Boolean> { private final String mUrlToFile; private HashMap<String, String> extractedUrls; private ProgressDialog pd; private Context mContext; ImportOpmlSubscriptionsTask(String urlToFile, Context context) { this.mUrlToFile = urlToFile; this.mContext = context; } @Override protected void onPreExecute() { pd = new ProgressDialog(mContext); pd.setTitle("Parsing OMPL..."); pd.setMessage("Please wait."); pd.setCancelable(false); pd.setIndeterminate(true); pd.show(); super.onPreExecute(); } @Override protected Boolean doInBackground(Void... params) { String opmlContent; try { if(mUrlToFile.startsWith("http")) {//http[s] opmlContent = URLConnectionReader.getText(mUrlToFile.toString()); } else { opmlContent = getStringFromFile(mUrlToFile); } InputStream is = new ByteArrayInputStream(opmlContent.getBytes()); XmlPullParser parser = Xml.newPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); parser.setInput(is, null); parser.nextTag(); extractedUrls = OpmlXmlParser.ReadFeed(parser); publishProgress(); API api = new APIv2(HttpJsonRequest.getInstance().getRootUrl()); HashMap<String, Long> existingFolders = new HashMap<>(); InputStream isFolder = HttpJsonRequest.getInstance().PerformJsonRequest(api.getFolderUrl()); String folderJSON = convertStreamToString(isFolder); JSONArray jArrFolder = new JSONObject(folderJSON).getJSONArray("folders"); for(int i = 0; i < jArrFolder.length(); i++) { JSONObject folder = ((JSONObject) jArrFolder.get(i)); long folderId = folder.getLong("id"); String folderName = folder.getString("name"); existingFolders.put(folderName, folderId); } for(String feedUrl : extractedUrls.keySet()) { long folderId = 0; //id of the parent folder, 0 for root String folderName = extractedUrls.get(feedUrl); if(folderName != null) { //Get Folder ID (create folder if not exists) if(existingFolders.containsKey(folderName)) { //Check if folder exists folderId = existingFolders.get(folderName); } else { //If not, create a new one on the server Tuple<Integer, String> status = HttpJsonRequest.getInstance().performCreateFolderRequest(api.getFolderUrl(), folderName); if (status.key == 200 || status.key == 409) { //200 = Ok, 409 = If the folder exists already JSONObject jObj = new JSONObject(status.value).getJSONArray("folders").getJSONObject(0); folderId = jObj.getLong("id"); existingFolders.put(folderName, folderId); //Add folder to list of existing folder in order to prevent that the method tries to create it multiple times } else { throw new Exception("Failed to create folder on server!"); } } } int status = HttpJsonRequest.getInstance().performCreateFeedRequest(api.getFeedUrl(), feedUrl, folderId); if(status == 200 || status == 409) { } else { throw new Exception("Failed to create feed on server!"); } } } catch (Exception e) { e.printStackTrace(); return false; } return true; } @Override protected void onProgressUpdate(Void... values) { String text = "Extracted the following feeds:\n"; for (String url : extractedUrls.keySet()) { text += "\n" + url; } pd.setMessage(text); super.onProgressUpdate(values); } @Override protected void onPostExecute(Boolean result) { if (pd != null) { pd.dismiss(); } if(!result) { Toast.makeText(mContext, "Failed to parse OPML file", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(mContext, "Successfully imported OPML!", Toast.LENGTH_LONG).show(); } super.onPostExecute(result); } } /** * Attempts to sign in or register the account specified by the login form. * If there are form errors (invalid email, missing fields, etc.), the * errors are presented and no actual login attempt is made. */ public void attemptAddNewFeed() { if (mAddFeedTask != null) { return; } Folder folder = folders.get(mFolderView.getSelectedItemPosition()); // Reset errors. mFeedUrlView.setError(null); // Store values at the time of the login attempt. String urlToFeed = mFeedUrlView.getText().toString(); boolean cancel = false; View focusView = null; // Check for a valid email address. if (TextUtils.isEmpty(urlToFeed)) { mFeedUrlView.setError(getString(R.string.error_field_required)); focusView = mFeedUrlView; cancel = true; } else if (!isUrlValid(urlToFeed)) { mFeedUrlView.setError(getString(R.string.error_invalid_url)); focusView = mFeedUrlView; cancel = true; } if (cancel) { // There was an error; don't attempt login and focus the first // form field with an error. focusView.requestFocus(); } else { // Show a progress spinner, and kick off a background task to // perform the user login attempt. showProgress(true); mAddFeedTask = new AddNewFeedTask(urlToFeed, folder.getId());//TODO needs testing! mAddFeedTask.execute((Void) null); } } private boolean isUrlValid(String url) { try { new URL(url); return true; } catch (Exception ex) { ex.printStackTrace(); } return false; } /** * Shows the progress UI and hides the login form. */ public void showProgress(final boolean show) { int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime); mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); mLoginFormView.animate().setDuration(shortAnimTime).alpha( show ? 0 : 1).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); } }); mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); mProgressView.animate().setDuration(shortAnimTime).alpha( show ? 1 : 0).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); } }); } public final static String ADD_NEW_SUCCESS = "success"; /** * Represents an asynchronous login/registration task used to authenticate * the user. */ public class AddNewFeedTask extends AsyncTask<Void, Void, Boolean> { private final String mUrlToFeed; private final long mFolderId; AddNewFeedTask(String urlToFeed, long folderId) { this.mUrlToFeed = urlToFeed; this.mFolderId = folderId; } @Override protected Boolean doInBackground(Void... params) { API api = new APIv2(HttpJsonRequest.getInstance().getRootUrl()); try { int status = HttpJsonRequest.getInstance().performCreateFeedRequest(api.getFeedUrl(), mUrlToFeed, mFolderId); if(status == 200) { return true; } } catch(Exception ex) { ex.printStackTrace(); } return false; } @Override protected void onPostExecute(final Boolean success) { mAddFeedTask = null; showProgress(false); if (success) { Intent returnIntent = new Intent(); returnIntent.putExtra("success", true); setResult(RESULT_OK,returnIntent); finish(); } else { mFeedUrlView.setError(getString(R.string.login_dialog_text_something_went_wrong)); mFeedUrlView.requestFocus(); } } @Override protected void onCancelled() { mAddFeedTask = null; showProgress(false); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { // Respond to the action bar's Up/Home button case android.R.id.home: if(mAddFeedTask != null) mAddFeedTask.cancel(true); //NavUtils.navigateUpFromSameTask(this); finish(); return true; } return super.onOptionsItemSelected(item); } @NonNull public static String convertStreamToString(InputStream is) throws Exception { BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line).append("\n"); } reader.close(); return sb.toString(); } public static String getStringFromFile (String filePath) throws Exception { File fl = new File(filePath); FileInputStream fin = new FileInputStream(fl); String ret = convertStreamToString(fin); //Make sure you close all streams. fin.close(); return ret; } }