/*
* Used to downloading pieces from a server. This is done by issuing a range
* request to server.
*/
package p2pp;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.klomp.snark.MetaInfo;
public class WebSeed {
// Descriptive name of the web seed class.
public final static String description = "web seed";
public static boolean isWebSeed(String str) {
return str != null ? str.equals(WebSeed.description) : false;
}
private String serverUrl;
// The absolute url to all the files.
private List<URL> files;
// List of file lengths.
private List<Long> lengths;
private Random random = new Random();
protected static final Logger log = Logger.getLogger("org.klomp.snark.peer");
public WebSeed(List<URL> files, List<Long> lengths) {
if(files.size() != lengths.size())
throw new IllegalArgumentException(
"The length list must match the files list!");
this.files = files;
this.lengths = lengths;
this.serverUrl = files.get(0).toString();
}
/**
* Creates the file urls by using the url to the server and the information
* from the metainfo. Assuming the torrent file has a name attribute 'movie',
* the files will be downloaded locally in the movie/ dir. If useNameInPath
* is set to true the name attribute is used to construct the urls, which
* gives the urls like 'http://ge.tt/#6aYJ5/movie/...'.
* @param url The server url. E.g. 'http://ge.tt/#6aYJ5'.
* @param metainfo The torrent file information.
* @param useNameInPath If set to true the name attribute in the torrent
* file is used to create the file urls.
* @throws MalformedURLException If the serverUrl is invalid.
*/
@SuppressWarnings("unchecked")
public WebSeed(String url, MetaInfo metainfo, boolean useNameInPath)
throws MalformedURLException {
this.serverUrl = url;
this.lengths = metainfo.getLengths();
if(this.lengths == null) {
this.lengths = new ArrayList<Long>();
this.lengths.add(metainfo.getTotalLength());
}
this.files = makeUrls(url, metainfo, useNameInPath);
for(URL u : files)
System.out.println(u);
if(!this.files.get(0).getProtocol().toLowerCase().equals("http"))
throw new IllegalArgumentException("Must use http protocol!");
}
public WebSeed(String url, MetaInfo metainfo)
throws MalformedURLException {
this(url, metainfo, true);
}
public WebSeed(String url, long length) throws MalformedURLException {
this.files = new ArrayList<URL>();
this.files.add(new URL(url));
if(!this.files.get(0).getProtocol().toLowerCase().equals("http"))
throw new IllegalArgumentException("Must use http protocol!");
this.lengths = new ArrayList<Long>();
this.lengths.add(length);
this.serverUrl = url;
}
@Override
public String toString() {
return serverUrl;
}
// Returns true if the server supports range requests.
public boolean supportsRange() {
URL server = files.get(random.nextInt(files.size()));
try {
HttpURLConnection connection = (HttpURLConnection) server.openConnection();
connection.setRequestMethod("HEAD");
connection.connect();
String accepts = connection.getHeaderField("Accept-Ranges");
if(accepts != null)
return accepts.trim().toLowerCase().equals("bytes");
}
catch (IOException e) {
e.printStackTrace();
}
return false;
}
// Returns the byte block specified by the index and the length.
public byte[] getBlock(long index, int length) throws IOException {
long start = index;
int file = 0;
long fileLength = lengths.get(file);
while(start > fileLength) {
file++;
start -= fileLength;
fileLength = lengths.get(file);
}
int read = 0;
byte[] block = new byte[length];
while(read < length) {
int need = length - read;
int next = start + need < fileLength ? need : (int) (fileLength - start);
getBlock(this.files.get(file), start, read, next, block);
read += next;
if(need - next > 0) {
file++;
fileLength = lengths.get(file);
start = 0;
}
}
return block;
}
private void getBlock(URL url, long index, int off, int length, byte[] bs)
throws IOException {
long end = index + length - 1;
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Range", "bytes=" + index + "-" + end);
connection.connect();
int contentLength = connection.getContentLength();
String contentRange = connection.getHeaderField("Content-Range");
if(connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL &&
contentRange != null) {
long[] range = parseContentRange(contentRange);
if(contentLength != length || (range[0] != index && range[1] != end))
throw new IOException("Wrong length or range received from server!");
}
else
throw new IOException("Not a HTTP partial response received from server!");
BufferedInputStream in =
new BufferedInputStream(connection.getInputStream());
int len = 0;
while((len = in.read(bs, off, length)) != -1 && length > 0) {
off += len;
length -= len;
}
in.close();
}
private List<URL> makeUrls(String server, MetaInfo meta, boolean useName)
throws MalformedURLException {
List<URL> urls = new ArrayList<URL>();
if(!server.endsWith("/"))
server += "/";
String base = server;
if(useName)
base += meta.getName() + "/";
@SuppressWarnings("unchecked")
List<List<String>> files = meta.getFiles();
if(files == null)
urls.add(new URL(base.substring(0, base.length() - 1)));
else {
for(List<String> path : files) {
Iterator<String> it = path.iterator();
String fullPath = base;
while(it.hasNext()) {
String current = it.next();
//if(it.hasNext())
fullPath += current + "/";
//else
// fullPath += current + "/";
}
urls.add(new URL(fullPath.substring(0, fullPath.length() - 1)));
}
}
for(URL url : urls)
log.log(Level.INFO, "Server url: " + url.toString());
return urls;
}
private long[] parseContentRange(String contentRange) {
String[] range = contentRange.split(" ")[1].split("/")[0].split("-");
if(range.length != 2)
return new long[2];
try {
long[] res = {Long.parseLong(range[0]), Long.parseLong(range[1])};
return res;
}
catch(NumberFormatException e) {
e.printStackTrace();
}
return new long[2];
}
}