Rework binding and execution of action queue.

This commit is contained in:
dosiecki 2014-09-17 20:04:01 -07:00
parent cbf90e7dac
commit 4bea75853a
5 changed files with 207 additions and 77 deletions

View file

@ -21,6 +21,7 @@ import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ReadingAction;
import com.newsblur.util.ReadFilter;
import com.newsblur.util.StoryOrder;
@ -359,36 +360,15 @@ public class BlurDatabaseHelper {
dbRW.update(DatabaseConstants.SOCIALFEED_TABLE, values, whereClause, whereArgs);
}
public void enqueueActionStoryRead(String hash, boolean read) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.ACTION_TIME, System.currentTimeMillis());
values.put(DatabaseConstants.ACTION_STORY_HASH, hash);
values.put(read ? DatabaseConstants.ACTION_MARK_READ : DatabaseConstants.ACTION_MARK_UNREAD, 1);
dbRW.insertOrThrow(DatabaseConstants.ACTION_TABLE, null, values);
}
public void enqueueActionFeedRead(FeedSet fs, Long olderThan, Long newerThan) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.ACTION_TIME, System.currentTimeMillis());
values.put(DatabaseConstants.ACTION_MARK_READ, 1);
values.put(DatabaseConstants.ACTION_FEED_ID, TextUtils.join(",", fs.getFeedIds()));
if (olderThan != null) values.put(DatabaseConstants.ACTION_INCLUDE_OLDER, olderThan);
if (newerThan != null) values.put(DatabaseConstants.ACTION_INCLUDE_NEWER, newerThan);
dbRW.insertOrThrow(DatabaseConstants.ACTION_TABLE, null, values);
public void enqueueAction(ReadingAction ra) {
dbRW.insertOrThrow(DatabaseConstants.ACTION_TABLE, null, ra.toContentValues());
}
public Cursor getActions(boolean includeDone) {
String q = "SELECT * FROM " + DatabaseConstants.ACTION_TABLE +
" WHERE " + DatabaseConstants.ACTION_DONE_REMOTE + " = " + (includeDone ? 1 : 0);
String q = "SELECT * FROM " + DatabaseConstants.ACTION_TABLE;
return dbRO.rawQuery(q, null);
}
public void setActionDone(String actionId) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.ACTION_DONE_REMOTE, 1);
dbRW.update(DatabaseConstants.ACTION_TABLE, values, DatabaseConstants.ACTION_ID + " = ?", new String[]{actionId});
}
public void clearAction(String actionId) {
dbRW.delete(DatabaseConstants.ACTION_TABLE, DatabaseConstants.ACTION_ID + " = ?", new String[]{actionId});
}

View file

@ -118,8 +118,6 @@ public class DatabaseConstants {
public static final String ACTION_TABLE = "story_actions";
public static final String ACTION_ID = BaseColumns._ID;
public static final String ACTION_TIME = "time";
public static final String ACTION_DONE_REMOTE = "done_remote";
public static final String ACTION_DONE_LOCAL = "done_local";
public static final String ACTION_MARK_READ = "mark_read";
public static final String ACTION_MARK_UNREAD = "mark_unread";
public static final String ACTION_SAVE = "save";
@ -251,7 +249,6 @@ public class DatabaseConstants {
static final String ACTION_SQL = "CREATE TABLE " + ACTION_TABLE + " (" +
ACTION_ID + INTEGER + " PRIMARY KEY AUTOINCREMENT, " +
ACTION_TIME + INTEGER + " NOT NULL, " +
ACTION_DONE_REMOTE + INTEGER + " DEFAULT 0, " +
ACTION_MARK_READ + INTEGER + " DEFAULT 0, " +
ACTION_MARK_UNREAD + INTEGER + " DEFAULT 0, " +
ACTION_SAVE + INTEGER + " DEFAULT 0, " +

View file

@ -27,6 +27,7 @@ import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.ImageCache;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ReadingAction;
import com.newsblur.util.ReadFilter;
import com.newsblur.util.StoryOrder;
@ -78,17 +79,27 @@ public class NBSyncService extends Service {
/** Feed sets that we need to sync and how many stories the UI wants for them. */
private static Map<FeedSet,Integer> PendingFeeds;
static { PendingFeeds = new HashMap<FeedSet,Integer>(); }
/** Feed sets that the API has said to have no more pages left. */
private static Set<FeedSet> ExhaustedFeeds;
static { ExhaustedFeeds = new HashSet<FeedSet>(); }
/** The number of pages we have collected for the given feed set. */
private static Map<FeedSet,Integer> FeedPagesSeen;
static { FeedPagesSeen = new HashMap<FeedSet,Integer>(); }
/** The number of stories we have collected for the given feed set. */
private static Map<FeedSet,Integer> FeedStoriesSeen;
static { FeedStoriesSeen = new HashMap<FeedSet,Integer>(); }
/** Unread story hashes the API listed that we do not appear to have locally yet. */
private static Set<String> StoryHashQueue;
static { StoryHashQueue = new HashSet<String>(); }
/** URLs of images contained in recently fetched stories that are candidates for prefetch. */
private static Set<String> ImageQueue;
static { ImageQueue = new HashSet<String>(); }
/** Story hashes of recently marked-read stories that may need to be re-marked locally due to overlapping API calls. */
private static List<ReadingAction> FollowupActions;
static { FollowupActions = new ArrayList<ReadingAction>(); }
private PowerManager.WakeLock wl = null;
private ExecutorService executor;
private APIManager apiManager;
@ -203,31 +214,8 @@ public class NBSyncService extends Service {
actionsloop : while (c.moveToNext()) {
String id = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_ID));
NewsBlurResponse response = null;
if (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_MARK_READ)) == 1) {
String hash = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_HASH));
String feedIds = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_FEED_ID));
Long includeOlder = DatabaseConstants.nullIfZero(c.getLong(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_INCLUDE_OLDER)));
Long includeNewer = DatabaseConstants.nullIfZero(c.getLong(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_INCLUDE_NEWER)));
if (hash != null) {
response = apiManager.markStoryAsRead(hash);
} else if (feedIds != null) {
Set<String> feeds = new HashSet<String>();
for (String feedId : TextUtils.split(feedIds, ",")) feeds.add(feedId);
if (feeds.size() == 0) {
response = apiManager.markAllAsRead();
} else {
response = apiManager.markFeedsAsRead(feeds, includeOlder, includeNewer);
}
} else {
Log.w(this.getClass().getName(), "discarding mark-read action of unknown type: " + id);
}
} else if (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_MARK_UNREAD)) == 1) {
String hash = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_HASH));
response = apiManager.markStoryHashUnread(hash);
} else {
Log.w(this.getClass().getName(), "discarding action of unknown type: " + id);
}
ReadingAction ra = ReadingAction.fromCursor(c);
NewsBlurResponse response = ra.doRemote(apiManager);
// if we attempted a call and it failed, do not mark the action as done
if (response != null) {
@ -236,7 +224,8 @@ public class NBSyncService extends Service {
}
}
dbHelper.setActionDone(id);
dbHelper.clearAction(id);
FollowupActions.add(ra);
}
} finally {
closeQuietly(c);
@ -252,33 +241,17 @@ public class NBSyncService extends Service {
private void finishActions() {
if (HaltNow) return;
Cursor c = null;
try {
c = dbHelper.getActions(true);
if (c.getCount() < 1) return;
if (FollowupActions.size() < 1) return;
Log.d(this.getClass().getName(), "found done actions: " + c.getCount());
Log.d(this.getClass().getName(), "found old actions: " + FollowupActions.size());
ActionsRunning = true;
NbActivity.updateAllActivities();
while (c.moveToNext()) {
String id = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_ID));
if (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_MARK_READ)) == 1) {
String hash = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_HASH));
if (hash != null ) {
dbHelper.setStoryReadState(hash, true);
}
// TODO: double-check stories from feed-marks
} else if (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_MARK_UNREAD)) == 1) {
String hash = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_HASH));
dbHelper.setStoryReadState(hash, false);
} else {
Log.w(this.getClass().getName(), "discarding action of unknown type: " + id);
}
dbHelper.clearAction(id);
for (ReadingAction ra : FollowupActions) {
ra.doLocalSecondary(dbHelper);
}
FollowupActions.clear();
} finally {
closeQuietly(c);
ActionsRunning = false;
NbActivity.updateAllActivities();
}

View file

@ -159,12 +159,13 @@ public class FeedUtils {
// update the local object to show as read before DB is touched
story.read = read;
// update unread state and unread counts in the local DB
dbHelper.setStoryReadState(story, read);
// tell the sync service we need to mark read
dbHelper.enqueueActionStoryRead(story.storyHash, read);
ReadingAction ra = (read ? ReadingAction.markStoryRead(story.storyHash) : ReadingAction.markStoryUnread(story.storyHash));
dbHelper.enqueueAction(ra);
triggerSync(context);
}
@ -172,7 +173,8 @@ public class FeedUtils {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... arg) {
dbHelper.enqueueActionFeedRead(fs, olderThan, newerThan);
ReadingAction ra = ReadingAction.markFeedRead(fs, olderThan, newerThan);
dbHelper.enqueueAction(ra);
dbHelper.markFeedsRead(fs, olderThan, newerThan);
triggerSync(context);
return null;

View file

@ -0,0 +1,178 @@
package com.newsblur.util;
import android.content.ContentValues;
import android.database.Cursor;
import android.text.TextUtils;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.network.APIManager;
import java.util.HashSet;
import java.util.Set;
public class ReadingAction {
private enum ActionType {
MARK_READ,
MARK_UNREAD,
SAVE,
UNSAVE
};
private ActionType type;
private String storyHash;
private FeedSet feedSet;
private Long olderThan;
private Long newerThan;
private ReadingAction() {
; // must use helpers
}
public static ReadingAction markStoryRead(String hash) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.MARK_READ;
ra.storyHash = hash;
return ra;
}
public static ReadingAction markStoryUnread(String hash) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.MARK_UNREAD;
ra.storyHash = hash;
return ra;
}
public static ReadingAction markFeedRead(FeedSet fs, Long olderThan, Long newerThan) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.MARK_READ;
ra.feedSet = fs;
ra.olderThan = olderThan;
ra.newerThan = newerThan;
return ra;
}
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.ACTION_TIME, System.currentTimeMillis());
switch (type) {
case MARK_READ:
values.put(DatabaseConstants.ACTION_MARK_READ, 1);
if (storyHash != null) {
values.put(DatabaseConstants.ACTION_STORY_HASH, storyHash);
} else if (feedSet != null) {
values.put(DatabaseConstants.ACTION_FEED_ID, TextUtils.join(",", feedSet.getFeedIds()));
if (olderThan != null) values.put(DatabaseConstants.ACTION_INCLUDE_OLDER, olderThan);
if (newerThan != null) values.put(DatabaseConstants.ACTION_INCLUDE_NEWER, newerThan);
}
break;
case MARK_UNREAD:
values.put(DatabaseConstants.ACTION_MARK_UNREAD, 1);
if (storyHash != null) {
values.put(DatabaseConstants.ACTION_STORY_HASH, storyHash);
}
break;
default:
throw new IllegalStateException("cannot serialise uknown type of action.");
}
return values;
}
public static ReadingAction fromCursor(Cursor c) {
ReadingAction ra = new ReadingAction();
if (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_MARK_READ)) == 1) {
ra.type = ActionType.MARK_READ;
String hash = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_HASH));
String feedIds = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_FEED_ID));
Long includeOlder = DatabaseConstants.nullIfZero(c.getLong(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_INCLUDE_OLDER)));
Long includeNewer = DatabaseConstants.nullIfZero(c.getLong(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_INCLUDE_NEWER)));
if (hash != null) {
ra.storyHash = hash;
} else if (feedIds != null) {
Set<String> feeds = new HashSet<String>();
for (String feedId : TextUtils.split(feedIds, ",")) feeds.add(feedId);
if (feeds.size() == 0) {
ra.feedSet = FeedSet.allFeeds();
} else {
ra.feedSet = FeedSet.folder(null, feeds);
}
ra.olderThan = includeOlder;
ra.newerThan = includeNewer;
} else {
throw new IllegalStateException("cannot deserialise uknown type of action.");
}
} else if (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_MARK_UNREAD)) == 1) {
ra.type = ActionType.MARK_UNREAD;
String hash = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_HASH));
ra.storyHash = hash;
} else {
throw new IllegalStateException("cannot deserialise uknown type of action.");
}
return ra;
}
/**
* Execute this action remotely via the API.
*/
public NewsBlurResponse doRemote(APIManager apiManager) {
switch (type) {
case MARK_READ:
if (storyHash != null) {
return apiManager.markStoryAsRead(storyHash);
} else if (feedSet != null) {
if (feedSet.isAllNormal()) {
return apiManager.markAllAsRead();
} else if (feedSet.getFeedIds() != null) {
return apiManager.markFeedsAsRead(feedSet.getFeedIds(), olderThan, newerThan);
}
}
break;
case MARK_UNREAD:
if (storyHash != null) {
return apiManager.markStoryHashUnread(storyHash);
}
break;
default:
}
throw new IllegalStateException("cannot execute uknown type of action.");
}
/**
* Excecute this action on the local DB performing only idempotent sub-actions.
* Basically, re-do any local effects of this action that can be done safely.
*/
public void doLocalSecondary(BlurDatabaseHelper dbHelper) {
switch (type) {
case MARK_READ:
if (storyHash != null) {
;
} else if (feedSet != null) {
;
}
break;
case MARK_UNREAD:
if (storyHash != null) {
;
}
break;
default:
// not all actions have these, which is fine
}
}
}