From bbd9385a3f702f5f46fafe08dce52d618ce1d4de Mon Sep 17 00:00:00 2001 From: dosiecki Date: Sun, 29 Mar 2015 16:54:22 -0700 Subject: [PATCH] Correctly handle folder heirarchies. (INCOMPLETE) --- .../newsblur/activity/FolderItemsList.java | 2 +- .../com/newsblur/database/BlurDatabase.java | 2 - .../newsblur/database/BlurDatabaseHelper.java | 38 +++--- .../newsblur/database/DatabaseConstants.java | 20 ++-- .../newsblur/database/FolderListAdapter.java | 74 +++++++----- .../src/com/newsblur/domain/Folder.java | 73 ++++++++---- .../newsblur/fragment/FolderListFragment.java | 18 +-- .../network/domain/FeedFolderResponse.java | 112 +++++++----------- .../com/newsblur/service/NBSyncService.java | 41 +++---- .../src/com/newsblur/util/AppConstants.java | 2 +- .../src/com/newsblur/util/FeedUtils.java | 7 +- 11 files changed, 188 insertions(+), 201 deletions(-) diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java index cd03c45c5..bf745e8b1 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java @@ -54,7 +54,7 @@ public class FolderItemsList extends ItemsList implements MarkAllReadDialogListe @Override protected FeedSet createFeedSet() { - return FeedSet.folder(this.folderName, FeedUtils.dbHelper.getFeedsForFolder(folderName)); + return FeedUtils.feedSetFromFolderName(this.folderName); } @Override diff --git a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java index 8d6a9275a..e7783b845 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java @@ -24,7 +24,6 @@ public class BlurDatabase extends SQLiteOpenHelper { db.execSQL(DatabaseConstants.COMMENT_SQL); db.execSQL(DatabaseConstants.REPLY_SQL); db.execSQL(DatabaseConstants.CLASSIFIER_SQL); - db.execSQL(DatabaseConstants.FEED_FOLDER_SQL); db.execSQL(DatabaseConstants.SOCIALFEED_STORIES_SQL); db.execSQL(DatabaseConstants.STARRED_STORIES_COUNT_SQL); db.execSQL(DatabaseConstants.ACTION_SQL); @@ -42,7 +41,6 @@ public class BlurDatabase extends SQLiteOpenHelper { db.execSQL(drop + DatabaseConstants.COMMENT_TABLE); db.execSQL(drop + DatabaseConstants.REPLY_TABLE); db.execSQL(drop + DatabaseConstants.CLASSIFIER_TABLE); - db.execSQL(drop + DatabaseConstants.FEED_FOLDER_MAP_TABLE); db.execSQL(drop + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE); db.execSQL(drop + DatabaseConstants.STARRED_STORY_COUNT_TABLE); db.execSQL(drop + DatabaseConstants.ACTION_TABLE); diff --git a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java index d7e13fb99..47ff86264 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java @@ -15,7 +15,7 @@ import android.util.Log; import com.newsblur.domain.Classifier; import com.newsblur.domain.Comment; import com.newsblur.domain.Feed; -import com.newsblur.domain.FeedResult; +import com.newsblur.domain.Folder; import com.newsblur.domain.Reply; import com.newsblur.domain.SocialFeed; import com.newsblur.domain.Story; @@ -136,7 +136,6 @@ public class BlurDatabaseHelper { public void cleanupFeedsFolders() { synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.FEED_TABLE, null, null);} synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.FOLDER_TABLE, null, null);} - synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.FEED_FOLDER_MAP_TABLE, null, null);} synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.SOCIALFEED_TABLE, null, null);} } @@ -147,7 +146,6 @@ public class BlurDatabaseHelper { public void deleteFeed(String feedId) { String[] selArgs = new String[] {feedId}; synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.FEED_TABLE, DatabaseConstants.FEED_ID + " = ?", selArgs);} - synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.FEED_FOLDER_MAP_TABLE, DatabaseConstants.FEED_FOLDER_FEED_ID + " = ?", selArgs);} synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.STORY_TABLE, DatabaseConstants.STORY_FEED_ID + " = ?", selArgs);} } @@ -166,13 +164,11 @@ public class BlurDatabaseHelper { } } - public void insertFeedsFolders(List feedValues, - List folderValues, - List ffmValues, + public void insertFeedsFolders(List folderValues, + List feedValues, List socialFeedValues) { - bulkInsertValues(DatabaseConstants.FEED_TABLE, feedValues); bulkInsertValues(DatabaseConstants.FOLDER_TABLE, folderValues); - bulkInsertValues(DatabaseConstants.FEED_FOLDER_MAP_TABLE, ffmValues); + bulkInsertValues(DatabaseConstants.FEED_TABLE, feedValues); bulkInsertValues(DatabaseConstants.SOCIALFEED_TABLE, socialFeedValues); } @@ -336,17 +332,13 @@ public class BlurDatabaseHelper { bulkInsertValues(DatabaseConstants.REPLY_TABLE, replyValues); } - public Set getFeedsForFolder(String folderName) { - Set feedIds = new HashSet(); - String q = "SELECT " + DatabaseConstants.FEED_FOLDER_FEED_ID + - " FROM " + DatabaseConstants.FEED_FOLDER_MAP_TABLE + - " WHERE " + DatabaseConstants.FEED_FOLDER_FOLDER_NAME + " = ?" ; - Cursor c = dbRO.rawQuery(q, new String[]{folderName}); - while (c.moveToNext()) { - feedIds.add(c.getString(c.getColumnIndexOrThrow(DatabaseConstants.FEED_FOLDER_FEED_ID))); - } - c.close(); - return feedIds; + public Folder getFolder(String folderName) { + String[] selArgs = new String[] {folderName}; + String selection = DatabaseConstants.FOLDER_NAME + " = ?"; + Cursor c = dbRO.query(DatabaseConstants.FOLDER_TABLE, null, selection, selArgs, null, null, null); + Folder folder = Folder.fromCursor(c); + closeQuietly(c); + return folder; } public void markStoryHashesRead(List hashes) { @@ -677,14 +669,14 @@ public class BlurDatabaseHelper { return query(false, DatabaseConstants.SOCIALFEED_TABLE, null, DatabaseConstants.getBlogSelectionFromState(stateFilter), null, null, null, "UPPER(" + DatabaseConstants.SOCIAL_FEED_TITLE + ") ASC", null, cancellationSignal); } - public Loader getFolderFeedMapLoader() { + public Loader getFoldersLoader() { return new QueryCursorLoader(context) { - protected Cursor createCursor() {return getFolderFeedMapCursor(cancellationSignal);} + protected Cursor createCursor() {return getFoldersCursor(cancellationSignal);} }; } - public Cursor getFolderFeedMapCursor(CancellationSignal cancellationSignal) { - return query(false, DatabaseConstants.FEED_FOLDER_MAP_TABLE, null, null, null, null, null, null, null, cancellationSignal); + public Cursor getFoldersCursor(CancellationSignal cancellationSignal) { + return query(false, DatabaseConstants.FOLDER_TABLE, null, null, null, null, null, null, null, cancellationSignal); } public Loader getFeedsLoader(final StateFilter stateFilter) { diff --git a/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java b/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java index 716e0a1b0..a9bfa7968 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java @@ -15,8 +15,10 @@ public class DatabaseConstants { private static final String INTEGER = " INTEGER"; public static final String FOLDER_TABLE = "folders"; - public static final String FOLDER_ID = BaseColumns._ID; public static final String FOLDER_NAME = "folder_name"; + public static final String FOLDER_PARENT_NAMES = "folder_parent_names"; + public static final String FOLDER_CHILDREN_NAMES = "folder_children_names"; + public static final String FOLDER_FEED_IDS = "folder_feedids"; public static final String FEED_TABLE = "feeds"; public static final String FEED_ID = BaseColumns._ID; @@ -45,10 +47,6 @@ public class DatabaseConstants { public static final String SOCIAL_FEED_NEUTRAL_COUNT = "nt"; public static final String SOCIAL_FEED_NEGATIVE_COUNT = "ng"; - public static final String FEED_FOLDER_MAP_TABLE = "feed_folder_map"; - public static final String FEED_FOLDER_FEED_ID = "feed_feed_id"; - public static final String FEED_FOLDER_FOLDER_NAME = "feed_folder_name"; - public static final String SOCIALFEED_STORY_MAP_TABLE = "socialfeed_story_map"; public static final String SOCIALFEED_STORY_USER_ID = "socialfeed_story_user_id"; public static final String SOCIALFEED_STORY_STORYID = "socialfeed_story_storyid"; @@ -139,8 +137,10 @@ public class DatabaseConstants { public static final String ACTION_INCLUDE_NEWER = "include_newer"; static final String FOLDER_SQL = "CREATE TABLE " + FOLDER_TABLE + " (" + - FOLDER_ID + INTEGER + " PRIMARY KEY AUTOINCREMENT, " + - FOLDER_NAME + TEXT + " UNIQUE " + + FOLDER_NAME + TEXT + " PRIMARY KEY, " + + FOLDER_PARENT_NAMES + TEXT + ", " + + FOLDER_CHILDREN_NAMES + TEXT + ", " + + FOLDER_FEED_IDS + TEXT + ")"; static final String FEED_SQL = "CREATE TABLE " + FEED_TABLE + " (" + @@ -245,12 +245,6 @@ public class DatabaseConstants { CLASSIFIER_VALUE + TEXT + ")"; - static final String FEED_FOLDER_SQL = "CREATE TABLE " + FEED_FOLDER_MAP_TABLE + " (" + - FEED_FOLDER_FOLDER_NAME + TEXT + " NOT NULL, " + - FEED_FOLDER_FEED_ID + INTEGER + " NOT NULL, " + - "PRIMARY KEY (" + FEED_FOLDER_FOLDER_NAME + ", " + FEED_FOLDER_FEED_ID + ") " + - ")"; - static final String SOCIALFEED_STORIES_SQL = "CREATE TABLE " + SOCIALFEED_STORY_MAP_TABLE + " (" + SOCIALFEED_STORY_STORYID + TEXT + " NOT NULL, " + SOCIALFEED_STORY_USER_ID + INTEGER + " NOT NULL, " + diff --git a/clients/android/NewsBlur/src/com/newsblur/database/FolderListAdapter.java b/clients/android/NewsBlur/src/com/newsblur/database/FolderListAdapter.java index 146784fe5..c5a7dad58 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/FolderListAdapter.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/FolderListAdapter.java @@ -30,6 +30,7 @@ import com.newsblur.activity.GlobalSharedStoriesItemsList; import com.newsblur.activity.NewsBlurApplication; import static com.newsblur.database.DatabaseConstants.getStr; import com.newsblur.domain.Feed; +import com.newsblur.domain.Folder; import com.newsblur.domain.SocialFeed; import com.newsblur.util.AppConstants; import com.newsblur.util.ImageLoader; @@ -48,7 +49,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter { private Cursor socialFeedCursor; - private Map> folderFeedMap = Collections.emptyMap(); + private Map folders = Collections.emptyMap(); private List activeFolderNames; private List> activeFolderChildren; private List neutCounts; @@ -308,18 +309,23 @@ public class FolderListAdapter extends BaseExpandableListAdapter { notifyDataSetChanged(); } - public synchronized void setFolderFeedMapCursor(Cursor cursor) { + public synchronized void setFoldersCursor(Cursor cursor) { if ((cursor.getCount() < 1) || (!cursor.isBeforeFirst())) return; - folderFeedMap = newCustomSortedMap(); + folders = new LinkedHashMap(cursor.getCount()); while (cursor.moveToNext()) { - String folderName = getStr(cursor, DatabaseConstants.FEED_FOLDER_FOLDER_NAME); - String feedId = getStr(cursor, DatabaseConstants.FEED_FOLDER_FEED_ID); - if (! folderFeedMap.containsKey(folderName)) folderFeedMap.put(folderName, new ArrayList()); - folderFeedMap.get(folderName).add(feedId); + Folder folder = Folder.fromCursor(cursor); + folders.put(folder.flatName(), folder); } - if (!folderFeedMap.containsKey(AppConstants.ROOT_FOLDER)) { - folderFeedMap.put(AppConstants.ROOT_FOLDER, new ArrayList()); + /* + if (!folders.containsKey(AppConstants.ROOT_FOLDER)) { + Folder fakeRoot = new Folder(); + fakeRoot.name = AppConstants.ROOT_FOLDER; + fakeRoot.parents = Collections.emptyList(); + fakeRoot.children = Collections.emptyList(); + fakeRoot.feedIds = Collections.emptyList(); + folders.put(AppConstants.ROOT_FOLDER, fakeRoot); } + */ recountFeeds(); notifyDataSetChanged(); } @@ -344,18 +350,22 @@ public class FolderListAdapter extends BaseExpandableListAdapter { } private void recountFeeds() { - if ((folderFeedMap == null) || (feeds == null)) return; - int c = folderFeedMap.keySet().size(); - activeFolderNames = new ArrayList(c); - activeFolderChildren = new ArrayList>(c); - neutCounts = new ArrayList(c); - posCounts = new ArrayList(c); - for (String folderName : folderFeedMap.keySet()) { + if ((folders == null) || (feeds == null)) return; + // re-init our local vars + activeFolderNames = new ArrayList(); + activeFolderChildren = new ArrayList>(); + neutCounts = new ArrayList(); + posCounts = new ArrayList(); + // create a sorted list of folder display names + List sortedFolderNames = new ArrayList(folders.keySet()); + customSortList(sortedFolderNames); + // inspect folders to see if the are active for display + for (String folderName : sortedFolderNames) { List activeFeeds = new ArrayList(); int neutCount = 0; int posCount = 0; - for (String feedId : folderFeedMap.get(folderName)) { - Feed f = feeds.get(feedId); + for (Long feedId : folders.get(folderName).feedIds) { + Feed f = feeds.get(feedId.toString()); if (f != null) { if (((currentState == StateFilter.BEST) && (f.positiveCount > 0)) || ((currentState == StateFilter.SOME) && ((f.positiveCount + f.neutralCount > 0))) || @@ -492,18 +502,20 @@ public class FolderListAdapter extends BaseExpandableListAdapter { * folder on top, and also the expectation that *despite locale*, folders * starting with an underscore should show up on top. */ - private Map> newCustomSortedMap() { - Comparator c = new Comparator() { - @Override - public int compare(String s1, String s2) { - if (TextUtils.equals(s1, s2)) return 0; - if (s1.equals(AppConstants.ROOT_FOLDER)) return -1; - if (s2.equals(AppConstants.ROOT_FOLDER)) return 1; - if (s1.startsWith("_")) return -1; - if (s2.startsWith("_")) return 1; - return String.CASE_INSENSITIVE_ORDER.compare(s1, s2); - } - }; - return new TreeMap>(c); + private void customSortList(List list) { + Collections.sort(list, CustomComparator); } + + private static Comparator CustomComparator = new Comparator() { + @Override + public int compare(String s1, String s2) { + if (TextUtils.equals(s1, s2)) return 0; + if (s1.equals(AppConstants.ROOT_FOLDER)) return -1; + if (s2.equals(AppConstants.ROOT_FOLDER)) return 1; + if (s1.startsWith("_")) return -1; + if (s2.startsWith("_")) return 1; + return String.CASE_INSENSITIVE_ORDER.compare(s1, s2); + } + }; + } diff --git a/clients/android/NewsBlur/src/com/newsblur/domain/Folder.java b/clients/android/NewsBlur/src/com/newsblur/domain/Folder.java index 8428963c8..161777793 100644 --- a/clients/android/NewsBlur/src/com/newsblur/domain/Folder.java +++ b/clients/android/NewsBlur/src/com/newsblur/domain/Folder.java @@ -4,38 +4,67 @@ import android.content.ContentValues; import android.database.Cursor; import android.text.TextUtils; +import java.util.ArrayList; +import java.util.List; + import com.newsblur.database.DatabaseConstants; public class Folder { - public final ContentValues values = new ContentValues(); + /** Actual unique name of the folder. */ + public String name; + /** List, drilling down from root to this folder of containing folders. NOTE: this is a path! */ + public List parents; + /** Set of any children folders contained in this folder. NOTE: this is a one-to-many set! */ + public List children; + /** Set of any feeds contained in this folder. */ + public List feedIds; - public void setId(final String id) { - values.put(DatabaseConstants.FOLDER_ID, id); - } - - public void setName(final String name) { - values.put(DatabaseConstants.FOLDER_NAME, name); - } - - public String getId() { - return values.getAsString(DatabaseConstants.FOLDER_ID); - } - - public String getName() { - return values.getAsString(DatabaseConstants.FOLDER_NAME); - } - - public static Folder fromCursor(Cursor folderCursor) { - final Folder folder = new Folder(); - folder.setId(folderCursor.getString(folderCursor.getColumnIndex(DatabaseConstants.FOLDER_ID))); - folder.setName(folderCursor.getString(folderCursor.getColumnIndex(DatabaseConstants.FOLDER_NAME))); + public static Folder fromCursor(Cursor c) { + if (c.isBeforeFirst()) { + c.moveToFirst(); + } + Folder folder = new Folder(); + folder.name = c.getString(c.getColumnIndex(DatabaseConstants.FOLDER_NAME)); + String parents = c.getString(c.getColumnIndex(DatabaseConstants.FOLDER_PARENT_NAMES)); + folder.parents = new ArrayList(); + for (String name : TextUtils.split(parents, ",")) { folder.parents.add(name);} + String children = c.getString(c.getColumnIndex(DatabaseConstants.FOLDER_CHILDREN_NAMES)); + folder.children = new ArrayList(); + for (String name : TextUtils.split(children, ",")) { folder.children.add(name);} + String feeds = c.getString(c.getColumnIndex(DatabaseConstants.FOLDER_FEED_IDS)); + folder.feedIds = new ArrayList(); + for (String id : TextUtils.split(feeds, ",")) { folder.feedIds.add(Long.valueOf(id));} return folder; } + + public ContentValues getValues() { + ContentValues values = new ContentValues(); + values.put(DatabaseConstants.FOLDER_NAME, name); + values.put(DatabaseConstants.FOLDER_PARENT_NAMES, TextUtils.join(",", parents)); + values.put(DatabaseConstants.FOLDER_CHILDREN_NAMES, TextUtils.join(",", children)); + values.put(DatabaseConstants.FOLDER_FEED_IDS, TextUtils.join(",", feedIds)); + return values; + } + + public String flatName() { + StringBuilder builder = new StringBuilder(); + for (String parentName : parents) { + builder.append(parentName); + builder.append(" - "); + } + builder.append(name); + return builder.toString(); + } @Override public boolean equals(Object otherFolder) { - return TextUtils.equals(((Folder) otherFolder).getId(), getId()); + return TextUtils.equals(((Folder) otherFolder).name, name); } + + @Override + public int hashCode() { + return name.hashCode(); + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java index d91f5760d..32636b473 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java @@ -44,7 +44,7 @@ import com.newsblur.util.UIUtils; public class FolderListFragment extends NbFragment implements OnGroupClickListener, OnChildClickListener, OnCreateContextMenuListener, LoaderManager.LoaderCallbacks { private static final int SOCIALFEEDS_LOADER = 1; - private static final int FOLDERFEEDMAP_LOADER = 2; + private static final int FOLDERS_LOADER = 2; private static final int FEEDS_LOADER = 3; private static final int SAVEDCOUNT_LOADER = 4; @@ -64,9 +64,9 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen public synchronized void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); sharedPreferences = getActivity().getSharedPreferences(PrefConstants.PREFERENCES, 0); - if (getLoaderManager().getLoader(FOLDERFEEDMAP_LOADER) == null) { + if (getLoaderManager().getLoader(FOLDERS_LOADER) == null) { getLoaderManager().initLoader(SOCIALFEEDS_LOADER, null, this); - getLoaderManager().initLoader(FOLDERFEEDMAP_LOADER, null, this); + getLoaderManager().initLoader(FOLDERS_LOADER, null, this); getLoaderManager().initLoader(FEEDS_LOADER, null, this); getLoaderManager().initLoader(SAVEDCOUNT_LOADER, null, this); } @@ -77,8 +77,8 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen switch (id) { case SOCIALFEEDS_LOADER: return FeedUtils.dbHelper.getSocialFeedsLoader(currentState); - case FOLDERFEEDMAP_LOADER: - return FeedUtils.dbHelper.getFolderFeedMapLoader(); + case FOLDERS_LOADER: + return FeedUtils.dbHelper.getFoldersLoader(); case FEEDS_LOADER: return FeedUtils.dbHelper.getFeedsLoader(currentState); case SAVEDCOUNT_LOADER: @@ -96,8 +96,8 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen case SOCIALFEEDS_LOADER: adapter.setSocialFeedCursor(cursor); break; - case FOLDERFEEDMAP_LOADER: - adapter.setFolderFeedMapCursor(cursor); + case FOLDERS_LOADER: + adapter.setFoldersCursor(cursor); break; case FEEDS_LOADER: adapter.setFeedCursor(cursor); @@ -124,7 +124,7 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen public void hasUpdated() { if (isAdded()) { getLoaderManager().restartLoader(SOCIALFEEDS_LOADER, null, this); - getLoaderManager().restartLoader(FOLDERFEEDMAP_LOADER, null, this); + getLoaderManager().restartLoader(FOLDERS_LOADER, null, this); getLoaderManager().restartLoader(FEEDS_LOADER, null, this); getLoaderManager().restartLoader(SAVEDCOUNT_LOADER, null, this); } @@ -215,7 +215,7 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen } else if (item.getItemId() == R.id.menu_mark_folder_as_read) { if (!adapter.isFolderRoot(groupPosition)) { String folderName = adapter.getGroup(groupPosition); - FeedUtils.markFeedsRead(FeedUtils.feedSetFromFolderName(folderName, getActivity()), null, null, getActivity()); + FeedUtils.markFeedsRead(FeedUtils.feedSetFromFolderName(folderName), null, null, getActivity()); } else { FeedUtils.markFeedsRead(FeedSet.allFeeds(), null, null, getActivity()); } diff --git a/clients/android/NewsBlur/src/com/newsblur/network/domain/FeedFolderResponse.java b/clients/android/NewsBlur/src/com/newsblur/network/domain/FeedFolderResponse.java index 798084358..a7675eb8f 100644 --- a/clients/android/NewsBlur/src/com/newsblur/network/domain/FeedFolderResponse.java +++ b/clients/android/NewsBlur/src/com/newsblur/network/domain/FeedFolderResponse.java @@ -1,12 +1,11 @@ package com.newsblur.network.domain; import java.util.ArrayList; -import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import android.util.Log; import com.google.gson.Gson; @@ -17,32 +16,26 @@ import com.google.gson.JsonParser; import com.google.gson.annotations.SerializedName; import com.google.gson.stream.JsonReader; import com.newsblur.domain.Feed; +import com.newsblur.domain.Folder; import com.newsblur.domain.SocialFeed; import com.newsblur.util.AppConstants; public class FeedFolderResponse { + /** Helper variable so users of the parser can pass along how long it took to read the JSON stream, for instrumentation. */ public long readTime; - @SerializedName("starred_count") - public int starredCount; + public Set folders; + public Set feeds; + public Set socialFeeds; - @SerializedName("feeds") - public Map feeds; - - @SerializedName("flat_folders") - public Map> folders; - - @SerializedName("social_feeds") - public SocialFeed[] socialFeeds; - public boolean isAuthenticated; public boolean isPremium; public boolean isStaff; + public int starredCount; public FeedFolderResponse(String json, Gson gson) { - // TODO: is there really any good reason the default GSON parser doesn't work here? JsonParser parser = new JsonParser(); JsonObject asJsonObject = parser.parse(json).getAsJsonObject(); @@ -55,23 +48,19 @@ public class FeedFolderResponse { this.isPremium = profile.get("is_premium").getAsBoolean(); } - JsonArray jsonFoldersArray = (JsonArray) asJsonObject.get("folders"); - ArrayList nestedFolderList = new ArrayList(); - folders = new HashMap>(); - parseFeedArray(nestedFolderList, folders, null, jsonFoldersArray); - JsonElement starredCountElement = asJsonObject.get("starred_count"); if(starredCountElement != null) { starredCount = gson.fromJson(starredCountElement, int.class); } + + folders = new HashSet(); + JsonArray jsonFoldersArray = (JsonArray) asJsonObject.get("folders"); + // recursively parse folders + parseFolderArray(new ArrayList(0), null, jsonFoldersArray); - // Inconsistent server response here. When user has no feeds we get - // "feeds": [] - // and other times we get - // "feeds": {"309667": { - // So support both I guess + // Inconsistent server response here. When user has no feeds we get an empty array, otherwise an object JsonElement feedsElement = asJsonObject.get("feeds"); - feeds = new HashMap(); + feeds = new HashSet(); if(feedsElement instanceof JsonObject) { JsonObject feedsObject = (JsonObject) asJsonObject.get("feeds"); if(feedsObject != null) { @@ -80,48 +69,43 @@ public class FeedFolderResponse { while(iterator.hasNext()) { Entry feedElement = iterator.next(); Feed feed = gson.fromJson(feedElement.getValue(), Feed.class); - feeds.put(feedElement.getKey(), feed); + feeds.add(feed); } } } // else server sent back '"feeds": []' - socialFeeds = new SocialFeed[0]; + socialFeeds = new HashSet(); JsonArray socialFeedsArray = (JsonArray) asJsonObject.get("social_feeds"); if(socialFeedsArray != null) { - List socialFeedsList = new ArrayList(); for(int i=0;i()); + Folder emptyRootFolder = new Folder(); + emptyRootFolder.name = AppConstants.ROOT_FOLDER; + // equality is based on folder name, so contains() will work + if (!folders.contains(emptyRootFolder)) { + folders.add(emptyRootFolder); Log.d( this.getClass().getName(), "root folder was missing. added it."); } } /** - * Parses a folder, which is a list of feeds and/or more folders. Nested folders - * are flattened into a single list, with names that are heirarchical. + * Parses a folder, which is a list of feeds and/or more folders. * - * @param nestedFolderList a list of any parent folders that surrounded this folder. - * @param folders the sink - * @param name the name of this folder. - * @param arrayValue the actual contents to be parsed. + * @param parentName folder that surrounded this folder. + * @param name the name of this folder or null if root. + * @param arrayValue the contents to be parsed. */ - private void parseFeedArray(List nestedFolderList, - Map> folders, String name, JsonArray arrayValue) { - - // determine our text name, like "grandparent - parent - me" - String fullFolderName = getFolderName(name, nestedFolderList); - // sink for any feeds found in this folder - ArrayList feedIds = new ArrayList(); - + private void parseFolderArray(List parentNames, String name, JsonArray arrayValue) { + if (name == null) name = AppConstants.ROOT_FOLDER; + List children = new ArrayList(); + List feedIds = new ArrayList(); for (JsonElement jsonElement : arrayValue) { // a folder array contains either feed IDs or nested folder objects if(jsonElement.isJsonPrimitive()) { @@ -129,35 +113,23 @@ public class FeedFolderResponse { } else { // if it wasn't a feed ID, it is a nested folder object Set> entrySet = ((JsonObject) jsonElement).entrySet(); - List nestedFolderListCopy = new ArrayList(nestedFolderList); - if(name != null) { - nestedFolderListCopy.add(name); - } // recurse - nested folders are just objects with (usually one) field named for the folder // that is a list of contained feeds or additional folders for (Entry next : entrySet) { - parseFeedArray( nestedFolderListCopy, folders, next.getKey(), (JsonArray) next.getValue() ); + children.add(next.getKey()); + List appendedParentList = new ArrayList(parentNames); + appendedParentList.add(name); + parseFolderArray(appendedParentList, next.getKey(), (JsonArray) next.getValue()); } } } - folders.put(fullFolderName, feedIds); - //Log.d( this.getClass().getName(), "parsed folder '" + fullFolderName + "' with " + feedIds.size() + " feeds" ); + Folder folder = new Folder(); + folder.name = name; + folder.parents = parentNames; + folder.children = children; + folder.feedIds = feedIds; + folders.add(folder); + Log.d(this.getClass().getName(), String.format("folder %s has parents %s and children %s and %d feeds", name, parentNames.toString(), children.toString(), feedIds.size())); } - private String getFolderName(String key, List parentFeedNames) { - StringBuilder builder = new StringBuilder(); - for(String parentFolder: parentFeedNames) { - builder.append(parentFolder); - builder.append(" - "); - } - if(key != null) { - builder.append(key); - } else { - // a null key means we are at the root. give these a pseudo-folder name, since the DB and many - // classes would be very unhappy with a null foldername. - builder.append(AppConstants.ROOT_FOLDER); - } - return builder.toString(); - } - } diff --git a/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java b/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java index ec131a835..45e04c610 100644 --- a/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java +++ b/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java @@ -18,6 +18,8 @@ import com.newsblur.activity.NbActivity; import com.newsblur.database.BlurDatabaseHelper; import static com.newsblur.database.BlurDatabaseHelper.closeQuietly; import com.newsblur.database.DatabaseConstants; +import com.newsblur.domain.Feed; +import com.newsblur.domain.Folder; import com.newsblur.domain.SocialFeed; import com.newsblur.domain.Story; import com.newsblur.network.APIConstants; @@ -385,7 +387,7 @@ public class NBSyncService extends Service { NbActivity.updateAllActivities(false); // there is a rare issue with feeds that have no folder. capture them for workarounds. - Set debugFeedIds = new HashSet(); + Set debugFeedIds = new HashSet(); orphanFeedIds = new HashSet(); try { @@ -420,42 +422,27 @@ public class NBSyncService extends Service { // data for the folder and folder-feed-mapping tables List folderValues = new ArrayList(); - List ffmValues = new ArrayList(); - for (Entry> entry : feedResponse.folders.entrySet()) { - if (!TextUtils.isEmpty(entry.getKey())) { - String folderName = entry.getKey().trim(); - if (!TextUtils.isEmpty(folderName)) { - ContentValues values = new ContentValues(); - values.put(DatabaseConstants.FOLDER_NAME, folderName); - folderValues.add(values); - } - - for (Long feedId : entry.getValue()) { - ContentValues values = new ContentValues(); - values.put(DatabaseConstants.FEED_FOLDER_FEED_ID, feedId); - values.put(DatabaseConstants.FEED_FOLDER_FOLDER_NAME, folderName); - ffmValues.add(values); - // note all feeds that belong to some folder - debugFeedIds.add(Long.toString(feedId)); - } - } + for (Folder folder : feedResponse.folders) { + folderValues.add(folder.getValues()); + // note all feeds that belong to some folder + debugFeedIds.addAll(folder.feedIds); } // data for the feeds table List feedValues = new ArrayList(); - feedaddloop: for (String feedId : feedResponse.feeds.keySet()) { + feedaddloop: for (Feed feed : feedResponse.feeds) { // sanity-check that the returned feeds actually exist in a folder or at the root // if they do not, they should neither display nor count towards unread numbers - if (! debugFeedIds.contains(feedId)) { - Log.w(this.getClass().getName(), "Found and ignoring un-foldered feed: " + feedId ); - orphanFeedIds.add(feedId); + if (! debugFeedIds.contains(feed.feedId)) { + Log.w(this.getClass().getName(), "Found and ignoring un-foldered feed: " + feed.feedId ); + orphanFeedIds.add(feed.feedId); continue feedaddloop; } - if (! feedResponse.feeds.get(feedId).active) { + if (! feed.active) { // the feed is disabled/hidden, pretend it doesn't exist continue feedaddloop; } - feedValues.add(feedResponse.feeds.get(feedId).getValues()); + feedValues.add(feed.getValues()); } // data for the the social feeds table @@ -464,7 +451,7 @@ public class NBSyncService extends Service { socialFeedValues.add(feed.getValues()); } - dbHelper.insertFeedsFolders(feedValues, folderValues, ffmValues, socialFeedValues); + dbHelper.insertFeedsFolders(folderValues, feedValues, socialFeedValues); // populate the starred stories count table dbHelper.updateStarredStoriesCount(feedResponse.starredCount); diff --git a/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java b/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java index 442dd586a..1547289fd 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java @@ -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; diff --git a/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java b/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java index d9f960343..ea281749b 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java @@ -21,6 +21,7 @@ import com.newsblur.activity.NbActivity; import com.newsblur.database.BlurDatabaseHelper; import com.newsblur.domain.Classifier; import com.newsblur.domain.Feed; +import com.newsblur.domain.Folder; import com.newsblur.domain.SocialFeed; import com.newsblur.domain.Story; import com.newsblur.network.APIManager; @@ -200,8 +201,10 @@ public class FeedUtils { context.startActivity(Intent.createChooser(intent, "Share using")); } - public static FeedSet feedSetFromFolderName(String folderName, Context context) { - Set feedIds = dbHelper.getFeedsForFolder(folderName); + public static FeedSet feedSetFromFolderName(String folderName) { + Folder folder = dbHelper.getFolder(folderName); + Set feedIds = new HashSet(folder.feedIds.size()); + for (Long id : folder.feedIds) feedIds.add(Long.toString(id)); return FeedSet.folder(folderName, feedIds); }