View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.stream;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufAllocator;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.FileRegion;
22  import io.netty.util.internal.ObjectUtil;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.RandomAccessFile;
27  
28  /**
29   * A {@link ChunkedInput} that fetches data from a file chunk by chunk.
30   * <p>
31   * If your operating system supports
32   * <a href="https://en.wikipedia.org/wiki/Zero-copy">zero-copy file transfer</a>
33   * such as {@code sendfile()}, you might want to use {@link FileRegion} instead.
34   */
35  public class ChunkedFile implements ChunkedInput<ByteBuf> {
36  
37      private final RandomAccessFile file;
38      private final long startOffset;
39      private final long endOffset;
40      private final int chunkSize;
41      private long offset;
42  
43      /**
44       * Creates a new instance that fetches data from the specified file.
45       */
46      public ChunkedFile(File file) throws IOException {
47          this(file, ChunkedStream.DEFAULT_CHUNK_SIZE);
48      }
49  
50      /**
51       * Creates a new instance that fetches data from the specified file.
52       *
53       * @param chunkSize the number of bytes to fetch on each
54       *                  {@link #readChunk(ChannelHandlerContext)} call
55       */
56      public ChunkedFile(File file, int chunkSize) throws IOException {
57          this(new RandomAccessFile(file, "r"), chunkSize);
58      }
59  
60      /**
61       * Creates a new instance that fetches data from the specified file.
62       */
63      public ChunkedFile(RandomAccessFile file) throws IOException {
64          this(file, ChunkedStream.DEFAULT_CHUNK_SIZE);
65      }
66  
67      /**
68       * Creates a new instance that fetches data from the specified file.
69       *
70       * @param chunkSize the number of bytes to fetch on each
71       *                  {@link #readChunk(ChannelHandlerContext)} call
72       */
73      public ChunkedFile(RandomAccessFile file, int chunkSize) throws IOException {
74          this(file, 0, file.length(), chunkSize);
75      }
76  
77      /**
78       * Creates a new instance that fetches data from the specified file.
79       *
80       * @param offset the offset of the file where the transfer begins
81       * @param length the number of bytes to transfer
82       * @param chunkSize the number of bytes to fetch on each
83       *                  {@link #readChunk(ChannelHandlerContext)} call
84       */
85      public ChunkedFile(RandomAccessFile file, long offset, long length, int chunkSize) throws IOException {
86          ObjectUtil.checkNotNull(file, "file");
87          ObjectUtil.checkPositiveOrZero(offset, "offset");
88          ObjectUtil.checkPositiveOrZero(length, "length");
89          ObjectUtil.checkPositive(chunkSize, "chunkSize");
90  
91          this.file = file;
92          this.offset = startOffset = offset;
93          this.endOffset = offset + length;
94          this.chunkSize = chunkSize;
95  
96          file.seek(offset);
97      }
98  
99      /**
100      * Returns the offset in the file where the transfer began.
101      */
102     public long startOffset() {
103         return startOffset;
104     }
105 
106     /**
107      * Returns the offset in the file where the transfer will end.
108      */
109     public long endOffset() {
110         return endOffset;
111     }
112 
113     /**
114      * Returns the offset in the file where the transfer is happening currently.
115      */
116     public long currentOffset() {
117         return offset;
118     }
119 
120     @Override
121     public boolean isEndOfInput() throws Exception {
122         return !(offset < endOffset && file.getChannel().isOpen());
123     }
124 
125     @Override
126     public void close() throws Exception {
127         file.close();
128     }
129 
130     @Deprecated
131     @Override
132     public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception {
133         return readChunk(ctx.alloc());
134     }
135 
136     @Override
137     public ByteBuf readChunk(ByteBufAllocator allocator) throws Exception {
138         long offset = this.offset;
139         if (offset >= endOffset) {
140             return null;
141         }
142 
143         int chunkSize = (int) Math.min(this.chunkSize, endOffset - offset);
144         // Check if the buffer is backed by an byte array. If so we can optimize it a bit an safe a copy
145 
146         ByteBuf buf = allocator.heapBuffer(chunkSize);
147         boolean release = true;
148         try {
149             file.readFully(buf.array(), buf.arrayOffset(), chunkSize);
150             buf.writerIndex(chunkSize);
151             this.offset = offset + chunkSize;
152             release = false;
153             return buf;
154         } finally {
155             if (release) {
156                 buf.release();
157             }
158         }
159     }
160 
161     @Override
162     public long length() {
163         return endOffset - startOffset;
164     }
165 
166     @Override
167     public long progress() {
168         return offset - startOffset;
169     }
170 }