kopia lustrzana https://github.com/ryukoposting/Signal-Android
Ensure blobs from old sessions are deleted before creating new ones.
There was a race condition where if you created a blob super-early in the application lifecycle, you could create it *before* we deleted the blobs from the previous session, leading you to lose the blob you just created immediately. In an effort to protect our cold start time, I just made a little initialization flow where read/write calls to BlobProvider will block until it's deleted blobs from the old session.fork-5.53.8
rodzic
da56c2790f
commit
32ac6e3429
|
@ -145,6 +145,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||
Conscrypt.setUseEngineSocketByDefault(true);
|
||||
}
|
||||
})
|
||||
.addBlocking("blob-provider", this::initializeBlobProvider)
|
||||
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||
.addNonBlocking(this::initializeGcmCheck)
|
||||
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
||||
|
@ -158,7 +159,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
||||
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
||||
.addPostRender(this::initializeExpiringMessageManager)
|
||||
.addPostRender(this::initializeBlobProvider)
|
||||
.execute();
|
||||
|
||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
|
@ -387,7 +387,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||
|
||||
@WorkerThread
|
||||
private void initializeBlobProvider() {
|
||||
BlobProvider.getInstance().onSessionStart(this);
|
||||
BlobProvider.getInstance().initialize(this);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.content.UriMatcher;
|
|||
import android.media.MediaDataSource;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -21,6 +22,7 @@ import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
|||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.util.IOFunction;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.video.ByteArrayMediaDataSource;
|
||||
import org.thoughtcrime.securesms.video.EncryptedMediaDataSource;
|
||||
|
||||
|
@ -64,6 +66,8 @@ public class BlobProvider {
|
|||
|
||||
private final Map<Uri, byte[]> memoryBlobs = new HashMap<>();
|
||||
|
||||
private volatile boolean initialized = false;
|
||||
|
||||
|
||||
public static BlobProvider getInstance() {
|
||||
return INSTANCE;
|
||||
|
@ -88,6 +92,7 @@ public class BlobProvider {
|
|||
* @throws IOException If the stream fails to open or the spec of the URI doesn't match.
|
||||
*/
|
||||
public synchronized @NonNull InputStream getStream(@NonNull Context context, @NonNull Uri uri) throws IOException {
|
||||
waitUntilInitialized();
|
||||
return getStream(context, uri, 0L);
|
||||
}
|
||||
|
||||
|
@ -96,6 +101,7 @@ public class BlobProvider {
|
|||
* @throws IOException If the stream fails to open or the spec of the URI doesn't match.
|
||||
*/
|
||||
public synchronized @NonNull InputStream getStream(@NonNull Context context, @NonNull Uri uri, long position) throws IOException {
|
||||
waitUntilInitialized();
|
||||
return getBlobRepresentation(context,
|
||||
uri,
|
||||
bytes -> {
|
||||
|
@ -112,6 +118,7 @@ public class BlobProvider {
|
|||
|
||||
@RequiresApi(23)
|
||||
public synchronized @NonNull MediaDataSource getMediaDataSource(@NonNull Context context, @NonNull Uri uri) throws IOException {
|
||||
waitUntilInitialized();
|
||||
return getBlobRepresentation(context,
|
||||
uri,
|
||||
ByteArrayMediaDataSource::new,
|
||||
|
@ -158,6 +165,8 @@ public class BlobProvider {
|
|||
* Delete the content with the specified URI.
|
||||
*/
|
||||
public synchronized void delete(@NonNull Context context, @NonNull Uri uri) {
|
||||
waitUntilInitialized();
|
||||
|
||||
if (!isAuthority(uri)) {
|
||||
Log.d(TAG, "Can't delete. Not the authority for uri: " + uri);
|
||||
return;
|
||||
|
@ -187,17 +196,30 @@ public class BlobProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Indicates a new app session has started, allowing old single-session blobs to be deleted.
|
||||
* Allows the class to be initialized. Part of this initialization is deleting any leftover
|
||||
* single-session blobs from the previous session. However, this class defers that work to a
|
||||
* background thread, so callers don't have to worry about it.
|
||||
*/
|
||||
public synchronized void onSessionStart(@NonNull Context context) {
|
||||
File directory = getOrCreateDirectory(context, SINGLE_SESSION_DIRECTORY);
|
||||
for (File file : directory.listFiles()) {
|
||||
if (file.delete()) {
|
||||
Log.d(TAG, "Deleted single-session file: " + file.getName());
|
||||
@AnyThread
|
||||
public synchronized void initialize(@NonNull Context context) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
File directory = getOrCreateDirectory(context, SINGLE_SESSION_DIRECTORY);
|
||||
File[] files = directory.listFiles();
|
||||
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.delete()) {
|
||||
Log.d(TAG, "Deleted single-session file: " + file.getName());
|
||||
} else {
|
||||
Log.w(TAG, "Failed to delete single-session file! " + file.getName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Failed to delete single-session file! " + file.getName());
|
||||
Log.w(TAG, "Null directory listing!");
|
||||
}
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
public static @Nullable String getMimeType(@NonNull Uri uri) {
|
||||
|
@ -249,7 +271,10 @@ public class BlobProvider {
|
|||
{
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<IOException> exception = new AtomicReference<>(null);
|
||||
Uri uri = writeBlobSpecToDiskAsync(context, blobSpec, latch::countDown, exception::set);
|
||||
Uri uri = writeBlobSpecToDiskAsync(context, blobSpec, latch::countDown, e -> {
|
||||
exception.set(e);
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
|
@ -285,6 +310,7 @@ public class BlobProvider {
|
|||
successListener.onSuccess();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Error during write!", e);
|
||||
if (errorListener != null) {
|
||||
errorListener.onError(e);
|
||||
}
|
||||
|
@ -401,6 +427,18 @@ public class BlobProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private synchronized void waitUntilInitialized() {
|
||||
if (!initialized) {
|
||||
Log.i(TAG, "Waiting for initialization...");
|
||||
synchronized (this) {
|
||||
while (!initialized) {
|
||||
Util.wait(this, 0);
|
||||
}
|
||||
Log.i(TAG, "Initialization complete.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryBlobBuilder extends BlobBuilder {
|
||||
|
||||
private byte[] data;
|
||||
|
|
Ładowanie…
Reference in New Issue