ca.zadrox.dota2esportticker.service.UpdateMatchService.java Source code

Java tutorial

Introduction

Here is the source code for ca.zadrox.dota2esportticker.service.UpdateMatchService.java

Source

/*
 * Copyright 2014 Nicholas Liu
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ca.zadrox.dota2esportticker.service;

import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import ca.zadrox.dota2esportticker.data.MatchContract;
import ca.zadrox.dota2esportticker.data.http.match.model.BundledMatchItem;
import ca.zadrox.dota2esportticker.util.LogUtils;
import ca.zadrox.dota2esportticker.util.MatchGetter;
import ca.zadrox.dota2esportticker.util.PrefUtils;
import ca.zadrox.dota2esportticker.util.TimeUtils;

/**
 * Created by Acco on 11/13/2014.
 */
public class UpdateMatchService extends IntentService {
    private static final String TAG = UpdateMatchService.class.getSimpleName();

    public static final String ACTION_UPDATE_MATCHES = "ca.zadrox.dota2esportticker.action.ACTION_UPDATE_MATCHES";

    public static final String ACTION_AUTO_UPDATE_MATCHES = "ca.zadrox.dota2esportticker.action.ACTION_AUTO_UPDATE_MATCHES";

    public static final String ACTION_UPDATE_RESULTS = "ca.zadrox.dota2esportticker.action.ACTION_UPDATE_RESULTS";

    public static final String ACTION_SCHEDULE_AUTO_UPDATE = "ca.zadrox.dota2esportticker.action.ACTION_SCHEDULE_AUTO_UPDATE";

    public static final String ACTION_CANCEL_AUTO_UPDATE = "ca.zadrox.dota2esportticker.action.ACTION_CANCEL_AUTO_UPDATE";

    public static final String UPDATE_COMPLETE = "ca.zadrox.dota2esportticker.action.UPDATE_COMPLETE";

    public static final String UPDATE_INTERRUPTED = "ca.zadrox.dota2esportticker.action.UPDATE_INTERRUPTED";

    public static final String UPDATE_NO_CONNECTIVITY = "ca.zadrox.dota2esportticker.action.UPDATE_NO_CONNECTIVITY";

    public static final String UPDATE_STARTED = "ca.zadrox.dota2esportticker.action.UPDATE_STARTED";

    public UpdateMatchService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        final String action = intent.getAction();

        if (ACTION_UPDATE_MATCHES.equals(action)) {
            updateMatches(true);
        }

        if (ACTION_AUTO_UPDATE_MATCHES.equals(action)) {
            autoUpdateMatches();
        }

        if (ACTION_SCHEDULE_AUTO_UPDATE.equals(action)) {
            scheduleAutoUpdates();
        }

        if (ACTION_CANCEL_AUTO_UPDATE.equals(action)) {
            cancelAutoUpdate();
        }

        if (ACTION_UPDATE_RESULTS.equals(action)) {
            updateResults();
        }

    }

    private void updateMatches(boolean doResults) {

        if (!checkForConnectivity()) {
            LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_NO_CONNECTIVITY));
            return;
        }

        LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_STARTED));

        final String BASE_URL = "http://www.gosugamers.net/dota2/gosubet";
        final String MATCH_LINK_URL_BASE = "http://www.gosugamers.net";

        try {

            String rawHtml = new OkHttpClient().newCall(new Request.Builder().url(BASE_URL).build()).execute()
                    .body().string();

            rawHtml = rawHtml.substring(rawHtml.indexOf("<div id=\"col1\" class=\"rows\">"),
                    rawHtml.indexOf("<div id=\"col2\" class=\"rows\">"));
            Document doc = Jsoup.parse(rawHtml);

            Elements tables = doc.getElementsByClass("matches");

            ArrayList<ArrayList<String>> matchLinks = new ArrayList<ArrayList<String>>(tables.size());

            int numSeries = 0;
            for (Element table : tables) {
                Elements links = table.getElementsByClass("match");
                if (links.size() != 0) {
                    ArrayList<String> innerMatchLink = new ArrayList<String>(links.size());
                    for (Element link : links) {
                        String linkHref = link.attr("href");
                        innerMatchLink.add(MATCH_LINK_URL_BASE + linkHref);
                        numSeries++;
                    }
                    matchLinks.add(innerMatchLink);
                }
            }

            // needed if there are massive reschedules to update content properly.
            Uri resultsUri = MatchContract.SeriesEntry.buildSeriesUriWithAfterTime(TimeUtils.getUTCTime());

            Cursor c = getContentResolver().query(resultsUri,
                    new String[] { MatchContract.SeriesEntry.COLUMN_GG_MATCH_PAGE }, null, null, null);

            while (c.moveToNext()) {
                if (!matchLinks.get(0).contains(c.getString(0))) {
                    matchLinks.get(0).add(c.getString(0));
                }
            }

            Iterator<ArrayList<String>> iterator = matchLinks.iterator();
            int numResults = 0;
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            ArrayList<Future<BundledMatchItem>> seriesItemFutures = new ArrayList<Future<BundledMatchItem>>(
                    numSeries);

            LogUtils.LOGD(TAG, "Starting Retrieval, num elements gathered: " + numSeries);
            int i = 0;
            while (iterator.hasNext()) {

                ArrayList<String> matchList = iterator.next();
                for (String matchUrl : matchList) {
                    boolean hasResult = !iterator.hasNext();
                    if (!doResults && hasResult) {
                        continue;
                    } else if (hasResult) {
                        numResults++;
                    }
                    seriesItemFutures.add(executorService.submit(new MatchGetter(matchUrl, hasResult)));
                    i++;
                }
            }
            executorService.shutdown();
            executorService.awaitTermination(20L, TimeUnit.SECONDS);
            LogUtils.LOGD(TAG, "Stopping Retrieval, elements submitted for fetching: " + i);

            ContentValues[] seriesEntries = new ContentValues[i];
            ContentValues[] resultEntries = new ContentValues[numResults];
            int seriesEntryWriteIndex = 0;
            int resultEntryWriteIndex = 0;

            for (Future<BundledMatchItem> seriesItemFuture : seriesItemFutures) {
                try {
                    BundledMatchItem seriesItem = seriesItemFuture.get();
                    if (seriesItem != null) {
                        seriesEntries[seriesEntryWriteIndex] = seriesItem.mMatch;
                        seriesEntryWriteIndex++;
                        if (seriesItem.hasResult) {
                            resultEntries[resultEntryWriteIndex] = seriesItem.mResult;
                            resultEntryWriteIndex++;
                        }
                    }
                } catch (ExecutionException e) {
                    Log.e(TAG, "Should never get here");
                }
            }

            this.getContentResolver().bulkInsert(MatchContract.SeriesEntry.CONTENT_URI, seriesEntries);

            if (doResults)
                this.getContentResolver().bulkInsert(MatchContract.ResultEntry.CONTENT_URI, resultEntries);

            PrefUtils.setLastUpdateTime(this, TimeUtils.getUTCTime());

        } catch (IOException e) {
            Log.e(TAG, e.getMessage(), e);
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_COMPLETE));

        PrefUtils.setLastResultsUpdateTime(this, TimeUtils.getUTCTime());
    }

    private void autoUpdateMatches() {
        long currentTime = TimeUtils.getUTCTime();
        long lastUpdateTime = PrefUtils.lastMatchUpdate(this);

        // no point in consuming so much data - skip update if last checked time is recent.
        if (currentTime - lastUpdateTime < 60000 * 60 * 1) {
            return;
        }

        if (wifiUpdate()) {
            updateMatches(false);
        }
    }

    private void scheduleAutoUpdates() {

        if (!PrefUtils.shouldAutoUpdate(this)) {
            return;
        }

        final long currentTime = TimeUtils.getUTCTime();

        final Intent updateMatchIntent = new Intent(UpdateMatchService.ACTION_AUTO_UPDATE_MATCHES, null, this,
                UpdateMatchService.class);

        final Intent updateResultIntent = new Intent(UpdateMatchService.ACTION_UPDATE_RESULTS, null, this,
                UpdateMatchService.class);

        final long matchUpdateTime = currentTime + (60000 * 60 * 12);
        final long resultUpdateTime = currentTime + (60000 * 60);

        PendingIntent umi = PendingIntent.getService(this.getApplicationContext(), 0, updateMatchIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        PendingIntent uri = PendingIntent.getService(this.getApplicationContext(), 0, updateResultIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        final AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);

        am.cancel(umi);
        am.cancel(uri);

        am.setInexactRepeating(AlarmManager.RTC, matchUpdateTime, AlarmManager.INTERVAL_HALF_DAY, umi);

        am.setInexactRepeating(AlarmManager.RTC, resultUpdateTime, AlarmManager.INTERVAL_HOUR, uri);
    }

    private void cancelAutoUpdate() {
        final Intent updateMatchIntent = new Intent(UpdateMatchService.ACTION_AUTO_UPDATE_MATCHES, null, this,
                UpdateMatchService.class);

        final Intent updateResultIntent = new Intent(UpdateMatchService.ACTION_UPDATE_RESULTS, null, this,
                UpdateMatchService.class);

        PendingIntent umi = PendingIntent.getService(this.getApplicationContext(), 0, updateMatchIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        PendingIntent uri = PendingIntent.getService(this.getApplicationContext(), 0, updateResultIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        final AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);

        am.cancel(umi);
        am.cancel(uri);
    }

    // we decouple results and matches because results tends to require less data
    // check this every hour, with no cpu wake?
    private void updateResults() {
        long currentTime = TimeUtils.getUTCTime();
        long lastUpdateTime = PrefUtils.lastResultsUpdate(this);

        // no point in consuming so much data - skip update if last checked time is recent.
        if (currentTime - lastUpdateTime < 60000 * 10) {
            LogUtils.LOGD(TAG, "Too soon, not bothering to check.");
            return;
        }

        if (!checkForConnectivity()) {
            LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_NO_CONNECTIVITY));
            return;
        }

        LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_STARTED));

        Uri resultsUri = MatchContract.SeriesEntry.buildLiveSeriesUri(TimeUtils.getUTCTime());

        Cursor updateCursor = this.getContentResolver().query(resultsUri,
                new String[] { MatchContract.SeriesEntry.COLUMN_GG_MATCH_PAGE }, null, null, null);
        // LOGD(TAG, "Not executed");

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        ArrayList<Future<BundledMatchItem>> matchItemFutures = new ArrayList<Future<BundledMatchItem>>();

        int i = 0;
        while (updateCursor.moveToNext()) {
            matchItemFutures.add(executorService.submit(new MatchGetter(updateCursor.getString(0), true)));
            i++;
        }

        updateCursor.close();

        executorService.shutdown();
        try {
            executorService.awaitTermination(20L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LogUtils.LOGD(TAG, "Stopping Retrieval, elements submitted for fetching: " + i);

        ArrayList<ContentValues> sE = new ArrayList<ContentValues>();
        ArrayList<ContentValues> rE = new ArrayList<ContentValues>();

        for (Future<BundledMatchItem> matchItemFuture : matchItemFutures) {
            try {
                BundledMatchItem matchItem = matchItemFuture.get();
                if (matchItem != null) {
                    sE.add(matchItem.mMatch);
                    if (matchItem.hasResult) {
                        rE.add(matchItem.mResult);
                    }
                }
            } catch (InterruptedException e) {
                Log.e(TAG, "Should never get here");
            } catch (ExecutionException e) {
                Log.e(TAG, "Oops;");
            }
        }
        if (!sE.isEmpty()) {
            ContentValues[] seriesEntries = new ContentValues[sE.size()];
            sE.toArray(seriesEntries);

            this.getContentResolver().bulkInsert(MatchContract.SeriesEntry.CONTENT_URI, seriesEntries);
        }

        if (!rE.isEmpty()) {
            ContentValues[] resultEntries = new ContentValues[rE.size()];
            resultEntries = rE.toArray(resultEntries);

            this.getContentResolver().bulkInsert(MatchContract.ResultEntry.CONTENT_URI, resultEntries);
        }

        LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_COMPLETE));

        PrefUtils.setLastResultsUpdateTime(this, currentTime);
    }

    private boolean checkForConnectivity() {
        ConnectivityManager cm = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();

        return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
    }

    private boolean wifiUpdate() {
        ConnectivityManager cm = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();

        boolean wifiState = true;

        if (PrefUtils.updateOnWifiOnly(this)) {
            wifiState = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;
        }

        return wifiState;
    }
}