• 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.downloader.offroad;
17 
18 import android.net.Uri;
19 import android.util.Pair;
20 
21 import com.google.android.downloader.DownloadConstraints;
22 import com.google.android.downloader.DownloadConstraints.NetworkType;
23 import com.google.android.downloader.DownloadDestination;
24 import com.google.android.downloader.DownloadRequest;
25 import com.google.android.downloader.DownloadResult;
26 import com.google.android.downloader.Downloader;
27 import com.google.android.downloader.OAuthTokenProvider;
28 import com.google.android.libraries.mobiledatadownload.DownloadException;
29 import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode;
30 import com.google.android.libraries.mobiledatadownload.downloader.CheckContentChangeRequest;
31 import com.google.android.libraries.mobiledatadownload.downloader.CheckContentChangeResponse;
32 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader;
33 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
34 import com.google.android.libraries.mobiledatadownload.file.common.MalformedUriException;
35 import com.google.android.libraries.mobiledatadownload.file.integration.downloader.DownloadDestinationOpener;
36 import com.google.android.libraries.mobiledatadownload.file.integration.downloader.DownloadMetadataStore;
37 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil;
38 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFluentFuture;
39 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures;
40 import com.google.common.base.Optional;
41 import com.google.common.base.Strings;
42 import com.google.common.util.concurrent.FluentFuture;
43 import com.google.common.util.concurrent.Futures;
44 import com.google.common.util.concurrent.ListenableFuture;
45 
46 import java.io.IOException;
47 import java.net.URI;
48 import java.util.concurrent.Executor;
49 
50 import javax.annotation.Nullable;
51 
52 /**
53  * An implementation of the {@link
54  * com.google.android.libraries.mobiledatadownload.downloader.FileDownloader} using <internal>
55  */
56 public final class Offroad2FileDownloader implements FileDownloader {
57     private static final String TAG = "Offroad2FileDownloader";
58 
59     private final Downloader downloader;
60     private final SynchronousFileStorage fileStorage;
61     private final Executor downloadExecutor;
62     private final DownloadMetadataStore downloadMetadataStore;
63     private final ExceptionHandler exceptionHandler;
64     //  private final Optional<Supplier<CookieJar>> cookieJarSupplierOptional;
65     private final Optional<Integer> defaultTrafficTag;
66     @Nullable
67     private final OAuthTokenProvider authTokenProvider;
68 
Offroad2FileDownloader( Downloader downloader, SynchronousFileStorage fileStorage, Executor downloadExecutor, @Nullable OAuthTokenProvider authTokenProvider, DownloadMetadataStore downloadMetadataStore, ExceptionHandler exceptionHandler, Optional<Integer> defaultTrafficTag)69     public Offroad2FileDownloader(
70             Downloader downloader,
71             SynchronousFileStorage fileStorage,
72             Executor downloadExecutor,
73             @Nullable OAuthTokenProvider authTokenProvider,
74             DownloadMetadataStore downloadMetadataStore,
75             ExceptionHandler exceptionHandler,
76 //      Optional<Supplier<CookieJar>> cookieJarSupplierOptional,
77             Optional<Integer> defaultTrafficTag) {
78         this.downloader = downloader;
79         this.fileStorage = fileStorage;
80         this.downloadExecutor = downloadExecutor;
81         this.authTokenProvider = authTokenProvider;
82         this.downloadMetadataStore = downloadMetadataStore;
83         this.exceptionHandler = exceptionHandler;
84 //    this.cookieJarSupplierOptional = cookieJarSupplierOptional;
85         this.defaultTrafficTag = defaultTrafficTag;
86     }
87 
88     @Override
startDownloading( com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest fileDownloaderRequest)89     public ListenableFuture<Void> startDownloading(
90             com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest
91                     fileDownloaderRequest) {
92         String fileName = Strings.nullToEmpty(fileDownloaderRequest.fileUri().getLastPathSegment());
93 
94         DownloadDestination downloadDestination;
95         try {
96             downloadDestination = buildDownloadDestination(fileDownloaderRequest.fileUri());
97         } catch (DownloadException e) {
98             return Futures.immediateFailedFuture(e);
99         }
100 
101         DownloadRequest offroad2DownloadRequest =
102                 buildDownloadRequest(fileDownloaderRequest, downloadDestination);
103 
104         FluentFuture<DownloadResult> resultFuture = downloader.execute(offroad2DownloadRequest);
105 
106         LogUtil.d(
107                 "%s: Data download scheduled for file: %s", TAG,
108                 fileDownloaderRequest.urlToDownload());
109 
110         return PropagatedFluentFuture.from(resultFuture)
111                 .catchingAsync(
112                         Exception.class,
113                         cause -> {
114                             LogUtil.d(
115                                     cause,
116                                     "%s: Failed to download file %s due to: %s",
117                                     TAG,
118                                     fileName,
119                                     Strings.nullToEmpty(cause.getMessage()));
120 
121                             DownloadException exception =
122                                     exceptionHandler.mapToDownloadException("failure in download!",
123                                             cause);
124 
125                             return Futures.immediateFailedFuture(exception);
126                         },
127                         downloadExecutor)
128                 .transformAsync(
129                         (DownloadResult result) -> {
130                             LogUtil.d(
131                                     "%s: Downloaded file %s, bytes written: %d",
132                                     TAG, fileName, result.bytesWritten());
133                             return PropagatedFutures.catchingAsync(
134                                     downloadMetadataStore.delete(fileDownloaderRequest.fileUri()),
135                                     Exception.class,
136                                     e -> {
137                                         // Failing to clean up metadata shouldn't cause a failure
138                                         // in the future, log and
139                                         // return void.
140                                         LogUtil.d(e, "%s: Failed to cleanup metadata", TAG);
141                                         return Futures.immediateVoidFuture();
142                                     },
143                                     downloadExecutor);
144                         },
145                         downloadExecutor);
146     }
147 
148     @Override
149     public ListenableFuture<CheckContentChangeResponse> isContentChanged(
150             CheckContentChangeRequest checkContentChangeRequest) {
151         return Futures.immediateFailedFuture(
152                 new UnsupportedOperationException(
153                         "Checking for content changes is currently unsupported for Downloader2"));
154     }
155 
156     private DownloadDestination buildDownloadDestination(Uri destinationUri)
157             throws DownloadException {
158         try {
159             // Create DownloadDestination using mobstore
160             // NOTE: the use of DirectExecutor here should be fine since all async operations
161             // of DownloadDestination happen within Downloader2 IOExecutor. Consider replacing
162             // this with
163             // lightweight executor.
164             return fileStorage.open(
165                     destinationUri,
166                     DownloadDestinationOpener.create(downloadMetadataStore));
167         } catch (IOException e) {
168             if (e instanceof MalformedUriException
169                     || e.getCause() instanceof IllegalArgumentException) {
170                 LogUtil.e("%s: The file uri is invalid, uri = %s", TAG, destinationUri);
171                 throw DownloadException.builder()
172                         .setDownloadResultCode(DownloadResultCode.MALFORMED_FILE_URI_ERROR)
173                         .setCause(e)
174                         .build();
175             } else {
176                 LogUtil.e(e, "%s: Unable to create DownloadDestination for file %s", TAG,
177                         destinationUri);
178                 // TODO: the result code is the most equivalent to downloader1 -- consider
179                 // creating a separate result code that's more appropriate for downloader2.
180                 throw DownloadException.builder()
181                         .setDownloadResultCode(
182                                 DownloadResultCode.UNABLE_TO_CREATE_MOBSTORE_RESPONSE_WRITER_ERROR)
183                         .setCause(e)
184                         .build();
185             }
186         }
187     }
188 
189     private DownloadRequest buildDownloadRequest(
190             com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest
191                     fileDownloaderRequest,
192             DownloadDestination downloadDestination) {
193         DownloadRequest.Builder requestBuilder =
194                 downloader.newRequestBuilder(
195                         URI.create(fileDownloaderRequest.urlToDownload()), downloadDestination);
196 
197 //    if (cookieJarSupplierOptional.isPresent()) {
198 //     requestBuilder.setCookieJar(cookieJarSupplierOptional.get().get());
199 //    }
200 
201         requestBuilder.setOAuthTokenProvider(authTokenProvider);
202 
203         if (com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
204                 .NETWORK_CONNECTED
205                 == fileDownloaderRequest.downloadConstraints()) {
206             requestBuilder.setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED);
207         } else {
208             // Use all network types except cellular and require unmetered network.
209             requestBuilder.setDownloadConstraints(
210                     DownloadConstraints.builder()
211                             .addRequiredNetworkType(NetworkType.WIFI)
212                             .addRequiredNetworkType(NetworkType.ETHERNET)
213                             .addRequiredNetworkType(NetworkType.BLUETOOTH)
214                             .setRequireUnmeteredNetwork(true)
215                             .build());
216         }
217 
218         // TODO(b/237653774): Enable traffic tagging.
219     /*if (fileDownloaderRequest.trafficTag() > 0) {
220       // Prefer traffic tag from request.
221       requestBuilder.setTrafficStatsTag(fileDownloaderRequest.trafficTag());
222     } else if (defaultTrafficTag.isPresent() && defaultTrafficTag.get() > 0) {
223       // Use default traffic tag as a fallback if present.
224       requestBuilder.setTrafficStatsTag(defaultTrafficTag.get());
225     }*/
226 
227         for (Pair<String, String> header : fileDownloaderRequest.extraHttpHeaders()) {
228             requestBuilder.addHeader(header.first, header.second);
229         }
230 
231         return requestBuilder.build();
232     }
233 }
234