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