All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
it.anyplace.sync.bep.BlockPuller Maven / Gradle / Ivy
/*
* Copyright (C) 2016 Davide Imbriaco
*
* This Java file is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/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 it.anyplace.sync.bep;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import static com.google.common.base.Objects.equal;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.eventbus.Subscribe;
import com.google.protobuf.ByteString;
import it.anyplace.sync.core.configuration.ConfigurationService;
import it.anyplace.sync.core.beans.BlockInfo;
import it.anyplace.sync.bep.protos.BlockExchageProtos.ErrorCode;
import it.anyplace.sync.bep.protos.BlockExchageProtos.Request;
import it.anyplace.sync.bep.BlockExchangeConnectionHandler.ResponseMessageReceivedEvent;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import static com.google.common.base.Preconditions.checkArgument;
import static it.anyplace.sync.bep.BlockPusher.BLOCK_SIZE;
import it.anyplace.sync.core.beans.FileBlocks;
import it.anyplace.sync.core.cache.BlockCache;
import java.io.Closeable;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.io.FileUtils;
import static com.google.common.base.Preconditions.checkArgument;
/**
*
* @author aleph
*/
public class BlockPuller {
private BlockCache blockCache;
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ConfigurationService configuration;
private final BlockExchangeConnectionHandler connectionHandler;
private final Map blocksByHash = Maps.newConcurrentMap();
private final List hashList = Lists.newArrayList();
private final Set missingHashes = Sets.newConcurrentHashSet();
private final Set requestIds = Sets.newConcurrentHashSet();
private boolean closeConnection = false;
public BlockPuller(ConfigurationService configuration, BlockExchangeConnectionHandler connectionHandler) {
this.configuration = configuration;
this.connectionHandler = connectionHandler;
this.blockCache = BlockCache.getBlockCache(configuration);
}
public BlockPuller(ConfigurationService configuration, BlockExchangeConnectionHandler connectionHandler, boolean closeConnection) {
this(configuration, connectionHandler);
this.closeConnection = closeConnection;
}
public FileDownloadObserver pullBlocks(FileBlocks fileBlocks) throws InterruptedException {
logger.info("pulling file = {}", fileBlocks);
checkArgument(connectionHandler.hasFolder(fileBlocks.getFolder()), "supplied connection handler %s will not share folder %s", connectionHandler, fileBlocks.getFolder());
final Object lock = new Object();
final AtomicReference error = new AtomicReference<>();
final Object listener = new Object() {
@Subscribe
public void handleResponseMessageReceivedEvent(ResponseMessageReceivedEvent event) {
synchronized (lock) {
try {
if (!requestIds.contains(event.getMessage().getId())) {
return;
}
checkArgument(equal(event.getMessage().getCode(), ErrorCode.NO_ERROR), "received error response, code = %s", event.getMessage().getCode());
byte[] data = event.getMessage().getData().toByteArray();
String hash = BaseEncoding.base16().encode(Hashing.sha256().hashBytes(data).asBytes());
blockCache.pushBlock(data);
if (missingHashes.remove(hash)) {
blocksByHash.put(hash, data);
logger.debug("aquired block, hash = {}", hash);
lock.notify();
} else {
logger.warn("received not-needed block, hash = {}", hash);
}
} catch (Exception ex) {
error.set(ex);
lock.notify();
}
}
}
};
FileDownloadObserver fileDownloadObserver = new FileDownloadObserver() {
private long getReceivedData() {
return blocksByHash.size() * BLOCK_SIZE;
}
private long getTotalData() {
return (blocksByHash.size() + missingHashes.size()) * BLOCK_SIZE;
}
@Override
public double getProgress() {
return isCompleted() ? 1d : getReceivedData() / ((double) getTotalData());
}
@Override
public String getProgressMessage() {
return (Math.round(getProgress() * 1000d) / 10d) + "% " + FileUtils.byteCountToDisplaySize(getReceivedData()) + " / " + FileUtils.byteCountToDisplaySize(getTotalData());
}
@Override
public boolean isCompleted() {
return missingHashes.isEmpty();
}
@Override
public void checkError() {
if (error.get() != null) {
throw new RuntimeException(error.get());
}
}
@Override
public double waitForProgressUpdate() throws InterruptedException {
if (!isCompleted()) {
synchronized (lock) {
checkError();
lock.wait();
checkError();
}
}
return getProgress();
}
@Override
public InputStream getInputStream() {
checkArgument(missingHashes.isEmpty(), "pull failed, some blocks are still missing");
List blockList = Lists.newArrayList(Lists.transform(hashList, Functions.forMap(blocksByHash)));
return new SequenceInputStream(Collections.enumeration(Lists.transform(blockList, new Function() {
@Override
public ByteArrayInputStream apply(byte[] data) {
return new ByteArrayInputStream(data);
}
})));
}
@Override
public void close() {
missingHashes.clear();
hashList.clear();
blocksByHash.clear();
try {
connectionHandler.getEventBus().unregister(listener);
} catch (Exception ex) {
}
if (closeConnection) {
connectionHandler.close();
}
}
};
try {
synchronized (lock) {
hashList.addAll(Lists.transform(fileBlocks.getBlocks(), new Function() {
@Override
public String apply(BlockInfo block) {
return block.getHash();
}
}));
missingHashes.addAll(hashList);
for (String hash : missingHashes) {
byte[] block = blockCache.pullBlock(hash);
if (block != null) {
blocksByHash.put(hash, block);
missingHashes.remove(hash);
}
}
connectionHandler.getEventBus().register(listener);
for (BlockInfo block : fileBlocks.getBlocks()) {
if (missingHashes.contains(block.getHash())) {
int requestId = Math.abs(new Random().nextInt());
requestIds.add(requestId);
connectionHandler.sendMessage(Request.newBuilder()
.setId(requestId)
.setFolder(fileBlocks.getFolder())
.setName(fileBlocks.getPath())
.setOffset(block.getOffset())
.setSize(block.getSize())
.setHash(ByteString.copyFrom(BaseEncoding.base16().decode(block.getHash())))
.build());
logger.debug("sent request for block, hash = {}", block.getHash());
}
}
return fileDownloadObserver;
}
} catch (Exception ex) {
fileDownloadObserver.close();
throw ex;
}
}
public abstract class FileDownloadObserver implements Closeable {
public abstract void checkError();
public abstract double getProgress();
public abstract String getProgressMessage();
public abstract boolean isCompleted();
public abstract double waitForProgressUpdate() throws InterruptedException;
public FileDownloadObserver waitForComplete() throws InterruptedException {
while (!isCompleted()) {
waitForProgressUpdate();
}
return this;
}
public abstract InputStream getInputStream();
@Override
public abstract void close();
}
}