package bitNom; import java.util.*; import java.util.concurrent.*; import java.io.*; import java.nio.channels.*; import org.ccnx.ccn.impl.support.Log; // TODO: Ideally, we want this to be the actual "Download" // class; the idea is to have the idea of a "segment" // be equal to the idea of a "chunk" However, for some reason, // ccnx can't send single files greater than ~512KiB, at // least through the file proxy. public class ChunkDownload implements Runnable { // Only have at most maxThreads running at a time public static final int maxThreads = 20; private String path; private String outFile; private int nSeg; private int doneSegs; private Download _parent; public List<String> peers; private List<SegDownloader> segDownloads; public ArrayBlockingQueue<SegDownloader> bstopped; public ArrayBlockingQueue<SegDownloader> active; private boolean segFin[]; private boolean done; public Dstatus status; public float percentDone; private ArrayBlockingQueue<Boolean> waitToken; private int waiters; RandomAccessFile file; FileChannel channel; ChunkDownload (String dpath, String output, List<String> recentPeers, int segments, Download parent){ done = false; path = dpath; nSeg = segments; peers = recentPeers; segFin = new boolean[nSeg]; doneSegs = 0; status = Dstatus.DOWNLOADING; outFile = output; _parent = parent; segDownloads = new ArrayList<SegDownloader>(segments); bstopped = new ArrayBlockingQueue<SegDownloader>(segments); active = new ArrayBlockingQueue<SegDownloader>(maxThreads); // Helping variables to allow a thread to blocking wait on this download. waitToken = new ArrayBlockingQueue<Boolean>(1); waitToken.add(true); waiters = 0; percentDone = 0; } // Methods just for reading private members public int nSeg() { return nSeg; } public int doneSegs() { return doneSegs; } public String outFile() { return outFile; } public boolean done() { return done; } public void run() { //Create a file channel for the file try { _parent.addToActive(this); file = new RandomAccessFile (Globals.ourHome + outFile, "rwd"); channel = file.getChannel(); // Create a segment downloader for each segment and run them simultaneously for (int i = 0; i < nSeg; i++) { segDownloads.add(new SegDownloader(this, peers.get(i % peers.size()) +"/"+ path, i)); (new Thread(segDownloads.get(segDownloads.size() - 1))).start(); } // Wait for a download thread to either finish or fail, and update our bookkeeping try { while (!done) { // Get a new segment downloader off the stopped list. SegDownloader s = bstopped.take(); // Give a new path to every download thread that failed. if (s.status() == Dstatus.FAILED) { synchronized(s) { s.dlPath = getNewPath(); removeStopped(s); s.notify(); } } // Check if that segment finishes off the download. else if (s.status() == Dstatus.FINISHED) { if (nSeg == doneSegs){ done = true; percentDone = 100; System.out.println("Download " + Globals.ourHome + outFile + " Finished!"); // "Notify" the waiters that we're done. while (waiters > 0 && waitToken.take()){ waiters--; } } } else throw new RuntimeException("Impossible"); } } catch (InterruptedException e){ status = Dstatus.FAILED; } } catch (IOException e) { Log.info("Could not open file for download!"); status = Dstatus.FAILED; } catch (InterruptedException e) { // TODO Auto-generated catch block status = Dstatus.FAILED; } } // Give a new download path to a downloader. public synchronized String getNewPath(){ String peer = peers.get(0); return peer + path; } public synchronized void addStopped(SegDownloader s){ try { bstopped.put(s); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public synchronized void removeStopped(SegDownloader s){ bstopped.remove(s); } // Only call this from a downloader thread. // If the active queue is full, the downloader will // block until a spot frees up. public void addActive(SegDownloader s){ try { active.put(s); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void removeActive(SegDownloader s){ try { if (doneSegs < nSeg) active.take(); } catch (InterruptedException e) { // Nothing } } public synchronized void finishSegment(int s){ if (s < 0) throw new RuntimeException( "Tried to finish nonexistent segment." ); segFin[s] = true; doneSegs++; } public void printStatus(){ System.out.println("File: " + outFile() + "\n\tDownload : " + percentDone + "% completed."); } // Wait on this download to finish. Implemented by trying to push something to the // blocking queue. Since the pusher waits until the queue has space, only empty // the queue if the download is finished. public void waitForMe(){ waiters++; try { waitToken.put(true); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }