• 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 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