/* * Copyright 2016 The Simple File Server Authors * * Licensed 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. */ package org.sfs.io; import io.vertx.core.AsyncResult; import io.vertx.core.Context; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; import io.vertx.core.logging.Logger; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; public class AsyncFileReaderImpl implements AsyncFileReader { private final Logger log; private final AsynchronousFileChannel ch; private final Context context; private Handler<Throwable> exceptionHandler; private boolean paused; private Handler<Buffer> dataHandler; private Handler<Void> endHandler; private long readPos; private boolean readInProgress; private final int bufferSize; private final long startPosition; private long bytesRemaining; public AsyncFileReaderImpl(Context context, long startPosition, int bufferSize, long length, AsynchronousFileChannel dataFile, Logger log) { this.log = log; this.bufferSize = bufferSize; this.readPos = startPosition; this.bytesRemaining = length; this.startPosition = startPosition; this.ch = dataFile; this.context = context; } @Override public long startPosition() { return startPosition; } @Override public long readPosition() { return readPos; } @Override public int bufferSize() { return bufferSize; } @Override public AsyncFileReaderImpl exceptionHandler(Handler<Throwable> handler) { this.exceptionHandler = handler; return this; } private void handleException(Throwable t) { if (exceptionHandler != null && t instanceof Exception) { exceptionHandler.handle(t); } else { log.error("Unhandled exception", t); } } private AsyncFileReaderImpl read(Buffer buffer, int offset, long position, int length, Handler<AsyncResult<Buffer>> handler) { try { ByteBuffer bb = ByteBuffer.allocate(length); doRead(buffer, offset, bb, position, handler); return this; } catch (Throwable e) { handleException(e); return this; } } private void doRead() { try { if (!readInProgress) { readInProgress = true; int countOfBytesToRead = (int) Math.min(bytesRemaining, bufferSize); final Buffer buff = Buffer.buffer(countOfBytesToRead); Handler<AsyncResult<Buffer>> handler = ar -> { readInProgress = false; try { if (ar.succeeded()) { Buffer buffer = ar.result(); int bufferLength = buffer.length(); bytesRemaining -= bufferLength; if (bufferLength <= 0) { // Empty buffer represents end of file handleEnd(); } else { readPos += bufferLength; handleData(buffer); if (!paused && dataHandler != null) { doRead(); } } } else { handleException(ar.cause()); } } catch (Throwable e) { handleException(e); } }; read(buff, 0, readPos, countOfBytesToRead, handler); } } catch (Throwable e) { handleException(e); } } @Override public AsyncFileReaderImpl handler(Handler<Buffer> handler) { this.dataHandler = handler; doRead(); return this; } @Override public AsyncFileReaderImpl endHandler(Handler<Void> handler) { this.endHandler = handler; return this; } @Override public AsyncFileReaderImpl pause() { paused = true; return this; } @Override public AsyncFileReaderImpl resume() { paused = false; doRead(); return this; } private void handleData(Buffer buffer) { if (dataHandler != null) { dataHandler.handle(buffer); } } private void handleEnd() { if (endHandler != null) { Handler<Void> h = endHandler; endHandler = null; h.handle(null); } } private void doRead(final Buffer writeBuff, final int offset, final ByteBuffer buff, final long position, final Handler<AsyncResult<Buffer>> handler) { ch.read(buff, position, null, new java.nio.channels.CompletionHandler<Integer, Object>() { long pos = position; private void done(boolean eof) { context.runOnContext(event -> { buff.flip(); writeBuff.setBytes(offset, buff); Future.succeededFuture(writeBuff).setHandler(handler); }); } public void completed(Integer bytesRead, Object attachment) { if (bytesRead == -1) { //End of file done(true); } else if (buff.hasRemaining()) { // partial read pos += bytesRead; // resubmit doRead(writeBuff, offset, buff, pos, handler); } else { // It's been fully written done(false); } } public void failed(final Throwable t, Object attachment) { context.runOnContext(event -> Future.<Buffer>failedFuture(t).setHandler(handler)); } }); } }