package edu.washington.cs.oneswarm.f2f.multisource;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;
import org.bouncycastle.util.encoders.Base64;
import org.gudy.azureus2.core3.disk.DiskManagerFileInfo;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerListener;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.HashWrapper;
import edu.washington.cs.oneswarm.f2f.FileListFile;
public class Sha1HashManager
{
private static Sha1HashManager instance = new Sha1HashManager();
private final static Logger logger = Logger.getLogger(Sha1HashManager.class.getName());
public static final String OS_HASHES_ADDED = "os_hashes";
public static final String OS_HASHES_TYPE_LOCAL = "local";
public static final String OS_HASHES_TYPE_REMOTE = "remote";
public static final String OS_HASHES_TYPE_TORRENT = "torrent";
private Sha1Calculator calc = new Sha1Calculator();
private DownloadManagerListener downloadManagerListener = new CompletionListener();
private Set<Sha1HashJobListener> jobListeners = new HashSet<Sha1HashJobListener>();
private Set<HashWrapper> submittedJobs = new HashSet<HashWrapper>();
private Timer initialDelayTimer = new Timer(
"sha1 calc delay",
true);
private Sha1HashManager() {
}
public void addJobListener(Sha1HashJobListener listener) {
synchronized (this) {
jobListeners.add(listener);
}
}
void downloadManagerAdded(final DownloadManager dm, boolean addListener) {
/*
* check if the download state has the sha1 hashes
*/
DownloadManagerState ds = dm.getDownloadState();
String hashedAdded = ds.getAttribute(OS_HASHES_ADDED);
if (hashedAdded == null) {
logger.finer("no hashes in dm: " + dm.getDisplayName());
/*
* no hashes added, add them from the .torrent file
*/
try {
List<String> sha1s = getHashesFromTorrent(dm.getTorrent(),
FileListFile.KEY_SHA1_HASH);
if (sha1s.size() > 0) {
logger.finest("adding hashes from torrent");
List<String> ed2ks = getHashesFromTorrent(dm.getTorrent(),
FileListFile.KEY_ED2K_HASH);
ds.setListAttribute(FileListFile.KEY_SHA1_HASH,
sha1s.toArray(new String[sha1s.size()]));
ds.setListAttribute(FileListFile.KEY_ED2K_HASH,
ed2ks.toArray(new String[ed2ks.size()]));
ds.setAttribute(OS_HASHES_ADDED, OS_HASHES_TYPE_TORRENT);
} else {
// the user might have added them after
String[] remoteSha1s = ds.getListAttribute(FileListFile.KEY_SHA1_HASH);
String[] remoteED2Ks = ds.getListAttribute(FileListFile.KEY_ED2K_HASH);
if (remoteSha1s != null && remoteSha1s.length > 0
&& remoteED2Ks != null && remoteED2Ks.length > 0) {
ds.setAttribute(OS_HASHES_ADDED, OS_HASHES_TYPE_REMOTE);
logger.finer("got remote hashes");
} else {
logger.finest("no remote hashes");
}
if (dm.isDownloadComplete(true)) {
if (hashedAdded == null
|| hashedAdded.equals(OS_HASHES_TYPE_REMOTE)) {
// need to calc hashes but wait until after 2 minutes to not slow down initial startup
logger.finest("need to calcluate hashes, hash job queued");
initialDelayTimer.schedule(new TimerTask() {
@Override
public void run() {
try {
queueHashCalc(dm);
} catch (TOTorrentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, 2 * 60 * 1000);
}
} else {
// if the download is running, add a listener
if (addListener) {
dm.addListener(downloadManagerListener);
}
}
}
} catch (TOTorrentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else if (hashedAdded.equals(OS_HASHES_TYPE_REMOTE)) {
String[] remoteSha1s = ds.getListAttribute(FileListFile.KEY_SHA1_HASH);
if (remoteSha1s.length == 0) {
ds.setAttribute(OS_HASHES_ADDED, null);
}
}
}
private void queueHashCalc(final DownloadManager dm)
throws TOTorrentException {
HashWrapper torrentHash = new HashWrapper(dm.getTorrent().getHash());
synchronized (this) {
if (!submittedJobs.contains(torrentHash)) {
submittedJobs.add(torrentHash);
List<Sha1CalcListener> listeners = new LinkedList<Sha1CalcListener>();
listeners.add(new Sha1CalcListener() {
public void completed(Sha1Result result) {
logger.finer("hashing completed for: " + dm.getDisplayName());
final DownloadManagerState ds = dm.getDownloadState();
String[] sha1s = new String[result.sha1Values.size()];
String[] ed2ks = new String[result.ed2kValues.size()];
for (int i = 0; i < sha1s.length; i++) {
sha1s[i] = new String(Base64.encode(result.sha1Values.get(i)));
ed2ks[i] = new String(Base64.encode(result.ed2kValues.get(i)));
logger.finest("hash for file " + i + ": sha1=" + sha1s[i]
+ " ed2k=" + ed2ks[i]);
}
ds.setListAttribute(FileListFile.KEY_SHA1_HASH, sha1s);
ds.setListAttribute(FileListFile.KEY_ED2K_HASH, ed2ks);
ds.setAttribute(OS_HASHES_ADDED, OS_HASHES_TYPE_LOCAL);
logger.fine("added hashes for: " + dm.getDisplayName());
}
public void errorOccured(Throwable cause) {
cause.printStackTrace();
}
public void progress(double fraction) {
}
});
for (Sha1HashJobListener l : jobListeners) {
Sha1CalcListener cl = l.jobAdded(dm.getDisplayName());
if (cl != null) {
listeners.add(cl);
}
}
logger.fine("submitting hash job: " + dm.getDisplayName());
calc.getHashesFromDownload(dm, listeners);
}
}
}
public void stop() {
calc.stop();
}
@SuppressWarnings("unchecked")
private static List<String> getHashesFromTorrent(TOTorrent torrent,
String type) throws TOTorrentException {
List<String> sha1Found = new LinkedList<String>();
if(torrent == null){
Debug.out("torrent is null!");
return sha1Found;
}
Map torrentMap = (Map) torrent.serialiseToMap().get("info");
if (torrent.isSimpleTorrent()) {
String torrentSha1 = getHashFromTorrentMap(torrentMap, type);
if (torrentSha1 != null) {
sha1Found.add(torrentSha1);
}
} else {
Object filePropertiesObj = torrentMap.get("files");
if (filePropertiesObj == null || !(filePropertiesObj instanceof List)) {
return sha1Found;
}
List fileProperties = (List) filePropertiesObj;
if (fileProperties != null) {
for (Object pObj : fileProperties) {
if (pObj != null && pObj instanceof Map) {
Map pMap = (Map) pObj;
String torrentSha1 = getHashFromTorrentMap(pMap, type);
if (torrentSha1 != null) {
sha1Found.add(torrentSha1);
}
}
}
}
}
return sha1Found;
}
@SuppressWarnings("unchecked")
private static String getHashFromTorrentMap(Map torrentMap, String type) {
if (torrentMap.containsKey(type)) {
return new String(Base64.encode((byte[]) torrentMap.get(type)));
} else {
return null;
}
}
public static Sha1HashManager getInstance() {
return instance;
}
private class CompletionListener
implements DownloadManagerListener
{
public void completionChanged(DownloadManager manager, boolean bCompleted) {
}
public void downloadComplete(DownloadManager dm) {
// each time a download completes, check if we need to add the sha1 hashes (but only when all files are available)
logger.fine("download completed: " + dm.getDisplayName());
try {
synchronized (Sha1HashManager.this) {
if (dm.isDownloadComplete(true)) {
DownloadManagerState ds = dm.getDownloadState();
String hashedAdded = ds.getAttribute(OS_HASHES_ADDED);
if (hashedAdded == null
|| hashedAdded.equals(OS_HASHES_TYPE_REMOTE)) {
// need to calc hashes
queueHashCalc(dm);
} else {
logger.fine("hashes already calculated: " + dm.getDisplayName());
}
} else {
logger.fine("skipping hash calc: " + dm.getDisplayName());
}
}
} catch (TOTorrentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void filePriorityChanged(DownloadManager download,
DiskManagerFileInfo file) {
}
public void positionChanged(DownloadManager download, int oldPosition,
int newPosition) {
}
public void stateChanged(DownloadManager manager, int state) {
}
}
public interface Sha1CalcListener
{
public void completed(Sha1Result result);
public void errorOccured(Throwable cause);
public void progress(double fraction);
}
public interface Sha1HashJobListener
{
public Sha1CalcListener jobAdded(String name);
}
public static class Sha1Result
{
private final List<byte[]> ed2kValues;
private final List<byte[]> sha1Values;
Sha1Result(List<byte[]> sha1Values, List<byte[]> ed2kValues) {
this.sha1Values = sha1Values;
this.ed2kValues = ed2kValues;
}
public List<byte[]> getEd2kValues() {
return ed2kValues;
}
public List<byte[]> getSha1Values() {
return sha1Values;
}
}
}