diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.java b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.java index 6c219e5371..c07cb1a9e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.java @@ -162,9 +162,8 @@ public class FullBackupImporter extends FullBackupBase { private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream) throws IOException { - File partsDirectory = context.getDir(AttachmentDatabase.DIRECTORY, Context.MODE_PRIVATE); - File dataFile = File.createTempFile("part", ".mms", partsDirectory); - Pair output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false); + File dataFile = AttachmentDatabase.newFile(context); + Pair output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false); ContentValues contentValues = new ContentValues(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/InternalPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/InternalPreference.kt index 355c26eaf5..3da20b8f9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/InternalPreference.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/InternalPreference.kt @@ -4,11 +4,8 @@ import android.view.View import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.PreferenceModel import org.thoughtcrime.securesms.recipients.Recipient -import org.thoughtcrime.securesms.util.Base64 -import org.thoughtcrime.securesms.util.Hex import org.thoughtcrime.securesms.util.MappingAdapter import org.thoughtcrime.securesms.util.MappingViewHolder -import java.util.UUID object InternalPreference { 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 7c743d783f..71b2afa8f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -73,6 +73,7 @@ import java.io.OutputStream; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -82,9 +83,10 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; public class AttachmentDatabase extends Database { - + private static final String TAG = Log.tag(AttachmentDatabase.class); public static final String TABLE_NAME = "part"; @@ -122,7 +124,7 @@ public class AttachmentDatabase extends Database { static final String UPLOAD_TIMESTAMP = "upload_timestamp"; static final String CDN_NUMBER = "cdn_number"; - public static final String DIRECTORY = "parts"; + private static final String DIRECTORY = "parts"; public static final int TRANSFER_PROGRESS_DONE = 0; public static final int TRANSFER_PROGRESS_STARTED = 1; @@ -475,14 +477,18 @@ public class AttachmentDatabase extends Database { } public int deleteAbandonedAttachmentFiles() { - Set filesOnDisk = new HashSet<>(); - Set filesInDb = new HashSet<>(); + File[] diskFiles = context.getDir(DIRECTORY, Context.MODE_PRIVATE).listFiles(); - File attachmentDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE); - for (File file : attachmentDirectory.listFiles()) { - filesOnDisk.add(file.getAbsolutePath()); + if (diskFiles == null) { + return 0; } + Set filesOnDisk = Arrays.stream(diskFiles) + .filter(f -> !PartFileProtector.isProtected(f)) + .map(File::getAbsolutePath) + .collect(Collectors.toSet()); + + Set filesInDb = new HashSet<>(); try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(true, TABLE_NAME, new String[] { DATA }, null, null, null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { filesInDb.add(CursorUtil.requireString(cursor, DATA)); @@ -866,10 +872,8 @@ public class AttachmentDatabase extends Database { return existing; } - File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE); - File transferFile = File.createTempFile("transfer", ".mms", partsDirectory); - - ContentValues values = new ContentValues(); + File transferFile = newTransferFile(); + ContentValues values = new ContentValues(); values.put(TRANSFER_FILE, transferFile.getAbsolutePath()); db.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()); @@ -1066,8 +1070,17 @@ public class AttachmentDatabase extends Database { } public File newFile() throws IOException { + return newFile(context); + } + + private File newTransferFile() throws IOException { File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE); - return File.createTempFile("part", ".mms", partsDirectory); + return PartFileProtector.protect(() -> File.createTempFile("transfer", ".mms", partsDirectory)); + } + + public static File newFile(Context context) throws IOException { + File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE); + return PartFileProtector.protect(() -> File.createTempFile("part", ".mms", partsDirectory)); } private @NonNull DataInfo setAttachmentData(@NonNull File destination, @@ -1085,6 +1098,7 @@ public class AttachmentDatabase extends Database { if (!tempFile.renameTo(destination)) { Log.w(TAG, "Couldn't rename " + tempFile.getPath() + " to " + destination.getPath()); + tempFile.delete(); throw new IllegalStateException("Couldn't rename " + tempFile.getPath() + " to " + destination.getPath()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/PartFileProtector.java b/app/src/main/java/org/thoughtcrime/securesms/database/PartFileProtector.java new file mode 100644 index 0000000000..61ca3e313e --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/PartFileProtector.java @@ -0,0 +1,47 @@ +package org.thoughtcrime.securesms.database; + +import androidx.annotation.NonNull; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * In-memory book keeping of newly created part files to prevent them from being + * deleted prematurely. + */ +public final class PartFileProtector { + + private static final long PROTECTED_DURATION = TimeUnit.MINUTES.toMillis(10); + + private static final Map protectedFiles = new HashMap<>(); + + public static synchronized File protect(@NonNull CreateFile createFile) throws IOException { + File file = createFile.create(); + protectedFiles.put(file.getAbsolutePath(), System.currentTimeMillis()); + return file; + } + + public static synchronized boolean isProtected(File file) { + long timestamp = 0; + + Long protectedTimestamp = protectedFiles.get(file.getAbsolutePath()); + if (protectedTimestamp != null) { + timestamp = Math.max(protectedTimestamp, file.lastModified()); + } + + boolean isProtected = timestamp > System.currentTimeMillis() - PROTECTED_DURATION; + + if (!isProtected) { + protectedFiles.remove(file.getAbsolutePath()); + } + + return isProtected; + } + + interface CreateFile { + @NonNull File create() throws IOException; + } +}