/
IOUtil.java
256 lines (231 loc) · 8.99 KB
/
IOUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/*
* Copyright 2015-2020 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
*
* 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 net.dv8tion.jda.internal.utils;
import com.neovisionaries.ws.client.WebSocketFactory;
import okhttp3.*;
import okio.Okio;
import org.slf4j.Logger;
import java.io.*;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipException;
public class IOUtil
{
private static final Logger log = JDALogger.getLog(IOUtil.class);
public static void silentClose(AutoCloseable closeable)
{
try
{
closeable.close();
}
catch (Exception ignored) {}
}
public static void silentClose(Closeable closeable)
{
try
{
closeable.close();
}
catch (IOException ignored) {}
}
public static String getHost(String uri)
{
return URI.create(uri).getHost();
}
public static void setServerName(WebSocketFactory factory, String url)
{
String host = getHost(url);
// null if the host is undefined, unlikely but we should handle it
if (host != null)
factory.setServerName(host);
}
public static OkHttpClient.Builder newHttpClientBuilder()
{
Dispatcher dispatcher = new Dispatcher();
// Allow 25 parallel requests to the same host (usually discord.com)
dispatcher.setMaxRequestsPerHost(25);
// Allow 5 idle threads with 10 seconds timeout for each
ConnectionPool connectionPool = new ConnectionPool(5, 10, TimeUnit.SECONDS);
return new OkHttpClient.Builder()
.connectionPool(connectionPool)
.dispatcher(dispatcher);
}
/**
* Used as an alternate to Java's nio Files.readAllBytes.
*
* <p>This customized version for File is provide (instead of just using {@link #readFully(java.io.InputStream)} with a FileInputStream)
* because with a File we can determine the total size of the array and do not need to have a buffer.
* This results in a memory footprint that is half the size of {@link #readFully(java.io.InputStream)}
*
* <p>Code provided from <a href="http://stackoverflow.com/a/6276139">Stackoverflow</a>
*
* @param file
* The file from which we should retrieve the bytes from
*
* @throws java.io.IOException
* Thrown if there is a problem while reading the file.
*
* @return A byte[] containing all of the file's data
*/
public static byte[] readFully(File file) throws IOException
{
Checks.notNull(file, "File");
Checks.check(file.exists(), "Provided file does not exist!");
try (InputStream is = new FileInputStream(file))
{
// Get the size of the file
long length = file.length();
// You cannot create an array using a long type.
// It needs to be an int type.
// Before converting to an int type, check
// to ensure that file is not larger than Integer.MAX_VALUE.
if (length > Integer.MAX_VALUE)
{
throw new IOException("Cannot read the file into memory completely due to it being too large!");
// File is too large
}
// Create the byte array to hold the data
byte[] bytes = new byte[(int) length];
// Read in the bytes
int offset = 0;
int numRead = 0;
while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)
{
offset += numRead;
}
// Ensure all the bytes have been read in
if (offset < bytes.length)
{
throw new IOException("Could not completely read file " + file.getName());
}
// Close the input stream and return bytes
is.close();
return bytes;
}
}
/**
* Provided as a simple way to fully read an InputStream into a byte[].
*
* <p>This method will block until the InputStream has been fully read, so if you provide an InputStream that is
* non-finite, you're gonna have a bad time.
*
* @param stream
* The Stream to be read.
*
* @throws IOException
* If the first byte cannot be read for any reason other than the end of the file,
* if the input stream has been closed, or if some other I/O error occurs.
*
* @return A byte[] containing all of the data provided by the InputStream
*/
public static byte[] readFully(InputStream stream) throws IOException
{
Checks.notNull(stream, "InputStream");
byte[] buffer = new byte[1024];
try (ByteArrayOutputStream bos = new ByteArrayOutputStream())
{
int readAmount = 0;
while ((readAmount = stream.read(buffer)) != -1)
{
bos.write(buffer, 0, readAmount);
}
return bos.toByteArray();
}
}
/**
* Creates a new request body that transmits the provided {@link java.io.InputStream InputStream}.
*
* @param contentType
* The {@link okhttp3.MediaType MediaType} of the data
* @param stream
* The {@link java.io.InputStream InputStream} to be transmitted
*
* @return RequestBody capable of transmitting the provided InputStream of data
*/
public static RequestBody createRequestBody(final MediaType contentType, final InputStream stream)
{
return new BufferedRequestBody(Okio.source(stream), contentType);
}
public static short getShortBigEndian(byte[] arr, int offset)
{
return (short) ((arr[offset ] & 0xff) << 8
| arr[offset + 1] & 0xff);
}
public static short getShortLittleEndian(byte[] arr, int offset)
{
// Same as big endian but reversed order of bytes (java uses big endian)
return (short) ((arr[offset ] & 0xff)
| (arr[offset + 1] & 0xff) << 8);
}
public static int getIntBigEndian(byte[] arr, int offset)
{
return arr[offset + 3] & 0xFF
| (arr[offset + 2] & 0xFF) << 8
| (arr[offset + 1] & 0xFF) << 16
| (arr[offset ] & 0xFF) << 24;
}
public static void setIntBigEndian(byte[] arr, int offset, int it)
{
arr[offset ] = (byte) ((it >>> 24) & 0xFF);
arr[offset + 1] = (byte) ((it >>> 16) & 0xFF);
arr[offset + 2] = (byte) ((it >>> 8) & 0xFF);
arr[offset + 3] = (byte) ( it & 0xFF);
}
public static ByteBuffer reallocate(ByteBuffer original, int length)
{
ByteBuffer buffer = ByteBuffer.allocate(length);
buffer.put(original);
return buffer;
}
/**
* Retrieves an {@link InputStream InputStream} for the provided {@link okhttp3.Response Response}.
* <br>When the header for {@code content-encoding} is set with {@code gzip} this will wrap the body
* in a {@link java.util.zip.GZIPInputStream GZIPInputStream} which decodes the data.
*
* <p>This is used to make usage of encoded responses more user-friendly in various parts of JDA.
*
* @param response
* The not-null Response object
*
* @return InputStream representing the body of this response
*/
@SuppressWarnings("ConstantConditions") // methods here don't return null despite the annotations on them, read the docs
public static InputStream getBody(okhttp3.Response response) throws IOException
{
String encoding = response.header("content-encoding", "");
InputStream data = new BufferedInputStream(response.body().byteStream());
data.mark(256);
try
{
if (encoding.equalsIgnoreCase("gzip"))
return new GZIPInputStream(data);
else if (encoding.equalsIgnoreCase("deflate"))
return new InflaterInputStream(data, new Inflater(true));
}
catch (ZipException | EOFException ex)
{
data.reset(); // reset to get full content
log.error("Failed to read gzip content for response. Headers: {}\nContent: '{}'",
response.headers(), JDALogger.getLazyString(() -> new String(readFully(data))), ex);
return null;
}
return data;
}
}