Prevent part files from being deleted prematurely.
This commit is contained in:
parent
662ba85c5a
commit
e2cb522e87
4 changed files with 75 additions and 18 deletions
|
@ -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)
|
private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
File partsDirectory = context.getDir(AttachmentDatabase.DIRECTORY, Context.MODE_PRIVATE);
|
File dataFile = AttachmentDatabase.newFile(context);
|
||||||
File dataFile = File.createTempFile("part", ".mms", partsDirectory);
|
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
|
||||||
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
|
|
||||||
|
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,8 @@ import android.view.View
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
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.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
object InternalPreference {
|
object InternalPreference {
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,7 @@ import java.io.OutputStream;
|
||||||
import java.security.DigestInputStream;
|
import java.security.DigestInputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -82,9 +83,10 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class AttachmentDatabase extends Database {
|
public class AttachmentDatabase extends Database {
|
||||||
|
|
||||||
private static final String TAG = Log.tag(AttachmentDatabase.class);
|
private static final String TAG = Log.tag(AttachmentDatabase.class);
|
||||||
|
|
||||||
public static final String TABLE_NAME = "part";
|
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 UPLOAD_TIMESTAMP = "upload_timestamp";
|
||||||
static final String CDN_NUMBER = "cdn_number";
|
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_DONE = 0;
|
||||||
public static final int TRANSFER_PROGRESS_STARTED = 1;
|
public static final int TRANSFER_PROGRESS_STARTED = 1;
|
||||||
|
@ -475,14 +477,18 @@ public class AttachmentDatabase extends Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int deleteAbandonedAttachmentFiles() {
|
public int deleteAbandonedAttachmentFiles() {
|
||||||
Set<String> filesOnDisk = new HashSet<>();
|
File[] diskFiles = context.getDir(DIRECTORY, Context.MODE_PRIVATE).listFiles();
|
||||||
Set<String> filesInDb = new HashSet<>();
|
|
||||||
|
|
||||||
File attachmentDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
if (diskFiles == null) {
|
||||||
for (File file : attachmentDirectory.listFiles()) {
|
return 0;
|
||||||
filesOnDisk.add(file.getAbsolutePath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<String> filesOnDisk = Arrays.stream(diskFiles)
|
||||||
|
.filter(f -> !PartFileProtector.isProtected(f))
|
||||||
|
.map(File::getAbsolutePath)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Set<String> filesInDb = new HashSet<>();
|
||||||
try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(true, TABLE_NAME, new String[] { DATA }, null, null, null, null, null, null)) {
|
try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(true, TABLE_NAME, new String[] { DATA }, null, null, null, null, null, null)) {
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
filesInDb.add(CursorUtil.requireString(cursor, DATA));
|
filesInDb.add(CursorUtil.requireString(cursor, DATA));
|
||||||
|
@ -866,10 +872,8 @@ public class AttachmentDatabase extends Database {
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
File transferFile = newTransferFile();
|
||||||
File transferFile = File.createTempFile("transfer", ".mms", partsDirectory);
|
ContentValues values = new ContentValues();
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(TRANSFER_FILE, transferFile.getAbsolutePath());
|
values.put(TRANSFER_FILE, transferFile.getAbsolutePath());
|
||||||
|
|
||||||
db.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
|
db.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
|
||||||
|
@ -1066,8 +1070,17 @@ public class AttachmentDatabase extends Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
public File newFile() throws IOException {
|
public File newFile() throws IOException {
|
||||||
|
return newFile(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private File newTransferFile() throws IOException {
|
||||||
File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
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,
|
private @NonNull DataInfo setAttachmentData(@NonNull File destination,
|
||||||
|
@ -1085,6 +1098,7 @@ public class AttachmentDatabase extends Database {
|
||||||
|
|
||||||
if (!tempFile.renameTo(destination)) {
|
if (!tempFile.renameTo(destination)) {
|
||||||
Log.w(TAG, "Couldn't rename " + tempFile.getPath() + " to " + destination.getPath());
|
Log.w(TAG, "Couldn't rename " + tempFile.getPath() + " to " + destination.getPath());
|
||||||
|
tempFile.delete();
|
||||||
throw new IllegalStateException("Couldn't rename " + tempFile.getPath() + " to " + destination.getPath());
|
throw new IllegalStateException("Couldn't rename " + tempFile.getPath() + " to " + destination.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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<String, Long> 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue