/*
* 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.filesystem.volume;
import io.vertx.core.logging.Logger;
import org.sfs.SfsVertx;
import org.sfs.filesystem.ChecksummedPositional;
import org.sfs.rx.ObservableFuture;
import org.sfs.rx.RxHelper;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Func1;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.math.LongMath.checkedAdd;
import static io.vertx.core.logging.LoggerFactory.getLogger;
import static java.lang.Boolean.TRUE;
import static org.sfs.block.RangeLock.lockedObservable;
import static org.sfs.filesystem.volume.IndexBlockReader.LockType;
import static org.sfs.filesystem.volume.IndexBlockReader.LockType.READ;
import static org.sfs.filesystem.volume.IndexBlockReader.LockType.WRITE;
import static org.sfs.math.Rounding.up;
import static org.sfs.protobuf.XVolume.XIndexBlock;
import static org.sfs.rx.RxHelper.iterate;
public class IndexScanner {
private static final Logger LOGGER = getLogger(IndexScanner.class);
private boolean isDebugEnabled;
private final IndexFile indexFile;
private final int blockSize;
private final int batchSize;
private final long lockWaitTimeout;
private long position;
private long blockCount = 0;
public IndexScanner(IndexFile indexFile, int batchSize, long lockWaitTimeout) {
this.isDebugEnabled = LOGGER.isDebugEnabled();
this.indexFile = indexFile;
this.blockSize = indexFile.getBlockSize();
this.batchSize = batchSize;
this.lockWaitTimeout = lockWaitTimeout;
this.position = 0;
}
public Observable<Void> scanIndex(SfsVertx vertx, LockType lockType, Func1<ChecksummedPositional<XIndexBlock>, Observable<Void>> transformer) {
return indexFile.size(vertx)
.flatMap(fileSize -> {
ObservableFuture<Void> handler = RxHelper.observableFuture();
long roundedFileSize = up(fileSize, blockSize);
if (isDebugEnabled) {
long numberOfBlocks = roundedFileSize / blockSize;
LOGGER.debug("Max position is " + roundedFileSize + ". Expecting at most " + numberOfBlocks + " blocks");
}
scanIndex0(vertx, lockType, transformer, handler, roundedFileSize);
return handler;
});
}
protected void scanIndex0(SfsVertx vertx, LockType lockType, Func1<ChecksummedPositional<XIndexBlock>, Observable<Void>> transformer, ObservableFuture<Void> handler, long fileSize) {
int bufferSize = batchSize * blockSize;
if (isDebugEnabled) {
LOGGER.debug("Reading " + batchSize + " blocks @ position " + position);
}
if (READ.equals(lockType) || WRITE.equals(lockType)) {
checkState(lockWaitTimeout > 0, "Invalid LockWaitTimeout value %s", lockWaitTimeout);
lockedObservable(vertx,
() -> {
if (WRITE.equals(lockType)) {
return indexFile.tryWriteLock(position, bufferSize);
} else if (READ.equals(lockType)) {
return indexFile.tryReadLock(position, bufferSize);
} else {
throw new IllegalStateException("Unsupported Locked Type " + lockType);
}
},
() -> indexFile.getBlocks(vertx, position, batchSize),
lockWaitTimeout)
.flatMap(checksummedPositionals ->
iterate(vertx,
checksummedPositionals,
xIndexBlockChecksummedPositional ->
transformer.call(xIndexBlockChecksummedPositional)
.doOnNext(aVoid -> blockCount++)
.map(aVoid -> TRUE)))
.doOnNext(aBoolean -> {
if (isDebugEnabled) {
LOGGER.debug("Scanned " + blockCount + " blocks");
}
})
.subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
if (isDebugEnabled) {
LOGGER.debug("Scanned " + blockCount + " blocks");
}
position = checkedAdd(position, bufferSize);
if (position >= fileSize) {
handler.complete(null);
} else {
if (!isUnsubscribed()) {
vertx.runOnContext(event -> scanIndex0(vertx, lockType, transformer, handler, fileSize));
}
}
}
@Override
public void onError(Throwable e) {
handler.fail(e);
}
@Override
public void onNext(Boolean aBoolean) {
}
});
} else {
indexFile.getBlocks(vertx, position, batchSize)
.flatMap(checksummedPositionals ->
iterate(vertx,
checksummedPositionals,
xIndexBlockChecksummedPositional ->
transformer.call(xIndexBlockChecksummedPositional)
.doOnNext(aVoid -> blockCount++)
.map(aVoid -> TRUE)))
.doOnNext(aBoolean -> {
if (isDebugEnabled) {
LOGGER.debug("Scanned " + blockCount + " blocks");
}
})
.subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
position = checkedAdd(position, bufferSize);
if (position >= fileSize) {
handler.complete(null);
} else {
vertx.runOnContext(event -> scanIndex0(vertx, lockType, transformer, handler, fileSize));
}
}
@Override
public void onError(Throwable e) {
handler.fail(e);
}
@Override
public void onNext(Boolean aBoolean) {
}
});
}
}
}