diff options
Diffstat (limited to 'src/main/java/de/thedevstack/conversationsplus/persistance')
5 files changed, 407 insertions, 2 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java b/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java new file mode 100644 index 00000000..7e3fdab0 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java @@ -0,0 +1,203 @@ +package de.thedevstack.conversationsplus.persistance; + +import android.database.Cursor; + +/** + * Created by steckbrief on 15.04.2016. + */ +public abstract class CursorHelper { + + static double getDouble(Cursor cursor, String columnName) { + if (null == cursor) { + return Double.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Double.MIN_VALUE; + } + return getDouble(cursor, columnIndex); + } + + static String getString(Cursor cursor, String columnName) { + if (null == cursor) { + return null; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return null; + } + return getString(cursor, columnIndex); + } + + static float getFloat(Cursor cursor, String columnName) { + if (null == cursor) { + return Float.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Float.MIN_VALUE; + } + return getFloat(cursor, columnIndex); + } + + static int getInt(Cursor cursor, String columnName) { + if (null == cursor) { + return Integer.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Integer.MIN_VALUE; + } + return getInt(cursor, columnIndex); + } + + static long getLong(Cursor cursor, String columnName) { + if (null == cursor) { + return Long.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Long.MIN_VALUE; + } + return getLong(cursor, columnIndex); + } + + static int getShort(Cursor cursor, String columnName) { + if (null == cursor) { + return Short.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Short.MIN_VALUE; + } + return getShort(cursor, columnIndex); + } + + static byte[] getBlob(Cursor cursor, String columnName) { + if (null == cursor) { + return null; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return null; + } + return getBlob(cursor, columnIndex); + } + + /** + * Returns the zero-based index for the given column name, or -1 if the column doesn't exist. + * If you expect the column to exist use {@link Cursor#getColumnIndexOrThrow(String)} instead, which + * will make the error more clear. + * + * @param columnName the name of the target column. + * @return the zero-based column index for the given column name, or -1 if + * the column name does not exist. + * @see Cursor#getColumnIndexOrThrow(String) + */ + static int getColumnIndex(Cursor cursor, String columnName) { + return cursor.getColumnIndex(columnName); + } + + /** + * Returns the value of the requested column as a byte array. + * + * <p>The result and whether this method throws an exception when the + * column value is null or the column type is not a blob type is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a byte array. + */ + static byte[] getBlob(Cursor cursor, int columnIndex) { + return cursor.getBlob(columnIndex); + } + + /** + * Returns the value of the requested column as a String. + * + * <p>The result and whether this method throws an exception when the + * column value is null or the column type is not a string type is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a String. + */ + static String getString(Cursor cursor, int columnIndex) { + return cursor.getString(columnIndex); + } + + /** + * Returns the value of the requested column as a short. + * + * <p>The result and whether this method throws an exception when the + * column value is null, the column type is not an integral type, or the + * integer value is outside the range [<code>Short.MIN_VALUE</code>, + * <code>Short.MAX_VALUE</code>] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a short. + */ + static short getShort(Cursor cursor, int columnIndex) { + return cursor.getShort(columnIndex); + } + + /** + * Returns the value of the requested column as an int. + * + * <p>The result and whether this method throws an exception when the + * column value is null, the column type is not an integral type, or the + * integer value is outside the range [<code>Integer.MIN_VALUE</code>, + * <code>Integer.MAX_VALUE</code>] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as an int. + */ + static int getInt(Cursor cursor, int columnIndex) { + return cursor.getInt(columnIndex); + } + + /** + * Returns the value of the requested column as a long. + * + * <p>The result and whether this method throws an exception when the + * column value is null, the column type is not an integral type, or the + * integer value is outside the range [<code>Long.MIN_VALUE</code>, + * <code>Long.MAX_VALUE</code>] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a long. + */ + static long getLong(Cursor cursor, int columnIndex) { + return cursor.getLong(columnIndex); + } + + /** + * Returns the value of the requested column as a float. + * + * <p>The result and whether this method throws an exception when the + * column value is null, the column type is not a floating-point type, or the + * floating-point value is not representable as a <code>float</code> value is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a float. + */ + static float getFloat(Cursor cursor, int columnIndex) { + return cursor.getFloat(columnIndex); + } + + /** + * Returns the value of the requested column as a double. + * + * <p>The result and whether this method throws an exception when the + * column value is null, the column type is not a floating-point type, or the + * floating-point value is not representable as a <code>double</code> value is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a double. + */ + static double getDouble(Cursor cursor, int columnIndex) { + return cursor.getDouble(columnIndex); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java index 4157d6f8..5a5746d5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java @@ -53,6 +53,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static final String DATABASE_NAME = "history"; private static final int DATABASE_VERSION = 25; + private static final int C_TO_CPLUS_VERSION_OFFSET = 1000; + private static final int CPLUS_DATABASE_VERSION = 1; + private static final int CPLUS_DATABASE_VERSION_MULTIPLIER = 100; + private static final int PHYSICAL_DATABASE_VERSION = DATABASE_VERSION + C_TO_CPLUS_VERSION_OFFSET + (CPLUS_DATABASE_VERSION * CPLUS_DATABASE_VERSION_MULTIPLIER); private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -129,8 +133,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { + ") ON CONFLICT IGNORE" + ");"; + private static int calculateCDatabaseVersion(int physicalDatabaseVersion) { + return physicalDatabaseVersion % CPLUS_DATABASE_VERSION_MULTIPLIER; + } + + private static int calculateCPLusDatabaseVersion(int physicalDatabaseVersion) { + int cPlusDatabaseVersion = (physicalDatabaseVersion - C_TO_CPLUS_VERSION_OFFSET) / CPLUS_DATABASE_VERSION_MULTIPLIER; + return cPlusDatabaseVersion < 0 ? 0 : cPlusDatabaseVersion; + } + private DatabaseBackend(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); + super(context, DATABASE_NAME, null, PHYSICAL_DATABASE_VERSION); } @Override @@ -176,10 +189,34 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_PREKEYS_STATEMENT); db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); db.execSQL(CREATE_IDENTITIES_STATEMENT); + + // Create Conversations+ related tables + db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE_V0); } + protected void onUpgradeCPlusDatabase(SQLiteDatabase db, int oldVersion, int newVersion) { + Logging.d("db.upgrade.cplus", "Updating Conversations+ database from version '" + oldVersion + "' to '" + newVersion + "'"); + if (oldVersion < newVersion) { + if (oldVersion == 0 && newVersion == 1) { + Logging.d("db.upgrade.cplus", "Creating additional parameters table for messages."); + db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE_V0); + db.execSQL("INSERT INTO " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + "(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") " + + " SELECT " + Message.UUID + " FROM " + Message.TABLENAME); + } + } + } + @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + onUpgradeConversationsDatabase(db, calculateCDatabaseVersion(oldVersion), calculateCDatabaseVersion(newVersion)); + onUpgradeCPlusDatabase(db, calculateCPLusDatabaseVersion(oldVersion), calculateCPLusDatabaseVersion(newVersion)); + } + + protected void onUpgradeConversationsDatabase(SQLiteDatabase db, int oldVersion, int newVersion) { + Logging.d("db.upgrade.conversations", "Updating Conversations database from version '" + oldVersion + "' to '" + newVersion + "'"); + if (oldVersion == newVersion) { + return; + } if (oldVersion < 2 && newVersion >= 2) { db.execSQL("update " + Account.TABLENAME + " set " + Account.OPTIONS + " = " + Account.OPTIONS + " | 8"); @@ -396,8 +433,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void createMessage(Message message) { + Logging.d("db.msg.insert", "Inserting new message with uuid '" + message.getUuid() + "', isRead: " + message.isRead()); + SQLiteDatabase db = this.getWritableDatabase(); + db.beginTransaction(); db.insert(Message.TABLENAME, null, message.getContentValues()); + db.insert(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, null, MessageDatabaseAccess.getAdditionalParametersContentValues(message)); + db.setTransactionSuccessful(); + db.endTransaction(); } public void createAccount(Account account) { @@ -471,6 +514,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { cursor.moveToLast(); do { Message message = Message.fromCursor(cursor); + MessageDatabaseAccess.populateMessageParameters(db, message); message.setConversation(conversation); list.add(message); } while (cursor.moveToPrevious()); @@ -502,6 +546,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { @Override public Message next() { Message message = Message.fromCursor(cursor); + MessageDatabaseAccess.populateMessageParameters(db, message); cursor.moveToNext(); return message; } @@ -598,17 +643,28 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void updateMessage(Message message) { + Logging.d("db.msg.update", "Updating message with uuid '" + message.getUuid() + "', isRead: " + message.isRead()); SQLiteDatabase db = this.getWritableDatabase(); String[] args = {message.getUuid()}; + db.beginTransaction(); db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args); + db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, MessageDatabaseAccess.getAdditionalParametersContentValues(message), MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", args); + db.setTransactionSuccessful(); + db.endTransaction(); } public void updateMessage(Message message, String uuid) { + Logging.d("db.msg.update", "Updating message with uuid '" + uuid + "', isRead: " + message.isRead()); + SQLiteDatabase db = this.getWritableDatabase(); String[] args = {uuid}; + db.beginTransaction(); db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args); + db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, MessageDatabaseAccess.getAdditionalParametersContentValues(message), MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", args); + db.setTransactionSuccessful(); + db.endTransaction(); } public void readRoster(Roster roster) { @@ -642,6 +698,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void deleteMessagesInConversation(Conversation conversation) { + Logging.d("db.msg.delete", "Deleting messages in conversation with uuid '" + conversation.getUuid()); + SQLiteDatabase db = this.getWritableDatabase(); String[] args = {conversation.getUuid()}; db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java index 5645caf6..9f7e473e 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java @@ -21,6 +21,7 @@ import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.exceptions.FileCopyException; +import de.thedevstack.conversationsplus.persistance.observers.FileDeletionObserver; import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; @@ -30,8 +31,34 @@ import de.thedevstack.conversationsplus.services.XmppConnectionService; public class FileBackend { private static final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); + private static FileBackend INSTANCE; - public static void createNoMedia() { + private FileDeletionObserver privateFilesDirectoryObserver; + private FileDeletionObserver privateFilesImageDirectoryObserver; + private FileDeletionObserver conversationsFilesDirectoryObserver; + private FileDeletionObserver conversationsImagesDirectoryObserver; + + public static void init() { + if (null == INSTANCE) { + INSTANCE = new FileBackend(); + } + INSTANCE.initFileObservers(); + INSTANCE.createNoMedia(); + } + + private void initFileObservers() { + this.privateFilesDirectoryObserver = new FileDeletionObserver(FileBackend.getPrivateFileDirectoryPath()); + this.privateFilesImageDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsFileDirectory()); + this.conversationsFilesDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsImageDirectory()); + this.conversationsImagesDirectoryObserver = new FileDeletionObserver(FileBackend.getPrivateImageDirectoryPath()); + + this.privateFilesDirectoryObserver.startWatching(); + this.privateFilesImageDirectoryObserver.startWatching(); + this.conversationsFilesDirectoryObserver.startWatching(); + this.conversationsImagesDirectoryObserver.startWatching(); + } + + private void createNoMedia() { final File nomedia = new File(getConversationsFileDirectory()+".nomedia"); if (!nomedia.exists()) { try { @@ -42,6 +69,19 @@ public class FileBackend { } } + public static void onFileTransferFolderChanged() { + INSTANCE.conversationsFilesDirectoryObserver.stopWatching(); + INSTANCE.conversationsFilesDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsFileDirectory()); + INSTANCE.conversationsFilesDirectoryObserver.startWatching(); + INSTANCE.createNoMedia(); + } + + public static void onImageTransferFolderChanged() { + INSTANCE.conversationsImagesDirectoryObserver.stopWatching(); + INSTANCE.conversationsImagesDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsImageDirectory()); + INSTANCE.conversationsImagesDirectoryObserver.startWatching(); + } + public static void updateMediaScanner(File file, XmppConnectionService xmppConnectionService) { if (file.getAbsolutePath().startsWith(getConversationsImageDirectory())) { Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java b/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java new file mode 100644 index 00000000..7776174d --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java @@ -0,0 +1,59 @@ +package de.thedevstack.conversationsplus.persistance; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; + +/** + * Created by steckbrief on 15.04.2016. + */ +public class MessageDatabaseAccess { + static final String TABLE_NAME_ADDITIONAL_PARAMETERS = "message_parameters"; + static final String COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD = "httpupload"; + static final String COLUMN_NAME_MSG_PARAMS_MSGUUID = "message_uuid"; + static final String COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION = "treatasdownloadable_decision"; + + static final String TABLE_ADDITIONAL_PARAMETERS_CREATE_V0 = "CREATE TABLE " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " (" + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + " TEXT, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD + " INTEGER DEFAULT 0, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION + " TEXT DEFAULT 'NOT_DECIDED', " + + "FOREIGN KEY(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") REFERENCES " + Message.TABLENAME + "(" + Message.UUID + ") ON DELETE CASCADE)"; + + static ContentValues getAdditionalParametersContentValues(Message message) { + ContentValues additionalParameters = new ContentValues(); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID, message.getUuid()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD, message.isHttpUploaded() ? 1 : 0); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION, message.treatAsDownloadable().name()); + + return additionalParameters; + } + + static void populateMessageParametersFromCursor(Cursor cursor, Message message) { + boolean isHttpUploaded = CursorHelper.getInt(cursor, COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD) == 1; + message.setHttpUploaded(isHttpUploaded); + String downloadable = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION); + Message.Decision treatAsDownloadable = Message.Decision.NOT_DECIDED; + try { + treatAsDownloadable = Message.Decision.valueOf(downloadable); + } catch (IllegalArgumentException e) { + // Should only happen if the database is corrupted, but to be on the save side catch it here + Logging.e("db.msg", "Unknown Decision for treatAsDownloadable found: '" + downloadable + "'"); + } + message.setTreatAsDownloadable(treatAsDownloadable); + } + + static void populateMessageParameters(SQLiteDatabase db, Message message) { + Cursor paramsCursor = db.query(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, + null, MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", + new String[] {message.getUuid()}, + null, null, null, null); + paramsCursor.moveToNext(); + MessageDatabaseAccess.populateMessageParametersFromCursor(paramsCursor, message); + paramsCursor.close(); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/observers/FileDeletionObserver.java b/src/main/java/de/thedevstack/conversationsplus/persistance/observers/FileDeletionObserver.java new file mode 100644 index 00000000..3534ccd2 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/observers/FileDeletionObserver.java @@ -0,0 +1,45 @@ +package de.thedevstack.conversationsplus.persistance.observers; + +import android.os.FileObserver; + +import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.Transferable; +import de.thedevstack.conversationsplus.entities.TransferablePlaceholder; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.UiUpdateHelper; +import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; + +/** + * Observer to mark messages containing files which are deleted. + */ +public class FileDeletionObserver extends FileObserver { + public FileDeletionObserver(String path) { + super(path, FileObserver.DELETE); + } + + @Override + public void onEvent(int event, String path) { + if (null != path) { + markFileDeleted(path.split("\\.")[0]); + } + } + + private void markFileDeleted(String uuid) { + if (null != XmppConnectionServiceAccessor.xmppConnectionService) { + for (Conversation conversation : XmppConnectionServiceAccessor.xmppConnectionService.getConversations()) { + Message message = conversation.findMessageWithFileAndUuid(uuid); + if (message != null) { + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + final int s = message.getStatus(); + if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { + MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED); + } else { + UiUpdateHelper.updateConversationUi(); + } + return; + } + } + } + } +} |