Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.io;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.ClosedChannelException;
import java.util.Objects;

/// [SeekableByteChannel] with read buffer for efficient reading.
///
/// Writing ([SeekableByteChannel#write(java.nio.ByteBuffer)]) are passthrough directly to the underlying channel, and will invalidate the internal data buffer.
///
/// There's no guarantee on thread safety of this implementation.
public class BufferedSeekableByteChannel implements SeekableByteChannel {

private final SeekableByteChannel underlying;
private final ByteBuffer buffer;
/// `buffer[i] == underlying[i + bufferStart]`
private long bufferStart;
/// `false` if [#buffer] should be refreshed
private boolean bufferValid;
/// current position, relative to the start of file
private long position;

/// Create a [BufferedSeekableByteChannel] with a buffer size of 8192
public BufferedSeekableByteChannel(SeekableByteChannel underlying) {
this(underlying, 8192);
}

/// Create a [BufferedSeekableByteChannel] with specified buffer size
///
/// @throws IllegalArgumentException if `bufferSize <= 0`
/// @throws NullPointerException if `underlying == null`
public BufferedSeekableByteChannel(SeekableByteChannel underlying, int bufferSize) {
if (bufferSize <= 0) {
throw new IllegalArgumentException(String.format("int bufferSize <= 0 (%s <= 0)", bufferSize));
}
this.underlying = Objects.requireNonNull(underlying, "SeekableByteChannel underlying == null");
this.buffer = ByteBuffer.allocate(bufferSize);
this.bufferStart = 0;
this.bufferValid = false;
this.position = 0;
}

@Override
public int read(ByteBuffer dst) throws IOException {
ensureOpen();
if (!dst.hasRemaining()) {
return 0;
}

int totalRead = 0;
while (dst.hasRemaining()) {
if (!bufferValid || buffer.remaining() == 0) {
fillBuffer();
if (!bufferValid || buffer.remaining() == 0) {
// EOL
break;
}
}

int bytesToCopy = Math.min(buffer.remaining(), dst.remaining());
ByteBuffer slice = buffer.slice();
slice.limit(bytesToCopy);
dst.put(slice);
buffer.position(buffer.position() + bytesToCopy);
totalRead += bytesToCopy;
position += bytesToCopy;
}

return totalRead == 0 && !bufferValid ? -1 : totalRead;
}

private void fillBuffer() throws IOException {
underlying.position(position);

buffer.clear();
int bytesRead = underlying.read(buffer);
if (bytesRead > 0) {
buffer.flip();
bufferStart = position;
bufferValid = true;
} else {
// EOF
bufferValid = false;
}
}

@Override
public int write(ByteBuffer src) throws IOException {
ensureOpen();
underlying.position(position);

int written = underlying.write(src);
if (written > 0) {
position += written;
invalidateBuffer();
}
return written;
}

@Override
public long position() throws IOException {
ensureOpen();
return position;
}

@Override
public SeekableByteChannel position(long newPosition) throws IOException {
ensureOpen();
if (newPosition < 0) {
throw new IllegalArgumentException("Negative position");
}

// avoid rebuilding buffer if possible
if (bufferValid && newPosition >= bufferStart && newPosition < bufferStart + buffer.limit()) {
int bufferOffset = (int) (newPosition - bufferStart);
buffer.position(bufferOffset);
} else {
invalidateBuffer();
}
position = newPosition;
return this;
}

@Override
public long size() throws IOException {
ensureOpen();
return underlying.size();
}

@Override
public SeekableByteChannel truncate(long size) throws IOException {
ensureOpen();
underlying.truncate(size);
invalidateBuffer();
if (position > size) {
position = size;
underlying.position(position);
}
return this;
}

@Override
public boolean isOpen() {
return underlying.isOpen();
}

@Override
public void close() throws IOException {
underlying.close();
invalidateBuffer();
}

private void invalidateBuffer() {
bufferValid = false;
buffer.clear();
}

private void ensureOpen() throws ClosedChannelException {
if (!isOpen()) {
throw new ClosedChannelException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public static ZipArchiveReader openZipFileWithPossibleEncoding(Path zipFile, Cha
if (possibleEncoding == null)
possibleEncoding = StandardCharsets.UTF_8;

ZipArchiveReader zipReader = new ZipArchiveReader(Files.newByteChannel(zipFile));
ZipArchiveReader zipReader = new ZipArchiveReader(new BufferedSeekableByteChannel(Files.newByteChannel(zipFile)));

Charset suitableEncoding;
try {
Expand All @@ -161,7 +161,7 @@ public static ZipArchiveReader openZipFileWithPossibleEncoding(Path zipFile, Cha
}

zipReader.close();
return new ZipArchiveReader(Files.newByteChannel(zipFile), suitableEncoding);
return new ZipArchiveReader(new BufferedSeekableByteChannel(Files.newByteChannel(zipFile)), suitableEncoding);
}

public static final class Builder {
Expand Down
Loading