• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.net.http.apihelpers;
6 
7 import android.net.http.UploadDataProvider;
8 import android.net.http.UploadDataSink;
9 import android.os.ParcelFileDescriptor;
10 
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.IOException;
14 import java.nio.ByteBuffer;
15 import java.nio.channels.FileChannel;
16 
17 /**
18  * Provides implementations of {@link UploadDataProvider} for common use cases.
19  */
20 public final class UploadDataProviders {
21     /**
22      * Uploads an entire file.
23      *
24      * @param file The file to upload
25      * @return A new UploadDataProvider for the given file
26      */
create(final File file)27     public static UploadDataProvider create(final File file) {
28         return new FileUploadProvider(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(new FileChannelProvider() {
45             @Override
46             public FileChannel getChannel() throws IOException {
47                 if (fd.getStatSize() != -1) {
48                     return new ParcelFileDescriptor.AutoCloseInputStream(fd).getChannel();
49                 } else {
50                     fd.close();
51                     throw new IllegalArgumentException("Not a file: " + fd);
52                 }
53             }
54         });
55     }
56 
57     /**
58      * Uploads a ByteBuffer, from the current {@code buffer.position()} to {@code buffer.limit()}
59      *
60      * @param buffer The data to upload
61      * @return A new UploadDataProvider for the given buffer
62      */
63     public static UploadDataProvider create(ByteBuffer buffer) {
64         return new ByteBufferUploadProvider(buffer.slice());
65     }
66 
67     /**
68      * Uploads {@code length} bytes from {@code data}, starting from {@code offset}
69      *
70      * @param data Array containing data to upload
71      * @param offset Offset within data to start with
72      * @param length Number of bytes to upload
73      * @return A new UploadDataProvider for the given data
74      */
75     public static UploadDataProvider create(byte[] data, int offset, int length) {
76         return new ByteBufferUploadProvider(ByteBuffer.wrap(data, offset, length).slice());
77     }
78 
79     /**
80      * Uploads the contents of {@code data}
81      *
82      * @param data Array containing data to upload
83      * @return A new UploadDataProvider for the given data
84      */
85     public static UploadDataProvider create(byte[] data) {
86         return create(data, 0, data.length);
87     }
88 
89     private interface FileChannelProvider {
90         FileChannel getChannel() throws IOException;
91     }
92 
93     private static final class FileUploadProvider extends UploadDataProvider {
94         private volatile FileChannel mChannel;
95         private final FileChannelProvider mProvider;
96         /**
97          * Guards initialization of {@code mChannel}
98          */
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             checkHasBytesRemaining(byteBuffer);
113             FileChannel channel = getChannel();
114             int bytesRead = 0;
115             while (bytesRead == 0) {
116                 int read = channel.read(byteBuffer);
117                 if (read == -1) {
118                     break;
119                 } else {
120                     bytesRead += read;
121                 }
122             }
123             uploadDataSink.onReadSucceeded(false);
124         }
125 
126         @Override
127         public void rewind(UploadDataSink uploadDataSink) throws IOException {
128             getChannel().position(0);
129             uploadDataSink.onRewindSucceeded();
130         }
131 
132         /**
133          * Lazily initializes the channel so that a blocking operation isn't performed on a
134          * non-executor thread.
135          */
136         private FileChannel getChannel() throws IOException {
137             if (mChannel == null) {
138                 synchronized (mLock) {
139                     if (mChannel == null) {
140                         mChannel = mProvider.getChannel();
141                     }
142                 }
143             }
144             return mChannel;
145         }
146 
147         @Override
148         public void close() throws IOException {
149             FileChannel channel = mChannel;
150             if (channel != null) {
151                 channel.close();
152             }
153         }
154     }
155 
156     private static final class ByteBufferUploadProvider extends UploadDataProvider {
157         private final ByteBuffer mUploadBuffer;
158 
159         private ByteBufferUploadProvider(ByteBuffer uploadBuffer) {
160             this.mUploadBuffer = uploadBuffer;
161         }
162 
163         @Override
164         public long getLength() {
165             return mUploadBuffer.limit();
166         }
167 
168         @Override
169         public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) {
170             checkHasBytesRemaining(byteBuffer);
171             if (byteBuffer.remaining() >= mUploadBuffer.remaining()) {
172                 byteBuffer.put(mUploadBuffer);
173             } else {
174                 int oldLimit = mUploadBuffer.limit();
175                 mUploadBuffer.limit(mUploadBuffer.position() + byteBuffer.remaining());
176                 byteBuffer.put(mUploadBuffer);
177                 mUploadBuffer.limit(oldLimit);
178             }
179             uploadDataSink.onReadSucceeded(false);
180         }
181 
182         @Override
183         public void rewind(UploadDataSink uploadDataSink) {
184             mUploadBuffer.position(0);
185             uploadDataSink.onRewindSucceeded();
186         }
187     }
188 
189     private static ByteBuffer checkHasBytesRemaining(ByteBuffer buffer) {
190         if (!buffer.hasRemaining()) {
191             throw new IllegalStateException("buffer with no bytes remaining");
192         }
193 
194         return buffer;
195     }
196 
197     // Prevent instantiation
198     private UploadDataProviders() {}
199 }
200