Merge branch 'master' into 5.1

* master: (30 commits)
  Tune fetch batch sizing, remove debug.
  Fix missing search icon for saved stories.
  Fix DB optimization crashes, remove O(n) search tagging, instrument live memory.
  Fix intel state filtering and reading session resets on filter changes.
  Adding include_inactive=true to flat feeds to only give inactive feeds. I hope this doesn't break someday.
  Separating flat_folders and flat_folders_with_inactive.
  Adding inactive feeds to flat folders. This shouldn't break anything, since clients should be able to handle missing feeds anyhow.
  Adding  to /reader/feeds?flat=true (for @dejal).
  Disable mark folder read context menu for all shared stories (#866)
  Android v4.7.1.
  Disable folder context menu for global shared stories row (#866)
  Fix search queries with special characters. (#868)
  I cannot believe it was a string-to-int type conversion that wasn't happening when deleting feeds. *smacks forehead* Stupid json.decode. Sorry to 85136323e2.
  Handling search queries with just spaces in them.
  Handling update_fields=[] when saving feeds.
  Fix new reading session tracker (partial!).
  Replace story activation with reading session table. (partial!)
  Fix search queries with special characters. (#868)
  Replace story activation with filtering by fetch time.
  Optimise story insert times.
  ...
This commit is contained in:
Samuel Clay 2016-01-30 16:59:11 -08:00
commit e9158d1550
47 changed files with 580 additions and 548 deletions

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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 = []

View file

@ -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,

View file

@ -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

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.newsblur"
android:versionCode="116"
android:versionName="4.7.0" >
android:versionCode="117"
android:versionName="4.7.1" >
<uses-sdk
android:minSdkVersion="14"

View file

@ -44,6 +44,13 @@
android:entries="@array/default_read_filter_entries"
android:entryValues="@array/default_read_filter_values"
android:defaultValue="@string/default_read_filter_value" />
<ListPreference
android:key="pref_confirm_mark_all_read"
android:title="@string/settings_confirm_mark_all_read"
android:dialogTitle="@string/settings_confirm_mark_all_read"
android:entries="@array/confirm_mark_all_read_entries"
android:entryValues="@array/confirm_mark_all_read_values"
android:defaultValue="@string/confirm_mark_all_read_value" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pref_auto_open_first_unread"

View file

@ -8,5 +8,5 @@
android:showAsAction="never" />
<item android:id="@+id/menu_search_stories"
android:title="@string/menu_search_stories"
android:showAsAction="never" />
android:showAsAction="ifRoom" android:icon="@drawable/search" />
</menu>

View file

@ -224,7 +224,7 @@
<string name="default_read_filter_value">ALL</string>
<string-array name="mark_all_read_options">
<item>Mark entire folder read</item>
<item>Mark all read</item>
<item>Cancel</item>
</string-array>
@ -279,4 +279,20 @@
<item>DOWN_NEXT</item>
</string-array>
<string name="default_volume_key_navigation_value">OFF</string>
<string name="settings_confirm_mark_all_read">Confirm Mark All Read</string>
<string name="none">None</string>
<string name="feed_and_folder">Feeds and Folders</string>
<string name="folder_only">Folders Only</string>
<string-array name="confirm_mark_all_read_entries">
<item>@string/feed_and_folder</item>
<item>@string/folder_only</item>
<item>@string/none</item>
</string-array>
<string-array name="confirm_mark_all_read_values">
<item>FEED_AND_FOLDER</item>
<item>FOLDER_ONLY</item>
<item>NONE</item>
</string-array>
<string name="confirm_mark_all_read_value">FOLDER_ONLY</string>
</resources>

View file

@ -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
}
}

View file

@ -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 {

View file

@ -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
}
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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.

View file

@ -21,10 +21,4 @@ public class SavedStoriesReading extends Reading {
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onLoadFinished(Loader<Cursor> 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);
}
}

View file

@ -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<Cursor> 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);
}
}
}

View file

@ -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);

View file

@ -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<ContentValues> userValues = new ArrayList<ContentValues>(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<ContentValues> feedValues = new ArrayList<ContentValues>(apiResponse.feeds.size());
for (Feed feed : apiResponse.feeds) {
feedValues.add(feed.getValues());
}
bulkInsertValues(DatabaseConstants.FEED_TABLE, feedValues);
}
// handle story content
List<ContentValues> storyValues = new ArrayList<ContentValues>(apiResponse.stories.length);
List<ContentValues> socialStoryValues = new ArrayList<ContentValues>();
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<ContentValues> userValues = new ArrayList<ContentValues>(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<ContentValues> feedValues = new ArrayList<ContentValues>(apiResponse.feeds.size());
for (Feed feed : apiResponse.feeds) {
feedValues.add(feed.getValues());
}
bulkInsertValuesExtSync(DatabaseConstants.FEED_TABLE, feedValues);
}
// handle story content
List<ContentValues> socialStoryValues = new ArrayList<ContentValues>();
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<String,Classifier> 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<String,Classifier> 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<ContentValues> 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<ContentValues> 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<ContentValues> commentValues = new ArrayList<ContentValues>();
List<ContentValues> replyValues = new ArrayList<ContentValues>();
// 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<String> freshCommentIds = new ArrayList<String>();
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<ContentValues> commentValues = new ArrayList<ContentValues>();
List<ContentValues> replyValues = new ArrayList<ContentValues>();
// 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<String> freshCommentIds = new ArrayList<String>();
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<String> 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<String> selArgs = new ArrayList<String>();
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<Cursor> 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<Cursor> getStoriesLoader(final FeedSet fs, final StateFilter stateFilter) {
public Loader<Cursor> 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<Cursor> 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<String> selArgs = new ArrayList<String>();
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<String> 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.");
}

View file

@ -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<String> 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;
}

View file

@ -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.
*/

View file

@ -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());
}
}

View file

@ -14,7 +14,7 @@ public class AllSharedStoriesItemListFragment extends ItemListFragment {
@Override
public void onLoadFinished(Loader<Cursor> 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()));

View file

@ -14,7 +14,7 @@ public class AllStoriesItemListFragment extends ItemListFragment {
@Override
public void onLoadFinished(Loader<Cursor> 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()));

View file

@ -31,7 +31,7 @@ public class FeedItemListFragment extends ItemListFragment {
@Override
public void onLoadFinished(Loader<Cursor> 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()));

View file

@ -21,7 +21,7 @@ public class FolderItemListFragment extends ItemListFragment {
@Override
public void onLoadFinished(Loader<Cursor> 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()));

View file

@ -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);

View file

@ -24,7 +24,7 @@ public class GlobalSharedStoriesItemListFragment extends ItemListFragment {
@Override
public void onLoadFinished(Loader<Cursor> 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));

View file

@ -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) {

View file

@ -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();

View file

@ -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);
}
}
}

View file

@ -29,7 +29,7 @@ public class ReadStoriesItemListFragment extends ItemListFragment {
@Override
public void onLoadFinished(Loader<Cursor> 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));

View file

@ -30,15 +30,13 @@ public class SavedStoriesItemListFragment extends ItemListFragment {
@Override
public void onLoadFinished(Loader<Cursor> 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

View file

@ -21,7 +21,7 @@ public class SocialFeedItemListFragment extends ItemListFragment {
@Override
public void onLoadFinished(Loader<Cursor> 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()));

View file

@ -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.

View file

@ -52,27 +52,28 @@ public class UnreadsService extends SubService {
NavigableMap<String,String> sortingMap = new TreeMap<String,String>();
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<String> 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<String, List<String[]>> entry : unreadHashes.unreadHashes.entrySet()) {
feedloop: for (Entry<String, List<String[]>> 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<String> 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;

View file

@ -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;

View file

@ -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;
}

View file

@ -94,33 +94,30 @@ public class FeedUtils {
}.execute();
}
public static void prepareReadingSession(final FeedSet fs, final StateFilter state) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... arg) {
dbHelper.prepareReadingSession(fs, state);
return null;
}
}.execute();
}
public static void clearReadingSession() {
new AsyncTask<Void, Void, Void>() {
@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<Void, Void, Void>() {
@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) {

View file

@ -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;
}
}
}

View file

@ -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";
}

View file

@ -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()));
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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;