summaryrefslogtreecommitdiff
path: root/java/com/google/android/libraries/mobiledatadownload/internal/SharedFileManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/android/libraries/mobiledatadownload/internal/SharedFileManager.java')
-rw-r--r--java/com/google/android/libraries/mobiledatadownload/internal/SharedFileManager.java579
1 files changed, 353 insertions, 226 deletions
diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/SharedFileManager.java b/java/com/google/android/libraries/mobiledatadownload/internal/SharedFileManager.java
index c0804b7..2c1775d 100644
--- a/java/com/google/android/libraries/mobiledatadownload/internal/SharedFileManager.java
+++ b/java/com/google/android/libraries/mobiledatadownload/internal/SharedFileManager.java
@@ -15,7 +15,11 @@
*/
package com.google.android.libraries.mobiledatadownload.internal;
+import static com.google.common.util.concurrent.Futures.getDone;
+import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
+import static com.google.common.util.concurrent.Futures.immediateFuture;
import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import android.content.Context;
import android.content.SharedPreferences;
@@ -45,8 +49,11 @@ import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil;
import com.google.android.libraries.mobiledatadownload.internal.util.DirectoryUtil;
import com.google.android.libraries.mobiledatadownload.internal.util.SharedPreferencesUtil;
import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor;
+import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFluentFuture;
+import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures;
import com.google.common.base.Optional;
-import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.errorprone.annotations.CheckReturnValue;
@@ -61,6 +68,7 @@ import com.google.mobiledatadownload.internal.MetadataProto.FileStatus;
import com.google.mobiledatadownload.internal.MetadataProto.GroupKey;
import com.google.mobiledatadownload.internal.MetadataProto.NewFileKey;
import com.google.mobiledatadownload.internal.MetadataProto.SharedFile;
+import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -166,7 +174,7 @@ public class SharedFileManager {
sharedFileManagerMetadata.edit().remove(PREFS_KEY_MIGRATED_TO_NEW_FILE_KEY).commit();
}
- return Futures.immediateFuture(true);
+ return immediateFuture(true);
}
/**
@@ -178,12 +186,12 @@ public class SharedFileManager {
*/
// TODO - refactor to throw Exception when write to SharedPreferences fails
public ListenableFuture<Boolean> reserveFileEntry(NewFileKey newFileKey) {
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
sharedFilesMetadata.read(newFileKey),
sharedFile -> {
if (sharedFile != null) {
// There's already an entry for this file. Nothing to do here.
- return Futures.immediateFuture(true);
+ return immediateFuture(true);
}
// Set the file name and update the metadata file.
SharedPreferences sharedFileManagerMetadata =
@@ -198,7 +206,7 @@ public class SharedFileManager {
.commit()) {
// TODO(b/131166925): MDD dump should not use lite proto toString.
LogUtil.e("%s: Unable to update file name %s", TAG, newFileKey);
- return Futures.immediateFuture(false);
+ return immediateFuture(false);
}
String fileName = FILE_NAME_PREFIX + nextFileName;
@@ -207,7 +215,7 @@ public class SharedFileManager {
.setFileStatus(FileStatus.SUBSCRIBED)
.setFileName(fileName)
.build();
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
sharedFilesMetadata.write(newFileKey, sharedFile),
writeSuccess -> {
if (!writeSuccess) {
@@ -215,9 +223,9 @@ public class SharedFileManager {
LogUtil.e(
"%s: Unable to write back subscription for file entry with %s",
TAG, newFileKey);
- return Futures.immediateFuture(false);
+ return immediateFuture(false);
}
- return Futures.immediateFuture(true);
+ return immediateFuture(true);
},
sequentialControlExecutor);
},
@@ -239,13 +247,13 @@ public class SharedFileManager {
@Nullable DownloadConditions downloadConditions,
FileSource inlineFileSource) {
if (!dataFile.getUrlToDownload().startsWith(MddConstants.INLINE_FILE_URL_SCHEME)) {
- return Futures.immediateFailedFuture(
+ return immediateFailedFuture(
DownloadException.builder()
.setDownloadResultCode(DownloadResultCode.INVALID_INLINE_FILE_URL_SCHEME)
.setMessage("Importing an inline file requires inlinefile scheme")
.build());
}
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
sharedFilesMetadata.read(newFileKey),
sharedFile -> {
if (sharedFile == null) {
@@ -254,7 +262,7 @@ public class SharedFileManager {
TAG, dataFile.getFileId());
SharedFileMissingException cause = new SharedFileMissingException();
// TODO(b/167582815): Log to Clearcut
- return Futures.immediateFailedFuture(
+ return immediateFailedFuture(
DownloadException.builder()
.setDownloadResultCode(DownloadResultCode.SHARED_FILE_NOT_FOUND_ERROR)
.setCause(cause)
@@ -274,7 +282,7 @@ public class SharedFileManager {
sharedFile.getFileName(), dataFile.getDownloadedFileChecksum())
: sharedFile.getFileName();
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
getDataFileGroupOrDefault(groupKey),
dataFileGroup ->
getImportFuture(
@@ -313,7 +321,7 @@ public class SharedFileManager {
ListenableFuture<Uri> downloadFileOnDeviceUriFuture =
getDownloadFileOnDeviceUri(
newFileKey.getAllowedReaders(), downloadFileName, dataFile.getChecksum());
- return FluentFuture.from(downloadFileOnDeviceUriFuture)
+ return PropagatedFluentFuture.from(downloadFileOnDeviceUriFuture)
.transformAsync(
unused -> {
sharedFileBuilder.setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS);
@@ -327,7 +335,7 @@ public class SharedFileManager {
sequentialControlExecutor)
.transformAsync(
unused -> {
- Uri downloadFileOnDeviceUri = Futures.getDone(downloadFileOnDeviceUriFuture);
+ Uri downloadFileOnDeviceUri = getDone(downloadFileOnDeviceUriFuture);
DownloaderCallback downloaderCallback =
new DownloaderCallbackImpl(
sharedFilesMetadata,
@@ -345,6 +353,7 @@ public class SharedFileManager {
// progress here.
return fileDownloader.startCopying(
+ newFileKey.getChecksum(),
downloadFileOnDeviceUri,
dataFile.getUrlToDownload(),
dataFile.getByteSize(),
@@ -376,7 +385,7 @@ public class SharedFileManager {
int trafficTag,
List<ExtraHttpHeader> extraHttpHeaders) {
if (dataFile.getUrlToDownload().startsWith(MddConstants.INLINE_FILE_URL_SCHEME)) {
- return Futures.immediateFailedFuture(
+ return immediateFailedFuture(
DownloadException.builder()
.setDownloadResultCode(DownloadResultCode.INVALID_INLINE_FILE_URL_SCHEME)
.setMessage(
@@ -384,77 +393,134 @@ public class SharedFileManager {
+ " instead.")
.build());
}
- return Futures.transformAsync(
- sharedFilesMetadata.read(newFileKey),
- sharedFile -> {
- if (sharedFile == null) {
- // TODO(b/131166925): MDD dump should not use lite proto toString.
- LogUtil.e(
- "%s: Start download called on file that doesn't exists. Key = %s!",
- TAG, newFileKey);
- SharedFileMissingException cause = new SharedFileMissingException();
- silentFeedback.send(cause, "Shared file not found in downloadFileGroup");
- return Futures.immediateFailedFuture(
- DownloadException.builder()
- .setDownloadResultCode(DownloadResultCode.SHARED_FILE_NOT_FOUND_ERROR)
- .setCause(cause)
- .build());
- }
- // If we have already downloaded the file, then return.
- if (sharedFile.getFileStatus() == FileStatus.DOWNLOAD_COMPLETE) {
- if (downloadMonitorOptional.isPresent()) {
- // For the downloaded file, we don't need to monitor the file change. We just need to
- // inform the monitor about its current size.
- downloadMonitorOptional
- .get()
- .notifyCurrentFileSize(groupKey.getGroupName(), dataFile.getByteSize());
- }
- return immediateVoidFuture();
- }
+ // Start futures in parallel for various calculated properties.
+ ListenableFuture<SharedFile> sharedFileFuture = getSharedFile(newFileKey);
+
+ ListenableFuture<@NullableType DeltaFile> firstDeltaFileFuture =
+ findFirstDeltaFileWithBaseFileDownloaded(dataFile, newFileKey.getAllowedReaders());
+
+ ListenableFuture<String> downloadFileNameFuture =
+ PropagatedFutures.whenAllSucceed(sharedFileFuture, firstDeltaFileFuture)
+ .call(
+ () -> {
+ String downloadFileName = getDone(sharedFileFuture).getFileName();
+ DeltaFile deltaFile = getDone(firstDeltaFileFuture);
+ if (deltaFile != null) {
+ downloadFileName =
+ FileNameUtil.getTempFileNameWithDownloadedFileChecksum(
+ downloadFileName, deltaFile.getChecksum());
+ } else if (dataFile.hasDownloadTransforms()) {
+ downloadFileName =
+ FileNameUtil.getTempFileNameWithDownloadedFileChecksum(
+ downloadFileName, dataFile.getDownloadedFileChecksum());
+ }
+ return downloadFileName;
+ },
+ directExecutor());
+
+ ListenableFuture<Uri> downloadFileOnDeviceUriFuture =
+ PropagatedFutures.transformAsync(
+ downloadFileNameFuture,
+ downloadFileName ->
+ getDownloadFileOnDeviceUri(
+ newFileKey.getAllowedReaders(), downloadFileName, dataFile.getChecksum()),
+ sequentialControlExecutor);
+
+ ListenableFuture<DataFileGroupInternal> dataFileGroupFuture =
+ getDataFileGroupOrDefault(groupKey);
+
+ // Combine all futures together so all complete successfully before continuing
+ ListenableFuture<Void> combinedPropertiesFuture =
+ PropagatedFutures.whenAllSucceed(
+ sharedFileFuture,
+ firstDeltaFileFuture,
+ downloadFileNameFuture,
+ downloadFileOnDeviceUriFuture,
+ dataFileGroupFuture)
+ .callAsync(Futures::immediateVoidFuture, directExecutor());
+
+ return PropagatedFluentFuture.from(combinedPropertiesFuture)
+ .transformAsync(
+ unused -> {
+ SharedFile sharedFile = getDone(sharedFileFuture);
+ DeltaFile deltaFile = getDone(firstDeltaFileFuture);
+ String downloadFileName = getDone(downloadFileNameFuture);
+ Uri downloadFileOnDeviceUri = getDone(downloadFileOnDeviceUriFuture);
+ DataFileGroupInternal dataFileGroup = getDone(dataFileGroupFuture);
- return Futures.transformAsync(
- findFirstDeltaFileWithBaseFileDownloaded(dataFile, newFileKey.getAllowedReaders()),
- deltaFile -> {
- SharedFile.Builder sharedFileBuilder = sharedFile.toBuilder();
- String downloadFileName = sharedFile.getFileName();
- if (deltaFile != null) {
- downloadFileName =
- FileNameUtil.getTempFileNameWithDownloadedFileChecksum(
- downloadFileName, deltaFile.getChecksum());
- } else if (dataFile.hasDownloadTransforms()) {
- downloadFileName =
- FileNameUtil.getTempFileNameWithDownloadedFileChecksum(
- downloadFileName, dataFile.getDownloadedFileChecksum());
+ // Check if download is complete
+ if (sharedFile.getFileStatus() == FileStatus.DOWNLOAD_COMPLETE) {
+ if (downloadMonitorOptional.isPresent()) {
+ // For the downloaded file, we don't need to monitor the file change. We just need
+ // to inform the monitor about its current size.
+ downloadMonitorOptional
+ .get()
+ .notifyCurrentFileSize(groupKey.getGroupName(), dataFile.getByteSize());
}
+ return immediateVoidFuture();
+ }
- // Variables captured in lambdas must be effectively final.
- String downloadFileNameCapture = downloadFileName;
- return Futures.transformAsync(
- getDataFileGroupOrDefault(groupKey),
- dataFileGroup ->
- getDownloadFuture(
- sharedFileBuilder,
- newFileKey,
- downloadFileNameCapture,
- dataFileGroup.getFileGroupVersionNumber(),
- dataFileGroup.getBuildId(),
- dataFileGroup.getVariantId(),
- groupKey,
- dataFile,
- deltaFile,
- downloadConditions,
- trafficTag,
- extraHttpHeaders),
+ // Check if a download is already in progress
+ if (sharedFile.getFileStatus() == FileStatus.DOWNLOAD_IN_PROGRESS) {
+ return PropagatedFutures.transformAsync(
+ fileDownloader.getInProgressFuture(
+ newFileKey.getChecksum(), downloadFileOnDeviceUri),
+ inProgressFuture -> {
+ if (inProgressFuture.isPresent()) {
+ mayNotifyCurrentSizeOfPartiallyDownloadedFile(
+ groupKey, downloadFileOnDeviceUri);
+ return inProgressFuture.get();
+ }
+ return getDownloadFuture(
+ newFileKey,
+ downloadFileName,
+ dataFileGroup.getFileGroupVersionNumber(),
+ dataFileGroup.getBuildId(),
+ dataFileGroup.getVariantId(),
+ groupKey,
+ dataFile,
+ deltaFile,
+ downloadConditions,
+ trafficTag,
+ extraHttpHeaders);
+ },
sequentialControlExecutor);
- },
- sequentialControlExecutor);
- },
- sequentialControlExecutor);
+ }
+
+ // Download is not in progress, start it.
+ return getDownloadFuture(
+ newFileKey,
+ downloadFileName,
+ dataFileGroup.getFileGroupVersionNumber(),
+ dataFileGroup.getBuildId(),
+ dataFileGroup.getVariantId(),
+ groupKey,
+ dataFile,
+ deltaFile,
+ downloadConditions,
+ trafficTag,
+ extraHttpHeaders);
+ },
+ sequentialControlExecutor)
+ .catchingAsync(
+ SharedFileMissingException.class,
+ ex -> {
+ // TODO(b/131166925): MDD dump should not use lite proto toString.
+ LogUtil.e(
+ "%s: Start download called on file that doesn't exist. Key = %s!",
+ TAG, newFileKey);
+ silentFeedback.send(ex, "Shared file not found in downloadFileGroup");
+ return immediateFailedFuture(
+ DownloadException.builder()
+ .setDownloadResultCode(DownloadResultCode.SHARED_FILE_NOT_FOUND_ERROR)
+ .setCause(ex)
+ .build());
+ },
+ sequentialControlExecutor);
}
private ListenableFuture<Void> getDownloadFuture(
- SharedFile.Builder sharedFileBuilder,
NewFileKey newFileKey,
String downloadFileName,
int fileGroupVersionNumber,
@@ -466,91 +532,114 @@ public class SharedFileManager {
@Nullable DownloadConditions downloadConditions,
int trafficTag,
List<ExtraHttpHeader> extraHttpHeaders) {
- ListenableFuture<Uri> downloadFileOnDeviceUriFuture =
- getDownloadFileOnDeviceUri(
- newFileKey.getAllowedReaders(), downloadFileName, dataFile.getChecksum());
- return FluentFuture.from(downloadFileOnDeviceUriFuture)
- .transformAsync(
- unused -> {
- sharedFileBuilder.setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS);
+ // It's possible to hit a race condition where the caller of this method sees the file as not
+ // downloaded and by the time this method is executed, the file is already downloaded.
+ //
+ // Check the shared file status before starting the download to confirm it is not downloaded and
+ // a download is not already in progress.
+ return PropagatedFutures.transformAsync(
+ getSharedFile(newFileKey),
+ latestSharedFile -> {
+ if (latestSharedFile.getFileStatus() == FileStatus.DOWNLOAD_COMPLETE) {
+ return immediateVoidFuture();
+ }
- // Ignoring failure to write back here, as it will just result in one extra try to
- // download the file.
- return sharedFilesMetadata.write(newFileKey, sharedFileBuilder.build());
- },
- sequentialControlExecutor)
- .transformAsync(
- unused -> {
- Uri downloadFileOnDeviceUri = Futures.getDone(downloadFileOnDeviceUriFuture);
- ListenableFuture<Void> fileDownloadFuture;
- if (!deltaDecoderOptional.isPresent() || deltaFile == null) {
- // Download full file when delta file is null
- DownloaderCallback downloaderCallback =
- new DownloaderCallbackImpl(
- sharedFilesMetadata,
- fileStorage,
- dataFile,
- newFileKey.getAllowedReaders(),
- eventLogger,
- groupKey,
- fileGroupVersionNumber,
- buildId,
- variantId,
- flags,
- sequentialControlExecutor);
+ // Download is not complete, proceed with starting the future.
+ SharedFile.Builder sharedFileBuilder = latestSharedFile.toBuilder();
+ ListenableFuture<Uri> downloadFileOnDeviceUriFuture =
+ getDownloadFileOnDeviceUri(
+ newFileKey.getAllowedReaders(), downloadFileName, dataFile.getChecksum());
+ return PropagatedFluentFuture.from(downloadFileOnDeviceUriFuture)
+ .transformAsync(
+ unused -> {
+ sharedFileBuilder.setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS);
- mayNotifyCurrentSizeOfPartiallyDownloadedFile(groupKey, downloadFileOnDeviceUri);
+ // Ignoring failure to write back here, as it will just result in one
+ // extra try
+ // to download the file.
+ return sharedFilesMetadata.write(newFileKey, sharedFileBuilder.build());
+ },
+ sequentialControlExecutor)
+ .transformAsync(
+ unused -> {
+ Uri downloadFileOnDeviceUri = getDone(downloadFileOnDeviceUriFuture);
+ ListenableFuture<Void> fileDownloadFuture;
+ if (!deltaDecoderOptional.isPresent() || deltaFile == null) {
+ // Download full file when delta file is null
+ DownloaderCallback downloaderCallback =
+ new DownloaderCallbackImpl(
+ sharedFilesMetadata,
+ fileStorage,
+ dataFile,
+ newFileKey.getAllowedReaders(),
+ eventLogger,
+ groupKey,
+ fileGroupVersionNumber,
+ buildId,
+ variantId,
+ flags,
+ sequentialControlExecutor);
- fileDownloadFuture =
- fileDownloader.startDownloading(
- groupKey,
- fileGroupVersionNumber,
- buildId,
- downloadFileOnDeviceUri,
- dataFile.getUrlToDownload(),
- dataFile.getByteSize(),
- downloadConditions,
- downloaderCallback,
- trafficTag,
- extraHttpHeaders);
- } else {
- DownloaderCallback downloaderCallback =
- new DeltaFileDownloaderCallbackImpl(
- context,
- sharedFilesMetadata,
- fileStorage,
- silentFeedback,
- dataFile,
- newFileKey.getAllowedReaders(),
- deltaDecoderOptional.get(),
- deltaFile,
- eventLogger,
- groupKey,
- fileGroupVersionNumber,
- buildId,
- variantId,
- instanceId,
- flags,
- sequentialControlExecutor);
+ mayNotifyCurrentSizeOfPartiallyDownloadedFile(
+ groupKey, downloadFileOnDeviceUri);
- mayNotifyCurrentSizeOfPartiallyDownloadedFile(groupKey, downloadFileOnDeviceUri);
+ fileDownloadFuture =
+ fileDownloader.startDownloading(
+ newFileKey.getChecksum(),
+ groupKey,
+ fileGroupVersionNumber,
+ buildId,
+ variantId,
+ downloadFileOnDeviceUri,
+ dataFile.getUrlToDownload(),
+ dataFile.getByteSize(),
+ downloadConditions,
+ downloaderCallback,
+ trafficTag,
+ extraHttpHeaders);
+ } else {
+ DownloaderCallback downloaderCallback =
+ new DeltaFileDownloaderCallbackImpl(
+ context,
+ sharedFilesMetadata,
+ fileStorage,
+ silentFeedback,
+ dataFile,
+ newFileKey.getAllowedReaders(),
+ deltaDecoderOptional.get(),
+ deltaFile,
+ eventLogger,
+ groupKey,
+ fileGroupVersionNumber,
+ buildId,
+ variantId,
+ instanceId,
+ flags,
+ sequentialControlExecutor);
- fileDownloadFuture =
- fileDownloader.startDownloading(
- groupKey,
- fileGroupVersionNumber,
- buildId,
- downloadFileOnDeviceUri,
- deltaFile.getUrlToDownload(),
- deltaFile.getByteSize(),
- downloadConditions,
- downloaderCallback,
- trafficTag,
- extraHttpHeaders);
- }
- return fileDownloadFuture;
- },
- sequentialControlExecutor);
+ mayNotifyCurrentSizeOfPartiallyDownloadedFile(
+ groupKey, downloadFileOnDeviceUri);
+
+ fileDownloadFuture =
+ fileDownloader.startDownloading(
+ newFileKey.getChecksum(),
+ groupKey,
+ fileGroupVersionNumber,
+ buildId,
+ variantId,
+ downloadFileOnDeviceUri,
+ deltaFile.getUrlToDownload(),
+ deltaFile.getByteSize(),
+ downloadConditions,
+ downloaderCallback,
+ trafficTag,
+ extraHttpHeaders);
+ }
+ return fileDownloadFuture;
+ },
+ sequentialControlExecutor);
+ },
+ sequentialControlExecutor);
}
/**
@@ -570,15 +659,15 @@ public class SharedFileManager {
checksum,
silentFeedback,
instanceId,
- /* androidShared = */ false);
+ /* androidShared= */ false);
if (downloadFileOnDeviceUri == null) {
LogUtil.e("%s: Failed to get file uri!", TAG);
- return Futures.immediateFailedFuture(
+ return immediateFailedFuture(
DownloadException.builder()
.setDownloadResultCode(DownloadResultCode.UNABLE_TO_CREATE_FILE_URI_ERROR)
.build());
}
- return Futures.immediateFuture(downloadFileOnDeviceUri);
+ return immediateFuture(downloadFileOnDeviceUri);
}
private void mayNotifyCurrentSizeOfPartiallyDownloadedFile(
@@ -599,10 +688,10 @@ public class SharedFileManager {
}
private ListenableFuture<DataFileGroupInternal> getDataFileGroupOrDefault(GroupKey groupKey) {
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
fileGroupsMetadata.read(groupKey),
fileGroup ->
- Futures.immediateFuture(
+ immediateFuture(
(fileGroup == null) ? DataFileGroupInternal.getDefaultInstance() : fileGroup),
sequentialControlExecutor);
}
@@ -621,10 +710,10 @@ public class SharedFileManager {
< FileKeyVersion.USE_CHECKSUM_ONLY.value
|| !deltaDecoderOptional.isPresent()
|| deltaDecoderOptional.get().getDecoderName() == DiffDecoder.UNSPECIFIED) {
- return Futures.immediateFuture(null);
+ return immediateFuture(null);
}
return findFirstDeltaFileWithBaseFileDownloaded(
- dataFile.getDeltaFileList(), /* index = */ 0, allowedReaders);
+ dataFile.getDeltaFileList(), /* index= */ 0, allowedReaders);
}
// We must use recursion here since the decision to continue iterating is dependent on the result
@@ -632,7 +721,7 @@ public class SharedFileManager {
private ListenableFuture<@NullableType DeltaFile> findFirstDeltaFileWithBaseFileDownloaded(
List<DeltaFile> deltaFiles, int index, AllowedReaders allowedReaders) {
if (index == deltaFiles.size()) {
- return Futures.immediateFuture(null);
+ return immediateFuture(null);
}
DeltaFile deltaFile = deltaFiles.get(index);
if (deltaFile.getDiffDecoder() != deltaDecoderOptional.get().getDecoderName()) {
@@ -643,7 +732,7 @@ public class SharedFileManager {
.setChecksum(deltaFile.getBaseFile().getChecksum())
.setAllowedReaders(allowedReaders)
.build();
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
sharedFilesMetadata.read(baseFileKey),
baseFileMetadata -> {
if (baseFileMetadata != null
@@ -656,9 +745,9 @@ public class SharedFileManager {
baseFileKey.getChecksum(),
silentFeedback,
instanceId,
- /* androidShared = */ false);
+ /* androidShared= */ false);
if (baseFileUri != null) {
- return Futures.immediateFuture(deltaFile);
+ return immediateFuture(deltaFile);
}
}
return findFirstDeltaFileWithBaseFileDownloaded(deltaFiles, index + 1, allowedReaders);
@@ -673,9 +762,9 @@ public class SharedFileManager {
* a SharedFileMissingException if the shared file metadata is missing.
*/
ListenableFuture<FileStatus> getFileStatus(NewFileKey newFileKey) {
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
getSharedFile(newFileKey),
- existingSharedFile -> Futures.immediateFuture(existingSharedFile.getFileStatus()),
+ existingSharedFile -> immediateFuture(existingSharedFile.getFileStatus()),
sequentialControlExecutor);
}
@@ -688,14 +777,14 @@ public class SharedFileManager {
* metadata is missing or the on disk file is corrupted.
*/
ListenableFuture<Void> reVerifyFile(NewFileKey newFileKey, DataFile dataFile) {
- return FluentFuture.from(getSharedFile(newFileKey))
+ return PropagatedFluentFuture.from(getSharedFile(newFileKey))
.transformAsync(
existingSharedFile -> {
if (existingSharedFile.getFileStatus() != FileStatus.DOWNLOAD_COMPLETE) {
- return Futures.immediateVoidFuture();
+ return immediateVoidFuture();
}
// Double check that it's really complete, and update status if it's not.
- return FluentFuture.from(getOnDeviceUri(newFileKey))
+ return PropagatedFluentFuture.from(getOnDeviceUri(newFileKey))
.transformAsync(
uri -> {
if (uri == null) {
@@ -717,7 +806,7 @@ public class SharedFileManager {
FileValidator.validateDownloadedFile(
fileStorage, dataFile, uri, dataFile.getChecksum());
}
- return Futures.immediateVoidFuture();
+ return immediateVoidFuture();
},
sequentialControlExecutor)
.catchingAsync(
@@ -730,7 +819,7 @@ public class SharedFileManager {
existingSharedFile.toBuilder()
.setFileStatus(FileStatus.CORRUPTED)
.build();
- return FluentFuture.from(
+ return PropagatedFluentFuture.from(
sharedFilesMetadata.write(newFileKey, updatedSharedFile))
.transformAsync(
ok -> {
@@ -755,16 +844,16 @@ public class SharedFileManager {
* may throw a SharedFileMissingException if the shared file metadata is missing.
*/
ListenableFuture<SharedFile> getSharedFile(NewFileKey newFileKey) {
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
sharedFilesMetadata.read(newFileKey),
existingSharedFile -> {
if (existingSharedFile == null) {
// TODO(b/131166925): MDD dump should not use lite proto toString.
LogUtil.e(
- "%s: getSharedFile called on file that doesn't exists! Key = %s", TAG, newFileKey);
- return Futures.immediateFailedFuture(new SharedFileMissingException());
+ "%s: getSharedFile called on file that doesn't exist! Key = %s", TAG, newFileKey);
+ return immediateFailedFuture(new SharedFileMissingException());
}
- return Futures.immediateFuture(existingSharedFile);
+ return immediateFuture(existingSharedFile);
},
sequentialControlExecutor);
}
@@ -803,7 +892,7 @@ public class SharedFileManager {
*/
ListenableFuture<Boolean> updateMaxExpirationDateSecs(
NewFileKey newFileKey, long fileExpirationDateSecs) {
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
getSharedFile(newFileKey),
existingSharedFile -> {
if (fileExpirationDateSecs > existingSharedFile.getMaxExpirationDateSecs()) {
@@ -813,7 +902,7 @@ public class SharedFileManager {
.build();
return sharedFilesMetadata.write(newFileKey, updatedSharedFile);
}
- return Futures.immediateFuture(true);
+ return immediateFuture(true);
},
sequentialControlExecutor);
}
@@ -827,29 +916,56 @@ public class SharedFileManager {
* is an error populating the uri of the file.
*/
public ListenableFuture<@NullableType Uri> getOnDeviceUri(NewFileKey newFileKey) {
- return Futures.transformAsync(
- sharedFilesMetadata.read(newFileKey),
- sharedFile -> {
- if (sharedFile == null) {
- // TODO(b/131166925): MDD dump should not use lite proto toString.
- LogUtil.e(
- "%s: getOnDeviceUri called on file that doesn't exists. Key = %s!",
- TAG, newFileKey);
- return Futures.immediateFailedFuture(new SharedFileMissingException());
- }
+ return PropagatedFutures.transform(
+ getOnDeviceUris(ImmutableSet.of(newFileKey)),
+ uris -> uris.get(newFileKey),
+ directExecutor());
+ }
- Uri onDeviceUri =
- DirectoryUtil.getOnDeviceUri(
- context,
- newFileKey.getAllowedReaders(),
- sharedFile.getFileName(),
- sharedFile.getAndroidSharingChecksum(),
- silentFeedback,
- instanceId,
- sharedFile.getAndroidShared());
- return Futures.immediateFuture(onDeviceUri);
- },
- sequentialControlExecutor);
+ /**
+ * Get the known on-device uris for a given list of {@link NewFileKey}s
+ *
+ * <p>The returned map may or may not have an entry for each NewFileKey on the list, depending on
+ * if it was possible to create the uri (see {@link DirectoryUtil#getOnDeviceUri()} for more
+ * details).
+ *
+ * <p>If any {@link NewFileKey} does not map to a {@link SharedFile}, the returned future will be
+ * a failure containing {@link SharedFileMissingException}.
+ */
+ ListenableFuture<ImmutableMap<NewFileKey, Uri>> getOnDeviceUris(
+ ImmutableSet<NewFileKey> newFileKeys) {
+ return PropagatedFluentFuture.from(sharedFilesMetadata.readAll(newFileKeys))
+ .transformAsync(
+ sharedFileMap -> {
+ ImmutableMap.Builder<NewFileKey, Uri> uriMapBuilder = ImmutableMap.builder();
+ for (NewFileKey newFileKey : newFileKeys) {
+ // Make sure all SharedFiles exist.
+ if (!sharedFileMap.containsKey(newFileKey)) {
+ // TODO(b/131166925): MDD dump should not use lite proto toString.
+ LogUtil.e(
+ "%s: getOnDeviceUris called on file that doesn't exist. Key = %s!",
+ TAG, newFileKey);
+ return immediateFailedFuture(new SharedFileMissingException());
+ }
+
+ SharedFile sharedFile = sharedFileMap.get(newFileKey);
+
+ Uri onDeviceUri =
+ DirectoryUtil.getOnDeviceUri(
+ context,
+ newFileKey.getAllowedReaders(),
+ sharedFile.getFileName(),
+ sharedFile.getAndroidSharingChecksum(),
+ silentFeedback,
+ instanceId,
+ sharedFile.getAndroidShared());
+ if (onDeviceUri != null) {
+ uriMapBuilder.put(newFileKey, onDeviceUri);
+ }
+ }
+ return immediateFuture(uriMapBuilder.build());
+ },
+ sequentialControlExecutor);
}
/**
@@ -861,13 +977,13 @@ public class SharedFileManager {
*/
// TODO - refactor to throw Exception when write to SharedPreferences fails
ListenableFuture<Boolean> removeFileEntry(NewFileKey newFileKey) {
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
sharedFilesMetadata.read(newFileKey),
sharedFile -> {
if (sharedFile == null) {
// TODO(b/131166925): MDD dump should not use lite proto toString.
LogUtil.e("%s: No file entry with key %s", TAG, newFileKey);
- return Futures.immediateFuture(false);
+ return immediateFuture(false);
}
Uri onDeviceUri =
@@ -878,19 +994,19 @@ public class SharedFileManager {
newFileKey.getChecksum(),
silentFeedback,
instanceId,
- /* androidShared = */ false);
+ /* androidShared= */ false);
if (onDeviceUri != null) {
- fileDownloader.stopDownloading(onDeviceUri);
+ fileDownloader.stopDownloading(newFileKey.getChecksum(), onDeviceUri);
}
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
sharedFilesMetadata.remove(newFileKey),
removeSuccess -> {
if (!removeSuccess) {
// TODO(b/131166925): MDD dump should not use lite proto toString.
LogUtil.e("%s: Unable to modify file subscription for key %s", TAG, newFileKey);
- return Futures.immediateFuture(false);
+ return immediateFuture(false);
}
- return Futures.immediateFuture(true);
+ return immediateFuture(true);
},
sequentialControlExecutor);
},
@@ -901,6 +1017,7 @@ public class SharedFileManager {
* Clears all storage used by the SharedFileManager and deletes all files that have been
* downloaded to MDD's directory.
*/
+
// TODO(b/124072754): Change to package private once all code is refactored.
public ListenableFuture<Void> clear() {
// If sdk is R+, try release all leases that the MDD Client may have acquired. This
@@ -913,14 +1030,14 @@ public class SharedFileManager {
} catch (IOException e) {
silentFeedback.send(e, "Failure while deleting mdd storage during clear");
}
- return Futures.immediateVoidFuture();
+ return immediateVoidFuture();
}
private void releaseAllAndroidSharedFiles() {
try {
Uri allLeasesUri = DirectoryUtil.getBlobStoreAllLeasesUri(context);
fileStorage.deleteFile(allLeasesUri);
- eventLogger.logEventSampled(0);
+ eventLogger.logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
} catch (UnsupportedFileStorageOperation e) {
LogUtil.v(
"%s: Failed to release the leases in the android shared storage."
@@ -928,12 +1045,12 @@ public class SharedFileManager {
TAG);
} catch (IOException e) {
LogUtil.e(e, "%s: Failed to release the leases in the android shared storage", TAG);
- eventLogger.logEventSampled(0);
+ eventLogger.logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
}
}
public ListenableFuture<Void> cancelDownloadAndClear() {
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
sharedFilesMetadata.getAllFileKeys(),
newFileKeyList -> {
List<ListenableFuture<Void>> cancelDownloadFutures = new ArrayList<>();
@@ -947,19 +1064,19 @@ public class SharedFileManager {
} catch (Exception e) {
silentFeedback.send(e, "Failed to cancel all downloads during clear");
}
- return Futures.whenAllComplete(cancelDownloadFutures)
+ return PropagatedFutures.whenAllComplete(cancelDownloadFutures)
.callAsync(this::clear, sequentialControlExecutor);
},
sequentialControlExecutor);
}
public ListenableFuture<Void> cancelDownload(NewFileKey newFileKey) {
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
sharedFilesMetadata.read(newFileKey),
sharedFile -> {
if (sharedFile == null) {
LogUtil.e("%s: Unable to read sharedFile from shared preferences.", TAG);
- return Futures.immediateFailedFuture(new SharedFileMissingException());
+ return immediateFailedFuture(new SharedFileMissingException());
}
if (sharedFile.getFileStatus() != FileStatus.DOWNLOAD_COMPLETE) {
Uri onDeviceUri =
@@ -970,9 +1087,19 @@ public class SharedFileManager {
newFileKey.getChecksum(),
silentFeedback,
instanceId,
- /* androidShared = */ false); // while downloading androidShared is always false
+ /* androidShared= */ false); // while downloading androidShared is always false
if (onDeviceUri != null) {
- fileDownloader.stopDownloading(onDeviceUri);
+ fileDownloader.stopDownloading(newFileKey.getChecksum(), onDeviceUri);
+ }
+ // If the download was in progress, reset it back to subscribed, so it can be properly
+ // restarted.
+ if (sharedFile.getFileStatus() == FileStatus.DOWNLOAD_IN_PROGRESS) {
+ return PropagatedFutures.transformAsync(
+ sharedFilesMetadata.write(
+ newFileKey,
+ sharedFile.toBuilder().setFileStatus(FileStatus.SUBSCRIBED).build()),
+ unused -> immediateVoidFuture(),
+ sequentialControlExecutor);
}
}
return immediateVoidFuture();
@@ -983,22 +1110,22 @@ public class SharedFileManager {
/** Dumps the current internal state of the SharedFileManager. */
public ListenableFuture<Void> dump(final PrintWriter writer) {
writer.println("==== MDD_SHARED_FILES ====");
- return Futures.transformAsync(
+ return PropagatedFutures.transformAsync(
sharedFilesMetadata.getAllFileKeys(),
allFileKeys -> {
ListenableFuture<Void> writeFilesFuture = immediateVoidFuture();
for (NewFileKey newFileKey : allFileKeys) {
writeFilesFuture =
- Futures.transformAsync(
+ PropagatedFutures.transformAsync(
writeFilesFuture,
voidArg ->
- Futures.transformAsync(
+ PropagatedFutures.transformAsync(
sharedFilesMetadata.read(newFileKey),
sharedFile -> {
if (sharedFile == null) {
LogUtil.e(
"%s: Unable to read sharedFile from shared preferences.", TAG);
- return Futures.immediateVoidFuture();
+ return immediateVoidFuture();
}
// TODO(b/131166925): MDD dump should not use lite proto toString.
writer.format(
@@ -1017,14 +1144,14 @@ public class SharedFileManager {
newFileKey.getChecksum(),
silentFeedback,
instanceId,
- /* androidShared = */ false);
+ /* androidShared= */ false);
if (serializedUri != null) {
writer.format(
"Checksum downloaded file: %s\n",
FileValidator.computeSha1Digest(fileStorage, serializedUri));
}
}
- return Futures.immediateVoidFuture();
+ return immediateVoidFuture();
},
sequentialControlExecutor),
sequentialControlExecutor);