package com.airlocksoftware.hackernews.model; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.text.Html; import android.text.Spanned; import android.util.Log; import com.airlocksoftware.database.DbUtils; import com.airlocksoftware.database.SqlObject; import com.airlocksoftware.hackernews.activity.LoginActivity; import com.airlocksoftware.hackernews.activity.LoginActivity.PostAction; import com.airlocksoftware.hackernews.cache.DbHelperSingleton; import com.airlocksoftware.hackernews.data.UserPrefs; import com.airlocksoftware.hackernews.loader.AsyncVotingService; import java.util.ArrayList; import java.util.List; @SuppressWarnings("serial") /** Encapsulates data about a comment. Since it's extending SqlObject, any public, non-static, * non-transient field is cached in the database**/ public class Comment extends SqlObject { public long commentId; public String username; public String ago; public String html; public int depth; public String replyUrl; public String whence; public String auth; public boolean isUpvoted = false; public long storyId; public static final String COMMENT_ID = "commentId"; public static final String USERNAME = "username"; public static final String AGO = "ago"; public static final String HTML = "html"; public static final String DEPTH = "depth"; public static final String REPLY_URL = "replyUrl"; public static final String WHENCE = "go_to"; public static final String AUTH = "auth"; public static final String IS_UPVOTED = "isUpvoted"; public static final String IS_FOLDED = "isFolded"; public static final String CHILD_COUNT = "childCount"; public static final String STORY_ID = "storyId"; public static final long CACHE_EXPIRATION = 1000 * 60 * 30; // 30 minutes public static final long JAN_1_2012 = 1325376000000L; // expected minimum date private static final String TAG = Comment.class.getSimpleName(); // used to cache the generated html -- NOTE: transient tells the ORM not to store it private transient Spanned mSpannedHtml = null; // used to hold folded comments public transient boolean isFolded = false; public transient int mChildCount = 0; public transient List<Comment> mChildren; public Comment() { // default constructor } public Spanned generateSpannedHtml() { if (mSpannedHtml == null && html != null) mSpannedHtml = Html.fromHtml(html); return mSpannedHtml; } public boolean upvote(Context context) { UserPrefs data = new UserPrefs(context); if (data.isLoggedIn()) { // create the vote and save it to database Vote vote = new Vote(); vote.auth = auth; vote.username = data.getUsername(); vote.whence = whence; vote.itemId = commentId; SQLiteDatabase db = DbHelperSingleton.getInstance(context) .getWritableDatabase(); vote.create(db); // update comments upvote status isUpvoted = true; update(db); // run async voting service AsyncVotingService service = new AsyncVotingService(context); service.execute(); return true; } else { Intent intent = new Intent(context, LoginActivity.class); intent.putExtra(LoginActivity.POST_ACTION, PostAction.UPVOTE); intent.putExtra(LoginActivity.POST_COMMENT, this); context.startActivity(intent); return false; } } public boolean create(SQLiteDatabase db) { return super.createAndGenerateId(db); } /** Loads a comment from the cache base on on it's commentId **/ public static Comment readFromCommentId(SQLiteDatabase db, long cId) { Comment comment = new Comment(); Cursor c = db.query(comment.getTableName(), comment.getColNames(), COMMENT_ID + "=?", new String[] { Long.toString(cId) }, null, null, null); if (c.moveToFirst()) { comment.readFromCursor(c); } c.close(); return comment; } /** Loads a list of comments form the cache based on the parent storyId. **/ public static List<Comment> getFromCache(SQLiteDatabase db, long sId) { Comment firstComment = new Comment(); List<Comment> comments = null; Cursor c = db.query(firstComment.getTableName(), firstComment.getColNames(), STORY_ID + "=?", new String[] { Long.toString(sId) }, null, null, null); if (c.moveToFirst()) { comments = new ArrayList<Comment>(); firstComment.readFromCursor(c); comments.add(firstComment); c.moveToNext(); for (int i = 1; i < c.getCount(); i++) { Comment comment = new Comment(); comment.readFromCursor(c); comments.add(comment); c.moveToNext(); } } c.close(); return comments; } /** Deletes any cached comment rows matching storyId **/ public static void clearCache(SQLiteDatabase db, String sId) { Comment comment = new Comment(); db.delete(comment.getTableName(), STORY_ID + "=?", new String[] { sId }); } /** * Adds these values to the cache, and deletes any expired comments * * @param timestamp **/ public static void cacheValues(SQLiteDatabase db, List<Comment> comments, CommentsTimestamp timestamp) { // make sure we have a comment to run queries against if (comments == null || comments.size() < 1) return; Comment first = comments.get(0); if (first == null) first = new Comment(); // delete any old comments (Timestamp.TIME < System.currentTimeMillis() - CACHE_EXPIRATION) Cursor c = db.query(timestamp.getTableName(), timestamp.getColNames(), StoryTimestamp.TIME + "<? AND " + StoryTimestamp.TIME + ">? ", new String[] { Long.toString(System.currentTimeMillis() - CACHE_EXPIRATION), Long.toString(JAN_1_2012) }, null, null, null); if (c.moveToFirst()) { for (int i = 1; i < c.getCount(); i++) { StoryTimestamp ts = new StoryTimestamp(); ts.readFromCursor(c); c.moveToNext(); Comment.clearCache(db, ts.secondaryId); ts.delete(db); Log.d(TAG, "Deleting comments with storyId=" + ts.secondaryId); } } // delete old StoryTimestamp toDelete = new StoryTimestamp(); toDelete.id = DbUtils.getId(db, timestamp.getTableName(), StoryTimestamp.SECONDARY_ID, timestamp.secondaryId); toDelete.delete(db); // create new timestamp.create(db); // should replace old value or insert it // cache new comments for (Comment comment : comments) { comment.create(db); } } }