• 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.inline;
17 
18 import static com.google.android.libraries.mobiledatadownload.internal.MddConstants.INLINE_FILE_URL_SCHEME;
19 import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
20 import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
21 
22 import com.google.android.libraries.mobiledatadownload.DownloadException;
23 import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode;
24 import com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest;
25 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader;
26 import com.google.android.libraries.mobiledatadownload.downloader.InlineDownloadParams;
27 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
28 import com.google.android.libraries.mobiledatadownload.file.openers.ReadStreamOpener;
29 import com.google.android.libraries.mobiledatadownload.file.openers.WriteStreamOpener;
30 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil;
31 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures;
32 import com.google.common.io.ByteStreams;
33 import com.google.common.util.concurrent.ListenableFuture;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.OutputStream;
37 import java.util.concurrent.Executor;
38 
39 /**
40  * An implementation of {@link FileDownloader} that supports copying in-memory data to disk.
41  *
42  * <p>This implementation only supports copying for "inlinefile:" url schemes. For more details see
43  * <internal>.
44  *
45  * <p>NOTE: copying in-memory data can be thought of as "inline file downloading," hence the naming
46  * of this class.
47  */
48 public final class InlineFileDownloader implements FileDownloader {
49   private static final String TAG = "InlineFileDownloader";
50 
51   private final SynchronousFileStorage fileStorage;
52   private final Executor downloadExecutor;
53 
54   /**
55    * Construct InlineFileDownloader instance.
56    *
57    * @param fileStorage a file storage instance used to perform I/O
58    * @param downloadExecutor executor that will perfrom the download. This should be
59    *     the @MddDownloadExecutor
60    */
InlineFileDownloader(SynchronousFileStorage fileStorage, Executor downloadExecutor)61   public InlineFileDownloader(SynchronousFileStorage fileStorage, Executor downloadExecutor) {
62     this.fileStorage = fileStorage;
63     this.downloadExecutor = downloadExecutor;
64   }
65 
66   @Override
startDownloading(DownloadRequest downloadRequest)67   public ListenableFuture<Void> startDownloading(DownloadRequest downloadRequest) {
68     if (!downloadRequest.urlToDownload().startsWith(INLINE_FILE_URL_SCHEME)) {
69       LogUtil.e(
70           "%s: Invalid url given, expected to start with 'inlinefile:', but was %s",
71           TAG, downloadRequest.urlToDownload());
72       return immediateFailedFuture(
73           DownloadException.builder()
74               .setDownloadResultCode(DownloadResultCode.INVALID_INLINE_FILE_URL_SCHEME)
75               .setMessage("InlineFileDownloader only supports copying inlinefile: scheme")
76               .build());
77     }
78     // DownloadRequest requires InlineDownloadParams to be present when building a request with
79     // inlinefile scheme, so we can access it directly here.
80     InlineDownloadParams inlineDownloadParams =
81         downloadRequest.inlineDownloadParamsOptional().get();
82 
83     return PropagatedFutures.submitAsync(
84         () -> {
85           try (InputStream inlineFileStream = getInputStream(inlineDownloadParams);
86               OutputStream destinationStream =
87                   fileStorage.open(downloadRequest.fileUri(), WriteStreamOpener.create())) {
88             ByteStreams.copy(inlineFileStream, destinationStream);
89             destinationStream.flush();
90           } catch (IOException e) {
91             LogUtil.e(e, "%s: Unable to copy file content.", TAG);
92             return immediateFailedFuture(
93                 DownloadException.builder()
94                     .setCause(e)
95                     .setDownloadResultCode(DownloadResultCode.INLINE_FILE_IO_ERROR)
96                     .build());
97           }
98           return immediateVoidFuture();
99         },
100         downloadExecutor);
101   }
102 
getInputStream(InlineDownloadParams params)103   private InputStream getInputStream(InlineDownloadParams params) throws IOException {
104     switch (params.inlineFileContent().getKind()) {
105       case URI:
106         return fileStorage.open(params.inlineFileContent().uri(), ReadStreamOpener.create());
107       case BYTESTRING:
108         return params.inlineFileContent().byteString().newInput();
109     }
110     throw new IllegalStateException("unreachable");
111   }
112 }
113