public void normal(String link) throws MalformedURLException, IOException{
BufferedInputStream in = null;
BufferedOutputStream out = null;
// connect to the server
URL url = new URL(link);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.connect();
// prepare for read from server
in = new BufferedInputStream(conn.getInputStream());
// prepare for write to local disk
String fileName = "TempFile";
out = new BufferedOutputStream(new FileOutputStream(fileName));
int n;
// read from the server and write to local storage
byte data[] = new byte[1024];
while( (n = in.read(data)) != -1){
out.write(data, 0, n);
}
in.close();
out.close();
}
What we are doing in this code is, we are first opening up a connection to the url as provided by the user. Then assign an input stream to the connection just for reading the incomings. Next step is to read from the input stream assigned to the connection and write the data to output stream that points to a local file output. Simple enough huh.
Now lets move for implementing parallel streams. As mentioned in the review part firstly we are going to chop the total size of the file into different chunks, use the partial content indication method to notify the server, get those content in separate threads and finally merge those chunks into single.
package workingonnetworking;
import java.io.*;
import java.net.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DownloadManager {
private OutputStream[] r;
private final int totalChunks = 5;
private File[] tempFile;
public DownloadManager() throws FileNotFoundException{
r = new OutputStream[totalChunks];
tempFile = new File[totalChunks];
for (int i = 0 ; i < totalChunks ; i++){
tempFile[i] = new File("temp"+i);
r[i] = new BufferedOutputStream(new FileOutputStream(tempFile[i]));
}
}
public static void main(String[] args) throws IOException {
DownloadManager cc = new DownloadManager();
// cc.normal(http://localhost:31415/public_html/img/textile.jpg);
cc.parallel("http://localhost:31415/public_html/img/textile.jpg");
}
public void normal(String link) throws MalformedURLException, IOException{
BufferedInputStream in = null;
BufferedOutputStream out = null;
// connect to the server
URL url = new URL(link);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.connect();
// prepare for read from server
in = new BufferedInputStream(conn.getInputStream());
// prepare for write to local disk
String fileName = getFileName(link);
out = new BufferedOutputStream(new FileOutputStream(fileName));
int n;
// read from the server and write to local storage
byte data[] = new byte[1024];
while( (n = in.read(data)) != -1){
out.write(data, 0, n);
}
in.close();
out.close();
}
public void parallel(String link){
try {
// open up a connection to get total size of the file
URL url = new URL(link);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
int total = conn.getContentLength();
// connection array
HttpURLConnection[] con = new HttpURLConnection[totalChunks];
Minions[] m = new Minions[totalChunks];
// calculate bytes each stream has to download
int eachDownload = total/totalChunks;
String byteRange = "";
ExecutorService tE = Executors.newCachedThreadPool();
for (int i = 0 ; i < totalChunks ; i++){
// open the connection streams with byte range
byteRange = (eachDownload*i)+"-"+((i!=totalChunks-1)?(eachDownload*(i+1)-1):total);
con[i] = (HttpURLConnection) url.openConnection();
con[i].setRequestProperty("Range", "bytes="+byteRange);
con[i].connect();
// if server doesn't support partial content
if (con[i].getResponseCode() != 206){
System.out.println("Parallel Stream is not supported by the server");
this.normal(link);
break;
}
// Fire the threads to do the job.
m[i] = new Minions(con[i], r[i]);
tE.execute(m[i]);
}
tE.shutdown(); // no new threads are to be created
tE.awaitTermination(10000, TimeUnit.DAYS); // wait untill all the threads terminates
File dest = new File(getFileName(link));
String filePath = mergeFiles(dest, tempFile);
System.out.println("Download File Completed.");
System.out.println("File has been saved to " + filePath);
} catch (InterruptedException ex) {
Logger.getLogger(DownloadManager.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(DownloadManager.class.getName()).log(Level.SEVERE, null, ex);
}
}
private String mergeFiles(File dest, File[] files){
try {
byte[] reader = new byte[1024*5];
int n;
InputStream is;
OutputStream os;
os = new BufferedOutputStream(new FileOutputStream(dest));
for (File iter : files){
is = new BufferedInputStream(new FileInputStream(iter));
while((n = is.read(reader)) != -1){
os.write(reader, 0, n);
}
}
os.flush();
os.close();
} catch (IOException ex) {
Logger.getLogger(DownloadManager.class.getName()).log(Level.SEVERE, null, ex);
}
return dest.getAbsolutePath();
}
// get filename out of the link unable to find one returns 'DownloadedFile' as default
private String getFileName(String link){
String fileName = link.substring(link.lastIndexOf("/"), link.length());
if (fileName.isEmpty()) fileName = "DownloadedFile";
return fileName;
}
// these are the threads fetching each of chunks
private class Minions implements Runnable{
HttpURLConnection con;
OutputStream r;
public Minions(HttpURLConnection acon, OutputStream ar){
con = acon;
r = ar;
}
@Override
public void run(){
try {
BufferedInputStream in = new BufferedInputStream(con.getInputStream());
int numRead;
byte data[] = new byte[1024];
while( (numRead = in.read(data, 0, 1024)) != -1){
r.write(data, 0, numRead);
r.flush();
}
r.close();
} catch (IOException ex) {
Logger.getLogger(DownloadManager.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
Firstly, we are opening up a connection to the server just to get the total size of the file. Then divide the size to nearly equal chunks. Each chunks are assigned certain range of data. Then for each chunk, open a connection that is set to fetch his part of file ( range of file). Range property can be used to notify server that we are only interested on certain range of the file, so provide us only those range of bytes. This property can also be used for pause and resume of the download.
But not all the servers around, provides support for partial content so we better check the response code provided by the server after we set the Range. If the response code is not 206 ( Partial Content ) then we proceed with the normal download. Else fire the threads and wait for all the threads to complete. Finally merge up all those partial contents. And thats it you have your file on your local machine.
kada boro kada!!!
ReplyDelete