blob: d2c87226c54fd385f37e74ea6b2020845912cd6b [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.libraries.mobiledatadownload.populator;
import android.util.Log;
import com.google.android.libraries.mobiledatadownload.AddFileGroupRequest;
import com.google.android.libraries.mobiledatadownload.MobileDataDownload;
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.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.mobiledatadownload.DownloadConfigProto.DataFile;
import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup;
import com.google.mobiledatadownload.DownloadConfigProto.DeltaFile;
import com.google.mobiledatadownload.DownloadConfigProto.ManifestConfig;
import java.util.ArrayList;
import java.util.List;
/** Shared functions for ManifestConfig. */
public final class ManifestConfigHelper {
public static final String URL_TEMPLATE_CHECKSUM_PLACEHOLDER = "{checksum}";
private static final String TAG = "ManifestConfigHelper";
private final MobileDataDownload mobileDataDownload;
private final Optional<ManifestConfigOverrider> overriderOptional;
/** Creates a new helper for converting manifest configs into data file groups. */
ManifestConfigHelper(
MobileDataDownload mobileDataDownload, Optional<ManifestConfigOverrider> overriderOptional) {
this.mobileDataDownload = mobileDataDownload;
this.overriderOptional = overriderOptional;
}
/**
* Reads file groups from {@link ManifestConfig} and adds to MDD after applying the {@link
* ManifestConfigOverrider} if it's present. This static method is shared with {@link
* ManifestFileGroupPopulator}.
*
* @param mobileDataDownload The MDD instance.
* @param manifestConfig The proto that contains configs for file groups and modifiers.
* @param overriderOptional An optional overrider that takes manifest config and returns a list of
* file groups to be added to MDD.
*/
static ListenableFuture<Void> refreshFromManifestConfig(
MobileDataDownload mobileDataDownload,
ManifestConfig manifestConfig,
Optional<ManifestConfigOverrider> overriderOptional) {
ManifestConfigHelper helper = new ManifestConfigHelper(mobileDataDownload, overriderOptional);
return PropagatedFluentFuture.from(helper.applyOverrider(manifestConfig))
.transformAsync(helper::addAllFileGroups, MoreExecutors.directExecutor());
}
/** Adds the specified list of file groups to MDD. */
ListenableFuture<Void> addAllFileGroups(List<DataFileGroup> fileGroups) {
List<ListenableFuture<Boolean>> addFileGroupFutures = new ArrayList<>();
for (DataFileGroup dataFileGroup : fileGroups) {
if (dataFileGroup == null || dataFileGroup.getGroupName().isEmpty()) {
continue;
}
ListenableFuture<Boolean> addFileGroupFuture =
mobileDataDownload.addFileGroup(
AddFileGroupRequest.newBuilder().setDataFileGroup(dataFileGroup).build());
PropagatedFutures.addCallback(
addFileGroupFuture,
new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
String groupName = dataFileGroup.getGroupName();
if (result.booleanValue()) {
Log.d(TAG, "Added file groups " + groupName);
} else {
Log.d(TAG, "Failed to add file group " + groupName);
}
}
@Override
public void onFailure(Throwable t) {
Log.e(TAG, "Failed to add file group", t);
}
},
MoreExecutors.directExecutor());
addFileGroupFutures.add(addFileGroupFuture);
}
return PropagatedFutures.whenAllComplete(addFileGroupFutures)
.call(() -> null, MoreExecutors.directExecutor());
}
/** Applies the overrider to the manifest config to generate a list of file groups for adding. */
ListenableFuture<List<DataFileGroup>> applyOverrider(ManifestConfig manifestConfig) {
if (overriderOptional.isPresent()) {
return overriderOptional.get().override(maybeApplyFileUrlTemplate(manifestConfig));
}
List<DataFileGroup> results = new ArrayList<>();
for (ManifestConfig.Entry entry : maybeApplyFileUrlTemplate(manifestConfig).getEntryList()) {
results.add(entry.getDataFileGroup());
}
return Futures.immediateFuture(results);
}
/**
* If file_url_template is populated and file url_to_download field is empty in the {@code
* ManifestConfig} manifestConfig then construct the url_to_download field using the template.
*
* <p>NOTE: If file_url_template is empty then the files are expected to have the complete
* download URL, validate and throw an {@link IllegalArgumentException} if url_to_download is not
* populated.
*/
public static ManifestConfig maybeApplyFileUrlTemplate(ManifestConfig manifestConfig) {
if (!manifestConfig.hasUrlTemplate()
|| manifestConfig.getUrlTemplate().getFileUrlTemplate().isEmpty()) {
return validateManifestConfigFileUrls(manifestConfig);
}
String fileDownloadUrlTemplate = manifestConfig.getUrlTemplate().getFileUrlTemplate();
ManifestConfig.Builder updatedManifestConfigBuilder = manifestConfig.toBuilder().clearEntry();
for (ManifestConfig.Entry entry : manifestConfig.getEntryList()) {
DataFileGroup.Builder dataFileGroupBuilder = entry.getDataFileGroup().toBuilder().clearFile();
for (DataFile dataFile : entry.getDataFileGroup().getFileList()) {
DataFile.Builder dataFileBuilder = dataFile.toBuilder().clearDeltaFile();
if (dataFile.getUrlToDownload().isEmpty()) {
dataFileBuilder.setUrlToDownload(
fileDownloadUrlTemplate.replace(
URL_TEMPLATE_CHECKSUM_PLACEHOLDER, dataFile.getChecksum()));
}
for (DeltaFile deltaFile : dataFile.getDeltaFileList()) {
dataFileBuilder.addDeltaFile(
deltaFile.getUrlToDownload().isEmpty()
? deltaFile.toBuilder()
.setUrlToDownload(
fileDownloadUrlTemplate.replace(
URL_TEMPLATE_CHECKSUM_PLACEHOLDER, deltaFile.getChecksum()))
.build()
: deltaFile);
}
dataFileGroupBuilder.addFile(dataFileBuilder);
}
updatedManifestConfigBuilder.addEntry(
entry.toBuilder().setDataFileGroup(dataFileGroupBuilder));
}
return updatedManifestConfigBuilder.build();
}
/**
* Validates that all the files in {@code ManifestConfig} manifestConfig have the url_to_download
* populated.
*/
private static ManifestConfig validateManifestConfigFileUrls(ManifestConfig manifestConfig) {
for (ManifestConfig.Entry entry : manifestConfig.getEntryList()) {
for (DataFile dataFile : entry.getDataFileGroup().getFileList()) {
if (dataFile.getUrlToDownload().isEmpty()) {
throw new IllegalArgumentException(
String.format("DataFile %s url_to_download is missing.", dataFile.getFileId()));
}
for (DeltaFile deltaFile : dataFile.getDeltaFileList()) {
if (deltaFile.getUrlToDownload().isEmpty()) {
throw new IllegalArgumentException(
String.format(
"DeltaFile for file %s url_to_download is missing.", dataFile.getFileId()));
}
}
}
}
return manifestConfig;
}
}