1 // Copyright 2016 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net.apihelpers; 6 7 import android.os.ParcelFileDescriptor; 8 9 import org.chromium.net.UploadDataProvider; 10 import org.chromium.net.UploadDataSink; 11 12 import java.io.File; 13 import java.io.FileInputStream; 14 import java.io.IOException; 15 import java.nio.ByteBuffer; 16 import java.nio.channels.FileChannel; 17 18 /** Provides implementations of {@link UploadDataProvider} for common use cases. */ 19 public final class UploadDataProviders { 20 /** 21 * Uploads an entire file. 22 * 23 * @param file The file to upload 24 * @return A new UploadDataProvider for the given file 25 */ create(final File file)26 public static UploadDataProvider create(final File file) { 27 return new FileUploadProvider( 28 new FileChannelProvider() { 29 @Override 30 public FileChannel getChannel() throws IOException { 31 return new FileInputStream(file).getChannel(); 32 } 33 }); 34 } 35 36 /** 37 * Uploads an entire file, closing the descriptor when it is no longer needed. 38 * 39 * @param fd The file descriptor to upload 40 * @return A new UploadDataProvider for the given file descriptor 41 * @throws IllegalArgumentException if {@code fd} is not a file. 42 */ 43 public static UploadDataProvider create(final ParcelFileDescriptor fd) { 44 return new FileUploadProvider( 45 new FileChannelProvider() { 46 @Override 47 public FileChannel getChannel() throws IOException { 48 if (fd.getStatSize() != -1) { 49 return new ParcelFileDescriptor.AutoCloseInputStream(fd).getChannel(); 50 } else { 51 fd.close(); 52 throw new IllegalArgumentException("Not a file: " + fd); 53 } 54 } 55 }); 56 } 57 58 /** 59 * Uploads a ByteBuffer, from the current {@code buffer.position()} to {@code buffer.limit()} 60 * 61 * @param buffer The data to upload 62 * @return A new UploadDataProvider for the given buffer 63 */ 64 public static UploadDataProvider create(ByteBuffer buffer) { 65 return new ByteBufferUploadProvider(buffer.slice()); 66 } 67 68 /** 69 * Uploads {@code length} bytes from {@code data}, starting from {@code offset} 70 * 71 * @param data Array containing data to upload 72 * @param offset Offset within data to start with 73 * @param length Number of bytes to upload 74 * @return A new UploadDataProvider for the given data 75 */ 76 public static UploadDataProvider create(byte[] data, int offset, int length) { 77 return new ByteBufferUploadProvider(ByteBuffer.wrap(data, offset, length).slice()); 78 } 79 80 /** 81 * Uploads the contents of {@code data} 82 * 83 * @param data Array containing data to upload 84 * @return A new UploadDataProvider for the given data 85 */ 86 public static UploadDataProvider create(byte[] data) { 87 return create(data, 0, data.length); 88 } 89 90 private interface FileChannelProvider { 91 FileChannel getChannel() throws IOException; 92 } 93 94 private static final class FileUploadProvider extends UploadDataProvider { 95 private volatile FileChannel mChannel; 96 private final FileChannelProvider mProvider; 97 98 /** Guards initialization of {@code mChannel} */ 99 private final Object mLock = new Object(); 100 101 private FileUploadProvider(FileChannelProvider provider) { 102 this.mProvider = provider; 103 } 104 105 @Override 106 public long getLength() throws IOException { 107 return getChannel().size(); 108 } 109 110 @Override 111 public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) throws IOException { 112 if (!byteBuffer.hasRemaining()) { 113 throw new IllegalStateException("Cronet passed a buffer with no bytes remaining"); 114 } 115 FileChannel channel = getChannel(); 116 int bytesRead = 0; 117 while (bytesRead == 0) { 118 int read = channel.read(byteBuffer); 119 if (read == -1) { 120 break; 121 } else { 122 bytesRead += read; 123 } 124 } 125 uploadDataSink.onReadSucceeded(false); 126 } 127 128 @Override 129 public void rewind(UploadDataSink uploadDataSink) throws IOException { 130 getChannel().position(0); 131 uploadDataSink.onRewindSucceeded(); 132 } 133 134 /** 135 * Lazily initializes the channel so that a blocking operation isn't performed on a 136 * non-executor thread. 137 */ 138 private FileChannel getChannel() throws IOException { 139 if (mChannel == null) { 140 synchronized (mLock) { 141 if (mChannel == null) { 142 mChannel = mProvider.getChannel(); 143 } 144 } 145 } 146 return mChannel; 147 } 148 149 @Override 150 public void close() throws IOException { 151 FileChannel channel = mChannel; 152 if (channel != null) { 153 channel.close(); 154 } 155 } 156 } 157 158 private static final class ByteBufferUploadProvider extends UploadDataProvider { 159 private final ByteBuffer mUploadBuffer; 160 161 private ByteBufferUploadProvider(ByteBuffer uploadBuffer) { 162 this.mUploadBuffer = uploadBuffer; 163 } 164 165 @Override 166 public long getLength() { 167 return mUploadBuffer.limit(); 168 } 169 170 @Override 171 public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) { 172 if (!byteBuffer.hasRemaining()) { 173 throw new IllegalStateException("Cronet passed a buffer with no bytes remaining"); 174 } 175 if (byteBuffer.remaining() >= mUploadBuffer.remaining()) { 176 byteBuffer.put(mUploadBuffer); 177 } else { 178 int oldLimit = mUploadBuffer.limit(); 179 mUploadBuffer.limit(mUploadBuffer.position() + byteBuffer.remaining()); 180 byteBuffer.put(mUploadBuffer); 181 mUploadBuffer.limit(oldLimit); 182 } 183 uploadDataSink.onReadSucceeded(false); 184 } 185 186 @Override 187 public void rewind(UploadDataSink uploadDataSink) { 188 mUploadBuffer.position(0); 189 uploadDataSink.onRewindSucceeded(); 190 } 191 } 192 193 // Prevent instantiation 194 private UploadDataProviders() {} 195 } 196