blob: ddcb9688ff2839c751b3ce20d3e4fcaaaab53d44 [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.libraries.mobiledatadownload.file;
import android.net.Uri;
import com.google.android.libraries.mobiledatadownload.file.spi.Backend;
import com.google.android.libraries.mobiledatadownload.file.spi.Monitor;
import com.google.android.libraries.mobiledatadownload.file.spi.Transform;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Encapsulates state for a single open call including selected backend, transforms, etc. This class
* is used as single parameter to {@link Opener#open} call.
*/
public final class OpenContext {
private final SynchronousFileStorage storage;
private final Backend backend;
private final List<Transform> transforms;
private final List<Monitor> monitors;
private final Uri originalUri;
private final Uri encodedUri;
/** Builder for constructing an OpenContext. */
static class Builder {
private SynchronousFileStorage storage;
private Backend backend;
private List<Transform> transforms;
private List<Monitor> monitors;
private Uri originalUri;
private Uri encodedUri;
private Builder() {}
Builder setStorage(SynchronousFileStorage storage) {
this.storage = storage;
return this;
}
Builder setBackend(Backend backend) {
this.backend = backend;
return this;
}
Builder setTransforms(List<Transform> transforms) {
this.transforms = transforms;
return this;
}
Builder setMonitors(List<Monitor> monitors) {
this.monitors = monitors;
return this;
}
Builder setEncodedUri(Uri encodedUri) {
this.encodedUri = encodedUri;
return this;
}
Builder setOriginalUri(Uri originalUri) {
this.originalUri = originalUri;
return this;
}
OpenContext build() {
return new OpenContext(this);
}
}
OpenContext(Builder builder) {
this.storage = builder.storage;
this.backend = builder.backend;
this.transforms = builder.transforms;
this.monitors = builder.monitors;
this.originalUri = builder.originalUri;
this.encodedUri = builder.encodedUri;
}
public static OpenContext.Builder builder() {
return new OpenContext.Builder();
}
/** Gets a reference to the same storage instance. */
public SynchronousFileStorage storage() {
return storage;
}
/** Access the backend selected by the URI. */
public Backend backend() {
return backend;
}
/**
* Return the URI after encoding of the filename and stripping of the fragment. This is what the
* backend sees.
*/
public Uri encodedUri() {
return encodedUri;
}
/** Get the original URI. This is the one the caller passed to the storage API. */
public Uri originalUri() {
return originalUri;
}
/**
* Composes an input stream by chaining {@link MonitorInputStream} and {@link
* Transform#wrapForRead}s.
*
* @return All of the input streams in the chain. The first is returned to client, and the last is
* the one produced by the backend.
*/
public List<InputStream> chainTransformsForRead(InputStream in) throws IOException {
List<InputStream> chain = new ArrayList<>();
chain.add(in);
if (!monitors.isEmpty()) {
MonitorInputStream monitorStream = MonitorInputStream.newInstance(monitors, originalUri, in);
if (monitorStream != null) {
chain.add(monitorStream);
}
}
for (Transform transform : transforms) {
chain.add(transform.wrapForRead(originalUri, Iterables.getLast(chain)));
}
Collections.reverse(chain);
return chain;
}
/**
* Composes an output stream by chaining {@link MonitorOutputStream} and {@link
* Transform#wrapForWrite}s.
*
* @return All of the output streams in the chain. The first is returned to client, and the last
* is the one produced by the backend.
*/
public List<OutputStream> chainTransformsForWrite(OutputStream out) throws IOException {
List<OutputStream> chain = new ArrayList<>();
chain.add(out);
if (!monitors.isEmpty()) {
MonitorOutputStream monitorStream =
MonitorOutputStream.newInstanceForWrite(monitors, originalUri, out);
if (monitorStream != null) {
chain.add(monitorStream);
}
}
for (Transform transform : transforms) {
chain.add(transform.wrapForWrite(originalUri, Iterables.getLast(chain)));
}
Collections.reverse(chain);
return chain;
}
/**
* Composes an output stream by chaining {@link MonitorOutputStream} and {@link
* Transform#wrapForAppend}s.
*
* @return All of the output streams in the chain. The first is returned to client, and the last
* is the one produced by the backend.
*/
public List<OutputStream> chainTransformsForAppend(OutputStream out) throws IOException {
List<OutputStream> chain = new ArrayList<>();
chain.add(out);
if (!monitors.isEmpty()) {
MonitorOutputStream monitorStream =
MonitorOutputStream.newInstanceForAppend(monitors, originalUri, out);
if (monitorStream != null) {
chain.add(monitorStream);
}
}
for (Transform transform : transforms) {
chain.add(transform.wrapForAppend(originalUri, Iterables.getLast(chain)));
}
Collections.reverse(chain);
return chain;
}
/** Tells whether there are any transforms configured for this open request. */
public boolean hasTransforms() {
// NOTE: a more intelligent API might check for any transforms that aren't Sizable
return !transforms.isEmpty();
}
}