mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
Merge pull request #649 from dosiecki/master
Android: Bugfixes and Lagfixes
This commit is contained in:
commit
5a783a7871
13 changed files with 245 additions and 167 deletions
|
@ -81,16 +81,6 @@ public abstract class ItemsList extends NbActivity implements StateChangedListen
|
|||
itemListFragment.hasUpdated();
|
||||
}
|
||||
|
||||
private void getFirstStories() {
|
||||
triggerRefresh(AppConstants.READING_STORY_PRELOAD, 0);
|
||||
}
|
||||
|
||||
public void triggerRefresh(int desiredStoryCount, int totalSeen) {
|
||||
boolean gotSome = NBSyncService.requestMoreForFeed(fs, desiredStoryCount, totalSeen);
|
||||
if (gotSome) triggerSync();
|
||||
updateStatusIndicators();
|
||||
}
|
||||
|
||||
public void markItemListAsRead() {
|
||||
FeedUtils.markFeedsRead(fs, null, null, this);
|
||||
Toast.makeText(this, R.string.toast_marked_stories_as_read, Toast.LENGTH_SHORT).show();
|
||||
|
@ -144,9 +134,6 @@ public abstract class ItemsList extends NbActivity implements StateChangedListen
|
|||
private void updateStatusIndicators() {
|
||||
boolean isLoading = NBSyncService.isFeedSetSyncing(this.fs);
|
||||
setProgressBarIndeterminateVisibility(isLoading);
|
||||
if (itemListFragment != null) {
|
||||
itemListFragment.setLoading(isLoading);
|
||||
}
|
||||
|
||||
if (overlayStatusText != null) {
|
||||
String syncStatus = NBSyncService.getSyncStatusMessage();
|
||||
|
@ -171,7 +158,6 @@ public abstract class ItemsList extends NbActivity implements StateChangedListen
|
|||
itemListFragment.resetEmptyState();
|
||||
itemListFragment.hasUpdated();
|
||||
itemListFragment.scrollToTop();
|
||||
getFirstStories();
|
||||
}
|
||||
|
||||
public abstract void updateStoryOrderPreference(StoryOrder newValue);
|
||||
|
@ -183,7 +169,6 @@ public abstract class ItemsList extends NbActivity implements StateChangedListen
|
|||
itemListFragment.resetEmptyState();
|
||||
itemListFragment.hasUpdated();
|
||||
itemListFragment.scrollToTop();
|
||||
getFirstStories();
|
||||
}
|
||||
|
||||
protected abstract void updateReadFilterPreference(ReadFilter newValue);
|
||||
|
|
|
@ -73,13 +73,12 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
|||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// clear the read-this-session flag from stories so they don't show up in the wrong place
|
||||
NBSyncService.clearPendingStoryRequest();
|
||||
NBSyncService.setActivationMode(NBSyncService.ActivationMode.ALL);
|
||||
FeedUtils.activateAllStories();
|
||||
FeedUtils.clearReadingSession();
|
||||
|
||||
updateStatusIndicators();
|
||||
// this view doesn't show stories, it is safe to alter stories
|
||||
FeedUtils.activateAllStories();
|
||||
NBSyncService.setActivationMode(NBSyncService.ActivationMode.ALL);
|
||||
triggerSync();
|
||||
|
||||
if (PrefsUtils.isLightThemeSelected(this) != isLightTheme) {
|
||||
|
|
|
@ -209,11 +209,6 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
|
|||
|
||||
// if this is the first time we've found a cursor, we know the onCreate chain is done
|
||||
if (this.pager == null) {
|
||||
int currentUnreadCount = getUnreadCount();
|
||||
if (currentUnreadCount > this.startingUnreadCount ) {
|
||||
this.startingUnreadCount = currentUnreadCount;
|
||||
}
|
||||
// set up the pager after the unread count, so the first mark-read doesn't happen too quickly
|
||||
setupPager();
|
||||
}
|
||||
|
||||
|
@ -328,6 +323,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
|
|||
@Override
|
||||
protected void handleUpdate(boolean freshData) {
|
||||
enableMainProgress(NBSyncService.isFeedSetSyncing(this.fs));
|
||||
updateOverlayNav();
|
||||
if (freshData) updateCursor();
|
||||
}
|
||||
|
||||
|
@ -430,19 +426,23 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
|
|||
}
|
||||
|
||||
/**
|
||||
* Update the left/right overlay UI after the read-state of a story changes or we navigate in any way.
|
||||
* Update the next/back overlay UI after the read-state of a story changes or we navigate in any way.
|
||||
*/
|
||||
private void updateOverlayNav() {
|
||||
int currentUnreadCount = getUnreadCount();
|
||||
if (currentUnreadCount > this.startingUnreadCount ) {
|
||||
this.startingUnreadCount = currentUnreadCount;
|
||||
}
|
||||
this.overlayLeft.setEnabled(this.getLastReadPosition(false) != -1);
|
||||
this.overlayRight.setText((getUnreadCount() > 0) ? R.string.overlay_next : R.string.overlay_done);
|
||||
this.overlayRight.setBackgroundResource((getUnreadCount() > 0) ? R.drawable.selector_overlay_bg_right : R.drawable.selector_overlay_bg_right_done);
|
||||
this.overlayRight.setText((currentUnreadCount > 0) ? R.string.overlay_next : R.string.overlay_done);
|
||||
this.overlayRight.setBackgroundResource((currentUnreadCount > 0) ? R.drawable.selector_overlay_bg_right : R.drawable.selector_overlay_bg_right_done);
|
||||
|
||||
if (this.startingUnreadCount == 0 ) {
|
||||
// sessions with no unreads just show a full progress bar
|
||||
this.overlayProgress.setMax(1);
|
||||
this.overlayProgress.setProgress(1);
|
||||
} else {
|
||||
int unreadProgress = this.startingUnreadCount - getUnreadCount();
|
||||
int unreadProgress = this.startingUnreadCount - currentUnreadCount;
|
||||
this.overlayProgress.setMax(this.startingUnreadCount);
|
||||
this.overlayProgress.setProgress(unreadProgress);
|
||||
}
|
||||
|
@ -488,13 +488,17 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
|
|||
* load is not needed and all latches are tripped.
|
||||
*/
|
||||
private void checkStoryCount(int position) {
|
||||
if (AppConstants.VERBOSE_LOG) {
|
||||
Log.d(this.getClass().getName(), String.format("story %d of %d selected, stopLoad: %b", position, stories.getCount(), stopLoading));
|
||||
}
|
||||
// if the pager is at or near the number of stories loaded, check for more unless we know we are at the end of the list
|
||||
if ((position + AppConstants.READING_STORY_PRELOAD) >= stories.getCount()) {
|
||||
if (stories == null ) {
|
||||
triggerRefresh(position + AppConstants.READING_STORY_PRELOAD);
|
||||
}
|
||||
} else {
|
||||
if (AppConstants.VERBOSE_LOG) {
|
||||
Log.d(this.getClass().getName(), String.format("story %d of %d selected, stopLoad: %b", position, stories.getCount(), stopLoading));
|
||||
}
|
||||
// if the pager is at or near the number of stories loaded, check for more unless we know we are at the end of the list
|
||||
if ((position + AppConstants.READING_STORY_PRELOAD) >= stories.getCount()) {
|
||||
triggerRefresh(position + AppConstants.READING_STORY_PRELOAD);
|
||||
}
|
||||
}
|
||||
|
||||
if (stopLoading) {
|
||||
// if we terminated because we are well and truly done, break any search loops and stop progress indication
|
||||
|
|
|
@ -153,8 +153,10 @@ public class FeedProvider extends ContentProvider {
|
|||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
|
||||
final SQLiteDatabase rdb = databaseHelper.getReadableDatabase();
|
||||
final LoggingDatabase db = new LoggingDatabase(rdb);
|
||||
final LoggingDatabase db;
|
||||
synchronized (BlurDatabaseHelper.RW_MUTEX) {
|
||||
db = new LoggingDatabase(databaseHelper.getReadableDatabase());
|
||||
}
|
||||
switch (uriMatcher.match(uri)) {
|
||||
|
||||
case USERS:
|
||||
|
|
|
@ -46,7 +46,6 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
|
|||
protected StoryItemsAdapter adapter;
|
||||
protected DefaultFeedView defaultFeedView;
|
||||
protected StateFilter currentState;
|
||||
private boolean isLoading = true;
|
||||
private boolean cursorSeenYet = false;
|
||||
private boolean firstStorySeenYet = false;
|
||||
|
||||
|
@ -83,17 +82,17 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
|
|||
}
|
||||
}
|
||||
|
||||
private void triggerRefresh(int desiredStoryCount, int totalSeen) {
|
||||
boolean gotSome = NBSyncService.requestMoreForFeed(getFeedSet(), desiredStoryCount, totalSeen);
|
||||
if (gotSome) triggerSync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the DB was cleared.
|
||||
*/
|
||||
public void resetEmptyState() {
|
||||
cursorSeenYet = false;
|
||||
firstStorySeenYet = false;
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
public void setLoading(boolean loading) {
|
||||
isLoading = loading;
|
||||
}
|
||||
|
||||
private void updateLoadingIndicator() {
|
||||
|
@ -107,6 +106,7 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
|
|||
}
|
||||
TextView emptyView = (TextView) itemList.getEmptyView();
|
||||
|
||||
boolean isLoading = NBSyncService.isFeedSetSyncing(getFeedSet());
|
||||
if (isLoading || (!cursorSeenYet)) {
|
||||
emptyView.setText(R.string.empty_list_view_loading);
|
||||
} else {
|
||||
|
@ -129,13 +129,9 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
|
|||
|
||||
@Override
|
||||
public synchronized void onScroll(AbsListView view, int firstVisible, int visibleCount, int totalCount) {
|
||||
// if we have seen a cursor, this method means the list was updated or scrolled. now is a good
|
||||
// time to see if we need more stories
|
||||
if (cursorSeenYet) {
|
||||
// load an extra page or two worth of stories past the viewport
|
||||
int desiredStoryCount = firstVisible + (visibleCount*2) + 1;
|
||||
activity.triggerRefresh(desiredStoryCount, totalCount);
|
||||
}
|
||||
// load an extra page or two worth of stories past the viewport
|
||||
int desiredStoryCount = firstVisible + (visibleCount*2) + 1;
|
||||
triggerRefresh(desiredStoryCount, totalCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -166,7 +162,7 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
|
|||
if (cursor != null) {
|
||||
cursorSeenYet = true;
|
||||
if (cursor.getCount() < 1) {
|
||||
activity.triggerRefresh(1, 0);
|
||||
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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.newsblur.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
|
@ -85,26 +86,28 @@ public class LoginProgressFragment extends Fragment {
|
|||
|
||||
@Override
|
||||
protected void onPostExecute(NewsBlurResponse result) {
|
||||
Context c = getActivity();
|
||||
if (c == null) return; // we might have run past the lifecycle of the activity
|
||||
if (!result.isError()) {
|
||||
final Animation a = AnimationUtils.loadAnimation(getActivity(), R.anim.text_down);
|
||||
final Animation a = AnimationUtils.loadAnimation(c, R.anim.text_down);
|
||||
updateStatus.setText(R.string.login_logged_in);
|
||||
loggingInProgress.setVisibility(View.GONE);
|
||||
updateStatus.startAnimation(a);
|
||||
|
||||
loginProfilePicture.setVisibility(View.VISIBLE);
|
||||
loginProfilePicture.setImageBitmap(UIUtils.roundCorners(PrefsUtils.getUserImage(getActivity()), 10f));
|
||||
loginProfilePicture.setImageBitmap(UIUtils.roundCorners(PrefsUtils.getUserImage(c), 10f));
|
||||
feedProgress.setVisibility(View.VISIBLE);
|
||||
|
||||
final Animation b = AnimationUtils.loadAnimation(getActivity(), R.anim.text_up);
|
||||
final Animation b = AnimationUtils.loadAnimation(c, R.anim.text_up);
|
||||
retrievingFeeds.setText(R.string.login_retrieving_feeds);
|
||||
retrievingFeeds.startAnimation(b);
|
||||
|
||||
Intent startMain = new Intent(getActivity(), Main.class);
|
||||
getActivity().startActivity(startMain);
|
||||
c.startActivity(startMain);
|
||||
|
||||
} else {
|
||||
UIUtils.safeToast(getActivity(), result.getErrorMessage(), Toast.LENGTH_LONG);
|
||||
startActivity(new Intent(getActivity(), Login.class));
|
||||
UIUtils.safeToast(c, result.getErrorMessage(), Toast.LENGTH_LONG);
|
||||
startActivity(new Intent(c, Login.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -602,57 +602,63 @@ public class ReadingItemFragment extends NbFragment implements ClassifierDialogF
|
|||
|
||||
@Override
|
||||
public void sharedCallback(String sharedText, boolean hasAlreadyBeenShared) {
|
||||
view.findViewById(R.id.reading_share_bar).setVisibility(View.VISIBLE);
|
||||
view.findViewById(R.id.share_bar_underline).setVisibility(View.VISIBLE);
|
||||
|
||||
if (!hasAlreadyBeenShared) {
|
||||
|
||||
if (!TextUtils.isEmpty(sharedText)) {
|
||||
View commentView = inflater.inflate(R.layout.include_comment, null);
|
||||
commentView.setTag(SetupCommentSectionTask.COMMENT_VIEW_BY + user.id);
|
||||
try {
|
||||
view.findViewById(R.id.reading_share_bar).setVisibility(View.VISIBLE);
|
||||
view.findViewById(R.id.share_bar_underline).setVisibility(View.VISIBLE);
|
||||
|
||||
if (!hasAlreadyBeenShared) {
|
||||
|
||||
if (!TextUtils.isEmpty(sharedText)) {
|
||||
View commentView = inflater.inflate(R.layout.include_comment, null);
|
||||
commentView.setTag(SetupCommentSectionTask.COMMENT_VIEW_BY + user.id);
|
||||
|
||||
TextView commentText = (TextView) commentView.findViewById(R.id.comment_text);
|
||||
commentText.setTag("commentBy" + user.id);
|
||||
TextView commentText = (TextView) commentView.findViewById(R.id.comment_text);
|
||||
commentText.setTag("commentBy" + user.id);
|
||||
commentText.setText(sharedText);
|
||||
|
||||
TextView commentLocation = (TextView) commentView.findViewById(R.id.comment_location);
|
||||
if (!TextUtils.isEmpty(user.location)) {
|
||||
commentLocation.setText(user.location.toUpperCase());
|
||||
} else {
|
||||
commentLocation.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (PrefsUtils.getUserImage(getActivity()) != null) {
|
||||
ImageView commentImage = (ImageView) commentView.findViewById(R.id.comment_user_image);
|
||||
commentImage.setImageBitmap(UIUtils.roundCorners(PrefsUtils.getUserImage(getActivity()), 10f));
|
||||
}
|
||||
|
||||
TextView commentSharedDate = (TextView) commentView.findViewById(R.id.comment_shareddate);
|
||||
commentSharedDate.setText(R.string.now);
|
||||
|
||||
TextView commentUsername = (TextView) commentView.findViewById(R.id.comment_username);
|
||||
commentUsername.setText(user.username);
|
||||
|
||||
((LinearLayout) view.findViewById(R.id.reading_friend_comment_container)).addView(commentView);
|
||||
|
||||
ViewUtils.setupCommentCount(getActivity(), view, story.commentCount + 1);
|
||||
|
||||
final ImageView image = ViewUtils.createSharebarImage(getActivity(), imageLoader, user.photoUrl, user.id);
|
||||
((FlowLayout) view.findViewById(R.id.reading_social_commentimages)).addView(image);
|
||||
|
||||
} else {
|
||||
ViewUtils.setupShareCount(getActivity(), view, story.sharedUserIds.length + 1);
|
||||
final ImageView image = ViewUtils.createSharebarImage(getActivity(), imageLoader, user.photoUrl, user.id);
|
||||
((FlowLayout) view.findViewById(R.id.reading_social_shareimages)).addView(image);
|
||||
}
|
||||
} else {
|
||||
View commentViewForUser = view.findViewWithTag(SetupCommentSectionTask.COMMENT_VIEW_BY + user.id);
|
||||
TextView commentText = (TextView) view.findViewWithTag(SetupCommentSectionTask.COMMENT_BY + user.id);
|
||||
commentText.setText(sharedText);
|
||||
|
||||
TextView commentLocation = (TextView) commentView.findViewById(R.id.comment_location);
|
||||
if (!TextUtils.isEmpty(user.location)) {
|
||||
commentLocation.setText(user.location.toUpperCase());
|
||||
} else {
|
||||
commentLocation.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (PrefsUtils.getUserImage(getActivity()) != null) {
|
||||
ImageView commentImage = (ImageView) commentView.findViewById(R.id.comment_user_image);
|
||||
commentImage.setImageBitmap(UIUtils.roundCorners(PrefsUtils.getUserImage(getActivity()), 10f));
|
||||
}
|
||||
|
||||
TextView commentSharedDate = (TextView) commentView.findViewById(R.id.comment_shareddate);
|
||||
commentSharedDate.setText(R.string.now);
|
||||
|
||||
TextView commentUsername = (TextView) commentView.findViewById(R.id.comment_username);
|
||||
commentUsername.setText(user.username);
|
||||
|
||||
((LinearLayout) view.findViewById(R.id.reading_friend_comment_container)).addView(commentView);
|
||||
|
||||
ViewUtils.setupCommentCount(getActivity(), view, story.commentCount + 1);
|
||||
|
||||
final ImageView image = ViewUtils.createSharebarImage(getActivity(), imageLoader, user.photoUrl, user.id);
|
||||
((FlowLayout) view.findViewById(R.id.reading_social_commentimages)).addView(image);
|
||||
|
||||
} else {
|
||||
ViewUtils.setupShareCount(getActivity(), view, story.sharedUserIds.length + 1);
|
||||
final ImageView image = ViewUtils.createSharebarImage(getActivity(), imageLoader, user.photoUrl, user.id);
|
||||
((FlowLayout) view.findViewById(R.id.reading_social_shareimages)).addView(image);
|
||||
}
|
||||
} else {
|
||||
View commentViewForUser = view.findViewWithTag(SetupCommentSectionTask.COMMENT_VIEW_BY + user.id);
|
||||
TextView commentText = (TextView) view.findViewWithTag(SetupCommentSectionTask.COMMENT_BY + user.id);
|
||||
commentText.setText(sharedText);
|
||||
|
||||
TextView commentDateText = (TextView) view.findViewWithTag(SetupCommentSectionTask.COMMENT_DATE_BY + user.id);
|
||||
commentDateText.setText(R.string.now);
|
||||
}
|
||||
TextView commentDateText = (TextView) view.findViewWithTag(SetupCommentSectionTask.COMMENT_DATE_BY + user.id);
|
||||
commentDateText.setText(R.string.now);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// this entire method does not respect context state and can be triggered on stale fragments. it should
|
||||
// be replaced with a proper Loader, or it will always risk crashing the application
|
||||
Log.w(this.getClass().getName(), "async error in callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -39,7 +39,8 @@ public class ImagePrefetchService extends SubService {
|
|||
|
||||
gotWork();
|
||||
|
||||
while (ImageQueue.size() > 0) {
|
||||
while ((ImageQueue.size() > 0) && PrefsUtils.isImagePrefetchEnabled(parent)) {
|
||||
startExpensiveCycle();
|
||||
Set<String> fetchedImages = new HashSet<String>();
|
||||
Set<String> batch = new HashSet<String>(AppConstants.IMAGE_PREFETCH_BATCH_SIZE);
|
||||
batchloop: for (String url : ImageQueue) {
|
||||
|
|
|
@ -67,6 +67,7 @@ public class NBSyncService extends Service {
|
|||
public enum ActivationMode { ALL, OLDER, NEWER };
|
||||
|
||||
private static final Object WAKELOCK_MUTEX = new Object();
|
||||
private static final Object PENDING_FEED_MUTEX = new Object();
|
||||
|
||||
private volatile static boolean ActionsRunning = false;
|
||||
private volatile static boolean CleanupRunning = false;
|
||||
|
@ -87,9 +88,10 @@ public class NBSyncService extends Service {
|
|||
private static long lastFeedCount = 0L;
|
||||
private static long lastFFWriteMillis = 0L;
|
||||
|
||||
/** Feed sets that we need to sync and how many stories the UI wants for them. */
|
||||
private static Map<FeedSet,Integer> PendingFeeds;
|
||||
static { PendingFeeds = new HashMap<FeedSet,Integer>(); }
|
||||
/** Feed set that we need to sync immediately for the UI. */
|
||||
private static FeedSet PendingFeed;
|
||||
private static Integer PendingFeedTarget = 0;
|
||||
|
||||
/** Feed sets that the API has said to have no more pages left. */
|
||||
private static Set<FeedSet> ExhaustedFeeds;
|
||||
static { ExhaustedFeeds = new HashSet<FeedSet>(); }
|
||||
|
@ -195,10 +197,14 @@ public class NBSyncService extends Service {
|
|||
syncActions();
|
||||
|
||||
// these requests are expressly enqueued by the UI/user, do them next
|
||||
syncPendingFeeds();
|
||||
syncPendingFeedStories();
|
||||
|
||||
syncMetadata(startId);
|
||||
|
||||
unreadsService.start(startId);
|
||||
|
||||
imagePrefetchService.start(startId);
|
||||
|
||||
finishActions();
|
||||
|
||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "finishing primary sync");
|
||||
|
@ -285,6 +291,7 @@ public class NBSyncService extends Service {
|
|||
ra.doLocal(dbHelper);
|
||||
}
|
||||
FollowupActions.clear();
|
||||
NbActivity.updateAllActivities(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -404,6 +411,7 @@ public class NBSyncService extends Service {
|
|||
lastFeedCount = feedValues.size();
|
||||
|
||||
unreadsService.start(startId);
|
||||
UnreadsService.doMetadata();
|
||||
|
||||
} finally {
|
||||
FFSyncRunning = false;
|
||||
|
@ -415,60 +423,73 @@ public class NBSyncService extends Service {
|
|||
/**
|
||||
* Fetch stories needed because the user is actively viewing a feed or folder.
|
||||
*/
|
||||
private void syncPendingFeeds() {
|
||||
private void syncPendingFeedStories() {
|
||||
FeedSet fs = PendingFeed;
|
||||
boolean finished = false;
|
||||
if (fs == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Set<FeedSet> handledFeeds = new HashSet<FeedSet>();
|
||||
feedloop: for (FeedSet fs : PendingFeeds.keySet()) {
|
||||
if (ExhaustedFeeds.contains(fs)) {
|
||||
Log.i(this.getClass().getName(), "No more stories for feed set: " + fs);
|
||||
finished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FeedPagesSeen.containsKey(fs)) {
|
||||
FeedPagesSeen.put(fs, 0);
|
||||
FeedStoriesSeen.put(fs, 0);
|
||||
}
|
||||
int pageNumber = FeedPagesSeen.get(fs);
|
||||
int totalStoriesSeen = FeedStoriesSeen.get(fs);
|
||||
|
||||
if (ExhaustedFeeds.contains(fs)) {
|
||||
Log.i(this.getClass().getName(), "No more stories for feed set: " + fs);
|
||||
handledFeeds.add(fs);
|
||||
continue feedloop;
|
||||
StoryOrder order = PrefsUtils.getStoryOrder(this, fs);
|
||||
ReadFilter filter = PrefsUtils.getReadFilter(this, fs);
|
||||
|
||||
while (totalStoriesSeen < PendingFeedTarget) {
|
||||
if (stopSync()) return;
|
||||
|
||||
if (!fs.equals(PendingFeed)) {
|
||||
// the active view has changed
|
||||
if (fs == null) finished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
StorySyncRunning = true;
|
||||
NbActivity.updateAllActivities(false);
|
||||
|
||||
if (!FeedPagesSeen.containsKey(fs)) {
|
||||
FeedPagesSeen.put(fs, 0);
|
||||
FeedStoriesSeen.put(fs, 0);
|
||||
|
||||
pageNumber++;
|
||||
StoriesResponse apiResponse = apiManager.getStories(fs, pageNumber, order, filter);
|
||||
|
||||
if (! isStoryResponseGood(apiResponse)) return;
|
||||
|
||||
// if any reading activities happened during the API call, the result is now stale.
|
||||
// discard it and start again
|
||||
if (dbHelper.getActions(false).getCount() > 0) return;
|
||||
|
||||
FeedPagesSeen.put(fs, pageNumber);
|
||||
totalStoriesSeen += apiResponse.stories.length;
|
||||
FeedStoriesSeen.put(fs, totalStoriesSeen);
|
||||
|
||||
insertStories(apiResponse);
|
||||
NbActivity.updateAllActivities(true);
|
||||
|
||||
if (apiResponse.stories.length == 0) {
|
||||
ExhaustedFeeds.add(fs);
|
||||
finished = true;
|
||||
return;
|
||||
}
|
||||
int pageNumber = FeedPagesSeen.get(fs);
|
||||
int totalStoriesSeen = FeedStoriesSeen.get(fs);
|
||||
|
||||
StoryOrder order = PrefsUtils.getStoryOrder(this, fs);
|
||||
ReadFilter filter = PrefsUtils.getReadFilter(this, fs);
|
||||
|
||||
pageloop: while (totalStoriesSeen < PendingFeeds.get(fs)) {
|
||||
if (stopSync()) return;
|
||||
|
||||
pageNumber++;
|
||||
StoriesResponse apiResponse = apiManager.getStories(fs, pageNumber, order, filter);
|
||||
|
||||
if (! isStoryResponseGood(apiResponse)) break feedloop;
|
||||
|
||||
FeedPagesSeen.put(fs, pageNumber);
|
||||
totalStoriesSeen += apiResponse.stories.length;
|
||||
FeedStoriesSeen.put(fs, totalStoriesSeen);
|
||||
|
||||
insertStories(apiResponse);
|
||||
NbActivity.updateAllActivities(true);
|
||||
|
||||
if (apiResponse.stories.length == 0) {
|
||||
ExhaustedFeeds.add(fs);
|
||||
break pageloop;
|
||||
}
|
||||
}
|
||||
|
||||
handledFeeds.add(fs);
|
||||
}
|
||||
finished = true;
|
||||
|
||||
PendingFeeds.keySet().removeAll(handledFeeds);
|
||||
} finally {
|
||||
if (StorySyncRunning) {
|
||||
StorySyncRunning = false;
|
||||
NbActivity.updateAllActivities(false);
|
||||
}
|
||||
synchronized (PENDING_FEED_MUTEX) {
|
||||
if (finished && fs.equals(PendingFeed)) PendingFeed = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -496,7 +517,10 @@ public class NBSyncService extends Service {
|
|||
|
||||
void decrementRunningChild(int startId) {
|
||||
synchronized (WAKELOCK_MUTEX) {
|
||||
if (wl != null) wl.release();
|
||||
if (wl == null) return;
|
||||
if (wl.isHeld()) {
|
||||
wl.release();
|
||||
}
|
||||
// our wakelock reference counts. only stop the service if it is in the background and if
|
||||
// we are the last thread to release the lock.
|
||||
if (!wl.isHeld()) {
|
||||
|
@ -540,7 +564,7 @@ public class NBSyncService extends Service {
|
|||
* Is there a sync for a given FeedSet running?
|
||||
*/
|
||||
public static boolean isFeedSetSyncing(FeedSet fs) {
|
||||
return (PendingFeeds.containsKey(fs) && StorySyncRunning);
|
||||
return (fs.equals(PendingFeed) && StorySyncRunning);
|
||||
}
|
||||
|
||||
public static String getSyncStatusMessage() {
|
||||
|
@ -589,31 +613,39 @@ public class NBSyncService extends Service {
|
|||
return false;
|
||||
}
|
||||
|
||||
synchronized (PendingFeeds) {
|
||||
synchronized (PENDING_FEED_MUTEX) {
|
||||
Integer alreadyPending = 0;
|
||||
if (fs.equals(PendingFeed)) alreadyPending = PendingFeedTarget;
|
||||
Integer alreadySeen = FeedStoriesSeen.get(fs);
|
||||
Integer alreadyRequested = PendingFeeds.get(fs);
|
||||
if (alreadySeen == null) alreadySeen = 0;
|
||||
if (alreadyRequested == null) alreadyRequested = 0;
|
||||
if ((callerSeen >= 0) && (alreadySeen > callerSeen)) {
|
||||
if (callerSeen < alreadySeen) {
|
||||
// the caller is probably filtering and thinks they have fewer than we do, so
|
||||
// update our count to agree with them, and force-allow another requet
|
||||
alreadySeen = callerSeen;
|
||||
FeedStoriesSeen.put(fs, callerSeen);
|
||||
alreadyRequested = 0;
|
||||
alreadyPending = 0;
|
||||
}
|
||||
|
||||
if (AppConstants.VERBOSE_LOG) Log.d(NBSyncService.class.getName(), "have:" + alreadySeen + " want:" + desiredStoryCount + " pending:" + alreadyPending);
|
||||
if (desiredStoryCount <= alreadySeen) {
|
||||
return false;
|
||||
}
|
||||
if (AppConstants.VERBOSE_LOG) Log.d(NBSyncService.class.getName(), "have:" + alreadySeen + " want:" + desiredStoryCount + " requested:" + alreadyRequested);
|
||||
if (desiredStoryCount <= alreadyRequested) {
|
||||
if (desiredStoryCount <= alreadyPending) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PendingFeeds.put(fs, desiredStoryCount);
|
||||
PendingFeed = fs;
|
||||
PendingFeedTarget = desiredStoryCount;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void clearPendingStoryRequest() {
|
||||
synchronized (PENDING_FEED_MUTEX) {
|
||||
PendingFeed = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetFeeds() {
|
||||
ExhaustedFeeds.clear();
|
||||
FeedPagesSeen.clear();
|
||||
|
|
|
@ -21,6 +21,7 @@ public abstract class SubService {
|
|||
protected NBSyncService parent;
|
||||
private ExecutorService executor;
|
||||
protected int startId;
|
||||
private long cycleStartTime = 0L;
|
||||
|
||||
private SubService() {
|
||||
; // no default construction
|
||||
|
@ -50,9 +51,10 @@ public abstract class SubService {
|
|||
|
||||
private synchronized void exec_() {
|
||||
try {
|
||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService started");
|
||||
//if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService started");
|
||||
exec();
|
||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService completed");
|
||||
//if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService completed");
|
||||
cycleStartTime = 0;
|
||||
} catch (Exception e) {
|
||||
Log.e(this.getClass().getName(), "Sync error.", e);
|
||||
} finally {
|
||||
|
@ -90,5 +92,36 @@ public abstract class SubService {
|
|||
protected abstract void setRunning(boolean running);
|
||||
protected abstract boolean isRunning();
|
||||
|
||||
/**
|
||||
* If called at the beginning of an expensive loop in a SubService, enforces the maximum duty cycle
|
||||
* defined in AppConstants by sleeping for a short while so the SubService does not dominate system
|
||||
* resources.
|
||||
*/
|
||||
protected void startExpensiveCycle() {
|
||||
if (cycleStartTime == 0) {
|
||||
cycleStartTime = System.nanoTime();
|
||||
return;
|
||||
}
|
||||
|
||||
double lastCycleTime = (System.nanoTime() - cycleStartTime);
|
||||
if (lastCycleTime < 1) return;
|
||||
|
||||
cycleStartTime = System.nanoTime();
|
||||
|
||||
double cooloffTime = lastCycleTime * (1.0 - AppConstants.MAX_BG_DUTY_CYCLE);
|
||||
if (cooloffTime < 1) return;
|
||||
long cooloffTimeMs = Math.round(cooloffTime / 1000000.0);
|
||||
if (cooloffTimeMs > AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS) cooloffTimeMs = AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS;
|
||||
|
||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "Sleeping for : " + cooloffTimeMs + "ms to enforce max duty cycle.");
|
||||
try {
|
||||
Thread.sleep(cooloffTimeMs);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ public class UnreadsService extends SubService {
|
|||
|
||||
private static volatile boolean Running = false;
|
||||
|
||||
private static volatile boolean doMetadata = false;
|
||||
|
||||
/** Unread story hashes the API listed that we do not appear to have locally yet. */
|
||||
private static List<String> StoryHashQueue;
|
||||
static { StoryHashQueue = new ArrayList<String>(); }
|
||||
|
@ -37,12 +39,14 @@ public class UnreadsService extends SubService {
|
|||
// only use the unread status API if the user is premium
|
||||
if (parent.isPremium != Boolean.TRUE) return;
|
||||
|
||||
gotWork();
|
||||
syncUnreadList();
|
||||
if (doMetadata) {
|
||||
gotWork();
|
||||
syncUnreadList();
|
||||
doMetadata = false;
|
||||
}
|
||||
|
||||
if (StoryHashQueue.size() < 1) return;
|
||||
|
||||
gotWork();
|
||||
getNewUnreadStories();
|
||||
}
|
||||
|
||||
|
@ -94,7 +98,9 @@ public class UnreadsService extends SubService {
|
|||
private void getNewUnreadStories() {
|
||||
unreadsyncloop: while (StoryHashQueue.size() > 0) {
|
||||
if (parent.stopSync()) return;
|
||||
if(!PrefsUtils.isOfflineEnabled(parent)) return;
|
||||
gotWork();
|
||||
startExpensiveCycle();
|
||||
|
||||
List<String> hashBatch = new ArrayList(AppConstants.UNREAD_FETCH_BATCH_SIZE);
|
||||
batchloop: for (String hash : StoryHashQueue) {
|
||||
|
@ -155,6 +161,10 @@ public class UnreadsService extends SubService {
|
|||
}
|
||||
}
|
||||
|
||||
public static void doMetadata() {
|
||||
doMetadata = true;
|
||||
}
|
||||
|
||||
public static boolean running() {
|
||||
return Running;
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ public class AppConstants {
|
|||
public static final int MAX_FEED_LIST_SIZE = 250;
|
||||
|
||||
// when reading stories, how many stories worth of buffer to keep loaded ahead of the user
|
||||
public static final int READING_STORY_PRELOAD = 5;
|
||||
public static final int READING_STORY_PRELOAD = 10;
|
||||
|
||||
// max old stories to keep in the DB per feed before fetching new unreads
|
||||
public static final int MAX_READ_STORIES_STORED = 500;
|
||||
|
@ -70,4 +70,11 @@ public class AppConstants {
|
|||
// moot to tune.
|
||||
public final static long SHUTDOWN_SLACK_SECONDS = 60L;
|
||||
|
||||
// the maximum duty cycle for expensive background tasks. Tune to <1.0 to force sync loops
|
||||
// to pause periodically and not peg the network/CPU
|
||||
public final static double MAX_BG_DUTY_CYCLE = 0.9;
|
||||
|
||||
// cap duty cycle backoffs to prevent unnecessarily large backoffs
|
||||
public final static long DUTY_CYCLE_BACKOFF_CAP_MILLIS = 5L * 1000L;
|
||||
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ public class PrefsUtils {
|
|||
Log.w(PrefsUtils.class.getName(), "could not determine app version");
|
||||
return false;
|
||||
}
|
||||
Log.i(PrefsUtils.class.getName(), "launching version: " + version);
|
||||
if (AppConstants.VERBOSE_LOG) Log.i(PrefsUtils.class.getName(), "launching version: " + version);
|
||||
|
||||
String oldVersion = prefs.getString(AppConstants.LAST_APP_VERSION, null);
|
||||
if ( (oldVersion == null) || (!oldVersion.equals(version)) ) {
|
||||
|
|
Loading…
Add table
Reference in a new issue