package com.vaguehope.onosendai.update;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import twitter4j.TwitterException;
import com.vaguehope.onosendai.config.Account;
import com.vaguehope.onosendai.config.Column;
import com.vaguehope.onosendai.config.ColumnFeed;
import com.vaguehope.onosendai.model.Filters;
import com.vaguehope.onosendai.model.Tweet;
import com.vaguehope.onosendai.model.TweetList;
import com.vaguehope.onosendai.provider.ProviderMgr;
import com.vaguehope.onosendai.provider.instapaper.InstapaperProvider;
import com.vaguehope.onosendai.provider.successwhale.SuccessWhaleException;
import com.vaguehope.onosendai.provider.successwhale.SuccessWhaleFeed;
import com.vaguehope.onosendai.provider.successwhale.SuccessWhaleProvider;
import com.vaguehope.onosendai.provider.twitter.TwitterFeed;
import com.vaguehope.onosendai.provider.twitter.TwitterFeeds;
import com.vaguehope.onosendai.provider.twitter.TwitterProvider;
import com.vaguehope.onosendai.provider.twitter.TwitterUtils;
import com.vaguehope.onosendai.storage.DbInterface;
import com.vaguehope.onosendai.storage.DbInterface.ColumnState;
import com.vaguehope.onosendai.util.ExcpetionHelper;
import com.vaguehope.onosendai.util.LogWrapper;
public class FetchColumn implements Callable<Void> {
protected static final LogWrapper LOG = new LogWrapper("FC");
private final DbInterface db;
private final FetchFeedRequest ffr;
private final ProviderMgr providerMgr;
private final Filters filters;
public FetchColumn (final DbInterface db, final FetchFeedRequest ffr, final ProviderMgr providerMgr, final Filters filters) {
if (db == null) throw new IllegalArgumentException("db can not be null.");
this.db = db;
if (ffr == null) throw new IllegalArgumentException("ffr can not be null.");
if (ffr.column == null) throw new IllegalArgumentException("ffr.column can not be null.");
if (ffr.account == null) throw new IllegalArgumentException("ffr.account can not be null.");
this.ffr = ffr;
if (providerMgr == null) throw new IllegalArgumentException("providerMgr can not be null.");
this.providerMgr = providerMgr;
this.filters = filters;
}
@Override
public Void call () {
fetchColumn(this.db, this.ffr, this.providerMgr, this.filters);
return null;
}
public static void fetchColumn (final DbInterface db, final FetchFeedRequest ffr, final ProviderMgr providerMgr, final Filters filters) {
db.notifyTwListenersColumnState(ffr.column.getId(), ColumnState.UPDATE_RUNNING);
try {
fetchColumnInner(db, ffr, providerMgr, filters);
}
finally {
db.notifyTwListenersColumnState(ffr.column.getId(), ColumnState.UPDATE_OVER);
}
}
private static void fetchColumnInner (final DbInterface db, final FetchFeedRequest ffr, final ProviderMgr providerMgr, final Filters filters) {
switch (ffr.account.getProvider()) {
case TWITTER:
fetchTwitterColumn(db, ffr.account, ffr.column, ffr.feed, providerMgr, filters);
break;
case SUCCESSWHALE:
fetchSuccessWhaleColumn(db, ffr.account, ffr.column, ffr.feed, providerMgr, filters);
break;
case INSTAPAPER:
pushInstapaperColumn(db, ffr.account, ffr.column, providerMgr);
break;
default:
LOG.e("Unknown account type: %s", ffr.account.getProvider());
}
}
private static void fetchTwitterColumn (final DbInterface db, final Account account, final Column column, final ColumnFeed columnFeed, final ProviderMgr providerMgr, final Filters filters) {
final long startTime = System.nanoTime();
try {
final TwitterProvider twitterProvider = providerMgr.getTwitterProvider();
twitterProvider.addAccount(account);
final TwitterFeed feed = TwitterFeeds.parse(columnFeed.getResource());
final String sinceIdRaw = readSinceId(db, column, columnFeed);
final long sinceId = sinceIdRaw != null ? Long.parseLong(sinceIdRaw) : -1;
final TweetList tweets = twitterProvider.getTweets(feed, account, sinceId, column.isHdMedia());
final int filteredCount = filterAndStore(db, column, columnFeed, filters, tweets);
storeQuoted(db, tweets);
storeSuccess(db, column);
final long durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
LOG.i("Fetched %d items for '%s' '%s' in %d millis. %s filtered.", tweets.count(), column.getTitle(), columnFeed.getResource(), durationMillis, filteredCount);
}
catch (final TwitterException e) {
LOG.w("Failed to fetch from Twitter: %s", ExcpetionHelper.causeTrace(e));
storeError(db, column, TwitterUtils.friendlyExceptionMessage(e));
}
}
private static void fetchSuccessWhaleColumn (final DbInterface db, final Account account, final Column column, final ColumnFeed columnFeed, final ProviderMgr providerMgr, final Filters filters) {
final long startTime = System.nanoTime();
try {
final SuccessWhaleProvider successWhaleProvider = providerMgr.getSuccessWhaleProvider();
successWhaleProvider.addAccount(account);
final SuccessWhaleFeed feed = new SuccessWhaleFeed(column, columnFeed);
final String sinceId = readSinceId(db, column, columnFeed);
final TweetList tweets = successWhaleProvider.getTweets(feed, account, sinceId);
final int filteredCount = filterAndStore(db, column, columnFeed, filters, tweets);
storeSuccess(db, column);
final long durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
LOG.i("Fetched %d items for '%s' '%s' in %d millis. %s filtered.", tweets.count(), column.getTitle(), columnFeed.getResource(), durationMillis, filteredCount);
}
catch (final SuccessWhaleException e) {
LOG.w("Failed to fetch from SuccessWhale: %s", ExcpetionHelper.causeTrace(e));
storeError(db, column, e.friendlyMessage());
}
}
private static String readSinceId (final DbInterface db, final Column column, final ColumnFeed columnFeed) {
return db.getValue(KvKeys.feedSinceId(column, columnFeed));
}
private static int filterAndStore (final DbInterface db, final Column column, final ColumnFeed columnFeed, final Filters filters, final TweetList tweets) {
int filteredCount = 0;
if (tweets.count() > 0) {
final List<Tweet> filteredTweets = filters.matchAndSet(tweets.getTweets());
filteredCount = Filters.countFiltered(filteredTweets);
db.storeTweets(column, filteredTweets);
db.storeValue(KvKeys.feedSinceId(column, columnFeed), tweets.getMostRecent().getSid());
}
return filteredCount;
}
/**
* TODO This should be a short term solution, or until a better idea presents.
*/
private static void storeQuoted (final DbInterface db, final TweetList tweets) {
for (final Tweet t : tweets.getQuotedTweets()) {
db.storeTweets(Column.ID_CACHED, Collections.singletonList(t));
}
}
private static void pushInstapaperColumn (final DbInterface db, final Account account, final Column column, final ProviderMgr providerMgr) {
final long startTime = System.nanoTime();
try {
final InstapaperProvider provider = providerMgr.getInstapaperProvider();
final String lastPushTimeRaw = db.getValue(KvKeys.colLastPushTime(column));
final long lastPushTime = lastPushTimeRaw != null ? Long.parseLong(lastPushTimeRaw) : 0L;
LOG.i("Looking for items since t=%s to push...", lastPushTime);
final List<Tweet> tweets = db.getTweetsSinceTime(column.getId(), lastPushTime, 10); // XXX Arbitrary limit.
LOG.i("Pushing %s items...", tweets.size());
for (final Tweet tweet : tweets) {
final Tweet fullTweet = db.getTweetDetails(column.getId(), tweet);
provider.add(account, fullTweet);
db.storeValue(KvKeys.colLastPushTime(column), String.valueOf(tweet.getTime()));
LOG.i("Pushed item sid=%s.", tweet.getSid());
}
storeSuccess(db, column);
final long durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
LOG.i("Pushed %d items for '%s' in %d millis.", tweets.size(), column.getTitle(), durationMillis);
}
catch (final Exception e) {
LOG.w("Failed to push to Instapaper: %s", ExcpetionHelper.causeTrace(e));
storeError(db, column, ExcpetionHelper.causeTrace(e));
}
}
private static void storeSuccess (final DbInterface db, final Column column) {
storeResult(db, column, null);
}
private static void storeError (final DbInterface db, final Column column, final String msg) {
storeResult(db, column, msg);
}
public static void storeDismiss (final DbInterface db, final Column column) {
storeResult(db, column, null);
}
private static void storeResult (final DbInterface db, final Column column, final String result) {
db.storeValue(KvKeys.colLastRefreshError(column), result);
}
}