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 read = inputStream.read(bytes);
243 }
244 localfileChannel.force(false);
245 } finally {
246 accessFile.close();
247 }
248 size = written;
249 if (definedSize > 0 && definedSize < size) {
250 if (!file.delete()) {
251 logger.warn("Failed to delete: {}", file);
252 }
253 file = null;
254 throw new IOException("Out of size: " + size + " > " + definedSize);
255 }
256 isRenamed = true;
257 setCompleted();
258 }
259
260 @Override
261 public void delete() {
262 if (fileChannel != null) {
263 try {
264 fileChannel.force(false);
265 } catch (IOException e) {
266 logger.warn("Failed to force.", e);
267 } finally {
268 try {
269 fileChannel.close();
270 } catch (IOException e) {
271 logger.warn("Failed to close a file.", e);
272 }
273 }
274 fileChannel = null;
275 }
276 if (!isRenamed) {
277 String filePath = null;
278
279 if (file != null && file.exists()) {
280 filePath = file.getPath();
281 if (!file.delete()) {
282 filePath = null;
283 logger.warn("Failed to delete: {}", file);
284 }
285 }
286
287
288 if (deleteOnExit() && filePath != null) {
289 DeleteFileOnExitHook.remove(filePath);
290 }
291 file = null;
292 }
293 }
294
295 @Override
296 public byte[] get() throws IOException {
297 if (file == null) {
298 return EmptyArrays.EMPTY_BYTES;
299 }
300 return readFrom(file);
301 }
302
303 @Override
304 public ByteBuf getByteBuf() throws IOException {
305 if (file == null) {
306 return EMPTY_BUFFER;
307 }
308 byte[] array = readFrom(file);
309 return wrappedBuffer(array);
310 }
311
312 @Override
313 public ByteBuf getChunk(int length) throws IOException {
314 if (file == null || length == 0) {
315 return EMPTY_BUFFER;
316 }
317 if (fileChannel == null) {
318 RandomAccessFile accessFile = new RandomAccessFile(file, "r");
319 fileChannel = accessFile.getChannel();
320 }
321 int read = 0;
322 ByteBuffer byteBuffer = ByteBuffer.allocate(length);
323 try {
324 while (read < length) {
325 int readnow = fileChannel.read(byteBuffer);
326 if (readnow == -1) {
327 fileChannel.close();
328 fileChannel = null;
329 break;
330 }
331 read += readnow;
332 }
333 } catch (IOException e) {
334 fileChannel.close();
335 fileChannel = null;
336 throw e;
337 }
338 if (read == 0) {
339 return EMPTY_BUFFER;
340 }
341 byteBuffer.flip();
342 ByteBuf buffer = wrappedBuffer(byteBuffer);
343 buffer.readerIndex(0);
344 buffer.writerIndex(read);
345 return buffer;
346 }
347
348 @Override
349 public String getString() throws IOException {
350 return getString(HttpConstants.DEFAULT_CHARSET);
351 }
352
353 @Override
354 public String getString(Charset encoding) throws IOException {
355 if (file == null) {
356 return "";
357 }
358 if (encoding == null) {
359 byte[] array = readFrom(file);
360 return new String(array, HttpConstants.DEFAULT_CHARSET.name());
361 }
362 byte[] array = readFrom(file);
363 return new String(array, encoding.name());
364 }
365
366 @Override
367 public boolean isInMemory() {
368 return false;
369 }
370
371 @Override
372 public boolean renameTo(File dest) throws IOException {
373 ObjectUtil.checkNotNull(dest, "dest");
374 if (file == null) {
375 throw new IOException("No file defined so cannot be renamed");
376 }
377 if (!file.renameTo(dest)) {
378
379 IOException exception = null;
380 RandomAccessFile inputAccessFile = null;
381 RandomAccessFile outputAccessFile = null;
382 long chunkSize = 8196;
383 long position = 0;
384 try {
385 inputAccessFile = new RandomAccessFile(file, "r");
386 outputAccessFile = new RandomAccessFile(dest, "rw");
387 FileChannel in = inputAccessFile.getChannel();
388 FileChannel out = outputAccessFile.getChannel();
389 while (position < size) {
390 if (chunkSize < size - position) {
391 chunkSize = size - position;
392 }
393 position += in.transferTo(position, chunkSize, out);
394 }
395 } catch (IOException e) {
396 exception = e;
397 } finally {
398 if (inputAccessFile != null) {
399 try {
400 inputAccessFile.close();
401 } catch (IOException e) {
402 if (exception == null) {
403 exception = e;
404 } else {
405 logger.warn("Multiple exceptions detected, the following will be suppressed {}", e);
406 }
407 }
408 }
409 if (outputAccessFile != null) {
410 try {
411 outputAccessFile.close();
412 } catch (IOException e) {
413 if (exception == null) {
414 exception = e;
415 } else {
416 logger.warn("Multiple exceptions detected, the following will be suppressed {}", e);
417 }
418 }
419 }
420 }
421 if (exception != null) {
422 throw exception;
423 }
424 if (position == size) {
425 if (!file.delete()) {
426 logger.warn("Failed to delete: {}", file);
427 }
428 file = dest;
429 isRenamed = true;
430 return true;
431 } else {
432 if (!dest.delete()) {
433 logger.warn("Failed to delete: {}", dest);
434 }
435 return false;
436 }
437 }
438 file = dest;
439 isRenamed = true;
440 return true;
441 }
442
443
444
445
446
447
448 private static byte[] readFrom(File src) throws IOException {
449 long srcsize = src.length();
450 if (srcsize > Integer.MAX_VALUE) {
451 throw new IllegalArgumentException(
452 "File too big to be loaded in memory");
453 }
454 RandomAccessFile accessFile = new RandomAccessFile(src, "r");
455 byte[] array = new byte[(int) srcsize];
456 try {
457 FileChannel fileChannel = accessFile.getChannel();
458 ByteBuffer byteBuffer = ByteBuffer.wrap(array);
459 int read = 0;
460 while (read < srcsize) {
461 read += fileChannel.read(byteBuffer);
462 }
463 } finally {
464 accessFile.close();
465 }
466 return array;
467 }
468
469 @Override
470 public File getFile() throws IOException {
471 return file;
472 }
473
474 @Override
475 public HttpData touch() {
476 return this;
477 }
478
479 @Override
480 public HttpData touch(Object hint) {
481 return this;
482 }
483 }