/*
* 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;
import io.vertx.core.logging.Logger;
import org.sfs.SfsVertx;
import org.sfs.rx.ObservableFuture;
import org.sfs.rx.RxHelper;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Func1;
import static io.vertx.core.logging.LoggerFactory.getLogger;
import static java.lang.Boolean.TRUE;
import static org.sfs.filesystem.JournalFile.Entry;
import static org.sfs.math.Rounding.up;
import static org.sfs.rx.Defer.just;
public class JournalScanner {
private static final Logger LOGGER = getLogger(JournalScanner.class);
private boolean isDebugEnabled;
private final JournalFile journalFile;
private final int blockSize;
private long position;
public JournalScanner(JournalFile journalFile, long position) {
this.journalFile = journalFile;
this.isDebugEnabled = LOGGER.isDebugEnabled();
this.blockSize = journalFile.getBlockSize();
this.position = position;
}
public Observable<Void> scan(SfsVertx vertx, Func1<Entry, Observable<Boolean>> transformer) {
return journalFile.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");
}
scan0(vertx, transformer, handler, roundedFileSize);
return handler;
});
}
protected void scan0(SfsVertx vertx, Func1<Entry, Observable<Boolean>> transformer, ObservableFuture<Void> handler, long fileSize) {
if (isDebugEnabled) {
LOGGER.debug("Reading 1 blocks @ position " + position);
}
journalFile.getEntry(vertx, position)
.flatMap(entryOptional -> {
if (entryOptional.isPresent()) {
Entry entry = entryOptional.get();
position = entry.getNextHeaderPosition();
return transformer.call(entry)
.doOnNext(_continue -> {
if (isDebugEnabled) {
LOGGER.debug("Scanned 1 block");
}
})
.doOnNext(_continue -> position = entry.getNextHeaderPosition());
} else {
// scan forward until we find a readable block. There will
// eventually be a header block that can be parsed
// or the end of file will be reached
if (isDebugEnabled) {
LOGGER.debug("Skipped 1 block @ position " + position);
}
position += blockSize;
return just(true);
}
})
.subscribe(new Subscriber<Boolean>() {
Boolean result;
@Override
public void onCompleted() {
if (!TRUE.equals(result) || position >= fileSize) {
handler.complete(null);
} else {
vertx.runOnContext(event -> scan0(vertx, transformer, handler, fileSize));
}
}
@Override
public void onError(Throwable e) {
handler.fail(e);
}
@Override
public void onNext(Boolean _continue) {
result = _continue;
}
});
}
}