mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
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:
commit
bcdd1fe221
14 changed files with 82 additions and 40 deletions
|
@ -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
|
||||||
|
|
|
@ -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.
BIN
media/android/NewsBlur/libs/gson-2.2.3.jar
Normal file
BIN
media/android/NewsBlur/libs/gson-2.2.3.jar
Normal file
Binary file not shown.
|
@ -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
|
||||||
|
|
|
@ -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 +
|
||||||
")";
|
")";
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue