1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http.multipart;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.handler.codec.http.HttpConstants;
20 import io.netty.util.internal.EmptyArrays;
21 import io.netty.util.internal.ObjectUtil;
22 import io.netty.util.internal.PlatformDependent;
23 import io.netty.util.internal.logging.InternalLogger;
24 import io.netty.util.internal.logging.InternalLoggerFactory;
25
26 import java.io.File;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.RandomAccessFile;
30 import java.nio.ByteBuffer;
31 import java.nio.channels.FileChannel;
32 import java.nio.charset.Charset;
33
34 import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
35 import static io.netty.buffer.Unpooled.wrappedBuffer;
36
37
38
39
40 public abstract class AbstractDiskHttpData extends AbstractHttpData {
41
42 private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractDiskHttpData.class);
43
44 private File file;
45 private boolean isRenamed;
46 private FileChannel fileChannel;
47
48 protected AbstractDiskHttpData(String name, Charset charset, long size) {
49 super(name, charset, size);
50 }
51
52
53
54
55
56 protected abstract String getDiskFilename();
57
58
59
60
61 protected abstract String getPrefix();
62
63
64
65
66 protected abstract String getBaseDirectory();
67
68
69
70
71 protected abstract String getPostfix();
72
73
74
75
76 protected abstract boolean deleteOnExit();
77
78
79
80
81 private File tempFile() throws IOException {
82 String newpostfix;
83 String diskFilename = getDiskFilename();
84 if (diskFilename != null) {
85 newpostfix = '_' + Integer.toString(diskFilename.hashCode());
86 } else {
87 newpostfix = getPostfix();
88 }
89 File tmpFile;
90 if (getBaseDirectory() == null) {
91
92 tmpFile = PlatformDependent.createTempFile(getPrefix(), newpostfix, null);
93 } else {
94 tmpFile = PlatformDependent.createTempFile(getPrefix(), newpostfix, new File(
95 getBaseDirectory()));
96 }
97 if (deleteOnExit()) {
98
99 DeleteFileOnExitHook.add(tmpFile.getPath());
100 }
101 return tmpFile;
102 }
103
104 @Override
105 public void setContent(ByteBuf buffer) throws IOException {
106 ObjectUtil.checkNotNull(buffer, "buffer");
107 try {
108 size = buffer.readableBytes();
109 checkSize(size);
110 if (definedSize > 0 && definedSize < size) {
111 throw new IOException("Out of size: " + size + " > " + definedSize);
112 }
113 if (file == null) {
114 file = tempFile();
115 }
116 if (buffer.readableBytes() == 0) {
117
118 if (!file.createNewFile()) {
119 if (file.length() == 0) {
120 return;
121 } else {
122 if (!file.delete() || !file.createNewFile()) {
123 throw new IOException("file exists already: " + file);
124 }
125 }
126 }
127 return;
128 }
129 RandomAccessFile accessFile = new RandomAccessFile(file, "rw");
130 try {
131 accessFile.setLength(0);
132 FileChannel localfileChannel = accessFile.getChannel();
133 ByteBuffer byteBuffer = buffer.nioBuffer();
134 int written = 0;
135 while (written < size) {
136 written += localfileChannel.write(byteBuffer);
137 }
138 buffer.readerIndex(buffer.readerIndex() + written);
139 localfileChannel.force(false);
140 } finally {
141 accessFile.close();
142 }
143 setCompleted();
144 } finally {
145
146
147 buffer.release();
148 }
149 }
150
151 @Override
152 public void addContent(ByteBuf buffer, boolean last)
153 throws IOException {
154 if (buffer != null) {
155 try {
156 int localsize = buffer.readableBytes();
157 checkSize(size + localsize);
158 if (definedSize > 0 && definedSize < size + localsize) {
159 throw new IOException("Out of size: " + (size + localsize) +
160 " > " + definedSize);
161 }
162 if (file == null) {
163 file = tempFile();
164 }
165 if (fileChannel == null) {
166 RandomAccessFile accessFile = new RandomAccessFile(file, "rw");
167 fileChannel = accessFile.getChannel();
168 }
169 int remaining = localsize;
170 long position = fileChannel.position();
171 int index = buffer.readerIndex();
172 while (remaining > 0) {
173 int written = buffer.getBytes(index, fileChannel, position, remaining);
174 if (written < 0) {
175 break;
176 }
177 remaining -= written;
178 position += written;
179 index += written;
180 }
181 fileChannel.position(position);
182 buffer.readerIndex(index);
183 size += localsize - remaining;
184 } finally {
185
186
187 buffer.release();
188 }
189 }
190 if (last) {
191 if (file == null) {
192 file = tempFile();
193 }
194 if (fileChannel == null) {
195 RandomAccessFile accessFile = new RandomAccessFile(file, "rw");
196 fileChannel = accessFile.getChannel();
197 }
198 try {
199 fileChannel.force(false);
200 } finally {
201 fileChannel.close();
202 }
203 fileChannel = null;
204 setCompleted();
205 } else {
206 ObjectUtil.checkNotNull(buffer, "buffer");
207 }
208 }
209
210 @Override
211 public void setContent(File file) throws IOException {
212 long size = file.length();
213 checkSize(size);
214 this.size = size;
215 if (this.file != null) {
216 delete();
217 }
218 this.file = file;
219 isRenamed = true;
220 setCompleted();
221 }
222
223 @Override
224 public void setContent(InputStream inputStream) throws IOException {
225 ObjectUtil.checkNotNull(inputStream, "inputStream");
226 if (file != null) {
227 delete();
228 }
229 file = tempFile();
230 RandomAccessFile accessFile = new RandomAccessFile(file, "rw");
231 int written = 0;
232 try {
233 accessFile.setLength(0);
234 FileChannel localfileChannel = accessFile.getChannel();
235 byte[] bytes = new byte[4096 * 4];
236 ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
237 int read = inputStream.read(bytes);
238 while (read > 0) {
239 byteBuffer.position(read).flip();
240 written += localfileChannel.write(byteBuffer);
241 checkSize(written);
242 byteBuffer.clear();
243 read = inputStream.read(bytes);
244 }
245 localfileChannel.force(false);
246 } finally {
247 accessFile.close();
248 }
249 size = written;
250 if (definedSize > 0 && definedSize < size) {
251 if (!file.delete()) {
252 logger.warn("Failed to delete: {}", file);
253 }
254 file = null;
255 throw new IOException("Out of size: " + size + " > " + definedSize);
256 }
257 isRenamed = true;
258 setCompleted();
259 }
260
261 @Override
262 public void delete() {
263 if (fileChannel != null) {
264 try {
265 fileChannel.force(false);
266 } catch (IOException e) {
267 logger.warn("Failed to force.", e);
268 } finally {
269 try {
270 fileChannel.close();
271 } catch (IOException e) {
272 logger.warn("Failed to close a file.", e);
273 }
274 }
275 fileChannel = null;
276 }
277 if (!isRenamed) {
278 String filePath = null;
279
280 if (file != null && file.exists()) {
281 filePath = file.getPath();
282 if (!file.delete()) {
283 filePath = null;
284 logger.warn("Failed to delete: {}", file);
285 }
286 }
287
288
289 if (deleteOnExit() && filePath != null) {
290 DeleteFileOnExitHook.remove(filePath);
291 }
292 file = null;
293 }
294 }
295
296 @Override
297 public byte[] get() throws IOException {
298 if (file == null) {
299 return EmptyArrays.EMPTY_BYTES;
300 }
301 return readFrom(file);
302 }
303
304 @Override
305 public ByteBuf getByteBuf() throws IOException {
306 if (file == null) {
307 return EMPTY_BUFFER;
308 }
309 byte[] array = readFrom(file);
310 return wrappedBuffer(array);
311 }
312
313 @Override
314 public ByteBuf getChunk(int length) throws IOException {
315 if (file == null || length == 0) {
316 return EMPTY_BUFFER;
317 }
318 if (fileChannel == null) {
319 RandomAccessFile accessFile = new RandomAccessFile(file, "r");
320 fileChannel = accessFile.getChannel();
321 }
322 int read = 0;
323 ByteBuffer byteBuffer = ByteBuffer.allocate(length);
324 try {
325 while (read < length) {
326 int readnow = fileChannel.read(byteBuffer);
327 if (readnow == -1) {
328 fileChannel.close();
329 fileChannel = null;
330 break;
331 }
332 read += readnow;
333 }
334 } catch (IOException e) {
335 fileChannel.close();
336 fileChannel = null;
337 throw e;
338 }
339 if (read == 0) {
340 return EMPTY_BUFFER;
341 }
342 byteBuffer.flip();
343 ByteBuf buffer = wrappedBuffer(byteBuffer);
344 buffer.readerIndex(0);
345 buffer.writerIndex(read);
346 return buffer;
347 }
348
349 @Override
350 public String getString() throws IOException {
351 return getString(HttpConstants.DEFAULT_CHARSET);
352 }
353
354 @Override
355 public String getString(Charset encoding) throws IOException {
356 if (file == null) {
357 return "";
358 }
359 if (encoding == null) {
360 byte[] array = readFrom(file);
361 return new String(array, HttpConstants.DEFAULT_CHARSET.name());
362 }
363 byte[] array = readFrom(file);
364 return new String(array, encoding.name());
365 }
366
367 @Override
368 public boolean isInMemory() {
369 return false;
370 }
371
372 @Override
373 public boolean renameTo(File dest) throws IOException {
374 ObjectUtil.checkNotNull(dest, "dest");
375 if (file == null) {
376 throw new IOException("No file defined so cannot be renamed");
377 }
378 if (!file.renameTo(dest)) {
379
380 IOException exception = null;
381 RandomAccessFile inputAccessFile = null;
382 RandomAccessFile outputAccessFile = null;
383 long chunkSize = 8196;
384 long position = 0;
385 try {
386 inputAccessFile = new RandomAccessFile(file, "r");
387 outputAccessFile = new RandomAccessFile(dest, "rw");
388 FileChannel in = inputAccessFile.getChannel();
389 FileChannel out = outputAccessFile.getChannel();
390 while (position < size) {
391 if (chunkSize < size - position) {
392 chunkSize = size - position;
393 }
394 position += in.transferTo(position, chunkSize, out);
395 }
396 } catch (IOException e) {
397 exception = e;
398 } finally {
399 if (inputAccessFile != null) {
400 try {
401 inputAccessFile.close();
402 } catch (IOException e) {
403 if (exception == null) {
404 exception = e;
405 } else {
406 logger.warn("Multiple exceptions detected, the following will be suppressed {}", e);
407 }
408 }
409 }
410 if (outputAccessFile != null) {
411 try {
412 outputAccessFile.close();
413 } catch (IOException e) {
414 if (exception == null) {
415 exception = e;
416 } else {
417 logger.warn("Multiple exceptions detected, the following will be suppressed {}", e);
418 }
419 }
420 }
421 }
422 if (exception != null) {
423 throw exception;
424 }
425 if (position == size) {
426 if (!file.delete()) {
427 logger.warn("Failed to delete: {}", file);
428 }
429 file = dest;
430 isRenamed = true;
431 return true;
432 } else {
433 if (!dest.delete()) {
434 logger.warn("Failed to delete: {}", dest);
435 }
436 return false;
437 }
438 }
439 file = dest;
440 isRenamed = true;
441 return true;
442 }
443
444
445
446
447
448
449 private static byte[] readFrom(File src) throws IOException {
450 long srcsize = src.length();
451 if (srcsize > Integer.MAX_VALUE) {
452 throw new IllegalArgumentException(
453 "File too big to be loaded in memory");
454 }
455 RandomAccessFile accessFile = new RandomAccessFile(src, "r");
456 byte[] array = new byte[(int) srcsize];
457 try {
458 FileChannel fileChannel = accessFile.getChannel();
459 ByteBuffer byteBuffer = ByteBuffer.wrap(array);
460 int read = 0;
461 while (read < srcsize) {
462 read += fileChannel.read(byteBuffer);
463 }
464 } finally {
465 accessFile.close();
466 }
467 return array;
468 }
469
470 @Override
471 public File getFile() throws IOException {
472 return file;
473 }
474
475 @Override
476 public HttpData touch() {
477 return this;
478 }
479
480 @Override
481 public HttpData touch(Object hint) {
482 return this;
483 }
484 }