package edu.washington.cs.oneswarm.f2f.multisource;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.disk.DiskManagerFileInfo;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.torrent.TOTorrentFile;
import org.gudy.azureus2.core3.util.ED2KHasher;
import org.gudy.azureus2.core3.util.SHA1Hasher;
import edu.washington.cs.oneswarm.f2f.multisource.Sha1HashManager.Sha1CalcListener;
import edu.washington.cs.oneswarm.f2f.multisource.Sha1HashManager.Sha1Result;
class Sha1Calculator
{
private final static Logger logger = Logger.getLogger(Sha1Calculator.class.getName());
private final ExecutorService executors;
private volatile boolean quit = false;
public Sha1Calculator() {
executors = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("Sha1+ed2k calculator worker thread");
t.setDaemon(true);
return t;
}
});
}
public void getHashesFromDownload(DownloadManager dm,
List<Sha1CalcListener> listeners) {
logger.fine("job submitted: " + dm.getDisplayName() + " listeners="
+ listeners.size());
if (quit == true) {
Exception e = new Exception("calculator shut down");
for (Sha1CalcListener listener : listeners) {
listener.errorOccured(e);
}
return;
}
if (dm == null) {
Exception e = new Exception("dm is null");
for (Sha1CalcListener listener : listeners) {
listener.errorOccured(e);
}
return;
}
executors.submit(new Sha1Ed2kWorkerWorker(dm, listeners));
}
public void stop() {
quit = true;
}
private class Sha1Ed2kWorkerWorker
implements Runnable
{
final DownloadManager dm;
final List<Sha1CalcListener> listeners;
private Sha1Ed2kWorkerWorker(DownloadManager dm,
List<Sha1CalcListener> listeners) {
this.listeners = listeners;
this.dm = dm;
}
private void notifyListenersCompleted(Sha1Result result) {
for (Sha1CalcListener listener : listeners) {
listener.completed(result);
}
}
private void notifyListenersErrorOccured(Exception e) {
for (Sha1CalcListener listener : listeners) {
listener.errorOccured(e);
}
}
private void notifyListenersProgress(double d) {
logger.finest("hash progress: " + d);
for (Sha1CalcListener listener : listeners) {
listener.progress(d);
}
}
public void run() {
try {
logger.fine("started: " + dm.getDisplayName());
if (!dm.isDownloadComplete(false)) {
notifyListenersErrorOccured(new Exception("download not completed"));
return;
}
if (quit == true) {
notifyListenersErrorOccured(new Exception("calc service stopped"));
return;
}
TOTorrentFile[] tFiles = dm.getTorrent().getFiles();
DiskManagerFileInfo[] dFiles = dm.getDiskManagerFileInfo();
ArrayList<byte[]> ed2kHashes = new ArrayList<byte[]>(tFiles.length);
ArrayList<byte[]> sha1Hashes = new ArrayList<byte[]>(tFiles.length);
double totalToHash = 0;
for (DiskManagerFileInfo d : dFiles) {
if (!d.isSkipped()) {
totalToHash += d.getLength();
logger.finest("adding: " + d.getLength() + " ("
+ d.getTorrentFile().getRelativePath() + ")");
}
}
logger.finer("total to hash: " + totalToHash);
double totalRead = 0;
for (int i = 0; i < dFiles.length && !quit; i++) {
DiskManagerFileInfo dFile = dFiles[i];
TOTorrentFile tFile = tFiles[i];
if (dFile.isSkipped()) {
ed2kHashes.add(null);
sha1Hashes.add(null);
continue;
}
File fileOnDisk = dFile.getFile(true);
if (!fileOnDisk.exists()) {
notifyListenersErrorOccured(new Exception("file: " + fileOnDisk
+ " not found"));
return;
}
if (fileOnDisk.length() != tFile.getLength()) {
notifyListenersErrorOccured(new Exception("file: " + fileOnDisk
+ " not completed (" + fileOnDisk.length() + " != "
+ tFile.getLength()));
return;
}
double rateLimitMBps = COConfigurationManager.getIntParameter("oneswarm.max.sha1.hash.rate.kbps") / 1024.0;
SHA1Hasher sha1_hash = new SHA1Hasher();
ED2KHasher ed2k_hash = new ED2KHasher();
byte[] buffer = new byte[1024 * 1024];
File f = dFile.getFile(true);
InputStream in = new FileInputStream(f);
logger.finer("hashing file: " + f.getCanonicalPath());
int len;
long lastRead = System.currentTimeMillis();
while ((len = in.read(buffer)) != -1 && !quit) {
totalRead += len;
sha1_hash.update(buffer, 0, len);
ed2k_hash.update(buffer, 0, len);
notifyListenersProgress(totalRead / totalToHash);
/*
* limit hash rate to rateMBps
*/
long msTaken = System.currentTimeMillis() - lastRead;
double mbRead = len / 1024.0 / 1024.0;
logger.finest("read " + mbRead + " MB in " + msTaken + "ms");
if (rateLimitMBps > 0) {
long targetMs = Math.round(1000 * (mbRead / rateLimitMBps));
if (msTaken < targetMs) {
long s = targetMs - msTaken;
logger.finest("sleeping " + s + " ms to limit hash speed");
Thread.sleep(s);
}
}
lastRead = System.currentTimeMillis();
}
sha1Hashes.add(sha1_hash.getDigest());
ed2kHashes.add(ed2k_hash.getDigest());
}
if (!quit) {
notifyListenersCompleted(new Sha1Result(sha1Hashes, ed2kHashes));
} else {
notifyListenersErrorOccured(new Exception("calc service stopped"));
return;
}
} catch (Exception e) {
notifyListenersErrorOccured(e);
}
}
}
}