From 34fcdda53fa8ae1174909b62860534d2d874eb72 Mon Sep 17 00:00:00 2001 From: steckbrief Date: Thu, 29 Sep 2016 11:57:16 +0200 Subject: Implements FS#235: Deletion of remote files uploaded via httpupload --- .../persistance/CursorHelper.java | 203 --------------------- .../persistance/DatabaseBackend.java | 33 ++-- .../conversationsplus/persistance/FileBackend.java | 81 +++++--- .../persistance/MessageDatabaseAccess.java | 59 ------ .../db/access/AbstractDatabaseAccess.java | 21 +++ .../persistance/db/access/CursorHelper.java | 203 +++++++++++++++++++++ .../db/access/MessageDatabaseAccess.java | 180 ++++++++++++++++++ .../FileParamsBodyToDatabaseFieldsMigration.java | 101 ++++++++++ 8 files changed, 575 insertions(+), 306 deletions(-) delete mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/db/access/AbstractDatabaseAccess.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/db/access/CursorHelper.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java (limited to 'src/main/java/de/thedevstack/conversationsplus/persistance') diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java b/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java deleted file mode 100644 index 7e3fdab0..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java +++ /dev/null @@ -1,203 +0,0 @@ -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. - * - *

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. - * - *

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. - * - *

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 [Short.MIN_VALUE, - * Short.MAX_VALUE] 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. - * - *

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 [Integer.MIN_VALUE, - * Integer.MAX_VALUE] 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. - * - *

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 [Long.MIN_VALUE, - * Long.MAX_VALUE] 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. - * - *

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 float 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. - * - *

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 double 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 7c2583ae..e721afbb 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java @@ -35,6 +35,7 @@ import org.json.JSONException; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlServiceImpl; import de.thedevstack.conversationsplus.crypto.axolotl.SQLiteAxolotlStore; import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; @@ -44,6 +45,8 @@ import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.Roster; import de.thedevstack.conversationsplus.entities.ServiceDiscoveryResult; +import de.thedevstack.conversationsplus.persistance.db.access.CursorHelper; +import de.thedevstack.conversationsplus.persistance.db.access.MessageDatabaseAccess; import de.thedevstack.conversationsplus.utils.SimpleCryptoUtil; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -55,7 +58,7 @@ 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 = 2; + private static final int CPLUS_DATABASE_VERSION = 3; 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); @@ -191,18 +194,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); db.execSQL(CREATE_IDENTITIES_STATEMENT); - // Create Conversations+ related tables - db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE_V0); + MessageDatabaseAccess.create(db); } 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 < 1 && 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); + MessageDatabaseAccess.upgrade(db, oldVersion, newVersion); } if (oldVersion < 2 && newVersion >= 2) { Logging.d("db.upgrade.cplus", "Encrypt all passwords for the first time"); @@ -217,6 +216,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { } cursor.close(); } + + if (oldVersion < 3 && newVersion >= 3) { + MessageDatabaseAccess.upgrade(db, oldVersion, newVersion); + } } } @@ -434,6 +437,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { } } + public static synchronized DatabaseBackend getInstance() { + return getInstance(ConversationsPlusApplication.getAppContext()); + } + public static synchronized DatabaseBackend getInstance(Context context) { if (instance == null) { instance = new DatabaseBackend(context); @@ -657,15 +664,7 @@ 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(); + this.updateMessage(message, message.getUuid()); } public void updateMessage(Message message, String uuid) { @@ -676,7 +675,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { 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); + MessageDatabaseAccess.updateMessageParameters(db, message, uuid); db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java index e5597c3c..a35ed043 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java @@ -20,6 +20,7 @@ import java.util.Locale; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.exceptions.FileCopyException; import de.thedevstack.conversationsplus.persistance.observers.FileDeletionObserver; import de.thedevstack.conversationsplus.utils.StreamUtil; @@ -106,31 +107,55 @@ public class FileBackend { } public static DownloadableFile getFile(Message message, boolean decrypted) { - final boolean encrypted = !decrypted - && (message.getEncryption() == Message.ENCRYPTION_PGP - || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); - final DownloadableFile file; - String path = message.getRelativeFilePath(); - if (path == null) { - path = message.getUuid(); - } - if (path.startsWith("/")) { - file = new DownloadableFile(path); - } else { - String mime = message.getMimeType(); - if (mime != null && mime.startsWith("image")) { - file = new DownloadableFile(getConversationsImageDirectory() + path); - } else { - file = new DownloadableFile(getConversationsFileDirectory() + path); - } - } - if (encrypted) { - return new DownloadableFile(getConversationsFileDirectory() + file.getName() + ".pgp"); - } else { - return file; - } + return new DownloadableFile(getFilePath(message, decrypted)); } + protected static String getFilePath(Message message, String extension) { + String path = FileBackend.getFilePath(message, true); + if (!path.endsWith(extension)) { + path += "." + extension; + } + + return path; + } + + protected static String getFilePath(Message message, Uri uri) { + String mime = ConversationsPlusApplication.getInstance().getContentResolver().getType(uri); + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); + + return getFilePath(message, extension); + } + + protected static String getFilePath(Message message, boolean decrypted) { + final boolean encrypted = !decrypted + && (message.getEncryption() == Message.ENCRYPTION_PGP + || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); + FileParams fileParams = message.getFileParams(); + if (null == fileParams) { + fileParams = new FileParams(); + message.setFileParams(fileParams); + } + String path = fileParams.getPath(); + + if (null == path) { // File does not yet exist + path = message.getUuid(); + String mime = message.getMimeType(); + if (mime != null && mime.startsWith("image")) { // TODO: Check if this can be determined in a better way + path = getConversationsImageDirectory() + path; + } else { + path = getConversationsFileDirectory() + path; + } + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); + path += "." + extension; + + fileParams.setPath(path); + } + if (encrypted) { + path += ".pgp"; + } + return path; + } + public static String getConversationsFileDirectory() { return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + ConversationsPlusPreferences.fileTransferFolder() + File.separator; } @@ -175,14 +200,16 @@ public class FileBackend { public static void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage"); - String mime = ConversationsPlusApplication.getInstance().getContentResolver().getType(uri); - String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); - message.setRelativeFilePath(message.getUuid() + "." + extension); + String path = getFilePath(message, uri); + message.getFileParams().setPath(path); + message.setRelativeFilePath(path); // TODO: Remove copyFileToPrivateStorage(getFile(message), uri); } public static DownloadableFile compressImageAndCopyToPrivateStorage(Message message, Bitmap scaledBitmap) throws FileCopyException { - message.setRelativeFilePath(FileBackend.getPrivateImageDirectoryPath() + message.getUuid() + ".jpg"); + String path = getFilePath(message, "jpg"); + message.getFileParams().setPath(path); + message.setRelativeFilePath(path); // TODO: Remove DownloadableFile file = getFile(message); file.getParentFile().mkdirs(); OutputStream os = null; diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java b/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java deleted file mode 100644 index 7776174d..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java +++ /dev/null @@ -1,59 +0,0 @@ -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/db/access/AbstractDatabaseAccess.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/AbstractDatabaseAccess.java new file mode 100644 index 00000000..407859c7 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/AbstractDatabaseAccess.java @@ -0,0 +1,21 @@ +package de.thedevstack.conversationsplus.persistance.db.access; + +import android.database.sqlite.SQLiteDatabase; + +/** + * Created by steckbrief on 24.08.2016. + */ +public abstract class AbstractDatabaseAccess { + static void executeAlterStatements(SQLiteDatabase db, String... alterStatements) { + for (String alterStatement : alterStatements) { + db.execSQL(alterStatement); + } + } + + static void addNewColumns(SQLiteDatabase db, String tableName, String... columnDefinitions) { + for (String columnDefinition : columnDefinitions) { + String alterStatement = "ALTER TABLE " + tableName + " ADD COLUMN " + columnDefinition; + db.execSQL(alterStatement); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/CursorHelper.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/CursorHelper.java new file mode 100644 index 00000000..122e5160 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/CursorHelper.java @@ -0,0 +1,203 @@ +package de.thedevstack.conversationsplus.persistance.db.access; + +import android.database.Cursor; + +/** + * Created by steckbrief on 15.04.2016. + */ +public abstract class CursorHelper { + + public 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); + } + + public 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); + } + + public 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); + } + + public 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); + } + + public 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); + } + + public 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); + } + + public 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) + */ + public static int getColumnIndex(Cursor cursor, String columnName) { + return cursor.getColumnIndex(columnName); + } + + /** + * Returns the value of the requested column as a byte array. + * + *

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. + */ + public static byte[] getBlob(Cursor cursor, int columnIndex) { + return cursor.getBlob(columnIndex); + } + + /** + * Returns the value of the requested column as a String. + * + *

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. + */ + public static String getString(Cursor cursor, int columnIndex) { + return cursor.getString(columnIndex); + } + + /** + * Returns the value of the requested column as a short. + * + *

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 [Short.MIN_VALUE, + * Short.MAX_VALUE] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a short. + */ + public static short getShort(Cursor cursor, int columnIndex) { + return cursor.getShort(columnIndex); + } + + /** + * Returns the value of the requested column as an int. + * + *

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 [Integer.MIN_VALUE, + * Integer.MAX_VALUE] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as an int. + */ + public static int getInt(Cursor cursor, int columnIndex) { + return cursor.getInt(columnIndex); + } + + /** + * Returns the value of the requested column as a long. + * + *

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 [Long.MIN_VALUE, + * Long.MAX_VALUE] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a long. + */ + public static long getLong(Cursor cursor, int columnIndex) { + return cursor.getLong(columnIndex); + } + + /** + * Returns the value of the requested column as a float. + * + *

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 float value is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a float. + */ + public static float getFloat(Cursor cursor, int columnIndex) { + return cursor.getFloat(columnIndex); + } + + /** + * Returns the value of the requested column as a double. + * + *

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 double value is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a double. + */ + public static double getDouble(Cursor cursor, int columnIndex) { + return cursor.getDouble(columnIndex); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java new file mode 100644 index 00000000..51ad99a4 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java @@ -0,0 +1,180 @@ +package de.thedevstack.conversationsplus.persistance.db.access; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.entities.FileParams; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.enums.FileStatus; +import de.thedevstack.conversationsplus.persistance.db.migrations.FileParamsBodyToDatabaseFieldsMigration; + +/** + * + */ +public class MessageDatabaseAccess extends AbstractDatabaseAccess { + // since cplus db version 1 + public static final String TABLE_NAME_ADDITIONAL_PARAMETERS = "message_parameters"; + private static final String COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD = "httpupload"; + private static final String COLUMN_NAME_MSG_PARAMS_MSGUUID = "message_uuid"; + private static final String COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION = "treatasdownloadable_decision"; + // since cplus db version 3 + private static final String COLUMN_NAME_MSG_PARAMS_FILE_SIZE = "file_size"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_TYPE = "file_type"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_WIDTH = "file_width"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT = "file_height"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_STATUS = "file_status"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_NAME = "file_name"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_PATH = "file_path"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_URL = "file_url"; + + private static final String TABLE_ADDITIONAL_PARAMETERS_CREATE_V1 = "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)"; + + private static final String TABLE_ADDITIONAL_PARAMETERS_CREATE = "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', " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_TYPE + " TEXT DEFAULT NULL, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_SIZE + " INTEGER DEFAULT 0, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT + " INTEGER DEFAULT 0, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH + " INTEGER DEFAULT 0, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_STATUS + " TEXT DEFAULT 'UNDEFINED', " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_NAME + " TEXT DEFAULT NULL, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_PATH + " TEXT DEFAULT NULL, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_URL + " TEXT DEFAULT NULL, " + + "FOREIGN KEY(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") REFERENCES " + Message.TABLENAME + "(" + Message.UUID + ") ON DELETE CASCADE)"; + + public static void updateMessageParameters(SQLiteDatabase db, Message message, String uuid) { + String[] args = {uuid}; + db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, MessageDatabaseAccess.getAdditionalParametersContentValues(message), MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", args); + } + + public 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()); + if (null != message.getFileParams()) { + FileParams fileParams = message.getFileParams(); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_TYPE, fileParams.getMimeType()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_SIZE, fileParams.getSize()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT, fileParams.getHeight()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH, fileParams.getWidth()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_PATH, fileParams.getPath()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_URL, fileParams.getUrl()); + if (null != fileParams.getFileStatus()) { + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_STATUS, fileParams.getFileStatus().name()); + } + } + + return additionalParameters; + } + + private 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); + + if (message.hasFileAttached()) { + FileParams fileParams = new FileParams(message.getBody()); + String fileType = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_TYPE); + fileParams.setMimeType(fileType); + String name = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_NAME); + fileParams.setName(name); + String path = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_PATH); + fileParams.setPath(path); + String url = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_URL); + fileParams.setUrl(url); + long fileSize = CursorHelper.getLong(cursor, COLUMN_NAME_MSG_PARAMS_FILE_SIZE); + fileParams.setSize(fileSize); + int imageHeight = CursorHelper.getInt(cursor, COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT); + fileParams.setHeight(imageHeight); + int imageWidth = CursorHelper.getInt(cursor, COLUMN_NAME_MSG_PARAMS_FILE_WIDTH); + fileParams.setWidth(imageWidth); + String status = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_STATUS); + if (null != status && !status.isEmpty()) { + FileStatus fileStatus = FileStatus.UNDEFINED; + try { + fileStatus = FileStatus.valueOf(status); + } 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 FileStatus for fileParams.fileStatus found: '" + status + "'"); + } + fileParams.setFileStatus(fileStatus); + } + message.setFileParams(fileParams); + } + } + + public 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(); + } + + public static void upgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion < 1 && newVersion >= 1) { + Logging.d("db.upgrade.cplus", "Creating additional parameters table for messages."); + db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE_V1); + db.execSQL("INSERT INTO " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + "(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") " + + " SELECT " + Message.UUID + " FROM " + Message.TABLENAME); + } + if (oldVersion < 3 && newVersion >= 3) { + Logging.d("db.upgrade.cplus", "Upgrade " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS); + String[] columnDefinitions = { + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_TYPE + " TEXT DEFAULT NULL", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_SIZE + " INTEGER DEFAULT 0", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT + " INTEGER DEFAULT 0", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH + " INTEGER DEFAULT 0", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_STATUS + " TEXT DEFAULT 'UNDEFINED'", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_NAME + " TEXT DEFAULT NULL", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_PATH + " TEXT DEFAULT NULL", + }; + + addNewColumns(db, MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, columnDefinitions); + + Logging.d("db.upgrade.cplus", "Migrate file params from message body to database fields"); + Cursor cursor = db.rawQuery("SELECT " + Message.UUID + ", " + Message.BODY + " FROM " + Message.TABLENAME + " WHERE " + Message.TYPE + "=? OR " + Message.TYPE + "=?", new String[] {String.valueOf(Message.TYPE_FILE), String.valueOf(Message.TYPE_IMAGE)}); + while (cursor.moveToNext()) { + String uuid = CursorHelper.getString(cursor, Message.UUID); + String body = CursorHelper.getString(cursor, Message.BODY); + FileParams fileParams = FileParamsBodyToDatabaseFieldsMigration.getFileParams(body); + String newBody = fileParams.getUrl(); + ContentValues values = new ContentValues(); + values.put(Message.BODY, newBody); + db.update(Message.TABLENAME, values, Message.UUID + "=?", new String[] {uuid}); + + ContentValues parameterValues = new ContentValues(); + parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_SIZE, fileParams.getSize()); + parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT, fileParams.getHeight()); + parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH, fileParams.getWidth()); + + db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, parameterValues, MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", new String[] {uuid}); + } + cursor.close(); + } + } + + public static void create(SQLiteDatabase db) { + // Create Conversations+ related tables + db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java new file mode 100644 index 00000000..c0aa63c0 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java @@ -0,0 +1,101 @@ +package de.thedevstack.conversationsplus.persistance.db.migrations; + +import java.net.MalformedURLException; +import java.net.URL; + +import de.thedevstack.conversationsplus.entities.FileParams; + +/** + * Created by steckbrief on 24.08.2016. + */ +public class FileParamsBodyToDatabaseFieldsMigration { + + public static FileParams getFileParams(String body) { + FileParams params = getLegacyFileParams(body); + if (params != null) { + return params; + } + params = new FileParams(); + if (body == null) { + return params; + } + String parts[] = body.split("\\|"); + switch (parts.length) { + case 1: + try { + params.setSize(Long.parseLong(parts[0])); + } catch (NumberFormatException e) { + try { + URL url = new URL(parts[0]); + params.setUrl(url.toString()); + } catch (MalformedURLException e1) { + } + } + break; + case 2: + case 4: + try { + URL url = new URL(parts[0]); + params.setUrl(url.toString()); + } catch (MalformedURLException e1) { + } + try { + params.setSize(Long.parseLong(parts[1])); + } catch (NumberFormatException e) { + } + try { + params.setWidth(Integer.parseInt(parts[2])); + } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + } + try { + params.setHeight(Integer.parseInt(parts[3])); + } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + } + break; + case 3: + try { + params.setSize(Long.parseLong(parts[0])); + } catch (NumberFormatException e) { + } + try { + params.setWidth(Integer.parseInt(parts[1])); + } catch (NumberFormatException e) { + } + try { + params.setHeight(Integer.parseInt(parts[2])); + } catch (NumberFormatException e) { + } + break; + } + return params; + } + + private static FileParams getLegacyFileParams(String body) { + FileParams params = new FileParams(); + if (body == null) { + return params; + } + String parts[] = body.split(","); + if (parts.length == 3) { + try { + params.setSize(Long.parseLong(parts[0])); + } catch (NumberFormatException e) { + return null; + } + try { + params.setWidth(Integer.parseInt(parts[1])); + } catch (NumberFormatException e) { + return null; + } + try { + params.setHeight(Integer.parseInt(parts[2])); + } catch (NumberFormatException e) { + return null; + } + return params; + } else { + return null; + } + } + +} -- cgit v1.2.3