package edu.washington.cs.oneswarm.f2f.multisource;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;
import org.apache.commons.io.FileSystemUtils;
import org.bouncycastle.util.encoders.Base64;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.disk.DiskManagerFileInfo;
import org.gudy.azureus2.core3.disk.DiskManagerPiece;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerException;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.global.GlobalManagerDownloadRemovalVetoException;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.util.Base32;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.FileUtil;
import org.gudy.azureus2.core3.util.HashWrapper;
import org.gudy.azureus2.core3.util.TorrentUtils;
import com.aelitis.azureus.core.AzureusCoreException;
import com.aelitis.azureus.core.impl.AzureusCoreImpl;
import edu.washington.cs.oneswarm.f2f.FileCollection;
import edu.washington.cs.oneswarm.f2f.FileListFile;
import edu.washington.cs.oneswarm.f2f.TextSearchResult.TextSearchResponseItem;
import edu.washington.cs.oneswarm.f2f.network.F2FDownloadManager;
import edu.washington.cs.oneswarm.f2f.network.OverlayManager;
import edu.washington.cs.oneswarm.f2f.network.SearchManager;
import edu.washington.cs.oneswarm.f2f.network.SearchManager.TextSearchListener;
import edu.washington.cs.oneswarm.f2f.permissions.GroupBean;
import edu.washington.cs.oneswarm.f2f.share.ShareManagerTools;
import edu.washington.cs.oneswarm.plugins.PluginCallback;
public class Sha1SourceFinder {
public static final String ONESWARM_AUTO_ADDED = Sha1DownloadManager.ONESWARM_AUTO_ADDED;
private final static long MAX_METAINFO_REQUEST_RATE = 60 * 1000;
private final static long MAX_AUTO_ADDED_SWARMS = 10;
private final static long REMOVE_AUTO_ADDED_IF_IDLE_SEC = 5 * 60;
private final static long MIN_TIME_BEFORE_REMOVE = 2 * 60 * 1000;
private final SearchManager searchManager;
private final OverlayManager overlayManager;
private static Logger logger = Logger.getLogger(Sha1SourceFinder.class.getName());
private final HashMap<DownloadManager, AdditionalSourceFinder> finders = new HashMap<DownloadManager, AdditionalSourceFinder>();
private final HashMap<HashWrapper, Long> alreadyDownloading = new HashMap<HashWrapper, Long>();
private final HashSet<HashWrapper> alreadyAdded = new HashSet<HashWrapper>();
private final static long startTime = System.currentTimeMillis();
private static final boolean DEBUG = false;
public Sha1SourceFinder(OverlayManager overlayManager) {
this.overlayManager = overlayManager;
this.searchManager = overlayManager.getSearchManager();
Thread t = new Thread(new AdditionalSourceFinderWorker());
t.setName("Sha1SourceFinder");
t.setDaemon(true);
t.start();
// run at app start 5 minutes in
Timer emptyFolderTimer = new Timer("emptyFolderTimer", true);
emptyFolderTimer.schedule(new TimerTask() {
@Override
public void run() {
removeOldAndEmptyFolders();
}
}, 5 * 60 * 1000);
}
@SuppressWarnings("unchecked")
private int getAutoAddedSwarmCount() {
List<DownloadManager> dms = AzureusCoreImpl.getSingleton().getGlobalManager()
.getDownloadManagers();
// for each auto added download, that it has at least on interesting
// sha1, if not, delete it!!!
int count = 0;
for (DownloadManager dm : dms) {
if (dm.getDownloadState().getBooleanAttribute(ONESWARM_AUTO_ADDED)) {
count++;
}
}
return count;
}
private volatile boolean checkQueued = false;
public void queueSha1Tasks() {
logger.finest("requesting sha1 task");
checkQueued = true;
}
private boolean isHashActive(byte[] hash) {
return AzureusCoreImpl.getSingleton().getGlobalManager()
.getDownloadManager(new HashWrapper(hash)) != null;
}
private void removeOldAndEmptyFolders() {
logger.finer("scanning for old unused auto added folders");
try {
File autoAddedDir = Sha1DownloadManager.getMultiTorrentDownloadDir();
if (autoAddedDir.isDirectory()) {
// check for .torrent files
File[] dotTorrents = autoAddedDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
boolean torrentFile = name.toLowerCase().endsWith(".torrent");
if (!torrentFile) {
return false;
}
return torrentFile;
}
});
logger.finest("looking for .torrent files, found: " + dotTorrents.length);
// remove any unused ones
for (File dotTorrent : dotTorrents) {
logger.finest("considering: " + dotTorrent.getCanonicalPath());
if (!dotTorrent.isFile()) {
logger.finest("not file: " + dotTorrent.getCanonicalPath());
continue;
}
try {
// all auto added torrent files have files names that is
// the base32 encode if the info hash
String name = dotTorrent.getName();
String id = name.substring(0, name.length() - ".torrent".length());
if (id.length() != 32) {
logger.finest("not base32: " + id + " len=" + id.length());
continue;
}
byte[] infoHash = Base32.decode(id);
if (infoHash.length == 20 && !isHashActive(infoHash)) {
logger.fine("removing no longer used .torrent file: " + name);
dotTorrent.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
File[] directories = autoAddedDir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (!pathname.isDirectory()) {
return false;
}
if (pathname.getName().length() != 32) {
return false;
}
return true;
}
});
logger.finest("looking for empty folders, candidates: " + directories.length);
for (File dir : directories) {
try {
byte[] infoHash = Base32.decode(dir.getName());
if (infoHash.length == 20 && !isHashActive(infoHash)) {
logger.fine("removing empty auto added download dir: " + dir.getName());
recursiveRemoveEmptyDirectory(dir.getAbsolutePath(), dir);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void recursiveRemoveEmptyDirectory(String canonicalpreefix, File rootDir)
throws IOException {
// check that we didn't take a symlink outside our base folder
if (!rootDir.getCanonicalPath().startsWith(canonicalpreefix)) {
return;
}
if (!rootDir.isDirectory()) {
return;
}
// do a depth first search and remove empty folders
File[] children = rootDir.listFiles();
for (File child : children) {
recursiveRemoveEmptyDirectory(canonicalpreefix, child);
}
if (rootDir.listFiles().length == 0) {
// if the dir is empty, delete
logger.finer("deleting: " + rootDir.getCanonicalPath());
rootDir.delete();
}
}
@SuppressWarnings("unchecked")
private void checkForUnusedAutoAddedAndDeleteThem(boolean pauseEncouraged) {
logger.finest("checking for unused autoadded torrents");
HashSet<HashWrapper> interestingSha1s = getInterestingSha1s();
boolean dmRemoved = false;
List<DownloadManager> dms = AzureusCoreImpl.getSingleton().getGlobalManager()
.getDownloadManagers();
// for each auto added download, check that it has at least one
// interesting sha1, if not, delete it!!!
dm_loop: for (DownloadManager dm : dms) {
if (dm.getDownloadState().getBooleanAttribute(ONESWARM_AUTO_ADDED)) {
HashWrapper[] hashesFromDownload = Sha1DownloadManager.getHashesFromDownload(dm,
FileListFile.KEY_SHA1_HASH, false);
// check if this dm still has something to give, that is
// if we got any data from it in the last 5 minutes
boolean active = dm.getStats().getTimeSinceLastDataReceivedInSeconds() < REMOVE_AUTO_ADDED_IF_IDLE_SEC;
boolean justStarted = System.currentTimeMillis() - startTime < MIN_TIME_BEFORE_REMOVE;
boolean justAdded = System.currentTimeMillis() - dm.getStats().getTimeStarted() < MIN_TIME_BEFORE_REMOVE;
boolean properState = dm.getState() == DownloadManager.STATE_DOWNLOADING
|| dm.getState() == DownloadManager.STATE_SEEDING;
// for the auto added download to be in good standing
// it must have a sha1 peer pointing at a non-auto-added
// download
// and that download must have a sha1 peer pointing at it
boolean hasProperVirtualPeers = false;
if (!justStarted && properState) {
PEPeerManager peerManager = dm.getPeerManager();
if (peerManager != null) {
List peers = peerManager.getPeers();
good_standing_loop: for (Object o : peers) {
if (o instanceof Sha1Peer) {
Sha1Peer localPeer = (Sha1Peer) o;
DownloadManager srcDownloadManager = localPeer
.getSourceDownloadManager();
// only non auto added downloads count
if (srcDownloadManager.getDownloadState().getBooleanAttribute(
ONESWARM_AUTO_ADDED)) {
continue;
}
PEPeerManager srcPeerManager = srcDownloadManager.getPeerManager();
if (srcPeerManager == null) {
continue;
}
for (Object o2 : srcPeerManager.getPeers()) {
if (o2 instanceof Sha1Peer) {
Sha1Peer s2 = (Sha1Peer) o2;
if (s2.getSourceDownloadManager().equals(dm)) {
hasProperVirtualPeers = true;
break good_standing_loop;
}
}
}
}
}
}
}
boolean interestingSha1Files = false;
for (HashWrapper h : hashesFromDownload) {
if (interestingSha1s.contains(h)) {
interestingSha1Files = true;
break;
}
}
if (interestingSha1Files && dm.getState() != DownloadManager.STATE_ERROR) {
// continue means that we keep the download
if (justAdded) {
continue dm_loop;
}
if (justStarted) {
continue dm_loop;
}
// if it is not just started or we just started oneswarm,
// make sure that it is in proper state, currently
// downloading, that the virtual peers are set up
if (properState && hasProperVirtualPeers && active) {// &&
// !completed)
// {
continue dm_loop;
}
}
// no interesting sha1s found, delete!!!
try {
logger.finer(dm.getDisplayName()
+ " is no longer needed, removing: properState=" + properState
+ " virtualPeers=" + hasProperVirtualPeers + " active=" + active
+ " insterestingSha1=" + interestingSha1Files);
TOTorrent torrent = dm.getTorrent();
boolean simple = torrent.isSimpleTorrent();
String id = new String(Base32.encode(torrent.getHash()));
DiskManagerFileInfo[] files = dm.getDiskManagerFileInfo();
File[] filesToDelete = new File[files.length];
for (int i = 0; i < files.length; i++) {
filesToDelete[i] = files[i].getFile(true);
}
AzureusCoreImpl.getSingleton().getGlobalManager()
.removeDownloadManager(dm, true, false);
dmRemoved = true;
File torrentFile = new File(Sha1DownloadManager.getMultiTorrentDownloadDir(),
id + ".torrent");
if (torrentFile.isFile()) {
torrentFile.delete();
}
// now, delete each file, slighly different checks depending
// on simple torrent or not
if (simple) {
// one file, parent should be the infohash
if (filesToDelete.length != 1) {
logger.warning("tried to delete files from simple torrent but there are more that 1 file!");
continue dm_loop;
}
File toDelete = filesToDelete[0];
File parent = toDelete.getCanonicalFile().getParentFile();
if (parent == null || !parent.getName().equals(id)) {
logger.warning("tried to delete files from simple torrent but the directory has changed! '"
+ parent + "'");
continue dm_loop;
}
if (toDelete.isFile()) {
logger.finer("deleting file: " + toDelete);
toDelete.delete();
}
if (parent.list().length == 0) {
logger.finer("directory empty, deleting it as well " + parent);
parent.delete();
}
} else {
// not simple
if (filesToDelete.length == 0) {
logger.warning("no files to delete");
continue dm_loop;
}
// the parents parent should be the id
File parent = filesToDelete[0].getCanonicalFile().getParentFile();
if (parent == null) {
logger.warning("parent file is null!");
continue dm_loop;
}
File grandParent = parent.getParentFile();
if (grandParent == null || !grandParent.getName().equals(id)) {
logger.warning("tried to delete files from torrent but the directory has changed! grandparent='"
+ parent + "'");
continue dm_loop;
}
for (int i = 0; i < filesToDelete.length; i++) {
File toDelete = filesToDelete[i];
logger.finer("deleting file: " + toDelete);
toDelete.delete();
}
if (parent != null && parent.isDirectory() && parent.list().length == 0) {
logger.finer("deleting parent dir: " + parent);
parent.delete();
}
if (grandParent != null && grandParent.isDirectory()
&& grandParent.list().length == 0) {
logger.finer("deleting grand parent dir: " + grandParent);
grandParent.delete();
}
}
} catch (AzureusCoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GlobalManagerDownloadRemovalVetoException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TOTorrentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/*
* if we removed a download manager we are done for this round, we can
* do the fine tuning next round
*/
if (dmRemoved) {
return;
}
// next step, check for individual files that no longer are interesting
// (maybe we completed it)
long totalSpaceToSave = 0;
for (DownloadManager dm : dms) {
try {
if (dm.getDownloadState().getBooleanAttribute(ONESWARM_AUTO_ADDED)) {
HashWrapper[] hashesFromDownload = Sha1DownloadManager.getHashesFromDownload(
dm, FileListFile.KEY_SHA1_HASH, false);
DiskManagerFileInfo[] files = dm.getDiskManagerFileInfo();
TOTorrent torrent = dm.getTorrent();
String id = new String(Base32.encode(torrent.getHash()));
boolean hasPaused = false;
boolean resumeNeeded = false;
for (int i = 0; i < hashesFromDownload.length; i++) {
DiskManagerFileInfo file = files[i];
HashWrapper hash = hashesFromDownload[i];
if (!file.isSkipped() && !interestingSha1s.contains(hash)) {
totalSpaceToSave += file.getLength();
// file is no longer interesting, consider to delete
File f = file.getFile(true);
logger.finest("found individual file that no longer is interesting, considering for deletion: "
+ f);
// check if we need to pause or if we are allowed to
// pause
if (pauseEncouraged || dm.isPaused()) {
// need to pause the dm if we didn't already
if (!hasPaused) {
resumeNeeded = dm.pause();
}
// the parent should be the id
File parent = f.getCanonicalFile().getParentFile();
if (parent == null) {
logger.warning("parent file is null!");
continue;
}
if (parent == null || !parent.getName().equals(id)) {
logger.warning("tried to compact files from torrent but the directory has changed! parent='"
+ parent + "'");
continue;
}
logger.finer("compacting file: " + f);
/*
* setting the file to compact will remove
* everything except the first and last piece as
* they might be needed for the adjacent files
*/
file.setSkipped(true);
file.setStorageType(DiskManagerFileInfo.ST_COMPACT);
}
}
}
if (resumeNeeded) {
dm.resume();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TOTorrentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
* call the function again if we can save enough space
*/
if (totalSpaceToSave > 1024 * 1024 * 1024) {
checkForUnusedAutoAddedAndDeleteThem(true);
}
}
public void searchForAlternativeSources(DownloadManager dm) {
if (COConfigurationManager.getBooleanParameter("oneswarm.multi.torrent.enabled") == false) {
logger.fine("skipping sha1 additional sources download");
return;
}
if (dm.getDownloadState().getBooleanAttribute(ONESWARM_AUTO_ADDED)) {
Debug.out("requested search for additional sources for autoadded download!!!");
return;
}
logger.fine("searching for additional sources for: " + dm.getDisplayName());
AdditionalSourceFinder finder = null;
synchronized (Sha1SourceFinder.this) {
finder = finders.get(dm);
if (finder == null) {
logger.finer("creating multi torrent source finder");
/*
* need to create a new source finder for this download
*/
DownloadManagerState downloadState = dm.getDownloadState();
if (downloadState == null) {
logger.finer("no download state, returning");
return;
}
String[] sha1s = downloadState.getListAttribute(FileListFile.KEY_SHA1_HASH);
if (sha1s == null || sha1s.length == 0) {
logger.finer("no sha1 hashes, returning");
return;
}
finder = new AdditionalSourceFinder(dm);
finders.put(dm, finder);
}
}
if (getAutoAddedSwarmCount() < MAX_AUTO_ADDED_SWARMS) {
finder.searchForAdditionalSources();
}
}
/**
* adds a non autoadded download without any files selected
*
* @param metainfo
*/
private void addDownload(String originalTorrentName, byte[] metainfo) {
if (COConfigurationManager.getBooleanParameter("oneswarm.multi.torrent.enabled") == false) {
logger.fine("skipping sha1 additional sources download");
return;
}
if (getAutoAddedSwarmCount() >= MAX_AUTO_ADDED_SWARMS) {
logger.finer("not adding download (max auto added num reached)");
return;
}
try {
TOTorrent torrent = TorrentUtils.readFromBEncodedInputStream(new ByteArrayInputStream(
metainfo));
logger.fine("auto adding download, torrent=" + originalTorrentName + " added="
+ new String(torrent.getName()));
String torrentHashString = Base32.encode(torrent.getHash());
String id = new String(torrentHashString);
String shortId = id.substring(0, 6);
String newName = originalTorrentName + " (additional source " + shortId + ")";
File f = new File(Sha1DownloadManager.getMultiTorrentDownloadDir(), torrentHashString
+ ".torrent");
TorrentUtils.writeToFile(torrent, f);
ArrayList<GroupBean> permissions = new ArrayList<GroupBean>();
permissions.add(GroupBean.ALL_FRIENDS);
HashSet<String> selectedFiles = new HashSet<String>();
File downloadDir = new File(Sha1DownloadManager.getMultiTorrentDownloadDir(), id);
if (!downloadDir.isDirectory()) {
downloadDir.mkdir();
}
logger.finer("saving auto added torrent to: " + downloadDir.getCanonicalPath());
DownloadManager dm = ShareManagerTools.addDownload(selectedFiles, permissions,
downloadDir.getCanonicalPath(), true, f, torrent);
synchronized (Sha1SourceFinder.this) {
alreadyAdded.add(new HashWrapper(torrent.getHash()));
}
DownloadManagerState dms = dm.getDownloadState();
dms.setBooleanAttribute(ONESWARM_AUTO_ADDED, true);
// listeners are notified in a separate thread, we need the sha1
// hashes straight away so set them here if available
Sha1HashManager.getInstance().downloadManagerAdded(dm, false);
// and check that they seem ok
HashWrapper[] hashes = Sha1DownloadManager.getHashesFromDownload(dm,
FileListFile.KEY_SHA1_HASH, false);
if (hashes == null || hashes.length != torrent.getFiles().length) {
logger.warning("downloaded torrent because of sha1 match but there are no (or wrong amount of) sha1 hashes in there!");
AzureusCoreImpl.getSingleton().getGlobalManager()
.removeDownloadManager(dm, true, true);
return;
}
dms.setDisplayName(newName);
logger.finest("setting name to: " + newName);
//
dm.renameDownload(id);
logger.finest("setting save path to: " + id);
/*
* now, figure out which files that are useful
*/
searchExistingAutoAddedDownloads();
// do a check again after we clean up any unused files
queueSha1Tasks();
} catch (TOTorrentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (AzureusCoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GlobalManagerDownloadRemovalVetoException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (DownloadManagerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
private void searchExistingAutoAddedDownloads() {
if (COConfigurationManager.getBooleanParameter("oneswarm.multi.torrent.enabled") == false) {
logger.fine("skipping sha1 additional sources download");
return;
}
logger.finer("searching existing auto-added downloads for interesting files that are skipped");
/*
* for each normal download that is currently downloading, get the sha1
* hashes we are interested in
*/
HashSet<HashWrapper> interestingSha1s = getInterestingSha1s();
List<DownloadManager> dms = AzureusCoreImpl.getSingleton().getGlobalManager()
.getDownloadManagers();
long totalDiskSpaceUsed = 0;
for (DownloadManager dm : dms) {
if (dm.getDownloadState().getBooleanAttribute(ONESWARM_AUTO_ADDED)) {
DiskManagerFileInfo[] files = dm.getDiskManagerFileInfo();
for (int i = 0; i < files.length; i++) {
if (!files[i].isSkipped()) {
totalDiskSpaceUsed += files[i].getLength();
}
}
}
}
logger.finer("using " + (totalDiskSpaceUsed / 1024 / 1024)
+ " MB for autoadded downloads already");
// this value is a bit tricky to explain, a value of 1 means that we
// will use as much for temp torrents as we have free space (after we
// allocated it all...)
double maxRatioOfFreeSpaceToUse = COConfigurationManager
.getFloatParameter("oneswarm.max.multi.torrent.auto.disk.space");
for (DownloadManager dm : dms) {
if (dm.getDownloadState().getBooleanAttribute(ONESWARM_AUTO_ADDED)) {
try {
File saveLocation = dm.getAbsoluteSaveLocation().getCanonicalFile()
.getParentFile();
if (!dm.getTorrent().isSimpleTorrent()) {
saveLocation = saveLocation.getParentFile();
}
long freeSpace = 0;
try {
saveLocation.getUsableSpace();
} catch (Throwable e) {
// for java 5
}
if (freeSpace == 0) {
if (FileUtil.getUsableSpaceSupported()) {
logger.finest("using FileUtil for free space");
freeSpace = FileUtil.getUsableSpace(saveLocation);
}
if (freeSpace == 0) {
logger.finest("using FileSystemUtil for free space");
freeSpace = 1024 * FileSystemUtils.freeSpaceKb(saveLocation
.getCanonicalPath());
}
}
logger.finest("free space at " + saveLocation.getAbsolutePath() + " "
+ (freeSpace / 1024 / 1024) + "MB");
HashWrapper[] hashesFromDownload = Sha1DownloadManager.getHashesFromDownload(
dm, FileListFile.KEY_SHA1_HASH, false);
DiskManagerFileInfo[] files = dm.getDiskManagerFileInfo();
// check all pieces and set the ones we are interested in to
// non
// skip
for (int i = 0; i < hashesFromDownload.length; i++) {
DiskManagerFileInfo file = files[i];
HashWrapper hash = hashesFromDownload[i];
if (file.isSkipped() && interestingSha1s.contains(hash)) {
logger.finer("considering file: "
+ file.getTorrentFile().getRelativePath());
long fileSize = file.getTorrentFile().getLength();
logger.finest("file size: " + (fileSize / 1024 / 1024) + "MB");
double limit = freeSpace * maxRatioOfFreeSpaceToUse;
long used = totalDiskSpaceUsed + fileSize;
if (used > limit) {
logger.finer("not enabling download of file, free="
+ (freeSpace / 1024 / 1024) + " used="
+ (used / 1024 / 1024) + " limit=" + (limit / 1024 / 1024));
continue;
}
logger.finer("enabling download of file: "
+ file.getTorrentFile().getRelativePath());
file.setSkipped(false);
totalDiskSpaceUsed += fileSize;
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
@SuppressWarnings("unchecked")
private HashSet<HashWrapper> getInterestingSha1s() {
HashSet<HashWrapper> interestingSha1s = new HashSet<HashWrapper>();
List<DownloadManager> dms_ = AzureusCoreImpl.getSingleton().getGlobalManager()
.getDownloadManagers();
for (DownloadManager dm : dms_) {
if (dm.getState() == DownloadManager.STATE_DOWNLOADING && !dm.isDownloadComplete(false)
&& !dm.getDownloadState().getBooleanAttribute(ONESWARM_AUTO_ADDED)) {
HashWrapper[] hashesFromDownload = Sha1DownloadManager.getHashesFromDownload(dm,
FileListFile.KEY_SHA1_HASH, false);
DiskManagerFileInfo[] files = dm.getDiskManagerFileInfo();
for (int i = 0; i < hashesFromDownload.length; i++) {
DiskManagerFileInfo file = files[i];
if (file.isSkipped()) {
continue;
}
if (file.getDownloaded() == file.getLength()) {
continue;
}
interestingSha1s.add(hashesFromDownload[i]);
}
}
}
return interestingSha1s;
}
private class AdditionalSourceFinderWorker implements Runnable {
private long lastRun = System.currentTimeMillis();
private final static long PERIODIC_CHECK_RATE = 60 * 1000;
@Override
public void run() {
try {
while (true) {
Thread.sleep(5000);
if (checkQueued || System.currentTimeMillis() - lastRun > PERIODIC_CHECK_RATE) {
checkQueued = false;
lastRun = System.currentTimeMillis();
logger.finest("deleting uninteresting dms");
checkForUnusedAutoAddedAndDeleteThem(false);
/*
* this might have caused there to be more free space,
* check if we can add any new files
*/
logger.finest("Checking for interesting files");
searchExistingAutoAddedDownloads();
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private class AdditionalSourceFinder {
private final static long MAX_CHECK_RATE = F2FDownloadManager.MAX_SEARCH_FREQ - 60 * 1000;
private final static int MAX_SEARCHES_TO_SEND = 5;
private final DownloadManager dm;
private final String[] sha1s;
private long lastCheck = 0;
public AdditionalSourceFinder(DownloadManager dm) {
this.dm = dm;
sha1s = dm.getDownloadState().getListAttribute(FileListFile.KEY_SHA1_HASH);
}
public void searchForAdditionalSources() {
long timeSinceLast = System.currentTimeMillis() - lastCheck;
if (timeSinceLast < MAX_CHECK_RATE) {
logger.finest("skipping sha1 source check, (last one was " + timeSinceLast / 1000
+ " seconds ago");
return;
}
lastCheck = System.currentTimeMillis();
if (dm.getDiskManager() == null) {
logger.finest("skipping sha1 source check (diskmanager not ready)");
return;
}
DiskManagerFileInfo[] files = dm.getDiskManagerFileInfo();
int searchesSent = 0;
// make a shuffled list so we don't search for the same files all
// the time
ArrayList<Integer> shuffled = new ArrayList<Integer>(sha1s.length);
for (int i = 0; i < sha1s.length; i++) {
shuffled.add(i);
}
Collections.shuffle(shuffled);
for (int i = 0; i < sha1s.length; i++) {
if (!DEBUG && searchesSent == MAX_SEARCHES_TO_SEND) {
logger.finest("already sent " + searchesSent + " searches, breaking");
break;
}
int currentIndex = shuffled.get(i);
DiskManagerFileInfo file = files[currentIndex];
String sha1 = sha1s[currentIndex];
if (file.isSkipped()) {
continue;
}
if (file.getDownloaded() == file.getLength()) {
continue;
}
// check for incomplete pieces, the file is only interesting if
// there are incomplete pieces in the middle of the file,
// first/last block does not count
boolean incompleteInterestingPieces = false;
DiskManagerPiece[] pieces = dm.getDiskManager().getPieces();
for (int j = file.getFirstPieceNumber() + 1; j < file.getLastPieceNumber() - 1
&& j < pieces.length; j++) {
if (pieces[j].isDone()) {
continue;
}
if (pieces[i].isSkipped()) {
continue;
}
incompleteInterestingPieces = true;
}
if (!incompleteInterestingPieces) {
continue;
}
final String torrentFileHash = dm.getDisplayName() + ": "
+ file.getTorrentFile().getRelativePath() + " (" + sha1 + ")";
logger.finer(torrentFileHash + ": searching for sources");
searchesSent++;
searchManager.sendTextSearch("sha1;" + sha1, new TextSearchListener() {
@Override
public void searchResponseReceived(TextSearchResponseItem r) {
List<FileCollection> swarms = r.getFileList().getElements();
logger.finest(torrentFileHash + ": got search results: " + swarms.size());
for (FileCollection swarm : swarms) {
final String infoHash = swarm.getUniqueID();
if (infoHash == null) {
logger.warning(torrentFileHash + ": infohash null!!!");
continue;
}
final HashWrapper hw = new HashWrapper(Base64.decode(infoHash));
if (AzureusCoreImpl.getSingleton().getGlobalManager()
.getDownloadManager(hw) != null) {
logger.finest(torrentFileHash + ": already added");
continue;
}
Long lastAttempt = alreadyDownloading.get(hw);
if (lastAttempt != null
&& System.currentTimeMillis() - lastAttempt.longValue() < MAX_METAINFO_REQUEST_RATE) {
logger.finest(torrentFileHash + ": already downloading metainfo");
continue;
}
synchronized (Sha1SourceFinder.this) {
if (alreadyAdded.contains(hw)) {
logger.finest("already added this torrent once, will not add again until next reboot");
continue;
}
alreadyDownloading.put(hw, System.currentTimeMillis());
}
logger.finer(torrentFileHash + ": downloading metainfo, swarm="
+ infoHash);
overlayManager.sendMetaInfoRequest(r.getConnectionId(),
r.getChannelId(), hw.getBytes(), 0,
new PluginCallback<byte[]>() {
@Override
public void requestCompleted(byte[] data) {
logger.finer(torrentFileHash
+ ": metainfo download completed, swarm="
+ infoHash);
addDownload(dm.getDisplayName(), data);
synchronized (Sha1SourceFinder.this) {
alreadyDownloading.remove(hw);
}
}
@Override
public void progressUpdate(int progress) {
logger.finest(torrentFileHash
+ ": downloading metainfo, swarm=" + infoHash
+ " progress=" + progress);
}
@Override
public void errorOccured(String string) {
}
@Override
public void dataRecieved(long bytes) {
}
});
}
}
});
}
}
}
}