package org.limewire.core.impl;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.limewire.bittorrent.Torrent;
import org.limewire.bittorrent.TorrentAlert;
import org.limewire.bittorrent.TorrentEvent;
import org.limewire.bittorrent.TorrentFileEntry;
import org.limewire.bittorrent.TorrentInfo;
import org.limewire.bittorrent.TorrentPeer;
import org.limewire.bittorrent.TorrentPiecesInfo;
import org.limewire.bittorrent.TorrentStatus;
import org.limewire.bittorrent.TorrentTracker;
import org.limewire.io.InvalidDataException;
import org.limewire.listener.EventListener;
import org.limewire.security.SHA1;
import org.limewire.util.Base32;
import org.limewire.util.Objects;
import org.limewire.util.StringUtils;
import org.limewire.util.URIUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.limegroup.gnutella.xml.LimeXMLDocument;
import com.limegroup.gnutella.xml.LimeXMLNames;
/**
* A Torrent file implemented from xml data. This contains a very limited
* subset of information about the Torrent.
*/
public class XMLTorrent implements Torrent {
private final String name;
private final List<TorrentFileEntry> torrentFileEntries;
private final List<TorrentTracker> trackers;
private final boolean isPrivate;
private final String sha1;
private final long size;
private final List<URI> trackerUris;
public XMLTorrent(LimeXMLDocument xmlDocument) throws InvalidDataException {
this.name = parseTorrentName(xmlDocument);
this.torrentFileEntries = parsePathEntries(xmlDocument);
this.size = computeTotalSize(this.torrentFileEntries);
this.trackers = parseTrackers(xmlDocument.getValue(LimeXMLNames.TORRENT_TRACKERS));
this.trackerUris = getTrackerUris(this.trackers);
String privateValue = xmlDocument.getValue(LimeXMLNames.TORRENT_PRIVATE);
this.isPrivate = privateValue != null && Boolean.parseBoolean(privateValue);
String hash = xmlDocument.getValue(LimeXMLNames.TORRENT_INFO_HASH);
if(!StringUtils.isEmpty(hash)) {
byte[] bytes = Base32.decode(hash);
if (bytes.length != SHA1.HASH_LENGTH) {
throw new InvalidDataException("torrent xml with invalid hash: " + xmlDocument);
}
sha1 = StringUtils.toHexString(bytes);
} else {
throw new InvalidDataException("torrent xml without info hash: " + xmlDocument);
}
}
private static String parseTorrentName(LimeXMLDocument xmlDocument) throws InvalidDataException {
String name = xmlDocument.getValue(LimeXMLNames.TORRENT_NAME);
if (name == null) {
throw new InvalidDataException("torrent xml without name: " + xmlDocument);
}
return name;
}
private static long computeTotalSize(List<TorrentFileEntry> torrentFileEntries) {
long sum = 0;
for (TorrentFileEntry file : torrentFileEntries) {
sum += file.getSize();
}
return sum;
}
@Override
public long getTotalPayloadSize() {
return size;
}
@Override
public void addListener(EventListener<TorrentEvent> listener) {}
@Override
public void addTracker(String url, int tier) {
throw new UnsupportedOperationException();
}
@Override
public void forceReannounce() {
throw new UnsupportedOperationException();
}
@Override
public float getDownloadRate() {
return 0;
}
@Override
public File getFastResumeFile() {
return null;
}
@Override
public Lock getLock() {
return null;
}
@Override
public String getName() {
return name;
}
@Override
public int getNumConnections() {
return 0;
}
@Override
public int getNumPeers() {
return 0;
}
@Override
public int getNumUploads() {
return 0;
}
@Override
public TorrentPiecesInfo getPiecesInfo() {
return null;
}
@Override
public <T> T getProperty(String key, T defaultValue) {
return null;
}
@Override
public float getSeedRatio() {
return 0;
}
@Override
public String getSha1() {
return sha1;
}
@Override
public long getStartTime() {
return -1;
}
@Override
public TorrentStatus getStatus() {
return null;
}
@Override
public File getTorrentDataFile() {
return null;
}
@Override
public File getTorrentDataFile(TorrentFileEntry torrentFileEntry) {
return null;
}
@Override
public File getTorrentFile() {
return null;
}
@Override
public List<TorrentFileEntry> getTorrentFileEntries() {
return torrentFileEntries;
}
@Override
public TorrentInfo getTorrentInfo() {
return null;
}
@Override
public List<TorrentPeer> getTorrentPeers() {
return Collections.emptyList();
}
@Override
public long getTotalUploaded() {
return 0;
}
@Override
public List<URI> getTrackerURIS() {
return trackerUris;
}
@Override
public List<TorrentTracker> getTrackers() {
return trackers;
}
@Override
public float getUploadRate() {
return 0;
}
@Override
public void handleFastResumeAlert(TorrentAlert alert) {
throw new UnsupportedOperationException();
}
@Override
public boolean hasMetaData() {
return true;
}
@Override
public boolean isAutoManaged() {
throw new UnsupportedOperationException();
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isPaused() {
return false;
}
@Override
public boolean isPrivate() {
return isPrivate;
}
@Override
public boolean isStarted() {
return false;
}
@Override
public boolean isValid() {
return true;
}
@Override
public void moveTorrent(File directory) {
throw new UnsupportedOperationException();
}
@Override
public void pause() {
throw new UnsupportedOperationException();
}
@Override
public boolean removeListener(EventListener<TorrentEvent> listener) {
return false;
}
@Override
public void removeTracker(String url, int tier) {
throw new UnsupportedOperationException();
}
@Override
public void resume() {
throw new UnsupportedOperationException();
}
@Override
public void saveFastResumeData() {
throw new UnsupportedOperationException();
}
@Override
public void scrapeTracker() {
throw new UnsupportedOperationException();
}
@Override
public void setAutoManaged(boolean autoManaged) {
throw new UnsupportedOperationException();
}
@Override
public void setFastResumeFile(File fastResumeFile) {
throw new UnsupportedOperationException();
}
@Override
public void setProperty(String key, Object value) {
throw new UnsupportedOperationException();
}
@Override
public void setTorrenFileEntryPriority(TorrentFileEntry torrentFileEntry, int priority) {
throw new UnsupportedOperationException();
}
@Override
public void setTorrentFile(File torrentFile) {
throw new UnsupportedOperationException();
}
@Override
public void start() {
throw new UnsupportedOperationException();
}
@Override
public void stop() {
throw new UnsupportedOperationException();
}
@Override
public void updateStatus(TorrentStatus torrentStatus) {
throw new UnsupportedOperationException();
}
@Override
public boolean isEditable() {
return false;
}
@Override
public int getMaxDownloadBandwidth() {
return -1;
}
@Override
public int getMaxUploadBandwidth() {
return -1;
}
@Override
public void setMaxDownloadBandwidth(int value) {
throw new UnsupportedOperationException();
}
@Override
public void setMaxUploadBandwidth(int value) {
throw new UnsupportedOperationException();
}
private static List<TorrentFileEntry> parsePathEntries(LimeXMLDocument xmlDocument) throws InvalidDataException {
String encodedPath = xmlDocument.getValue(LimeXMLNames.TORRENT_FILE_PATHS);
String encodedSizes = xmlDocument.getValue(LimeXMLNames.TORRENT_FILE_SIZES);
if (encodedPath == null || encodedSizes == null) {
String name = xmlDocument.getValue(LimeXMLNames.TORRENT_NAME);
String length = xmlDocument.getValue(LimeXMLNames.TORRENT_LENGTH);
if (name != null && length != null) {
try {
long size = Long.parseLong(length);
return Collections.<TorrentFileEntry>singletonList(new LimeXMLTorrentFileEntry(name, size, 0));
} catch (NumberFormatException nfe) {
throw new InvalidDataException("torrent xml with invalid length: " + xmlDocument, nfe);
}
}
throw new InvalidDataException("torrent xml without files: " + xmlDocument);
}
String[] paths = encodedPath.split("//");
String[] sizes = encodedSizes.split(" ");
if (paths.length != sizes.length) {
throw new InvalidDataException("torrent xml with invalid files and sizes: " + xmlDocument);
}
List<TorrentFileEntry> entries = new ArrayList<TorrentFileEntry>(paths.length);
for (int i = 0; i < paths.length; i++) {
try {
String path = paths[i];
if (StringUtils.isEmpty(path)) {
throw new InvalidDataException("torrent xml with empty path: " + xmlDocument);
}
entries.add(new LimeXMLTorrentFileEntry(paths[i].substring(1), Long.parseLong(sizes[i]), i));
} catch (NumberFormatException nfe){
throw new InvalidDataException("torrent xml with invalid file size: " + xmlDocument);
}
}
return Collections.unmodifiableList(entries);
}
private static List<TorrentTracker> parseTrackers(String encodedTrackers) throws InvalidDataException {
if (encodedTrackers == null) {
return Collections.emptyList();
}
Builder<TorrentTracker> builder = ImmutableList.builder();
for (String tracker : encodedTrackers.split(" ")) {
try {
URI uri = URIUtils.toURI(tracker);
builder.add(new LimeXMLTorrentTracker(uri));
} catch (URISyntaxException use) {
throw new InvalidDataException("torrent xml with invalid tracker: " + encodedTrackers, use);
}
}
return builder.build();
}
private static List<URI> getTrackerUris(List<TorrentTracker> trackers) {
Builder<URI> builder = ImmutableList.builder();
for (TorrentTracker tracker : trackers) {
builder.add(tracker.getURI());
}
return builder.build();
}
private static class LimeXMLTorrentTracker implements TorrentTracker {
private final URI trackerUri;
public LimeXMLTorrentTracker(URI trackerUri) {
this.trackerUri = Objects.nonNull(trackerUri, "trackerUri");
}
@Override
public int getTier() {
return 0;
}
@Override
public String toString() {
return trackerUri.toString();
}
@Override
public URI getURI() {
return trackerUri;
}
}
private static class LimeXMLTorrentFileEntry implements TorrentFileEntry {
private final String path;
private final long size;
private final int index;
public LimeXMLTorrentFileEntry(String path, long size, int index) throws InvalidDataException {
if (StringUtils.isEmpty(path)) {
throw new InvalidDataException("empty path for index: " + index);
}
this.path = path;
this.size = size;
this.index = index;
}
@Override
public int getIndex() {
return index;
}
@Override
public String getPath() {
return path;
}
@Override
public int getPriority() {
return 0;
}
@Override
public float getProgress() {
return 0;
}
@Override
public long getSize() {
return size;
}
@Override
public long getTotalDone() {
return 0;
}
@Override
public String toString() {
return path;
}
}
@Override
public String toString() {
return StringUtils.toString(this);
}
}