/** * Copyright (c) 2013, Redsolution LTD. All rights reserved. * * This file is part of Xabber project; you can redistribute it and/or * modify it under the terms of the GNU General Public License, Version 3. * * Xabber is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License, * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.xabber.android.data.database.sqlite; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.xabber.android.data.database.DatabaseManager; import com.xabber.android.data.database.messagerealm.MessageItem; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.UserJid; import com.xabber.android.data.message.ChatAction; import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.stringprep.XmppStringprepException; /** * Storage with messages. * * @author alexander.ivanov */ public class MessageTable extends AbstractEntityTable { private static final class Fields implements AbstractEntityTable.Fields { private Fields() { } /** * Message archive collection tag. */ public static final String TAG = "tag"; /** * User's resource or nick in chat room. */ public static final String RESOURCE = "resource"; /** * Text message. */ public static final String TEXT = "text"; /** * Message action. * <ul> * <li>Must be empty string for usual text message.</li> * <li>Must be one of names in MessageAction.</li> * </ul> * <p/> * {@link #TEXT} can contains some description on this action. */ public static final String ACTION = "action"; /** * Time when this message was created locally. */ public static final String TIMESTAMP = "timestamp"; /** * Receive and send delay. */ public static final String DELAY_TIMESTAMP = "delay_timestamp"; /** * Whether message is incoming. */ public static final String INCOMING = "incoming"; /** * Whether incoming message was read. */ public static final String READ = "read"; /** * Whether this outgoing message was sent. */ public static final String SENT = "sent"; /** * Whether this outgoing message was not received. */ public static final String ERROR = "error"; /** * usual message stanza (packet) id */ public static final String STANZA_ID = "stanza_id"; /** * Unique and Stable Stanza ID (XEP-0359) */ public static final String UNIQUE_STANZA_ID = "unique_stanza_id"; /** * If message was received from server message archive (XEP-0313) */ public static final String IS_RECEIVED_FROM_MESSAGE_ARCHIVE = "is_received_from_message_archive"; } private static final String NAME = "messages"; private static final String[] PROJECTION = new String[]{Fields._ID, Fields.ACCOUNT, Fields.USER, Fields.RESOURCE, Fields.TEXT, Fields.ACTION, Fields.TIMESTAMP, Fields.DELAY_TIMESTAMP, Fields.INCOMING, Fields.READ, Fields.SENT, Fields.ERROR, Fields.TAG, Fields.STANZA_ID, Fields.UNIQUE_STANZA_ID, Fields.IS_RECEIVED_FROM_MESSAGE_ARCHIVE}; private final DatabaseManager databaseManager; private static MessageTable instance; public static MessageTable getInstance() { if (instance == null) { instance = new MessageTable(DatabaseManager.getInstance()); } return instance; } private MessageTable(DatabaseManager databaseManager) { this.databaseManager = databaseManager; } @Override public void create(SQLiteDatabase db) { String sql; sql = "CREATE TABLE " + NAME + " (" + Fields._ID + " INTEGER PRIMARY KEY," + Fields.ACCOUNT + " TEXT," + Fields.USER + " TEXT," + Fields.RESOURCE + " TEXT," + Fields.TEXT + " TEXT," + Fields.ACTION + " TEXT," + Fields.TIMESTAMP + " INTEGER," + Fields.DELAY_TIMESTAMP + " INTEGER," + Fields.INCOMING + " BOOLEAN," + Fields.READ + " BOOLEAN," + Fields.SENT + " BOOLEAN," + Fields.ERROR + " BOOLEAN," + Fields.TAG + " TEXT, " + Fields.STANZA_ID + " TEXT, " + Fields.UNIQUE_STANZA_ID + " TEXT," + Fields.IS_RECEIVED_FROM_MESSAGE_ARCHIVE + " BOOLEAN" + ");"; DatabaseManager.execSQL(db, sql); sql = "CREATE INDEX " + NAME + "_list ON " + NAME + " (" + Fields.ACCOUNT + ", " + Fields.USER + ", " + Fields.TIMESTAMP + " ASC)"; DatabaseManager.execSQL(db, sql); } @Override public void migrate(SQLiteDatabase db, int toVersion) { super.migrate(db, toVersion); String sql; switch (toVersion) { case 4: sql = "CREATE TABLE messages (_id INTEGER PRIMARY KEY," + "account INTEGER," + "user TEXT," + "text TEXT," + "timestamp INTEGER," + "delay_timestamp INTEGER," + "incoming BOOLEAN," + "read BOOLEAN," + "notified BOOLEAN);"; DatabaseManager.execSQL(db, sql); sql = "CREATE INDEX messages_list ON messages (account, user, timestamp ASC);"; DatabaseManager.execSQL(db, sql); break; case 8: DatabaseManager.dropTable(db, "messages"); sql = "CREATE TABLE messages (_id INTEGER PRIMARY KEY," + "account TEXT," + "user TEXT," + "text TEXT," + "timestamp INTEGER," + "delay_timestamp INTEGER," + "incoming BOOLEAN," + "read BOOLEAN," + "notified BOOLEAN);"; DatabaseManager.execSQL(db, sql); sql = "CREATE INDEX messages_list ON messages (account, user, timestamp ASC);"; DatabaseManager.execSQL(db, sql); break; case 10: sql = "ALTER TABLE messages ADD COLUMN send BOOLEAN;"; DatabaseManager.execSQL(db, sql); sql = "ALTER TABLE messages ADD COLUMN error BOOLEAN;"; DatabaseManager.execSQL(db, sql); sql = "UPDATE messages SET send = 1, error = 0 WHERE incoming = 0;"; DatabaseManager.execSQL(db, sql); break; case 15: sql = "UPDATE messages SET send = 1 WHERE incoming = 1;"; DatabaseManager.execSQL(db, sql); break; case 17: sql = "ALTER TABLE messages ADD COLUMN save BOOLEAN;"; DatabaseManager.execSQL(db, sql); sql = "UPDATE messages SET save = 1;"; DatabaseManager.execSQL(db, sql); break; case 23: sql = "ALTER TABLE messages ADD COLUMN resource TEXT;"; DatabaseManager.execSQL(db, sql); sql = "UPDATE messages SET resource = \"\";"; DatabaseManager.execSQL(db, sql); sql = "ALTER TABLE messages ADD COLUMN action TEXT;"; DatabaseManager.execSQL(db, sql); sql = "UPDATE messages SET action = \"\";"; DatabaseManager.execSQL(db, sql); break; case 27: DatabaseManager.renameTable(db, "messages", "old_messages"); sql = "CREATE TABLE messages (_id INTEGER PRIMARY KEY," + "account TEXT," + "user TEXT," + "resource TEXT," + "text TEXT," + "action TEXT," + "timestamp INTEGER," + "delay_timestamp INTEGER," + "incoming BOOLEAN," + "read BOOLEAN," + "notified BOOLEAN," + "send BOOLEAN," + "error BOOLEAN);"; DatabaseManager.execSQL(db, sql); sql = "INSERT INTO messages (" + "account, user, resource, text, action, timestamp, delay_timestamp, incoming, read, notified, send, error" + ") SELECT " + "account, user, resource, text, action, timestamp, delay_timestamp, incoming, read, notified, send, error" + " FROM old_messages WHERE save;"; DatabaseManager.execSQL(db, sql); DatabaseManager.dropTable(db, "old_messages"); // Create index after drop old index. sql = "CREATE INDEX messages_list ON messages (account, user, timestamp ASC);"; DatabaseManager.execSQL(db, sql); break; case 28: DatabaseManager.renameTable(db, "messages", "old_messages"); sql = "CREATE TABLE messages (_id INTEGER PRIMARY KEY," + "account TEXT," + "user TEXT," + "resource TEXT," + "text TEXT," + "action TEXT," + "timestamp INTEGER," + "delay_timestamp INTEGER," + "incoming BOOLEAN," + "read BOOLEAN," + "notified BOOLEAN," + "sent BOOLEAN," + "error BOOLEAN);"; DatabaseManager.execSQL(db, sql); sql = "INSERT INTO messages (" + "account, user, resource, text, action, timestamp, delay_timestamp, incoming, read, notified, sent, error" + ") SELECT " + "account, user, resource, text, action, timestamp, delay_timestamp, incoming, read, notified, send, error" + " FROM old_messages;"; DatabaseManager.execSQL(db, sql); DatabaseManager.dropTable(db, "old_messages"); sql = "CREATE INDEX messages_list ON messages (account, user, timestamp ASC);"; DatabaseManager.execSQL(db, sql); break; case 58: sql = "ALTER TABLE messages ADD COLUMN tag TEXT;"; DatabaseManager.execSQL(db, sql); break; case 61: DatabaseManager.renameTable(db, "messages", "old_messages"); sql = "CREATE TABLE messages (_id INTEGER PRIMARY KEY," + "account TEXT," + "user TEXT," + "resource TEXT," + "text TEXT," + "action TEXT," + "timestamp INTEGER," + "delay_timestamp INTEGER," + "incoming BOOLEAN," + "read BOOLEAN," + "sent BOOLEAN," + "error BOOLEAN," + "tag TEXT);"; DatabaseManager.execSQL(db, sql); sql = "INSERT INTO messages (" + "account, user, resource, text, action, timestamp, delay_timestamp, incoming, read, sent, error, tag" + ") SELECT " + "account, user, resource, text, action, timestamp, delay_timestamp, incoming, read, sent, error, tag" + " FROM old_messages;"; DatabaseManager.execSQL(db, sql); DatabaseManager.dropTable(db, "old_messages"); sql = "CREATE INDEX messages_list ON messages (account, user, timestamp ASC);"; DatabaseManager.execSQL(db, sql); break; case 69: sql = "ALTER TABLE " + NAME + " ADD COLUMN " + Fields.STANZA_ID + " TEXT;"; DatabaseManager.execSQL(db, sql); sql = "ALTER TABLE " + NAME + " ADD COLUMN " + Fields.UNIQUE_STANZA_ID + " TEXT;"; DatabaseManager.execSQL(db, sql); break; case 70: sql = "ALTER TABLE " + NAME + " ADD COLUMN " + Fields.IS_RECEIVED_FROM_MESSAGE_ARCHIVE + " BOOLEAN;"; DatabaseManager.execSQL(db, sql); break; default: break; } } public static MessageItem createMessageItem(Cursor cursor) throws XmppStringprepException, UserJid.UserJidCreateException { MessageItem messageItem = new MessageItem(); messageItem.setAccount(AccountJid.from(getAccount(cursor))); messageItem.setUser(UserJid.from(getUser(cursor))); messageItem.setResource(Resourcepart.from(getResource(cursor))); messageItem.setText(getText(cursor)); ChatAction action = getAction(cursor); if (action != null) { messageItem.setAction(action.name()); } messageItem.setTimestamp(getTimeStamp(cursor)); messageItem.setDelayTimestamp(getDelayTimeStamp(cursor)); messageItem.setIncoming(isIncoming(cursor)); messageItem.setRead(isRead(cursor)); messageItem.setSent(isSent(cursor)); messageItem.setError(hasError(cursor)); messageItem.setStanzaId(getStanzaId(cursor)); messageItem.setReceivedFromMessageArchive(getReceivedFromMessageArchive(cursor)); return messageItem; } public Cursor getAllMessages() { SQLiteDatabase db = databaseManager.getReadableDatabase(); return db.query(NAME, PROJECTION, null, null, null, null, null); } public int removeAllMessages() { SQLiteDatabase db = databaseManager.getWritableDatabase(); return db.delete(NAME, null, null); } /** * Removes all read and sent messages. * * @param account */ void removeReadAndSent(String account) { SQLiteDatabase db = databaseManager.getWritableDatabase(); db.delete(NAME, Fields.ACCOUNT + " = ? AND " + Fields.READ + " = ? AND " + Fields.SENT + " = ?", new String[]{account, "1", "1"}); } /** * Removes all sent messages. * * @param account */ void removeSent(String account) { SQLiteDatabase db = databaseManager.getWritableDatabase(); db.delete(NAME, Fields.ACCOUNT + " = ? AND " + Fields.SENT + " = ?", new String[]{account, "1",}); } @Override protected String getTableName() { return NAME; } @Override protected String[] getProjection() { return PROJECTION; } static String getResource(Cursor cursor) { return cursor.getString(cursor.getColumnIndex(Fields.RESOURCE)); } static String getText(Cursor cursor) { return cursor.getString(cursor.getColumnIndex(Fields.TEXT)); } static ChatAction getAction(Cursor cursor) { return ChatAction.getChatAction(cursor.getString(cursor .getColumnIndex(Fields.ACTION))); } static boolean isIncoming(Cursor cursor) { return cursor.getInt(cursor.getColumnIndex(Fields.INCOMING)) != 0; } static boolean isSent(Cursor cursor) { return cursor.getInt(cursor.getColumnIndex(Fields.SENT)) != 0; } static boolean isRead(Cursor cursor) { return cursor.getInt(cursor.getColumnIndex(Fields.READ)) != 0; } static boolean hasError(Cursor cursor) { return cursor.getInt(cursor.getColumnIndex(Fields.ERROR)) != 0; } static Long getTimeStamp(Cursor cursor) { return cursor.getLong(cursor.getColumnIndex(Fields.TIMESTAMP)); } static Long getDelayTimeStamp(Cursor cursor) { if (cursor.isNull(cursor.getColumnIndex(Fields.DELAY_TIMESTAMP))) { return null; } return cursor.getLong(cursor.getColumnIndex(Fields.DELAY_TIMESTAMP)); } static String getStanzaId(Cursor cursor) { if (cursor.isNull(cursor.getColumnIndex(Fields.STANZA_ID))) { return null; } else { return cursor.getString(cursor.getColumnIndex(Fields.STANZA_ID)); } } static boolean getReceivedFromMessageArchive(Cursor cursor) { return cursor.getInt(cursor.getColumnIndex(Fields.IS_RECEIVED_FROM_MESSAGE_ARCHIVE)) != 0; } }