package com.github.ruediste1.btrbck;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.ruediste1.btrbck.dom.Snapshot;
import com.github.ruediste1.btrbck.dom.Stream;
import com.github.ruediste1.btrbck.dom.VersionHistory;
import com.github.ruediste1.btrbck.dom.VersionHistory.HistoryNode;
import com.github.ruediste1.btrbck.dto.StreamState;
import com.github.ruediste1.btrbck.dto.StreamState.SnapshotEntry;
import com.google.common.base.Objects;
@Singleton
public class SyncService {
Logger log = LoggerFactory.getLogger(SyncService.class);
@Inject
StreamService streamService;
public static class SendFileSpec {
List<Snapshot> cloneSources = new ArrayList<>();
Snapshot target;
@Override
public String toString() {
return Objects.toStringHelper(this).add("sources", cloneSources)
.add("target", target).toString();
}
}
/**
* Create a {@link StreamState} for a stream
*
* @param isNew
*/
public StreamState calculateStreamState(Stream stream, boolean isNew) {
StreamState result = new StreamState();
result.isNewStream = isNew;
for (Snapshot sn : streamService.getSnapshots(stream).values()) {
result.availableSnapshots.add(new SnapshotEntry(sn));
}
return result;
}
/**
* Determine the files (and thus snaphots) to be sent in order to
* synchronize a stream in the stateOfTarget to the sourceStream.
*/
public List<SendFileSpec> determineSendFiles(Stream sourceStream,
StreamState stateOfTarget) {
TreeMap<Integer, Snapshot> sourceSnapshots = streamService
.getSnapshots(sourceStream);
VersionHistory versionHistory = sourceStream.versionHistory;
Set<SnapshotEntry> availableSnapshotNumbers;
if (stateOfTarget == null) {
availableSnapshotNumbers = Collections.emptySet();
} else {
availableSnapshotNumbers = stateOfTarget.availableSnapshots;
}
return determineSendFiles(sourceStream.id, sourceSnapshots,
versionHistory, availableSnapshotNumbers);
}
List<SendFileSpec> determineSendFiles(UUID sourceStreamId,
TreeMap<Integer, Snapshot> sourceSnapshots,
VersionHistory versionHistory, Set<SnapshotEntry> availableSnapshots) {
// calculate the snapshots which are present in the source but missing
// on the target
List<Snapshot> missingSnapshots = calculateMissingSnapshots(
sourceSnapshots.values(), availableSnapshots);
TreeSet<Integer> availableCloneSources = new TreeSet<>();
for (SnapshotEntry entry : availableSnapshots) {
if (sourceSnapshots.containsKey(entry.snapshotNr)
&& Objects.equal(entry.senderStreamId, sourceStreamId)) {
availableCloneSources.add(entry.snapshotNr);
}
}
// calculate the SendFileSpecs for the snapshots missing on the target
ArrayList<SendFileSpec> result = new ArrayList<>();
for (Snapshot snapshot : missingSnapshots) {
SendFileSpec spec = new SendFileSpec();
spec.target = snapshot;
// consider the next snapshot as clone source
{
Integer nextNr = calculateNextSnapshotNr(snapshot,
availableCloneSources);
if (nextNr != null) {
spec.cloneSources.add(sourceSnapshots.get(nextNr));
}
}
// consider ancestors
{
Set<Integer> ancestors = calculateAncestorNrs(snapshot,
versionHistory, availableCloneSources);
for (Integer ancestorNr : ancestors) {
spec.cloneSources.add(sourceSnapshots.get(ancestorNr));
}
}
result.add(spec);
availableCloneSources.add(snapshot.nr);
}
return result;
}
/**
* Determines the ancestors of the given snapshot which are available on the
* target
*/
Set<Integer> calculateAncestorNrs(Snapshot snapshot,
VersionHistory versionHistory,
TreeSet<Integer> availableCloneSources) {
Set<Integer> result = new HashSet<>();
TreeMap<Integer, HistoryNode> nodes = versionHistory.calculateNodes();
// log.debug("Node Map: " + nodes);
HistoryNode node = nodes.get(snapshot.nr);
fillAvailableAncestors(node, result, availableCloneSources);
return result;
}
private void fillAvailableAncestors(HistoryNode node, Set<Integer> result,
TreeSet<Integer> availableCloneSources) {
for (HistoryNode parent : node.parents) {
if (availableCloneSources.contains(parent.snapshotNr)) {
result.add(parent.snapshotNr);
} else {
fillAvailableAncestors(parent, result, availableCloneSources);
}
}
}
/**
* Calculate the next available snapshot after the given snapshot, or null
* if none is available
*/
Integer calculateNextSnapshotNr(Snapshot snapshot,
TreeSet<Integer> availableCloneSources) {
return availableCloneSources.higher(snapshot.nr);
}
List<Snapshot> calculateMissingSnapshots(
Collection<Snapshot> sourceSnapshots,
Set<SnapshotEntry> availableSnapshots) {
List<Snapshot> result = new ArrayList<>();
HashSet<Integer> availableSnapshotNrs = new HashSet<>();
for (SnapshotEntry e : availableSnapshots) {
availableSnapshotNrs.add(e.snapshotNr);
}
for (Snapshot s : sourceSnapshots) {
if (!availableSnapshotNrs.contains(s.nr)) {
result.add(s);
}
}
return result;
}
}