diff --git a/src/org/thoughtcrime/securesms/ImageMediaAdapter.java b/src/org/thoughtcrime/securesms/ImageMediaAdapter.java index bd037ff8bb..200fe28a86 100644 --- a/src/org/thoughtcrime/securesms/ImageMediaAdapter.java +++ b/src/org/thoughtcrime/securesms/ImageMediaAdapter.java @@ -72,7 +72,7 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter { part.setDataUri(imageRecord.getUri()); part.setContentType(imageRecord.getContentType().getBytes()); - part.setId(imageRecord.getPartId()); + part.setPartId(imageRecord.getPartId()); Slide slide = MediaUtil.getSlideForPart(getContext(), masterSecret, part, imageRecord.getContentType()); if (slide != null) { diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index 161d813927..7c9451857c 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -60,7 +60,8 @@ public class DatabaseFactory { private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14; private static final int INTRODUCED_THUMBNAILS_VERSION = 15; private static final int INTRODUCED_IDENTITY_COLUMN_VERSION = 16; - private static final int DATABASE_VERSION = 16; + private static final int INTRODUCED_UNIQUE_PART_IDS_VERSION = 17; + private static final int DATABASE_VERSION = 17; private static final String DATABASE_NAME = "messages.db"; private static final Object lock = new Object(); @@ -717,6 +718,10 @@ public class DatabaseFactory { db.execSQL("ALTER TABLE mms ADD COLUMN network_failures TEXT"); } + if (oldVersion < INTRODUCED_UNIQUE_PART_IDS_VERSION) { + db.execSQL("ALTER TABLE part ADD COLUMN unique_id INTEGER NOT NULL DEFAULT 0"); + } + db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 161d076388..70c24e7267 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -1171,11 +1171,11 @@ public class MmsDatabase extends MessagingDatabase { } } - private PduBody getPartsAsBody(List> parts) { + private PduBody getPartsAsBody(List parts) { PduBody body = new PduBody(); - for (Pair part : parts) { - body.addPart(part.second); + for (PduPart part : parts) { + body.addPart(part); } return body; diff --git a/src/org/thoughtcrime/securesms/database/PartDatabase.java b/src/org/thoughtcrime/securesms/database/PartDatabase.java index 234b64efa9..938f2036e4 100644 --- a/src/org/thoughtcrime/securesms/database/PartDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PartDatabase.java @@ -16,7 +16,6 @@ */ package org.thoughtcrime.securesms.database; -import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -30,7 +29,6 @@ import android.util.Pair; import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream; -import org.thoughtcrime.securesms.crypto.MasterCipher; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.util.BitmapDecodingException; @@ -60,7 +58,7 @@ public class PartDatabase extends Database { private static final String TAG = PartDatabase.class.getSimpleName(); private static final String TABLE_NAME = "part"; - private static final String ID = "_id"; + private static final String ROW_ID = "_id"; private static final String MMS_ID = "mid"; private static final String SEQUENCE = "seq"; private static final String CONTENT_TYPE = "ct"; @@ -78,27 +76,28 @@ public class PartDatabase extends Database { private static final String SIZE = "data_size"; private static final String THUMBNAIL = "thumbnail"; private static final String ASPECT_RATIO = "aspect_ratio"; + private static final String UNIQUE_ID = "unique_id"; - private static final String ID_CONTENT_WHERE = ID + " = ? AND " + CONTENT_ID + " = ?"; + private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?"; - public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " + MMS_ID + " INTEGER, " + SEQUENCE + " INTEGER DEFAULT 0, " + CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + CHARSET + " INTEGER, " + CONTENT_DISPOSITION + " TEXT, " + FILENAME + " TEXT, " + CONTENT_ID + " TEXT, " + CONTENT_LOCATION + " TEXT, " + CONTENT_TYPE_START + " INTEGER, " + CONTENT_TYPE_TYPE + " TEXT, " + ENCRYPTED + " INTEGER, " + PENDING_PUSH_ATTACHMENT + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " + - THUMBNAIL + " TEXT, " + ASPECT_RATIO + " REAL);"; + THUMBNAIL + " TEXT, " + ASPECT_RATIO + " REAL, " + UNIQUE_ID + " INTEGER NOT NULL);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", "CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + PENDING_PUSH_ATTACHMENT + ");", }; - private final static String IMAGES_QUERY = "SELECT " + TABLE_NAME + "." + ID + ", " + private final static String IMAGES_QUERY = "SELECT " + TABLE_NAME + "." + ROW_ID + ", " + TABLE_NAME + "." + CONTENT_TYPE + ", " + TABLE_NAME + "." + ASPECT_RATIO + ", " - + TABLE_NAME + "." + CONTENT_ID + ", " + + TABLE_NAME + "." + UNIQUE_ID + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.NORMALIZED_DATE_RECEIVED + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ADDRESS + " " + "FROM " + TABLE_NAME + " LEFT JOIN " + MmsDatabase.TABLE_NAME @@ -107,7 +106,7 @@ public class PartDatabase extends Database { + " FROM " + MmsDatabase.TABLE_NAME + " WHERE " + MmsDatabase.THREAD_ID + " = ?) AND " + CONTENT_TYPE + " LIKE 'image/%' " - + "ORDER BY " + TABLE_NAME + "." + ID + " DESC"; + + "ORDER BY " + TABLE_NAME + "." + ROW_ID + " DESC"; private final ExecutorService thumbnailExecutor = Util.newSingleThreadedLifoExecutor(); @@ -116,13 +115,13 @@ public class PartDatabase extends Database { super(context, databaseHelper); } - public InputStream getPartStream(MasterSecret masterSecret, long partId, byte[] contentId) + public InputStream getPartStream(MasterSecret masterSecret, PartId partId) throws FileNotFoundException { - return getDataStream(masterSecret, partId, contentId, DATA); + return getDataStream(masterSecret, partId, DATA); } - public void updateFailedDownloadedPart(long messageId, long partId, PduPart part) + public void updateFailedDownloadedPart(long messageId, PartId partId, PduPart part) throws MmsException { SQLiteDatabase database = databaseHelper.getWritableDatabase(); @@ -134,16 +133,16 @@ public class PartDatabase extends Database { values.put(DATA, (String)null); - database.update(TABLE_NAME, values, ID_WHERE, new String[] {partId+""}); + database.update(TABLE_NAME, values, PART_ID_WHERE, partId.toStrings()); notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); } - public PduPart getPart(long partId) { + public PduPart getPart(PartId partId) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); Cursor cursor = null; try { - cursor = database.query(TABLE_NAME, null, ID_WHERE, new String[] {partId+""}, null, null, null); + cursor = database.query(TABLE_NAME, null, PART_ID_WHERE, partId.toStrings(), null, null, null); if (cursor != null && cursor.moveToFirst()) return getPart(cursor); else return null; @@ -161,19 +160,17 @@ public class PartDatabase extends Database { return cursor; } - public List> getParts(long mmsId) { - SQLiteDatabase database = databaseHelper.getReadableDatabase(); - List> results = new LinkedList<>(); - Cursor cursor = null; + public List getParts(long mmsId) { + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + List results = new LinkedList<>(); + Cursor cursor = null; try { cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {mmsId+""}, null, null, null); while (cursor != null && cursor.moveToNext()) { - PduPart part = getPart(cursor); - results.add(new Pair<>(cursor.getLong(cursor.getColumnIndexOrThrow(ID)), - part)); + results.add(getPart(cursor)); } return results; @@ -228,14 +225,15 @@ public class PartDatabase extends Database { void insertParts(MasterSecret masterSecret, long mmsId, PduBody body) throws MmsException { for (int i=0;i partData = null; @@ -452,21 +450,22 @@ public class PartDatabase extends Database { contentValues.put(SIZE, partData.second); } - long partId = database.insert(TABLE_NAME, null, contentValues); + long partRowId = database.insert(TABLE_NAME, null, contentValues); + PartId partId = new PartId(partRowId, part.getUniqueId()); if (thumbnail != null) { Log.w(TAG, "inserting pre-generated thumbnail"); ThumbnailData data = new ThumbnailData(thumbnail); updatePartThumbnail(masterSecret, partId, part, data.toDataStream(), data.getAspectRatio()); } else if (!part.isPendingPush()) { - thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, partId, part.getContentId())); + thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, partId)); } return partId; } public void updateDownloadedPart(MasterSecret masterSecret, long messageId, - long partId, PduPart part, InputStream data) + PartId partId, PduPart part, InputStream data) throws MmsException { SQLiteDatabase database = databaseHelper.getWritableDatabase(); @@ -482,9 +481,9 @@ public class PartDatabase extends Database { values.put(SIZE, partData.second); } - database.update(TABLE_NAME, values, ID_WHERE, new String[]{partId+""}); + database.update(TABLE_NAME, values, PART_ID_WHERE, partId.toStrings()); - thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, partId, part.getContentId())); + thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, partId)); notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); } @@ -499,8 +498,8 @@ public class PartDatabase extends Database { Cursor cursor = null; try { - cursor = database.query(TABLE_NAME, new String[]{DATA}, ID_WHERE, - new String[]{part.getId()+""}, null, null, null); + cursor = database.query(TABLE_NAME, new String[]{DATA}, PART_ID_WHERE, + part.getPartId().toStrings(), null, null, null); if (cursor != null && cursor.moveToFirst()) { int dataColumn = cursor.getColumnIndexOrThrow(DATA); @@ -517,11 +516,11 @@ public class PartDatabase extends Database { part.setDataSize(partData.second); - database.update(TABLE_NAME, values, ID_WHERE, new String[] {part.getId()+""}); - Log.w(TAG, "updated data for part #" + part.getId()); + database.update(TABLE_NAME, values, PART_ID_WHERE, part.getPartId().toStrings()); + Log.w(TAG, "updated data for part #" + part.getPartId()); } - public void updatePartThumbnail(MasterSecret masterSecret, long partId, PduPart part, InputStream in, float aspectRatio) + public void updatePartThumbnail(MasterSecret masterSecret, PartId partId, PduPart part, InputStream in, float aspectRatio) throws MmsException { Log.w(TAG, "updating part thumbnail for #" + partId); @@ -534,17 +533,16 @@ public class PartDatabase extends Database { values.put(THUMBNAIL, thumbnailFile.first.getAbsolutePath()); values.put(ASPECT_RATIO, aspectRatio); - database.update(TABLE_NAME, values, ID_WHERE, new String[]{partId+""}); + database.update(TABLE_NAME, values, PART_ID_WHERE, partId.toStrings()); } public static class ImageRecord { - private long partId; - private byte[] contentId; + private PartId partId; private String contentType; private String address; private long date; - private ImageRecord(long partId, byte[] contentId, String contentType, String address, long date) { + private ImageRecord(PartId partId, String contentType, String address, long date) { this.partId = partId; this.contentType = contentType; this.address = address; @@ -552,14 +550,16 @@ public class PartDatabase extends Database { } public static ImageRecord from(Cursor cursor) { - return new ImageRecord(cursor.getLong(cursor.getColumnIndexOrThrow(ID)), - Util.toIsoBytes(cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_ID))), + PartId partId = new PartId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)), + cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))); + + return new ImageRecord(partId, cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)), cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)), cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)) * 1000); } - public long getPartId() { + public PartId getPartId() { return partId; } @@ -576,24 +576,22 @@ public class PartDatabase extends Database { } public Uri getUri() { - return PartAuthority.getPartUri(partId, contentId); + return PartAuthority.getPartUri(partId); } } @VisibleForTesting class ThumbnailFetchCallable implements Callable { private final MasterSecret masterSecret; - private final long partId; - private final byte[] contentId; + private final PartId partId; - public ThumbnailFetchCallable(MasterSecret masterSecret, long partId, byte[] contentId) { + public ThumbnailFetchCallable(MasterSecret masterSecret, PartId partId) { this.masterSecret = masterSecret; this.partId = partId; - this.contentId = contentId; } @Override public InputStream call() throws Exception { - final InputStream stream = getDataStream(masterSecret, partId, contentId, THUMBNAIL); + final InputStream stream = getDataStream(masterSecret, partId, THUMBNAIL); if (stream != null) { return stream; } @@ -609,7 +607,38 @@ public class PartDatabase extends Database { throw new IOException(bde); } - return getDataStream(masterSecret, partId, contentId, THUMBNAIL); + return getDataStream(masterSecret, partId, THUMBNAIL); + } + } + + public static class PartId { + + private final long rowId; + private final long uniqueId; + + public PartId(long rowId, long uniqueId) { + this.rowId = rowId; + this.uniqueId = uniqueId; + } + + public long getRowId() { + return rowId; + } + + public long getUniqueId() { + return uniqueId; + } + + public String[] toStrings() { + return new String[] {String.valueOf(rowId), String.valueOf(uniqueId)}; + } + + public String toString() { + return "(row id: " + rowId + ", unique ID: " + uniqueId + ")"; + } + + public boolean isValid() { + return rowId >= 0 && uniqueId >= 0; } } } diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index ce04e40766..4402aa4a17 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.util.Log; -import android.util.Pair; import org.thoughtcrime.securesms.crypto.MasterCipher; import org.thoughtcrime.securesms.crypto.MasterSecret; @@ -57,21 +56,21 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable Log.w(TAG, "Downloading push parts for: " + messageId); - List> parts = database.getParts(messageId); + List parts = database.getParts(messageId); - for (Pair partPair : parts) { - retrievePart(masterSecret, partPair.second, messageId, partPair.first); - Log.w(TAG, "Got part: " + partPair.first); + for (PduPart part : parts) { + retrievePart(masterSecret, part, messageId); + Log.w(TAG, "Got part: " + part.getPartId()); } } @Override public void onCanceled() { - PartDatabase database = DatabaseFactory.getPartDatabase(context); - List> parts = database.getParts(messageId); + PartDatabase database = DatabaseFactory.getPartDatabase(context); + List parts = database.getParts(messageId); - for (Pair partPair : parts) { - markFailed(messageId, partPair.second, partPair.first); + for (PduPart part : parts) { + markFailed(messageId, part, part.getPartId()); } } @@ -80,11 +79,12 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable return (exception instanceof PushNetworkException); } - private void retrievePart(MasterSecret masterSecret, PduPart part, long messageId, long partId) + private void retrievePart(MasterSecret masterSecret, PduPart part, long messageId) throws IOException { - PartDatabase database = DatabaseFactory.getPartDatabase(context); - File attachmentFile = null; + PartDatabase database = DatabaseFactory.getPartDatabase(context); + File attachmentFile = null; + PartDatabase.PartId partId = part.getPartId(); try { attachmentFile = createTempFile(); @@ -133,7 +133,7 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable } } - private void markFailed(long messageId, PduPart part, long partId) { + private void markFailed(long messageId, PduPart part, PartDatabase.PartId partId) { try { PartDatabase database = DatabaseFactory.getPartDatabase(context); database.updateFailedDownloadedPart(messageId, partId, part); diff --git a/src/org/thoughtcrime/securesms/jobs/SendJob.java b/src/org/thoughtcrime/securesms/jobs/SendJob.java index 51d62b5590..c4cca4e532 100644 --- a/src/org/thoughtcrime/securesms/jobs/SendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SendJob.java @@ -87,7 +87,7 @@ public abstract class SendJob extends MasterSecretJob { PduPart part) throws IOException, MmsException { - Log.w(TAG, "resizing part " + part.getId()); + Log.w(TAG, "resizing part " + part.getPartId()); final long oldSize = part.getDataSize(); final byte[] data = constraints.getResizedMedia(context, masterSecret, part); diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java index faf51a7b2a..38e7da6786 100644 --- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java +++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java @@ -46,7 +46,7 @@ public class ImageSlide extends Slide { if (!getPart().isPendingPush() && getPart().getDataUri() != null) { return isDraft() ? getPart().getDataUri() - : PartAuthority.getThumbnailUri(getPart().getId(), part.getContentId()); + : PartAuthority.getThumbnailUri(getPart().getPartId()); } return null; diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index 4b8f13e22b..aaa826aefa 100644 --- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -27,15 +27,11 @@ public class IncomingMediaMessage { private final String groupId; private final boolean push; - public IncomingMediaMessage(RetrieveConf retreived) { - this.headers = retreived.getPduHeaders(); - this.body = retreived.getBody(); + public IncomingMediaMessage(RetrieveConf retrieved) { + this.headers = retrieved.getPduHeaders(); + this.body = retrieved.getBody(); this.groupId = null; this.push = false; - - for (int i=0;i(); - setContentId(String.valueOf(System.currentTimeMillis()).getBytes()); + setUniqueId(System.currentTimeMillis()); } public void setEncrypted(boolean isEncrypted) { @@ -441,12 +443,21 @@ public class PduPart { } } - public long getId() { - return id; + public PartDatabase.PartId getPartId() { + return new PartDatabase.PartId(rowId, uniqueId); } - public void setId(long id) { - this.id = id; + public void setPartId(PartDatabase.PartId partId) { + this.rowId = partId.getRowId(); + this.uniqueId = partId.getUniqueId(); + } + + public long getRowId() { + return rowId; + } + + public void setRowId(long rowId) { + this.rowId = rowId; } public Bitmap getThumbnail() { @@ -456,5 +467,13 @@ public class PduPart { public void setThumbnail(Bitmap thumbnail) { this.thumbnail = thumbnail; } + + public long getUniqueId() { + return uniqueId; + } + + public void setUniqueId(long uniqueId) { + this.uniqueId = uniqueId; + } }