Merge branch 'master' into tagging

# By ojiikun (6) and Samuel Clay (3)
* master:
  Adding ellipses in webkit.
  Adding downtime handling to readme.
  Putting a max on sporadic checks on broken feeds to 8x normal amount.
  Complete styling of overlay buttons.
  Placeholder styling of overlay buttons.
  Enable/disabe state for overlay buttons.
  Make overlay buttons fuctional.
  Basic overlay button UI.
  Fix crash on marking all stories read (#382)
This commit is contained in:
Samuel Clay 2013-08-15 15:20:14 -07:00
commit 2b5c625c48
26 changed files with 253 additions and 36 deletions

View file

@ -240,6 +240,77 @@ reader, and feed importer. To run the test suite:
./manage.py test --settings=utils.test-settings
## In Case of Downtime
You got the downtime message either through email or SMS. This is the order of operations for determining what's wrong.
0. Ensure you have `secrets-newsblur/configs/hosts` installed in your `/etc/hosts` so server hostnames
work.
1. Check www.newsblur.com to confirm it's down.
If you don't get a 502 page, then NewsBlur isn't even reachable and you just need to contact the
hosting provider and yell at them.
2. Check [Sentry](https://app.getsentry.com/newsblur/app/) and see if the answer is at the top of the
list.
This will show if a database (redis, mongo, postgres) can't be found.
3. Check the various databases:
a. If Redis server (db_redis, db_redis_story, db_redis_pubsub) can't connect, redis is probably down.
SSH into the offending server (or just check both the `db_redis` and `db_redis_story` servers) and
check if `redis` is running. You can often `tail -f -n 100 /var/log/redis.log` to find out if
background saving was being SIG(TERM|INT)'ed. When redis goes down, it's always because it's
consuming too much memory. That shouldn't happen, so check the [munin
graphs](http://db_redis/munin/).
Boot it with `sudo /etc/init.d/redis start`.
b. If mongo (db_mongo) can't connect, mongo is probably down.
This is rare and usually signifies hardware failure. SSH into `db_mongo` and check logs with `tail
-f -n 100 /var/log/mongodb/mongodb.log`. Start mongo with `sudo /etc/init.d/mongodb start` then
promote the next largest mongodb server. You want to then promote one of the secondaries to
primary, kill the offending primary machine, and rebuild it (preferably at a higher size). I
recommend waiting a day to rebuild it so that you get a different machine. Don't forget to lodge a
support ticket with the hosting provider so they know to check the machine.
If it's the db_mongo_analytics machine, there is no backup nor secondaries of the data (because
it's ephemeral and used for, you guessed it, analytics). You can easily provision a new mongodb
server and point to that machine.
c. If postgresql (db_pgsql) can't connect, postgres is probably down.
This is the rarest of the rare and has in fact never happened. Machine failure. If you can salvage
the db data, move it to another machine. Worst case you have nightly backups in S3. The fabfile.py
has commands to assist in restoring from backup (the backup file just needs to be local).
4. Point to a new/different machine
a. Confirm the IP address of the new machine with `fab list_do`.
b. Change `secrets-newsbur/config/hosts` to reflect the new machine.
c. Copy the new `hosts` file to all machines with:
```
fab all setup_hosts
fab ec2task setup_hosts
```
d. Changes should be instant, but you can also bounce every machine with:
```
fab web deploy:fast=True # fast=True just kill -9's processes.
fab task celery
fab ec2task celery
```
e. Monitor tlnb.py and tlnbt.py for lots of reading and feed fetching.
## Author
* Created by [Samuel Clay](http://www.samuelclay.com).

View file

@ -1392,8 +1392,7 @@ class Feed(models.Model):
total = total * 12
# 3 day max
if total > 60*24*3:
total = 60*24*3
total = min(total, 60*24*2)
if verbose:
logging.debug(" ---> [%-30s] Fetched every %s min - Subs: %s/%s/%s Stories: %s" % (
@ -1411,6 +1410,7 @@ class Feed(models.Model):
if error_count:
total = total * error_count
total = min(total, 60*24*7)
if verbose:
logging.debug(' ---> [%-30s] ~FBScheduling feed fetch geometrically: '
'~SB%s errors. Time: %s min' % (

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_pressed="true" android:drawable="@drawable/overlay_left_pressed" />
<item android:state_enabled="false" android:drawable="@drawable/overlay_left_disabled" />
<item android:state_enabled="true" android:drawable="@drawable/overlay_left_enabled" />
</selector>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_pressed="true" android:drawable="@drawable/overlay_right_pressed" />
<item android:state_enabled="false" android:drawable="@drawable/overlay_right_disabled" />
<item android:state_enabled="true" android:drawable="@drawable/overlay_right_enabled" />
</selector>

View file

@ -14,4 +14,35 @@
android:layout_height="1dip"
android:layout_alignParentTop="true" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp"
android:layout_marginRight="8dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true" >
<Button
android:id="@+id/reading_overlay_left"
android:layout_width="52dp"
android:layout_height="41dp"
android:background="@drawable/selector_overlay_bg_left"
android:textSize="14sp"
android:padding="6dp"
android:onClick="overlayLeft" />
<Button
android:id="@+id/reading_overlay_right"
android:layout_width="125dp"
android:layout_height="41dp"
android:background="@drawable/selector_overlay_bg_right"
android:text="@string/overlay_next"
android:textColor="@color/half_darkgray"
android:textSize="14sp"
android:padding="6dp"
android:onClick="overlayRight" />
</LinearLayout>
</RelativeLayout>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<com.newsblur.view.NonfocusScrollview xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/reading_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/item_background" >
@ -41,4 +42,4 @@
</LinearLayout>
</com.newsblur.view.NonfocusScrollview>
</com.newsblur.view.NonfocusScrollview>

View file

@ -46,7 +46,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp" >
android:layout_marginRight="20dp"
android:layout_marginBottom="50dp" >
<LinearLayout
android:id="@+id/reading_friend_comment_container"

View file

@ -58,6 +58,9 @@
<string name="save_this">SAVE THIS STORY</string>
<string name="share_this">SHARE THIS STORY</string>
<string name="overlay_next">NEXT</string>
<string name="overlay_done">DONE</string>
<string name="reply_to">Reply to \"%s\"</string>
<string name="alert_dialog_ok">Okay</string>

View file

@ -17,7 +17,6 @@ import com.newsblur.util.StoryOrder;
public class AllSharedStoriesReading extends Reading {
private Cursor stories;
private int currentPage;
private boolean requestingPage = false;
private boolean stopLoading = false;
@ -80,11 +79,8 @@ public class AllSharedStoriesReading extends Reading {
@Override
public void updateAfterSync() {
setSupportProgressBarIndeterminateVisibility(false);
stories.requery();
readingAdapter.notifyDataSetChanged();
checkStoryCount(pager.getCurrentItem());
requestingPage = false;
super.updateAfterSync();
}
@Override

View file

@ -17,7 +17,6 @@ import com.newsblur.util.StoryOrder;
public class AllStoriesReading extends Reading {
private Cursor stories;
private int currentPage;
private ArrayList<String> feedIds;
private boolean stopLoading = false;
@ -81,11 +80,8 @@ public class AllStoriesReading extends Reading {
@Override
public void updateAfterSync() {
setSupportProgressBarIndeterminateVisibility(false);
stories.requery();
requestedPage = false;
readingAdapter.notifyDataSetChanged();
checkStoryCount(pager.getCurrentItem());
super.updateAfterSync();
}
@Override

View file

@ -69,11 +69,8 @@ public class FeedReading extends Reading {
@Override
public void updateAfterSync() {
setSupportProgressBarIndeterminateVisibility(false);
stories.requery();
requestedPage = false;
readingAdapter.notifyDataSetChanged();
checkStoryCount(pager.getCurrentItem());
super.updateAfterSync();
}
@Override

View file

@ -68,11 +68,8 @@ public class FolderReading extends Reading {
@Override
public void updateAfterSync() {
setSupportProgressBarIndeterminateVisibility(false);
stories.requery();
requestedPage = false;
readingAdapter.notifyDataSetChanged();
checkStoryCount(pager.getCurrentItem());
super.updateAfterSync();
}
@Override

View file

@ -12,6 +12,9 @@ import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
@ -34,8 +37,9 @@ import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ReadFilter;
import com.newsblur.util.StoryOrder;
import com.newsblur.util.UIUtils;
import com.newsblur.view.NonfocusScrollview.ScrollChangeListener;
public abstract class Reading extends NbFragmentActivity implements OnPageChangeListener, SyncUpdateFragment.SyncUpdateFragmentInterface, OnSeekBarChangeListener {
public abstract class Reading extends NbFragmentActivity implements OnPageChangeListener, SyncUpdateFragment.SyncUpdateFragmentInterface, OnSeekBarChangeListener, ScrollChangeListener {
public static final String EXTRA_FEED = "feed_selected";
public static final String EXTRA_POSITION = "feed_position";
@ -45,10 +49,14 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
public static final String EXTRA_FEED_IDS = "feed_ids";
private static final String TEXT_SIZE = "textsize";
private static final int OVERLAY_RANGE_TOP_DP = 50;
private static final int OVERLAY_RANGE_BOT_DP = 60;
protected int passedPosition;
protected int currentState;
protected ViewPager pager;
protected Button overlayLeft, overlayRight;
protected FragmentManager fragmentManager;
protected ReadingAdapter readingAdapter;
protected ContentResolver contentResolver;
@ -58,12 +66,18 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
private Set<Story> storiesToMarkAsRead;
private float overlayRangeTopPx;
private float overlayRangeBotPx;
@Override
protected void onCreate(Bundle savedInstanceBundle) {
requestWindowFeature(Window.FEATURE_PROGRESS);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(savedInstanceBundle);
setContentView(R.layout.activity_reading);
this.overlayLeft = (Button) findViewById(R.id.reading_overlay_left);
this.overlayRight = (Button) findViewById(R.id.reading_overlay_right);
fragmentManager = getSupportFragmentManager();
@ -76,6 +90,10 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
this.apiManager = new APIManager(this);
// this value is expensive to compute but doesn't change during a single runtime
this.overlayRangeTopPx = (float) UIUtils.convertDPsToPixels(this, OVERLAY_RANGE_TOP_DP);
this.overlayRangeBotPx = (float) UIUtils.convertDPsToPixels(this, OVERLAY_RANGE_BOT_DP);
}
protected void setupPager() {
@ -93,6 +111,7 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
pager.setAdapter(readingAdapter);
pager.setCurrentItem(passedPosition);
readingAdapter.setCurrentItem(passedPosition);
this.enableOverlays();
}
@Override
@ -151,6 +170,8 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
}
}
// interface OnPageChangeListener
@Override
public void onPageScrollStateChanged(int arg0) {
}
@ -160,15 +181,49 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
}
@Override
public void onPageSelected(final int position) {
public void onPageSelected(int position) {
this.setOverlayAlpha(1.0f);
this.enableOverlays();
}
// interface ScrollChangeListener
@Override
public void scrollChanged(int hPos, int vPos, int currentWidth, int currentHeight) {
int scrollMax = currentHeight - findViewById(android.R.id.content).getMeasuredHeight();
int posFromBot = (scrollMax - vPos);
float newAlpha = 0.0f;
if (vPos < this.overlayRangeTopPx) {
float delta = this.overlayRangeTopPx - ((float) vPos);
newAlpha = delta / this.overlayRangeTopPx;
} else if (posFromBot < this.overlayRangeBotPx) {
float delta = this.overlayRangeBotPx - ((float) posFromBot);
newAlpha = delta / this.overlayRangeBotPx;
}
this.setOverlayAlpha(newAlpha);
}
private void setOverlayAlpha(float a) {
UIUtils.setViewAlpha(this.overlayLeft, a);
UIUtils.setViewAlpha(this.overlayRight, a);
}
private void enableOverlays() {
int page = this.pager.getCurrentItem();
this.overlayLeft.setEnabled(page > 0);
this.overlayRight.setEnabled(page < (this.readingAdapter.getCount()-1));
this.overlayRight.setText((page < (this.readingAdapter.getCount()-1)) ? R.string.overlay_next : R.string.overlay_done);
}
@Override
public void updateAfterSync() {
setSupportProgressBarIndeterminateVisibility(false);
stories.requery();
readingAdapter.notifyDataSetChanged();
checkStoryCount(pager.getCurrentItem());
this.enableOverlays();
}
@Override
@ -176,6 +231,7 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
stories.requery();
readingAdapter.notifyDataSetChanged();
checkStoryCount(pager.getCurrentItem());
this.enableOverlays();
}
public abstract void checkStoryCount(int position);
@ -246,7 +302,12 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
public void onStopTrackingTouch(SeekBar seekBar) {
}
public void overlayRight(View v) {
pager.setCurrentItem(pager.getCurrentItem()+1, true);
}
public void overlayLeft(View v) {
pager.setCurrentItem(pager.getCurrentItem()-1, true);
}
}

View file

@ -12,7 +12,6 @@ import com.newsblur.service.SyncService;
public class SavedStoriesReading extends Reading {
private Cursor stories;
private int currentPage;
private boolean stopLoading = false;
private boolean requestedPage = false;
@ -66,11 +65,8 @@ public class SavedStoriesReading extends Reading {
@Override
public void updateAfterSync() {
setSupportProgressBarIndeterminateVisibility(false);
stories.requery();
requestedPage = false;
readingAdapter.notifyDataSetChanged();
checkStoryCount(pager.getCurrentItem());
super.updateAfterSync();
}
@Override

View file

@ -153,7 +153,7 @@ public class DatabaseConstants {
private static final String SOCIAL_INTELLIGENCE_BEST = " (" + DatabaseConstants.SOCIAL_FEED_POSITIVE_COUNT + ") > 0 ";
private static final String SUM_STORY_TOTAL = "storyTotal";
public static final String FEED_FILTER_FOCUS = " WHERE " + FEED_TABLE + "." + FEED_POSITIVE_COUNT + " > 0 ";
public static final String FEED_FILTER_FOCUS = FEED_TABLE + "." + FEED_POSITIVE_COUNT + " > 0 ";
private static String STORY_SUM_TOTAL = " CASE " +
"WHEN MAX(" + DatabaseConstants.STORY_INTELLIGENCE_AUTHORS + "," + DatabaseConstants.STORY_INTELLIGENCE_TAGS + "," + DatabaseConstants.STORY_INTELLIGENCE_TITLE + ") > 0 " +
@ -163,9 +163,9 @@ public class DatabaseConstants {
"ELSE " + DatabaseConstants.STORY_INTELLIGENCE_FEED + " " +
"END AS " + DatabaseConstants.SUM_STORY_TOTAL;
public static final String STORY_INTELLIGENCE_BEST = DatabaseConstants.SUM_STORY_TOTAL + " > 0 ";
public static final String STORY_INTELLIGENCE_SOME = DatabaseConstants.SUM_STORY_TOTAL + " >= 0 ";
public static final String STORY_INTELLIGENCE_ALL = DatabaseConstants.SUM_STORY_TOTAL + " >= 0 ";
private static final String STORY_INTELLIGENCE_BEST = DatabaseConstants.SUM_STORY_TOTAL + " > 0 ";
private static final String STORY_INTELLIGENCE_SOME = DatabaseConstants.SUM_STORY_TOTAL + " >= 0 ";
private static final String STORY_INTELLIGENCE_ALL = DatabaseConstants.SUM_STORY_TOTAL + " >= 0 ";
public static final String[] STORY_COLUMNS = {
STORY_AUTHORS, STORY_COMMENT_COUNT, STORY_CONTENT, STORY_DATE, 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,

View file

@ -355,7 +355,7 @@ public class FeedProvider extends ContentProvider {
String feedsQuery = "SELECT " + TextUtils.join(",", DatabaseConstants.FEED_COLUMNS) + " FROM " + DatabaseConstants.FEED_FOLDER_MAP_TABLE +
" INNER JOIN " + DatabaseConstants.FEED_TABLE +
" ON " + DatabaseConstants.FEED_TABLE + "." + DatabaseConstants.FEED_ID + " = " + DatabaseConstants.FEED_FOLDER_MAP_TABLE + "." + DatabaseConstants.FEED_FOLDER_FEED_ID +
((selection == null) ? "" : selection) +
((selection == null) ? "" : " WHERE " + selection) +
" ORDER BY " + DatabaseConstants.FEED_TABLE + "." + DatabaseConstants.FEED_TITLE + " COLLATE NOCASE";
return db.rawQuery(feedsQuery, selectionArgs);

View file

@ -1,5 +1,6 @@
package com.newsblur.fragment;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@ -42,6 +43,7 @@ import com.newsblur.util.UIUtils;
import com.newsblur.util.ViewUtils;
import com.newsblur.view.FlowLayout;
import com.newsblur.view.NewsblurWebview;
import com.newsblur.view.NonfocusScrollview;
public class ReadingItemFragment extends Fragment implements ClassifierDialogFragment.TagUpdateCallback, ShareDialogFragment.SharedCallbackDialog {
@ -64,6 +66,7 @@ public class ReadingItemFragment extends Fragment implements ClassifierDialogFra
private UserDetails user;
public String previouslySavedShareText;
private ImageView feedIcon;
private Reading activity;
public static ReadingItemFragment newInstance(Story story, String feedTitle, String feedFaviconColor, String feedFaviconFade, String feedFaviconBorder, String faviconText, String faviconUrl, Classifier classifier, boolean displayFeedDetails) {
ReadingItemFragment readingFragment = new ReadingItemFragment();
@ -83,6 +86,13 @@ public class ReadingItemFragment extends Fragment implements ClassifierDialogFra
return readingFragment;
}
@Override
public void onAttach(Activity activity) {
if (activity instanceof Reading) {
this.activity = (Reading) activity;
}
super.onAttach(activity);
}
@Override
public void onCreate(Bundle savedInstanceState) {
@ -147,6 +157,9 @@ public class ReadingItemFragment extends Fragment implements ClassifierDialogFra
setupItemCommentsAndShares(view);
}
NonfocusScrollview scrollView = (NonfocusScrollview) view.findViewById(R.id.reading_scrollview);
scrollView.registerScrollChangeListener(this.activity);
return view;
}

View file

@ -9,6 +9,8 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.os.Build;
import android.view.View;
public class UIUtils {
@ -70,9 +72,20 @@ public class UIUtils {
* used throughout Android.
* See: http://bit.ly/MfsAUZ (Romain Guy's comment)
*/
public static int convertDPsToPixels(Context context, final int dps) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dps * scale + 0.5f);
}
/**
* Sets the alpha of a view in a manner that is safe to use before API version 11.
* If alpha isn't supported, just make the view invisible if the alpha is so low
* that it may as well be.
*/
public static void setViewAlpha(View v, float alpha) {
v.setVisibility((alpha > 0.001) ? View.VISIBLE : View.INVISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
v.setAlpha(alpha);
}
}
}

View file

@ -1,13 +1,19 @@
package com.newsblur.view;
import java.util.HashSet;
import java.util.Set;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.webkit.WebView;
import android.widget.ScrollView;
public class NonfocusScrollview extends ScrollView {
private Set<ScrollChangeListener> changeListeners = new HashSet<ScrollChangeListener>();
public NonfocusScrollview(Context context) {
super(context);
}
@ -23,4 +29,22 @@ public class NonfocusScrollview extends ScrollView {
}
super.requestChildFocus(child, focused);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
int w = this.getChildAt(0).getMeasuredWidth();
int h = this.getChildAt(0).getMeasuredHeight();
for (ScrollChangeListener listener : this.changeListeners) {
listener.scrollChanged(l, t, w, h);
}
super.onScrollChanged(l, t, oldl, oldt);
}
public void registerScrollChangeListener(ScrollChangeListener listener) {
this.changeListeners.add(listener);
}
public interface ScrollChangeListener {
public void scrollChanged(int hPos, int vPos, int currentWidth, int currentHeight);
}
}

View file

@ -674,6 +674,11 @@ body {
height: 14px;
overflow: hidden;
text-shadow: 0 1px 0 rgba(250, 250, 250, .4);
text-overflow: ellipsis;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
display: -webkit-box;
}
.NB-feedlist .feed .NB-feedlist-manage-icon,