blob: 9545ec10f019cc958685bb01471d228abc1254c9 [file] [log] [blame]
package org.wordpress.android.datasets;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import org.wordpress.android.WordPress;
import org.wordpress.android.WordPressDB;
import org.wordpress.android.models.Comment;
import org.wordpress.android.models.CommentList;
import org.wordpress.android.models.CommentStatus;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.SqlUtils;
import org.wordpress.android.util.StringUtils;
/**
* replaces the comments table used in versions prior to 2.6.1, which didn't use a primary key
* and missed a few important fields
*/
public class CommentTable {
public static final String COMMENTS_TABLE = "comments";
public static void createTables(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + COMMENTS_TABLE + " ("
+ " blog_id INTEGER DEFAULT 0,"
+ " post_id INTEGER DEFAULT 0,"
+ " comment_id INTEGER DEFAULT 0,"
+ " comment TEXT,"
+ " published TEXT,"
+ " status TEXT,"
+ " author_name TEXT,"
+ " author_url TEXT,"
+ " author_email TEXT,"
+ " post_title TEXT,"
+ " profile_image_url TEXT,"
+ " PRIMARY KEY (blog_id, post_id, comment_id)"
+ " );");
}
private static void dropTables(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + COMMENTS_TABLE);
}
public static void reset(SQLiteDatabase db) {
AppLog.i(AppLog.T.COMMENTS, "resetting comment table");
dropTables(db);
createTables(db);
}
private static SQLiteDatabase getReadableDb() {
return WordPress.wpDB.getDatabase();
}
private static SQLiteDatabase getWritableDb() {
return WordPress.wpDB.getDatabase();
}
/*
* purge comments attached to blogs that no longer exist, and remove older comments
* TODO: call after hiding or deleting blogs
*/
private static final int MAX_COMMENTS = 1000;
public static int purge(SQLiteDatabase db) {
int numDeleted = 0;
// get rid of comments on blogs that don't exist or are hidden
String sql = " blog_id NOT IN (SELECT DISTINCT id FROM " + WordPressDB.BLOGS_TABLE
+ " WHERE isHidden = 0)";
numDeleted += db.delete(COMMENTS_TABLE, sql, null);
// get rid of older comments if we've reached the max
int numExisting = (int)SqlUtils.getRowCount(db, COMMENTS_TABLE);
if (numExisting > MAX_COMMENTS) {
int numToPurge = numExisting - MAX_COMMENTS;
sql = " comment_id IN (SELECT DISTINCT comment_id FROM " + COMMENTS_TABLE
+ " ORDER BY published LIMIT " + Integer.toString(numToPurge) + ")";
numDeleted += db.delete(COMMENTS_TABLE, sql, null);
}
return numDeleted;
}
/**
* add a single comment - will update existing comment with same IDs
* @param localBlogId - unique id in account table for the blog the comment is from
* @param comment - comment object to store
*/
public static void addComment(int localBlogId, final Comment comment) {
if (comment == null)
return;
ContentValues values = new ContentValues();
values.put("blog_id", localBlogId);
values.put("post_id", comment.postID);
values.put("comment_id", comment.commentID);
values.put("author_name", comment.getAuthorName());
values.put("author_url", comment.getAuthorUrl());
values.put("comment", SqlUtils.maxSQLiteText(comment.getCommentText()));
values.put("status", comment.getStatus());
values.put("author_email", comment.getAuthorEmail());
values.put("post_title", comment.getPostTitle());
values.put("published", comment.getPublished());
values.put("profile_image_url", comment.getProfileImageUrl());
getWritableDb().insertWithOnConflict(COMMENTS_TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
/**
* retrieve a single comment
* @param localBlogId - unique id in account table for the blog the comment is from
* @param commentId - commentId of the actual comment
* @return Comment if found, null otherwise
*/
public static Comment getComment(int localBlogId, long commentId) {
String[] args = {Integer.toString(localBlogId), Long.toString(commentId)};
Cursor c = getReadableDb().rawQuery("SELECT * FROM " + COMMENTS_TABLE + " WHERE blog_id=? AND comment_id=?", args);
try {
if (!c.moveToFirst()) {
return null;
}
return getCommentFromCursor(c);
} finally {
SqlUtils.closeCursor(c);
}
}
/**
* get all comments for a blog
* @param localBlogId - unique id in account table for this blog
* @return list of comments for this blog
*/
public static CommentList getCommentsForBlog(int localBlogId) {
CommentList comments = new CommentList();
String[] args = {Integer.toString(localBlogId)};
Cursor c = getReadableDb().rawQuery(
"SELECT * FROM " + COMMENTS_TABLE + " WHERE blog_id=? ORDER BY published DESC", args);
try {
while (c.moveToNext()) {
Comment comment = getCommentFromCursor(c);
comments.add(comment);
}
return comments;
} finally {
SqlUtils.closeCursor(c);
}
}
/**
* get comments for a blog that have a specific status
* @param localBlogId - unique id in account table for this blog
* @param filter - status to filter comments by
* @return list of comments for this blog
*/
public static CommentList getCommentsForBlogWithFilter(int localBlogId, CommentStatus filter) {
CommentList comments = new CommentList();
Cursor c;
//aggregating 'all' to include approved and unapproved comments
if (CommentStatus.UNKNOWN.equals(filter)){
//we need to get the filter values for both XMLrpc and REST api as in the case of a migration where existing
// data is present on a device, we still need to be able to filter both values
String[] args = {Integer.toString(localBlogId),
CommentStatus.toString(CommentStatus.APPROVED),
CommentStatus.toString(CommentStatus.UNAPPROVED),
CommentStatus.toRESTString(CommentStatus.APPROVED),
CommentStatus.toRESTString(CommentStatus.UNAPPROVED)};
c = getReadableDb().rawQuery(
"SELECT * FROM " + COMMENTS_TABLE + " WHERE blog_id=? AND (status=? OR status=? OR status=? OR status=?) ORDER BY published DESC", args);
} else {
//we need to get the filter values for both XMLrpc and REST api as in the case of a migration where existing
// data is present on a device, we still need to be able to filter both values
String[] args = {Integer.toString(localBlogId), CommentStatus.toString(filter), CommentStatus.toRESTString(filter)};
c = getReadableDb().rawQuery(
"SELECT * FROM " + COMMENTS_TABLE + " WHERE blog_id=? AND (status=? OR status=?) ORDER BY published DESC", args);
}
try {
while (c.moveToNext()) {
Comment comment = getCommentFromCursor(c);
comments.add(comment);
}
return comments;
} finally {
SqlUtils.closeCursor(c);
}
}
/**
* delete all comments for a blog
* @param localBlogId - unique id in account table for this blog
* @return number of comments deleted
*/
public static int deleteCommentsForBlog(int localBlogId) {
return getWritableDb().delete(COMMENTS_TABLE, "blog_id=?", new String[]{Integer.toString(localBlogId)});
}
/**
* delete comments for a blog that match a specific status
* @param localBlogId - unique id in account table for this blog
* @param filter - status to use to filter the query
* @return number of comments deleted
*/
public static int deleteCommentsForBlogWithFilter(int localBlogId, CommentStatus filter) {
if (CommentStatus.UNKNOWN.equals(filter)){
//we need to get the filter values for both XMLrpc and REST api as in the case of a migration where existing
// data is present on a device, we still need to be able to filter both values
String[] args = {Integer.toString(localBlogId),
CommentStatus.toString(CommentStatus.APPROVED),
CommentStatus.toString(CommentStatus.UNAPPROVED),
CommentStatus.toRESTString(CommentStatus.APPROVED),
CommentStatus.toRESTString(CommentStatus.UNAPPROVED)};
return getWritableDb().delete(COMMENTS_TABLE, "blog_id=? AND (status=? OR status=? OR status=? OR status=?)", args);
} else {
//we need to get the filter values for both XMLrpc and REST api as in the case of a migration where existing
// data is present on a device, we still need to be able to filter both values
String[] args = {Integer.toString(localBlogId), CommentStatus.toString(filter), CommentStatus.toRESTString(filter)};
return getWritableDb().delete(COMMENTS_TABLE, "blog_id=? AND (status=? OR status=?)", args);
}
}
/**
* saves comments for passed blog to local db, overwriting existing ones if necessary
* @param localBlogId - unique id in account table for this blog
* @param comments - list of comments to save
* @return true if saved, false on failure
*/
public static boolean saveComments(int localBlogId, final CommentList comments) {
if (comments == null || comments.size() == 0)
return false;
final String sql = " INSERT OR REPLACE INTO " + COMMENTS_TABLE + "("
+ " blog_id," // 1
+ " post_id," // 2
+ " comment_id," // 3
+ " comment," // 4
+ " published," // 5
+ " status," // 6
+ " author_name," // 7
+ " author_url," // 8
+ " author_email," // 9
+ " post_title," // 10
+ " profile_image_url" // 11
+ " ) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11)";
SQLiteDatabase db = getWritableDb();
SQLiteStatement stmt = db.compileStatement(sql);
db.beginTransaction();
try {
try {
for (Comment comment: comments) {
stmt.bindLong ( 1, localBlogId);
stmt.bindLong ( 2, comment.postID);
stmt.bindLong ( 3, comment.commentID);
stmt.bindString( 4, SqlUtils.maxSQLiteText(comment.getCommentText()));
stmt.bindString( 5, comment.getPublished());
stmt.bindString( 6, comment.getStatus());
stmt.bindString( 7, comment.getAuthorName());
stmt.bindString( 8, comment.getAuthorUrl());
stmt.bindString( 9, comment.getAuthorEmail());
stmt.bindString(10, comment.getPostTitle());
stmt.bindString(11, comment.getProfileImageUrl());
stmt.execute();
}
db.setTransactionSuccessful();
return true;
} catch (SQLiteException e) {
AppLog.e(AppLog.T.COMMENTS, e);
return false;
}
} finally {
db.endTransaction();
SqlUtils.closeStatement(stmt);
}
}
/**
* updates the passed comment
* @param localBlogId - unique id in account table for this blog
* @param comment - comment to update
*/
public static void updateComment(int localBlogId, final Comment comment) {
// this will replace the existing comment
addComment(localBlogId, comment);
}
/**
* updates the status for the passed comment
* @param localBlogId - unique id in account table for this blog
* @param commentId - id of comment (returned by api)
* @param newStatus - status to change to
*/
public static void updateCommentStatus(int localBlogId, long commentId, String newStatus) {
ContentValues values = new ContentValues();
values.put("status", newStatus);
String[] args = {Integer.toString(localBlogId),
Long.toString(commentId)};
getWritableDb().update(COMMENTS_TABLE, values, "blog_id=? AND comment_id=?", args);
}
/**
* updates the status for the passed list of comments
* @param localBlogId - unique id in account table for this blog
* @param comments - list of comments to update
* @param newStatus - status to change to
*/
public static void updateCommentsStatus(int localBlogId, final CommentList comments, String newStatus) {
if (comments == null || comments.size() == 0)
return;
getWritableDb().beginTransaction();
try {
for (Comment comment: comments) {
updateCommentStatus(localBlogId, comment.commentID, newStatus);
}
getWritableDb().setTransactionSuccessful();
} finally {
getWritableDb().endTransaction();
}
}
/**
* updates the post title for the passed comment
* @param localBlogId - unique id in account table for this blog
* @param postTitle - title to update to
* @return true if title updated
*/
public static boolean updateCommentPostTitle(int localBlogId, long commentId, String postTitle) {
ContentValues values = new ContentValues();
values.put("post_title", StringUtils.notNullStr(postTitle));
String[] args = {Integer.toString(localBlogId),
Long.toString(commentId)};
int count = getWritableDb().update(COMMENTS_TABLE, values, "blog_id=? AND comment_id=?", args);
return (count > 0);
}
/**
* delete a single comment
* @param localBlogId - unique id in account table for this blog
* @param commentId - commentId of the actual comment
* @return true if comment deleted, false otherwise
*/
public static boolean deleteComment(int localBlogId, long commentId) {
String[] args = {Integer.toString(localBlogId),
Long.toString(commentId)};
int count = getWritableDb().delete(COMMENTS_TABLE, "blog_id=? AND comment_id=?", args);
return (count > 0);
}
/**
* delete a list of comments
* @param localBlogId - unique id in account table for this blog
* @param comments - list of comments to delete
*/
public static void deleteComments(int localBlogId, final CommentList comments) {
if (comments == null || comments.size() == 0)
return;
getWritableDb().beginTransaction();
try {
for (Comment comment: comments) {
deleteComment(localBlogId, comment.commentID);
}
getWritableDb().setTransactionSuccessful();
} finally {
getWritableDb().endTransaction();
}
}
/**
* returns the number of unmoderated comments for a specific blog
* @param localBlogId - unique id in account table for this blog
*/
public static int getUnmoderatedCommentCount(int localBlogId) {
String sql = "SELECT COUNT(*) FROM " + COMMENTS_TABLE + " WHERE blog_id=? AND status=?";
String[] args = {Integer.toString(localBlogId), "hold"};
return SqlUtils.intForQuery(getReadableDb(), sql, args);
}
private static Comment getCommentFromCursor(Cursor c) {
final String authorName = c.getString(c.getColumnIndex("author_name"));
final String content = c.getString(c.getColumnIndex("comment"));
final String published = c.getString(c.getColumnIndex("published"));
final String status = c.getString(c.getColumnIndex("status"));
final String authorUrl = c.getString(c.getColumnIndex("author_url"));
final String authorEmail = c.getString(c.getColumnIndex("author_email"));
final String postTitle = c.getString(c.getColumnIndex("post_title"));
final String profileImageUrl = c.getString(c.getColumnIndex("profile_image_url"));
int postId = c.getInt(c.getColumnIndex("post_id"));
int commentId = c.getInt(c.getColumnIndex("comment_id"));
return new Comment(
postId,
commentId,
authorName,
published,
content,
status,
postTitle,
authorUrl,
authorEmail,
profileImageUrl);
}
/**
* Delete big comments (Maximum 512 * 1024 = 524288) (fix #2855)
* @return number of deleted comments
*/
public static int deleteBigComments(SQLiteDatabase db) {
return db.delete(COMMENTS_TABLE, "LENGTH(comment) >= 524288", null);
}
}