diff --git a/res/values/strings.xml b/res/values/strings.xml index 9f82a190..2bd63ab7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -23,9 +23,6 @@ Light Connection HTTP Authentication - Login failed: API disabled. - Login failed: no data received. - Login failed: username or password incorrect. Logged in. No unread feeds. No unread headlines. @@ -35,15 +32,11 @@ Refresh feeds Close article Share article - Could not decode content (UnsupportedEncodingException) Sort feeds by unread count Load more... Show all articles Show unread articles Accept any SSL certificate - Error: no data received. - No feeds to display. - Error: invalid object received. Log sent and received data Toggle starred @@ -57,4 +50,24 @@ Download and display feed icons Enable feed categories Close category + No feeds to display + No headlines to display + + Error: %1$s + + No error + Error: Unknown error (see log) + Error: 401 unauthorized + Error: 403 forbidden + Error: 404 not found + Error: 500 server error + Error: other HTTP error (see log) + Error: SSL certificate rejected + Error: JSON parse failed + Error: I/O failure (server down?) + Error: unknown error (see log) + Error: API disabled for this user + Error: unknown API error (see log) + Error: username or password incorrect + diff --git a/src/org/fox/ttrss/ApiRequest.java b/src/org/fox/ttrss/ApiRequest.java index 9af9d33f..1611cbb9 100644 --- a/src/org/fox/ttrss/ApiRequest.java +++ b/src/org/fox/ttrss/ApiRequest.java @@ -1,6 +1,7 @@ package org.fox.ttrss; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; @@ -29,21 +30,27 @@ import android.util.Log; import com.google.gson.Gson; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; public class ApiRequest extends AsyncTask, Integer, JsonElement> { private final String TAG = this.getClass().getSimpleName(); - protected static final int STATUS_LOGIN_FAILED = 0; - protected static final int STATUS_OK = 1; - protected static final int STATUS_API_DISABLED = 2; - protected static final int STATUS_OTHER_ERROR = 3; + public enum ApiError { NO_ERROR, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, + HTTP_SERVER_ERROR, HTTP_OTHER_ERROR, SSL_REJECTED, PARSE_ERROR, IO_ERROR, OTHER_ERROR, API_DISABLED, API_UNKNOWN, LOGIN_FAILED }; + public static final int API_STATUS_OK = 0; + public static final int API_STATUS_ERR = 1; + private String m_api; private boolean m_trustAny = false; private boolean m_transportDebugging = false; + protected int m_httpStatusCode = 0; + protected int m_apiStatusCode = 0; private Context m_context; private SharedPreferences m_prefs; + + protected ApiError m_lastError; public ApiRequest(Context context) { m_context = context; @@ -53,6 +60,42 @@ public class ApiRequest extends AsyncTask, Integer, JsonE m_api = m_prefs.getString("ttrss_url", null); m_trustAny = m_prefs.getBoolean("ssl_trust_any", false); m_transportDebugging = m_prefs.getBoolean("transport_debugging", false); + m_lastError = ApiError.NO_ERROR; + + } + + protected int getErrorMessage() { + switch (m_lastError) { + case NO_ERROR: + return R.string.error_unknown; + case HTTP_UNAUTHORIZED: + return R.string.error_http_unauthorized; + case HTTP_FORBIDDEN: + return R.string.error_http_forbidden; + case HTTP_NOT_FOUND: + return R.string.error_http_not_found; + case HTTP_SERVER_ERROR: + return R.string.error_http_server_error; + case HTTP_OTHER_ERROR: + return R.string.error_http_other_error; + case SSL_REJECTED: + return R.string.error_ssl_rejected; + case PARSE_ERROR: + return R.string.error_parse_error; + case IO_ERROR: + return R.string.error_io_error; + case OTHER_ERROR: + return R.string.error_other_error; + case API_DISABLED: + return R.string.error_api_disabled; + case API_UNKNOWN: + return R.string.error_api_unknown; + case LOGIN_FAILED: + return R.string.error_login_failed; + default: + Log.d(TAG, "getErrorMessage: unknown error code=" + m_lastError); + return R.string.error_unknown; + } } @Override @@ -106,25 +149,77 @@ public class ApiRequest extends AsyncTask, Integer, JsonE httpPost.setEntity(new StringEntity(requestStr, "utf-8")); HttpResponse execute = client.execute(httpPost); - InputStream content = execute.getEntity().getContent(); + m_httpStatusCode = execute.getStatusLine().getStatusCode(); - BufferedReader buffer = new BufferedReader( - new InputStreamReader(content), 8192); - - String s = ""; - String response = ""; - - while ((s = buffer.readLine()) != null) { - response += s; + switch (m_httpStatusCode) { + case 200: + InputStream content = execute.getEntity().getContent(); + + BufferedReader buffer = new BufferedReader( + new InputStreamReader(content), 8192); + + String s = ""; + String response = ""; + + while ((s = buffer.readLine()) != null) { + response += s; + } + + if (m_transportDebugging) Log.d(TAG, "<<< " + response); + + JsonParser parser = new JsonParser(); + + JsonElement result = parser.parse(response); + JsonObject resultObj = result.getAsJsonObject(); + + m_apiStatusCode = resultObj.get("status").getAsInt(); + + switch (m_apiStatusCode) { + case API_STATUS_OK: + return result.getAsJsonObject().get("content"); + case API_STATUS_ERR: + JsonObject contentObj = resultObj.get("content").getAsJsonObject(); + String error = contentObj.get("error").getAsString(); + + if (error.equals("LOGIN_ERROR")) { + m_lastError = ApiError.LOGIN_FAILED; + } else if (error.equals("API_DISABLED")) { + m_lastError = ApiError.LOGIN_FAILED; + } else { + m_lastError = ApiError.API_UNKNOWN; + } + } + + return null; + case 401: + m_lastError = ApiError.HTTP_UNAUTHORIZED; + break; + case 403: + m_lastError = ApiError.HTTP_FORBIDDEN; + break; + case 404: + m_lastError = ApiError.HTTP_NOT_FOUND; + break; + case 500: + m_lastError = ApiError.HTTP_SERVER_ERROR; + break; + default: + m_lastError = ApiError.HTTP_OTHER_ERROR; + break; } - - if (m_transportDebugging) Log.d(TAG, "<<< " + response); - - JsonParser parser = new JsonParser(); - - return parser.parse(response); + return null; + } catch (javax.net.ssl.SSLPeerUnverifiedException e) { + m_lastError = ApiError.SSL_REJECTED; + e.printStackTrace(); + } catch (IOException e) { + m_lastError = ApiError.IO_ERROR; + e.printStackTrace(); + } catch (com.google.gson.JsonSyntaxException e) { + m_lastError = ApiError.PARSE_ERROR; + e.printStackTrace(); } catch (Exception e) { + m_lastError = ApiError.OTHER_ERROR; e.printStackTrace(); } diff --git a/src/org/fox/ttrss/ArticleFragment.java b/src/org/fox/ttrss/ArticleFragment.java index 9fc2619b..a2146838 100644 --- a/src/org/fox/ttrss/ArticleFragment.java +++ b/src/org/fox/ttrss/ArticleFragment.java @@ -94,31 +94,25 @@ public class ArticleFragment extends Fragment implements OnClickListener { // this is ridiculous // TODO white on black style for dark theme String content; - try { - String cssOverride = ""; - - if (m_prefs.getString("theme", "THEME_DARK").equals("THEME_DARK")) { - web.setBackgroundColor(android.R.color.black); - cssOverride = "body { background : black; color : #f0f0f0}\n"; - } - - content = - "" + - "" + - "" + - "" + - "" + - "" + m_article.content + ""; - - } catch (Exception e) { - content = getString(R.string.could_not_decode_content); - e.printStackTrace(); + String cssOverride = ""; + + if (m_prefs.getString("theme", "THEME_DARK").equals("THEME_DARK")) { + web.setBackgroundColor(android.R.color.black); + cssOverride = "body { background : black; color : #f0f0f0}\n"; } + content = + "" + + "" + + "" + + "" + + "" + + "" + m_article.content + ""; + web.loadDataWithBaseURL(null, content, "text/html", "utf-8", null); if (activity.isSmallScreen()) diff --git a/src/org/fox/ttrss/FeedCategoriesFragment.java b/src/org/fox/ttrss/FeedCategoriesFragment.java index 8c1cd9b5..5ef5b504 100644 --- a/src/org/fox/ttrss/FeedCategoriesFragment.java +++ b/src/org/fox/ttrss/FeedCategoriesFragment.java @@ -14,6 +14,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -162,52 +163,43 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe public CatsRequest(Context context) { super(context); } - + protected void onPostExecute(JsonElement result) { if (result != null) { try { - JsonObject rv = result.getAsJsonObject(); - - Gson gson = new Gson(); - - int status = rv.get("status").getAsInt(); - - if (status == 0) { - JsonArray content = rv.get("content").getAsJsonArray(); - if (content != null) { - Type listType = new TypeToken>() {}.getType(); - final List cats = gson.fromJson(content, listType); - - m_cats.clear(); - - for (FeedCategory c : cats) - m_cats.add(c); - - sortCats(); - - if (m_cats.size() == 0) - setLoadingStatus(R.string.error_no_feeds, false); - else - setLoadingStatus(R.string.blank, false); - - } - } else { - MainActivity activity = (MainActivity)getActivity(); - activity.login(); + JsonArray content = result.getAsJsonArray(); + if (content != null) { + Type listType = new TypeToken>() {}.getType(); + final List cats = new Gson().fromJson(content, listType); + + m_cats.clear(); + + for (FeedCategory c : cats) + m_cats.add(c); + + sortCats(); + + if (m_cats.size() == 0) + setLoadingStatus(R.string.no_feeds_to_display, false); + else + setLoadingStatus(R.string.blank, false); + + return; } + } catch (Exception e) { - e.printStackTrace(); - setLoadingStatus(R.string.error_invalid_object, false); - // report invalid object received + e.printStackTrace(); } - } else { - // report null object received, unless we've been awakened from sleep right in the right time - // so that current request failed - if (m_cats.size() == 0) setLoadingStatus(R.string.error_no_data, false); } - - return; - } + + if (m_lastError == ApiError.LOGIN_FAILED) { + MainActivity activity = (MainActivity)getActivity(); + activity.login(); + } else { + setLoadingStatus(getErrorMessage(), false); + } + } + } public void sortCats() { diff --git a/src/org/fox/ttrss/FeedsFragment.java b/src/org/fox/ttrss/FeedsFragment.java index a2438b27..6243f79b 100644 --- a/src/org/fox/ttrss/FeedsFragment.java +++ b/src/org/fox/ttrss/FeedsFragment.java @@ -51,7 +51,6 @@ import android.widget.TextView; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; public class FeedsFragment extends Fragment implements OnItemClickListener, OnSharedPreferenceChangeListener { @@ -220,7 +219,7 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh if (result != null) { try { - JsonElement iconsUrl = result.getAsJsonObject().get("content").getAsJsonObject().get("icons_dir"); + JsonElement iconsUrl = result.getAsJsonObject().get("icons_dir"); if (iconsUrl != null) { String iconsStr = iconsUrl.getAsString(); @@ -261,54 +260,45 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh public FeedsRequest(Context context) { super(context); } - + protected void onPostExecute(JsonElement result) { if (result != null) { try { - JsonObject rv = result.getAsJsonObject(); + JsonArray content = result.getAsJsonArray(); + if (content != null) { - Gson gson = new Gson(); - - int status = rv.get("status").getAsInt(); - - if (status == 0) { - JsonArray content = rv.get("content").getAsJsonArray(); - if (content != null) { - Type listType = new TypeToken>() {}.getType(); - final List feeds = gson.fromJson(content, listType); - - m_feeds.clear(); - - for (Feed f : feeds) - if (f.id > -10) // skip labels for now - m_feeds.add(f); - - sortFeeds(); - - if (m_feeds.size() == 0) - setLoadingStatus(R.string.error_no_feeds, false); - else - setLoadingStatus(R.string.blank, false); + Type listType = new TypeToken>() {}.getType(); + final List feeds = new Gson().fromJson(content, listType); + + m_feeds.clear(); + + for (Feed f : feeds) + if (f.id > -10) // skip labels for now + m_feeds.add(f); + + sortFeeds(); + + if (m_feeds.size() == 0) + setLoadingStatus(R.string.no_feeds_to_display, false); + else + setLoadingStatus(R.string.blank, false); - if (m_enableFeedIcons) getFeedIcons(); - - } - } else { - MainActivity activity = (MainActivity)getActivity(); - activity.login(); + if (m_enableFeedIcons) getFeedIcons(); + + return; } + } catch (Exception e) { - e.printStackTrace(); - setLoadingStatus(R.string.error_invalid_object, false); - // report invalid object received + e.printStackTrace(); } - } else { - // report null object received, unless we've been awakened from sleep right in the right time - // so that current request failed - if (m_feeds.size() == 0) setLoadingStatus(R.string.error_no_data, false); } - - return; + + if (m_lastError == ApiError.LOGIN_FAILED) { + MainActivity activity = (MainActivity)getActivity(); + activity.login(); + } else { + setLoadingStatus(getErrorMessage(), false); + } } } diff --git a/src/org/fox/ttrss/HeadlinesFragment.java b/src/org/fox/ttrss/HeadlinesFragment.java index 8e0d9756..defd5ee0 100644 --- a/src/org/fox/ttrss/HeadlinesFragment.java +++ b/src/org/fox/ttrss/HeadlinesFragment.java @@ -34,7 +34,6 @@ import android.widget.TextView; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; public class HeadlinesFragment extends Fragment implements OnItemClickListener { @@ -174,54 +173,50 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener { protected void onPostExecute(JsonElement result) { if (result != null) { try { - JsonObject rv = result.getAsJsonObject(); - - Gson gson = new Gson(); - - int status = rv.get("status").getAsInt(); - - if (status == 0) { - JsonArray content = rv.get("content").getAsJsonArray(); - if (content != null) { - Type listType = new TypeToken>() {}.getType(); - final List
articles = gson.fromJson(content, listType); - - if (m_offset == 0) - m_articles.clear(); - - int last_position = m_articles.size(); - - for (Article f : articles) - m_articles.add(f); - - m_adapter.notifyDataSetChanged(); - - ListView list = (ListView)getView().findViewById(R.id.headlines); - - if (list != null && m_offset != 0) { - list.setSelection(last_position+1); - } - - MainActivity activity = (MainActivity)getActivity(); - activity.setCanLoadMore(articles.size() >= 30); - activity.initMainMenu(); - - setLoadingStatus(R.string.blank, false); + JsonArray content = result.getAsJsonArray(); + if (content != null) { + Type listType = new TypeToken>() {}.getType(); + final List
articles = new Gson().fromJson(content, listType); + + if (m_offset == 0) + m_articles.clear(); + + int last_position = m_articles.size(); + + for (Article f : articles) + m_articles.add(f); + + m_adapter.notifyDataSetChanged(); + + ListView list = (ListView)getView().findViewById(R.id.headlines); + + if (list != null && m_offset != 0) { + list.setSelection(last_position+1); } - } else { - MainActivity activity = (MainActivity)getActivity(); - activity.login(); - } - } catch (Exception e) { - e.printStackTrace(); - setLoadingStatus(R.string.error_invalid_object, false); - } - } else { - setLoadingStatus(R.string.error_no_data, false); - } - - return; + + MainActivity activity = (MainActivity)getActivity(); + activity.setCanLoadMore(articles.size() >= 30); + activity.initMainMenu(); + + if (m_articles.size() == 0) + setLoadingStatus(R.string.no_headlines_to_display, false); + else + setLoadingStatus(R.string.blank, false); + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (m_lastError == ApiError.LOGIN_FAILED) { + MainActivity activity = (MainActivity)getActivity(); + activity.login(); + } else { + setLoadingStatus(getErrorMessage(), false); + } } public void setOffset(int skip) { diff --git a/src/org/fox/ttrss/MainActivity.java b/src/org/fox/ttrss/MainActivity.java index af6e239a..a5ffc0ed 100644 --- a/src/org/fox/ttrss/MainActivity.java +++ b/src/org/fox/ttrss/MainActivity.java @@ -633,60 +633,41 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe protected void onPostExecute(JsonElement result) { if (result != null) { try { - JsonObject rv = result.getAsJsonObject(); - - int status = rv.get("status").getAsInt(); - - if (status == 0) { - JsonObject content = rv.get("content").getAsJsonObject(); - if (content != null) { - m_sessionId = content.get("session_id").getAsString(); - - Log.d(TAG, "Authenticated!"); - - setLoadingStatus(R.string.loading_message, true); - - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - - if (m_enableCats) { - FeedCategoriesFragment frag = new FeedCategoriesFragment(); - ft.replace(R.id.cats_fragment, frag); - } else { - FeedsFragment frag = new FeedsFragment(); - ft.replace(R.id.feeds_fragment, frag); - } - - ft.commit(); - - loginSuccess(); - - } - } else { - JsonObject content = rv.get("content").getAsJsonObject(); + JsonObject content = result.getAsJsonObject(); + if (content != null) { + m_sessionId = content.get("session_id").getAsString(); - if (content != null) { - String error = content.get("error").getAsString(); + Log.d(TAG, "Authenticated!"); + + setLoadingStatus(R.string.loading_message, true); - m_sessionId = null; + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + + if (m_enableCats) { + FeedCategoriesFragment frag = new FeedCategoriesFragment(); + ft.replace(R.id.cats_fragment, frag); + } else { + FeedsFragment frag = new FeedsFragment(); + ft.replace(R.id.feeds_fragment, frag); + } - if (error.equals("LOGIN_ERROR")) { - setLoadingStatus(R.string.login_wrong_password, false); - } else if (error.equals("API_DISABLED")) { - setLoadingStatus(R.string.login_api_disabled, false); - } else { - setLoadingStatus(R.string.login_failed, false); - } - - m_menu.findItem(R.id.login).setVisible(true); - } + ft.commit(); + + loginSuccess(); + return; } + } catch (Exception e) { e.printStackTrace(); } - } else { - setLoadingStatus(R.string.login_no_data, false); } + + m_sessionId = null; + + setLoadingStatus(getErrorMessage(), false); + m_menu.findItem(R.id.login).setVisible(true); } + } @Override