package org.dcache.http; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.stream.ChunkedInput; import io.netty.handler.stream.ChunkedNioFile; import java.nio.ByteBuffer; import org.dcache.pool.repository.RepositoryChannel; /* * Portions of this file based on Netty's ChunkedNioFile which has the * following copyright: * * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ public class ReusableChunkedNioFile implements ChunkedInput<ByteBuf> { private final RepositoryChannel _channel; private final long _startOffset; private final long _endOffset; private final int _chunkSize; private volatile long _offset; public ReusableChunkedNioFile(RepositoryChannel channel, long offset, long length, int chunkSize) { if (channel == null) { throw new NullPointerException("Channel must not be null"); } if (offset < 0) { throw new IllegalArgumentException("offset: " + offset + " (expected: 0 or greater)"); } if (length < 0) { throw new IllegalArgumentException("length: " + length + " (expected: 0 or greater)"); } if (chunkSize <= 0) { throw new IllegalArgumentException("chunkSize: " + chunkSize + " (expected: 1 or greater)"); } _channel = channel; _chunkSize = chunkSize; _startOffset = _offset = offset; _endOffset = _offset + length; } /** * With a normal ChunkedNioFile, Netty at some point receives a * "connection closed by peer" signal and closes the file, trying to * release the resources. As this closes the disk-file, the mover becomes * useless despite keep-alive. To avoid this, close here is a no-op. */ @Override public void close() throws Exception { /* make sure to close the backing stream yourself */ } @Override public boolean isEndOfInput() throws Exception { return _offset >= _endOffset || !_channel.isOpen(); } @Override public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception { return readChunk(ctx.alloc()); } /** * Like {@link ChunkedNioFile#readChunk}, but uses position independent * IO calls. */ @Override public ByteBuf readChunk(ByteBufAllocator allocator) throws Exception { long offset = _offset; if (offset >= _endOffset) { return null; } int length = (int) Math.min(_chunkSize, _endOffset - offset); ByteBuf chunk = allocator.buffer(length); boolean release = true; try { ByteBuffer buffer = chunk.nioBuffer(0, length); while (buffer.hasRemaining()) { /* use position independent thread safe call */ int bytes = _channel.read(buffer, offset); if (bytes < 0) { break; } offset += bytes; } chunk.writerIndex(buffer.position()); _offset = offset; release = false; return chunk; } finally { if (release) { chunk.release(); } } } @Override public long length() { return _endOffset - _startOffset; } @Override public long progress() { return _offset - _startOffset; } /** * Returns the repository channel. Used for unit testing. */ RepositoryChannel getChannel() { return _channel; } /** * Returns the end offset. Used for unit testing. */ long getEndOffset() { return _endOffset; } /** * Returns the current offset. Used for unit testing. */ long getOffset() { return _offset; } }