/*
* Copyright 2004 - 2008 Christian Sprajc, Dennis Waldherr. All rights reserved.
*
* This file is part of PowerFolder.
*
* PowerFolder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation.
*
* PowerFolder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PowerFolder. If not, see <http://www.gnu.org/licenses/>.
*
* $Id$
*/
package de.dal33t.powerfolder.transfer;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import de.dal33t.powerfolder.Controller;
import de.dal33t.powerfolder.Member;
import de.dal33t.powerfolder.light.FileInfo;
import de.dal33t.powerfolder.light.MemberInfo;
import de.dal33t.powerfolder.message.RequestPart;
import de.dal33t.powerfolder.transfer.Transfer.TransferState;
import de.dal33t.powerfolder.util.Range;
import de.dal33t.powerfolder.util.Reject;
import de.dal33t.powerfolder.util.Util;
import de.dal33t.powerfolder.util.delta.FilePartsRecord;
import de.dal33t.powerfolder.util.delta.FilePartsState.PartState;
/**
* This download manager will try to download from all available sources.
*
* @author Dennis "Bytekeeper" Waldherr
*/
public class MultiSourceDownloadManager extends AbstractDownloadManager {
private static final Logger log = Logger
.getLogger(MultiSourceDownloadManager.class.getName());
private final ConcurrentMap<MemberInfo, Download> downloads = Util
.createConcurrentHashMap(4);
private Download pendingPartRecordFrom;
public static final DownloadManagerFactory factory = new DownloadManagerFactory()
{
public DownloadManager createDownloadManager(Controller controller,
FileInfo file, boolean automatic)
{
return new MultiSourceDownloadManager(controller, file, automatic);
}
};
private MultiSourceDownloadManager(Controller controller, FileInfo file,
boolean automatic)
{
super(controller, file, automatic);
}
@Override
protected void addSourceImpl(Download download) {
assert download != null;
assert canAddSource(download.getPartner());
// logFine("Adding source: " + download);
if (downloads.put(download.getPartner().getInfo(), download) != null) {
logSevere("Overridden previous download for member: "
+ download.getPartner() + ". " + download);
}
// Non-automatic overrides automatic
if (isRequestedAutomatic() != download.isRequestedAutomatic()) {
setAutomatic(false);
}
}
public boolean canAddSource(Member member) {
Reject.ifNull(member, "Member is null");
return downloads.isEmpty() || Util.useSwarming(getController(), member);
}
public Download getSourceFor(Member member) {
Reject.ifNull(member, "Member is null");
return downloads.get(member.getInfo());
}
public boolean hasSource(Download d) {
Reject.ifNull(d, "Download is null!");
return downloads.get(d.getPartner().getInfo()) == d;
}
/**
* @return the completed date of a download. Takes the most recent completed
* date of its sources.
*/
public Date getCompletedDate() {
if (!downloads.isEmpty()) {
Date mostRecentCompletion = null;
for (Download download : downloads.values()) {
Date completedDate = download.getCompletedDate();
if (completedDate != null) {
if (mostRecentCompletion == null
|| completedDate.after(mostRecentCompletion))
{
mostRecentCompletion = completedDate;
}
}
}
return mostRecentCompletion;
}
return null;
}
/*
* Returns the sources of this manager.
* @see de.dal33t.powerfolder.transfer.MultiSourceDownload#getSources()
*/
public Collection<Download> getSources() {
return Collections.unmodifiableCollection(downloads.values());
}
public boolean hasSources() {
return !downloads.isEmpty();
}
@Override
protected void removeSourceImpl(Download download) {
assert download != null;
assert hasSource(download);
if (downloads.remove(download.getPartner().getInfo()) == null) {
throw new AssertionError("Removed non-managed download:" + download
+ " " + download.getPartner().getInfo());
}
// All pending requests from that download are void.
if (filePartsState != null) {
for (RequestPart req : download.getPendingRequests()) {
filePartsState.setPartState(req.getRange(), PartState.NEEDED);
}
}
}
@Override
public String toString() {
String string = super.toString() + "; sources=" + downloads.values()
+ "; pending requested bytes: ";
if (filePartsState != null) {
string += filePartsState.countPartStates(filePartsState.getRange(),
PartState.PENDING)
+ "; available: "
+ filePartsState.countPartStates(filePartsState.getRange(),
PartState.AVAILABLE)
+ "; needed: "
+ filePartsState.countPartStates(filePartsState.getRange(),
PartState.NEEDED);
}
return string;
}
/**
* Returns an available source for requesting the {@link FilePartsRecord}
*
* @param download
* @return
*/
protected Download findPartRecordSource(Download download) {
assert download != null;
for (Download d : downloads.values()) {
if (d.isStarted() && !d.isBroken()
&& Util.useDeltaSync(getController(), d))
{
download = d;
break;
}
}
return download;
}
protected void requestFilePartsRecord(Download download) {
assert download == null || Util.useDeltaSync(getController(), download);
if (pendingPartRecordFrom != null) {
// logFine("Pending FPR from: " + pendingPartRecordFrom);
// Check if we really need to do this first
if (!pendingPartRecordFrom.isBroken()) {
return;
}
logWarning("Source should have been removed: "
+ pendingPartRecordFrom);
pendingPartRecordFrom = null;
}
if (download == null) {
download = findPartRecordSource(null);
}
// logFine("Selected FPR source: " + download);
if (download != null) {
assert Util.useDeltaSync(getController(), download);
if (isFine()) {
logFine("Requesting Filepartsrecord from " + download);
}
setTransferState(TransferState.FILERECORD_REQUEST);
pendingPartRecordFrom = download;
pendingPartRecordFrom.requestFilePartsRecord();
setStarted();
}
}
protected void sendPartRequests() throws BrokenDownloadException {
if (isFiner()) {
logFiner("X Sending part requests: "
+ filePartsState.countPartStates(filePartsState.getRange(),
PartState.NEEDED));
}
setTransferState(TransferState.DOWNLOADING);
Range range;
while (true) {
range = filePartsState.findFirstPart(PartState.NEEDED);
if (range == null) {
// File completed, or only pending requests left
break;
}
range = Range.getRangeByLength(range.getStart(), Math.min(
getController().getTransferManager().getMaxFileChunkSize(),
range.getLength()));
// Split requests across sources
if (findAndRequestDownloadFor(range)) {
filePartsState.setPartState(range, PartState.PENDING);
} else {
break;
}
}
if (isFiner()) {
logFiner("X Sending part requests over");
}
long p = filePartsState.countPartStates(filePartsState.getRange(),
PartState.PENDING);
if (p > 0) {
for (Download d : downloads.values()) {
if (d.isStarted() && !d.isBroken()) {
for (RequestPart rp : d.getPendingRequests()) {
p -= rp.getRange().getLength();
}
}
}
assert p == 0;
}
assert filePartsState.isCompleted()
|| filePartsState.countPartStates(filePartsState.getRange(),
PartState.PENDING) > 0 || hasNoAvailableSources() : "AVAIL: "
+ filePartsState.countPartStates(filePartsState.getRange(),
PartState.AVAILABLE)
+ ", NEED : "
+ filePartsState.countPartStates(filePartsState.getRange(),
PartState.NEEDED)
+ ", PEND : "
+ filePartsState.countPartStates(filePartsState.getRange(),
PartState.PENDING);
}
/**
* Checks if no sources are available for requests.
*/
private boolean hasNoAvailableSources() {
for (Download d : downloads.values()) {
if (d.isStarted() && !d.isBroken()) {
return false;
}
}
return true;
}
private boolean findAndRequestDownloadFor(Range range)
throws BrokenDownloadException
{
assert range != null;
if (isFiner()) {
logFiner("X findAndRequestDownloadFor: " + range);
}
for (Download d : downloads.values()) {
if (!d.isStarted() || d.isBroken()) {
continue;
}
if (d.requestPart(range)) {
return true;
}
}
return false;
}
}