TubeLab-App-Android/frostwire-jlibtorrent/src/main/java/com/frostwire/jlibtorrent/TorrentInfo.java

710 lines
22 KiB
Java

package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.announce_entry_vector;
import com.frostwire.jlibtorrent.swig.bdecode_node;
import com.frostwire.jlibtorrent.swig.byte_vector;
import com.frostwire.jlibtorrent.swig.create_torrent;
import com.frostwire.jlibtorrent.swig.error_code;
import com.frostwire.jlibtorrent.swig.libtorrent;
import com.frostwire.jlibtorrent.swig.libtorrent_jni;
import com.frostwire.jlibtorrent.swig.sha1_hash_vector;
import com.frostwire.jlibtorrent.swig.string_int_pair;
import com.frostwire.jlibtorrent.swig.string_int_pair_vector;
import com.frostwire.jlibtorrent.swig.string_string_pair_vector;
import com.frostwire.jlibtorrent.swig.string_vector;
import com.frostwire.jlibtorrent.swig.torrent_info;
import com.frostwire.jlibtorrent.swig.web_seed_entry_vector;
import java.io.File;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* This class represents the information stored in a .torrent file
*
* @author gubatron
* @author aldenml
*/
public final class TorrentInfo {
private final torrent_info ti;
public TorrentInfo(torrent_info ti) {
this.ti = ti;
}
/**
* Load the torrent file and decode it inside the constructor, for convenience.
* <p>
* This might not be the most suitable for applications that
* want to be able to report detailed errors on what might go wrong.
*
* @param torrent the torrent file
*/
public TorrentInfo(File torrent) {
this(bdecode0(torrent));
}
/**
* Load the torrent data and decode it inside the constructor, for convenience.
* <p>
* This might not be the most suitable for applications that
* want to be able to report detailed errors on what might go wrong.
*
* @param data the torrent data
*/
public TorrentInfo(byte[] data) {
this(bdecode0(data));
}
public TorrentInfo(MappedByteBuffer buffer) {
try {
long ptr = libtorrent_jni.directBufferAddress(buffer);
long size = libtorrent_jni.directBufferCapacity(buffer);
error_code ec = new error_code();
this.ti = new torrent_info(ptr, (int) size, ec);
if (ec.value() != 0) {
throw new IllegalArgumentException("Can't decode data: " + ec.message());
}
} catch (Throwable e) {
throw new IllegalArgumentException("Can't decode data mapped buffer: " + e.getMessage(), e);
}
}
/**
* @return the native object
*/
public torrent_info swig() {
return this.ti;
}
/**
* The {@link FileStorage} object contains the information on
* how to map the pieces to files.
* <p>
* It is separated from the {@link TorrentInfo} object because when creating torrents
* a storage object needs to be created without having a torrent file. When renaming files
* in a storage, the storage needs to make its own copy of the {@link FileStorage} in order
* to make its mapping differ from the one in the torrent file.
*
* @return the files storage
*/
public FileStorage files() {
return new FileStorage(ti.files(), ti);
}
/**
* Returns the original (unmodified) file storage for this torrent. This
* is used by the web server connection, which needs to request files with the original
* names. Filename may be changed using {@link #renameFile(int, String)}.
*
* @return the original file storage
*/
public FileStorage origFiles() {
return new FileStorage(ti.orig_files(), ti);
}
/**
* Renames a the file with the specified index to the new name. The new
* filename is reflected by the {@link FileStorage} returned by {@link #files()}
* but not by the one returned by {@link #origFiles()}.
* <p>
* If you want to rename the base name of the torrent (for a multifile
* torrent), you can copy the {@code FileStorage} (see {@link #files()} and
* {@link #origFiles()} ), change the name, and then use
* {@link #remapFiles(FileStorage)}.
* <p>
* The {@code newFilename} can both be a relative path, in which case the
* file name is relative to the {@code savePath} of the torrent. If the
* {@code newFilename} is an absolute path then the file is detached from
* the {@code savePath} of the torrent. In this case the file is not moved when
* {@link TorrentHandle#moveStorage(String, MoveFlags)} is invoked.
*
* @param index the file index to rename
* @param newFilename the new file name
*/
public void renameFile(int index, String newFilename) {
ti.rename_file(index, newFilename);
}
/**
* Remaps the file storage to a new file layout. This can be used to, for
* instance, download all data in a torrent to a single file, or to a
* number of fixed size sector aligned files, regardless of the number
* and sizes of the files in the torrent.
* <p>
* The new specified {@link FileStorage} must have the exact same size as
* the current one.
*
* @param f the file storage
*/
public void remapFiles(FileStorage f) {
ti.remap_files(f.swig());
}
/**
* Adds a tracker to the announce-list.
*
* @param url the tracker url
*/
public void addTracker(String url) {
ti.add_tracker(url);
}
/**
* Adds a tracker to the announce-list. The {@code tier} determines the order in
* which the trackers are to be tried.
*
* @param url the tracker url
* @param tier the tracker tier
*/
public void addTracker(String url, int tier) {
ti.add_tracker(url, tier);
}
/**
* Will return a sorted list with the trackers of this torrent info.
* <p>
* Each announce entry contains a string, which is the tracker url, and a tier index. The
* tier index is the high-level priority. No matter which trackers that works or not, the
* ones with lower tier will always be tried before the one with higher tier number.
*
* @return the list of trackers
*/
public ArrayList<AnnounceEntry> trackers() {
return trackers(ti.trackers());
}
/**
* This function is related to BEP38_ (mutable torrents). The
* vector returned from this correspond to the "similar" in the
* .torrent file. The info-hashes from within the info-dict
* and from outside of it are included.
* <p>
* BEP38: http://www.bittorrent.org/beps/bep_0038.html
*
* @return
*/
public ArrayList<Sha1Hash> similarTorrents() {
return Sha1Hash.convert(ti.similar_torrents());
}
/**
* This function is related to BEP38_ (mutable torrents). The
* vector returned from this correspond to the "collections" keys
* in the .torrent file. The collections from within the info-dict
* and from outside of it are included.
* <p>
* BEP38: http://www.bittorrent.org/beps/bep_0038.html
*
* @return
*/
public ArrayList<String> collections() {
string_vector v = ti.collections();
int size = (int) v.size();
ArrayList<String> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
l.add(v.get(i));
}
return l;
}
/**
* Clear the internal list of trackers.
*/
public void clearTrackers() {
ti.trackers().clear();
}
/**
* Adds one url to the list of url seeds. Currently, the only transport protocol
* supported for the url is http.
*
* @param url
* @see #addHttpSeed(String, String)
*/
public void addUrlSeed(String url) {
ti.add_url_seed(url);
}
/**
* Adds one url to the list of url seeds. Currently, the only transport protocol
* supported for the url is http.
* <p>
* The {@code externAuth} argument can be used for other authorization schemes than
* basic HTTP authorization. If set, it will override any username and password
* found in the URL itself. The string will be sent as the HTTP authorization header's
* value (without specifying "Basic").
* <p>
* This is the same as calling {@link #addUrlSeed(String, String, List)} with an
* empty list.
*
* @param url
* @param externAuth
*/
public void addUrlSeed(String url, String externAuth) {
ti.add_url_seed(url, externAuth);
}
/**
* Adds one url to the list of url seeds. Currently, the only transport protocol
* supported for the url is http.
* <p>
* The {@code externAuth} argument can be used for other authorization schemes than
* basic HTTP authorization. If set, it will override any username and password
* found in the URL itself. The string will be sent as the HTTP authorization header's
* value (without specifying "Basic").
* <p>
* The {@code extraHeaders} argument can be used to insert custom HTTP headers
* in the requests to a specific web seed.
*
* @param url
* @param externAuth
* @param extraHeaders
*/
public void addUrlSeed(String url, String externAuth, List<Pair<String, String>> extraHeaders) {
string_string_pair_vector v = new string_string_pair_vector();
for (Pair<String, String> p : extraHeaders) {
v.push_back(p.to_string_string_pair());
}
ti.add_url_seed(url, externAuth, v);
}
/**
* Adds one url to the list of http seeds. Currently, the only transport protocol supported for the url
* is http.
*
* @param url
*/
public void addHttpSeed(String url) {
ti.add_url_seed(url);
}
/**
* Adds one url to the list of http seeds. Currently, the only transport protocol supported for the url
* is http.
* <p>
* The {@code externAuth} argument can be used for other authorization schemes than
* basic HTTP authorization. If set, it will override any username and password
* found in the URL itself. The string will be sent as the HTTP authorization header's
* value (without specifying "Basic").
*
* @param url
* @param externAuth
*/
public void addHttpSeed(String url, String externAuth) {
ti.add_url_seed(url, externAuth);
}
/**
* Adds one url to the list of http seeds. Currently, the only transport protocol supported
* for the url is http.
* <p>
* The {@code externAuth} argument can be used for other authorization schemes than
* basic HTTP authorization. If set, it will override any username and password
* found in the URL itself. The string will be sent as the HTTP authorization header's
* value (without specifying "Basic").
* <p>
* The {@code extraHeaders} argument defaults to an empty list, but can be used to
* insert custom HTTP headers in the requests to a specific web seed.
*
* @param url
* @param externAuth
* @param extraHeaders
*/
public void addHttpSeed(String url, String externAuth, List<Pair<String, String>> extraHeaders) {
string_string_pair_vector v = new string_string_pair_vector();
for (Pair<String, String> p : extraHeaders) {
v.push_back(p.to_string_string_pair());
}
ti.add_url_seed(url, externAuth, v);
}
/**
* Returns all url seeds and http seeds in the torrent. Each entry
* is a {@link WebSeedEntry} and may refer to either a url seed or http seed.
*
* @return the list of web seeds
*/
public ArrayList<WebSeedEntry> webSeeds() {
web_seed_entry_vector v = ti.web_seeds();
int size = (int) v.size();
ArrayList<WebSeedEntry> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
l.add(new WebSeedEntry(v.get(i)));
}
return l;
}
/**
* Replaces all web seeds with the ones specified in the
* {@code seeds} list.
*
* @param seeds the list of web seeds
*/
public void setWebSeeds(List<WebSeedEntry> seeds) {
web_seed_entry_vector v = new web_seed_entry_vector();
for (WebSeedEntry e : seeds) {
v.push_back(e.swig());
}
ti.set_web_seeds(v);
}
/**
* The total number of bytes the torrent-file represents (all the files in it).
*
* @return
*/
public long totalSize() {
return ti.total_size();
}
/**
* The number of byte for each piece.
* <p>
* The difference between {@link #pieceSize(int)} and {@link #pieceLength()} is that
* {@link #pieceSize(int)} takes the piece index as argument and gives you the exact size
* of that piece. It will always be the same as {@link #pieceLength()} except in the case
* of the last piece, which may be smaller.
*
* @return
*/
public int pieceLength() {
return ti.piece_length();
}
/**
* The total number of pieces.
*
* @return
*/
public int numPieces() {
return ti.num_pieces();
}
/**
* returns the info-hash of the torrent.
*
* @return
*/
public Sha1Hash infoHash() {
return new Sha1Hash(ti.info_hash());
}
/**
* If you need index-access to files you can use this method
* to access files using indices.
*
* @return
*/
public int numFiles() {
return ti.num_files();
}
/**
* This function will map a piece index, a byte offset within that piece and
* a size (in bytes) into the corresponding files with offsets where that data
* for that piece is supposed to be stored.
*
* @param piece
* @param offset
* @param size
* @return
* @see com.frostwire.jlibtorrent.FileSlice
*/
public ArrayList<FileSlice> mapBlock(int piece, long offset, int size) {
return FileStorage.mapBlock(ti.map_block(piece, offset, size));
}
/**
* This function will map a range in a specific file into a range in the torrent.
* The {@code offset} parameter is the offset in the file, given in bytes, where
* 0 is the start of the file.
* <p>
* The input range is assumed to be valid within the torrent. {@code offset + size}
* is not allowed to be greater than the file size. {@code index}
* must refer to a valid file, i.e. it cannot be {@code >= numFiles()}.
*
* @param file
* @param offset
* @param size
* @return
* @see com.frostwire.jlibtorrent.PeerRequest
*/
public PeerRequest mapFile(int file, long offset, int size) {
return new PeerRequest(ti.map_file(file, offset, size));
}
/**
* Returns the SSL root certificate for the torrent, if it is an SSL
* torrent. Otherwise returns an empty string. The certificate is
* the the public certificate in x509 format.
*
* @return the cert as a byte array
*/
byte[] sslCert() {
byte_vector v = ti.ssl_cert().to_bytes();
return Vectors.byte_vector2bytes(v);
}
/**
* Returns true if this torrent_info object has a torrent loaded.
* <p>
* This is primarily used to determine if a magnet link has had its
* metadata resolved yet or not.
*
* @return
*/
public boolean isValid() {
return ti.is_valid();
}
/**
* Returns true if this torrent is private. i.e., it should not be
* distributed on the trackerless network (the kademlia DHT).
*
* @return
*/
public boolean isPrivate() {
return ti.priv();
}
/**
* Returns true if this is an i2p torrent. This is determined by whether
* or not it has a tracker whose URL domain name ends with ".i2p". i2p
* torrents disable the DHT and local peer discovery as well as talking
* to peers over anything other than the i2p network.
*
* @return
*/
public boolean isI2p() {
return ti.is_i2p();
}
public int pieceSize(int index) {
return ti.piece_size(index);
}
/**
* takes a piece-index and returns the 20-bytes sha1-hash for that
* piece and ``info_hash()`` returns the 20-bytes sha1-hash for the info-section of the
* torrent file.
*
* @param index
* @return
*/
public Sha1Hash hashForPiece(int index) {
return new Sha1Hash(ti.hash_for_piece(index));
}
public boolean isLoaded() {
return ti.is_loaded();
}
/**
* Returns a copy to the merkle tree for this
* torrent, if any.
*
* @return
*/
public ArrayList<Sha1Hash> merkleTree() {
return Sha1Hash.convert(ti.merkle_tree());
}
/**
* Copies the passed in merkle tree into the torrent info object.
* <p>
* You need to set the merkle tree for a torrent that you've just created
* (as a merkle torrent). The merkle tree is retrieved from the
* {@link #merkleTree()} function, and need to be saved
* separately from the torrent file itself. Once it's added to
* libtorrent, the merkle tree will be persisted in the resume data.
*
* @param tree
*/
public void merkleTree(List<Sha1Hash> tree) {
sha1_hash_vector v = new sha1_hash_vector();
for (Sha1Hash h : tree) {
v.push_back(h.swig());
}
ti.set_merkle_tree(v);
}
/**
* returns the name of the torrent.
* <p>
* the name is an UTF-8 encoded strings.
*
* @return
*/
public String name() {
return ti.name();
}
/**
* Returns the creation date of he torrent as time_t (`posix time`_).
* If there's no time stamp in the torrent file,
* a value of zero is returned.
*
* @return the time
*/
public long creationDate() {
return ti.creation_date();
}
/**
* Returns the creator string in the torrent. If there is no creator string
* it will return an empty string.
*
* @return the creator
*/
public String creator() {
return ti.creator();
}
/**
* Returns the comment associated with the torrent. If there's no comment,
* it will return an empty string.
* <p>
* The comment is an UTF-8 encoded strings.
*
* @return the comment
*/
public String comment() {
return ti.comment();
}
/**
* If this torrent contains any DHT nodes, they are returned in
* their original form (host name and port number).
*
* @return
*/
public ArrayList<Pair<String, Integer>> nodes() {
string_int_pair_vector v = ti.nodes();
int size = (int) v.size();
ArrayList<Pair<String, Integer>> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
string_int_pair p = v.get(i);
l.add(new Pair<>(p.getFirst(), p.getSecond()));
}
return l;
}
/**
* This is used when creating torrent. Use this to add a known DHT node.
* It may be used, by the client, to bootstrap into the DHT network.
*
* @param host
* @param port
*/
public void addNode(String host, int port) {
ti.add_node(new string_int_pair(host, port));
}
/**
* This function looks up keys from the info-dictionary of the loaded
* torrent file. It can be used to access extension values put in the
* .torrent file. If the specified key cannot be found, it returns NULL.
*
* @param key
* @return
*/
public bdecode_node info(String key) {
return ti.info(key);
}
/**
* Returns whether or not this is a merkle torrent.
* See BEP30: http://bittorrent.org/beps/bep_0030.html
*
* @return
*/
public boolean isMerkleTorrent() {
return ti.is_merkle_torrent();
}
/**
* Generates a magnet URI from the specified torrent. If the torrent
* is invalid, null is returned.
* <p>
* For more information about magnet links, see magnet-links_.
*
* @return
*/
public String makeMagnetUri() {
return ti.is_valid() ? libtorrent.make_magnet_uri(ti) : null;
}
public Entry toEntry() {
return new Entry(new create_torrent(ti).generate());
}
public byte[] bencode() {
return toEntry().bencode();
}
public static TorrentInfo bdecode(byte[] data) {
return new TorrentInfo(bdecode0(data));
}
// helper function
static ArrayList<AnnounceEntry> trackers(announce_entry_vector v) {
int size = (int) v.size();
ArrayList<AnnounceEntry> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
l.add(new AnnounceEntry(v.get(i)));
}
return l;
}
private static torrent_info bdecode0(File file) {
try {
byte[] data = Files.bytes(file);
return bdecode0(data);
} catch (IOException e) {
throw new IllegalArgumentException("Can't decode data from file: " + file, e);
}
}
private static torrent_info bdecode0(byte[] data) {
byte_vector buffer = Vectors.bytes2byte_vector(data);
bdecode_node n = new bdecode_node();
error_code ec = new error_code();
int ret = bdecode_node.bdecode(buffer, n, ec);
if (ret == 0) {
ec.clear();
torrent_info ti = new torrent_info(n, ec);
buffer.clear(); // prevents GC
if (ec.value() != 0) {
throw new IllegalArgumentException("Can't decode data: " + ec.message());
}
return ti;
} else {
throw new IllegalArgumentException("Can't decode data: " + ec.message());
}
}
}