diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java index cfde38ba91..b709a7b532 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java @@ -35,10 +35,10 @@ public abstract class Attachment { private final String fastPreflightId; private final boolean voiceNote; - private final int width; - private final int height; - + private final int width; + private final int height; private final boolean quote; + private final long uploadTimestamp; @Nullable private final String caption; @@ -55,8 +55,9 @@ public abstract class Attachment { public Attachment(@NonNull String contentType, int transferState, long size, @Nullable String fileName, @Nullable String location, @Nullable String key, @Nullable String relay, @Nullable byte[] digest, @Nullable String fastPreflightId, boolean voiceNote, - int width, int height, boolean quote, @Nullable String caption, @Nullable StickerLocator stickerLocator, - @Nullable BlurHash blurHash, @Nullable TransformProperties transformProperties) + int width, int height, boolean quote, long uploadTimestamp, @Nullable String caption, + @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, + @Nullable TransformProperties transformProperties) { this.contentType = contentType; this.transferState = transferState; @@ -71,6 +72,7 @@ public abstract class Attachment { this.width = width; this.height = height; this.quote = quote; + this.uploadTimestamp = uploadTimestamp; this.stickerLocator = stickerLocator; this.caption = caption; this.blurHash = blurHash; @@ -147,6 +149,10 @@ public abstract class Attachment { return quote; } + public long getUploadTimestamp() { + return uploadTimestamp; + } + public boolean isSticker() { return stickerLocator != null; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java index b4e4fbc597..c62d82d827 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java @@ -28,9 +28,10 @@ public class DatabaseAttachment extends Attachment { byte[] digest, String fastPreflightId, boolean voiceNote, int width, int height, boolean quote, @Nullable String caption, @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, - @Nullable TransformProperties transformProperties, int displayOrder) + @Nullable TransformProperties transformProperties, int displayOrder, + long uploadTimestamp) { - super(contentType, transferProgress, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height, quote, caption, stickerLocator, blurHash, transformProperties); + super(contentType, transferProgress, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, transformProperties); this.attachmentId = attachmentId; this.hasData = hasData; this.hasThumbnail = hasThumbnail; diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java index a066b8e6d6..bb38d33bc5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java @@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase; public class MmsNotificationAttachment extends Attachment { public MmsNotificationAttachment(int status, long size) { - super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null, null, false, 0, 0, false, null, null, null, null); + super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null, null, false, 0, 0, false, 0, null, null, null, null); } @Nullable diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java index b0eb0cf33c..4b9c58b7b8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java @@ -21,10 +21,10 @@ public class PointerAttachment extends Attachment { @Nullable String fileName, @NonNull String location, @Nullable String key, @Nullable String relay, @Nullable byte[] digest, @Nullable String fastPreflightId, boolean voiceNote, - int width, int height, @Nullable String caption, @Nullable StickerLocator stickerLocator, + int width, int height, long uploadTimestamp, @Nullable String caption, @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash) { - super(contentType, transferState, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height, false, caption, stickerLocator, blurHash, null); + super(contentType, transferState, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null); } @Nullable @@ -100,6 +100,7 @@ public class PointerAttachment extends Attachment { pointer.get().asPointer().getVoiceNote(), pointer.get().asPointer().getWidth(), pointer.get().asPointer().getHeight(), + pointer.get().asPointer().getUploadTimestamp(), pointer.get().asPointer().getCaption().orNull(), stickerLocator, BlurHash.parseOrNull(pointer.get().asPointer().getBlurHash().orNull()))); @@ -121,6 +122,7 @@ public class PointerAttachment extends Attachment { false, thumbnail != null ? thumbnail.asPointer().getWidth() : 0, thumbnail != null ? thumbnail.asPointer().getHeight() : 0, + thumbnail != null ? thumbnail.asPointer().getUploadTimestamp() : 0, thumbnail != null ? thumbnail.asPointer().getCaption().orNull() : null, null, null)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java index 7791d2ebdd..cab3c04cac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java @@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase; public class TombstoneAttachment extends Attachment { public TombstoneAttachment(@NonNull String contentType, boolean quote) { - super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, null, null, null, null, null, false, 0, 0, quote, null, null, null, null); + super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, null, null, null, null, null, false, 0, 0, quote, 0, null, null, null, null); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java index 86aa5b8180..a9d29cf58c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java @@ -27,7 +27,7 @@ public class UriAttachment extends Attachment { boolean voiceNote, boolean quote, @Nullable String caption, @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, @Nullable TransformProperties transformProperties) { - super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId, voiceNote, width, height, quote, caption, stickerLocator, blurHash, transformProperties); + super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId, voiceNote, width, height, quote, 0, caption, stickerLocator, blurHash, transformProperties); this.dataUri = dataUri; this.thumbnailUri = thumbnailUri; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index e9e1e5c43b..5aded42db8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -123,6 +123,7 @@ public class AttachmentDatabase extends Database { static final String BLUR_HASH = "blur_hash"; static final String TRANSFORM_PROPERTIES = "transform_properties"; static final String DISPLAY_ORDER = "display_order"; + static final String UPLOAD_TIMESTAMP = "upload_timestamp"; public static final String DIRECTORY = "parts"; @@ -144,24 +145,46 @@ public class AttachmentDatabase extends Database { QUOTE, DATA_RANDOM, THUMBNAIL_RANDOM, WIDTH, HEIGHT, CAPTION, STICKER_PACK_ID, STICKER_PACK_KEY, STICKER_ID, DATA_HASH, BLUR_HASH, TRANSFORM_PROPERTIES, TRANSFER_FILE, - DISPLAY_ORDER }; + DISPLAY_ORDER, UPLOAD_TIMESTAMP }; - public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " + - MMS_ID + " INTEGER, " + "seq" + " INTEGER DEFAULT 0, " + - CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + "chset" + " INTEGER, " + - CONTENT_DISPOSITION + " TEXT, " + "fn" + " TEXT, " + "cid" + " TEXT, " + - CONTENT_LOCATION + " TEXT, " + "ctt_s" + " INTEGER, " + - "ctt_t" + " TEXT, " + "encrypted" + " INTEGER, " + - TRANSFER_STATE + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " + - FILE_NAME + " TEXT, " + THUMBNAIL + " TEXT, " + THUMBNAIL_ASPECT_RATIO + " REAL, " + - UNIQUE_ID + " INTEGER NOT NULL, " + DIGEST + " BLOB, " + FAST_PREFLIGHT_ID + " TEXT, " + - VOICE_NOTE + " INTEGER DEFAULT 0, " + DATA_RANDOM + " BLOB, " + THUMBNAIL_RANDOM + " BLOB, " + - QUOTE + " INTEGER DEFAULT 0, " + WIDTH + " INTEGER DEFAULT 0, " + HEIGHT + " INTEGER DEFAULT 0, " + - CAPTION + " TEXT DEFAULT NULL, " + STICKER_PACK_ID + " TEXT DEFAULT NULL, " + - STICKER_PACK_KEY + " DEFAULT NULL, " + STICKER_ID + " INTEGER DEFAULT -1, " + - DATA_HASH + " TEXT DEFAULT NULL, " + BLUR_HASH + " TEXT DEFAULT NULL, " + - TRANSFORM_PROPERTIES + " TEXT DEFAULT NULL, " + TRANSFER_FILE + " TEXT DEFAULT NULL, " + - DISPLAY_ORDER + " INTEGER DEFAULT 0);"; + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " + + MMS_ID + " INTEGER, " + + "seq" + " INTEGER DEFAULT 0, " + + CONTENT_TYPE + " TEXT, " + + NAME + " TEXT, " + + "chset" + " INTEGER, " + + CONTENT_DISPOSITION + " TEXT, " + + "fn" + " TEXT, " + + "cid" + " TEXT, " + + CONTENT_LOCATION + " TEXT, " + + "ctt_s" + " INTEGER, " + + "ctt_t" + " TEXT, " + + "encrypted" + " INTEGER, " + + TRANSFER_STATE + " INTEGER, " + + DATA + " TEXT, " + + SIZE + " INTEGER, " + + FILE_NAME + " TEXT, " + + THUMBNAIL + " TEXT, " + + THUMBNAIL_ASPECT_RATIO + " REAL, " + + UNIQUE_ID + " INTEGER NOT NULL, " + + DIGEST + " BLOB, " + + FAST_PREFLIGHT_ID + " TEXT, " + + VOICE_NOTE + " INTEGER DEFAULT 0, " + + DATA_RANDOM + " BLOB, " + + THUMBNAIL_RANDOM + " BLOB, " + + QUOTE + " INTEGER DEFAULT 0, " + + WIDTH + " INTEGER DEFAULT 0, " + + HEIGHT + " INTEGER DEFAULT 0, " + + CAPTION + " TEXT DEFAULT NULL, " + + STICKER_PACK_ID + " TEXT DEFAULT NULL, " + + STICKER_PACK_KEY + " DEFAULT NULL, " + + STICKER_ID + " INTEGER DEFAULT -1, " + + DATA_HASH + " TEXT DEFAULT NULL, " + + BLUR_HASH + " TEXT DEFAULT NULL, " + + TRANSFORM_PROPERTIES + " TEXT DEFAULT NULL, " + + TRANSFER_FILE + " TEXT DEFAULT NULL, " + + DISPLAY_ORDER + " INTEGER DEFAULT 0, " + + UPLOAD_TIMESTAMP + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", @@ -601,8 +624,9 @@ public class AttachmentDatabase extends Database { } - public void updateAttachmentAfterUpload(@NonNull AttachmentId id, @NonNull Attachment attachment) { + public void updateAttachmentAfterUpload(@NonNull AttachmentId id, @NonNull Attachment attachment, long uploadTimestamp) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); + DataInfo dataInfo = getAttachmentDataFileInfo(id, DATA); ContentValues values = new ContentValues(); values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE); @@ -613,8 +637,13 @@ public class AttachmentDatabase extends Database { values.put(SIZE, attachment.getSize()); values.put(FAST_PREFLIGHT_ID, attachment.getFastPreflightId()); values.put(BLUR_HASH, getBlurHashStringOrNull(attachment.getBlurHash())); + values.put(UPLOAD_TIMESTAMP, uploadTimestamp); - database.update(TABLE_NAME, values, PART_ID_WHERE, id.toStrings()); + if (dataInfo != null && dataInfo.hash != null) { + updateAttachmentAndMatchingHashes(database, id, dataInfo.hash, values); + } else { + database.update(TABLE_NAME, values, PART_ID_WHERE, id.toStrings()); + } } public @NonNull DatabaseAttachment insertAttachmentForPreUpload(@NonNull Attachment attachment) throws MmsException { @@ -1093,7 +1122,8 @@ public class AttachmentDatabase extends Database { : null, BlurHash.parseOrNull(object.getString(BLUR_HASH)), TransformProperties.parse(object.getString(TRANSFORM_PROPERTIES)), - object.getInt(DISPLAY_ORDER))); + object.getInt(DISPLAY_ORDER), + object.getLong(UPLOAD_TIMESTAMP))); } } @@ -1125,7 +1155,8 @@ public class AttachmentDatabase extends Database { : null, BlurHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(BLUR_HASH))), TransformProperties.parse(cursor.getString(cursor.getColumnIndexOrThrow(TRANSFORM_PROPERTIES))), - cursor.getInt(cursor.getColumnIndexOrThrow(DISPLAY_ORDER)))); + cursor.getInt(cursor.getColumnIndexOrThrow(DISPLAY_ORDER)), + cursor.getLong(cursor.getColumnIndexOrThrow(UPLOAD_TIMESTAMP)))); } } catch (JSONException e) { throw new AssertionError(e); @@ -1159,15 +1190,17 @@ public class AttachmentDatabase extends Database { } } + boolean useTemplateUpload = template.getUploadTimestamp() > attachment.getUploadTimestamp() && template.getTransferState() == TRANSFER_PROGRESS_DONE; + ContentValues contentValues = new ContentValues(); contentValues.put(MMS_ID, mmsId); contentValues.put(CONTENT_TYPE, template.getContentType()); contentValues.put(TRANSFER_STATE, attachment.getTransferState()); contentValues.put(UNIQUE_ID, uniqueId); - contentValues.put(CONTENT_LOCATION, attachment.getLocation()); - contentValues.put(DIGEST, attachment.getDigest()); - contentValues.put(CONTENT_DISPOSITION, attachment.getKey()); - contentValues.put(NAME, attachment.getRelay()); + contentValues.put(CONTENT_LOCATION, useTemplateUpload ? template.getLocation() : attachment.getLocation()); + contentValues.put(DIGEST, useTemplateUpload ? template.getDigest() : attachment.getDigest()); + contentValues.put(CONTENT_DISPOSITION, useTemplateUpload ? template.getKey() : attachment.getKey()); + contentValues.put(NAME, useTemplateUpload ? template.getRelay() : attachment.getRelay()); contentValues.put(FILE_NAME, StorageUtil.getCleanFileName(attachment.getFileName())); contentValues.put(SIZE, template.getSize()); contentValues.put(FAST_PREFLIGHT_ID, attachment.getFastPreflightId()); @@ -1176,12 +1209,13 @@ public class AttachmentDatabase extends Database { contentValues.put(HEIGHT, template.getHeight()); contentValues.put(QUOTE, quote); contentValues.put(CAPTION, attachment.getCaption()); + contentValues.put(UPLOAD_TIMESTAMP, useTemplateUpload ? template.getUploadTimestamp() : attachment.getUploadTimestamp()); if (attachment.getTransformProperties().isVideoEdited()) { contentValues.putNull(BLUR_HASH); contentValues.put(TRANSFORM_PROPERTIES, attachment.getTransformProperties().serialize()); thumbnailTimeUs = Math.max(STANDARD_THUMB_TIME, attachment.getTransformProperties().videoTrimStartTimeUs); } else { - contentValues.put(BLUR_HASH, getBlurHashStringOrNull(attachment.getBlurHash())); + contentValues.put(BLUR_HASH, getBlurHashStringOrNull(template.getBlurHash())); contentValues.put(TRANSFORM_PROPERTIES, template.getTransformProperties().serialize()); thumbnailTimeUs = STANDARD_THUMB_TIME; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java index 272dabbe9b..cbc098dc00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -47,6 +47,7 @@ public class MediaDatabase extends Database { + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CAPTION + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.NAME + ", " + + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UPLOAD_TIMESTAMP + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.MESSAGE_BOX + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + ", " diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index a670a70ea3..17e7bb0bf7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -213,7 +213,8 @@ public class MmsDatabase extends MessagingDatabase { "'" + AttachmentDatabase.STICKER_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ", " + "'" + AttachmentDatabase.BLUR_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.BLUR_HASH + ", " + "'" + AttachmentDatabase.TRANSFORM_PROPERTIES + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFORM_PROPERTIES + ", " + - "'" + AttachmentDatabase.DISPLAY_ORDER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + + "'" + AttachmentDatabase.DISPLAY_ORDER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", " + + "'" + AttachmentDatabase.UPLOAD_TIMESTAMP + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UPLOAD_TIMESTAMP + ")) AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS, }; @@ -887,59 +888,6 @@ public class MmsDatabase extends MessagingDatabase { return Collections.emptyList(); } - public long copyMessageInbox(long messageId) throws MmsException { - try { - OutgoingMediaMessage request = getOutgoingMessage(messageId); - ContentValues contentValues = new ContentValues(); - contentValues.put(RECIPIENT_ID, request.getRecipient().getId().serialize()); - contentValues.put(DATE_SENT, request.getSentTimeMillis()); - contentValues.put(MESSAGE_BOX, Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT); - contentValues.put(THREAD_ID, getThreadIdForMessage(messageId)); - contentValues.put(READ, 1); - contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT)); - contentValues.put(EXPIRES_IN, request.getExpiresIn()); - contentValues.put(VIEW_ONCE, request.isViewOnce()); - - List attachments = new LinkedList<>(); - - for (Attachment attachment : request.getAttachments()) { - DatabaseAttachment databaseAttachment = (DatabaseAttachment)attachment; - attachments.add(new DatabaseAttachment(databaseAttachment.getAttachmentId(), - databaseAttachment.getMmsId(), - databaseAttachment.hasData(), - databaseAttachment.hasThumbnail(), - databaseAttachment.getContentType(), - AttachmentDatabase.TRANSFER_PROGRESS_DONE, - databaseAttachment.getSize(), - databaseAttachment.getFileName(), - databaseAttachment.getLocation(), - databaseAttachment.getKey(), - databaseAttachment.getRelay(), - databaseAttachment.getDigest(), - databaseAttachment.getFastPreflightId(), - databaseAttachment.isVoiceNote(), - databaseAttachment.getWidth(), - databaseAttachment.getHeight(), - databaseAttachment.isQuote(), - databaseAttachment.getCaption(), - databaseAttachment.getSticker(), - databaseAttachment.getBlurHash(), - databaseAttachment.getTransformProperties(), - databaseAttachment.getDisplayOrder())); - } - - return insertMediaMessage(request.getBody(), - attachments, - new LinkedList<>(), - request.getSharedContacts(), - request.getLinkPreviews(), - contentValues, - null); - } catch (NoSuchMessageException e) { - throw new MmsException(e); - } - } - private Optional insertMessageInbox(IncomingMediaMessage retrieved, String contentLocation, long threadId, long mailbox) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index e98cb7af07..c3ea5b1c84 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -361,7 +361,8 @@ public class MmsSmsDatabase extends Database { "'" + AttachmentDatabase.STICKER_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ", " + "'" + AttachmentDatabase.BLUR_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.BLUR_HASH + ", " + "'" + AttachmentDatabase.TRANSFORM_PROPERTIES + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFORM_PROPERTIES + ", " + - "'" + AttachmentDatabase.DISPLAY_ORDER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + + "'" + AttachmentDatabase.DISPLAY_ORDER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", " + + "'" + AttachmentDatabase.UPLOAD_TIMESTAMP + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UPLOAD_TIMESTAMP + ")) AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID, SmsDatabase.TYPE, SmsDatabase.RECIPIENT_ID, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index d59af0c7c1..4df76ecb0f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -125,8 +125,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int PROFILE_DATA_MIGRATION = 53; private static final int AVATAR_LOCATION_MIGRATION = 54; private static final int GROUPS_V2 = 55; + private static final int ATTACHMENT_UPLOAD_TIMESTAMP = 56; - private static final int DATABASE_VERSION = 55; + private static final int DATABASE_VERSION = 56; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -858,6 +859,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL("ALTER TABLE groups ADD COLUMN decrypted_group"); } + if (oldVersion < ATTACHMENT_UPLOAD_TIMESTAMP) { + db.execSQL("ALTER TABLE part ADD COLUMN upload_timestamp DEFAULT 0"); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index a52defe0aa..04f06d69a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -204,24 +204,14 @@ public class AttachmentDownloadJob extends BaseJob { Optional.fromNullable(attachment.getFileName()), attachment.isVoiceNote(), Optional.absent(), - Optional.fromNullable(attachment.getBlurHash()).transform(BlurHash::getHash)); + Optional.fromNullable(attachment.getBlurHash()).transform(BlurHash::getHash), + attachment.getUploadTimestamp()); } catch (IOException | ArithmeticException e) { Log.w(TAG, e); throw new InvalidPartException(e); } } - private File createTempFile() throws InvalidPartException { - try { - File file = File.createTempFile("push-attachment", "tmp", context.getCacheDir()); - file.deleteOnExit(); - - return file; - } catch (IOException e) { - throw new InvalidPartException(e); - } - } - private void markFailed(long messageId, AttachmentId attachmentId) { try { AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java index 3a2791f104..873d486ec5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java @@ -49,6 +49,8 @@ public final class AttachmentUploadJob extends BaseJob { @SuppressWarnings("unused") private static final String TAG = Log.tag(AttachmentUploadJob.class); + private static final long UPLOAD_REUSE_THRESHOLD = TimeUnit.DAYS.toMillis(3); + private static final String KEY_ROW_ID = "row_id"; private static final String KEY_UNIQUE_ID = "unique_id"; @@ -95,6 +97,14 @@ public final class AttachmentUploadJob extends BaseJob { throw new InvalidAttachmentException("Cannot find the specified attachment."); } + long timeSinceUpload = System.currentTimeMillis() - databaseAttachment.getUploadTimestamp(); + if (timeSinceUpload < UPLOAD_REUSE_THRESHOLD) { + Log.i(TAG, "We can re-use an already-uploaded file. It was uploaded " + timeSinceUpload + " ms ago. Skipping."); + return; + } else if (databaseAttachment.getUploadTimestamp() > 0) { + Log.i(TAG, "This file was previously-uploaded, but too long ago to be re-used. Age: " + timeSinceUpload + " ms"); + } + Log.i(TAG, "Uploading attachment for message " + databaseAttachment.getMmsId() + " with ID " + databaseAttachment.getAttachmentId()); try (NotificationController notification = getNotificationForAttachment(databaseAttachment)) { @@ -102,7 +112,7 @@ public final class AttachmentUploadJob extends BaseJob { SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream()); Attachment attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get(); - database.updateAttachmentAfterUpload(databaseAttachment.getAttachmentId(), attachment); + database.updateAttachmentAfterUpload(databaseAttachment.getAttachmentId(), attachment, remoteAttachment.getUploadTimestamp()); } } @@ -138,6 +148,7 @@ public final class AttachmentUploadJob extends BaseJob { .withVoiceNote(attachment.isVoiceNote()) .withWidth(attachment.getWidth()) .withHeight(attachment.getHeight()) + .withUploadTimestamp(System.currentTimeMillis()) .withCaption(attachment.getCaption()) .withCancelationSignal(this::isCanceled) .withListener((total, progress) -> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java index 0dfbb72fef..9d72ac58cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java @@ -90,7 +90,7 @@ public class AvatarDownloadJob extends BaseJob { attachment.deleteOnExit(); SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver(); - SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false, Optional.absent(), Optional.absent()); + SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false, Optional.absent(), Optional.absent(), System.currentTimeMillis()); InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, AvatarHelper.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE); AvatarHelper.setAvatar(context, record.get().getRecipientId(), inputStream); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java index 50184bb831..c38183bae2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -198,7 +198,8 @@ public abstract class PushSendJob extends SendJob { Optional.fromNullable(attachment.getFileName()), attachment.isVoiceNote(), Optional.fromNullable(attachment.getCaption()), - Optional.fromNullable(attachment.getBlurHash()).transform(BlurHash::getHash)); + Optional.fromNullable(attachment.getBlurHash()).transform(BlurHash::getHash), + attachment.getUploadTimestamp()); } catch (IOException | ArithmeticException e) { Log.w(TAG, e); return null; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index 80e8ce059e..afa7c584a9 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -381,7 +381,8 @@ public class SignalServiceMessageSender { attachment.getFileName(), attachment.getVoiceNote(), attachment.getCaption(), - attachment.getBlurHash()); + attachment.getBlurHash(), + attachment.getUploadTimestamp()); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachment.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachment.java index 248c2b8f6a..61c4385a4c 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachment.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachment.java @@ -51,6 +51,7 @@ public abstract class SignalServiceAttachment { private int height; private String caption; private String blurHash; + private long uploadTimestamp; private Builder() {} @@ -109,6 +110,11 @@ public abstract class SignalServiceAttachment { return this; } + public Builder withUploadTimestamp(long uploadTimestamp) { + this.uploadTimestamp = uploadTimestamp; + return this; + } + public SignalServiceAttachmentStream build() { if (inputStream == null) throw new IllegalArgumentException("Must specify stream!"); if (contentType == null) throw new IllegalArgumentException("No content type specified!"); @@ -122,6 +128,7 @@ public abstract class SignalServiceAttachment { Optional.absent(), width, height, + uploadTimestamp, Optional.fromNullable(caption), Optional.fromNullable(blurHash), listener, diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentPointer.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentPointer.java index bf0b84083a..afdd6c1d9c 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentPointer.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentPointer.java @@ -12,7 +12,7 @@ import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; /** * Represents a received SignalServiceAttachment "handle." This * is a pointer to the actual attachment content, which needs to be - * retrieved using {@link SignalServiceMessageReceiver#retrieveAttachment(SignalServiceAttachmentPointer, java.io.File, int)} + * retrieved using {@link SignalServiceMessageReceiver#retrieveAttachment(SignalServiceAttachmentPointer, java.io.File, long)} * * @author Moxie Marlinspike */ @@ -29,26 +29,28 @@ public class SignalServiceAttachmentPointer extends SignalServiceAttachment { private final int height; private final Optional caption; private final Optional blurHash; + private final long uploadTimestamp; public SignalServiceAttachmentPointer(long id, String contentType, byte[] key, Optional size, Optional preview, int width, int height, Optional digest, Optional fileName, boolean voiceNote, Optional caption, - Optional blurHash) + Optional blurHash, long uploadTimestamp) { super(contentType); - this.id = id; - this.key = key; - this.size = size; - this.preview = preview; - this.width = width; - this.height = height; - this.digest = digest; - this.fileName = fileName; - this.voiceNote = voiceNote; - this.caption = caption; - this.blurHash = blurHash; + this.id = id; + this.key = key; + this.size = size; + this.preview = preview; + this.width = width; + this.height = height; + this.digest = digest; + this.fileName = fileName; + this.voiceNote = voiceNote; + this.caption = caption; + this.blurHash = blurHash; + this.uploadTimestamp = uploadTimestamp; } public long getId() { @@ -104,4 +106,8 @@ public class SignalServiceAttachmentPointer extends SignalServiceAttachment { public Optional getBlurHash() { return blurHash; } + + public long getUploadTimestamp() { + return uploadTimestamp; + } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentStream.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentStream.java index b58c61a0b1..64234a1280 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentStream.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentStream.java @@ -25,11 +25,12 @@ public class SignalServiceAttachmentStream extends SignalServiceAttachment { private final boolean voiceNote; private final int width; private final int height; + private final long uploadTimestamp; private final Optional caption; private final Optional blurHash; public SignalServiceAttachmentStream(InputStream inputStream, String contentType, long length, Optional fileName, boolean voiceNote, ProgressListener listener, CancelationSignal cancelationSignal) { - this(inputStream, contentType, length, fileName, voiceNote, Optional.absent(), 0, 0, Optional.absent(), Optional.absent(), listener, cancelationSignal); + this(inputStream, contentType, length, fileName, voiceNote, Optional.absent(), 0, 0, System.currentTimeMillis(), Optional.absent(), Optional.absent(), listener, cancelationSignal); } public SignalServiceAttachmentStream(InputStream inputStream, @@ -40,6 +41,7 @@ public class SignalServiceAttachmentStream extends SignalServiceAttachment { Optional preview, int width, int height, + long uploadTimestamp, Optional caption, Optional blurHash, ProgressListener listener, @@ -54,6 +56,7 @@ public class SignalServiceAttachmentStream extends SignalServiceAttachment { this.preview = preview; this.width = width; this.height = height; + this.uploadTimestamp = uploadTimestamp; this.caption = caption; this.blurHash = blurHash; this.cancelationSignal = cancelationSignal; @@ -112,4 +115,8 @@ public class SignalServiceAttachmentStream extends SignalServiceAttachment { public Optional getBlurHash() { return blurHash; } + + public long getUploadTimestamp() { + return uploadTimestamp; + } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java index 2ac40641e5..4685ec2cea 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java @@ -747,7 +747,8 @@ public final class SignalServiceContent { pointer.hasFileName() ? Optional.of(pointer.getFileName()) : Optional.absent(), (pointer.getFlags() & SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE_VALUE) != 0, pointer.hasCaption() ? Optional.of(pointer.getCaption()) : Optional.absent(), - pointer.hasBlurHash() ? Optional.of(pointer.getBlurHash()) : Optional.absent()); + pointer.hasBlurHash() ? Optional.of(pointer.getBlurHash()) : Optional.absent(), + pointer.hasUploadTimestamp() ? pointer.getUploadTimestamp() : 0); } @@ -803,7 +804,8 @@ public final class SignalServiceContent { Optional.absent(), false, Optional.absent(), - Optional.absent()); + Optional.absent(), + pointer.hasUploadTimestamp() ? pointer.getUploadTimestamp() : 0); } return new SignalServiceGroup(type, content.getGroup().getId().toByteArray(), name, members, avatar); diff --git a/libsignal/service/src/main/proto/SignalService.proto b/libsignal/service/src/main/proto/SignalService.proto index 37121b2ba2..672a9bcc05 100644 --- a/libsignal/service/src/main/proto/SignalService.proto +++ b/libsignal/service/src/main/proto/SignalService.proto @@ -377,18 +377,19 @@ message AttachmentPointer { VOICE_MESSAGE = 1; } - optional fixed64 id = 1; - optional string contentType = 2; - optional bytes key = 3; - optional uint32 size = 4; - optional bytes thumbnail = 5; - optional bytes digest = 6; - optional string fileName = 7; - optional uint32 flags = 8; - optional uint32 width = 9; - optional uint32 height = 10; - optional string caption = 11; - optional string blurHash = 12; + optional fixed64 id = 1; + optional string contentType = 2; + optional bytes key = 3; + optional uint32 size = 4; + optional bytes thumbnail = 5; + optional bytes digest = 6; + optional string fileName = 7; + optional uint32 flags = 8; + optional uint32 width = 9; + optional uint32 height = 10; + optional string caption = 11; + optional string blurHash = 12; + optional uint64 uploadTimestamp = 13; } message GroupContext {