diff --git a/apps/reader/fixtures/subscriptions.json b/apps/reader/fixtures/subscriptions.json index 21533daa7..70ad854dd 100644 --- a/apps/reader/fixtures/subscriptions.json +++ b/apps/reader/fixtures/subscriptions.json @@ -21,6 +21,14 @@ "user": 1 } }, + { + "pk": 2, + "model": "reader.usersubscriptionfolders", + "fields": { + "folders": "[5299728, 644144, 1187026, {\"Brainiacs & Opinion\": [569, 38, 3581, 183139, 1186180, 15]}, {\"Science & Technology\": [731503, 140145, 1272495, 76, 161, 39, {\"Hacker\": [5985150, 3323431]}]}, {\"Humor\": [212379, 3530, 5994357]}, {\"Videos\": [3240, 5168]}]", + "user": 2 + } + }, { "pk": 2, @@ -161,6 +169,24 @@ "email": "samuel@newsblur.com", "date_joined": "2009-01-04 17:32:58" } + }, + { + "pk": 2, + "model": "auth.user", + "fields": { + "username": "Dejal", + "first_name": "", + "last_name": "", + "is_active": 1, + "is_superuser": 1, + "is_staff": 1, + "last_login": "2009-04-07 19:22:24", + "groups": [], + "user_permissions": [], + "password": "sha1$7b94b$ac9e6cf08d0fa16a67e56e319c0935aeb26db2a2", + "email": "dejal@newsblur.com", + "date_joined": "2009-01-04 17:32:58" + } }, { "pk": 1206, diff --git a/apps/reader/models.py b/apps/reader/models.py index 70c6bb820..0987be0a3 100644 --- a/apps/reader/models.py +++ b/apps/reader/models.py @@ -1288,13 +1288,17 @@ class UserSubscriptionFolders(models.Model): return _arrange_folder(user_sub_folders) - def flatten_folders(self, feeds=None): + def flatten_folders(self, feeds=None, inactive_feeds=None): folders = json.decode(self.folders) flat_folders = {" ": []} + if feeds and not inactive_feeds: + inactive_feeds = [] def _flatten_folders(items, parent_folder="", depth=0): for item in items: - if isinstance(item, int) and ((not feeds) or (feeds and item in feeds)): + if (isinstance(item, int) and + (not feeds or + (item in feeds or item in inactive_feeds))): if not parent_folder: parent_folder = ' ' if parent_folder in flat_folders: @@ -1317,6 +1321,7 @@ class UserSubscriptionFolders(models.Model): return flat_folders def delete_feed(self, feed_id, in_folder, commit_delete=True): + feed_id = int(feed_id) def _find_feed_in_folders(old_folders, folder_name='', multiples_found=False, deleted=False): new_folders = [] for k, folder in enumerate(old_folders): @@ -1462,6 +1467,7 @@ class UserSubscriptionFolders(models.Model): logging.user(self.user, "~FBMoving ~SB%s~SN feeds to folder: ~SB%s" % ( len(feeds_by_folder), to_folder)) for feed_id, in_folder in feeds_by_folder: + feed_id = int(feed_id) self.move_feed_to_folder(feed_id, in_folder, to_folder) return self diff --git a/apps/reader/tests.py b/apps/reader/tests.py index f9c173650..ba44ddd22 100644 --- a/apps/reader/tests.py +++ b/apps/reader/tests.py @@ -96,6 +96,22 @@ class ReaderTest(TestCase): response = self.client.get(reverse('load-feeds')) feeds = json.decode(response.content) self.assertEquals(feeds['folders'], [2, 3, 8, 9, {'Tech': [1, 4, 5, {'Deep Tech': [6, 7]}]}, {'Blogs': [8, 9]}]) + + def test_move_feeds_by_folder(self): + self.client.login(username='Dejal', password='test') + + response = self.client.get(reverse('load-feeds')) + feeds = json.decode(response.content) + self.assertEquals(feeds['folders'], [5299728, 644144, 1187026, {"Brainiacs & Opinion": [569, 38, 3581, 183139, 1186180, 15]}, {"Science & Technology": [731503, 140145, 1272495, 76, 161, 39, {"Hacker": [5985150, 3323431]}]}, {"Humor": [212379, 3530, 5994357]}, {"Videos": [3240, 5168]}]) + + # Move feeds by folder + response = self.client.post(reverse('move-feeds-by-folder-to-folder'), {u'feeds_by_folder': u'[\n [\n "5994357",\n "Humor"\n ],\n [\n "3530",\n "Humor"\n ]\n]', u'to_folder': u'Brainiacs & Opinion'}) + response = json.decode(response.content) + self.assertEquals(response['code'], 1) + + response = self.client.get(reverse('load-feeds')) + feeds = json.decode(response.content) + self.assertEquals(feeds['folders'], [5299728, 644144, 1187026, {"Brainiacs & Opinion": [569, 38, 3581, 183139, 1186180, 15, 5994357, 3530]}, {"Science & Technology": [731503, 140145, 1272495, 76, 161, 39, {"Hacker": [5985150, 3323431]}]}, {"Humor": [212379]}, {"Videos": [3240, 5168]}]) def test_load_single_feed(self): # from django.conf import settings diff --git a/apps/reader/views.py b/apps/reader/views.py index aa86b6435..0913fc7af 100644 --- a/apps/reader/views.py +++ b/apps/reader/views.py @@ -322,8 +322,10 @@ def load_feeds_flat(request): user = request.user include_favicons = is_true(request.REQUEST.get('include_favicons', False)) update_counts = is_true(request.REQUEST.get('update_counts', True)) + include_inactive = is_true(request.REQUEST.get('include_inactive', False)) feeds = {} + inactive_feeds = {} day_ago = datetime.datetime.now() - datetime.timedelta(days=1) scheduled_feeds = [] iphone_version = "2.1" # Preserved forever. Don't change. @@ -345,7 +347,9 @@ def load_feeds_flat(request): if not user_subs and folders: folders.auto_activate() user_subs = UserSubscription.objects.select_related('feed').filter(user=user, active=True) - + if include_inactive: + inactive_subs = UserSubscription.objects.select_related('feed').filter(user=user, active=False) + for sub in user_subs: if update_counts and sub.needs_unread_recalc: sub.calculate_feed_scores(silent=True) @@ -357,14 +361,21 @@ def load_feeds_flat(request): elif sub.feed.next_scheduled_update < day_ago: scheduled_feeds.append(sub.feed.pk) + if include_inactive: + for sub in inactive_subs: + inactive_feeds[sub.feed_id] = sub.canonical(include_favicon=include_favicons) + if len(scheduled_feeds) > 0 and request.user.is_authenticated(): logging.user(request, "~SN~FMTasking the scheduling immediate fetch of ~SB%s~SN feeds..." % len(scheduled_feeds)) ScheduleImmediateFetches.apply_async(kwargs=dict(feed_ids=scheduled_feeds, user_id=user.pk)) flat_folders = [] + flat_folders_with_inactive = [] if folders: flat_folders = folders.flatten_folders(feeds=feeds) + flat_folders_with_inactive = folders.flatten_folders(feeds=feeds, + inactive_feeds=inactive_feeds) social_params = { 'user_id': user.pk, @@ -382,12 +393,14 @@ def load_feeds_flat(request): if not user_subs: categories = MCategory.serialize() - logging.user(request, "~FB~SBLoading ~FY%s~FB/~FM%s~FB feeds/socials ~FMflat~FB%s" % ( - len(feeds.keys()), len(social_feeds), '. ~FCUpdating counts.' if update_counts else '')) + logging.user(request, "~FB~SBLoading ~FY%s~FB/~FM%s~FB/~FR%s~FB feeds/socials/inactive ~FMflat~FB%s" % ( + len(feeds.keys()), len(social_feeds), len(inactive_feeds), '. ~FCUpdating counts.' if update_counts else '')) data = { "flat_folders": flat_folders, - "feeds": feeds, + "flat_folders_with_inactive": flat_folders_with_inactive, + "feeds": feeds if not include_inactive else {"0": "Don't include `include_inactive=true` if you want active feeds."}, + "inactive_feeds": inactive_feeds if include_inactive else {"0": "Include `include_inactive=true`"}, "social_feeds": social_feeds, "social_profile": social_profile, "social_services": social_services, @@ -541,7 +554,7 @@ def load_single_feed(request, feed_id): offset = limit * (page-1) order = request.REQUEST.get('order', 'newest') read_filter = request.REQUEST.get('read_filter', 'all') - query = request.REQUEST.get('query') + query = request.REQUEST.get('query', '').strip() include_story_content = is_true(request.REQUEST.get('include_story_content', True)) include_hidden = is_true(request.REQUEST.get('include_hidden', False)) message = None @@ -793,7 +806,7 @@ def load_starred_stories(request): offset = int(request.REQUEST.get('offset', 0)) limit = int(request.REQUEST.get('limit', 10)) page = int(request.REQUEST.get('page', 0)) - query = request.REQUEST.get('query') + query = request.REQUEST.get('query', '').strip() order = request.REQUEST.get('order', 'newest') tag = request.REQUEST.get('tag') story_hashes = request.REQUEST.getlist('h')[:100] @@ -1097,7 +1110,7 @@ def load_read_stories(request): limit = int(request.REQUEST.get('limit', 10)) page = int(request.REQUEST.get('page', 0)) order = request.REQUEST.get('order', 'newest') - query = request.REQUEST.get('query') + query = request.REQUEST.get('query', '').strip() now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone) message = None if page: offset = limit * (page - 1) @@ -1185,7 +1198,7 @@ def load_river_stories__redis(request): page = int(request.REQUEST.get('page', 1)) order = request.REQUEST.get('order', 'newest') read_filter = request.REQUEST.get('read_filter', 'unread') - query = request.REQUEST.get('query') + query = request.REQUEST.get('query', '').strip() include_hidden = is_true(request.REQUEST.get('include_hidden', False)) now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone) usersubs = [] diff --git a/apps/rss_feeds/models.py b/apps/rss_feeds/models.py index 4d291d567..e4c6acaf5 100644 --- a/apps/rss_feeds/models.py +++ b/apps/rss_feeds/models.py @@ -20,6 +20,7 @@ from django.db import models from django.db import IntegrityError from django.conf import settings from django.db.models.query import QuerySet +from django.db.utils import DatabaseError from django.core.urlresolvers import reverse from django.contrib.auth.models import User from django.contrib.sites.models import Site @@ -226,6 +227,9 @@ class Feed(models.Model): try: super(Feed, self).save(*args, **kwargs) + except DatabaseError, e: + logging.debug(" ---> ~FBFeed update failed, no change: %s / %s..." % (kwargs.get('update_fields', None), e)) + pass except IntegrityError, e: logging.debug(" ---> ~FRFeed save collision (%s), checking dupe..." % e) duplicate_feeds = Feed.objects.filter(feed_address=self.feed_address, @@ -2484,8 +2488,11 @@ class MStarredStoryCounts(mongo.Document): if not total_only: cls.objects(user_id=user_id).delete() - user_tags = cls.count_tags_for_user(user_id) - user_feeds = cls.count_feeds_for_user(user_id) + try: + user_tags = cls.count_tags_for_user(user_id) + user_feeds = cls.count_feeds_for_user(user_id) + except pymongo.errors.OperationFailure, e: + logging.debug(" ---> ~FBOperationError on mongo: ~SB%s" % e) total_stories_count = MStarredStory.objects(user_id=user_id).count() cls.objects(user_id=user_id, tag=None, feed_id=None).update_one(set__count=total_stories_count, diff --git a/apps/social/views.py b/apps/social/views.py index f89dd57ca..1d5886068 100644 --- a/apps/social/views.py +++ b/apps/social/views.py @@ -47,7 +47,7 @@ def load_social_stories(request, user_id, username=None): page = request.REQUEST.get('page') order = request.REQUEST.get('order', 'newest') read_filter = request.REQUEST.get('read_filter', 'all') - query = request.REQUEST.get('query') + query = request.REQUEST.get('query', '').strip() stories = [] message = None diff --git a/clients/android/NewsBlur/AndroidManifest.xml b/clients/android/NewsBlur/AndroidManifest.xml index 5849824b3..107d9ac37 100644 --- a/clients/android/NewsBlur/AndroidManifest.xml +++ b/clients/android/NewsBlur/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="117" + android:versionName="4.7.1" > + + android:showAsAction="ifRoom" android:icon="@drawable/search" /> diff --git a/clients/android/NewsBlur/res/values/strings.xml b/clients/android/NewsBlur/res/values/strings.xml index 67458c624..287bdd64f 100644 --- a/clients/android/NewsBlur/res/values/strings.xml +++ b/clients/android/NewsBlur/res/values/strings.xml @@ -224,7 +224,7 @@ ALL - Mark entire folder read + Mark all read Cancel @@ -279,4 +279,20 @@ DOWN_NEXT OFF + + Confirm Mark All Read + None + Feeds and Folders + Folders Only + + @string/feed_and_folder + @string/folder_only + @string/none + + + FEED_AND_FOLDER + FOLDER_ONLY + NONE + + FOLDER_ONLY diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/AllStoriesItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/AllStoriesItemsList.java index a0db39acf..e148a32d7 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/AllStoriesItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/AllStoriesItemsList.java @@ -2,23 +2,19 @@ package com.newsblur.activity; import android.os.Bundle; import android.app.FragmentTransaction; -import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import com.newsblur.R; import com.newsblur.fragment.AllStoriesItemListFragment; -import com.newsblur.fragment.MarkAllReadDialogFragment; -import com.newsblur.fragment.MarkAllReadDialogFragment.MarkAllReadDialogListener; import com.newsblur.util.DefaultFeedView; import com.newsblur.util.FeedSet; import com.newsblur.util.PrefConstants; import com.newsblur.util.PrefsUtils; import com.newsblur.util.ReadFilter; -import com.newsblur.util.StoryOrder; import com.newsblur.util.UIUtils; -public class AllStoriesItemsList extends ItemsList implements MarkAllReadDialogListener { +public class AllStoriesItemsList extends ItemsList { @Override protected void onCreate(Bundle bundle) { @@ -40,17 +36,6 @@ public class AllStoriesItemsList extends ItemsList implements MarkAllReadDialogL protected FeedSet createFeedSet() { return FeedSet.allFeeds(); } - - @Override - public void markItemListAsRead() { - MarkAllReadDialogFragment dialog = MarkAllReadDialogFragment.newInstance(getResources().getString(R.string.all_stories)); - dialog.show(fragmentManager, "dialog"); - } - - @Override - public void onMarkAllRead() { - super.markItemListAsRead(); - } @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -77,10 +62,4 @@ public class AllStoriesItemsList extends ItemsList implements MarkAllReadDialogL itemListFragment.setDefaultFeedView(value); } } - - @Override - public void onCancel() { - // do nothing - } - } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/FeedItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/FeedItemsList.java index c917a1e7c..bd496afc8 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/FeedItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/FeedItemsList.java @@ -15,7 +15,6 @@ import com.newsblur.util.DefaultFeedView; import com.newsblur.util.FeedSet; import com.newsblur.util.PrefsUtils; import com.newsblur.util.ReadFilter; -import com.newsblur.util.StoryOrder; import com.newsblur.util.UIUtils; public class FeedItemsList extends ItemsList { diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java index ad37bffa8..da4a9fcb1 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java @@ -4,21 +4,17 @@ import android.os.Bundle; import android.app.FragmentTransaction; import android.view.Menu; import android.view.MenuInflater; -import android.util.Log; import com.newsblur.R; import com.newsblur.fragment.FolderItemListFragment; -import com.newsblur.fragment.MarkAllReadDialogFragment; -import com.newsblur.fragment.MarkAllReadDialogFragment.MarkAllReadDialogListener; import com.newsblur.util.DefaultFeedView; import com.newsblur.util.FeedSet; import com.newsblur.util.FeedUtils; import com.newsblur.util.PrefsUtils; import com.newsblur.util.ReadFilter; -import com.newsblur.util.StoryOrder; import com.newsblur.util.UIUtils; -public class FolderItemsList extends ItemsList implements MarkAllReadDialogListener { +public class FolderItemsList extends ItemsList { public static final String EXTRA_FOLDER_NAME = "folderName"; private String folderName; @@ -54,17 +50,6 @@ public class FolderItemsList extends ItemsList implements MarkAllReadDialogListe return true; } - @Override - public void markItemListAsRead() { - MarkAllReadDialogFragment dialog = MarkAllReadDialogFragment.newInstance(folderName); - dialog.show(fragmentManager, "dialog"); - } - - @Override - public void onMarkAllRead() { - super.markItemListAsRead(); - } - @Override protected void updateReadFilterPreference(ReadFilter newValue) { PrefsUtils.setReadFilterForFolder(this, folderName, newValue); @@ -82,10 +67,4 @@ public class FolderItemsList extends ItemsList implements MarkAllReadDialogListe itemListFragment.setDefaultFeedView(value); } } - - @Override - public void onCancel() { - // do nothing - } - } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/ItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/ItemsList.java index 7b2902924..86c19079c 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/ItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/ItemsList.java @@ -17,13 +17,17 @@ import butterknife.FindView; import com.newsblur.R; import com.newsblur.fragment.DefaultFeedViewDialogFragment; import com.newsblur.fragment.ItemListFragment; +import com.newsblur.fragment.MarkAllReadDialogFragment; +import com.newsblur.fragment.MarkAllReadDialogFragment.MarkAllReadDialogListener; import com.newsblur.fragment.ReadFilterDialogFragment; import com.newsblur.fragment.StoryOrderDialogFragment; import com.newsblur.service.NBSyncService; +import com.newsblur.util.AppConstants; import com.newsblur.util.DefaultFeedView; import com.newsblur.util.DefaultFeedViewChangedListener; import com.newsblur.util.FeedSet; import com.newsblur.util.FeedUtils; +import com.newsblur.util.MarkAllReadConfirmation; import com.newsblur.util.PrefsUtils; import com.newsblur.util.ReadFilter; import com.newsblur.util.ReadFilterChangedListener; @@ -32,7 +36,7 @@ import com.newsblur.util.StoryOrder; import com.newsblur.util.StoryOrderChangedListener; import com.newsblur.util.UIUtils; -public abstract class ItemsList extends NbActivity implements StoryOrderChangedListener, ReadFilterChangedListener, DefaultFeedViewChangedListener { +public abstract class ItemsList extends NbActivity implements StoryOrderChangedListener, ReadFilterChangedListener, DefaultFeedViewChangedListener, MarkAllReadDialogListener { private static final String STORY_ORDER = "storyOrder"; private static final String READ_FILTER = "readFilter"; @@ -64,7 +68,7 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL if (PrefsUtils.isAutoOpenFirstUnread(this)) { if (FeedUtils.dbHelper.getUnreadCount(fs, intelState) > 0) { - UIUtils.startReadingActivity(fs, Reading.FIND_FIRST_UNREAD, this, false); + UIUtils.startReadingActivity(fs, Reading.FIND_FIRST_UNREAD, this); } } @@ -95,6 +99,7 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL @Override protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); if (searchQueryInput != null) { String q = searchQueryInput.getText().toString().trim(); if (q.length() > 0) { @@ -126,6 +131,17 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL } public void markItemListAsRead() { + MarkAllReadConfirmation confirmation = PrefsUtils.getMarkAllReadConfirmation(this); + if (confirmation.feedSetRequiresConfirmation(fs)) { + MarkAllReadDialogFragment dialog = MarkAllReadDialogFragment.newInstance(fs); + dialog.show(fragmentManager, "dialog"); + } else { + onMarkAllRead(fs); + } + } + + @Override + public void onMarkAllRead(FeedSet feedSet) { if (itemListFragment != null) { // since v6.0 of Android, the ListView in the fragment likes to crash if the underlying // dataset changes rapidly as happens when marking-all-read and when the fragment is @@ -205,6 +221,9 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL if (overlayStatusText != null) { String syncStatus = NBSyncService.getSyncStatusMessage(this, true); if (syncStatus != null) { + if (AppConstants.VERBOSE_LOG) { + syncStatus = syncStatus + UIUtils.getMemoryUsageDebug(this); + } overlayStatusText.setText(syncStatus); overlayStatusText.setVisibility(View.VISIBLE); } else { @@ -231,7 +250,6 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL @Override public void storyOrderChanged(StoryOrder newValue) { updateStoryOrderPreference(newValue); - FeedUtils.clearReadingSession(); itemListFragment.resetEmptyState(); itemListFragment.hasUpdated(); itemListFragment.scrollToTop(); @@ -240,7 +258,6 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL @Override public void readFilterChanged(ReadFilter newValue) { updateReadFilterPreference(newValue); - FeedUtils.clearReadingSession(); itemListFragment.resetEmptyState(); itemListFragment.hasUpdated(); itemListFragment.scrollToTop(); diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/Main.java b/clients/android/NewsBlur/src/com/newsblur/activity/Main.java index 97263e536..d4f89dac4 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/Main.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/Main.java @@ -28,16 +28,18 @@ import com.newsblur.fragment.FeedIntelligenceSelectorFragment; import com.newsblur.fragment.FolderListFragment; import com.newsblur.fragment.LoginAsDialogFragment; import com.newsblur.fragment.LogoutDialogFragment; +import com.newsblur.fragment.MarkAllReadDialogFragment.MarkAllReadDialogListener; import com.newsblur.service.BootReceiver; import com.newsblur.service.NBSyncService; import com.newsblur.util.AppConstants; +import com.newsblur.util.FeedSet; import com.newsblur.util.FeedUtils; import com.newsblur.util.PrefsUtils; import com.newsblur.util.StateFilter; import com.newsblur.util.UIUtils; import com.newsblur.view.StateToggleButton.StateChangedListener; -public class Main extends NbActivity implements StateChangedListener, SwipeRefreshLayout.OnRefreshListener, AbsListView.OnScrollListener, PopupMenu.OnMenuItemClickListener { +public class Main extends NbActivity implements StateChangedListener, SwipeRefreshLayout.OnRefreshListener, AbsListView.OnScrollListener, PopupMenu.OnMenuItemClickListener, MarkAllReadDialogListener { private FolderListFragment folderFeedList; private FragmentManager fragmentManager; @@ -100,8 +102,6 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre NBSyncService.clearPendingStoryRequest(); NBSyncService.flushRecounts(); - NBSyncService.setActivationMode(NBSyncService.ActivationMode.ALL); - FeedUtils.activateAllStories(); FeedUtils.clearReadingSession(); updateStatusIndicators(); @@ -172,6 +172,9 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre if (overlayStatusText != null) { String syncStatus = NBSyncService.getSyncStatusMessage(this, false); if (syncStatus != null) { + if (AppConstants.VERBOSE_LOG) { + syncStatus = syncStatus + UIUtils.getMemoryUsageDebug(this); + } overlayStatusText.setText(syncStatus); overlayStatusText.setVisibility(View.VISIBLE); } else { @@ -278,4 +281,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre } } + @Override + public void onMarkAllRead(FeedSet feedSet) { + FeedUtils.markFeedsRead(feedSet, null, null, this); + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java b/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java index 567e36152..7d7e597b5 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java @@ -220,7 +220,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener finish(); return null; } - return FeedUtils.dbHelper.getStoriesLoader(fs, intelState); + return FeedUtils.dbHelper.getActiveStoriesLoader(fs); } @Override @@ -281,7 +281,6 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener } } // if the story wasn't found, try to get more stories into the cursor - FeedUtils.activateAllStories(); this.checkStoryCount(readingAdapter.getCount()+1); } @@ -396,6 +395,9 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener if (overlayStatusText != null) { String syncStatus = NBSyncService.getSyncStatusMessage(this, true); if (syncStatus != null) { + if (AppConstants.VERBOSE_LOG) { + syncStatus = syncStatus + UIUtils.getMemoryUsageDebug(this); + } overlayStatusText.setText(syncStatus); overlayStatusText.setVisibility(View.VISIBLE); } else { @@ -677,13 +679,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener */ private void nextUnread() { unreadSearchActive = true; - - // the first time an unread search is triggered, also trigger an activation of unreads, so - // we don't search for a story that doesn't exist in the cursor - if (!unreadSearchStarted) { - FeedUtils.activateAllStories(); - unreadSearchStarted = true; - } + unreadSearchStarted = true; // if we somehow got tapped before construction or are running during destruction, stop and // let either finish. search will happen when the cursor is pushed. diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/SavedStoriesReading.java b/clients/android/NewsBlur/src/com/newsblur/activity/SavedStoriesReading.java index 56cdd6adf..478fe9d91 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/SavedStoriesReading.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/SavedStoriesReading.java @@ -21,10 +21,4 @@ public class SavedStoriesReading extends Reading { getLoaderManager().initLoader(0, null, this); } - @Override - public void onLoadFinished(Loader loader, Cursor cursor) { - // every time we see a set of saved stories, tag them so they don't disappear during this reading session - FeedUtils.dbHelper.markSavedReadingSession(); - super.onLoadFinished(loader, cursor); - } } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/SocialFeedReading.java b/clients/android/NewsBlur/src/com/newsblur/activity/SocialFeedReading.java index fc1fafe9c..99cb7a861 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/SocialFeedReading.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/SocialFeedReading.java @@ -11,13 +11,9 @@ import com.newsblur.util.UIUtils; public class SocialFeedReading extends Reading { - public static final String EXTRA_IGNORE_FILTERS = "ignore_filters"; - private boolean ignoreFilters; - @Override protected void onCreate(Bundle savedInstanceBundle) { super.onCreate(savedInstanceBundle); - ignoreFilters = getIntent().hasExtra(EXTRA_IGNORE_FILTERS); SocialFeed socialFeed = FeedUtils.dbHelper.getSocialFeed(fs.getSingleSocialFeed().getKey()); if (socialFeed == null) finish(); // don't open fatally stale intents UIUtils.setCustomActionBar(this, socialFeed.photoUrl, socialFeed.feedTitle); @@ -25,14 +21,4 @@ public class SocialFeedReading extends Reading { getLoaderManager().initLoader(0, null, this); } - @Override - public Loader onCreateLoader(int loaderId, Bundle bundle) { - // If we have navigated from the profile we want to ignore the StateFilter and ReadFilter settings - // for the feed to ensure we can find the story. - if (ignoreFilters) { - return FeedUtils.dbHelper.getStoriesLoaderIgnoreFilters(fs); - } else { - return super.onCreateLoader(loaderId, bundle); - } - } } diff --git a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java index e7783b845..aa8f2ab7f 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java @@ -20,6 +20,7 @@ public class BlurDatabase extends SQLiteOpenHelper { db.execSQL(DatabaseConstants.FOLDER_SQL); db.execSQL(DatabaseConstants.USER_SQL); db.execSQL(DatabaseConstants.STORY_SQL); + db.execSQL(DatabaseConstants.READING_SESSION_SQL); db.execSQL(DatabaseConstants.STORY_TEXT_SQL); db.execSQL(DatabaseConstants.COMMENT_SQL); db.execSQL(DatabaseConstants.REPLY_SQL); @@ -36,6 +37,7 @@ public class BlurDatabase extends SQLiteOpenHelper { db.execSQL(drop + DatabaseConstants.SOCIALFEED_TABLE); db.execSQL(drop + DatabaseConstants.FOLDER_TABLE); db.execSQL(drop + DatabaseConstants.STORY_TABLE); + db.execSQL(drop + DatabaseConstants.READING_SESSION_TABLE); db.execSQL(drop + DatabaseConstants.STORY_TEXT_TABLE); db.execSQL(drop + DatabaseConstants.USER_TABLE); db.execSQL(drop + DatabaseConstants.COMMENT_TABLE); diff --git a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java index 169fbbff8..9c79a98b5 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java @@ -136,10 +136,6 @@ public class BlurDatabaseHelper { } } - public void cleanupAllStories() { - synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.STORY_TABLE, null, null);} - } - public void cleanupStoryText() { String q = "DELETE FROM " + DatabaseConstants.STORY_TEXT_TABLE + " WHERE " + DatabaseConstants.STORY_TEXT_STORY_HASH + " NOT IN " + @@ -266,149 +262,136 @@ public class BlurDatabaseHelper { return urls; } - public void insertStories(StoriesResponse apiResponse, NBSyncService.ActivationMode actMode, long modeCutoff) { - // to insert classifiers, we need to determine the feed ID of the stories in this - // response, so sniff one out. - String impliedFeedId = null; + public void insertStories(StoriesResponse apiResponse, boolean forImmediateReading) { + StateFilter intelState = PrefsUtils.getStateFilter(context); + synchronized (RW_MUTEX) { + // do not attempt to use beginTransactionNonExclusive() to reduce lock time for this very heavy set + // of calls. most versions of Android incorrectly implement the underlying SQLite calls and will + // result in crashes that poison the DB beyond repair + dbRW.beginTransaction(); + try { + + // to insert classifiers, we need to determine the feed ID of the stories in this + // response, so sniff one out. + String impliedFeedId = null; - // handle users - if (apiResponse.users != null) { - List userValues = new ArrayList(apiResponse.users.length); - for (UserProfile user : apiResponse.users) { - userValues.add(user.getValues()); - } - bulkInsertValues(DatabaseConstants.USER_TABLE, userValues); - } - - // handle supplemental feed data that may have been included (usually in social requests) - if (apiResponse.feeds != null) { - List feedValues = new ArrayList(apiResponse.feeds.size()); - for (Feed feed : apiResponse.feeds) { - feedValues.add(feed.getValues()); - } - bulkInsertValues(DatabaseConstants.FEED_TABLE, feedValues); - } - - // handle story content - List storyValues = new ArrayList(apiResponse.stories.length); - List socialStoryValues = new ArrayList(); - for (Story story : apiResponse.stories) { - ContentValues values = story.getValues(); - // the basic columns are fine for the stories table - storyValues.add(values); - // if a story was shared by a user, also insert it into the social table under their userid, too - for (String sharedUserId : story.sharedUserIds) { - ContentValues socialValues = new ContentValues(); - socialValues.put(DatabaseConstants.SOCIALFEED_STORY_USER_ID, sharedUserId); - socialValues.put(DatabaseConstants.SOCIALFEED_STORY_STORYID, values.getAsString(DatabaseConstants.STORY_ID)); - socialStoryValues.add(socialValues); - } - impliedFeedId = story.feedId; - } - if (storyValues.size() > 0) { - synchronized (RW_MUTEX) { - dbRW.beginTransaction(); - try { - bulkInsertValuesExtSync(DatabaseConstants.STORY_TABLE, storyValues); - markStoriesActive(actMode, modeCutoff); - dbRW.setTransactionSuccessful(); - } finally { - dbRW.endTransaction(); + // handle users + if (apiResponse.users != null) { + List userValues = new ArrayList(apiResponse.users.length); + for (UserProfile user : apiResponse.users) { + userValues.add(user.getValues()); + } + bulkInsertValuesExtSync(DatabaseConstants.USER_TABLE, userValues); } - } - } - if (socialStoryValues.size() > 0) { - synchronized (RW_MUTEX) { - dbRW.beginTransaction(); - try { + + // handle supplemental feed data that may have been included (usually in social requests) + if (apiResponse.feeds != null) { + List feedValues = new ArrayList(apiResponse.feeds.size()); + for (Feed feed : apiResponse.feeds) { + feedValues.add(feed.getValues()); + } + bulkInsertValuesExtSync(DatabaseConstants.FEED_TABLE, feedValues); + } + + // handle story content + List socialStoryValues = new ArrayList(); + for (Story story : apiResponse.stories) { + ContentValues values = story.getValues(); + // immediate insert the story data + dbRW.insertWithOnConflict(DatabaseConstants.STORY_TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE); + // if a story was shared by a user, also insert it into the social table under their userid, too + for (String sharedUserId : story.sharedUserIds) { + ContentValues socialValues = new ContentValues(); + socialValues.put(DatabaseConstants.SOCIALFEED_STORY_USER_ID, sharedUserId); + socialValues.put(DatabaseConstants.SOCIALFEED_STORY_STORYID, values.getAsString(DatabaseConstants.STORY_ID)); + socialStoryValues.add(socialValues); + } + // if the story is being fetched for the immediate session, also add the hash to the session table + if (forImmediateReading && story.isStoryVisibileInState(intelState)) { + ContentValues sessionHashValues = new ContentValues(); + sessionHashValues.put(DatabaseConstants.READING_SESSION_STORY_HASH, story.storyHash); + dbRW.insert(DatabaseConstants.READING_SESSION_TABLE, null, sessionHashValues); + } + impliedFeedId = story.feedId; + } + if (socialStoryValues.size() > 0) { for(ContentValues values: socialStoryValues) { dbRW.insertWithOnConflict(DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE); } - markStoriesActive(actMode, modeCutoff); - dbRW.setTransactionSuccessful(); - } finally { - dbRW.endTransaction(); } - } - } - // handle classifiers - if (apiResponse.classifiers != null) { - for (Map.Entry entry : apiResponse.classifiers.entrySet()) { - // the API might not have included a feed ID, in which case it deserialized as -1 and must be implied - String classifierFeedId = entry.getKey(); - if (classifierFeedId.equals("-1")) { - classifierFeedId = impliedFeedId; + // handle classifiers + if (apiResponse.classifiers != null) { + for (Map.Entry entry : apiResponse.classifiers.entrySet()) { + // the API might not have included a feed ID, in which case it deserialized as -1 and must be implied + String classifierFeedId = entry.getKey(); + if (classifierFeedId.equals("-1")) { + classifierFeedId = impliedFeedId; + } + List classifierValues = entry.getValue().getContentValues(); + for (ContentValues values : classifierValues) { + values.put(DatabaseConstants.CLASSIFIER_ID, classifierFeedId); + } + dbRW.delete(DatabaseConstants.CLASSIFIER_TABLE, DatabaseConstants.CLASSIFIER_ID + " = ?", new String[] { classifierFeedId }); + bulkInsertValuesExtSync(DatabaseConstants.CLASSIFIER_TABLE, classifierValues); + } } - List classifierValues = entry.getValue().getContentValues(); - for (ContentValues values : classifierValues) { - values.put(DatabaseConstants.CLASSIFIER_ID, classifierFeedId); - } - synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.CLASSIFIER_TABLE, DatabaseConstants.CLASSIFIER_ID + " = ?", new String[] { classifierFeedId });} - bulkInsertValues(DatabaseConstants.CLASSIFIER_TABLE, classifierValues); - } - } - // handle comments - List commentValues = new ArrayList(); - List replyValues = new ArrayList(); - // track which comments were seen, so replies can be cleared before re-insertion. there isn't - // enough data to de-dupe them for an insert/update operation - List freshCommentIds = new ArrayList(); - for (Story story : apiResponse.stories) { - for (Comment comment : story.publicComments) { - comment.storyId = story.id; - // we need a primary key for comments, so construct one - comment.id = Comment.constructId(story.id, story.feedId, comment.userId); - commentValues.add(comment.getValues()); - for (Reply reply : comment.replies) { - reply.commentId = comment.id; - reply.id = reply.constructId(); - replyValues.add(reply.getValues()); + // handle comments + List commentValues = new ArrayList(); + List replyValues = new ArrayList(); + // track which comments were seen, so replies can be cleared before re-insertion. there isn't + // enough data to de-dupe them for an insert/update operation + List freshCommentIds = new ArrayList(); + for (Story story : apiResponse.stories) { + for (Comment comment : story.publicComments) { + comment.storyId = story.id; + // we need a primary key for comments, so construct one + comment.id = Comment.constructId(story.id, story.feedId, comment.userId); + commentValues.add(comment.getValues()); + for (Reply reply : comment.replies) { + reply.commentId = comment.id; + reply.id = reply.constructId(); + replyValues.add(reply.getValues()); + } + freshCommentIds.add(comment.id); + } + for (Comment comment : story.friendsComments) { + comment.storyId = story.id; + // we need a primary key for comments, so construct one + comment.id = Comment.constructId(story.id, story.feedId, comment.userId); + comment.byFriend = true; + commentValues.add(comment.getValues()); + for (Reply reply : comment.replies) { + reply.commentId = comment.id; + reply.id = reply.constructId(); + replyValues.add(reply.getValues()); + } + freshCommentIds.add(comment.id); + } + for (Comment comment : story.friendsShares) { + comment.isPseudo = true; + comment.storyId = story.id; + // we need a primary key for comments, so construct one + comment.id = Comment.constructId(story.id, story.feedId, comment.userId); + comment.byFriend = true; + commentValues.add(comment.getValues()); + for (Reply reply : comment.replies) { + reply.commentId = comment.id; + reply.id = reply.constructId(); + replyValues.add(reply.getValues()); + } + freshCommentIds.add(comment.id); + } } - freshCommentIds.add(comment.id); - } - for (Comment comment : story.friendsComments) { - comment.storyId = story.id; - // we need a primary key for comments, so construct one - comment.id = Comment.constructId(story.id, story.feedId, comment.userId); - comment.byFriend = true; - commentValues.add(comment.getValues()); - for (Reply reply : comment.replies) { - reply.commentId = comment.id; - reply.id = reply.constructId(); - replyValues.add(reply.getValues()); - } - freshCommentIds.add(comment.id); - } - for (Comment comment : story.friendsShares) { - comment.isPseudo = true; - comment.storyId = story.id; - // we need a primary key for comments, so construct one - comment.id = Comment.constructId(story.id, story.feedId, comment.userId); - comment.byFriend = true; - commentValues.add(comment.getValues()); - for (Reply reply : comment.replies) { - reply.commentId = comment.id; - reply.id = reply.constructId(); - replyValues.add(reply.getValues()); - } - freshCommentIds.add(comment.id); - } - } - deleteRepliesForComments(freshCommentIds); - bulkInsertValues(DatabaseConstants.COMMENT_TABLE, commentValues); - bulkInsertValues(DatabaseConstants.REPLY_TABLE, replyValues); - } - - private void deleteRepliesForComments(Collection commentIds) { - // NB: attempting to do this with a "WHERE col IN (vector)" for speed can cause errors on some versions of sqlite - synchronized (RW_MUTEX) { - dbRW.beginTransaction(); - try { - for (String commentId : commentIds) { + // before inserting new replies, remove existing ones for the fetched comments + // NB: attempting to do this with a "WHERE col IN (vector)" for speed can cause errors on some versions of sqlite + for (String commentId : freshCommentIds) { dbRW.delete(DatabaseConstants.REPLY_TABLE, DatabaseConstants.REPLY_COMMENTID + " = ?", new String[]{commentId}); } + bulkInsertValuesExtSync(DatabaseConstants.COMMENT_TABLE, commentValues); + bulkInsertValuesExtSync(DatabaseConstants.REPLY_TABLE, replyValues); + dbRW.setTransactionSuccessful(); } finally { dbRW.endTransaction(); @@ -498,16 +481,15 @@ public class BlurDatabaseHelper { // update the story's read state ContentValues values = new ContentValues(); values.put(DatabaseConstants.STORY_READ, read); - values.put(DatabaseConstants.STORY_READ_THIS_SESSION, read); dbRW.update(DatabaseConstants.STORY_TABLE, values, DatabaseConstants.STORY_HASH + " = ?", new String[]{story.storyHash}); // which column to inc/dec depends on story intel String impactedCol; String impactedSocialCol; - if (story.intelTotal < 0) { + if (story.intelligence.calcTotalIntel() < 0) { // negative stories don't affect counts dbRW.setTransactionSuccessful(); return impactedFeeds; - } else if (story.intelTotal == 0 ) { + } else if (story.intelligence.calcTotalIntel() == 0 ) { impactedCol = DatabaseConstants.FEED_NEUTRAL_COUNT; impactedSocialCol = DatabaseConstants.SOCIAL_FEED_NEUTRAL_COUNT; } else { @@ -672,7 +654,11 @@ public class BlurDatabaseHelper { * Get the unread count for the given feedset based on local story state. */ public int getLocalUnreadCount(FeedSet fs, StateFilter stateFilter) { - Cursor c = getStoriesCursor(fs, stateFilter, ReadFilter.PURE_UNREAD, null, null); + StringBuilder sel = new StringBuilder(); + ArrayList selArgs = new ArrayList(); + getLocalStorySelectionAndArgs(sel, selArgs, fs, stateFilter, ReadFilter.UNREAD); + + Cursor c = dbRO.rawQuery(sel.toString(), selArgs.toArray(new String[selArgs.size()])); int count = c.getCount(); c.close(); return count; @@ -796,41 +782,6 @@ public class BlurDatabaseHelper { synchronized (RW_MUTEX) {dbRW.insertOrThrow(DatabaseConstants.STORY_TEXT_TABLE, null, values);} } - /** - * Tags all saved stories with the reading session flag so they don't disappear if unsaved. - */ - public void markSavedReadingSession() { - ContentValues values = new ContentValues(); - values.put(DatabaseConstants.STORY_READ_THIS_SESSION, true); - synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, DatabaseConstants.STORY_STARRED + " = 1", null);} - } - - /** - * Clears the read_this_session and search_hit flags for all stories. - */ - public void clearReadingSession() { - ContentValues values = new ContentValues(); - values.put(DatabaseConstants.STORY_READ_THIS_SESSION, false); - values.put(DatabaseConstants.STORY_SEARCHIT, false); - synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, null, null);} - } - - public void markStoriesActive(NBSyncService.ActivationMode actMode, long modeCutoff) { - ContentValues values = new ContentValues(); - values.put(DatabaseConstants.STORY_ACTIVE, true); - - String selection = null; - if (actMode == NBSyncService.ActivationMode.ALL) { - // leave the selection null to mark all - } else if (actMode == NBSyncService.ActivationMode.OLDER) { - selection = DatabaseConstants.STORY_TIMESTAMP + " <= " + Long.toString(modeCutoff); - } else if (actMode == NBSyncService.ActivationMode.NEWER) { - selection = DatabaseConstants.STORY_TIMESTAMP + " >= " + Long.toString(modeCutoff); - } - - synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, selection, null);} - } - public Loader getSocialFeedsLoader(final StateFilter stateFilter) { return new QueryCursorLoader(context) { protected Cursor createCursor() {return getSocialFeedsCursor(stateFilter, cancellationSignal);} @@ -891,112 +842,118 @@ public class BlurDatabaseHelper { return dbRO.query(DatabaseConstants.STARRED_STORY_COUNT_TABLE, null, null, null, null, null, null); } - public Loader getStoriesLoader(final FeedSet fs, final StateFilter stateFilter) { + public Loader getActiveStoriesLoader(final FeedSet fs) { + final StoryOrder order = PrefsUtils.getStoryOrder(context, fs); return new QueryCursorLoader(context) { protected Cursor createCursor() { - ReadFilter readFilter = PrefsUtils.getReadFilter(context, fs); - return getStoriesCursor(fs, stateFilter, readFilter, cancellationSignal); + return getActiveStoriesCursor(fs, order, cancellationSignal); } }; } + + private Cursor getActiveStoriesCursor(FeedSet fs, StoryOrder order, CancellationSignal cancellationSignal) { + // stories aren't actually queried directly via the FeedSet and filters set in the UI. rather, + // those filters are use to push live or cached story hashes into the reading session table, and + // those hashes are used to pull story data from the story table + StringBuilder q = new StringBuilder(DatabaseConstants.STORY_QUERY_BASE); + + if (fs.isAllRead()) { + q.append(" ORDER BY " + DatabaseConstants.READ_STORY_ORDER); + } else if (fs.isAllSaved()) { + q.append(" ORDER BY " + DatabaseConstants.getSavedStoriesSortOrder(order)); + } else { + q.append(" ORDER BY ").append(DatabaseConstants.getStorySortOrder(order)); + } + return rawQuery(q.toString(), null, cancellationSignal); + } + + public void clearStorySession() { + synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.READING_SESSION_TABLE, null, null);} + } + + public void prepareReadingSession(FeedSet fs, StateFilter stateFilter) { + ReadFilter readFilter = PrefsUtils.getReadFilter(context, fs); + prepareReadingSession(fs, stateFilter, readFilter); + } /** - * When navigating to a social story from an interaction/activity we want to ignore - * the any state so we can be sure we find the selected story. + * Populates the reading session table with hashes of already-fetched stories that meet the + * criteria for the given FeedSet and filters; these hashes will be supplemented by hashes + * fetched via the API and used to actually select story data when rendering story lists. */ - public Loader getStoriesLoaderIgnoreFilters(final FeedSet fs) { - return new QueryCursorLoader(context) { - protected Cursor createCursor() {return getStoriesCursor(fs, StateFilter.ALL, ReadFilter.ALL, cancellationSignal);} - }; + private void prepareReadingSession(FeedSet fs, StateFilter stateFilter, ReadFilter readFilter) { + // a selection filter that will be used to pull active story hashes from the stories table into the reading session table + StringBuilder sel = new StringBuilder(); + // any selection args that need to be used within the inner select statement + ArrayList selArgs = new ArrayList(); + + getLocalStorySelectionAndArgs(sel, selArgs, fs, stateFilter, readFilter); + + // use the inner select statement to push the active hashes into the session table + StringBuilder q = new StringBuilder("INSERT INTO " + DatabaseConstants.READING_SESSION_TABLE); + q.append(" (" + DatabaseConstants.READING_SESSION_STORY_HASH + ") "); + q.append(sel); + + dbRW.execSQL(q.toString(), selArgs.toArray(new String[selArgs.size()])); } - private Cursor getStoriesCursor(FeedSet fs, StateFilter stateFilter, ReadFilter readFilter, CancellationSignal cancellationSignal) { - if (fs == null) return null; - StoryOrder order = PrefsUtils.getStoryOrder(context, fs); - return getStoriesCursor(fs, stateFilter, readFilter, order, cancellationSignal); - } - - private Cursor getStoriesCursor(FeedSet fs, StateFilter stateFilter, ReadFilter readFilter, StoryOrder order, CancellationSignal cancellationSignal) { - if (fs == null) return null; - + /** + * Gets hashes of already-fetched stories that satisfy the given FeedSet and filters. Can be used + * both to populate a reading session or to count local unreads. + */ + private void getLocalStorySelectionAndArgs(StringBuilder sel, List selArgs, FeedSet fs, StateFilter stateFilter, ReadFilter readFilter) { + sel.append("SELECT " + DatabaseConstants.STORY_HASH); if (fs.getSingleFeed() != null) { - StringBuilder q = new StringBuilder("SELECT "); - q.append(TextUtils.join(",", DatabaseConstants.STORY_COLUMNS)); - q.append(" FROM " + DatabaseConstants.STORY_TABLE); - q.append(" WHERE " + DatabaseConstants.STORY_FEED_ID + " = ?"); - DatabaseConstants.appendStorySelectionGroupOrder(q, readFilter, order, stateFilter, null, (fs.getSearchQuery() != null)); - return rawQuery(q.toString(), new String[]{fs.getSingleFeed()}, cancellationSignal); + sel.append(" FROM " + DatabaseConstants.STORY_TABLE); + sel.append(" WHERE " + DatabaseConstants.STORY_FEED_ID + " = ?"); + selArgs.add(fs.getSingleFeed()); + DatabaseConstants.appendStorySelection(sel, selArgs, readFilter, stateFilter, fs.getSearchQuery()); } else if (fs.getMultipleFeeds() != null) { - StringBuilder q = new StringBuilder(DatabaseConstants.MULTIFEED_STORIES_QUERY_BASE); - q.append(" FROM " + DatabaseConstants.STORY_TABLE); - q.append(DatabaseConstants.JOIN_FEEDS_ON_STORIES); - q.append(" WHERE " + DatabaseConstants.STORY_TABLE + "." + DatabaseConstants.STORY_FEED_ID + " IN ( "); - q.append(TextUtils.join(",", fs.getMultipleFeeds()) + ")"); - DatabaseConstants.appendStorySelectionGroupOrder(q, readFilter, order, stateFilter, null, (fs.getSearchQuery() != null)); - return rawQuery(q.toString(), null, cancellationSignal); + sel.append(" FROM " + DatabaseConstants.STORY_TABLE); + sel.append(" WHERE " + DatabaseConstants.STORY_TABLE + "." + DatabaseConstants.STORY_FEED_ID + " IN ( "); + sel.append(TextUtils.join(",", fs.getMultipleFeeds()) + ")"); + DatabaseConstants.appendStorySelection(sel, selArgs, readFilter, stateFilter, fs.getSearchQuery()); } else if (fs.getSingleSocialFeed() != null) { - StringBuilder q = new StringBuilder(DatabaseConstants.MULTIFEED_STORIES_QUERY_BASE); - q.append(" FROM " + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE); - q.append(DatabaseConstants.JOIN_STORIES_ON_SOCIALFEED_MAP); - q.append(DatabaseConstants.JOIN_FEEDS_ON_STORIES); - q.append(" WHERE " + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE + "." + DatabaseConstants.SOCIALFEED_STORY_USER_ID + " = ? "); - DatabaseConstants.appendStorySelectionGroupOrder(q, readFilter, order, stateFilter, null, (fs.getSearchQuery() != null)); - return rawQuery(q.toString(), new String[]{fs.getSingleSocialFeed().getKey()}, cancellationSignal); + sel.append(" FROM " + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE); + sel.append(DatabaseConstants.JOIN_STORIES_ON_SOCIALFEED_MAP); + sel.append(" WHERE " + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE + "." + DatabaseConstants.SOCIALFEED_STORY_USER_ID + " = ? "); + selArgs.add(fs.getSingleSocialFeed().getKey()); + DatabaseConstants.appendStorySelection(sel, selArgs, readFilter, stateFilter, fs.getSearchQuery()); } else if (fs.isAllNormal()) { - StringBuilder q = new StringBuilder(DatabaseConstants.MULTIFEED_STORIES_QUERY_BASE); - q.append(" FROM " + DatabaseConstants.STORY_TABLE); - q.append(DatabaseConstants.JOIN_FEEDS_ON_STORIES); - q.append(" WHERE 1"); - DatabaseConstants.appendStorySelectionGroupOrder(q, readFilter, order, stateFilter, null, (fs.getSearchQuery() != null)); - return rawQuery(q.toString(), null, cancellationSignal); + sel.append(" FROM " + DatabaseConstants.STORY_TABLE); + sel.append(" WHERE 1"); + DatabaseConstants.appendStorySelection(sel, selArgs, readFilter, stateFilter, fs.getSearchQuery()); } else if (fs.isAllSocial()) { - StringBuilder q = new StringBuilder(DatabaseConstants.MULTIFEED_STORIES_QUERY_BASE); - q.append(" FROM " + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE); - q.append(DatabaseConstants.JOIN_STORIES_ON_SOCIALFEED_MAP); - q.append(DatabaseConstants.JOIN_FEEDS_ON_STORIES); - q.append(DatabaseConstants.JOIN_SOCIAL_FEEDS_ON_SOCIALFEED_MAP); - DatabaseConstants.appendStorySelectionGroupOrder(q, readFilter, order, stateFilter, DatabaseConstants.STORY_TABLE + "." + DatabaseConstants.STORY_ID, false); - return rawQuery(q.toString(), null, cancellationSignal); + sel.append(" FROM " + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE); + sel.append(DatabaseConstants.JOIN_STORIES_ON_SOCIALFEED_MAP); + DatabaseConstants.appendStorySelection(sel, selArgs, readFilter, stateFilter, fs.getSearchQuery()); } else if (fs.isAllRead()) { - StringBuilder q = new StringBuilder(DatabaseConstants.MULTIFEED_STORIES_QUERY_BASE); - q.append(" FROM " + DatabaseConstants.STORY_TABLE); - q.append(DatabaseConstants.JOIN_FEEDS_ON_STORIES); - q.append(" WHERE (" + DatabaseConstants.STORY_LAST_READ_DATE + " > 0)"); - q.append(" ORDER BY " + DatabaseConstants.READ_STORY_ORDER); - return rawQuery(q.toString(), null, cancellationSignal); + sel.append(" FROM " + DatabaseConstants.STORY_TABLE); + sel.append(" WHERE (" + DatabaseConstants.STORY_LAST_READ_DATE + " > 0)"); } else if (fs.isAllSaved()) { - StringBuilder q = new StringBuilder(DatabaseConstants.MULTIFEED_STORIES_QUERY_BASE); - q.append(" FROM " + DatabaseConstants.STORY_TABLE); - q.append(DatabaseConstants.JOIN_FEEDS_ON_STORIES); - q.append(" WHERE ((" + DatabaseConstants.STORY_STARRED + " = 1)"); - q.append(" OR (" + DatabaseConstants.STORY_READ_THIS_SESSION + " = 1))"); - if (fs.getSearchQuery() != null) { - q.append(" AND (" + DatabaseConstants.STORY_TABLE + "." + DatabaseConstants.STORY_SEARCHIT + " = 1)"); - } - q.append(" ORDER BY " + DatabaseConstants.getSavedStoriesSortOrder(order)); - return rawQuery(q.toString(), null, cancellationSignal); + sel.append(" FROM " + DatabaseConstants.STORY_TABLE); + sel.append(" WHERE (" + DatabaseConstants.STORY_STARRED + " = 1)"); + DatabaseConstants.appendStorySelection(sel, selArgs, ReadFilter.ALL, StateFilter.ALL, fs.getSearchQuery()); } else if (fs.isGlobalShared()) { - StringBuilder q = new StringBuilder(DatabaseConstants.MULTIFEED_STORIES_QUERY_BASE); - q.append(" FROM " + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE); - q.append(DatabaseConstants.JOIN_STORIES_ON_SOCIALFEED_MAP); - q.append(DatabaseConstants.JOIN_FEEDS_ON_STORIES); - DatabaseConstants.appendStorySelectionGroupOrder(q, readFilter, order, stateFilter, DatabaseConstants.STORY_TABLE + "." + DatabaseConstants.STORY_ID, false); - return rawQuery(q.toString(), null, cancellationSignal); + sel.append(" FROM " + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE); + sel.append(DatabaseConstants.JOIN_STORIES_ON_SOCIALFEED_MAP); + DatabaseConstants.appendStorySelection(sel, selArgs, readFilter, stateFilter, fs.getSearchQuery()); + } else { throw new IllegalStateException("Asked to get stories for FeedSet of unknown type."); } diff --git a/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java b/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java index a7f68a86e..5d626d9dc 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java @@ -77,31 +77,29 @@ public class DatabaseConstants { public static final String STORY_SHARED_DATE = "sharedDate"; public static final String STORY_CONTENT = "content"; public static final String STORY_SHORT_CONTENT = "short_content"; - public static final String STORY_COMMENT_COUNT = "comment_count"; public static final String STORY_FEED_ID = "feed_id"; public static final String STORY_INTELLIGENCE_AUTHORS = "intelligence_authors"; public static final String STORY_INTELLIGENCE_TAGS = "intelligence_tags"; public static final String STORY_INTELLIGENCE_FEED = "intelligence_feed"; public static final String STORY_INTELLIGENCE_TITLE = "intelligence_title"; + public static final String STORY_INTELLIGENCE_TOTAL = "intelligence_total"; public static final String STORY_PERMALINK = "permalink"; public static final String STORY_READ = "read"; - public static final String STORY_READ_THIS_SESSION = "read_this_session"; public static final String STORY_STARRED = "starred"; public static final String STORY_STARRED_DATE = "starred_date"; - public static final String STORY_SHARE_COUNT = "share_count"; public static final String STORY_SHARED_USER_IDS = "shared_user_ids"; public static final String STORY_FRIEND_USER_IDS = "comment_user_ids"; - public static final String STORY_PUBLIC_USER_IDS = "public_user_ids"; - public static final String STORY_SHORTDATE = "shortDate"; public static final String STORY_LONGDATE = "longDate"; public static final String STORY_SOCIAL_USER_ID = "socialUserId"; public static final String STORY_SOURCE_USER_ID = "sourceUserId"; public static final String STORY_TAGS = "tags"; public static final String STORY_HASH = "story_hash"; - public static final String STORY_ACTIVE = "active"; public static final String STORY_IMAGE_URLS = "image_urls"; public static final String STORY_LAST_READ_DATE = "last_read_date"; - public static final String STORY_SEARCHIT = "search_hit"; + public static final String STORY_SEARCH_HIT = "search_hit"; + + public static final String READING_SESSION_TABLE = "reading_session"; + public static final String READING_SESSION_STORY_HASH = "session_story_hash"; public static final String STORY_TEXT_TABLE = "storytext"; public static final String STORY_TEXT_STORY_HASH = "story_hash"; @@ -213,38 +211,37 @@ public class DatabaseConstants { ")"; static final String STORY_SQL = "CREATE TABLE " + STORY_TABLE + " (" + - STORY_HASH + TEXT + ", " + + STORY_HASH + TEXT + " PRIMARY KEY, " + STORY_AUTHORS + TEXT + ", " + STORY_CONTENT + TEXT + ", " + STORY_SHORT_CONTENT + TEXT + ", " + STORY_TIMESTAMP + INTEGER + ", " + STORY_SHARED_DATE + INTEGER + ", " + - STORY_SHORTDATE + TEXT + ", " + STORY_LONGDATE + TEXT + ", " + STORY_FEED_ID + INTEGER + ", " + - STORY_ID + TEXT + " PRIMARY KEY, " + + STORY_ID + TEXT + ", " + STORY_INTELLIGENCE_AUTHORS + INTEGER + ", " + STORY_INTELLIGENCE_FEED + INTEGER + ", " + STORY_INTELLIGENCE_TAGS + INTEGER + ", " + STORY_INTELLIGENCE_TITLE + INTEGER + ", " + - STORY_COMMENT_COUNT + INTEGER + ", " + - STORY_SHARE_COUNT + INTEGER + ", " + + STORY_INTELLIGENCE_TOTAL + INTEGER + ", " + STORY_SOCIAL_USER_ID + TEXT + ", " + STORY_SOURCE_USER_ID + TEXT + ", " + STORY_SHARED_USER_IDS + TEXT + ", " + - STORY_PUBLIC_USER_IDS + TEXT + ", " + STORY_FRIEND_USER_IDS + TEXT + ", " + STORY_TAGS + TEXT + ", " + STORY_PERMALINK + TEXT + ", " + STORY_READ + INTEGER + ", " + - STORY_READ_THIS_SESSION + INTEGER + ", " + STORY_STARRED + INTEGER + ", " + STORY_STARRED_DATE + INTEGER + ", " + STORY_TITLE + TEXT + ", " + - STORY_ACTIVE + INTEGER + " DEFAULT 0, " + STORY_IMAGE_URLS + TEXT + ", " + STORY_LAST_READ_DATE + INTEGER + ", " + - STORY_SEARCHIT + INTEGER + " DEFAULT 0" + + STORY_SEARCH_HIT + TEXT + + ")"; + + static final String READING_SESSION_SQL = "CREATE TABLE " + READING_SESSION_TABLE + " (" + + READING_SESSION_STORY_HASH + TEXT + ")"; static final String STORY_TEXT_SQL = "CREATE TABLE " + STORY_TEXT_TABLE + " (" + @@ -300,53 +297,41 @@ public class DatabaseConstants { SOCIAL_FEED_ID, SOCIAL_FEED_USERNAME, SOCIAL_FEED_TITLE, SOCIAL_FEED_ICON, SOCIAL_FEED_POSITIVE_COUNT, SOCIAL_FEED_NEUTRAL_COUNT, SOCIAL_FEED_NEGATIVE_COUNT, }; - public static final String SUM_STORY_TOTAL = "storyTotal"; - private static String STORY_SUM_TOTAL = " CASE " + - "WHEN MAX(" + STORY_INTELLIGENCE_AUTHORS + "," + STORY_INTELLIGENCE_TAGS + "," + STORY_INTELLIGENCE_TITLE + ") > 0 " + - "THEN MAX(" + STORY_INTELLIGENCE_AUTHORS + "," + STORY_INTELLIGENCE_TAGS + "," + STORY_INTELLIGENCE_TITLE + ") " + - "WHEN MIN(" + STORY_INTELLIGENCE_AUTHORS + "," + STORY_INTELLIGENCE_TAGS + "," + STORY_INTELLIGENCE_TITLE + ") < 0 " + - "THEN MIN(" + STORY_INTELLIGENCE_AUTHORS + "," + STORY_INTELLIGENCE_TAGS + "," + STORY_INTELLIGENCE_TITLE + ") " + - "ELSE " + STORY_INTELLIGENCE_FEED + " " + - "END AS " + SUM_STORY_TOTAL; - private static final String STORY_INTELLIGENCE_BEST = SUM_STORY_TOTAL + " > 0 "; - private static final String STORY_INTELLIGENCE_SOME = SUM_STORY_TOTAL + " >= 0 "; - private static final String STORY_INTELLIGENCE_NEUT = SUM_STORY_TOTAL + " = 0 "; - private static final String STORY_INTELLIGENCE_NEG = SUM_STORY_TOTAL + " < 0 "; - - public static final String[] STORY_COLUMNS = { - STORY_AUTHORS, STORY_COMMENT_COUNT, STORY_SHORT_CONTENT, STORY_TIMESTAMP, STORY_SHARED_DATE, STORY_SHORTDATE, STORY_LONGDATE, - STORY_TABLE + "." + STORY_FEED_ID, STORY_TABLE + "." + STORY_ID, STORY_INTELLIGENCE_AUTHORS, STORY_INTELLIGENCE_FEED, STORY_INTELLIGENCE_TAGS, - STORY_INTELLIGENCE_TITLE, STORY_PERMALINK, STORY_READ, STORY_STARRED, STORY_STARRED_DATE, STORY_SHARE_COUNT, STORY_TAGS, STORY_TITLE, - STORY_SOCIAL_USER_ID, STORY_SOURCE_USER_ID, STORY_SHARED_USER_IDS, STORY_FRIEND_USER_IDS, STORY_PUBLIC_USER_IDS, STORY_SUM_TOTAL, STORY_HASH, - STORY_LAST_READ_DATE, STORY_SEARCHIT, + private static final String[] BASE_STORY_COLUMNS = { + STORY_AUTHORS, STORY_SHORT_CONTENT, STORY_TIMESTAMP, STORY_SHARED_DATE, STORY_LONGDATE, + STORY_TABLE + "." + STORY_FEED_ID, STORY_TABLE + "." + STORY_ID, + STORY_INTELLIGENCE_AUTHORS, STORY_INTELLIGENCE_FEED, STORY_INTELLIGENCE_TAGS, STORY_INTELLIGENCE_TOTAL, + STORY_INTELLIGENCE_TITLE, STORY_PERMALINK, STORY_READ, STORY_STARRED, STORY_STARRED_DATE, STORY_TAGS, STORY_TITLE, + STORY_SOCIAL_USER_ID, STORY_SOURCE_USER_ID, STORY_SHARED_USER_IDS, STORY_FRIEND_USER_IDS, STORY_HASH, + STORY_LAST_READ_DATE, }; - public static final String MULTIFEED_STORIES_QUERY_BASE = - "SELECT " + TextUtils.join(",", STORY_COLUMNS) + ", " + + private static final String STORY_COLUMNS = + TextUtils.join(",", BASE_STORY_COLUMNS) + ", " + FEED_TITLE + ", " + FEED_FAVICON_URL + ", " + FEED_FAVICON_COLOR + ", " + FEED_FAVICON_BORDER + ", " + FEED_FAVICON_FADE + ", " + FEED_FAVICON_TEXT; - public static final String JOIN_FEEDS_ON_STORIES = - " INNER JOIN " + FEED_TABLE + " ON " + STORY_TABLE + "." + STORY_FEED_ID + " = " + FEED_TABLE + "." + FEED_ID; + public static final String STORY_QUERY_BASE = + "SELECT " + + STORY_COLUMNS + + " FROM " + STORY_TABLE + + " INNER JOIN " + FEED_TABLE + + " ON " + STORY_TABLE + "." + STORY_FEED_ID + " = " + FEED_TABLE + "." + FEED_ID + + " WHERE " + STORY_HASH + " IN (" + + " SELECT DISTINCT " + READING_SESSION_STORY_HASH + + " FROM " + READING_SESSION_TABLE + ")" + + " GROUP BY " + STORY_HASH; public static final String JOIN_STORIES_ON_SOCIALFEED_MAP = " INNER JOIN " + STORY_TABLE + " ON " + STORY_TABLE + "." + STORY_ID + " = " + SOCIALFEED_STORY_MAP_TABLE + "." + SOCIALFEED_STORY_STORYID; - public static final String JOIN_SOCIAL_FEEDS_ON_SOCIALFEED_MAP = - " INNER JOIN " + SOCIALFEED_TABLE + " ON " + SOCIALFEED_TABLE + "." + SOCIAL_FEED_ID + " = " + SOCIALFEED_STORY_MAP_TABLE + "." + SOCIALFEED_STORY_USER_ID; - public static final String READ_STORY_ORDER = STORY_LAST_READ_DATE + " DESC"; /** * Appends to the given story query any and all selection statements that are required to satisfy the specified - * filtration parameters, dedup column, and ordering requirements. + * filtration parameters. */ - public static void appendStorySelectionGroupOrder(StringBuilder q, ReadFilter readFilter, StoryOrder order, StateFilter stateFilter, String dedupCol, boolean requireQueryHit) { + public static void appendStorySelection(StringBuilder q, List selArgs, ReadFilter readFilter, StateFilter stateFilter, String requireQueryHit) { if (readFilter == ReadFilter.UNREAD) { - // When a user is viewing "unread only" stories, what they really want are stories that were unread when they started reading, - // or else the selection set will constantly change as they see things! - q.append(" AND ((" + STORY_READ + " = 0) OR (" + STORY_READ_THIS_SESSION + " = 1))"); - } else if (readFilter == ReadFilter.PURE_UNREAD) { - // This means really just unreads, useful for getting counts q.append(" AND (" + STORY_READ + " = 0)"); } @@ -355,18 +340,9 @@ public class DatabaseConstants { q.append(" AND " + stateSelection); } - if (requireQueryHit) { - q.append(" AND (" + STORY_TABLE + "." + STORY_SEARCHIT + " = 1)"); - } - - q.append(" AND (" + STORY_TABLE + "." + STORY_ACTIVE + " = 1)"); - - if (dedupCol != null) { - q.append( " GROUP BY " + dedupCol); - } - - if (order != null) { - q.append(" ORDER BY " + getStorySortOrder(order)); + if (requireQueryHit != null) { + q.append(" AND (" + STORY_TABLE + "." + STORY_SEARCH_HIT + " = ?)"); + selArgs.add(requireQueryHit); } } @@ -378,13 +354,13 @@ public class DatabaseConstants { case ALL: return null; case SOME: - return STORY_INTELLIGENCE_SOME; + return STORY_INTELLIGENCE_TOTAL + " >= 0 "; case NEUT: - return STORY_INTELLIGENCE_NEUT; + return STORY_INTELLIGENCE_TOTAL + " = 0 "; case BEST: - return STORY_INTELLIGENCE_BEST; + return STORY_INTELLIGENCE_TOTAL + " > 0 "; case NEG: - return STORY_INTELLIGENCE_NEG; + return STORY_INTELLIGENCE_TOTAL + " < 0 "; default: return null; } diff --git a/clients/android/NewsBlur/src/com/newsblur/domain/Story.java b/clients/android/NewsBlur/src/com/newsblur/domain/Story.java index d728ebcbc..b1073333c 100644 --- a/clients/android/NewsBlur/src/com/newsblur/domain/Story.java +++ b/clients/android/NewsBlur/src/com/newsblur/domain/Story.java @@ -7,7 +7,9 @@ import android.database.Cursor; import android.text.TextUtils; import com.google.gson.annotations.SerializedName; + import com.newsblur.database.DatabaseConstants; +import com.newsblur.util.StateFilter; public class Story implements Serializable { @@ -18,21 +20,12 @@ public class Story implements Serializable { @SerializedName("story_permalink") public String permalink; - @SerializedName("share_count") - public String shareCount; - @SerializedName("share_user_ids") public String[] sharedUserIds; @SerializedName("shared_by_friends") public String[] friendUserIds = new String[]{}; - @SerializedName("shared_by_public") - public String[] publicUserIds = new String[]{}; - - @SerializedName("comment_count") - public int commentCount; - @SerializedName("read_status") public boolean read; @@ -83,11 +76,6 @@ public class Story implements Serializable { @SerializedName("intelligence") public Intelligence intelligence = new Intelligence(); - public int intelTotal; - - @SerializedName("short_parsed_date") - public String shortDate; - @SerializedName("long_parsed_date") public String longDate; @@ -101,30 +89,28 @@ public class Story implements Serializable { // not yet vended by the API, but tracked locally and fudged (see SyncService) for remote stories public long lastReadTimestamp = 0L; - public boolean isSearchHit = false; + // non-API and only set once when story is pushed to DB so it can be selected upon + public String searchHit = ""; public ContentValues getValues() { final ContentValues values = new ContentValues(); values.put(DatabaseConstants.STORY_ID, id); values.put(DatabaseConstants.STORY_TITLE, title.replace("\n", " ").replace("\r", " ")); values.put(DatabaseConstants.STORY_TIMESTAMP, timestamp); - values.put(DatabaseConstants.STORY_SHORTDATE, shortDate); values.put(DatabaseConstants.STORY_LONGDATE, longDate); values.put(DatabaseConstants.STORY_CONTENT, content); values.put(DatabaseConstants.STORY_SHORT_CONTENT, shortContent); values.put(DatabaseConstants.STORY_PERMALINK, permalink); - values.put(DatabaseConstants.STORY_COMMENT_COUNT, commentCount); - values.put(DatabaseConstants.STORY_SHARE_COUNT, shareCount); values.put(DatabaseConstants.STORY_AUTHORS, authors); values.put(DatabaseConstants.STORY_SOCIAL_USER_ID, socialUserId); values.put(DatabaseConstants.STORY_SOURCE_USER_ID, sourceUserId); values.put(DatabaseConstants.STORY_SHARED_USER_IDS, TextUtils.join(",", sharedUserIds)); values.put(DatabaseConstants.STORY_FRIEND_USER_IDS, TextUtils.join(",", friendUserIds)); - values.put(DatabaseConstants.STORY_PUBLIC_USER_IDS, TextUtils.join(",", publicUserIds)); values.put(DatabaseConstants.STORY_INTELLIGENCE_AUTHORS, intelligence.intelligenceAuthors); values.put(DatabaseConstants.STORY_INTELLIGENCE_FEED, intelligence.intelligenceFeed); values.put(DatabaseConstants.STORY_INTELLIGENCE_TAGS, intelligence.intelligenceTags); values.put(DatabaseConstants.STORY_INTELLIGENCE_TITLE, intelligence.intelligenceTitle); + values.put(DatabaseConstants.STORY_INTELLIGENCE_TOTAL, intelligence.calcTotalIntel()); values.put(DatabaseConstants.STORY_TAGS, TextUtils.join(",", tags)); values.put(DatabaseConstants.STORY_READ, read); values.put(DatabaseConstants.STORY_STARRED, starred); @@ -133,7 +119,7 @@ public class Story implements Serializable { values.put(DatabaseConstants.STORY_HASH, storyHash); values.put(DatabaseConstants.STORY_IMAGE_URLS, TextUtils.join(",", imageUrls)); values.put(DatabaseConstants.STORY_LAST_READ_DATE, lastReadTimestamp); - values.put(DatabaseConstants.STORY_SEARCHIT, isSearchHit); + values.put(DatabaseConstants.STORY_SEARCH_HIT, searchHit); return values; } @@ -146,21 +132,16 @@ public class Story implements Serializable { story.shortContent = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_SHORT_CONTENT)); story.title = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_TITLE)); story.timestamp = cursor.getLong(cursor.getColumnIndex(DatabaseConstants.STORY_TIMESTAMP)); - story.shortDate = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_SHORTDATE)); story.longDate = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_LONGDATE)); - story.shareCount = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_SHARE_COUNT)); - story.commentCount = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_COMMENT_COUNT)); story.socialUserId = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_SOCIAL_USER_ID)); story.sourceUserId = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_SOURCE_USER_ID)); story.permalink = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_PERMALINK)); story.sharedUserIds = TextUtils.split(cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_SHARED_USER_IDS)), ","); story.friendUserIds = TextUtils.split(cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_FRIEND_USER_IDS)), ","); - story.publicUserIds = TextUtils.split(cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_PUBLIC_USER_IDS)), ","); story.intelligence.intelligenceAuthors = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_AUTHORS)); story.intelligence.intelligenceFeed = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_FEED)); story.intelligence.intelligenceTags = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_TAGS)); story.intelligence.intelligenceTitle = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_TITLE)); - story.intelTotal = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.SUM_STORY_TOTAL)); story.read = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_READ)) > 0; story.starred = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_STARRED)) > 0; story.starredTimestamp = cursor.getLong(cursor.getColumnIndex(DatabaseConstants.STORY_STARRED_DATE)); @@ -169,7 +150,6 @@ public class Story implements Serializable { story.id = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_ID)); story.storyHash = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_HASH)); story.lastReadTimestamp = cursor.getLong(cursor.getColumnIndex(DatabaseConstants.STORY_LAST_READ_DATE)); - story.isSearchHit = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_SEARCHIT)) > 0; return story; } @@ -187,8 +167,42 @@ public class Story implements Serializable { @SerializedName("title") public int intelligenceTitle = 0; + + public int calcTotalIntel() { + int max = 0; + max = Math.max(max, intelligenceAuthors); + max = Math.max(max, intelligenceTags); + max = Math.max(max, intelligenceTitle); + if (max > 0) return max; + + int min = 0; + min = Math.min(min, intelligenceAuthors); + min = Math.min(min, intelligenceTags); + min = Math.min(min, intelligenceTitle); + if (min < 0) return min; + + return intelligenceFeed; + } } + public boolean isStoryVisibileInState(StateFilter state) { + int score = intelligence.calcTotalIntel(); + switch (state) { + case ALL: + return true; + case SOME: + return (score >= 0); + case NEUT: + return (score == 0); + case BEST: + return (score > 0); + case NEG: + return (score < 0); + default: + return false; + } + } + /** * Custom equality based on storyID/feedID equality so that a Set can de-duplicate story objects. */ diff --git a/clients/android/NewsBlur/src/com/newsblur/domain/ValueMultimap.java b/clients/android/NewsBlur/src/com/newsblur/domain/ValueMultimap.java index 210baeecd..4a18b12f3 100644 --- a/clients/android/NewsBlur/src/com/newsblur/domain/ValueMultimap.java +++ b/clients/android/NewsBlur/src/com/newsblur/domain/ValueMultimap.java @@ -1,6 +1,7 @@ package com.newsblur.domain; import java.io.Serializable; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -40,7 +41,7 @@ public class ValueMultimap implements Serializable { final StringBuilder builder = new StringBuilder(); builder.append(key); builder.append("="); - builder.append(value); + builder.append(URLEncoder.encode(value)); parameters.add(builder.toString()); } } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/AllSharedStoriesItemListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/AllSharedStoriesItemListFragment.java index ab33366fe..8e5874c6a 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/AllSharedStoriesItemListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/AllSharedStoriesItemListFragment.java @@ -14,7 +14,7 @@ public class AllSharedStoriesItemListFragment extends ItemListFragment { @Override public void onLoadFinished(Loader loader, Cursor cursor) { if ((adapter == null) && (cursor != null)) { - String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.SUM_STORY_TOTAL, DatabaseConstants.FEED_TITLE }; + String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.STORY_INTELLIGENCE_TOTAL, DatabaseConstants.FEED_TITLE }; int[] groupTo = new int[] { R.id.row_item_title, R.id.row_item_content, R.id.row_item_author, R.id.row_item_date, R.id.row_item_sidebar, R.id.row_item_feedtitle }; adapter = new MultipleFeedItemsAdapter(getActivity(), R.layout.row_folderitem, cursor, groupFrom, groupTo); adapter.setViewBinder(new SocialItemViewBinder(getActivity())); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/AllStoriesItemListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/AllStoriesItemListFragment.java index d5337f48f..6cbcde1f6 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/AllStoriesItemListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/AllStoriesItemListFragment.java @@ -14,7 +14,7 @@ public class AllStoriesItemListFragment extends ItemListFragment { @Override public void onLoadFinished(Loader loader, Cursor cursor) { if ((adapter == null) && (cursor != null)) { - String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.SUM_STORY_TOTAL, DatabaseConstants.FEED_TITLE }; + String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.STORY_INTELLIGENCE_TOTAL, DatabaseConstants.FEED_TITLE }; int[] groupTo = new int[] { R.id.row_item_title, R.id.row_item_content, R.id.row_item_author, R.id.row_item_date, R.id.row_item_sidebar, R.id.row_item_feedtitle }; adapter = new MultipleFeedItemsAdapter(getActivity(), R.layout.row_folderitem, cursor, groupFrom, groupTo); adapter.setViewBinder(new SocialItemViewBinder(getActivity())); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/FeedItemListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/FeedItemListFragment.java index 8a4f24b3c..6d0962936 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/FeedItemListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/FeedItemListFragment.java @@ -31,7 +31,7 @@ public class FeedItemListFragment extends ItemListFragment { @Override public void onLoadFinished(Loader loader, Cursor cursor) { if ((adapter == null) && (cursor != null)) { - String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.SUM_STORY_TOTAL }; + String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.STORY_INTELLIGENCE_TOTAL }; int[] groupTo = new int[] { R.id.row_item_title, R.id.row_item_content, R.id.row_item_author, R.id.row_item_date, R.id.row_item_sidebar }; adapter = new FeedItemsAdapter(getActivity(), feed, R.layout.row_item, cursor, groupFrom, groupTo); adapter.setViewBinder(new FeedItemViewBinder(getActivity())); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/FolderItemListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/FolderItemListFragment.java index d58ac3a29..34a3eac56 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/FolderItemListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/FolderItemListFragment.java @@ -21,7 +21,7 @@ public class FolderItemListFragment extends ItemListFragment { @Override public void onLoadFinished(Loader loader, Cursor cursor) { if ((adapter == null) && (cursor != null)) { - String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.FEED_TITLE, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.SUM_STORY_TOTAL, DatabaseConstants.STORY_AUTHORS }; + String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.FEED_TITLE, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.STORY_INTELLIGENCE_TOTAL, DatabaseConstants.STORY_AUTHORS }; int[] groupTo = new int[] { R.id.row_item_title, R.id.row_item_content, R.id.row_item_feedtitle, R.id.row_item_date, R.id.row_item_sidebar, R.id.row_item_author }; adapter = new MultipleFeedItemsAdapter(getActivity(), R.layout.row_folderitem, cursor, groupFrom, groupTo); adapter.setViewBinder(new FeedItemViewBinder(getActivity())); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java index 17c990398..85cfa78a9 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java @@ -39,6 +39,7 @@ import com.newsblur.domain.SocialFeed; import com.newsblur.util.AppConstants; import com.newsblur.util.FeedSet; import com.newsblur.util.FeedUtils; +import com.newsblur.util.MarkAllReadConfirmation; import com.newsblur.util.PrefConstants; import com.newsblur.util.PrefsUtils; import com.newsblur.util.StateFilter; @@ -211,6 +212,8 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen case ExpandableListView.PACKED_POSITION_TYPE_GROUP: if (adapter.isRowSavedStories(groupPosition)) break; if (adapter.isRowReadStories(groupPosition)) break; + if (groupPosition == FolderListAdapter.GLOBAL_SHARED_STORIES_GROUP_POSITION) break; + if (groupPosition == FolderListAdapter.ALL_SHARED_STORIES_GROUP_POSITION) break; inflater.inflate(R.menu.context_folder, menu); break; @@ -244,20 +247,25 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen return true; } else if (item.getItemId() == R.id.menu_mark_feed_as_read) { String feedId = adapter.getChild(groupPosition, childPosition); + FeedSet fs = null; if (groupPosition == FolderListAdapter.ALL_SHARED_STORIES_GROUP_POSITION) { SocialFeed socialFeed = adapter.getSocialFeed(feedId); - FeedUtils.markFeedsRead(FeedSet.singleSocialFeed(socialFeed.userId, socialFeed.username), null, null, getActivity()); + fs = FeedSet.singleSocialFeed(socialFeed.userId, socialFeed.username); } else { - FeedUtils.markFeedsRead(FeedSet.singleFeed(feedId), null, null, getActivity()); + fs = FeedSet.singleFeed(feedId); } + + markFeedsAsRead(fs); return true; } else if (item.getItemId() == R.id.menu_mark_folder_as_read) { - if (!adapter.isFolderRoot(groupPosition)) { + FeedSet fs = null; + if (!adapter.isFolderRoot(groupPosition)) { String folderName = adapter.getGroup(groupPosition); - FeedUtils.markFeedsRead(FeedUtils.feedSetFromFolderName(folderName), null, null, getActivity()); + fs = FeedUtils.feedSetFromFolderName(folderName); } else { - FeedUtils.markFeedsRead(FeedSet.allFeeds(), null, null, getActivity()); + fs = FeedSet.allFeeds(); } + markFeedsAsRead(fs); return true; } else if (item.getItemId() == R.id.menu_choose_folders) { DialogFragment chooseFoldersFragment = ChooseFoldersFragment.newInstance(adapter.getFeed(adapter.getChild(groupPosition, childPosition))); @@ -267,6 +275,16 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen return super.onContextItemSelected(item); } + private void markFeedsAsRead(FeedSet fs) { + MarkAllReadConfirmation confirmation = PrefsUtils.getMarkAllReadConfirmation(getActivity()); + if (confirmation.feedSetRequiresConfirmation(fs)) { + MarkAllReadDialogFragment dialog = MarkAllReadDialogFragment.newInstance(fs); + dialog.show(getFragmentManager(), "dialog"); + } else { + FeedUtils.markFeedsRead(fs, null, null, getActivity()); + } + } + public void changeState(StateFilter state) { currentState = state; PrefsUtils.setStateFilter(getActivity(), state); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/GlobalSharedStoriesItemListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/GlobalSharedStoriesItemListFragment.java index c922964e8..e91c30665 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/GlobalSharedStoriesItemListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/GlobalSharedStoriesItemListFragment.java @@ -24,7 +24,7 @@ public class GlobalSharedStoriesItemListFragment extends ItemListFragment { @Override public void onLoadFinished(Loader loader, Cursor cursor) { if ((adapter == null) && (cursor != null)) { - String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.SUM_STORY_TOTAL, DatabaseConstants.FEED_TITLE }; + String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.STORY_INTELLIGENCE_TOTAL, DatabaseConstants.FEED_TITLE }; int[] groupTo = new int[] { R.id.row_item_title, R.id.row_item_content, R.id.row_item_author, R.id.row_item_date, R.id.row_item_sidebar, R.id.row_item_feedtitle }; adapter = new MultipleFeedItemsAdapter(getActivity(), R.layout.row_folderitem, cursor, groupFrom, groupTo, true); adapter.setViewBinder(new SocialItemViewBinder(getActivity(), true)); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/ItemListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/ItemListFragment.java index e990554ab..236ba22c3 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/ItemListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/ItemListFragment.java @@ -55,7 +55,6 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis protected DefaultFeedView defaultFeedView; protected StateFilter intelState; private boolean cursorSeenYet = false; - private boolean firstStorySeenYet = false; private boolean stopLoading = false; // loading indicator for when stories are present but stale (at top of list) @@ -124,10 +123,15 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis super.onActivityCreated(savedInstanceState); stopLoading = false; if (getLoaderManager().getLoader(ITEMLIST_LOADER) == null) { + initReadingSession(); getLoaderManager().initLoader(ITEMLIST_LOADER, null, this); } } + private void initReadingSession() { + FeedUtils.prepareReadingSession(getFeedSet(), intelState); + } + private void triggerRefresh(int desiredStoryCount, int totalSeen) { boolean gotSome = NBSyncService.requestMoreForFeed(getFeedSet(), desiredStoryCount, totalSeen); if (gotSome) triggerSync(); @@ -145,7 +149,8 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis */ public void resetEmptyState() { cursorSeenYet = false; - firstStorySeenYet = false; + FeedUtils.dbHelper.clearStorySession(); + initReadingSession(); } /** @@ -230,7 +235,7 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis try { getActivity().finish(); } catch (Exception e) {;} return null; } - return FeedUtils.dbHelper.getStoriesLoader(getFeedSet(), intelState); + return FeedUtils.dbHelper.getActiveStoriesLoader(getFeedSet()); } @Override @@ -240,20 +245,6 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis cursorSeenYet = true; if (cursor.getCount() < 1) { triggerRefresh(1, 0); - } else { - if (!firstStorySeenYet) { - // once we have at least a single story, we can instruct the sync service as to how to safely - // activate new stories we recieve - firstStorySeenYet = true; - cursor.moveToFirst(); - long cutoff = cursor.getLong(cursor.getColumnIndex(DatabaseConstants.STORY_TIMESTAMP)); - cursor.moveToPosition(-1); - if (activity.getStoryOrder() == StoryOrder.NEWEST) { - NBSyncService.setActivationMode(NBSyncService.ActivationMode.OLDER, cutoff); - } else { - NBSyncService.setActivationMode(NBSyncService.ActivationMode.NEWER, cutoff); - } - } } adapter.swapCursor(cursor); } @@ -344,7 +335,7 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis int truePosition = position - 1; Story story = adapter.getStory(truePosition); if (getActivity().isFinishing()) return; - UIUtils.startReadingActivity(getFeedSet(), story.storyHash, getActivity(), false); + UIUtils.startReadingActivity(getFeedSet(), story.storyHash, getActivity()); } protected void setupBezelSwipeDetector(View v) { diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/MarkAllReadDialogFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/MarkAllReadDialogFragment.java index a5291c729..02072c2f2 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/MarkAllReadDialogFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/MarkAllReadDialogFragment.java @@ -1,6 +1,8 @@ package com.newsblur.fragment; import com.newsblur.R; +import com.newsblur.util.FeedSet; +import com.newsblur.util.FeedUtils; import android.app.Activity; import android.app.AlertDialog; @@ -10,23 +12,22 @@ import android.os.Bundle; import android.app.DialogFragment; public class MarkAllReadDialogFragment extends DialogFragment { - private static final String FOLDER_NAME = "folder_name"; + private static final String FEED_SET = "feed_set"; public interface MarkAllReadDialogListener { - public void onMarkAllRead(); - public void onCancel(); + void onMarkAllRead(FeedSet feedSet); } private MarkAllReadDialogListener listener; - public static MarkAllReadDialogFragment newInstance(String folderName) { + public static MarkAllReadDialogFragment newInstance(FeedSet feedSet) { MarkAllReadDialogFragment fragment = new MarkAllReadDialogFragment(); Bundle args = new Bundle(); - args.putString(FOLDER_NAME, folderName); + args.putSerializable(FEED_SET, feedSet); fragment.setArguments(args); return fragment; } - + @Override public void onAttach(Activity activity) { super.onAttach(activity); @@ -36,15 +37,25 @@ public class MarkAllReadDialogFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle(getArguments().getString(FOLDER_NAME)) + + final FeedSet feedSet = (FeedSet)getArguments().getSerializable(FEED_SET); + String title = null; + if (feedSet.isAllNormal()) { + title = getResources().getString(R.string.all_stories); + } else if (feedSet.isFolder()) { + title = feedSet.getFolderName(); + } else if (feedSet.isSingleSocial()) { + title = FeedUtils.getSocialFeed(feedSet.getSingleSocialFeed().getKey()).feedTitle; + } else { + title = FeedUtils.getFeed(feedSet.getSingleFeed()).title; + } + + builder.setTitle(title) .setItems(R.array.mark_all_read_options, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (which == 0) { - listener.onMarkAllRead(); - } else { - listener.onCancel(); + listener.onMarkAllRead(feedSet); } - } }); return builder.create(); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/ProfileActivityDetailsFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/ProfileActivityDetailsFragment.java index 9c09d642a..09b6087a3 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/ProfileActivityDetailsFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/ProfileActivityDetailsFragment.java @@ -144,7 +144,7 @@ public abstract class ProfileActivityDetailsFragment extends Fragment implements context.startActivity(intent); } } else if (activity.category == Category.STAR) { - UIUtils.startReadingActivity(FeedSet.allSaved(), activity.storyHash, context, false); + UIUtils.startReadingActivity(FeedSet.allSaved(), activity.storyHash, context); } else if (isSocialFeedCategory(activity)) { // Strip the social: prefix from feedId String socialFeedId = activity.feedId.substring(7); @@ -152,7 +152,7 @@ public abstract class ProfileActivityDetailsFragment extends Fragment implements if (feed == null) { Toast.makeText(context, R.string.profile_do_not_follow, Toast.LENGTH_SHORT).show(); } else { - UIUtils.startReadingActivity(FeedSet.singleSocialFeed(feed.userId, feed.username), activity.storyHash, context, true); + UIUtils.startReadingActivity(FeedSet.singleSocialFeed(feed.userId, feed.username), activity.storyHash, context); } } } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadStoriesItemListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadStoriesItemListFragment.java index 0d9330dd3..7da91e46c 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadStoriesItemListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadStoriesItemListFragment.java @@ -29,7 +29,7 @@ public class ReadStoriesItemListFragment extends ItemListFragment { @Override public void onLoadFinished(Loader loader, Cursor cursor) { if ((adapter == null) && (cursor != null)) { - String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.SUM_STORY_TOTAL, DatabaseConstants.FEED_TITLE }; + String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.STORY_INTELLIGENCE_TOTAL, DatabaseConstants.FEED_TITLE }; int[] groupTo = new int[] { R.id.row_item_title, R.id.row_item_content, R.id.row_item_author, R.id.row_item_date, R.id.row_item_sidebar, R.id.row_item_feedtitle }; adapter = new MultipleFeedItemsAdapter(getActivity(), R.layout.row_folderitem, cursor, groupFrom, groupTo); adapter.setViewBinder(new SocialItemViewBinder(getActivity(), true)); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/SavedStoriesItemListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/SavedStoriesItemListFragment.java index 73306c9bf..5a86db594 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/SavedStoriesItemListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/SavedStoriesItemListFragment.java @@ -30,15 +30,13 @@ public class SavedStoriesItemListFragment extends ItemListFragment { @Override public void onLoadFinished(Loader loader, Cursor cursor) { if ((adapter == null) && (cursor != null)) { - String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.SUM_STORY_TOTAL, DatabaseConstants.FEED_TITLE }; + String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.STORY_INTELLIGENCE_TOTAL, DatabaseConstants.FEED_TITLE }; int[] groupTo = new int[] { R.id.row_item_title, R.id.row_item_content, R.id.row_item_author, R.id.row_item_date, R.id.row_item_sidebar, R.id.row_item_feedtitle }; adapter = new MultipleFeedItemsAdapter(getActivity(), R.layout.row_folderitem, cursor, groupFrom, groupTo, true); adapter.setViewBinder(new SocialItemViewBinder(getActivity(), true)); itemList.setAdapter(adapter); } super.onLoadFinished(loader, cursor); - // every time we see a set of saved stories, tag them so they don't disappear during this reading session - FeedUtils.dbHelper.markSavedReadingSession(); } @Override diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/SocialFeedItemListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/SocialFeedItemListFragment.java index f79d59975..6edc4a434 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/SocialFeedItemListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/SocialFeedItemListFragment.java @@ -21,7 +21,7 @@ public class SocialFeedItemListFragment extends ItemListFragment { @Override public void onLoadFinished(Loader loader, Cursor cursor) { if ((adapter == null) && (cursor != null)) { - String[] groupFroms = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.FEED_FAVICON_URL, DatabaseConstants.FEED_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.SUM_STORY_TOTAL}; + String[] groupFroms = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.FEED_FAVICON_URL, DatabaseConstants.FEED_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_INTELLIGENCE_TOTAL}; int[] groupTos = new int[] { R.id.row_item_title, R.id.row_item_feedicon, R.id.row_item_feedtitle, R.id.row_item_content, R.id.row_item_date, R.id.row_item_author, R.id.row_item_sidebar}; adapter = new MultipleFeedItemsAdapter(getActivity(), R.layout.row_folderitem, cursor, groupFroms, groupTos); adapter.setViewBinder(new SocialItemViewBinder(getActivity())); diff --git a/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java b/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java index 6c6c2ae66..02a828634 100644 --- a/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java +++ b/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java @@ -65,12 +65,6 @@ import java.util.concurrent.TimeUnit; */ public class NBSyncService extends Service { - /** - * Mode switch for which newly received stories are suitable for display so - * that they don't disrupt actively visible pager and list offsets. - */ - public enum ActivationMode { ALL, OLDER, NEWER }; - private static final Object WAKELOCK_MUTEX = new Object(); private static final Object PENDING_FEED_MUTEX = new Object(); @@ -83,8 +77,6 @@ public class NBSyncService extends Service { private volatile static boolean DoFeedsFolders = false; private volatile static boolean DoUnreads = false; private volatile static boolean HaltNow = false; - private volatile static ActivationMode ActMode = ActivationMode.ALL; - private volatile static long ModeCutoff = 0L; /** Informational flag only, as to whether we were offline last time we cycled. */ public volatile static boolean OfflineNow = false; @@ -398,7 +390,6 @@ public class NBSyncService extends Service { if (stopSync()) return; if (backoffBackgroundCalls()) return; - if (ActMode != ActivationMode.ALL) return; if (dbHelper.getActions(false).getCount() > 0) return; FFSyncRunning = true; @@ -425,7 +416,6 @@ public class NBSyncService extends Service { } if (stopSync()) return; - if (ActMode != ActivationMode.ALL) return; if (dbHelper.getActions(false).getCount() > 0) return; // a metadata sync invalidates pagination and feed status @@ -615,9 +605,9 @@ public class NBSyncService extends Service { if (FlushRecounts) return; // don't let the page loop block actions if (dbHelper.getActions(false).getCount() > 0) return; + + // bail if the active view has changed if (!fs.equals(PendingFeed)) { - // the active view has changed - if (fs == null) finished = true; return; } @@ -629,16 +619,14 @@ public class NBSyncService extends Service { if (! isStoryResponseGood(apiResponse)) return; + if (!fs.equals(PendingFeed)) { + return; + } + FeedPagesSeen.put(fs, pageNumber); totalStoriesSeen += apiResponse.stories.length; FeedStoriesSeen.put(fs, totalStoriesSeen); - // lock in the activation cutoff based upon the timestamp of the first - // story received for a given pagination session. it will be the newest - // or oldest story for the feedset, as dictated by order. - if ((pageNumber == 1) && (apiResponse.stories.length > 0)) { - ModeCutoff = apiResponse.stories[0].timestamp; - } insertStories(apiResponse, fs); NbActivity.updateAllActivities(NbActivity.UPDATE_STORY); @@ -707,15 +695,15 @@ public class NBSyncService extends Service { // If this set of stories was found in response to the active search query, note // them as such in the DB so the UI can filter for them for (Story story : apiResponse.stories) { - story.isSearchHit = true; + story.searchHit = fs.getSearchQuery(); } } - dbHelper.insertStories(apiResponse, ActMode, ModeCutoff); + dbHelper.insertStories(apiResponse, true); } void insertStories(StoriesResponse apiResponse) { - dbHelper.insertStories(apiResponse, ActMode, ModeCutoff); + dbHelper.insertStories(apiResponse, false); } void incrementRunningChild() { @@ -838,18 +826,6 @@ public class NBSyncService extends Service { FlushRecounts = true; } - /** - * Tell the service which stories can be activated if received. See ActivationMode. - */ - public static void setActivationMode(ActivationMode actMode) { - ActMode = actMode; - } - - public static void setActivationMode(ActivationMode actMode, long modeCutoff) { - ActMode = actMode; - ModeCutoff = modeCutoff; - } - /** * Requests that the service fetch additional stories for the specified feed/folder. Returns * true if more will be fetched as a result of this request. diff --git a/clients/android/NewsBlur/src/com/newsblur/service/UnreadsService.java b/clients/android/NewsBlur/src/com/newsblur/service/UnreadsService.java index 80af7f4eb..d53c9ad7d 100644 --- a/clients/android/NewsBlur/src/com/newsblur/service/UnreadsService.java +++ b/clients/android/NewsBlur/src/com/newsblur/service/UnreadsService.java @@ -52,27 +52,28 @@ public class UnreadsService extends SubService { NavigableMap sortingMap = new TreeMap(); UnreadStoryHashesResponse unreadHashes = parent.apiManager.getUnreadStoryHashes(); + if (parent.stopSync()) return; // note all the stories we thought were unread before. if any fail to appear in // the API request for unreads, we will mark them as read List oldUnreadHashes = parent.dbHelper.getUnreadStoryHashes(); // process the api response, both bookkeeping no-longer-unread stories and populating // the sortation map we will use to create the fetch list for step two - for (Entry> entry : unreadHashes.unreadHashes.entrySet()) { + feedloop: for (Entry> entry : unreadHashes.unreadHashes.entrySet()) { String feedId = entry.getKey(); // ignore unreads from orphaned feeds - if( ! parent.orphanFeedIds.contains(feedId)) { + if(parent.orphanFeedIds.contains(feedId)) continue feedloop; + for (String[] newHash : entry.getValue()) { // only fetch the reported unreads if we don't already have them - List existingHashes = parent.dbHelper.getStoryHashesForFeed(feedId); - for (String[] newHash : entry.getValue()) { - if (!existingHashes.contains(newHash[0])) { - sortingMap.put(newHash[1]+newHash[0], newHash[0]); - } + if (!oldUnreadHashes.contains(newHash[0])) { + sortingMap.put(newHash[1]+newHash[0], newHash[0]); + } else { oldUnreadHashes.remove(newHash[0]); } } } + if (parent.stopSync()) return; // now that we have the sorted set of hashes, turn them into a list over which we // can iterate to fetch them if (PrefsUtils.getDefaultStoryOrder(parent) == StoryOrder.NEWEST) { @@ -90,6 +91,7 @@ public class UnreadsService extends SubService { } private void getNewUnreadStories() { + int totalCount = StoryHashQueue.size(); unreadsyncloop: while (StoryHashQueue.size() > 0) { if (parent.stopSync()) return; if(!PrefsUtils.isOfflineEnabled(parent)) return; diff --git a/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java b/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java index 6fe7120de..75f90ba4e 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java @@ -56,7 +56,7 @@ public class AppConstants { public static final int MAX_READ_STORIES_STORED = 500; // how many unread stories to fetch via hash at a time - public static final int UNREAD_FETCH_BATCH_SIZE = 100; + public static final int UNREAD_FETCH_BATCH_SIZE = 50; // how many images to prefetch before updating the countdown UI public static final int IMAGE_PREFETCH_BATCH_SIZE = 6; diff --git a/clients/android/NewsBlur/src/com/newsblur/util/FeedSet.java b/clients/android/NewsBlur/src/com/newsblur/util/FeedSet.java index 276d3f76a..e99489f0a 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/FeedSet.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/FeedSet.java @@ -172,6 +172,10 @@ public class FeedSet implements Serializable { return (((savedFeeds != null) && (savedFeeds.size() < 1)) || ((savedTags != null) && (savedTags.size() < 1))); } + public boolean isSingleSocial() { + return ((socialFeeds != null) && (socialFeeds.size() == 1)); + } + public boolean isGlobalShared() { return this.isGlobalShared; } diff --git a/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java b/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java index ae11879ba..90ea6b23a 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java @@ -94,33 +94,30 @@ public class FeedUtils { }.execute(); } + public static void prepareReadingSession(final FeedSet fs, final StateFilter state) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... arg) { + dbHelper.prepareReadingSession(fs, state); + return null; + } + }.execute(); + } + public static void clearReadingSession() { new AsyncTask() { @Override protected Void doInBackground(Void... arg) { + // TODO: this reset might no longer be necessary after every FeedSet switch and could save a lot of API calls NBSyncService.resetFeeds(); try { - dbHelper.clearReadingSession(); + dbHelper.clearStorySession(); } catch (Exception e) { ; // this one call can evade the on-upgrade DB wipe and throw exceptions } return null; } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public static void activateAllStories() { - new AsyncTask() { - @Override - protected Void doInBackground(Void... arg) { - try { - dbHelper.markStoriesActive(NBSyncService.ActivationMode.ALL, 0L); - } catch (Exception e) { - ; // this call can evade the on-upgrade DB wipe and throw exceptions - } - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + }.execute(); } public static void markStoryUnread(final Story story, final Context context) { diff --git a/clients/android/NewsBlur/src/com/newsblur/util/MarkAllReadConfirmation.java b/clients/android/NewsBlur/src/com/newsblur/util/MarkAllReadConfirmation.java new file mode 100644 index 000000000..85702b344 --- /dev/null +++ b/clients/android/NewsBlur/src/com/newsblur/util/MarkAllReadConfirmation.java @@ -0,0 +1,26 @@ +package com.newsblur.util; + +/** + * Enum to represent mark all read confirmation preference. + * @author mark + */ +public enum MarkAllReadConfirmation { + + FEED_AND_FOLDER("feed_and_folder"), + FOLDER_ONLY("folder_only"), + NONE("none"); + + private String parameterValue; + + MarkAllReadConfirmation(String parameterValue) { + this.parameterValue = parameterValue; + } + + public boolean feedSetRequiresConfirmation(FeedSet fs) { + if (fs.isFolder() || fs.isAllNormal()) { + return this != NONE; + } else { + return this == FEED_AND_FOLDER; + } + } +} diff --git a/clients/android/NewsBlur/src/com/newsblur/util/PrefConstants.java b/clients/android/NewsBlur/src/com/newsblur/util/PrefConstants.java index f8b6323d4..0b134dc35 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/PrefConstants.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/PrefConstants.java @@ -67,4 +67,5 @@ public class PrefConstants { public static final String LAST_CLEANUP_TIME = "last_cleanup_time"; public static final String VOLUME_KEY_NAVIGATION = "volume_key_navigation"; + public static final String MARK_ALL_READ_CONFIRMATION = "pref_confirm_mark_all_read"; } diff --git a/clients/android/NewsBlur/src/com/newsblur/util/PrefsUtils.java b/clients/android/NewsBlur/src/com/newsblur/util/PrefsUtils.java index d9aa041a7..31ba92736 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/PrefsUtils.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/PrefsUtils.java @@ -567,4 +567,9 @@ public class PrefsUtils { SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0); return VolumeKeyNavigation.valueOf(prefs.getString(PrefConstants.VOLUME_KEY_NAVIGATION, VolumeKeyNavigation.OFF.toString())); } + + public static MarkAllReadConfirmation getMarkAllReadConfirmation(Context context) { + SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0); + return MarkAllReadConfirmation.valueOf(prefs.getString(PrefConstants.MARK_ALL_READ_CONFIRMATION, MarkAllReadConfirmation.FOLDER_ONLY.toString())); + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/util/ReadFilter.java b/clients/android/NewsBlur/src/com/newsblur/util/ReadFilter.java index 54e27ca04..39b060528 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/ReadFilter.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/ReadFilter.java @@ -5,8 +5,7 @@ package com.newsblur.util; */ public enum ReadFilter { ALL("all"), - UNREAD("unread"), // in the app, this often means "unread and read since switching activities" - PURE_UNREAD("unread"); + UNREAD("unread"); private String parameterValue; diff --git a/clients/android/NewsBlur/src/com/newsblur/util/UIUtils.java b/clients/android/NewsBlur/src/com/newsblur/util/UIUtils.java index bd6fb7262..320503cb4 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/UIUtils.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/UIUtils.java @@ -164,7 +164,7 @@ public class UIUtils { }); } - public static void startReadingActivity(FeedSet fs, String startingHash, Context context, boolean ignoreFilters) { + public static void startReadingActivity(FeedSet fs, String startingHash, Context context) { Class activityClass; if (fs.isAllSaved()) { activityClass = SavedStoriesReading.class; @@ -189,9 +189,15 @@ public class UIUtils { Intent i = new Intent(context, activityClass); i.putExtra(Reading.EXTRA_FEEDSET, fs); i.putExtra(Reading.EXTRA_STORY_HASH, startingHash); - if (ignoreFilters) { - i.putExtra(SocialFeedReading.EXTRA_IGNORE_FILTERS, true); - } context.startActivity(i); } + + public static String getMemoryUsageDebug(Context context) { + String memInfo = " ("; + android.app.ActivityManager activityManager = (android.app.ActivityManager) context.getSystemService(android.app.Activity.ACTIVITY_SERVICE); + int[] pids = new int[]{android.os.Process.myPid()}; + android.os.Debug.MemoryInfo[] mi = activityManager.getProcessMemoryInfo(pids); + memInfo = memInfo + (mi[0].getTotalPss() / 1024) + "MB used)"; + return memInfo; + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/view/FeedItemViewBinder.java b/clients/android/NewsBlur/src/com/newsblur/view/FeedItemViewBinder.java index 169ae6e55..a8eed3847 100644 --- a/clients/android/NewsBlur/src/com/newsblur/view/FeedItemViewBinder.java +++ b/clients/android/NewsBlur/src/com/newsblur/view/FeedItemViewBinder.java @@ -48,7 +48,7 @@ public class FeedItemViewBinder implements ViewBinder { ((TextView) view).setText(cursor.getString(columnIndex).toUpperCase()); } return true; - } else if (TextUtils.equals(columnName, DatabaseConstants.SUM_STORY_TOTAL)) { + } else if (TextUtils.equals(columnName, DatabaseConstants.STORY_INTELLIGENCE_TOTAL)) { int score = cursor.getInt(columnIndex); Drawable icon; if (score > 0) { diff --git a/clients/android/NewsBlur/src/com/newsblur/view/SocialItemViewBinder.java b/clients/android/NewsBlur/src/com/newsblur/view/SocialItemViewBinder.java index 7cc087b00..c0507054b 100644 --- a/clients/android/NewsBlur/src/com/newsblur/view/SocialItemViewBinder.java +++ b/clients/android/NewsBlur/src/com/newsblur/view/SocialItemViewBinder.java @@ -39,7 +39,7 @@ public class SocialItemViewBinder implements ViewBinder { String faviconUrl = cursor.getString(columnIndex); FeedUtils.imageLoader.displayImage(faviconUrl, ((ImageView) view), true); return true; - } else if (TextUtils.equals(columnName, DatabaseConstants.SUM_STORY_TOTAL)) { + } else if (TextUtils.equals(columnName, DatabaseConstants.STORY_INTELLIGENCE_TOTAL)) { if (! this.ignoreIntel) { int score = cursor.getInt(columnIndex); Drawable icon;