/* * Copyright 2013 Thomas Bocek, Maxat Pernebayev * * 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 net.tomp2p.synchronization; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.Set; import java.util.TreeMap; import net.tomp2p.dht.DHTBuilder; import net.tomp2p.futures.BaseFutureAdapter; import net.tomp2p.futures.FutureChannelCreator; import net.tomp2p.futures.FutureDone; import net.tomp2p.futures.FutureResponse; import net.tomp2p.message.DataMap; import net.tomp2p.message.Message; import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number640; import net.tomp2p.peers.PeerAddress; import net.tomp2p.storage.Data; import net.tomp2p.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The builder for the synchronization. This class first sends an info message to get the checksums, then it checks what * needs to be done, (nothing, full copy, diff). * * @author Thomas Bocek * @author Maxat Pernebayev * */ public class SyncBuilder extends DHTBuilder<SyncBuilder> { private static final Logger LOG = LoggerFactory.getLogger(SyncBuilder.class); private static final FutureDone<SyncStat> FUTURE_SHUTDOWN = new FutureDone<SyncStat>() .failed("sync builder - peer is shutting down"); static final int DEFAULT_BLOCK_SIZE = 700; private final PeerAddress other; private final PeerSync peerSync; private final int blockSize; private DataMap dataMap; private Number640 key; private Set<Number640> keys; private NavigableMap<Number640, Collection<Number160>> dataMapHash; private ArrayList<Instruction> instructions; private boolean syncFromOldVersion = false; public SyncBuilder(final PeerSync peerSync, final PeerAddress other) { this(peerSync, other, DEFAULT_BLOCK_SIZE); } /** * Constructor. * * @param peer * The responsible peer that performs synchronization */ public SyncBuilder(final PeerSync peerSync, final PeerAddress other, final int blockSize) { super(peerSync.peerDHT(), Number160.ZERO); self(this); this.other = other; this.peerSync = peerSync; this.blockSize = blockSize; } public SyncBuilder dataMap(DataMap dataMap) { this.dataMap = dataMap; return this; } public Number640 key() { return key; } public SyncBuilder key(Number640 key) { this.key = key; return this; } public Set<Number640> keys() { return keys; } public SyncBuilder keys(Set<Number640> keys) { this.keys = keys; return this; } public SyncBuilder syncFromOldVersion() { syncFromOldVersion = true; return this; } public boolean isSyncFromOldVersion() { return syncFromOldVersion; } public SyncBuilder syncFromOldVersion(boolean syncFromOldVersion) { this.syncFromOldVersion = syncFromOldVersion; return this; } public DataMap dataMap() { if (dataMap != null) { return dataMap; } else { NavigableMap<Number640, Data> newDataMap = new TreeMap<Number640, Data>(); if (key != null) { Data data = peer.storageLayer().get(key); if (data == null) { data = new Data().flag2(); } newDataMap.put(key, data); } if (keys != null) { for (Number640 key : keys) { Data data = peer.storageLayer().get(key); if (data == null) { data = new Data().flag2(); } newDataMap.put(key, data); } } if (newDataMap.size() > 0) { return new DataMap(newDataMap); } else { throw new IllegalArgumentException("Need either dataMap, key, or keys!"); } } } public NavigableMap<Number640, Collection<Number160>> dataMapHash() { if (dataMapHash == null) { dataMapHash = new TreeMap<Number640, Collection<Number160>>(); } if (dataMap != null) { for (Map.Entry<Number640, Number160> entry : dataMap.convertToHash().entrySet()) { Set<Number160> hashSet = new HashSet<Number160>(1); hashSet.add(entry.getValue()); dataMapHash.put(entry.getKey(), hashSet); } } if (key != null) { Set<Number160> hashSet = new HashSet<Number160>(1); hashSet.add(peer.storageLayer().get(key).hash()); dataMapHash.put(key, hashSet); } if (keys != null) { for (Number640 key : keys) { Set<Number160> hashSet = new HashSet<Number160>(1); hashSet.add(peer.storageLayer().get(key).hash()); dataMapHash.put(key, hashSet); } } return dataMapHash; } public ArrayList<Instruction> instructions() { return instructions; } public FutureDone<SyncStat> start() { if (peer.peer().isShutdown()) { return FUTURE_SHUTDOWN; } final FutureDone<SyncStat> futureSync = new FutureDone<SyncStat>(); FutureChannelCreator futureChannelCreator = peer.peer().connectionBean().reservation().create(0, 2); Utils.addReleaseListener(futureChannelCreator, futureSync); futureChannelCreator.addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(final FutureChannelCreator future2) throws Exception { if (!future2.isSuccess()) { futureSync.failed(future2); LOG.error("checkDirect failed {}", future2.failedReason()); return; } final FutureResponse futureResponse = peerSync.syncRPC().infoMessage(other, SyncBuilder.this, future2.channelCreator()); futureResponse.addListener(new BaseFutureAdapter<FutureResponse>() { @Override public void operationComplete(FutureResponse future) throws Exception { if (future.isFailed()) { Utils.addReleaseListener(future2.channelCreator(), futureResponse); futureSync.failed(future); LOG.error("checkDirect failed {}", future.failedReason()); return; } Message responseMessage = future.responseMessage(); DataMap dataMap = responseMessage.dataMap(0); if (dataMap == null) { LOG.error("nothing received, something is wrong"); futureSync.failed("nothing received, something is wrong"); return; } NavigableMap<Number640, Data> retVal = new TreeMap<Number640, Data>(); boolean syncMessageRequired = false; int dataCopy = 0; int dataOrig = 0; //int dataCopyCount = 0; //int diffCount = 0; //int dataNotCopied = 0; for (Map.Entry<Number640, Data> entry : dataMap.dataMap().entrySet()) { Data data = entry.getValue(); if(data.length() == 0) { if(data.isFlag1()) { LOG.debug("no sync required"); syncMessageRequired = false; } else if(data.isFlag2()) { LOG.debug("copy required for key {}",entry.getKey()); syncMessageRequired = true; Data data2 = peer.storageLayer().get(entry.getKey()); dataOrig += data2.length(); //copy retVal.put(entry.getKey(), data2); dataCopy += data2.length(); } } else { LOG.debug("sync required"); syncMessageRequired = true; Data data2 = peer.storageLayer().get(entry.getKey()); dataOrig += data2.length(); final ByteBuf buffer = data.buffer(); Number160 versionKey = SyncUtils.decodeHeader(buffer); Number160 hash = SyncUtils.decodeHeader(buffer); List<Checksum> checksums = SyncUtils.decodeChecksums(buffer); //buffer.release(); // TODO: don't copy data, toBytes does a copy! List<Instruction> instructions = RSync.instructions( data2.toBytes(), checksums, blockSize); ByteBuf abuf = Unpooled.buffer(); dataCopy += SyncUtils.encodeInstructions(instructions, versionKey, hash, abuf); //abuf.release(); //diff Data data1 = new Data(abuf).flag1(); retVal.put(entry.getKey(), data1); } } final SyncStat syncStat = new SyncStat(peer.peerAddress().peerId(), other.peerId(), dataCopy, dataOrig); if (syncMessageRequired) { SyncBuilder.this.dataMap(new DataMap(retVal)); FutureResponse fr = peerSync.syncRPC().syncMessage(other, SyncBuilder.this, future2.channelCreator()); fr.addListener(new BaseFutureAdapter<FutureResponse>() { @Override public void operationComplete(FutureResponse future) throws Exception { if (future.isFailed()) { futureSync.failed(future); } else { futureSync.done(syncStat); } } }); } else { futureSync.done(syncStat); } } }); } }); return futureSync; } }