diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index e6a0bb7aa98..665f1c563b2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.migrations.AvatarIdRemovalMigrationJob; import org.thoughtcrime.securesms.migrations.AvatarMigrationJob; import org.thoughtcrime.securesms.migrations.BackupNotificationMigrationJob; +import org.thoughtcrime.securesms.migrations.BlobStorageLocationMigrationJob; import org.thoughtcrime.securesms.migrations.CachedAttachmentsMigrationJob; import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob; import org.thoughtcrime.securesms.migrations.DirectoryRefreshMigrationJob; @@ -149,6 +150,7 @@ public static Map getJobFactories(@NonNull Application appl put(AvatarIdRemovalMigrationJob.KEY, new AvatarIdRemovalMigrationJob.Factory()); put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory()); put(BackupNotificationMigrationJob.KEY, new BackupNotificationMigrationJob.Factory()); + put(BlobStorageLocationMigrationJob.KEY, new BlobStorageLocationMigrationJob.Factory()); put(CachedAttachmentsMigrationJob.KEY, new CachedAttachmentsMigrationJob.Factory()); put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory()); put(DirectoryRefreshMigrationJob.KEY, new DirectoryRefreshMigrationJob.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index d4f8691a374..1412c4a806b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -40,7 +40,7 @@ public class ApplicationMigrations { private static final int LEGACY_CANONICAL_VERSION = 455; - public static final int CURRENT_VERSION = 26; + public static final int CURRENT_VERSION = 27; private static final class Version { static final int LEGACY = 1; @@ -69,6 +69,7 @@ private static final class Version { static final int GV1_MIGRATION = 24; static final int USER_NOTIFICATION = 25; static final int DAY_BY_DAY_STICKERS = 26; + static final int BLOB_LOCATION = 27; } /** @@ -291,6 +292,10 @@ private static LinkedHashMap getMigrationJobs(@NonNull Co jobs.put(Version.DAY_BY_DAY_STICKERS, new StickerDayByDayMigrationJob()); } + if (lastSeenVersion < Version.BLOB_LOCATION) { + jobs.put(Version.BLOB_LOCATION, new BlobStorageLocationMigrationJob()); + } + return jobs; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/BlobStorageLocationMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/BlobStorageLocationMigrationJob.java new file mode 100644 index 00000000000..bf3e220ebaf --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/BlobStorageLocationMigrationJob.java @@ -0,0 +1,77 @@ +package org.thoughtcrime.securesms.migrations; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; + +import java.io.File; + +/** + * We moved files stored by {@link org.thoughtcrime.securesms.providers.BlobProvider} from the cache + * into internal storage, so we gotta move any existing multi-session files. + */ +public class BlobStorageLocationMigrationJob extends MigrationJob { + + private static final String TAG = Log.tag(BlobStorageLocationMigrationJob.class); + + public static final String KEY = "BlobStorageLocationMigrationJob"; + + BlobStorageLocationMigrationJob() { + this(new Job.Parameters.Builder().build()); + } + + private BlobStorageLocationMigrationJob(@NonNull Parameters parameters) { + super(parameters); + } + + @Override + boolean isUiBlocking() { + return false; + } + + @Override + void performMigration() { + File oldDirectory = new File(context.getCacheDir(), "multi_session_blobs"); + + File[] oldFiles = oldDirectory.listFiles(); + + if (oldFiles == null) { + Log.i(TAG, "No files to move."); + return; + } + + Log.i(TAG, "Preparing to move " + oldFiles.length + " files."); + + File newDirectory = context.getDir("multi_session_blobs", Context.MODE_PRIVATE); + + for (File oldFile : oldFiles) { + if (oldFile.renameTo(new File(newDirectory, oldFile.getName()))) { + Log.i(TAG, "Successfully moved file: " + oldFile.getName()); + } else { + Log.w(TAG, "Failed to move file! " + oldFile.getAbsolutePath()); + } + } + } + + @Override + boolean shouldRetry(@NonNull Exception e) { + return false; + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + public static class Factory implements Job.Factory { + + @Override + public @NonNull BlobStorageLocationMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new BlobStorageLocationMigrationJob(parameters); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/providers/BlobProvider.java b/app/src/main/java/org/thoughtcrime/securesms/providers/BlobProvider.java index 243ca595aee..ce04cd960ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/providers/BlobProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/providers/BlobProvider.java @@ -141,7 +141,7 @@ public BlobBuilder forData(@NonNull InputStream data, long fileSize) { } else { String id = uri.getPathSegments().get(ID_PATH_SEGMENT); String directory = getDirectory(storageType); - File file = new File(getOrCreateCacheDirectory(context, directory), buildFileName(id)); + File file = new File(getOrCreateDirectory(context, directory), buildFileName(id)); return getFileRepresentation.apply(file); } @@ -163,6 +163,8 @@ public synchronized void delete(@NonNull Context context, @NonNull Uri uri) { return; } + Log.d(TAG, "Deleting " + uri); + try { StorageType storageType = StorageType.decode(uri.getPathSegments().get(STORAGE_TYPE_PATH_SEGMENT)); @@ -171,9 +173,11 @@ public synchronized void delete(@NonNull Context context, @NonNull Uri uri) { } else { String id = uri.getPathSegments().get(ID_PATH_SEGMENT); String directory = getDirectory(storageType); - File file = new File(getOrCreateCacheDirectory(context, directory), buildFileName(id)); + File file = new File(getOrCreateDirectory(context, directory), buildFileName(id)); - if (!file.delete()) { + if (file.delete()) { + Log.d(TAG, "Successfully deleted " + uri); + } else { throw new IOException("File wasn't deleted."); } } @@ -186,9 +190,13 @@ public synchronized void delete(@NonNull Context context, @NonNull Uri uri) { * Indicates a new app session has started, allowing old single-session blobs to be deleted. */ public synchronized void onSessionStart(@NonNull Context context) { - File directory = getOrCreateCacheDirectory(context, SINGLE_SESSION_DIRECTORY); + File directory = getOrCreateDirectory(context, SINGLE_SESSION_DIRECTORY); for (File file : directory.listFiles()) { - file.delete(); + if (file.delete()) { + Log.d(TAG, "Deleted single-session file: " + file.getName()); + } else { + Log.w(TAG, "Failed to delete single-session file! " + file.getName()); + } } } @@ -266,7 +274,7 @@ public static boolean isAuthority(@NonNull Uri uri) { { AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(); String directory = getDirectory(blobSpec.getStorageType()); - File outputFile = new File(getOrCreateCacheDirectory(context, directory), buildFileName(blobSpec.id)); + File outputFile = new File(getOrCreateDirectory(context, directory), buildFileName(blobSpec.id)); OutputStream outputStream = ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).second; SignalExecutors.UNBOUNDED.execute(() -> { @@ -310,13 +318,8 @@ public static boolean isAuthority(@NonNull Uri uri) { .build(); } - private static File getOrCreateCacheDirectory(@NonNull Context context, @NonNull String directory) { - File file = new File(context.getCacheDir(), directory); - if (!file.exists()) { - file.mkdir(); - } - - return file; + private static File getOrCreateDirectory(@NonNull Context context, @NonNull String directory) { + return context.getDir(directory, Context.MODE_PRIVATE); } public class BlobBuilder { diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java index dc7b4ae9038..821e5ff8569 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java @@ -104,7 +104,8 @@ void getResolved(@NonNull List uris, @NonNull Callback> .forData(stream, size) .withMimeType(mimeType) .withFileName(fileName) - .createForMultipleSessionsOnDisk(context); + .createForSingleSessionOnDisk(context); + // TODO Convert to multi-session after file drafts are fixed. } return ShareData.forIntentData(blobUri, mimeType, true, isMmsSupported(context, mimeType, size));