![JAR search and dependency download from the Maven repository](/logo.png)
org.libtorrent4j.TorrentInfo Maven / Gradle / Ivy
Show all versions of libtorrent4j Show documentation
/*
* Copyright (c) 2018-2021, Alden Torres
*
* Licensed under the terms of the MIT license.
* Copy of the license at https://opensource.org/licenses/MIT
*/
package org.libtorrent4j;
import org.libtorrent4j.swig.*;
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.
*
* 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.
*
* 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.
*
* 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()}.
*
* 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)}.
*
* 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.
*
* 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.
*
* 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 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.
*
* BEP38: http://www.bittorrent.org/beps/bep_0038.html
*
*
*/
public ArrayList 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.
*
* BEP38: http://www.bittorrent.org/beps/bep_0038.html
*
*
*/
public ArrayList collections() {
string_vector v = ti.collections();
int size = (int) v.size();
ArrayList 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.
*
* 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").
*
* 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.
*
* 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").
*
* 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> extraHeaders) {
string_string_pair_vector v = new string_string_pair_vector();
for (Pair p : extraHeaders) {
v.add(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.
*
* 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.
*
* 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").
*
* 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> extraHeaders) {
string_string_pair_vector v = new string_string_pair_vector();
for (Pair p : extraHeaders) {
v.add(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 webSeeds() {
web_seed_entry_vector v = ti.web_seeds();
int size = (int) v.size();
ArrayList 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 seeds) {
web_seed_entry_vector v = new web_seed_entry_vector();
for (WebSeedEntry e : seeds) {
v.add(e.swig());
}
ti.set_web_seeds(v);
}
/**
* The total number of bytes the torrent-file represents (all the files in it).
*/
public long totalSize() {
return ti.total_size();
}
/**
* Returns the sum of all non-pad file sizes. i.e. the files that will
* actually be saved to disk by this torrent.
*/
public long sizeOnDisk() {
return ti.size_on_disk();
}
/**
* The number of byte for each piece.
*
* 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.
*
*
*/
public int pieceLength() {
return ti.piece_length();
}
/**
* The total number of pieces.
*
*
*/
public int numPieces() {
return ti.num_pieces();
}
/**
* Returns the info-hash of the torrent.
*
* For BitTorrent v2 support, use `infoHash()` to get an object that
* may hold both a v1 and v2 info-hash.
*/
public Sha1Hash infoHash() {
return new Sha1Hash(ti.info_hash());
}
/**
* Returns an object that may hold both a v1 and v2 info-hash.
*/
public InfoHash infoHashes() {
return new InfoHash(ti.info_hashes());
}
/**
* Returns whether this torrent has v1 metadata.
*
* Hybrid torrents have both. This is a shortcut for
* `infoHashes().hasV1()`.
*/
public boolean hasV1() {
return ti.v1();
}
/**
* Returns whether this torrent has v2 metadata.
*
* Hybrid torrents have both. This is a shortcut for
* `infoHashes().hasV2()`.
*/
public boolean hasV2() {
return ti.v2();
}
/**
* If you need index-access to files you can use this method
* to access files using indices.
*
*
*/
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
*
* @see FileSlice
*/
public ArrayList 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.
*
* 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
*
* @see PeerRequest
*/
public PeerRequest mapFile(int file, long offset, int size) {
return new PeerRequest(ti.map_file(file, offset, size));
}
/**
* Returns true if this torrent_info object has a torrent loaded.
*
* This is primarily used to determine if a magnet link has had its
* metadata resolved yet or not.
*
*
*/
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).
*
*
*/
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.
*
*
*/
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
*
*/
public Sha1Hash hashForPiece(int index) {
return new Sha1Hash(ti.hash_for_piece(index));
}
public boolean isLoaded() {
return ti.is_loaded();
}
/**
* returns the name of the torrent.
*
* the name is an UTF-8 encoded strings.
*
*
*/
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.
*
* 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).
*
*
*/
public ArrayList> nodes() {
string_int_pair_vector v = ti.nodes();
int size = (int) v.size();
ArrayList> 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
*
*/
public bdecode_node info(String key) {
return ti.info(key);
}
/**
* Clears the piece layers from the torrent_info. This is done by the
* session when a torrent is added, to avoid storing it twice. The piece
* layer (or other hashes part of the merkle tree) are stored in the
* internal torrent object.
*/
public void freePieceLayers() {
ti.free_piece_layers();
}
/**
* Generates a magnet URI from the specified torrent. If the torrent
* is invalid, null is returned.
*
* For more information about magnet links, see magnet-links_.
*
*
*/
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 trackers(announce_entry_vector v) {
int size = (int) v.size();
ArrayList 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());
}
}
}