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    *   http://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.channel.ChannelHandlerContext;
20  import io.netty.channel.FileRegion;
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.nio.channels.FileChannel;
26  
27  /**
28   * A {@link ChunkedInput} that fetches data from a file chunk by chunk using
29   * NIO {@link FileChannel}.
30   * <p>
31   * If your operating system supports
32   * <a href="http://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 ChunkedNioFile implements ChunkedInput<ByteBuf> {
36  
37      private final FileChannel in;
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 ChunkedNioFile(File in) throws IOException {
47          this(new FileInputStream(in).getChannel());
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 ChunkedNioFile(File in, int chunkSize) throws IOException {
57          this(new FileInputStream(in).getChannel(), chunkSize);
58      }
59  
60      /**
61       * Creates a new instance that fetches data from the specified file.
62       */
63      public ChunkedNioFile(FileChannel in) throws IOException {
64          this(in, 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 ChunkedNioFile(FileChannel in, int chunkSize) throws IOException {
74          this(in, 0, in.size(), 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 ChunkedNioFile(FileChannel in, long offset, long length, int chunkSize)
86              throws IOException {
87          if (in == null) {
88              throw new NullPointerException("in");
89          }
90          if (offset < 0) {
91              throw new IllegalArgumentException(
92                      "offset: " + offset + " (expected: 0 or greater)");
93          }
94          if (length < 0) {
95              throw new IllegalArgumentException(
96                      "length: " + length + " (expected: 0 or greater)");
97          }
98          if (chunkSize <= 0) {
99              throw new IllegalArgumentException(
100                     "chunkSize: " + chunkSize +
101                     " (expected: a positive integer)");
102         }
103 
104         if (offset != 0) {
105             in.position(offset);
106         }
107         this.in = in;
108         this.chunkSize = chunkSize;
109         this.offset = startOffset = offset;
110         endOffset = offset + length;
111     }
112 
113     /**
114      * Returns the offset in the file where the transfer began.
115      */
116     public long startOffset() {
117         return startOffset;
118     }
119 
120     /**
121      * Returns the offset in the file where the transfer will end.
122      */
123     public long endOffset() {
124         return endOffset;
125     }
126 
127     /**
128      * Returns the offset in the file where the transfer is happening currently.
129      */
130     public long currentOffset() {
131         return offset;
132     }
133 
134     @Override
135     public boolean isEndOfInput() throws Exception {
136         return !(offset < endOffset && in.isOpen());
137     }
138 
139     @Override
140     public void close() throws Exception {
141         in.close();
142     }
143 
144     @Override
145     public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception {
146         long offset = this.offset;
147         if (offset >= endOffset) {
148             return null;
149         }
150 
151         int chunkSize = (int) Math.min(this.chunkSize, endOffset - offset);
152         ByteBuf buffer = ctx.alloc().buffer(chunkSize);
153         boolean release = true;
154         try {
155             int readBytes = 0;
156             for (;;) {
157                 int localReadBytes = buffer.writeBytes(in, chunkSize - readBytes);
158                 if (localReadBytes < 0) {
159                     break;
160                 }
161                 readBytes += localReadBytes;
162                 if (readBytes == chunkSize) {
163                     break;
164                 }
165             }
166             this.offset += readBytes;
167             release = false;
168             return buffer;
169         } finally {
170             if (release) {
171                 buffer.release();
172             }
173         }
174     }
175 
176     @Override
177     public long length() {
178         return endOffset - startOffset;
179     }
180 
181     @Override
182     public long progress() {
183         return offset - startOffset;
184     }
185 }