aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/wordpress/android/ui/stats/service/StatsService.java
diff options
context:
space:
mode:
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/ui/stats/service/StatsService.java')
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/stats/service/StatsService.java614
1 files changed, 614 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/service/StatsService.java b/WordPress/src/main/java/org/wordpress/android/ui/stats/service/StatsService.java
new file mode 100644
index 000000000..77e42c01a
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/service/StatsService.java
@@ -0,0 +1,614 @@
+package org.wordpress.android.ui.stats.service;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.text.TextUtils;
+
+import com.android.volley.Request;
+import com.android.volley.VolleyError;
+import com.wordpress.rest.RestRequest;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.networking.RestClientUtils;
+import org.wordpress.android.ui.stats.StatsEvents;
+import org.wordpress.android.ui.stats.StatsTimeframe;
+import org.wordpress.android.ui.stats.StatsUtils;
+import org.wordpress.android.ui.stats.StatsWidgetProvider;
+import org.wordpress.android.ui.stats.datasets.StatsTable;
+import org.wordpress.android.ui.stats.exceptions.StatsError;
+import org.wordpress.android.ui.stats.models.AuthorsModel;
+import org.wordpress.android.ui.stats.models.BaseStatsModel;
+import org.wordpress.android.ui.stats.models.ClicksModel;
+import org.wordpress.android.ui.stats.models.CommentFollowersModel;
+import org.wordpress.android.ui.stats.models.CommentsModel;
+import org.wordpress.android.ui.stats.models.FollowersModel;
+import org.wordpress.android.ui.stats.models.GeoviewsModel;
+import org.wordpress.android.ui.stats.models.InsightsAllTimeModel;
+import org.wordpress.android.ui.stats.models.InsightsLatestPostDetailsModel;
+import org.wordpress.android.ui.stats.models.InsightsLatestPostModel;
+import org.wordpress.android.ui.stats.models.InsightsPopularModel;
+import org.wordpress.android.ui.stats.models.PublicizeModel;
+import org.wordpress.android.ui.stats.models.ReferrersModel;
+import org.wordpress.android.ui.stats.models.SearchTermsModel;
+import org.wordpress.android.ui.stats.models.TagsContainerModel;
+import org.wordpress.android.ui.stats.models.TopPostsAndPagesModel;
+import org.wordpress.android.ui.stats.models.VideoPlaysModel;
+import org.wordpress.android.ui.stats.models.VisitModel;
+import org.wordpress.android.ui.stats.models.VisitsModel;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+
+import java.io.Serializable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import de.greenrobot.event.EventBus;
+
+/**
+ * Background service to retrieve Stats.
+ * Parsing of response(s) and submission of new network calls are done by using a ThreadPoolExecutor with a single thread.
+ */
+
+public class StatsService extends Service {
+ public static final String ARG_BLOG_ID = "blog_id";
+ public static final String ARG_PERIOD = "stats_period";
+ public static final String ARG_DATE = "stats_date";
+ public static final String ARG_SECTION = "stats_section";
+ public static final String ARG_MAX_RESULTS = "stats_max_results";
+ public static final String ARG_PAGE_REQUESTED = "stats_page_requested";
+
+ private static final int DEFAULT_NUMBER_OF_RESULTS = 12;
+ // The number of results to return per page for Paged REST endpoints. Numbers larger than 20 will default to 20 on the server.
+ public static final int MAX_RESULTS_REQUESTED_PER_PAGE = 20;
+
+ public enum StatsEndpointsEnum {
+ VISITS,
+ TOP_POSTS,
+ REFERRERS,
+ CLICKS,
+ GEO_VIEWS,
+ AUTHORS,
+ VIDEO_PLAYS,
+ COMMENTS,
+ FOLLOWERS_WPCOM,
+ FOLLOWERS_EMAIL,
+ COMMENT_FOLLOWERS,
+ TAGS_AND_CATEGORIES,
+ PUBLICIZE,
+ SEARCH_TERMS,
+ INSIGHTS_POPULAR,
+ INSIGHTS_ALL_TIME,
+ INSIGHTS_TODAY,
+ INSIGHTS_LATEST_POST_SUMMARY,
+ INSIGHTS_LATEST_POST_VIEWS;
+
+ public String getRestEndpointPath() {
+ switch (this) {
+ case VISITS:
+ return "visits";
+ case TOP_POSTS:
+ return "top-posts";
+ case REFERRERS:
+ return "referrers";
+ case CLICKS:
+ return "clicks";
+ case GEO_VIEWS:
+ return "country-views";
+ case AUTHORS:
+ return "top-authors";
+ case VIDEO_PLAYS:
+ return "video-plays";
+ case COMMENTS:
+ return "comments";
+ case FOLLOWERS_WPCOM:
+ return "followers?type=wpcom";
+ case FOLLOWERS_EMAIL:
+ return "followers?type=email";
+ case COMMENT_FOLLOWERS:
+ return "comment-followers";
+ case TAGS_AND_CATEGORIES:
+ return "tags";
+ case PUBLICIZE:
+ return "publicize";
+ case SEARCH_TERMS:
+ return "search-terms";
+ case INSIGHTS_POPULAR:
+ return "insights";
+ case INSIGHTS_ALL_TIME:
+ return "";
+ case INSIGHTS_TODAY:
+ return "summary";
+ case INSIGHTS_LATEST_POST_SUMMARY:
+ return "posts";
+ case INSIGHTS_LATEST_POST_VIEWS:
+ return "post";
+ default:
+ AppLog.i(T.STATS, "Called an update of Stats of unknown section!?? " + this.name());
+ return "";
+ }
+ }
+
+ public StatsEvents.SectionUpdatedAbstract getEndpointUpdateEvent(final String blogId, final StatsTimeframe timeframe, final String date,
+ final int maxResultsRequested, final int pageRequested, final BaseStatsModel data) {
+ switch (this) {
+ case VISITS:
+ return new StatsEvents.VisitorsAndViewsUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (VisitsModel)data);
+ case TOP_POSTS:
+ return new StatsEvents.TopPostsUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (TopPostsAndPagesModel)data);
+ case REFERRERS:
+ return new StatsEvents.ReferrersUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (ReferrersModel)data);
+ case CLICKS:
+ return new StatsEvents.ClicksUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (ClicksModel)data);
+ case AUTHORS:
+ return new StatsEvents.AuthorsUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (AuthorsModel)data);
+ case GEO_VIEWS:
+ return new StatsEvents.CountriesUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (GeoviewsModel)data);
+ case VIDEO_PLAYS:
+ return new StatsEvents.VideoPlaysUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (VideoPlaysModel)data);
+ case SEARCH_TERMS:
+ return new StatsEvents.SearchTermsUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (SearchTermsModel)data);
+ case COMMENTS:
+ return new StatsEvents.CommentsUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (CommentsModel)data);
+ case COMMENT_FOLLOWERS:
+ return new StatsEvents.CommentFollowersUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (CommentFollowersModel)data);
+ case TAGS_AND_CATEGORIES:
+ return new StatsEvents.TagsUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (TagsContainerModel)data);
+ case PUBLICIZE:
+ return new StatsEvents.PublicizeUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (PublicizeModel)data);
+ case FOLLOWERS_WPCOM:
+ return new StatsEvents.FollowersWPCOMUdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (FollowersModel)data);
+ case FOLLOWERS_EMAIL:
+ return new StatsEvents.FollowersEmailUdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (FollowersModel)data);
+ case INSIGHTS_POPULAR:
+ return new StatsEvents.InsightsPopularUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (InsightsPopularModel)data);
+ case INSIGHTS_ALL_TIME:
+ return new StatsEvents.InsightsAllTimeUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (InsightsAllTimeModel)data);
+ case INSIGHTS_TODAY:
+ return new StatsEvents.VisitorsAndViewsUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (VisitsModel)data);
+ case INSIGHTS_LATEST_POST_SUMMARY:
+ return new StatsEvents.InsightsLatestPostSummaryUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (InsightsLatestPostModel)data);
+ case INSIGHTS_LATEST_POST_VIEWS:
+ return new StatsEvents.InsightsLatestPostDetailsUpdated(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, (InsightsLatestPostDetailsModel)data);
+ default:
+ AppLog.e(T.STATS, "Can't find an Update Event that match the current endpoint: " + this.name());
+ }
+
+ return null;
+ }
+ }
+
+ private int mServiceStartId;
+ private final LinkedList<Request<JSONObject>> mStatsNetworkRequests = new LinkedList<>();
+ private final ThreadPoolExecutor singleThreadNetworkHandler = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ AppLog.i(T.STATS, "service created");
+ }
+
+ @Override
+ public void onDestroy() {
+ stopRefresh();
+ AppLog.i(T.STATS, "service destroyed");
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent == null) {
+ AppLog.e(T.STATS, "StatsService was killed and restarted with a null intent.");
+ // if this service's process is killed while it is started (after returning from onStartCommand(Intent, int, int)),
+ // then leave it in the started state but don't retain this delivered intent.
+ // Later the system will try to re-create the service.
+ // Because it is in the started state, it will guarantee to call onStartCommand(Intent, int, int) after creating the new service instance;
+ // if there are not any pending start commands to be delivered to the service, it will be called with a null intent object.
+ stopRefresh();
+ return START_NOT_STICKY;
+ }
+
+ final String blogId = intent.getStringExtra(ARG_BLOG_ID);
+ if (TextUtils.isEmpty(blogId)) {
+ AppLog.e(T.STATS, "StatsService was started with a blank blog_id ");
+ return START_NOT_STICKY;
+ }
+
+ final StatsTimeframe period;
+ if (intent.hasExtra(ARG_PERIOD)) {
+ period = (StatsTimeframe) intent.getSerializableExtra(ARG_PERIOD);
+ } else {
+ period = StatsTimeframe.DAY;
+ }
+
+ final String requestedDate;
+ if (intent.getStringExtra(ARG_DATE) == null) {
+ AppLog.w(T.STATS, "StatsService is started with a NULL date on this blogID - "
+ + blogId + ". Using current date!!!");
+ int parsedBlogID = Integer.parseInt(blogId);
+ int localTableBlogId = WordPress.wpDB.getLocalTableBlogIdForRemoteBlogId(parsedBlogID);
+ requestedDate = StatsUtils.getCurrentDateTZ(localTableBlogId);
+ } else {
+ requestedDate = intent.getStringExtra(ARG_DATE);
+ }
+
+ final int maxResultsRequested = intent.getIntExtra(ARG_MAX_RESULTS, DEFAULT_NUMBER_OF_RESULTS);
+ final int pageRequested = intent.getIntExtra(ARG_PAGE_REQUESTED, -1);
+
+ int[] sectionFromIntent = intent.getIntArrayExtra(ARG_SECTION);
+
+ this.mServiceStartId = startId;
+ for (int i=0; i < sectionFromIntent.length; i++){
+ final StatsEndpointsEnum currentSectionsToUpdate = StatsEndpointsEnum.values()[sectionFromIntent[i]];
+ singleThreadNetworkHandler.submit(new Thread() {
+ @Override
+ public void run() {
+ startTasks(blogId, period, requestedDate, currentSectionsToUpdate, maxResultsRequested, pageRequested);
+ }
+ });
+ }
+
+ return START_NOT_STICKY;
+ }
+
+ private void stopRefresh() {
+ synchronized (mStatsNetworkRequests) {
+ this.mServiceStartId = 0;
+ for (Request<JSONObject> req : mStatsNetworkRequests) {
+ if (req != null && !req.hasHadResponseDelivered() && !req.isCanceled()) {
+ req.cancel();
+ }
+ }
+ mStatsNetworkRequests.clear();
+ }
+ }
+
+ // A fast way to disable caching during develop or when we want to disable it
+ // under some circumstances. Always true for now.
+ private boolean isCacheEnabled() {
+ return true;
+ }
+
+ // Check if we already have Stats
+ private String getCachedStats(final String blogId, final StatsTimeframe timeframe, final String date, final StatsEndpointsEnum sectionToUpdate,
+ final int maxResultsRequested, final int pageRequested) {
+ if (!isCacheEnabled()) {
+ return null;
+ }
+
+ int parsedBlogID = Integer.parseInt(blogId);
+ int localTableBlogId = WordPress.wpDB.getLocalTableBlogIdForRemoteBlogId(parsedBlogID);
+ return StatsTable.getStats(this, localTableBlogId, timeframe, date, sectionToUpdate, maxResultsRequested, pageRequested);
+ }
+
+ private void startTasks(final String blogId, final StatsTimeframe timeframe, final String date, final StatsEndpointsEnum sectionToUpdate,
+ final int maxResultsRequested, final int pageRequested) {
+
+ EventBus.getDefault().post(new StatsEvents.UpdateStatusChanged(true));
+
+ String cachedStats = getCachedStats(blogId, timeframe, date, sectionToUpdate, maxResultsRequested, pageRequested);
+ if (cachedStats != null) {
+ BaseStatsModel mResponseObjectModel;
+ try {
+ JSONObject response = new JSONObject(cachedStats);
+ mResponseObjectModel = StatsUtils.parseResponse(sectionToUpdate, blogId, response);
+
+ EventBus.getDefault().post(
+ sectionToUpdate.getEndpointUpdateEvent(blogId, timeframe, date,
+ maxResultsRequested, pageRequested, mResponseObjectModel)
+ );
+
+ updateWidgetsUI(blogId, sectionToUpdate, timeframe, date, pageRequested, mResponseObjectModel);
+ checkAllRequestsFinished(null);
+ return;
+ } catch (JSONException e) {
+ AppLog.e(AppLog.T.STATS, e);
+ }
+ }
+
+ final RestClientUtils restClientUtils = WordPress.getRestClientUtilsV1_1();
+
+ String period = timeframe.getLabelForRestCall();
+ /*AppLog.i(T.STATS, "A new Stats network request is required for blogID: " + blogId + " - period: " + period
+ + " - date: " + date + " - StatsType: " + sectionToUpdate.name());
+*/
+
+
+ RestListener vListener = new RestListener(sectionToUpdate, blogId, timeframe, date, maxResultsRequested, pageRequested);
+
+ final String periodDateMaxPlaceholder = "?period=%s&date=%s&max=%s";
+
+ String path = String.format("/sites/%s/stats/" + sectionToUpdate.getRestEndpointPath(), blogId);
+ synchronized (mStatsNetworkRequests) {
+ switch (sectionToUpdate) {
+ case VISITS:
+ path = String.format(path + "?unit=%s&quantity=15&date=%s", period, date);
+ break;
+ case TOP_POSTS:
+ case REFERRERS:
+ case CLICKS:
+ case GEO_VIEWS:
+ case AUTHORS:
+ case VIDEO_PLAYS:
+ case SEARCH_TERMS:
+ path = String.format(path + periodDateMaxPlaceholder, period, date, maxResultsRequested);
+ break;
+ case TAGS_AND_CATEGORIES:
+ case PUBLICIZE:
+ path = String.format(path + "?max=%s", maxResultsRequested);
+ break;
+ case COMMENTS:
+ // No parameters
+ break;
+ case FOLLOWERS_WPCOM:
+ if (pageRequested < 1) {
+ path = String.format(path + "&max=%s", maxResultsRequested);
+ } else {
+ path = String.format(path + "&period=%s&date=%s&max=%s&page=%s",
+ period, date, maxResultsRequested, pageRequested);
+ }
+ break;
+ case FOLLOWERS_EMAIL:
+ if (pageRequested < 1) {
+ path = String.format(path + "&max=%s", maxResultsRequested);
+ } else {
+ path = String.format(path + "&period=%s&date=%s&max=%s&page=%s",
+ period, date, maxResultsRequested, pageRequested);
+ }
+ break;
+ case COMMENT_FOLLOWERS:
+ if (pageRequested < 1) {
+ path = String.format(path + "?max=%s", maxResultsRequested);
+ } else {
+ path = String.format(path + "?period=%s&date=%s&max=%s&page=%s", period,
+ date, maxResultsRequested, pageRequested);
+ }
+ break;
+ case INSIGHTS_ALL_TIME:
+ case INSIGHTS_POPULAR:
+ break;
+ case INSIGHTS_TODAY:
+ path = String.format(path + "?period=day&date=%s", date);
+ break;
+ case INSIGHTS_LATEST_POST_SUMMARY:
+ // This is an edge cases since we're not loading stats but posts
+ path = String.format("/sites/%s/%s", blogId, sectionToUpdate.getRestEndpointPath()
+ + "?order_by=date&number=1&type=post&fields=ID,title,URL,discussion,like_count,date");
+ break;
+ case INSIGHTS_LATEST_POST_VIEWS:
+ // This is a kind of edge case, since we used the pageRequested parameter to request a single postID
+ path = String.format(path + "/%s?fields=views", pageRequested);
+ break;
+ default:
+ AppLog.i(T.STATS, "Called an update of Stats of unknown section!?? " + sectionToUpdate.name());
+ return;
+ }
+
+ // We need to check if we already have the same request in the queue
+ if (checkIfRequestShouldBeEnqueued(restClientUtils, path)) {
+ AppLog.d(AppLog.T.STATS, "Enqueuing the following Stats request " + path);
+ Request<JSONObject> currentRequest = restClientUtils.get(path, vListener, vListener);
+ vListener.currentRequest = currentRequest;
+ currentRequest.setTag("StatsCall");
+ mStatsNetworkRequests.add(currentRequest);
+ } else {
+ AppLog.d(AppLog.T.STATS, "Stats request is already in the queue:" + path);
+ }
+ }
+ }
+
+ /**
+ * This method checks if we already have the same request in the Queue. No need to re-enqueue a new request
+ * if one with the same parameters is there.
+ *
+ * This method is a kind of tricky, since it does the comparison by checking the origin URL of requests.
+ * To do that we had to get the fullURL of the new request by calling a method of the REST client `getAbsoluteURL`.
+ * That's good for now, but could lead to errors if the RestClient changes the way the URL is constructed internally,
+ * by calling `getAbsoluteURL`.
+ *
+ * - Another approach would involve the get of the requests ErrorListener and the check Listener's parameters.
+ * - Cleanest approach is for sure to create a new class that extends Request<JSONObject> and stores parameters for later comparison,
+ * unfortunately we have to change the REST Client and RestClientUtils a lot if we want follow this way...
+ *
+ */
+ private boolean checkIfRequestShouldBeEnqueued(final RestClientUtils restClientUtils, String path) {
+ String absoluteRequestPath = restClientUtils.getRestClient().getAbsoluteURL(path);
+ Iterator<Request<JSONObject>> it = mStatsNetworkRequests.iterator();
+ while (it.hasNext()) {
+ Request<JSONObject> req = it.next();
+ if (!req.hasHadResponseDelivered() && !req.isCanceled() &&
+ absoluteRequestPath.equals(req.getOriginUrl())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Call an updates on the installed widgets if the blog is the primary, the endpoint is Visits
+ // the timeframe is DAY or INSIGHTS, and the date = TODAY
+ private void updateWidgetsUI(String blogId, final StatsEndpointsEnum endpointName,
+ StatsTimeframe timeframe, String date, int pageRequested,
+ Serializable responseObjectModel) {
+ if (pageRequested != -1) {
+ return;
+ }
+ if (endpointName != StatsEndpointsEnum.VISITS) {
+ return;
+ }
+ if (timeframe != StatsTimeframe.DAY && timeframe != StatsTimeframe.INSIGHTS) {
+ return;
+ }
+
+ int parsedBlogID = Integer.parseInt(blogId);
+ int localTableBlogId = WordPress.wpDB.getLocalTableBlogIdForRemoteBlogId(parsedBlogID);
+ // make sure the data is for the current date
+ if (!date.equals(StatsUtils.getCurrentDateTZ(localTableBlogId))) {
+ return;
+ }
+
+ if (responseObjectModel == null) {
+ // TODO What we want to do here?
+ return;
+ }
+
+ if (!StatsWidgetProvider.isBlogDisplayedInWidget(parsedBlogID)) {
+ AppLog.d(AppLog.T.STATS, "The blog with remoteID " + parsedBlogID + " is NOT displayed in any widget. Stats Service doesn't call an update of the widget.");
+ return;
+ }
+
+ if (responseObjectModel instanceof VisitsModel) {
+ VisitsModel visitsModel = (VisitsModel) responseObjectModel;
+ if (visitsModel.getVisits() == null || visitsModel.getVisits().size() == 0) {
+ return;
+ }
+ List<VisitModel> visits = visitsModel.getVisits();
+ VisitModel data = visits.get(visits.size() - 1);
+ StatsWidgetProvider.updateWidgets(getApplicationContext(), parsedBlogID, data);
+ } else if (responseObjectModel instanceof VolleyError) {
+ VolleyError error = (VolleyError) responseObjectModel;
+ StatsWidgetProvider.updateWidgets(getApplicationContext(), parsedBlogID, error);
+ } else if (responseObjectModel instanceof StatsError) {
+ StatsError statsError = (StatsError) responseObjectModel;
+ StatsWidgetProvider.updateWidgets(getApplicationContext(), parsedBlogID, statsError);
+ }
+ }
+
+ private class RestListener implements RestRequest.Listener, RestRequest.ErrorListener {
+ final String mRequestBlogId;
+ private final StatsTimeframe mTimeframe;
+ final StatsEndpointsEnum mEndpointName;
+ private final String mDate;
+ private Request<JSONObject> currentRequest;
+ private final int mMaxResultsRequested, mPageRequested;
+
+ public RestListener(StatsEndpointsEnum endpointName, String blogId, StatsTimeframe timeframe, String date,
+ final int maxResultsRequested, final int pageRequested) {
+ mRequestBlogId = blogId;
+ mTimeframe = timeframe;
+ mEndpointName = endpointName;
+ mDate = date;
+ mMaxResultsRequested = maxResultsRequested;
+ mPageRequested = pageRequested;
+ }
+
+ @Override
+ public void onResponse(final JSONObject response) {
+ singleThreadNetworkHandler.submit(new Thread() {
+ @Override
+ public void run() {
+ // do other stuff here
+ BaseStatsModel mResponseObjectModel = null;
+ if (response != null) {
+ try {
+ //AppLog.d(T.STATS, response.toString());
+ mResponseObjectModel = StatsUtils.parseResponse(mEndpointName, mRequestBlogId, response);
+ if (isCacheEnabled()) {
+ int parsedBlogID = Integer.parseInt(mRequestBlogId);
+ int localTableBlogId = WordPress.wpDB.getLocalTableBlogIdForRemoteBlogId(parsedBlogID);
+ StatsTable.insertStats(StatsService.this, localTableBlogId, mTimeframe, mDate, mEndpointName,
+ mMaxResultsRequested, mPageRequested,
+ response.toString(), System.currentTimeMillis());
+ }
+ } catch (JSONException e) {
+ AppLog.e(AppLog.T.STATS, e);
+ }
+ }
+
+ EventBus.getDefault().post(
+ mEndpointName.getEndpointUpdateEvent(mRequestBlogId, mTimeframe, mDate,
+ mMaxResultsRequested, mPageRequested, mResponseObjectModel)
+ );
+
+ updateWidgetsUI(mRequestBlogId, mEndpointName, mTimeframe, mDate, mPageRequested, mResponseObjectModel);
+ checkAllRequestsFinished(currentRequest);
+ }
+ });
+ }
+
+ @Override
+ public void onErrorResponse(final VolleyError volleyError) {
+ singleThreadNetworkHandler.submit(new Thread() {
+ @Override
+ public void run() {
+ AppLog.e(T.STATS, "Error while loading Stats!");
+ StatsUtils.logVolleyErrorDetails(volleyError);
+ BaseStatsModel mResponseObjectModel = null;
+ // Check here if this is an authentication error
+ // .com authentication errors are handled automatically by the app
+ if (volleyError instanceof com.android.volley.AuthFailureError) {
+ int localId = StatsUtils.getLocalBlogIdFromRemoteBlogId(
+ Integer.parseInt(mRequestBlogId)
+ );
+ Blog blog = WordPress.wpDB.instantiateBlogByLocalId(localId);
+ if (blog != null && blog.isJetpackPowered()) {
+ // It's a kind of edge case, but the Jetpack site could have REST Disabled
+ // In that case (only used in insights for now) shows the error in the module that use the REST API
+ if (!StatsUtils.isRESTDisabledError(volleyError)) {
+ EventBus.getDefault().post(new StatsEvents.JetpackAuthError(localId));
+ }
+ }
+ }
+
+
+ EventBus.getDefault().post(new StatsEvents.SectionUpdateError(mEndpointName, mRequestBlogId, mTimeframe, mDate,
+ mMaxResultsRequested, mPageRequested, volleyError));
+
+ updateWidgetsUI(mRequestBlogId, mEndpointName, mTimeframe, mDate, mPageRequested, mResponseObjectModel);
+ checkAllRequestsFinished(currentRequest);
+ }
+ });
+ }
+ }
+
+ private void stopService() {
+ /* Stop the service if this is the current response, or mServiceBlogId is null
+ String currentServiceBlogId = getServiceBlogId();
+ if (currentServiceBlogId == null || currentServiceBlogId.equals(mRequestBlogId)) {
+ stopService();
+ }*/
+ EventBus.getDefault().post(new StatsEvents.UpdateStatusChanged(false));
+ stopSelf(mServiceStartId);
+ }
+
+
+ private void checkAllRequestsFinished(Request<JSONObject> req) {
+ synchronized (mStatsNetworkRequests) {
+ if (req != null) {
+ mStatsNetworkRequests.remove(req);
+ }
+ boolean isStillWorking = mStatsNetworkRequests.size() > 0 || singleThreadNetworkHandler.getQueue().size() > 0;
+ EventBus.getDefault().post(new StatsEvents.UpdateStatusChanged(isStillWorking));
+ }
+ }
+} \ No newline at end of file