it.anyplace.sync.bep.BlockPuller.java Source code

Java tutorial

Introduction

Here is the source code for it.anyplace.sync.bep.BlockPuller.java

Source

/* 
 * 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<String, byte[]> blocksByHash = Maps.newConcurrentMap();
    private final List<String> hashList = Lists.newArrayList();
    private final Set<String> missingHashes = Sets.newConcurrentHashSet();
    private final Set<Integer> 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<Exception> 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<byte[]> blockList = Lists
                        .newArrayList(Lists.transform(hashList, Functions.forMap(blocksByHash)));
                return new SequenceInputStream(Collections
                        .enumeration(Lists.transform(blockList, new Function<byte[], ByteArrayInputStream>() {
                            @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<BlockInfo, String>() {
                    @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();

    }

}