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.buffer.ByteBufAllocator;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.FileRegion;
22  
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.nio.channels.FileChannel;
27  
28  /**
29   * A {@link ChunkedInput} that fetches data from a file chunk by chunk using
30   * NIO {@link FileChannel}.
31   * <p>
32   * If your operating system supports
33   * <a href="http://en.wikipedia.org/wiki/Zero-copy">zero-copy file transfer</a>
34   * such as {@code sendfile()}, you might want to use {@link FileRegion} instead.
35   */
36  public class ChunkedNioFile implements ChunkedInput<ByteBuf> {
37  
38      private final FileChannel in;
39      private final long startOffset;
40      private final long endOffset;
41      private final int chunkSize;
42      private long offset;
43  
44      /**
45       * Creates a new instance that fetches data from the specified file.
46       */
47      public ChunkedNioFile(File in) throws IOException {
48          this(new FileInputStream(in).getChannel());
49      }
50  
51      /**
52       * Creates a new instance that fetches data from the specified file.
53       *
54       * @param chunkSize the number of bytes to fetch on each
55       *                  {@link #readChunk(ChannelHandlerContext)} call
56       */
57      public ChunkedNioFile(File in, int chunkSize) throws IOException {
58          this(new FileInputStream(in).getChannel(), chunkSize);
59      }
60  
61      /**
62       * Creates a new instance that fetches data from the specified file.
63       */
64      public ChunkedNioFile(FileChannel in) throws IOException {
65          this(in, ChunkedStream.DEFAULT_CHUNK_SIZE);
66      }
67  
68      /**
69       * Creates a new instance that fetches data from the specified file.
70       *
71       * @param chunkSize the number of bytes to fetch on each
72       *                  {@link #readChunk(ChannelHandlerContext)} call
73       */
74      public ChunkedNioFile(FileChannel in, int chunkSize) throws IOException {
75          this(in, 0, in.size(), chunkSize);
76      }
77  
78      /**
79       * Creates a new instance that fetches data from the specified file.
80       *
81       * @param offset the offset of the file where the transfer begins
82       * @param length the number of bytes to transfer
83       * @param chunkSize the number of bytes to fetch on each
84       *                  {@link #readChunk(ChannelHandlerContext)} call
85       */
86      public ChunkedNioFile(FileChannel in, long offset, long length, int chunkSize)
87              throws IOException {
88          if (in == null) {
89              throw new NullPointerException("in");
90          }
91          if (offset < 0) {
92              throw new IllegalArgumentException(
93                      "offset: " + offset + " (expected: 0 or greater)");
94          }
95          if (length < 0) {
96              throw new IllegalArgumentException(
97                      "length: " + length + " (expected: 0 or greater)");
98          }
99          if (chunkSize <= 0) {
100             throw new IllegalArgumentException(
101                     "chunkSize: " + chunkSize +
102                     " (expected: a positive integer)");
103         }
104 
105         if (offset != 0) {
106             in.position(offset);
107         }
108         this.in = in;
109         this.chunkSize = chunkSize;
110         this.offset = startOffset = offset;
111         endOffset = offset + length;
112     }
113 
114     /**
115      * Returns the offset in the file where the transfer began.
116      */
117     public long startOffset() {
118         return startOffset;
119     }
120 
121     /**
122      * Returns the offset in the file where the transfer will end.
123      */
124     public long endOffset() {
125         return endOffset;
126     }
127 
128     /**
129      * Returns the offset in the file where the transfer is happening currently.
130      */
131     public long currentOffset() {
132         return offset;
133     }
134 
135     @Override
136     public boolean isEndOfInput() throws Exception {
137         return !(offset < endOffset && in.isOpen());
138     }
139 
140     @Override
141     public void close() throws Exception {
142         in.close();
143     }
144 
145     @Deprecated
146     @Override
147     public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception {
148         return readChunk(ctx.alloc());
149     }
150 
151     @Override
152     public ByteBuf readChunk(ByteBufAllocator allocator) throws Exception {
153         long offset = this.offset;
154         if (offset >= endOffset) {
155             return null;
156         }
157 
158         int chunkSize = (int) Math.min(this.chunkSize, endOffset - offset);
159         ByteBuf buffer = allocator.buffer(chunkSize);
160         boolean release = true;
161         try {
162             int readBytes = 0;
163             for (;;) {
164                 int localReadBytes = buffer.writeBytes(in, chunkSize - readBytes);
165                 if (localReadBytes < 0) {
166                     break;
167                 }
168                 readBytes += localReadBytes;
169                 if (readBytes == chunkSize) {
170                     break;
171                 }
172             }
173             this.offset += readBytes;
174             release = false;
175             return buffer;
176         } finally {
177             if (release) {
178                 buffer.release();
179             }
180         }
181     }
182 
183     @Override
184     public long length() {
185         return endOffset - startOffset;
186     }
187 
188     @Override
189     public long progress() {
190         return offset - startOffset;
191     }
192 }