Complete unread recounting after reading sessions.

This commit is contained in:
dosiecki 2015-02-13 01:14:46 -08:00
parent 73136825ab
commit 90116ee45d
7 changed files with 149 additions and 1 deletions

View file

@ -74,6 +74,7 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
super.onResume();
NBSyncService.clearPendingStoryRequest();
NBSyncService.flushRecounts();
NBSyncService.setActivationMode(NBSyncService.ActivationMode.ALL);
FeedUtils.activateAllStories();
FeedUtils.clearReadingSession();

View file

@ -529,6 +529,14 @@ public class BlurDatabaseHelper {
return result;
}
public void updateFeedCounts(String feedId, ContentValues values) {
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.FEED_TABLE, values, DatabaseConstants.FEED_ID + " = ?", new String[]{feedId});}
}
public void updateSocialFeedCounts(String feedId, ContentValues values) {
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.SOCIALFEED_TABLE, values, DatabaseConstants.SOCIAL_FEED_ID + " = ?", new String[]{feedId});}
}
/**
* Refreshes the counts in the feeds/socialfeeds tables by counting stories in the story table.
*/

View file

@ -11,6 +11,7 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import android.content.ContentValues;
import android.content.Context;
@ -37,6 +38,7 @@ import com.newsblur.network.domain.ProfileResponse;
import com.newsblur.network.domain.RegisterResponse;
import com.newsblur.network.domain.StoriesResponse;
import com.newsblur.network.domain.StoryTextResponse;
import com.newsblur.network.domain.UnreadCountResponse;
import com.newsblur.network.domain.UnreadStoryHashesResponse;
import com.newsblur.serialization.BooleanTypeAdapter;
import com.newsblur.serialization.ClassifierMapTypeAdapter;
@ -243,6 +245,15 @@ public class APIManager {
}
}
public UnreadCountResponse getFeedUnreadCounts(Set<String> apiIds) {
ValueMultimap values = new ValueMultimap();
for (String id : apiIds) {
values.put(APIConstants.PARAMETER_FEEDID, id);
}
APIResponse response = get(APIConstants.URL_FEED_UNREAD_COUNT, values);
return (UnreadCountResponse) response.getResponse(gson, UnreadCountResponse.class);
}
public UnreadStoryHashesResponse getUnreadStoryHashes() {
ValueMultimap values = new ValueMultimap();
values.put(APIConstants.PARAMETER_INCLUDE_TIMESTAMPS, "1");

View file

@ -0,0 +1,34 @@
package com.newsblur.network.domain;
import android.content.ContentValues;
import java.util.Map;
import com.google.gson.annotations.SerializedName;
import com.newsblur.database.DatabaseConstants;
public class UnreadCountResponse extends NewsBlurResponse {
@SerializedName("feeds")
public Map<String,UnreadMD> feeds;
@SerializedName("social_feeds")
public Map<String,UnreadMD> socialFeeds;
public class UnreadMD {
public int ps;
public int nt;
public int ng;
public ContentValues getValues() {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.FEED_POSITIVE_COUNT, ps);
values.put(DatabaseConstants.FEED_NEUTRAL_COUNT, nt);
values.put(DatabaseConstants.FEED_NEGATIVE_COUNT, ng);
return values;
}
}
}

View file

@ -20,10 +20,12 @@ import static com.newsblur.database.BlurDatabaseHelper.closeQuietly;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.domain.SocialFeed;
import com.newsblur.domain.Story;
import com.newsblur.network.APIConstants;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.FeedFolderResponse;
import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.network.domain.StoriesResponse;
import com.newsblur.network.domain.UnreadCountResponse;
import com.newsblur.network.domain.UnreadStoryHashesResponse;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
@ -76,6 +78,7 @@ public class NBSyncService extends Service {
private volatile static boolean FFSyncRunning = false;
private volatile static boolean StorySyncRunning = false;
private volatile static boolean HousekeepingRunning = false;
private volatile static boolean RecountsRunning = false;
private volatile static boolean DoFeedsFolders = false;
private volatile static boolean DoUnreads = false;
@ -108,6 +111,11 @@ public class NBSyncService extends Service {
private static List<ReadingAction> FollowupActions;
static { FollowupActions = new ArrayList<ReadingAction>(); }
/** Feed IDs (API stype) that have been acted upon and need a double-check for counts. */
private static Set<FeedSet> RecountCandidates;
static { RecountCandidates = new HashSet<FeedSet>(); }
private volatile static boolean FlushRecounts = false;
Set<String> orphanFeedIds;
private ExecutorService primaryExecutor;
@ -212,6 +220,8 @@ public class NBSyncService extends Service {
syncMetadata(startId);
checkRecounts();
unreadsService.start(startId);
imagePrefetchService.start(startId);
@ -368,6 +378,7 @@ public class NBSyncService extends Service {
FeedPagesSeen.clear();
FeedStoriesSeen.clear();
UnreadsService.clearHashes();
RecountCandidates.clear();
FeedFolderResponse feedResponse = apiManager.getFolderFeedMapping(true);
@ -454,6 +465,58 @@ public class NBSyncService extends Service {
}
/**
* See if any feeds have been touched in a way that require us to double-check unread counts;
*/
private void checkRecounts() {
if (stopSync()) return;
if (!FlushRecounts) return;
try {
if (RecountCandidates.size() < 1) return;
RecountsRunning = true;
NbActivity.updateAllActivities(false);
// if we are offline, the best we can do is perform a local unread recount and
// save the true one for when we go back online.
if (!NetworkUtils.isOnline(this)) {
for (FeedSet fs : RecountCandidates) {
dbHelper.updateLocalFeedCounts(fs);
}
} else {
Set<String> apiIds = new HashSet<String>();
for (FeedSet fs : RecountCandidates) {
apiIds.addAll(fs.getFlatFeedIds());
}
Log.d(this.getClass().getName(), "IDs to check: " + apiIds);
UnreadCountResponse apiResponse = apiManager.getFeedUnreadCounts(apiIds);
if ((apiResponse == null) || (apiResponse.isError())) {
Log.w(this.getClass().getName(), "Bad response to feed_unread_count");
return;
}
if (apiResponse.feeds != null ) {
for (Map.Entry<String,UnreadCountResponse.UnreadMD> entry : apiResponse.feeds.entrySet()) {
dbHelper.updateFeedCounts(entry.getKey(), entry.getValue().getValues());
}
}
if (apiResponse.socialFeeds != null ) {
for (Map.Entry<String,UnreadCountResponse.UnreadMD> entry : apiResponse.socialFeeds.entrySet()) {
String feedId = entry.getKey().replaceAll(APIConstants.VALUE_PREFIX_SOCIAL, "");
dbHelper.updateSocialFeedCounts(feedId, entry.getValue().getValues());
}
}
RecountCandidates.clear();
}
} finally {
if (RecountsRunning) {
RecountsRunning = false;
NbActivity.updateAllActivities(false);
}
FlushRecounts = false;
}
}
/**
* Fetch stories needed because the user is actively viewing a feed or folder.
*/
@ -626,6 +689,10 @@ public class NBSyncService extends Service {
DoFeedsFolders = true;
}
public static void flushRecounts() {
FlushRecounts = true;
}
/**
* Tell the service which stories can be activated if received. See ActivationMode.
*/
@ -695,6 +762,10 @@ public class NBSyncService extends Service {
OriginalTextService.addHash(hash);
}
public static void addRecountCandidates(Set<FeedSet> fs) {
RecountCandidates.addAll(fs);
}
public static void softInterrupt() {
if (AppConstants.VERBOSE_LOG) Log.d(NBSyncService.class.getName(), "soft stop");
HaltNow = true;

View file

@ -10,6 +10,8 @@ import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.newsblur.network.APIConstants;
/**
* A subset of one, several, or all NewsBlur feeds or social feeds. Used to encapsulate the
* complexity of the fact that social feeds are special and requesting a river of feeds is not
@ -195,6 +197,26 @@ public class FeedSet implements Serializable {
return this.folderName;
}
/**
* Gets a flat set of feed IDs that can be passed to API calls that take raw numeric IDs or
* social IDs prefixed with "social:". Returns an empty set for feed sets that don't track
* unread counts or that are essentially "everything".
*/
public Set<String> getFlatFeedIds() {
Set<String> result = new HashSet<String>();
if (feeds != null) {
for (String id : feeds) {
result.add(id);
}
}
if (socialFeeds != null) {
for (Map.Entry<String,String> e : socialFeeds.entrySet()) {
result.add(APIConstants.VALUE_PREFIX_SOCIAL + e.getKey());
}
}
return result;
}
private static final String COM_SER_NUL = "NUL";
public String toCompactSerial() {

View file

@ -133,13 +133,14 @@ public class FeedUtils {
story.read = read;
// update unread state and unread counts in the local DB
dbHelper.setStoryReadState(story, read);
Set<FeedSet> impactedFeeds = dbHelper.setStoryReadState(story, read);
NbActivity.updateAllActivities();
// tell the sync service we need to mark read
ReadingAction ra = (read ? ReadingAction.markStoryRead(story.storyHash) : ReadingAction.markStoryUnread(story.storyHash));
dbHelper.enqueueAction(ra);
triggerSync(context);
NBSyncService.addRecountCandidates(impactedFeeds);
}
public static void markFeedsRead(final FeedSet fs, final Long olderThan, final Long newerThan, final Context context) {