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

669 lines
18 KiB
Java

package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.add_files_listener;
import com.frostwire.jlibtorrent.swig.create_flags_t;
import com.frostwire.jlibtorrent.swig.create_torrent;
import com.frostwire.jlibtorrent.swig.error_code;
import com.frostwire.jlibtorrent.swig.file_storage;
import com.frostwire.jlibtorrent.swig.set_piece_hashes_listener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import static com.frostwire.jlibtorrent.swig.libtorrent.add_files_ex;
import static com.frostwire.jlibtorrent.swig.libtorrent.set_piece_hashes_ex;
/**
* @author gubatron
* @author aldenml
*/
public final class TorrentBuilder {
private File path;
private int pieceSize;
private int padFileLimit;
private create_flags_t flags;
private int alignment;
private String comment;
private String creator;
/**
* This will insert pad files to align the files to piece boundaries, for
* optimized disk-I/O. This will minimize the number of bytes of pad-
* files, to keep the impact down for clients that don't support
* them.
*/
public static final create_flags_t OPTIMIZE_ALIGNMENT = create_torrent.optimize_alignment;
/**
* This will create a merkle hash tree torrent. A merkle torrent cannot
* be opened in clients that don't specifically support merkle torrents.
* The benefit is that the resulting torrent file will be much smaller and
* not grow with more pieces. When this option is specified, it is
* recommended to have a fairly small piece size, say 64 kiB.
* When creating merkle torrents, the full hash tree is also generated
* and should be saved off separately.
*/
public static final create_flags_t MERKLE = create_torrent.merkle;
/**
* This will include the file modification time as part of the torrent.
* This is not enabled by default, as it might cause problems when you
* create a torrent from separate files with the same content, hoping to
* yield the same info-hash. If the files have different modification times,
* with this option enabled, you would get different info-hashes for the
* files.
*/
public static final create_flags_t MODIFICATION_TIME = create_torrent.modification_time;
/**
* If this flag is set, files that are symlinks get a symlink attribute
* set on them and their data will not be included in the torrent. This
* is useful if you need to reconstruct a file hierarchy which contains
* symlinks.
*/
public static final create_flags_t SYMLINKS = create_torrent.symlinks;
private boolean priv;
/**
* To create a torrent that can be updated via a *mutable torrent*
* (see BEP38_). This also needs to be enabled for torrents that update
* another torrent.
* <p>
* BEP38: http://www.bittorrent.org/beps/bep_0038.html
*/
public static final create_flags_t MUTABLE_TORRENT_SUPPORT = create_torrent.mutable_torrent_support;
private final List<String> urlSeeds;
private Listener listener;
public TorrentBuilder() {
this.pieceSize = 0;
this.padFileLimit = -1;
this.flags = OPTIMIZE_ALIGNMENT;
this.alignment = -1;
this.urlSeeds = new LinkedList<>();
this.httpSeeds = new LinkedList<>();
this.nodes = new LinkedList<>();
this.trackers = new LinkedList<>();
this.similarTorrents = new LinkedList<>();
this.collections = new LinkedList<>();
}
/**
* @return
*/
public File path() {
return path;
}
/**
* Adds the file specified by {@code value}
*
* @param value
* @return
*/
public TorrentBuilder path(File value) {
this.path = value;
return this;
}
/**
* @return
*/
public int pieceSize() {
return pieceSize;
}
/**
* The size of each piece in bytes. It must
* be a multiple of 16 kiB. If a piece size of 0 is specified, a
* {@code pieceSize} will be calculated such that the torrent file is roughly 40 kB.
*
* @param value
* @return
*/
public TorrentBuilder pieceSize(int value) {
this.pieceSize = value;
return this;
}
/**
* @return
*/
public int padFileLimit() {
return padFileLimit;
}
/**
* If a ``pad_size_limit`` is specified (other than -1), any file larger than
* the specified number of bytes will be preceded by a pad file to align it
* with the start of a piece. The pad_file_limit is ignored unless the
* {@link OPTIMIZE_ALIGNMENT} flag is passed. Typically it doesn't make sense
* to set this any lower than 4kiB.
*
* @param value
* @return
*/
public TorrentBuilder padFileLimit(int value) {
this.padFileLimit = value;
return this;
}
/**
* @return
*/
public create_flags_t flags() {
return flags;
}
/**
* Specifies options for the torrent creation. It can
* be any combination of the flags defined by {@link create_flags_t}
*
* @param value
* @return
*/
public TorrentBuilder flags(create_flags_t value) {
this.flags = value;
return this;
}
/**
* @return
*/
public int alignment() {
return alignment;
}
/**
* Used when pad files are enabled. This is the size
* eligible files are aligned to. The default is -1, which means the
* piece size of the torrent.
*
* @param value
* @return
*/
public TorrentBuilder alignment(int value) {
this.alignment = value;
return this;
}
/**
* The comment for the torrent. The comment in a torrent file is optional.
*
* @return
*/
public String comment() {
return comment;
}
/**
* Sets the comment for the torrent. The comment in a torrent file is optional.
*
* @param value
* @return
*/
public TorrentBuilder comment(String value) {
this.comment = value;
return this;
}
/**
* The creator of the torrent. This is optional.
*
* @return
*/
public String creator() {
return creator;
}
/**
* Sets the creator of the torrent. This is optional.
*
* @param value
* @return
*/
public TorrentBuilder creator(String value) {
this.creator = value;
return this;
}
/**
* @return
*/
public List<String> urlSeeds() {
return urlSeeds;
}
/**
* @param value
* @return
*/
public TorrentBuilder addUrlSeeds(List<String> value) {
if (value != null) {
this.urlSeeds.addAll(value);
}
return this;
}
/**
* This adds a url seed to the torrent. You can have any number of url seeds. For a
* single file torrent, this should be an HTTP url, pointing to a file with identical
* content as the file of the torrent. For a multi-file torrent, it should point to
* a directory containing a directory with the same name as this torrent, and all the
* files of the torrent in it.
*
* @param value
* @return
*/
public TorrentBuilder addUrlSeed(String value) {
if (value != null) {
this.urlSeeds.add(value);
}
return this;
}
/**
* @return
*/
public List<String> httpSeeds() {
return httpSeeds;
}
/**
* @param value
* @return
*/
public TorrentBuilder addHttpSeeds(List<String> value) {
if (value != null) {
this.httpSeeds.addAll(value);
}
return this;
}
/**
* This adds a HTTP seed to the torrent. You can have any number of url seeds. For a
* single file torrent, this should be an HTTP url, pointing to a file with identical
* content as the file of the torrent. For a multi-file torrent, it should point to
* a directory containing a directory with the same name as this torrent, and all the
* files of the torrent in it.
*
* @param value
* @return
*/
public TorrentBuilder addHttpSeed(String value) {
if (value != null) {
this.httpSeeds.add(value);
}
return this;
}
/**
* @return
*/
public List<Pair<String, Integer>> nodes() {
return nodes;
}
/**
* @param value
* @return
*/
public TorrentBuilder addNodes(List<Pair<String, Integer>> value) {
if (value != null) {
this.nodes.addAll(value);
}
return this;
}
/**
* This adds a DHT node to the torrent. This especially useful if you're creating a
* tracker less torrent. It can be used by clients to bootstrap their DHT node from.
* The node is a hostname and a port number where there is a DHT node running.
* You can have any number of DHT nodes in a torrent.
*
* @param value
* @return
*/
public TorrentBuilder addNode(Pair<String, Integer> value) {
if (value != null) {
this.nodes.add(value);
}
return this;
}
/**
* @return
*/
public List<Pair<String, Integer>> trackers() {
return trackers;
}
/**
* @param value
* @return
*/
public TorrentBuilder addTrackers(List<Pair<String, Integer>> value) {
if (value != null) {
this.trackers.addAll(value);
}
return this;
}
/**
* @param value
* @return
*/
public TorrentBuilder addTracker(Pair<String, Integer> value) {
if (value != null) {
this.trackers.add(value);
}
return this;
}
/**
* Adds a tracker to the torrent. This is not strictly required, but most torrents
* use a tracker as their main source of peers. The url should be an http:// or udp://
* url to a machine running a bittorrent tracker that accepts announces for this torrent's
* info-hash. The tier is the fallback priority of the tracker. All trackers with tier 0 are
* tried first (in any order). If all fail, trackers with tier 1 are tried. If all of those
* fail, trackers with tier 2 are tried, and so on.
*
* @param url
* @param tier
* @return
*/
public TorrentBuilder addTracker(String url, int tier) {
return addTracker(new Pair<>(url, tier));
}
/**
* @param url
* @return
*/
public TorrentBuilder addTracker(String url) {
return addTracker(url, 0);
}
/**
* @return
*/
public boolean isPrivate() {
return priv;
}
/**
* Sets the private flag of the torrent.
* <p>
* Torrents with the private flag set ask clients to not use any other
* sources than the tracker for peers, and to not advertise itself publicly,
* apart from the tracker.
*
* @param value
* @return
*/
public TorrentBuilder setPrivate(boolean value) {
this.priv = value;
return this;
}
/**
* @return
*/
public List<Sha1Hash> similarTorrents() {
return similarTorrents;
}
/**
* @param value
* @return
*/
public TorrentBuilder addSimilarTorrents(List<Sha1Hash> value) {
if (value != null) {
this.similarTorrents.addAll(value);
}
return this;
}
/**
* Add similar torrents (by info-hash).
* <p>
* Similar torrents are expected to share some files with this torrent.
* Torrents sharing a collection name with this torrent are also expected
* to share files with this torrent. A torrent may have more than one
* collection and more than one similar torrents. For more information,
* see BEP 38.
* <p>
* BEP 38: http://www.bittorrent.org/beps/bep_0038.html
*
* @param value
* @return
*/
public TorrentBuilder addSimilarTorrent(Sha1Hash value) {
if (value != null) {
this.similarTorrents.add(value);
}
return this;
}
/**
* @return
*/
public List<String> collections() {
return collections;
}
/**
* @param value
* @return
*/
public TorrentBuilder addCollections(List<String> value) {
if (value != null) {
this.collections.addAll(value);
}
return this;
}
/**
* Add collections of similar torrents.
* <p>
* Similar torrents are expected to share some files with this torrent.
* Torrents sharing a collection name with this torrent are also expected
* to share files with this torrent. A torrent may have more than one
* collection and more than one similar torrents. For more information,
* see BEP 38.
* <p>
* BEP 38: http://www.bittorrent.org/beps/bep_0038.html
*
* @param value
* @return
*/
public TorrentBuilder addCollection(String value) {
if (value != null) {
this.collections.add(value);
}
return this;
}
/**
* @return
*/
public Listener listener() {
return listener;
}
/**
* @param value
* @return
*/
public TorrentBuilder listener(Listener value) {
this.listener = value;
return this;
}
/**
* This function will generate a result withe the .torrent file as a bencode tree.
*
* @return
* @throws IOException
*/
public Result generate() throws IOException {
if (path == null) {
throw new IOException("path can't be null");
}
File absPath = path.getAbsoluteFile();
file_storage fs = new file_storage();
add_files_listener l1 = new add_files_listener() {
@Override
public boolean pred(String p) {
return listener == null || listener.accept(p);
}
};
add_files_ex(fs, absPath.getPath(), l1, flags);
if (fs.total_size() == 0) {
throw new IOException("content total size can't be 0");
}
create_torrent t = new create_torrent(fs, pieceSize, padFileLimit, flags, alignment);
final int numPieces = t.num_pieces();
set_piece_hashes_listener l2 = new set_piece_hashes_listener() {
@Override
public void progress(int i) {
if (listener != null) {
listener.progress(i, numPieces);
}
}
};
File parent = absPath.getParentFile();
if (parent == null) {
throw new IOException("path's parent can't be null");
}
error_code ec = new error_code();
set_piece_hashes_ex(t, parent.getAbsolutePath(), l2, ec);
if (ec.value() != 0) {
throw new IOException(ec.message());
}
if (comment != null) {
t.set_comment(comment);
}
if (creator != null) {
t.set_creator(creator);
}
for (String s : urlSeeds) {
t.add_url_seed(s);
}
for (String s : httpSeeds) {
t.add_http_seed(s);
}
for (Pair<String, Integer> n : nodes) {
t.add_node(n.to_string_int_pair());
}
for (Pair<String, Integer> tr : trackers) {
t.add_tracker(tr.first, tr.second);
}
if (priv) {
t.set_priv(priv);
}
if (!similarTorrents.isEmpty()) {
for (Sha1Hash h : similarTorrents) {
t.add_similar_torrent(h.swig());
}
}
if (!collections.isEmpty()) {
for (String s : collections) {
t.add_collection(s);
}
}
return new Result(t);
}
private final List<String> httpSeeds;
private final List<Pair<String, Integer>> nodes;
private final List<Pair<String, Integer>> trackers;
private final List<Sha1Hash> similarTorrents;
private final List<String> collections;
/**
*
*/
public interface Listener {
/**
* @param filename
* @return
*/
boolean accept(String filename);
/**
* @param pieceIndex
* @param numPieces
*/
void progress(int pieceIndex, int numPieces);
}
/**
*
*/
public static final class Result {
private final create_torrent t;
private final Entry entry;
private Result(create_torrent t) {
this.t = t;
this.entry = new Entry(t.generate());
}
/**
* @return
*/
public Entry entry() {
return entry;
}
/**
* @return
*/
public int numPieces() {
return t.num_pieces();
}
/**
* @return
*/
public int pieceLength() {
return t.piece_length();
}
/**
* @param index
* @return
*/
public int pieceSize(int index) {
return t.piece_size(index);
}
/**
* This function returns the merkle hash tree, if the torrent was created
* as a merkle torrent. The tree is created by {@link #generate()} and won't
* be valid until that function has been called.
* <p>
* When creating a merkle tree torrent, the actual tree itself has to
* be saved off separately and fed into libtorrent the first time you start
* seeding it, through the {@link TorrentInfo#merkleTree(List)} function.
* From that point onwards, the tree will be saved in the resume data.
*
* @return
*/
public ArrayList<Sha1Hash> merkleTree() {
return Sha1Hash.convert(t.merkle_tree());
}
}
}