• 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.file;
17 
18 import android.net.Uri;
19 import com.google.android.libraries.mobiledatadownload.file.spi.Backend;
20 import com.google.android.libraries.mobiledatadownload.file.spi.Monitor;
21 import com.google.android.libraries.mobiledatadownload.file.spi.Transform;
22 import com.google.common.collect.Iterables;
23 import com.google.errorprone.annotations.CanIgnoreReturnValue;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 
31 /**
32  * Encapsulates state for a single open call including selected backend, transforms, etc. This class
33  * is used as single parameter to {@link Opener#open} call.
34  */
35 public final class OpenContext {
36 
37   private final SynchronousFileStorage storage;
38   private final Backend backend;
39   private final List<Transform> transforms;
40   private final List<Monitor> monitors;
41   private final Uri originalUri;
42   private final Uri encodedUri;
43 
44   /** Builder for constructing an OpenContext. */
45   static class Builder {
46     private SynchronousFileStorage storage;
47     private Backend backend;
48     private List<Transform> transforms;
49     private List<Monitor> monitors;
50     private Uri originalUri;
51     private Uri encodedUri;
52 
Builder()53     private Builder() {}
54 
55     @CanIgnoreReturnValue
setStorage(SynchronousFileStorage storage)56     Builder setStorage(SynchronousFileStorage storage) {
57       this.storage = storage;
58       return this;
59     }
60 
61     @CanIgnoreReturnValue
setBackend(Backend backend)62     Builder setBackend(Backend backend) {
63       this.backend = backend;
64       return this;
65     }
66 
67     @CanIgnoreReturnValue
setTransforms(List<Transform> transforms)68     Builder setTransforms(List<Transform> transforms) {
69       this.transforms = transforms;
70       return this;
71     }
72 
73     @CanIgnoreReturnValue
setMonitors(List<Monitor> monitors)74     Builder setMonitors(List<Monitor> monitors) {
75       this.monitors = monitors;
76       return this;
77     }
78 
79     @CanIgnoreReturnValue
setEncodedUri(Uri encodedUri)80     Builder setEncodedUri(Uri encodedUri) {
81       this.encodedUri = encodedUri;
82       return this;
83     }
84 
85     @CanIgnoreReturnValue
setOriginalUri(Uri originalUri)86     Builder setOriginalUri(Uri originalUri) {
87       this.originalUri = originalUri;
88       return this;
89     }
90 
build()91     OpenContext build() {
92       return new OpenContext(this);
93     }
94   }
95 
OpenContext(Builder builder)96   OpenContext(Builder builder) {
97     this.storage = builder.storage;
98     this.backend = builder.backend;
99     this.transforms = builder.transforms;
100     this.monitors = builder.monitors;
101     this.originalUri = builder.originalUri;
102     this.encodedUri = builder.encodedUri;
103   }
104 
builder()105   public static OpenContext.Builder builder() {
106     return new OpenContext.Builder();
107   }
108 
109   /** Gets a reference to the same storage instance. */
storage()110   public SynchronousFileStorage storage() {
111     return storage;
112   }
113 
114   /** Access the backend selected by the URI. */
backend()115   public Backend backend() {
116     return backend;
117   }
118 
119   /**
120    * Return the URI after encoding of the filename and stripping of the fragment. This is what the
121    * backend sees.
122    */
encodedUri()123   public Uri encodedUri() {
124     return encodedUri;
125   }
126 
127   /** Get the original URI. This is the one the caller passed to the storage API. */
originalUri()128   public Uri originalUri() {
129     return originalUri;
130   }
131 
132   /**
133    * Composes an input stream by chaining {@link MonitorInputStream} and {@link
134    * Transform#wrapForRead}s.
135    *
136    * @return All of the input streams in the chain. The first is returned to client, and the last is
137    *     the one produced by the backend.
138    */
chainTransformsForRead(InputStream in)139   public List<InputStream> chainTransformsForRead(InputStream in) throws IOException {
140     List<InputStream> chain = new ArrayList<>();
141     chain.add(in);
142     if (!monitors.isEmpty()) {
143       MonitorInputStream monitorStream = MonitorInputStream.newInstance(monitors, originalUri, in);
144       if (monitorStream != null) {
145         chain.add(monitorStream);
146       }
147     }
148     for (Transform transform : transforms) {
149       chain.add(transform.wrapForRead(originalUri, Iterables.getLast(chain)));
150     }
151     Collections.reverse(chain);
152     return chain;
153   }
154 
155   /**
156    * Composes an output stream by chaining {@link MonitorOutputStream} and {@link
157    * Transform#wrapForWrite}s.
158    *
159    * @return All of the output streams in the chain. The first is returned to client, and the last
160    *     is the one produced by the backend.
161    */
chainTransformsForWrite(OutputStream out)162   public List<OutputStream> chainTransformsForWrite(OutputStream out) throws IOException {
163     List<OutputStream> chain = new ArrayList<>();
164     chain.add(out);
165     if (!monitors.isEmpty()) {
166       MonitorOutputStream monitorStream =
167           MonitorOutputStream.newInstanceForWrite(monitors, originalUri, out);
168       if (monitorStream != null) {
169         chain.add(monitorStream);
170       }
171     }
172     for (Transform transform : transforms) {
173       chain.add(transform.wrapForWrite(originalUri, Iterables.getLast(chain)));
174     }
175     Collections.reverse(chain);
176     return chain;
177   }
178 
179   /**
180    * Composes an output stream by chaining {@link MonitorOutputStream} and {@link
181    * Transform#wrapForAppend}s.
182    *
183    * @return All of the output streams in the chain. The first is returned to client, and the last
184    *     is the one produced by the backend.
185    */
chainTransformsForAppend(OutputStream out)186   public List<OutputStream> chainTransformsForAppend(OutputStream out) throws IOException {
187     List<OutputStream> chain = new ArrayList<>();
188     chain.add(out);
189     if (!monitors.isEmpty()) {
190       MonitorOutputStream monitorStream =
191           MonitorOutputStream.newInstanceForAppend(monitors, originalUri, out);
192       if (monitorStream != null) {
193         chain.add(monitorStream);
194       }
195     }
196     for (Transform transform : transforms) {
197       chain.add(transform.wrapForAppend(originalUri, Iterables.getLast(chain)));
198     }
199     Collections.reverse(chain);
200     return chain;
201   }
202 
203   /** Tells whether there are any transforms configured for this open request. */
hasTransforms()204   public boolean hasTransforms() {
205     // NOTE: a more intelligent API might check for any transforms that aren't Sizable
206     return !transforms.isEmpty();
207   }
208 }
209