mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-21 05:45:13 +00:00
Merge pull request #662 from dosiecki/master
Android: Bugfixes and Speed
This commit is contained in:
commit
9b51f50bb4
32 changed files with 457 additions and 178 deletions
|
@ -2,11 +2,11 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.newsblur"
|
||||
android:versionCode="85"
|
||||
android:versionName="4.2.0b1" >
|
||||
android:versionName="4.2.0b3" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="11"
|
||||
android:targetSdkVersion="19" />
|
||||
android:targetSdkVersion="21" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
@ -55,8 +55,9 @@
|
|||
|
||||
<activity
|
||||
android:name=".activity.Main"
|
||||
android:label="@string/newsblur" />
|
||||
|
||||
android:label="@string/newsblur"
|
||||
android:launchMode="singleTask"
|
||||
android:alwaysRetainTaskState="true" />
|
||||
|
||||
<activity
|
||||
android:name=".activity.Profile"
|
||||
|
@ -126,6 +127,12 @@
|
|||
|
||||
<receiver android:name=".service.ServiceScheduleReceiver" />
|
||||
|
||||
<receiver android:name=".service.NetStateReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -11,4 +11,4 @@
|
|||
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-19
|
||||
target=android-21
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shareInterpolator="false" >
|
||||
<translate
|
||||
android:duration="200"
|
||||
android:duration="300"
|
||||
android:fromXDelta="-100%"
|
||||
android:toXDelta="0%" />
|
||||
</set>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shareInterpolator="false" >
|
||||
<translate
|
||||
android:duration="200"
|
||||
android:duration="300"
|
||||
android:fromXDelta="0%"
|
||||
android:toXDelta="100%" />
|
||||
</set>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:padding="3dp"
|
||||
android:padding="2dp"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/status_overlay_text"
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/fragment_feedintelligenceselector"
|
||||
android:padding="3dp"
|
||||
android:padding="2dp"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/status_overlay_text"
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
<color name="progress_circle_complete">#8F918B</color>
|
||||
<color name="progress_circle_remaining">#D5D7CF</color>
|
||||
|
||||
<color name="status_overlay_text">#DD111111</color>
|
||||
<color name="status_overlay_text">#DDFFFFFF</color>
|
||||
<color name="status_overlay_background">#AA777777</color>
|
||||
|
||||
<color name="story_buttons_dark">#90928b</color>
|
||||
|
|
|
@ -110,26 +110,14 @@
|
|||
<string name="menu_mark_story_as_read">Mark as read</string>
|
||||
<string name="menu_mark_unread">Mark as unread</string>
|
||||
<string name="menu_fullscreen">Full screen</string>
|
||||
|
||||
<string name="toast_error_loading_stories">Error loading stories</string>
|
||||
|
||||
<string name="toast_marked_stories_as_read">Stories marked as read</string>
|
||||
|
||||
<string name="toast_error_marking_feed_as_read">Error marking feed as read. Check your internet connection.</string>
|
||||
|
||||
<string name="toast_story_saved">Story saved</string>
|
||||
<string name="toast_story_save_error">Error marking story as saved.</string>
|
||||
<string name="toast_story_unsaved">Story unsaved</string>
|
||||
<string name="toast_story_unsave_error">Error marking story as unsaved.</string>
|
||||
|
||||
<string name="toast_story_unread">Story marked as unread</string>
|
||||
<string name="toast_story_unread_error">Error marking story as unread</string>
|
||||
<string name="toast_story_read_error">Error marking story as unread</string>
|
||||
|
||||
<string name="toast_unread_search_error">Could not load next unread story</string>
|
||||
|
||||
<string name="toast_feed_deleted">Feed deleted</string>
|
||||
<string name="toast_feed_delete_error">There was an error deleting the feed.</string>
|
||||
|
||||
<string name="logout_warning">Are you sure you want to log out?</string>
|
||||
|
||||
|
@ -148,7 +136,6 @@
|
|||
<string name="empty_list_view_no_stories">No stories to read</string>
|
||||
|
||||
<string name="login_registration_register">Register</string>
|
||||
<string name="add_sites">Add some sites</string>
|
||||
<string name="get_started">Let\'s get started</string>
|
||||
<string name="add_friends">Add your friends</string>
|
||||
<string name="connect_with_your_friends">Connect with your friends to easily follow the stories that matter to them</string>
|
||||
|
@ -159,11 +146,6 @@
|
|||
<string name="add_facebook">Add Facebook friends</string>
|
||||
<string name="need_to_login"><u>I need to log in!</u></string>
|
||||
<string name="need_to_register"><u>I need to register</u></string>
|
||||
<string name="wonderful_things">Wonderful things are happening at NewsBlur. Add our blog for the latest news.</string>
|
||||
<string name="addfollow_add_newsblur">Follow the NewsBlur blog</string>
|
||||
<string name="addfollow_add_popular">Follow Popular Stories</string>
|
||||
<string name="add_follow">All Done!</string>
|
||||
<string name="share_with_comments">Share with comments</string>
|
||||
<string name="share_this_story">Share this story</string>
|
||||
<string name="comment_favourited">Comment favorited</string>
|
||||
<string name="error_liking_comment">Error favoriting comment</string>
|
||||
|
@ -260,6 +242,7 @@
|
|||
<string name="sync_status_unreads">Storing%sunread stories...</string>
|
||||
<string name="sync_status_text">Storing text for %s stories...</string>
|
||||
<string name="sync_status_images">Storing %s images...</string>
|
||||
<string name="sync_status_offline">Offline</string>
|
||||
|
||||
<string name="volume_key_navigation">Volume Key Navigation</string>
|
||||
<string name="off">Off</string>
|
||||
|
|
|
@ -1,21 +1,12 @@
|
|||
package com.newsblur.activity;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.newsblur.R;
|
||||
import com.newsblur.database.DatabaseConstants;
|
||||
import com.newsblur.fragment.AllStoriesItemListFragment;
|
||||
import com.newsblur.fragment.FeedItemListFragment;
|
||||
import com.newsblur.fragment.MarkAllReadDialogFragment;
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package com.newsblur.activity;
|
||||
|
||||
|
||||
public class FeedSearch extends NbActivity {
|
||||
|
||||
}
|
|
@ -15,7 +15,6 @@ import android.widget.Toast;
|
|||
import android.util.Log;
|
||||
|
||||
import com.newsblur.R;
|
||||
import com.newsblur.database.DatabaseConstants;
|
||||
import com.newsblur.fragment.FolderItemListFragment;
|
||||
import com.newsblur.fragment.MarkAllReadDialogFragment;
|
||||
import com.newsblur.fragment.MarkAllReadDialogFragment.MarkAllReadDialogListener;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.newsblur.activity;
|
||||
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.ContentResolver;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
@ -17,16 +16,12 @@ import com.newsblur.util.StoryOrder;
|
|||
|
||||
public class GlobalSharedStoriesItemsList extends ItemsList {
|
||||
|
||||
private ContentResolver resolver;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
setTitle(getResources().getString(R.string.global_shared_stories));
|
||||
|
||||
resolver = getContentResolver();
|
||||
|
||||
itemListFragment = (GlobalSharedStoriesItemListFragment) fragmentManager.findFragmentByTag(GlobalSharedStoriesItemListFragment.class.getName());
|
||||
if (itemListFragment == null) {
|
||||
itemListFragment = GlobalSharedStoriesItemListFragment.newInstance(getDefaultFeedView(), currentState);
|
||||
|
|
|
@ -81,6 +81,12 @@ public abstract class ItemsList extends NbActivity implements StateChangedListen
|
|||
itemListFragment.hasUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
NBSyncService.addRecountCandidates(fs);
|
||||
}
|
||||
|
||||
public void markItemListAsRead() {
|
||||
FeedUtils.markFeedsRead(fs, null, null, this);
|
||||
Toast.makeText(this, R.string.toast_marked_stories_as_read, Toast.LENGTH_SHORT).show();
|
||||
|
|
|
@ -74,6 +74,7 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
|||
super.onResume();
|
||||
|
||||
NBSyncService.clearPendingStoryRequest();
|
||||
NBSyncService.flushRecounts();
|
||||
NBSyncService.setActivationMode(NBSyncService.ActivationMode.ALL);
|
||||
FeedUtils.activateAllStories();
|
||||
FeedUtils.clearReadingSession();
|
||||
|
@ -155,12 +156,6 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
|||
folderFeedList.changeState(state);
|
||||
}
|
||||
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
folderFeedList.hasUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(boolean freshData) {
|
||||
updateStatusIndicators();
|
||||
|
|
|
@ -99,6 +99,8 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
|
|||
private float overlayRangeTopPx;
|
||||
private float overlayRangeBotPx;
|
||||
|
||||
private int lastVScrollPos = 0;
|
||||
|
||||
private List<Story> pageHistory;
|
||||
|
||||
protected DefaultFeedView defaultFeedView;
|
||||
|
@ -377,9 +379,10 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
|
|||
|
||||
@Override
|
||||
public void scrollChanged(int hPos, int vPos, int currentWidth, int currentHeight) {
|
||||
// only update overlay alpha about half the time. modern screens are so dense that it
|
||||
// only update overlay alpha every few pixels. modern screens are so dense that it
|
||||
// is way overkill to do it on every pixel
|
||||
if (vPos % 2 == 1) return;
|
||||
if (Math.abs(lastVScrollPos-vPos) < 2) return;
|
||||
lastVScrollPos = vPos;
|
||||
|
||||
int scrollMax = currentHeight - contentView.getMeasuredHeight();
|
||||
int posFromBot = (scrollMax - vPos);
|
||||
|
@ -710,6 +713,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
|
|||
}
|
||||
|
||||
public void overlaySend(View v) {
|
||||
if ((readingAdapter == null) || (pager == null)) return;
|
||||
Story story = readingAdapter.getStory(pager.getCurrentItem());
|
||||
FeedUtils.shareStory(story, this);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
package com.newsblur.activity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.view.Menu;
|
||||
|
@ -14,7 +7,6 @@ import android.view.MenuInflater;
|
|||
import android.widget.Toast;
|
||||
|
||||
import com.newsblur.R;
|
||||
import com.newsblur.database.DatabaseConstants;
|
||||
import com.newsblur.fragment.SavedStoriesItemListFragment;
|
||||
import com.newsblur.fragment.FeedItemListFragment;
|
||||
import com.newsblur.util.DefaultFeedView;
|
||||
|
@ -27,16 +19,12 @@ import com.newsblur.util.StoryOrder;
|
|||
|
||||
public class SavedStoriesItemsList extends ItemsList {
|
||||
|
||||
private ContentResolver resolver;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
setTitle(getResources().getString(R.string.saved_stories_title));
|
||||
|
||||
resolver = getContentResolver();
|
||||
|
||||
itemListFragment = (SavedStoriesItemListFragment) fragmentManager.findFragmentByTag(SavedStoriesItemListFragment.class.getName());
|
||||
if (itemListFragment == null) {
|
||||
itemListFragment = SavedStoriesItemListFragment.newInstance(getDefaultFeedView());
|
||||
|
|
|
@ -355,33 +355,25 @@ public class BlurDatabaseHelper {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a story (un)read but does not adjust counts.
|
||||
*/
|
||||
public void setStoryReadState(String hash, boolean read) {
|
||||
Cursor c = getStory(hash);
|
||||
if (c.getCount() < 1) {
|
||||
Log.w(this.getClass().getName(), "story removed before finishing mark-read");
|
||||
return;
|
||||
}
|
||||
Story story = Story.fromCursor(c);
|
||||
if (story == null) {
|
||||
Log.w(this.getClass().getName(), "story removed before finishing mark-read");
|
||||
return;
|
||||
}
|
||||
setStoryReadState(story, read);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DatabaseConstants.STORY_READ, read);
|
||||
values.put(DatabaseConstants.STORY_READ_THIS_SESSION, read);
|
||||
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, DatabaseConstants.STORY_HASH + " = ?", new String[]{hash});}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a story (un)read and also adjusts unread counts for it.
|
||||
*
|
||||
* @return the set of feed IDs that potentially have counts impacted by the mark.
|
||||
*/
|
||||
public void setStoryReadState(Story story, boolean read) {
|
||||
// read flag
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DatabaseConstants.STORY_READ, read);
|
||||
values.put(DatabaseConstants.STORY_READ_THIS_SESSION, read);
|
||||
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, DatabaseConstants.STORY_HASH + " = ?", new String[]{story.storyHash});}
|
||||
// non-social feed count
|
||||
refreshFeedCounts(FeedSet.singleFeed(story.feedId));
|
||||
// social feed counts
|
||||
public Set<FeedSet> setStoryReadState(Story story, boolean read) {
|
||||
// calculate the impact surface so the caller can re-check counts if needed
|
||||
Set<FeedSet> impactedFeeds = new HashSet<FeedSet>();
|
||||
impactedFeeds.add(FeedSet.singleFeed(story.feedId));
|
||||
Set<String> socialIds = new HashSet<String>();
|
||||
if (!TextUtils.isEmpty(story.socialUserId)) {
|
||||
socialIds.add(story.socialUserId);
|
||||
|
@ -392,10 +384,73 @@ public class BlurDatabaseHelper {
|
|||
}
|
||||
}
|
||||
if (socialIds.size() > 0) {
|
||||
refreshFeedCounts(FeedSet.multipleSocialFeeds(socialIds));
|
||||
impactedFeeds.add(FeedSet.multipleSocialFeeds(socialIds));
|
||||
}
|
||||
// check the story's starting state and the desired state and adjust it as an atom so we
|
||||
// know if it truly changed or not
|
||||
synchronized (RW_MUTEX) {
|
||||
dbRW.beginTransaction();
|
||||
try {
|
||||
// get a fresh copy of the story from the DB so we know if it changed
|
||||
Cursor c = dbRW.query(DatabaseConstants.STORY_TABLE,
|
||||
new String[]{DatabaseConstants.STORY_READ},
|
||||
DatabaseConstants.STORY_HASH + " = ?",
|
||||
new String[]{story.storyHash},
|
||||
null, null, null);
|
||||
if (c.getCount() < 1) {
|
||||
Log.w(this.getClass().getName(), "story removed before finishing mark-read");
|
||||
return impactedFeeds;
|
||||
}
|
||||
c.moveToFirst();
|
||||
boolean origState = (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.STORY_READ)) > 0);
|
||||
c.close();
|
||||
// if there is nothing to be done, halt
|
||||
if (origState == read) {
|
||||
dbRW.setTransactionSuccessful();
|
||||
return impactedFeeds;
|
||||
}
|
||||
// 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) {
|
||||
// negative stories don't affect counts
|
||||
dbRW.setTransactionSuccessful();
|
||||
return impactedFeeds;
|
||||
} else if (story.intelTotal == 0 ) {
|
||||
impactedCol = DatabaseConstants.FEED_NEUTRAL_COUNT;
|
||||
impactedSocialCol = DatabaseConstants.SOCIAL_FEED_NEUTRAL_COUNT;
|
||||
} else {
|
||||
impactedCol = DatabaseConstants.FEED_POSITIVE_COUNT;
|
||||
impactedSocialCol = DatabaseConstants.SOCIAL_FEED_POSITIVE_COUNT;
|
||||
}
|
||||
String operator = (read ? " - 1" : " + 1");
|
||||
StringBuilder q = new StringBuilder("UPDATE " + DatabaseConstants.FEED_TABLE);
|
||||
q.append(" SET ").append(impactedCol).append(" = ").append(impactedCol).append(operator);
|
||||
q.append(" WHERE " + DatabaseConstants.FEED_ID + " = ").append(story.feedId);
|
||||
dbRW.execSQL(q.toString());
|
||||
for (String socialId : socialIds) {
|
||||
q = new StringBuilder("UPDATE " + DatabaseConstants.SOCIALFEED_TABLE);
|
||||
q.append(" SET ").append(impactedSocialCol).append(" = ").append(impactedSocialCol).append(operator);
|
||||
q.append(" WHERE " + DatabaseConstants.SOCIAL_FEED_ID + " = ").append(socialId);
|
||||
dbRW.execSQL(q.toString());
|
||||
}
|
||||
dbRW.setTransactionSuccessful();
|
||||
} finally {
|
||||
dbRW.endTransaction();
|
||||
}
|
||||
}
|
||||
return impactedFeeds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a range of stories in a subset of feeds as read. Does not update unread counts;
|
||||
* the caller must use updateLocalFeedCounts() or the /reader/feed_unread_count API.
|
||||
*/
|
||||
public void markStoriesRead(FeedSet fs, Long olderThan, Long newerThan) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DatabaseConstants.STORY_READ, true);
|
||||
|
@ -419,14 +474,73 @@ public class BlurDatabaseHelper {
|
|||
throw new IllegalStateException("Asked to mark stories for FeedSet of unknown type.");
|
||||
}
|
||||
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, conjoinSelections(feedSelection, rangeSelection), null);}
|
||||
}
|
||||
|
||||
refreshFeedCounts(fs);
|
||||
/**
|
||||
* Get the unread count for the given feedset based on the totals in the feeds table.
|
||||
*/
|
||||
public int getUnreadCount(FeedSet fs, StateFilter stateFilter) {
|
||||
if (fs.isAllNormal()) {
|
||||
return getFeedsUnreadCount(stateFilter, null, null);
|
||||
} else if (fs.isAllSocial()) {
|
||||
//return getSocialFeedsUnreadCount(stateFilter, null, null);
|
||||
// even though we can count up and total the unreads in social feeds, the API doesn't vend
|
||||
// unread status for stories viewed when reading All Shared Stories, so force this to 0.
|
||||
return 0;
|
||||
} else if (fs.getMultipleFeeds() != null) {
|
||||
StringBuilder selection = new StringBuilder(DatabaseConstants.FEED_ID + " IN ( ");
|
||||
selection.append(TextUtils.join(",", fs.getMultipleFeeds())).append(")");
|
||||
return getFeedsUnreadCount(stateFilter, selection.toString(), null);
|
||||
} else if (fs.getMultipleSocialFeeds() != null) {
|
||||
StringBuilder selection = new StringBuilder(DatabaseConstants.SOCIAL_FEED_ID + " IN ( ");
|
||||
selection.append(TextUtils.join(",", fs.getMultipleFeeds())).append(")");
|
||||
return getSocialFeedsUnreadCount(stateFilter, selection.toString(), null);
|
||||
} else if (fs.getSingleFeed() != null) {
|
||||
return getFeedsUnreadCount(stateFilter, DatabaseConstants.FEED_ID + " = ?", new String[]{fs.getSingleFeed()});
|
||||
} else if (fs.getSingleSocialFeed() != null) {
|
||||
return getSocialFeedsUnreadCount(stateFilter, DatabaseConstants.SOCIAL_FEED_ID + " = ?", new String[]{fs.getSingleSocialFeed().getKey()});
|
||||
} else {
|
||||
// all other types of view don't track unreads correctly
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private int getFeedsUnreadCount(StateFilter stateFilter, String selection, String[] selArgs) {
|
||||
int result = 0;
|
||||
Cursor c = dbRO.query(DatabaseConstants.FEED_TABLE, null, selection, selArgs, null, null, null);
|
||||
while (c.moveToNext()) {
|
||||
Feed f = Feed.fromCursor(c);
|
||||
result += f.positiveCount;
|
||||
if ((stateFilter == StateFilter.SOME) || (stateFilter == StateFilter.ALL)) result += f.neutralCount;
|
||||
if (stateFilter == StateFilter.ALL) result += f.negativeCount;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int getSocialFeedsUnreadCount(StateFilter stateFilter, String selection, String[] selArgs) {
|
||||
int result = 0;
|
||||
Cursor c = dbRO.query(DatabaseConstants.SOCIALFEED_TABLE, null, selection, selArgs, null, null, null);
|
||||
while (c.moveToNext()) {
|
||||
SocialFeed f = SocialFeed.fromCursor(c);
|
||||
result += f.positiveCount;
|
||||
if ((stateFilter == StateFilter.SOME) || (stateFilter == StateFilter.ALL)) result += f.neutralCount;
|
||||
if (stateFilter == StateFilter.ALL) result += f.negativeCount;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void updateFeedCounts(String feedId, ContentValues values) {
|
||||
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.FEED_TABLE, values, DatabaseConstants.FEED_ID + " = ?", new String[]{feedId});}
|
||||
}
|
||||
|
||||
public void updateSocialFeedCounts(String feedId, ContentValues values) {
|
||||
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.SOCIALFEED_TABLE, values, DatabaseConstants.SOCIAL_FEED_ID + " = ?", new String[]{feedId});}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the counts in the feeds/socialfeeds tables by counting stories in the story table.
|
||||
*/
|
||||
public void refreshFeedCounts(FeedSet fs) {
|
||||
public void updateLocalFeedCounts(FeedSet fs) {
|
||||
// decompose the FeedSet into a list of single feeds that need to be recounted
|
||||
List<String> feedIds = new ArrayList<String>();
|
||||
List<String> socialFeedIds = new ArrayList<String>();
|
||||
|
@ -450,23 +564,26 @@ public class BlurDatabaseHelper {
|
|||
for (String feedId : feedIds) {
|
||||
FeedSet singleFs = FeedSet.singleFeed(feedId);
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DatabaseConstants.FEED_NEGATIVE_COUNT, getUnreadCount(singleFs, StateFilter.NEG));
|
||||
values.put(DatabaseConstants.FEED_NEUTRAL_COUNT, getUnreadCount(singleFs, StateFilter.NEUT));
|
||||
values.put(DatabaseConstants.FEED_POSITIVE_COUNT, getUnreadCount(singleFs, StateFilter.BEST));
|
||||
values.put(DatabaseConstants.FEED_NEGATIVE_COUNT, getLocalUnreadCount(singleFs, StateFilter.NEG));
|
||||
values.put(DatabaseConstants.FEED_NEUTRAL_COUNT, getLocalUnreadCount(singleFs, StateFilter.NEUT));
|
||||
values.put(DatabaseConstants.FEED_POSITIVE_COUNT, getLocalUnreadCount(singleFs, StateFilter.BEST));
|
||||
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.FEED_TABLE, values, DatabaseConstants.FEED_ID + " = ?", new String[]{feedId});}
|
||||
}
|
||||
|
||||
for (String socialId : socialFeedIds) {
|
||||
FeedSet singleFs = FeedSet.singleSocialFeed(socialId, "");
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DatabaseConstants.SOCIAL_FEED_NEGATIVE_COUNT, getUnreadCount(singleFs, StateFilter.NEG));
|
||||
values.put(DatabaseConstants.SOCIAL_FEED_NEUTRAL_COUNT, getUnreadCount(singleFs, StateFilter.NEUT));
|
||||
values.put(DatabaseConstants.SOCIAL_FEED_POSITIVE_COUNT, getUnreadCount(singleFs, StateFilter.BEST));
|
||||
values.put(DatabaseConstants.SOCIAL_FEED_NEGATIVE_COUNT, getLocalUnreadCount(singleFs, StateFilter.NEG));
|
||||
values.put(DatabaseConstants.SOCIAL_FEED_NEUTRAL_COUNT, getLocalUnreadCount(singleFs, StateFilter.NEUT));
|
||||
values.put(DatabaseConstants.SOCIAL_FEED_POSITIVE_COUNT, getLocalUnreadCount(singleFs, StateFilter.BEST));
|
||||
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.SOCIALFEED_TABLE, values, DatabaseConstants.SOCIAL_FEED_ID + " = ?", new String[]{socialId});}
|
||||
}
|
||||
}
|
||||
|
||||
public int getUnreadCount(FeedSet fs, StateFilter stateFilter) {
|
||||
/**
|
||||
* 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);
|
||||
int count = c.getCount();
|
||||
c.close();
|
||||
|
@ -486,12 +603,6 @@ public class BlurDatabaseHelper {
|
|||
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.ACTION_TABLE, DatabaseConstants.ACTION_ID + " = ?", new String[]{actionId});}
|
||||
}
|
||||
|
||||
public Cursor getStory(String hash) {
|
||||
String q = "SELECT * FROM " + DatabaseConstants.STORY_TABLE +
|
||||
" WHERE " + DatabaseConstants.STORY_HASH + " = ?";
|
||||
return dbRO.rawQuery(q, new String[]{hash});
|
||||
}
|
||||
|
||||
public void setStoryStarred(String hash, boolean starred) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DatabaseConstants.STORY_STARRED, starred);
|
||||
|
|
|
@ -81,6 +81,8 @@ public class Story implements Serializable {
|
|||
@SerializedName("intelligence")
|
||||
public Intelligence intelligence = new Intelligence();
|
||||
|
||||
public int intelTotal;
|
||||
|
||||
@SerializedName("short_parsed_date")
|
||||
public String shortDate;
|
||||
|
||||
|
@ -149,6 +151,7 @@ public class Story implements Serializable {
|
|||
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));
|
||||
|
|
|
@ -19,6 +19,9 @@ public class LogoutDialogFragment extends DialogFragment {
|
|||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
PrefsUtils.logout(getActivity());
|
||||
// make sure the instance of Main that called us is killed now, or else the system
|
||||
// might try to recycle it with a stale login ID, which will cause it to self-destruct
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {
|
||||
|
|
|
@ -25,6 +25,7 @@ public class APIConstants {
|
|||
public static final String URL_SHARED_RIVER_STORIES = NEWSBLUR_URL + "/social/river_stories";
|
||||
|
||||
public static final String URL_FEED_STORIES = NEWSBLUR_URL + "/reader/feed";
|
||||
public static final String URL_FEED_UNREAD_COUNT = NEWSBLUR_URL + "/reader/feed_unread_count";
|
||||
public static final String URL_SOCIALFEED_STORIES = NEWSBLUR_URL + "/social/stories";
|
||||
public static final String URL_SIGNUP = NEWSBLUR_URL + "/api/signup";
|
||||
public static final String URL_MARK_FEED_AS_READ = NEWSBLUR_URL + "/reader/mark_feed_as_read/";
|
||||
|
|
|
@ -11,8 +11,8 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -38,6 +38,7 @@ import com.newsblur.network.domain.ProfileResponse;
|
|||
import com.newsblur.network.domain.RegisterResponse;
|
||||
import com.newsblur.network.domain.StoriesResponse;
|
||||
import com.newsblur.network.domain.StoryTextResponse;
|
||||
import com.newsblur.network.domain.UnreadCountResponse;
|
||||
import com.newsblur.network.domain.UnreadStoryHashesResponse;
|
||||
import com.newsblur.serialization.BooleanTypeAdapter;
|
||||
import com.newsblur.serialization.ClassifierMapTypeAdapter;
|
||||
|
@ -58,12 +59,10 @@ public class APIManager {
|
|||
|
||||
private Context context;
|
||||
private Gson gson;
|
||||
private ContentResolver contentResolver;
|
||||
private String customUserAgent;
|
||||
|
||||
public APIManager(final Context context) {
|
||||
this.context = context;
|
||||
this.contentResolver = context.getContentResolver();
|
||||
|
||||
this.gson = new GsonBuilder()
|
||||
.registerTypeAdapter(Date.class, new DateStringTypeAdapter())
|
||||
|
@ -246,6 +245,15 @@ public class APIManager {
|
|||
}
|
||||
}
|
||||
|
||||
public UnreadCountResponse getFeedUnreadCounts(Set<String> apiIds) {
|
||||
ValueMultimap values = new ValueMultimap();
|
||||
for (String id : apiIds) {
|
||||
values.put(APIConstants.PARAMETER_FEEDID, id);
|
||||
}
|
||||
APIResponse response = get(APIConstants.URL_FEED_UNREAD_COUNT, values);
|
||||
return (UnreadCountResponse) response.getResponse(gson, UnreadCountResponse.class);
|
||||
}
|
||||
|
||||
public UnreadStoryHashesResponse getUnreadStoryHashes() {
|
||||
ValueMultimap values = new ValueMultimap();
|
||||
values.put(APIConstants.PARAMETER_INCLUDE_TIMESTAMPS, "1");
|
||||
|
@ -370,7 +378,9 @@ public class APIManager {
|
|||
}
|
||||
|
||||
// note: this response is complex enough, we have to do a custom parse in the FFR
|
||||
return new FeedFolderResponse(response.getResponseBody(), gson);
|
||||
FeedFolderResponse result = new FeedFolderResponse(response.getResponseBody(), gson);
|
||||
result.readTime = response.readTime;
|
||||
return result;
|
||||
}
|
||||
|
||||
public NewsBlurResponse trainClassifier(String feedId, String key, int type, int action) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package com.newsblur.network;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Scanner;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
@ -28,6 +29,7 @@ public class APIResponse {
|
|||
private String errorMessage;
|
||||
private String cookie;
|
||||
private String responseBody;
|
||||
public long readTime;
|
||||
|
||||
/**
|
||||
* Construct an online response. Will test the response for errors and extract all the
|
||||
|
@ -71,8 +73,14 @@ public class APIResponse {
|
|||
|
||||
try {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Scanner scanner = new Scanner(connection.getInputStream(), "UTF-8");
|
||||
while (scanner.hasNextLine()) { builder.append(scanner.nextLine()); }
|
||||
Reader reader = new InputStreamReader(connection.getInputStream());
|
||||
char[] chunk = new char[1024];
|
||||
int len;
|
||||
long startTime = System.currentTimeMillis();
|
||||
while ( (len = reader.read(chunk)) > 0) {
|
||||
builder.append(chunk, 0, len);
|
||||
}
|
||||
readTime = System.currentTimeMillis() - startTime;
|
||||
this.responseBody = builder.toString();
|
||||
} catch (Exception e) {
|
||||
Log.e(this.getClass().getName(), e.getClass().getName() + " (" + e.getMessage() + ") reading " + originalUrl, e);
|
||||
|
@ -81,16 +89,23 @@ public class APIResponse {
|
|||
return;
|
||||
}
|
||||
|
||||
if (AppConstants.VERBOSE_LOG_NET) {
|
||||
Log.d(this.getClass().getName(), "received API response: \n" + this.responseBody);
|
||||
}
|
||||
|
||||
try {
|
||||
connection.disconnect();
|
||||
} catch (Exception e) {
|
||||
Log.e(this.getClass().getName(), e.getClass().getName() + " caught closing connection: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
|
||||
if (AppConstants.VERBOSE_LOG_NET) {
|
||||
// the default kernel truncates log lines. split by something we probably have, like a json delim
|
||||
if (responseBody.length() < 2048) {
|
||||
Log.d(this.getClass().getName(), "API response: \n" + this.responseBody);
|
||||
} else {
|
||||
Log.d(this.getClass().getName(), "API response: ");
|
||||
for (String s : TextUtils.split(responseBody, "\\}")) {
|
||||
Log.d(this.getClass().getName(), s + "}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -131,7 +146,9 @@ public class APIResponse {
|
|||
} else {
|
||||
// otherwise, parse the response as the expected class and defer error detection
|
||||
// to the NewsBlurResponse parent class
|
||||
return gson.fromJson(this.responseBody, classOfT);
|
||||
T response = gson.fromJson(this.responseBody, classOfT);
|
||||
response.readTime = readTime;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ import com.newsblur.domain.SocialFeed;
|
|||
import com.newsblur.util.AppConstants;
|
||||
|
||||
public class FeedFolderResponse {
|
||||
|
||||
public long readTime;
|
||||
|
||||
@SerializedName("starred_count")
|
||||
public int starredCount;
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
package com.newsblur.network.domain;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.newsblur.database.DatabaseConstants;
|
||||
|
||||
public class FeedRefreshResponse extends NewsBlurResponse {
|
||||
|
||||
|
||||
@SerializedName("feeds")
|
||||
public Map<String, Count> feedCounts;
|
||||
|
||||
@SerializedName("social_feeds")
|
||||
public Map<String, Count> socialfeedCounts;
|
||||
|
||||
public class Count {
|
||||
|
||||
@SerializedName("ps")
|
||||
int positive;
|
||||
|
||||
@SerializedName("ng")
|
||||
int negative;
|
||||
|
||||
@SerializedName("nt")
|
||||
int neutral;
|
||||
|
||||
public ContentValues getValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DatabaseConstants.FEED_NEGATIVE_COUNT, negative);
|
||||
values.put(DatabaseConstants.FEED_NEUTRAL_COUNT, neutral);
|
||||
values.put(DatabaseConstants.FEED_POSITIVE_COUNT, positive);
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -2,6 +2,9 @@ package com.newsblur.network.domain;
|
|||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A generic response to an API call that only encapsuates success versus failure.
|
||||
*/
|
||||
|
@ -11,6 +14,9 @@ public class NewsBlurResponse {
|
|||
public int code;
|
||||
public String message;
|
||||
public ResponseErrors errors;
|
||||
public long readTime;
|
||||
|
||||
public static final Pattern KnownUserErrors = Pattern.compile("cannot mark as unread");
|
||||
|
||||
public boolean isError() {
|
||||
if ((message != null) && (!message.equals(""))) {
|
||||
|
@ -24,6 +30,20 @@ public class NewsBlurResponse {
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO: can we add a canonical flag of some sort to 100% of API responses that differentiates
|
||||
// between 400-type and 2/3/500-type errors? Until then, we have to sniff known bad ones.
|
||||
public boolean isUserError() {
|
||||
if (message != null) {
|
||||
Matcher m = KnownUserErrors.matcher(message);
|
||||
if (m.find()) return true;
|
||||
}
|
||||
if ((errors != null) && (errors.message.length > 0) && (errors.message[0] != null)) {
|
||||
Matcher m = KnownUserErrors.matcher(errors.message[0]);
|
||||
if (m.find()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error message returned by the API, or defaultMessage if none was found.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package com.newsblur.network.domain;
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.newsblur.database.DatabaseConstants;
|
||||
|
||||
public class UnreadCountResponse extends NewsBlurResponse {
|
||||
|
||||
@SerializedName("feeds")
|
||||
public Map<String,UnreadMD> feeds;
|
||||
|
||||
@SerializedName("social_feeds")
|
||||
public Map<String,UnreadMD> socialFeeds;
|
||||
|
||||
public class UnreadMD {
|
||||
|
||||
public int ps;
|
||||
public int nt;
|
||||
public int ng;
|
||||
|
||||
public ContentValues getValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DatabaseConstants.FEED_POSITIVE_COUNT, ps);
|
||||
values.put(DatabaseConstants.FEED_NEUTRAL_COUNT, nt);
|
||||
values.put(DatabaseConstants.FEED_NEGATIVE_COUNT, ng);
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,10 +20,12 @@ import static com.newsblur.database.BlurDatabaseHelper.closeQuietly;
|
|||
import com.newsblur.database.DatabaseConstants;
|
||||
import com.newsblur.domain.SocialFeed;
|
||||
import com.newsblur.domain.Story;
|
||||
import com.newsblur.network.APIConstants;
|
||||
import com.newsblur.network.APIManager;
|
||||
import com.newsblur.network.domain.FeedFolderResponse;
|
||||
import com.newsblur.network.domain.NewsBlurResponse;
|
||||
import com.newsblur.network.domain.StoriesResponse;
|
||||
import com.newsblur.network.domain.UnreadCountResponse;
|
||||
import com.newsblur.network.domain.UnreadStoryHashesResponse;
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.FeedSet;
|
||||
|
@ -32,6 +34,7 @@ import com.newsblur.util.NetworkUtils;
|
|||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.ReadingAction;
|
||||
import com.newsblur.util.ReadFilter;
|
||||
import com.newsblur.util.StateFilter;
|
||||
import com.newsblur.util.StoryOrder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -76,6 +79,7 @@ public class NBSyncService extends Service {
|
|||
private volatile static boolean FFSyncRunning = false;
|
||||
private volatile static boolean StorySyncRunning = false;
|
||||
private volatile static boolean HousekeepingRunning = false;
|
||||
private volatile static boolean RecountsRunning = false;
|
||||
|
||||
private volatile static boolean DoFeedsFolders = false;
|
||||
private volatile static boolean DoUnreads = false;
|
||||
|
@ -83,11 +87,15 @@ public class NBSyncService extends Service {
|
|||
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;
|
||||
|
||||
public volatile static Boolean isPremium = null;
|
||||
public volatile static Boolean isStaff = null;
|
||||
|
||||
private volatile static boolean isMemoryLow = false;
|
||||
private static long lastFeedCount = 0L;
|
||||
private static long lastFFReadMillis = 0L;
|
||||
private static long lastFFWriteMillis = 0L;
|
||||
|
||||
/** Feed set that we need to sync immediately for the UI. */
|
||||
|
@ -108,6 +116,11 @@ public class NBSyncService extends Service {
|
|||
private static List<ReadingAction> FollowupActions;
|
||||
static { FollowupActions = new ArrayList<ReadingAction>(); }
|
||||
|
||||
/** Feed IDs (API stype) that have been acted upon and need a double-check for counts. */
|
||||
private static Set<FeedSet> RecountCandidates;
|
||||
static { RecountCandidates = new HashSet<FeedSet>(); }
|
||||
private volatile static boolean FlushRecounts = false;
|
||||
|
||||
Set<String> orphanFeedIds;
|
||||
|
||||
private ExecutorService primaryExecutor;
|
||||
|
@ -192,6 +205,11 @@ public class NBSyncService extends Service {
|
|||
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE);
|
||||
}
|
||||
|
||||
if (OfflineNow) {
|
||||
OfflineNow = false;
|
||||
NbActivity.updateAllActivities(false);
|
||||
}
|
||||
|
||||
// do this even if background syncs aren't enabled, because it absolutely must happen
|
||||
// on all devices
|
||||
housekeeping();
|
||||
|
@ -212,6 +230,8 @@ public class NBSyncService extends Service {
|
|||
|
||||
syncMetadata(startId);
|
||||
|
||||
checkRecounts();
|
||||
|
||||
unreadsService.start(startId);
|
||||
|
||||
imagePrefetchService.start(startId);
|
||||
|
@ -296,7 +316,11 @@ public class NBSyncService extends Service {
|
|||
// if we attempted a call and it failed, do not mark the action as done
|
||||
if (response != null) {
|
||||
if (response.isError()) {
|
||||
continue actionsloop;
|
||||
if (response.isUserError()) {
|
||||
Log.d(this.getClass().getName(), "Discarding reading action with user error.");
|
||||
} else {
|
||||
continue actionsloop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,6 +392,7 @@ public class NBSyncService extends Service {
|
|||
FeedPagesSeen.clear();
|
||||
FeedStoriesSeen.clear();
|
||||
UnreadsService.clearHashes();
|
||||
RecountCandidates.clear();
|
||||
|
||||
FeedFolderResponse feedResponse = apiManager.getFolderFeedMapping(true);
|
||||
|
||||
|
@ -382,6 +407,7 @@ public class NBSyncService extends Service {
|
|||
return;
|
||||
}
|
||||
|
||||
lastFFReadMillis = feedResponse.readTime;
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
isPremium = feedResponse.isPremium;
|
||||
|
@ -454,6 +480,74 @@ public class NBSyncService extends Service {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* See if any feeds have been touched in a way that require us to double-check unread counts;
|
||||
*/
|
||||
private void checkRecounts() {
|
||||
if (!FlushRecounts) return;
|
||||
|
||||
try {
|
||||
if (RecountCandidates.size() < 1) return;
|
||||
|
||||
RecountsRunning = true;
|
||||
NbActivity.updateAllActivities(false);
|
||||
|
||||
// of all candidate feeds that were touched, now check to see if
|
||||
// any of them have mismatched local and remote counts we need to reconcile
|
||||
Set<FeedSet> dirtySets = new HashSet<FeedSet>();
|
||||
for (FeedSet fs : RecountCandidates) {
|
||||
if (dbHelper.getUnreadCount(fs, StateFilter.SOME) != dbHelper.getLocalUnreadCount(fs, StateFilter.SOME)) {
|
||||
dirtySets.add(fs);
|
||||
}
|
||||
}
|
||||
if (dirtySets.size() < 1) {
|
||||
RecountCandidates.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are offline, the best we can do is perform a local unread recount and
|
||||
// save the true one for when we go back online.
|
||||
if (!NetworkUtils.isOnline(this)) {
|
||||
for (FeedSet fs : RecountCandidates) {
|
||||
dbHelper.updateLocalFeedCounts(fs);
|
||||
}
|
||||
} else {
|
||||
if (stopSync()) return;
|
||||
Set<String> apiIds = new HashSet<String>();
|
||||
for (FeedSet fs : RecountCandidates) {
|
||||
apiIds.addAll(fs.getFlatFeedIds());
|
||||
}
|
||||
|
||||
// if any reading activities are pending, it makes no sense to recount yet
|
||||
if (dbHelper.getActions(false).getCount() > 0) return;
|
||||
|
||||
UnreadCountResponse apiResponse = apiManager.getFeedUnreadCounts(apiIds);
|
||||
if ((apiResponse == null) || (apiResponse.isError())) {
|
||||
Log.w(this.getClass().getName(), "Bad response to feed_unread_count");
|
||||
return;
|
||||
}
|
||||
if (apiResponse.feeds != null ) {
|
||||
for (Map.Entry<String,UnreadCountResponse.UnreadMD> entry : apiResponse.feeds.entrySet()) {
|
||||
dbHelper.updateFeedCounts(entry.getKey(), entry.getValue().getValues());
|
||||
}
|
||||
}
|
||||
if (apiResponse.socialFeeds != null ) {
|
||||
for (Map.Entry<String,UnreadCountResponse.UnreadMD> entry : apiResponse.socialFeeds.entrySet()) {
|
||||
String feedId = entry.getKey().replaceAll(APIConstants.VALUE_PREFIX_SOCIAL, "");
|
||||
dbHelper.updateSocialFeedCounts(feedId, entry.getValue().getValues());
|
||||
}
|
||||
}
|
||||
RecountCandidates.clear();
|
||||
}
|
||||
} finally {
|
||||
if (RecountsRunning) {
|
||||
RecountsRunning = false;
|
||||
NbActivity.updateAllActivities(true);
|
||||
}
|
||||
FlushRecounts = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch stories needed because the user is actively viewing a feed or folder.
|
||||
*/
|
||||
|
@ -572,7 +666,10 @@ public class NBSyncService extends Service {
|
|||
return true;
|
||||
}
|
||||
if (context == null) return false;
|
||||
if (!NetworkUtils.isOnline(context)) return true;
|
||||
if (!NetworkUtils.isOnline(context)) {
|
||||
OfflineNow = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -596,7 +693,7 @@ public class NBSyncService extends Service {
|
|||
* Is the main feed/folder list sync running?
|
||||
*/
|
||||
public static boolean isFeedFolderSyncRunning() {
|
||||
return (HousekeepingRunning || ActionsRunning || FFSyncRunning || CleanupRunning || UnreadsService.running() || StorySyncRunning || OriginalTextService.running() || ImagePrefetchService.running());
|
||||
return (HousekeepingRunning || ActionsRunning || RecountsRunning || FFSyncRunning || CleanupRunning || UnreadsService.running() || StorySyncRunning || OriginalTextService.running() || ImagePrefetchService.running());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -608,13 +705,14 @@ public class NBSyncService extends Service {
|
|||
|
||||
public static String getSyncStatusMessage(Context context) {
|
||||
if (HousekeepingRunning) return context.getResources().getString(R.string.sync_status_housekeeping);
|
||||
if (ActionsRunning) return context.getResources().getString(R.string.sync_status_actions);
|
||||
if (ActionsRunning||RecountsRunning) return context.getResources().getString(R.string.sync_status_actions);
|
||||
if (FFSyncRunning) return context.getResources().getString(R.string.sync_status_ffsync);
|
||||
if (CleanupRunning) return context.getResources().getString(R.string.sync_status_cleanup);
|
||||
if (StorySyncRunning) return context.getResources().getString(R.string.sync_status_stories);
|
||||
if (UnreadsService.running()) return String.format(context.getResources().getString(R.string.sync_status_unreads), UnreadsService.getPendingCount());
|
||||
if (OriginalTextService.running()) return String.format(context.getResources().getString(R.string.sync_status_text), OriginalTextService.getPendingCount());
|
||||
if (ImagePrefetchService.running()) return String.format(context.getResources().getString(R.string.sync_status_images), ImagePrefetchService.getPendingCount());
|
||||
if (OfflineNow) return context.getResources().getString(R.string.sync_status_offline);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -626,6 +724,10 @@ public class NBSyncService extends Service {
|
|||
DoFeedsFolders = true;
|
||||
}
|
||||
|
||||
public static void flushRecounts() {
|
||||
FlushRecounts = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the service which stories can be activated if received. See ActivationMode.
|
||||
*/
|
||||
|
@ -695,6 +797,16 @@ public class NBSyncService extends Service {
|
|||
OriginalTextService.addHash(hash);
|
||||
}
|
||||
|
||||
public static void addRecountCandidates(FeedSet fs) {
|
||||
if (fs != null) {
|
||||
RecountCandidates.add(fs);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addRecountCandidates(Set<FeedSet> fs) {
|
||||
RecountCandidates.addAll(fs);
|
||||
}
|
||||
|
||||
public static void softInterrupt() {
|
||||
if (AppConstants.VERBOSE_LOG) Log.d(NBSyncService.class.getName(), "soft stop");
|
||||
HaltNow = true;
|
||||
|
@ -709,17 +821,19 @@ public class NBSyncService extends Service {
|
|||
try {
|
||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "onDestroy - stopping execution");
|
||||
HaltNow = true;
|
||||
unreadsService.shutdown();
|
||||
originalTextService.shutdown();
|
||||
imagePrefetchService.shutdown();
|
||||
primaryExecutor.shutdown();
|
||||
try {
|
||||
primaryExecutor.awaitTermination(AppConstants.SHUTDOWN_SLACK_SECONDS, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
primaryExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
if (unreadsService != null) unreadsService.shutdown();
|
||||
if (originalTextService != null) originalTextService.shutdown();
|
||||
if (imagePrefetchService != null) imagePrefetchService.shutdown();
|
||||
if (primaryExecutor != null) {
|
||||
primaryExecutor.shutdown();
|
||||
try {
|
||||
primaryExecutor.awaitTermination(AppConstants.SHUTDOWN_SLACK_SECONDS, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
primaryExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
dbHelper.close();
|
||||
if (dbHelper != null) dbHelper.close();
|
||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "onDestroy - execution halted");
|
||||
super.onDestroy();
|
||||
} catch (Exception ex) {
|
||||
|
@ -738,7 +852,7 @@ public class NBSyncService extends Service {
|
|||
|
||||
public static String getSpeedInfo() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append(lastFeedCount).append(" in ").append(lastFFWriteMillis);
|
||||
s.append(lastFeedCount).append(" in ").append(lastFFReadMillis).append(" and ").append(lastFFWriteMillis);
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.newsblur.service;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class NetStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// poke the sync service when network state changes, in case we were offline
|
||||
if (!NBSyncService.OfflineNow) return;
|
||||
Intent i = new Intent(context, NBSyncService.class);
|
||||
context.startService(i);
|
||||
}
|
||||
|
||||
}
|
|
@ -5,7 +5,7 @@ public class AppConstants {
|
|||
// Enables high-volume logging that may be useful for debugging. This should
|
||||
// never be enabled for releases, as it not only slows down the app considerably,
|
||||
// it will log sensitive info such as passwords!
|
||||
public static final boolean VERBOSE_LOG = false;
|
||||
public static final boolean VERBOSE_LOG = true;
|
||||
public static final boolean VERBOSE_LOG_DB = false;
|
||||
public static final boolean VERBOSE_LOG_NET = false;
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ import java.util.Iterator;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.newsblur.network.APIConstants;
|
||||
|
||||
/**
|
||||
* A subset of one, several, or all NewsBlur feeds or social feeds. Used to encapsulate the
|
||||
* complexity of the fact that social feeds are special and requesting a river of feeds is not
|
||||
|
@ -195,6 +197,26 @@ public class FeedSet implements Serializable {
|
|||
return this.folderName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a flat set of feed IDs that can be passed to API calls that take raw numeric IDs or
|
||||
* social IDs prefixed with "social:". Returns an empty set for feed sets that don't track
|
||||
* unread counts or that are essentially "everything".
|
||||
*/
|
||||
public Set<String> getFlatFeedIds() {
|
||||
Set<String> result = new HashSet<String>();
|
||||
if (feeds != null) {
|
||||
for (String id : feeds) {
|
||||
result.add(id);
|
||||
}
|
||||
}
|
||||
if (socialFeeds != null) {
|
||||
for (Map.Entry<String,String> e : socialFeeds.entrySet()) {
|
||||
result.add(APIConstants.VALUE_PREFIX_SOCIAL + e.getKey());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final String COM_SER_NUL = "NUL";
|
||||
|
||||
public String toCompactSerial() {
|
||||
|
|
|
@ -133,16 +133,20 @@ public class FeedUtils {
|
|||
story.read = read;
|
||||
|
||||
// update unread state and unread counts in the local DB
|
||||
dbHelper.setStoryReadState(story, read);
|
||||
Set<FeedSet> impactedFeeds = dbHelper.setStoryReadState(story, read);
|
||||
NbActivity.updateAllActivities();
|
||||
|
||||
// tell the sync service we need to mark read
|
||||
ReadingAction ra = (read ? ReadingAction.markStoryRead(story.storyHash) : ReadingAction.markStoryUnread(story.storyHash));
|
||||
dbHelper.enqueueAction(ra);
|
||||
triggerSync(context);
|
||||
NBSyncService.addRecountCandidates(impactedFeeds);
|
||||
}
|
||||
|
||||
public static void markFeedsRead(final FeedSet fs, final Long olderThan, final Long newerThan, final Context context) {
|
||||
dbHelper.markStoriesRead(fs, olderThan, newerThan);
|
||||
dbHelper.updateLocalFeedCounts(fs);
|
||||
NbActivity.updateAllActivities();
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... arg) {
|
||||
|
@ -153,8 +157,6 @@ public class FeedUtils {
|
|||
FeedSet newFeedSet = FeedSet.folder("all", dbHelper.getAllFeeds());
|
||||
ra = ReadingAction.markFeedRead(newFeedSet, olderThan, newerThan);
|
||||
}
|
||||
dbHelper.markStoriesRead(fs, olderThan, newerThan);
|
||||
NbActivity.updateAllActivities();
|
||||
dbHelper.enqueueAction(ra);
|
||||
triggerSync(context);
|
||||
return null;
|
||||
|
|
|
@ -91,6 +91,7 @@ public class PrefsUtils {
|
|||
} else {
|
||||
s.append("unknown");
|
||||
}
|
||||
s.append("%0Aprefetch: ").append(isOfflineEnabled(context) ? "yes" : "no");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue