• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.libraries.mobiledatadownload.populator;
17 
18 import android.accounts.Account;
19 import com.google.android.libraries.mobiledatadownload.AddFileGroupRequest;
20 import com.google.android.libraries.mobiledatadownload.AggregateException;
21 import com.google.android.libraries.mobiledatadownload.MobileDataDownload;
22 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil;
23 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFluentFuture;
24 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures;
25 import com.google.common.base.Optional;
26 import com.google.common.util.concurrent.FutureCallback;
27 import com.google.common.util.concurrent.Futures;
28 import com.google.common.util.concurrent.ListenableFuture;
29 import com.google.common.util.concurrent.MoreExecutors;
30 import com.google.mobiledatadownload.DownloadConfigProto.DataFile;
31 import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup;
32 import com.google.mobiledatadownload.DownloadConfigProto.DeltaFile;
33 import com.google.mobiledatadownload.DownloadConfigProto.ManifestConfig;
34 import java.util.ArrayList;
35 import java.util.List;
36 
37 /** Shared functions for ManifestConfig. */
38 public final class ManifestConfigHelper {
39   public static final String URL_TEMPLATE_CHECKSUM_PLACEHOLDER = "{checksum}";
40 
41   private static final String TAG = "ManifestConfigHelper";
42 
43   private final MobileDataDownload mobileDataDownload;
44   private final Optional<ManifestConfigOverrider> overriderOptional;
45   private final List<Account> accounts;
46   private final boolean addGroupsWithVariantId;
47 
48   /** Creates a new helper for converting manifest configs into data file groups. */
ManifestConfigHelper( MobileDataDownload mobileDataDownload, Optional<ManifestConfigOverrider> overriderOptional, List<Account> accounts, boolean addGroupsWithVariantId)49   ManifestConfigHelper(
50       MobileDataDownload mobileDataDownload,
51       Optional<ManifestConfigOverrider> overriderOptional,
52       List<Account> accounts,
53       boolean addGroupsWithVariantId) {
54     this.mobileDataDownload = mobileDataDownload;
55     this.overriderOptional = overriderOptional;
56     this.accounts = accounts;
57     this.addGroupsWithVariantId = addGroupsWithVariantId;
58   }
59 
60   /**
61    * Reads file groups from {@link ManifestConfig} and adds to MDD after applying the {@link
62    * ManifestConfigOverrider} if it's present.
63    *
64    * <p>This static method encapsulates shared logic between a few populators:
65    *
66    * <ul>
67    *   <li>{@link ManifestFileGroupPopulator}
68    *   <li>{@link ManifestConfigFlagPopulator}
69    *   <li>{@link LocalManifestFileGroupPopulator}
70    *   <li>{@link EmbeddedAssetManifestPopulator}
71    * </ul>
72    *
73    * @param mobileDataDownload The MDD instance
74    * @param manifestConfig The proto that contains configs for file groups and modifiers
75    * @param overriderOptional An optional overrider that takes manifest config and returns a list of
76    *     file groups to be added ot MDD
77    * @param accounts A list of accounts that the parsed file groups should be associated with
78    * @param addGroupsWithVariantId whether variantId should be included when adding the parsed file
79    *     groups
80    */
refreshFromManifestConfig( MobileDataDownload mobileDataDownload, ManifestConfig manifestConfig, Optional<ManifestConfigOverrider> overriderOptional, List<Account> accounts, boolean addGroupsWithVariantId)81   static ListenableFuture<Void> refreshFromManifestConfig(
82       MobileDataDownload mobileDataDownload,
83       ManifestConfig manifestConfig,
84       Optional<ManifestConfigOverrider> overriderOptional,
85       List<Account> accounts,
86       boolean addGroupsWithVariantId) {
87     ManifestConfigHelper helper =
88         new ManifestConfigHelper(
89             mobileDataDownload, overriderOptional, accounts, addGroupsWithVariantId);
90     return PropagatedFluentFuture.from(helper.applyOverrider(manifestConfig))
91         .transformAsync(helper::addAllFileGroups, MoreExecutors.directExecutor())
92         .catchingAsync(
93             AggregateException.class,
94             ex -> Futures.immediateVoidFuture(),
95             MoreExecutors.directExecutor());
96   }
97 
98   /** Adds the specified list of file groups to MDD. */
addAllFileGroups(List<DataFileGroup> fileGroups)99   ListenableFuture<Void> addAllFileGroups(List<DataFileGroup> fileGroups) {
100     List<ListenableFuture<Boolean>> addFileGroupFutures = new ArrayList<>();
101     Optional<String> variantId = Optional.absent();
102 
103     for (DataFileGroup dataFileGroup : fileGroups) {
104       if (dataFileGroup == null || dataFileGroup.getGroupName().isEmpty()) {
105         continue;
106       }
107 
108       // Include variantId if variant is present and helper is configured to do so
109       if (addGroupsWithVariantId && !dataFileGroup.getVariantId().isEmpty()) {
110         variantId = Optional.of(dataFileGroup.getVariantId());
111       }
112 
113       AddFileGroupRequest.Builder addFileGroupRequestBuilder =
114           AddFileGroupRequest.newBuilder()
115               .setDataFileGroup(dataFileGroup)
116               .setVariantIdOptional(variantId);
117 
118       // Add once without any account
119       ListenableFuture<Boolean> addFileGroupFuture =
120           mobileDataDownload.addFileGroup(addFileGroupRequestBuilder.build());
121       attachLoggingCallback(
122           addFileGroupFuture,
123           dataFileGroup.getGroupName(),
124           /* account= */ Optional.absent(),
125           variantId);
126       addFileGroupFutures.add(addFileGroupFuture);
127 
128       // Add for each account
129       for (Account account : accounts) {
130         ListenableFuture<Boolean> addFileGroupFutureWithAccount =
131             mobileDataDownload.addFileGroup(
132                 addFileGroupRequestBuilder.setAccountOptional(Optional.of(account)).build());
133         attachLoggingCallback(
134             addFileGroupFutureWithAccount,
135             dataFileGroup.getGroupName(),
136             Optional.of(account),
137             variantId);
138         addFileGroupFutures.add(addFileGroupFutureWithAccount);
139       }
140     }
141     return PropagatedFutures.whenAllComplete(addFileGroupFutures)
142         .call(
143             () -> {
144               AggregateException.throwIfFailed(addFileGroupFutures, "Failed to add file groups");
145               return null;
146             },
147             MoreExecutors.directExecutor());
148   }
149 
attachLoggingCallback( ListenableFuture<Boolean> addFileGroupFuture, String groupName, Optional<Account> account, Optional<String> variant)150   private void attachLoggingCallback(
151       ListenableFuture<Boolean> addFileGroupFuture,
152       String groupName,
153       Optional<Account> account,
154       Optional<String> variant) {
155     PropagatedFutures.addCallback(
156         addFileGroupFuture,
157         new FutureCallback<Boolean>() {
158           @Override
159           public void onSuccess(Boolean result) {
160             if (result.booleanValue()) {
161               LogUtil.d(
162                   "%s: Added file group %s with account: %s, variant: %s",
163                   TAG,
164                   groupName,
165                   String.valueOf(account.orNull()),
166                   String.valueOf(variant.orNull()));
167             } else {
168               LogUtil.d(
169                   "%s: Failed to add file group %s with account: %s, variant: %s",
170                   TAG,
171                   groupName,
172                   String.valueOf(account.orNull()),
173                   String.valueOf(variant.orNull()));
174             }
175           }
176 
177           @Override
178           public void onFailure(Throwable t) {
179             LogUtil.e(
180                 t,
181                 "%s: Failed to add file group %s with account: %s, variant: %s",
182                 TAG,
183                 groupName,
184                 String.valueOf(account.orNull()),
185                 String.valueOf(variant.orNull()));
186           }
187         },
188         MoreExecutors.directExecutor());
189   }
190 
191   /** Applies the overrider to the manifest config to generate a list of file groups for adding. */
applyOverrider(ManifestConfig manifestConfig)192   ListenableFuture<List<DataFileGroup>> applyOverrider(ManifestConfig manifestConfig) {
193     if (overriderOptional.isPresent()) {
194       return overriderOptional.get().override(maybeApplyFileUrlTemplate(manifestConfig));
195     }
196     List<DataFileGroup> results = new ArrayList<>();
197     for (ManifestConfig.Entry entry : maybeApplyFileUrlTemplate(manifestConfig).getEntryList()) {
198       results.add(entry.getDataFileGroup());
199     }
200     return Futures.immediateFuture(results);
201   }
202 
203   /**
204    * If file_url_template is populated and file url_to_download field is empty in the {@code
205    * ManifestConfig} manifestConfig then construct the url_to_download field using the template.
206    *
207    * <p>NOTE: If file_url_template is empty then the files are expected to have the complete
208    * download URL, validate and throw an {@link IllegalArgumentException} if url_to_download is not
209    * populated.
210    */
maybeApplyFileUrlTemplate(ManifestConfig manifestConfig)211   public static ManifestConfig maybeApplyFileUrlTemplate(ManifestConfig manifestConfig) {
212     if (!manifestConfig.hasUrlTemplate()
213         || manifestConfig.getUrlTemplate().getFileUrlTemplate().isEmpty()) {
214       return validateManifestConfigFileUrls(manifestConfig);
215     }
216     String fileDownloadUrlTemplate = manifestConfig.getUrlTemplate().getFileUrlTemplate();
217     ManifestConfig.Builder updatedManifestConfigBuilder = manifestConfig.toBuilder().clearEntry();
218 
219     for (ManifestConfig.Entry entry : manifestConfig.getEntryList()) {
220       DataFileGroup.Builder dataFileGroupBuilder = entry.getDataFileGroup().toBuilder().clearFile();
221       for (DataFile dataFile : entry.getDataFileGroup().getFileList()) {
222         DataFile.Builder dataFileBuilder = dataFile.toBuilder().clearDeltaFile();
223 
224         if (dataFile.getUrlToDownload().isEmpty()) {
225           dataFileBuilder.setUrlToDownload(
226               fileDownloadUrlTemplate.replace(
227                   URL_TEMPLATE_CHECKSUM_PLACEHOLDER, dataFile.getChecksum()));
228         }
229 
230         for (DeltaFile deltaFile : dataFile.getDeltaFileList()) {
231           dataFileBuilder.addDeltaFile(
232               deltaFile.getUrlToDownload().isEmpty()
233                   ? deltaFile.toBuilder()
234                       .setUrlToDownload(
235                           fileDownloadUrlTemplate.replace(
236                               URL_TEMPLATE_CHECKSUM_PLACEHOLDER, deltaFile.getChecksum()))
237                       .build()
238                   : deltaFile);
239         }
240 
241         dataFileGroupBuilder.addFile(dataFileBuilder);
242       }
243       updatedManifestConfigBuilder.addEntry(
244           entry.toBuilder().setDataFileGroup(dataFileGroupBuilder));
245     }
246     return updatedManifestConfigBuilder.build();
247   }
248 
249   /**
250    * Validates that all the files in {@code ManifestConfig} manifestConfig have the url_to_download
251    * populated.
252    */
validateManifestConfigFileUrls(ManifestConfig manifestConfig)253   private static ManifestConfig validateManifestConfigFileUrls(ManifestConfig manifestConfig) {
254     for (ManifestConfig.Entry entry : manifestConfig.getEntryList()) {
255       for (DataFile dataFile : entry.getDataFileGroup().getFileList()) {
256         if (dataFile.getUrlToDownload().isEmpty()) {
257           throw new IllegalArgumentException(
258               String.format("DataFile %s url_to_download is missing.", dataFile.getFileId()));
259         }
260         for (DeltaFile deltaFile : dataFile.getDeltaFileList()) {
261           if (deltaFile.getUrlToDownload().isEmpty()) {
262             throw new IllegalArgumentException(
263                 String.format(
264                     "DeltaFile for file %s url_to_download is missing.", dataFile.getFileId()));
265           }
266         }
267       }
268     }
269     return manifestConfig;
270   }
271 }
272