Merge branch 'master' into muserstory_remove

# By ojiikun (3) and Samuel Clay (2)
* master:
  Fixing hashes only call on folders.
  Handling another unicode decode issue in uploaded OPMLs.
  Make Story.read a boolean rather than an int.
  Upper limit on mark-read batch size auto-flushes.
  Remove forced sleep suspected of causing null activity access in fragments.
This commit is contained in:
Samuel Clay 2013-05-13 12:59:04 -07:00
commit bcdd1fe221
14 changed files with 82 additions and 40 deletions

View file

@ -3,6 +3,7 @@ import pickle
import base64 import base64
from utils import log as logging from utils import log as logging
from oauth2client.client import OAuth2WebServerFlow, FlowExchangeError from oauth2client.client import OAuth2WebServerFlow, FlowExchangeError
from bson.errors import InvalidStringData
import uuid import uuid
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
# from django.db import IntegrityError # from django.db import IntegrityError
@ -36,7 +37,7 @@ def opml_upload(request):
xml_opml = file.read() xml_opml = file.read()
try: try:
uploaded_opml = UploadedOPML.objects.create(user_id=request.user.pk, opml_file=xml_opml) uploaded_opml = UploadedOPML.objects.create(user_id=request.user.pk, opml_file=xml_opml)
except UnicodeDecodeError: except (UnicodeDecodeError, InvalidStringData):
uploaded_opml = None uploaded_opml = None
folders = None folders = None
code = -1 code = -1

View file

@ -111,7 +111,6 @@ class UserSubscription(models.Model):
else: else:
r.sdiffstore(unread_stories_key, stories_key, read_stories_key) r.sdiffstore(unread_stories_key, stories_key, read_stories_key)
sorted_stories_key = 'zF:%s' % (self.feed_id) sorted_stories_key = 'zF:%s' % (self.feed_id)
unread_ranked_stories_key = 'zU:%s:%s' % (self.user_id, self.feed_id)
r.zinterstore(unread_ranked_stories_key, [sorted_stories_key, unread_stories_key]) r.zinterstore(unread_ranked_stories_key, [sorted_stories_key, unread_stories_key])
current_time = int(time.time() + 60*60*24) current_time = int(time.time() + 60*60*24)

Binary file not shown.

View file

@ -190,10 +190,7 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
@Override @Override
protected void onPause() { protected void onPause() {
synchronized(this.storiesToMarkAsRead) { flushStoriesMarkedRead();
FeedUtils.markStoriesAsRead(this.storiesToMarkAsRead, this);
this.storiesToMarkAsRead.clear();
}
super.onPause(); super.onPause();
} }
@ -203,10 +200,23 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
*/ */
protected void addStoryToMarkAsRead(Story story) { protected void addStoryToMarkAsRead(Story story) {
if (story == null) return; if (story == null) return;
if (story.read != Story.UNREAD) return; if (story.read) return;
synchronized (this.storiesToMarkAsRead) { synchronized (this.storiesToMarkAsRead) {
this.storiesToMarkAsRead.add(story); this.storiesToMarkAsRead.add(story);
} }
// flush immediately if the batch reaches a sufficient size
if (this.storiesToMarkAsRead.size() >= AppConstants.MAX_MARK_READ_BATCH) {
flushStoriesMarkedRead();
}
}
private void flushStoriesMarkedRead() {
synchronized(this.storiesToMarkAsRead) {
if (this.storiesToMarkAsRead.size() > 0) {
FeedUtils.markStoriesAsRead(this.storiesToMarkAsRead, this);
this.storiesToMarkAsRead.clear();
}
}
} }
@Override @Override

View file

@ -10,7 +10,6 @@ public class BlurDatabase extends SQLiteOpenHelper {
private final String TEXT = " text"; private final String TEXT = " text";
private final String INTEGER = " integer"; private final String INTEGER = " integer";
private final static String TAG = "DatabaseHelper";
public final static String DB_NAME = "blur.db"; public final static String DB_NAME = "blur.db";
private final static int VERSION = 1; private final static int VERSION = 1;
@ -105,7 +104,7 @@ public class BlurDatabase extends SQLiteOpenHelper {
DatabaseConstants.STORY_FRIEND_USER_IDS + TEXT + ", " + DatabaseConstants.STORY_FRIEND_USER_IDS + TEXT + ", " +
DatabaseConstants.STORY_TAGS + TEXT + ", " + DatabaseConstants.STORY_TAGS + TEXT + ", " +
DatabaseConstants.STORY_PERMALINK + TEXT + ", " + DatabaseConstants.STORY_PERMALINK + TEXT + ", " +
DatabaseConstants.STORY_READ + TEXT + ", " + DatabaseConstants.STORY_READ + INTEGER + ", " +
DatabaseConstants.STORY_TITLE + TEXT + DatabaseConstants.STORY_TITLE + TEXT +
")"; ")";

View file

@ -60,8 +60,7 @@ public class FeedItemsAdapter extends SimpleCursorAdapter {
borderTwo.setBackgroundColor(Color.LTGRAY); borderTwo.setBackgroundColor(Color.LTGRAY);
} }
// 1 is read if (! Story.fromCursor(cursor).read) {
if (Story.fromCursor(cursor).read == Story.UNREAD) {
((TextView) v.findViewById(R.id.row_item_author)).setTextColor(storyAuthorUnread); ((TextView) v.findViewById(R.id.row_item_author)).setTextColor(storyAuthorUnread);
((TextView) v.findViewById(R.id.row_item_date)).setTextColor(storyDateUnread); ((TextView) v.findViewById(R.id.row_item_date)).setTextColor(storyDateUnread);

View file

@ -70,7 +70,7 @@ public class MultipleFeedItemsAdapter extends SimpleCursorAdapter {
} }
// 1 is read // 1 is read
if (Story.fromCursor(cursor).read == Story.UNREAD) { if (! Story.fromCursor(cursor).read) {
((TextView) v.findViewById(R.id.row_item_author)).setTextColor(storyAuthorUnread); ((TextView) v.findViewById(R.id.row_item_author)).setTextColor(storyAuthorUnread);
((TextView) v.findViewById(R.id.row_item_date)).setTextColor(storyDateUnread); ((TextView) v.findViewById(R.id.row_item_date)).setTextColor(storyDateUnread);
((TextView) v.findViewById(R.id.row_item_feedtitle)).setTextColor(storyFeedUnread); ((TextView) v.findViewById(R.id.row_item_feedtitle)).setTextColor(storyFeedUnread);

View file

@ -18,9 +18,6 @@ public class Story implements Serializable {
private static final long serialVersionUID = 7629596752129163308L; private static final long serialVersionUID = 7629596752129163308L;
public static final int UNREAD = 0;
public static final int READ = 1;
public String id; public String id;
@SerializedName("story_permalink") @SerializedName("story_permalink")
@ -42,7 +39,7 @@ public class Story implements Serializable {
public int commentCount; public int commentCount;
@SerializedName("read_status") @SerializedName("read_status")
public int read; public boolean read;
@SerializedName("story_tags") @SerializedName("story_tags")
public String[] tags; public String[] tags;
@ -135,7 +132,7 @@ public class Story implements Serializable {
story.intelligence.intelligenceFeed = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_FEED)); story.intelligence.intelligenceFeed = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_FEED));
story.intelligence.intelligenceTags = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_TAGS)); story.intelligence.intelligenceTags = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_TAGS));
story.intelligence.intelligenceTitle = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_TITLE)); story.intelligence.intelligenceTitle = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_TITLE));
story.read = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_READ)); story.read = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_READ)) > 0;
story.tags = TextUtils.split(cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_TAGS)), ","); story.tags = TextUtils.split(cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_TAGS)), ",");
story.feedId = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_FEED_ID)); story.feedId = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_FEED_ID));
story.id = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_ID)); story.id = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_ID));

View file

@ -183,7 +183,7 @@ public class FeedItemListFragment extends ItemListFragment implements LoaderMana
final AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo(); final AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
if (item.getItemId() == R.id.menu_mark_story_as_read) { if (item.getItemId() == R.id.menu_mark_story_as_read) {
final Story story = adapter.getStory(menuInfo.position); final Story story = adapter.getStory(menuInfo.position);
if(story.read == Story.UNREAD) { if(! story.read) {
ArrayList<Story> storiesToMarkAsRead = new ArrayList<Story>(); ArrayList<Story> storiesToMarkAsRead = new ArrayList<Story>();
storiesToMarkAsRead.add(story); storiesToMarkAsRead.add(story);
FeedUtils.markStoriesAsRead(storiesToMarkAsRead, getActivity()); FeedUtils.markStoriesAsRead(storiesToMarkAsRead, getActivity());
@ -193,7 +193,7 @@ public class FeedItemListFragment extends ItemListFragment implements LoaderMana
final ArrayList<Story> previousStories = adapter.getPreviousStories(menuInfo.position); final ArrayList<Story> previousStories = adapter.getPreviousStories(menuInfo.position);
ArrayList<Story> storiesToMarkAsRead = new ArrayList<Story>(); ArrayList<Story> storiesToMarkAsRead = new ArrayList<Story>();
for(Story story: previousStories) { for(Story story: previousStories) {
if(story.read == Story.UNREAD) { if(! story.read) {
storiesToMarkAsRead.add(story); storiesToMarkAsRead.add(story);
} }
} }

View file

@ -80,12 +80,6 @@ public class LoginProgressFragment extends Fragment {
protected LoginResponse doInBackground(String... params) { protected LoginResponse doInBackground(String... params) {
LoginResponse response = apiManager.login(username, password); LoginResponse response = apiManager.login(username, password);
apiManager.updateUserProfile(); apiManager.updateUserProfile();
try {
// TODO: get rid of this and use proper UI transactions
Thread.sleep(500);
} catch (InterruptedException e) {
Log.e(this.getClass().getName(), "Error sleeping during login.");
}
return response; return response;
} }

View file

@ -44,27 +44,24 @@ import com.newsblur.network.domain.Message;
import com.newsblur.network.domain.ProfileResponse; import com.newsblur.network.domain.ProfileResponse;
import com.newsblur.network.domain.SocialFeedResponse; import com.newsblur.network.domain.SocialFeedResponse;
import com.newsblur.network.domain.StoriesResponse; import com.newsblur.network.domain.StoriesResponse;
import com.newsblur.serialization.BooleanTypeAdapter;
import com.newsblur.serialization.DateStringTypeAdapter; import com.newsblur.serialization.DateStringTypeAdapter;
import com.newsblur.util.PrefsUtils; import com.newsblur.util.PrefsUtils;
public class APIManager { public class APIManager {
private Context context; private Context context;
private static Gson gson; private Gson gson;
private ContentResolver contentResolver; private ContentResolver contentResolver;
public APIManager(final Context context) { public APIManager(final Context context) {
this.context = context; this.context = context;
contentResolver = context.getContentResolver(); this.contentResolver = context.getContentResolver();
initGsonIfNeededBuilder(); this.gson = new GsonBuilder()
} .registerTypeAdapter(Date.class, new DateStringTypeAdapter())
.registerTypeAdapter(Boolean.class, new BooleanTypeAdapter())
private static synchronized void initGsonIfNeededBuilder() { .registerTypeAdapter(boolean.class, new BooleanTypeAdapter())
if(gson == null) { .create();
final GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Date.class, new DateStringTypeAdapter());
gson = builder.create();
}
} }
public LoginResponse login(final String username, final String password) { public LoginResponse login(final String username, final String password) {

View file

@ -0,0 +1,43 @@
package com.newsblur.serialization;
import java.io.IOException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
/**
* A more forgiving type adapter to deserialize JSON booleans. Specifically, this implementation is
* friendly to backend code that may send numeric 0s and 1s for boolean fields, on which the strict
* GSON base impl would choke.
*/
public class BooleanTypeAdapter extends TypeAdapter<Boolean> {
@Override
public Boolean read(JsonReader in) throws IOException {
JsonToken type = in.peek();
if (type == JsonToken.NULL) {
in.nextNull();
return null;
} else if (type == JsonToken.BOOLEAN) {
return in.nextBoolean();
} else if (type == JsonToken.NUMBER) {
return in.nextInt() > 0;
} else if (type == JsonToken.STRING) {
return Boolean.parseBoolean(in.nextString());
} else {
throw new IOException( "Could not parse JSON boolean." );
}
}
@Override
public void write(JsonWriter out, Boolean b) throws IOException{
if (b == null) {
out.nullValue();
} else {
out.value(b);
}
}
}

View file

@ -19,4 +19,7 @@ public class AppConstants {
public static final String ROOT_FOLDER = "0000_TOP_LEVEL_"; public static final String ROOT_FOLDER = "0000_TOP_LEVEL_";
public static final String LAST_APP_VERSION = "LAST_APP_VERSION"; public static final String LAST_APP_VERSION = "LAST_APP_VERSION";
// the max number of mark-as-read ops to batch up before flushing to the server
public static final int MAX_MARK_READ_BATCH = 5;
} }