Fix data deduping issues.

Fixes #9109
This commit is contained in:
Greyson Parrelli 2019-10-20 03:47:42 -04:00
parent b9057a1c11
commit 660ec88202

View File

@ -113,7 +113,7 @@ public class AttachmentDatabase extends Database {
static final String WIDTH = "width"; static final String WIDTH = "width";
static final String HEIGHT = "height"; static final String HEIGHT = "height";
static final String CAPTION = "caption"; static final String CAPTION = "caption";
static final String DATA_HASH = "data_hash"; private static final String DATA_HASH = "data_hash";
static final String BLUR_HASH = "blur_hash"; static final String BLUR_HASH = "blur_hash";
public static final String DIRECTORY = "parts"; public static final String DIRECTORY = "parts";
@ -293,14 +293,13 @@ public class AttachmentDatabase extends Database {
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL, CONTENT_TYPE, DATA_HASH, ROW_ID, UNIQUE_ID}, MMS_ID + " = ?", cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL, CONTENT_TYPE, ROW_ID, UNIQUE_ID}, MMS_ID + " = ?",
new String[] {mmsId+""}, null, null, null); new String[] {mmsId+""}, null, null, null);
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
deleteAttachmentOnDisk(cursor.getString(cursor.getColumnIndex(DATA)), deleteAttachmentOnDisk(cursor.getString(cursor.getColumnIndex(DATA)),
cursor.getString(cursor.getColumnIndex(THUMBNAIL)), cursor.getString(cursor.getColumnIndex(THUMBNAIL)),
cursor.getString(cursor.getColumnIndex(CONTENT_TYPE)), cursor.getString(cursor.getColumnIndex(CONTENT_TYPE)),
cursor.getString(cursor.getColumnIndex(DATA_HASH)),
new AttachmentId(cursor.getLong(cursor.getColumnIndex(ROW_ID)), new AttachmentId(cursor.getLong(cursor.getColumnIndex(ROW_ID)),
cursor.getLong(cursor.getColumnIndex(UNIQUE_ID)))); cursor.getLong(cursor.getColumnIndex(UNIQUE_ID))));
} }
@ -318,14 +317,13 @@ public class AttachmentDatabase extends Database {
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL, CONTENT_TYPE, DATA_HASH, ROW_ID, UNIQUE_ID}, MMS_ID + " = ?", cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL, CONTENT_TYPE, ROW_ID, UNIQUE_ID}, MMS_ID + " = ?",
new String[] {mmsId+""}, null, null, null); new String[] {mmsId+""}, null, null, null);
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
deleteAttachmentOnDisk(cursor.getString(cursor.getColumnIndex(DATA)), deleteAttachmentOnDisk(cursor.getString(cursor.getColumnIndex(DATA)),
cursor.getString(cursor.getColumnIndex(THUMBNAIL)), cursor.getString(cursor.getColumnIndex(THUMBNAIL)),
cursor.getString(cursor.getColumnIndex(CONTENT_TYPE)), cursor.getString(cursor.getColumnIndex(CONTENT_TYPE)),
cursor.getString(cursor.getColumnIndex(DATA_HASH)),
new AttachmentId(cursor.getLong(cursor.getColumnIndex(ROW_ID)), new AttachmentId(cursor.getLong(cursor.getColumnIndex(ROW_ID)),
cursor.getLong(cursor.getColumnIndex(UNIQUE_ID)))); cursor.getLong(cursor.getColumnIndex(UNIQUE_ID))));
} }
@ -361,7 +359,7 @@ public class AttachmentDatabase extends Database {
SQLiteDatabase database = databaseHelper.getWritableDatabase(); SQLiteDatabase database = databaseHelper.getWritableDatabase();
try (Cursor cursor = database.query(TABLE_NAME, try (Cursor cursor = database.query(TABLE_NAME,
new String[]{DATA, THUMBNAIL, CONTENT_TYPE, DATA_HASH}, new String[]{DATA, THUMBNAIL, CONTENT_TYPE},
PART_ID_WHERE, PART_ID_WHERE,
id.toStrings(), id.toStrings(),
null, null,
@ -375,10 +373,9 @@ public class AttachmentDatabase extends Database {
String data = cursor.getString(cursor.getColumnIndex(DATA)); String data = cursor.getString(cursor.getColumnIndex(DATA));
String thumbnail = cursor.getString(cursor.getColumnIndex(THUMBNAIL)); String thumbnail = cursor.getString(cursor.getColumnIndex(THUMBNAIL));
String contentType = cursor.getString(cursor.getColumnIndex(CONTENT_TYPE)); String contentType = cursor.getString(cursor.getColumnIndex(CONTENT_TYPE));
String dataHash = cursor.getString(cursor.getColumnIndex(DATA_HASH));
database.delete(TABLE_NAME, PART_ID_WHERE, id.toStrings()); database.delete(TABLE_NAME, PART_ID_WHERE, id.toStrings());
deleteAttachmentOnDisk(data, thumbnail, contentType, dataHash, id); deleteAttachmentOnDisk(data, thumbnail, contentType, id);
notifyAttachmentListeners(); notifyAttachmentListeners();
} }
} }
@ -402,13 +399,14 @@ public class AttachmentDatabase extends Database {
private void deleteAttachmentOnDisk(@Nullable String data, private void deleteAttachmentOnDisk(@Nullable String data,
@Nullable String thumbnail, @Nullable String thumbnail,
@Nullable String contentType, @Nullable String contentType,
@Nullable String attachmentHash,
@NonNull AttachmentId attachmentId) @NonNull AttachmentId attachmentId)
{ {
boolean dataInUse = isDataUsedByAnotherAttachment(attachmentHash, attachmentId); boolean dataInUse = isDataUsedByAnotherAttachment(data, attachmentId);
if (dataInUse) { if (dataInUse) {
Log.i(TAG, "Data is used by another attachment, skipping deletion"); Log.i(TAG, "[deleteAttachmentOnDisk] Attachment in use. Skipping deletion. " + data);
} else {
Log.i(TAG, "[deleteAttachmentOnDisk] No other users of this attachment. Safe to delete. " + data);
} }
if (!TextUtils.isEmpty(data) && !dataInUse) { if (!TextUtils.isEmpty(data) && !dataInUse) {
@ -424,13 +422,13 @@ public class AttachmentDatabase extends Database {
} }
} }
private boolean isDataUsedByAnotherAttachment(@Nullable String attachmentHash, @NonNull AttachmentId attachmentId) { private boolean isDataUsedByAnotherAttachment(@Nullable String data, @NonNull AttachmentId attachmentId) {
if (attachmentHash == null) return false; if (data == null) return false;
SQLiteDatabase database = databaseHelper.getReadableDatabase(); SQLiteDatabase database = databaseHelper.getReadableDatabase();
long matches = DatabaseUtils.longForQuery(database, long matches = DatabaseUtils.longForQuery(database,
"SELECT count(*) FROM " + TABLE_NAME + " WHERE " + DATA_HASH + " = ? AND " + UNIQUE_ID + " != ? AND " + ROW_ID + " != ?;", "SELECT count(*) FROM " + TABLE_NAME + " WHERE " + DATA + " = ? AND " + UNIQUE_ID + " != ? AND " + ROW_ID + " != ?;",
new String[]{attachmentHash, new String[]{data,
Long.toString(attachmentId.getUniqueId()), Long.toString(attachmentId.getUniqueId()),
Long.toString(attachmentId.getRowId())}); Long.toString(attachmentId.getRowId())});
@ -447,7 +445,7 @@ public class AttachmentDatabase extends Database {
DataInfo dataInfo = setAttachmentData(inputStream, false, attachmentId); DataInfo dataInfo = setAttachmentData(inputStream, false, attachmentId);
if (oldInfo != null) { if (oldInfo != null) {
updateAttachmentDataHash(database, dataInfo.hash, oldInfo.hash); updateAttachmentDataHash(database, oldInfo.hash, dataInfo);
} }
if (placeholder != null && placeholder.isQuote() && !placeholder.getContentType().startsWith("image")) { if (placeholder != null && placeholder.isQuote() && !placeholder.getContentType().startsWith("image")) {
@ -578,27 +576,36 @@ public class AttachmentDatabase extends Database {
mediaStream.getStream(), mediaStream.getStream(),
false, false,
databaseAttachment.getAttachmentId()); databaseAttachment.getAttachmentId());
updateAttachmentDataHash(database, dataInfo.hash, oldDataInfo.hash);
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(SIZE, dataInfo.length); contentValues.put(SIZE, dataInfo.length);
contentValues.put(CONTENT_TYPE, mediaStream.getMimeType()); contentValues.put(CONTENT_TYPE, mediaStream.getMimeType());
contentValues.put(WIDTH, mediaStream.getWidth()); contentValues.put(WIDTH, mediaStream.getWidth());
contentValues.put(HEIGHT, mediaStream.getHeight()); contentValues.put(HEIGHT, mediaStream.getHeight());
contentValues.put(DATA, dataInfo.file.getAbsolutePath());
contentValues.put(DATA_RANDOM, dataInfo.random); contentValues.put(DATA_RANDOM, dataInfo.random);
contentValues.put(DATA_HASH, dataInfo.hash); contentValues.put(DATA_HASH, dataInfo.hash);
database.update(TABLE_NAME, contentValues, PART_ID_WHERE, databaseAttachment.getAttachmentId().toStrings()); String selection = "(" + ROW_ID + " = ? AND " + UNIQUE_ID + " = ?) OR " +
"(" + DATA_HASH + " NOT NULL AND " + DATA_HASH + " = ?)";
String[] args = new String[]{String.valueOf(databaseAttachment.getAttachmentId().getRowId()),
String.valueOf(databaseAttachment.getAttachmentId().getUniqueId()),
oldDataInfo.hash};
int updateCount = database.update(TABLE_NAME, contentValues, selection, args);
Log.i(TAG, "[updateAttachmentData] Updated " + updateCount + " rows.");
} }
private static void updateAttachmentDataHash(@NonNull SQLiteDatabase database, private static void updateAttachmentDataHash(@NonNull SQLiteDatabase database,
@NonNull String newHash, @NonNull String oldHash,
@Nullable String oldHash) @NonNull DataInfo newData)
{ {
if (oldHash == null) return; if (oldHash == null) return;
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(DATA_HASH, newHash); contentValues.put(DATA, newData.file.getAbsolutePath());
contentValues.put(DATA_RANDOM, newData.random);
contentValues.put(DATA_HASH, newData.hash);
database.update(TABLE_NAME, database.update(TABLE_NAME,
contentValues, contentValues,
DATA_HASH + " = ?", DATA_HASH + " = ?",
@ -775,14 +782,16 @@ public class AttachmentDatabase extends Database {
String hash = Base64.encodeBytes(digestInputStream.getMessageDigest().digest()); String hash = Base64.encodeBytes(digestInputStream.getMessageDigest().digest());
if (!isThumbnail) { if (!isThumbnail) {
Log.i(TAG, "setAttachmentData: " + destination.getAbsolutePath());
SQLiteDatabase database = databaseHelper.getWritableDatabase(); SQLiteDatabase database = databaseHelper.getWritableDatabase();
Optional<DataInfo> sharedDataInfo = findDuplicateDataFileInfo(database, hash, attachmentId); Optional<DataInfo> sharedDataInfo = findDuplicateDataFileInfo(database, hash, attachmentId);
if (sharedDataInfo.isPresent()) { if (sharedDataInfo.isPresent()) {
if (sharedDataInfo.get().file != destination && !destination.delete()) { Log.i(TAG, "[setAttachmentData] Duplicate data file found! " + sharedDataInfo.get().file.getAbsolutePath());
Log.w(TAG, "setAttachmentData: Tried to delete original destination file but failed :("); if (sharedDataInfo.get().file != destination && destination.delete()) {
Log.i(TAG, "[setAttachmentData] Deleted original file. " + destination);
} }
return sharedDataInfo.get(); return sharedDataInfo.get();
} else {
Log.i(TAG, "[setAttachmentData] No matching attachment data found. " + destination.getAbsolutePath());
} }
} }