package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.alerts.Alert;
import com.frostwire.jlibtorrent.alerts.DhtImmutableItemAlert;
import com.frostwire.jlibtorrent.alerts.GenericAlert;
import com.frostwire.jlibtorrent.swig.*;
import com.frostwire.jlibtorrent.swig.session.options_t;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* The session holds all state that spans multiple torrents. Among other
* things it runs the network loop and manages all torrents. Once it's
* created, the session object will spawn the main thread that will do all
* the work. The main thread will be idle as long it doesn't have any
* torrents to participate in.
* <p/>
* This class belongs to a middle logical layer of abstraction. It's a wrapper
* of the underlying swig session object (from libtorrent), but it does not
* expose all the raw features, not expose a very high level interface
* like {@link com.frostwire.jlibtorrent.DHT DHT} or
* {@link com.frostwire.jlibtorrent.Downloader Downloader}.
*
* @author gubatron
* @author aldenml
*/
public final class Session {
private static final Logger LOG = Logger.getLogger(Session.class);
private static final long REQUEST_STATUS_RESOLUTION_MILLIS = 500;
private static final long ALERTS_LOOP_WAIT_MILLIS = 500;
private static final Map<Integer, CastAlertFunction> CAST_TABLE = buildCastAlertTable();
private final session s;
private long lastStatusRequestTime;
private SessionStatus lastStatus;
private final SparseArray<ArrayList<AlertListener>> listeners;
private final SparseArray<AlertListener[]> listenerSnapshots;
private boolean running;
public Session(Fingerprint print, Pair<Integer, Integer> prange, String iface) {
int flags = session.session_flags_t.start_default_features.swigValue() | session.session_flags_t.add_default_plugins.swigValue();
int alert_mask = alert.category_t.all_categories.swigValue();
this.s = new session(print.getSwig(), prange.to_int_int_pair(), iface, flags, alert_mask);
this.listeners = new SparseArray<ArrayList<AlertListener>>();
this.listenerSnapshots = new SparseArray<AlertListener[]>();
this.running = true;
alertsLoop();
s.add_dht_router(new string_int_pair("router.bittorrent.com", 6881));
s.add_dht_router(new string_int_pair("dht.transmissionbt.com", 6881));
}
public Session(Fingerprint print) {
this(print, new Pair<Integer, Integer>(0, 0), "0.0.0.0");
}
public Session(Pair<Integer, Integer> prange, String iface) {
this(new Fingerprint(), prange, iface);
}
public Session() {
this(new Fingerprint());
}
public session getSwig() {
return s;
}
public void addListener(AlertListener listener) {
modifyListeners(true, listener);
}
public void removeListener(AlertListener listener) {
modifyListeners(false, listener);
}
/**
* You add torrents through the add_torrent() function where you give an
* object with all the parameters. The add_torrent() overloads will block
* until the torrent has been added (or failed to be added) and returns
* an error code and a torrent_handle. In order to add torrents more
* efficiently, consider using async_add_torrent() which returns
* immediately, without waiting for the torrent to add. Notification of
* the torrent being added is sent as add_torrent_alert.
* <p/>
* The overload that does not take an error_code throws an exception on
* error and is not available when building without exception support.
* The torrent_handle returned by add_torrent() can be used to retrieve
* information about the torrent's progress, its peers etc. It is also
* used to abort a torrent.
* <p/>
* If the torrent you are trying to add already exists in the session (is
* either queued for checking, being checked or downloading)
* ``add_torrent()`` will throw libtorrent_exception which derives from
* ``std::exception`` unless duplicate_is_error is set to false. In that
* case, add_torrent() will return the handle to the existing torrent.
* <p/>
* all torrent_handles must be destructed before the session is destructed!
*
* @param ti
* @param saveDir
* @param priorities
* @param resumeFile
* @return
*/
public TorrentHandle addTorrent(TorrentInfo ti, File saveDir, Priority[] priorities, File resumeFile) {
return addTorrentSupport(ti, saveDir, priorities, resumeFile, false);
}
/**
* You add torrents through the add_torrent() function where you give an
* object with all the parameters. The add_torrent() overloads will block
* until the torrent has been added (or failed to be added) and returns
* an error code and a torrent_handle. In order to add torrents more
* efficiently, consider using async_add_torrent() which returns
* immediately, without waiting for the torrent to add. Notification of
* the torrent being added is sent as add_torrent_alert.
* <p/>
* The overload that does not take an error_code throws an exception on
* error and is not available when building without exception support.
* The torrent_handle returned by add_torrent() can be used to retrieve
* information about the torrent's progress, its peers etc. It is also
* used to abort a torrent.
* <p/>
* If the torrent you are trying to add already exists in the session (is
* either queued for checking, being checked or downloading)
* ``add_torrent()`` will throw libtorrent_exception which derives from
* ``std::exception`` unless duplicate_is_error is set to false. In that
* case, add_torrent() will return the handle to the existing torrent.
* <p/>
* all torrent_handles must be destructed before the session is destructed!
*
* @param torrent
* @param saveDir
* @param resumeFile
* @return
*/
public TorrentHandle addTorrent(File torrent, File saveDir, File resumeFile) {
return addTorrent(new TorrentInfo(torrent), saveDir, null, resumeFile);
}
/**
* You add torrents through the add_torrent() function where you give an
* object with all the parameters. The add_torrent() overloads will block
* until the torrent has been added (or failed to be added) and returns
* an error code and a torrent_handle. In order to add torrents more
* efficiently, consider using async_add_torrent() which returns
* immediately, without waiting for the torrent to add. Notification of
* the torrent being added is sent as add_torrent_alert.
* <p/>
* The overload that does not take an error_code throws an exception on
* error and is not available when building without exception support.
* The torrent_handle returned by add_torrent() can be used to retrieve
* information about the torrent's progress, its peers etc. It is also
* used to abort a torrent.
* <p/>
* If the torrent you are trying to add already exists in the session (is
* either queued for checking, being checked or downloading)
* ``add_torrent()`` will throw libtorrent_exception which derives from
* ``std::exception`` unless duplicate_is_error is set to false. In that
* case, add_torrent() will return the handle to the existing torrent.
* <p/>
* all torrent_handles must be destructed before the session is destructed!
*
* @param torrent
* @param saveDir
* @return
*/
public TorrentHandle addTorrent(File torrent, File saveDir) {
return addTorrent(torrent, saveDir, null);
}
/**
* You add torrents through the add_torrent() function where you give an
* object with all the parameters. The add_torrent() overloads will block
* until the torrent has been added (or failed to be added) and returns
* an error code and a torrent_handle. In order to add torrents more
* efficiently, consider using async_add_torrent() which returns
* immediately, without waiting for the torrent to add. Notification of
* the torrent being added is sent as add_torrent_alert.
* <p/>
* The overload that does not take an error_code throws an exception on
* error and is not available when building without exception support.
* The torrent_handle returned by add_torrent() can be used to retrieve
* information about the torrent's progress, its peers etc. It is also
* used to abort a torrent.
* <p/>
* If the torrent you are trying to add already exists in the session (is
* either queued for checking, being checked or downloading)
* ``add_torrent()`` will throw libtorrent_exception which derives from
* ``std::exception`` unless duplicate_is_error is set to false. In that
* case, add_torrent() will return the handle to the existing torrent.
* <p/>
* all torrent_handles must be destructed before the session is destructed!
*
* @param ti
* @param saveDir
* @param priorities
* @param resumeFile
*/
public void asyncAddTorrent(TorrentInfo ti, File saveDir, Priority[] priorities, File resumeFile) {
addTorrentSupport(ti, saveDir, priorities, resumeFile, true);
}
/**
* You add torrents through the add_torrent() function where you give an
* object with all the parameters. The add_torrent() overloads will block
* until the torrent has been added (or failed to be added) and returns
* an error code and a torrent_handle. In order to add torrents more
* efficiently, consider using async_add_torrent() which returns
* immediately, without waiting for the torrent to add. Notification of
* the torrent being added is sent as add_torrent_alert.
* <p/>
* The overload that does not take an error_code throws an exception on
* error and is not available when building without exception support.
* The torrent_handle returned by add_torrent() can be used to retrieve
* information about the torrent's progress, its peers etc. It is also
* used to abort a torrent.
* <p/>
* If the torrent you are trying to add already exists in the session (is
* either queued for checking, being checked or downloading)
* ``add_torrent()`` will throw libtorrent_exception which derives from
* ``std::exception`` unless duplicate_is_error is set to false. In that
* case, add_torrent() will return the handle to the existing torrent.
* <p/>
* all torrent_handles must be destructed before the session is destructed!
*
* @param torrent
* @param saveDir
* @param resumeFile
*/
public void asyncAddTorrent(File torrent, File saveDir, File resumeFile) {
asyncAddTorrent(new TorrentInfo(torrent), saveDir, null, resumeFile);
}
/**
* You add torrents through the add_torrent() function where you give an
* object with all the parameters. The add_torrent() overloads will block
* until the torrent has been added (or failed to be added) and returns
* an error code and a torrent_handle. In order to add torrents more
* efficiently, consider using async_add_torrent() which returns
* immediately, without waiting for the torrent to add. Notification of
* the torrent being added is sent as add_torrent_alert.
* <p/>
* The overload that does not take an error_code throws an exception on
* error and is not available when building without exception support.
* The torrent_handle returned by add_torrent() can be used to retrieve
* information about the torrent's progress, its peers etc. It is also
* used to abort a torrent.
* <p/>
* If the torrent you are trying to add already exists in the session (is
* either queued for checking, being checked or downloading)
* ``add_torrent()`` will throw libtorrent_exception which derives from
* ``std::exception`` unless duplicate_is_error is set to false. In that
* case, add_torrent() will return the handle to the existing torrent.
* <p/>
* all torrent_handles must be destructed before the session is destructed!
*
* @param torrent
* @param saveDir
*/
public void asyncAddTorrent(File torrent, File saveDir) {
asyncAddTorrent(torrent, saveDir, null);
}
/**
* This method will close all peer connections associated with the torrent and tell the
* tracker that we've stopped participating in the swarm. This operation cannot fail.
* When it completes, you will receive a torrent_removed_alert.
* <p/>
* The optional second argument options can be used to delete all the files downloaded
* by this torrent. To do so, pass in the value session::delete_files. The removal of
* the torrent is asyncronous, there is no guarantee that adding the same torrent immediately
* after it was removed will not throw a libtorrent_exception exception. Once the torrent
* is deleted, a torrent_deleted_alert is posted.
*
* @param th
*/
public void removeTorrent(TorrentHandle th, Options options) {
s.remove_torrent(th.getSwig(), options.getSwig());
}
/**
* This method will close all peer connections associated with the torrent and tell the
* tracker that we've stopped participating in the swarm. This operation cannot fail.
* When it completes, you will receive a torrent_removed_alert.
*
* @param th
*/
public void removeTorrent(TorrentHandle th) {
if (th.isValid()) {
s.remove_torrent(th.getSwig());
}
}
/**
* In case you want to destruct the session asynchrounously, you can
* request a session destruction proxy. If you don't do this, the
* destructor of the session object will block while the trackers are
* contacted. If you keep one ``session_proxy`` to the session when
* destructing it, the destructor will not block, but start to close down
* the session, the destructor of the proxy will then synchronize the
* threads. So, the destruction of the session is performed from the
* ``session`` destructor call until the ``session_proxy`` destructor
* call. The ``session_proxy`` does not have any operations on it (since
* the session is being closed down, no operations are allowed on it).
* The only valid operation is calling the destructor::
*
* @return
*/
public SessionProxy abort() {
running = false;
return new SessionProxy(s.abort());
}
/**
* Pausing the session has the same effect as pausing every torrent in
* it, except that torrents will not be resumed by the auto-manage
* mechanism.
*/
public void pause() {
s.pause();
}
/**
* Resuming will restore the torrents to their previous paused
* state. i.e. the session pause state is separate from the torrent pause
* state. A torrent is inactive if it is paused or if the session is
* paused.
*/
public void resume() {
s.resume();
}
public boolean isPaused() {
return s.is_paused();
}
/**
* returns the port we ended up listening on. Since you
* just pass a port-range to the constructor and to ``listen_on()``, to
* know which port it ended up using, you have to ask the session using
* this function.
*
* @return
*/
public int getListenPort() {
return s.listen_port();
}
public int getSslListenPort() {
return s.ssl_listen_port();
}
/**
* will tell you whether or not the session has
* successfully opened a listening port. If it hasn't, this function will
* return false, and then you can use ``listen_on()`` to make another
* attempt.
*
* @return
*/
public boolean isListening() {
return s.is_listening();
}
/**
* Returns session wide-statistics and status.
* <p/>
* It is important not to call this method for each field in the status
* for performance reasons.
*
* @return
*/
public SessionStatus getStatus(boolean force) {
long now = System.currentTimeMillis();
if (force || (now - lastStatusRequestTime) >= REQUEST_STATUS_RESOLUTION_MILLIS) {
lastStatusRequestTime = now;
lastStatus = new SessionStatus(s.status());
}
return lastStatus;
}
/**
* Returns session wide-statistics and status.
*
* @return
*/
public SessionStatus getStatus() {
return this.getStatus(false);
}
/**
* The session settings and the packet encryption settings
* respectively. See session_settings and pe_settings for more
* information on available options.
*
* @return
*/
public SessionSettings getSettings() {
return new SessionSettings(s.settings());
}
/**
* Sets the session settings and the packet encryption settings
* respectively. See session_settings and pe_settings for more
* information on available options.
*
* @param settings
*/
public void setSettings(SessionSettings settings) {
s.set_settings(settings.getSwig());
}
/**
* These functions sets and queries the proxy settings to be used for the
* session.
* <p/>
* For more information on what settings are available for proxies, see
* proxy_settings. If the session is not in anonymous mode, proxies that
* aren't working or fail, will automatically be disabled and packets
* will flow without using any proxy. If you want to enforce using a
* proxy, even when the proxy doesn't work, enable anonymous_mode in
* session_settings.
*
* @return
*/
public ProxySettings getProxy() {
return new ProxySettings(s.proxy());
}
/**
* These functions sets and queries the proxy settings to be used for the
* session.
* <p/>
* For more information on what settings are available for proxies, see
* proxy_settings. If the session is not in anonymous mode, proxies that
* aren't working or fail, will automatically be disabled and packets
* will flow without using any proxy. If you want to enforce using a
* proxy, even when the proxy doesn't work, enable anonymous_mode in
* session_settings.
*
* @param settings
*/
public void setProxy(ProxySettings settings) {
s.set_proxy(settings.getSwig());
}
/**
* Loads and saves all session settings, including dht_settings,
* encryption settings and proxy settings. ``save_state`` writes all keys
* to the ``entry`` that's passed in, which needs to either not be
* initialized, or initialized as a dictionary.
* <p/>
* ``load_state`` expects a lazy_entry which can be built from a bencoded
* buffer with lazy_bdecode().
* <p/>
* The ``flags`` arguments passed in to ``save_state`` can be used to
* filter which parts of the session state to save. By default, all state
* is saved (except for the individual torrents). see save_state_flags_t
*
* @return
*/
public byte[] saveState() {
entry e = new entry();
s.save_state(e);
return Vectors.char_vector2bytes(e.bencode());
}
/**
* Loads and saves all session settings, including dht_settings,
* encryption settings and proxy settings. ``save_state`` writes all keys
* to the ``entry`` that's passed in, which needs to either not be
* initialized, or initialized as a dictionary.
* <p/>
* ``load_state`` expects a lazy_entry which can be built from a bencoded
* buffer with lazy_bdecode().
* <p/>
* The ``flags`` arguments passed in to ``save_state`` can be used to
* filter which parts of the session state to save. By default, all state
* is saved (except for the individual torrents). see save_state_flags_t
*
* @param data
*/
public void loadState(byte[] data) {
char_vector buffer = Vectors.bytes2char_vector(data);
lazy_entry e = new lazy_entry();
error_code ec = new error_code();
int ret = lazy_entry.bdecode(buffer, e, ec);
if (ret == 0) {
s.load_state(e);
} else {
LOG.error("failed to decode torrent: " + ec.message());
}
}
/**
* Looks for a torrent with the given info-hash. In
* case there is such a torrent in the session, a torrent_handle to that
* torrent is returned.
* <p/>
* In case the torrent cannot be found, a null is returned.
*
* @param infoHash
* @return
*/
public TorrentHandle findTorrent(Sha1Hash infoHash) {
torrent_handle th = s.find_torrent(infoHash.getSwig());
return th != null && th.is_valid() ? new TorrentHandle(th) : null;
}
/**
* Returns a list of torrent handles to all the
* torrents currently in the session.
*
* @return
*/
public List<TorrentHandle> getTorrents() {
torrent_handle_vector v = s.get_torrents();
long size = v.size();
List<TorrentHandle> l = new ArrayList<TorrentHandle>((int) size);
for (int i = 0; i < size; i++) {
l.add(new TorrentHandle(v.get(i)));
}
return l;
}
// starts/stops UPnP, NATPMP or LSD port mappers they are stopped by
// default These functions are not available in case
// ``TORRENT_DISABLE_DHT`` is defined. ``start_dht`` starts the dht node
// and makes the trackerless service available to torrents. The startup
// state is optional and can contain nodes and the node id from the
// previous session. The dht node state is a bencoded dictionary with the
// following entries:
//
// nodes
// A list of strings, where each string is a node endpoint encoded in
// binary. If the string is 6 bytes long, it is an IPv4 address of 4
// bytes, encoded in network byte order (big endian), followed by a 2
// byte port number (also network byte order). If the string is 18
// bytes long, it is 16 bytes of IPv6 address followed by a 2 bytes
// port number (also network byte order).
//
// node-id
// The node id written as a readable string as a hexadecimal number.
//
// ``dht_state`` will return the current state of the dht node, this can
// be used to start up the node again, passing this entry to
// ``start_dht``. It is a good idea to save this to disk when the session
// is closed, and read it up again when starting.
//
// If the port the DHT is supposed to listen on is already in use, and
// exception is thrown, ``asio::error``.
//
// ``stop_dht`` stops the dht node.
//
// ``add_dht_node`` adds a node to the routing table. This can be used if
// your client has its own source of bootstrapping nodes.
//
// ``set_dht_settings`` sets some parameters availavle to the dht node.
// See dht_settings for more information.
//
// ``is_dht_running()`` returns true if the DHT support has been started
// and false
// otherwise.
public void startDHT() {
s.start_dht();
}
public void stopDHT() {
s.stop_dht();
}
void setDHTSettings(DHTSettings settings) {
s.set_dht_settings(settings.getSwig());
}
public boolean isDHTRunning() {
return s.is_dht_running();
}
/**
* takes a host name and port pair. That endpoint will be
* pinged, and if a valid DHT reply is received, the node will be added to
* the routing table.
*
* @param node
*/
public void addDHTNode(Pair<String, Integer> node) {
s.add_dht_node(node.to_string_int_pair());
}
/**
* adds the given endpoint to a list of DHT router nodes.
* If a search is ever made while the routing table is empty, those nodes will
* be used as backups. Nodes in the router node list will also never be added
* to the regular routing table, which effectively means they are only used
* for bootstrapping, to keep the load off them.
* <p/>
* An example routing node that you could typically add is
* ``router.bittorrent.com``.
*
* @param node
*/
public void addDHTRouter(Pair<String, Integer> node) {
s.add_dht_router(node.to_string_int_pair());
}
/**
* Query the DHT for an immutable item at the target hash.
* the result is posted as a {@link DhtImmutableItemAlert}.
*
* @param target
*/
public void dhtGetItem(Sha1Hash target) {
s.dht_get_item(target.getSwig());
}
/**
* Query the DHT for a mutable item under the public key ``key``.
* this is an ed25519 key. ``salt`` is optional and may be left
* as an empty string if no salt is to be used.
* if the item is found in the DHT, a dht_mutable_item_alert is
* posted.
*
* @param key
*/
public void dhtGetItem(byte[] key) {
s.dht_get_item(Vectors.bytes2char_vector(key));
}
/**
* Query the DHT for a mutable item under the public key ``key``.
* this is an ed25519 key. ``salt`` is optional and may be left
* as an empty string if no salt is to be used.
* if the item is found in the DHT, a dht_mutable_item_alert is
* posted.
*
* @param key
* @param salt
*/
public void dhtGetItem(byte[] key, String salt) {
s.dht_get_item(Vectors.bytes2char_vector(key), salt);
}
/**
* Store the given bencoded data as an immutable item in the DHT.
* the returned hash is the key that is to be used to look the item
* up agan. It's just the sha-1 hash of the bencoded form of the
* structure.
*
* @param entry
* @return
*/
public Sha1Hash dhtPutItem(Entry entry) {
return new Sha1Hash(s.dht_put_item(entry.getSwig()));
}
// store an immutable item. The ``key`` is the public key the blob is
// to be stored under. The optional ``salt`` argument is a string that
// is to be mixed in with the key when determining where in the DHT
// the value is to be stored. The callback function is called from within
// the libtorrent network thread once we've found where to store the blob,
// possibly with the current value stored under the key.
// The values passed to the callback functions are:
//
// entry& value
// the current value stored under the key (may be empty). Also expected
// to be set to the value to be stored by the function.
//
// boost::array<char,64>& signature
// the signature authenticating the current value. This may be zeroes
// if there is currently no value stored. The functon is expected to
// fill in this buffer with the signature of the new value to store.
// To generate the signature, you may want to use the
// ``sign_mutable_item`` function.
//
// boost::uint64_t& seq
// current sequence number. May be zero if there is no current value.
// The function is expected to set this to the new sequence number of
// the value that is to be stored. Sequence numbers must be monotonically
// increasing. Attempting to overwrite a value with a lower or equal
// sequence number will fail, even if the signature is correct.
//
// std::string const& salt
// this is the salt that was used for this put call.
//
// Since the callback function ``cb`` is called from within libtorrent,
// it is critical to not perform any blocking operations. Ideally not
// even locking a mutex. Pass any data required for this function along
// with the function object's context and make the function entirely
// self-contained. The only reason data blobs' values are computed
// via a function instead of just passing in the new value is to avoid
// race conditions. If you want to *update* the value in the DHT, you
// must first retrieve it, then modify it, then write it back. The way
// the DHT works, it is natural to always do a lookup before storing and
// calling the callback in between is convenient.
public void dhtPutItem(byte[] publicKey, byte[] privateKey, Entry entry) {
s.dht_put_item(Vectors.bytes2char_vector(publicKey), Vectors.bytes2char_vector(privateKey), entry.getSwig());
}
// store an immutable item. The ``key`` is the public key the blob is
// to be stored under. The optional ``salt`` argument is a string that
// is to be mixed in with the key when determining where in the DHT
// the value is to be stored. The callback function is called from within
// the libtorrent network thread once we've found where to store the blob,
// possibly with the current value stored under the key.
// The values passed to the callback functions are:
//
// entry& value
// the current value stored under the key (may be empty). Also expected
// to be set to the value to be stored by the function.
//
// boost::array<char,64>& signature
// the signature authenticating the current value. This may be zeroes
// if there is currently no value stored. The functon is expected to
// fill in this buffer with the signature of the new value to store.
// To generate the signature, you may want to use the
// ``sign_mutable_item`` function.
//
// boost::uint64_t& seq
// current sequence number. May be zero if there is no current value.
// The function is expected to set this to the new sequence number of
// the value that is to be stored. Sequence numbers must be monotonically
// increasing. Attempting to overwrite a value with a lower or equal
// sequence number will fail, even if the signature is correct.
//
// std::string const& salt
// this is the salt that was used for this put call.
//
// Since the callback function ``cb`` is called from within libtorrent,
// it is critical to not perform any blocking operations. Ideally not
// even locking a mutex. Pass any data required for this function along
// with the function object's context and make the function entirely
// self-contained. The only reason data blobs' values are computed
// via a function instead of just passing in the new value is to avoid
// race conditions. If you want to *update* the value in the DHT, you
// must first retrieve it, then modify it, then write it back. The way
// the DHT works, it is natural to always do a lookup before storing and
// calling the callback in between is convenient.
public void dhtPutItem(byte[] publicKey, byte[] privateKey, Entry entry, String salt) {
s.dht_put_item(Vectors.bytes2char_vector(publicKey), Vectors.bytes2char_vector(privateKey), entry.getSwig(), salt);
}
public void dhtGetPeers(Sha1Hash infoHash) {
s.dht_get_peers(infoHash.getSwig());
}
public void dhtAnnounce(Sha1Hash infoHash, int port, int flags) {
s.dht_announce(infoHash.getSwig(), port, flags);
}
public void dhtAnnounce(Sha1Hash infoHash) {
s.dht_announce(infoHash.getSwig());
}
/**
* Starts and stops Local Service Discovery. This service will broadcast
* the infohashes of all the non-private torrents on the local network to
* look for peers on the same swarm within multicast reach.
* <p/>
* It is turned off by default.
*/
public void startLSD() {
s.start_lsd();
}
/**
* Starts and stops Local Service Discovery. This service will broadcast
* the infohashes of all the non-private torrents on the local network to
* look for peers on the same swarm within multicast reach.
* <p/>
* It is turned off by default.
*/
public void stopLSD() {
s.stop_lsd();
}
/**
* Starts the UPnP service. When started, the listen port and
* the DHT port are attempted to be forwarded on local UPnP router
* devices.
*/
public void startUPnP() {
s.start_upnp();
}
/**
* Stops the UPnP service. When started, the listen port and
* the DHT port are attempted to be forwarded on local UPnP router
* devices.
*/
public void stopUPnP() {
s.stop_upnp();
}
/**
* add_port_mapping adds a port forwarding on UPnP and/or NAT-PMP,
* whichever is enabled. The return value is a handle referring to the
* port mapping that was just created. Pass it to delete_port_mapping()
* to remove it.
*
* @param t
* @param externalPort
* @param localPort
* @return
*/
public int addPortMapping(ProtocolType t, int externalPort, int localPort) {
return s.add_port_mapping(t.getSwig(), externalPort, localPort);
}
public void deletePortMapping(int handle) {
s.delete_port_mapping(handle);
}
/**
* Starts the NAT-PMP service. When started, the listen port
* and the DHT port are attempted to be forwarded on the router through
* NAT-PMP.
*/
public void startNATPMP() {
s.start_natpmp();
}
/**
* Stops the NAT-PMP service. When started, the listen port
* and the DHT port are attempted to be forwarded on the router through
* NAT-PMP.
*/
public void stopNATPMP() {
s.stop_natpmp();
}
@Override
protected void finalize() throws Throwable {
this.running = false;
super.finalize();
}
void fireAlert(Alert<?> a) {
int type = a.getSwig() != null ? a.getSwig().type() : a.getType().getSwig();
fireAlert(a, type);
fireAlert(a, -1);
}
private void fireAlert(Alert<?> a, int type) {
AlertListener[] listeners = listenerSnapshots.get(type);
if (listeners != null) {
for (int i = 0; i < listeners.length; i++) {
try {
listeners[i].alert(a);
} catch (Throwable e) {
LOG.warn("Error calling alert listener", e);
}
}
}
}
private TorrentHandle addTorrentSupport(TorrentInfo ti, File saveDir, Priority[] priorities, File resumeFile, boolean async) {
String savePath = null;
if (saveDir != null) {
savePath = saveDir.getAbsolutePath();
} else if (resumeFile == null) {
throw new IllegalArgumentException("Both saveDir and resumeFile can't be null at the same time");
}
add_torrent_params p = add_torrent_params.create_instance();
p.setTi(ti.getSwig());
if (savePath != null) {
p.setSave_path(savePath);
}
if (priorities != null) {
p.setFile_priorities(Vectors.priorities2unsigned_char_vector(priorities));
}
p.setStorage_mode(storage_mode_t.storage_mode_sparse);
long flags = p.getFlags();
flags &= ~add_torrent_params.flags_t.flag_auto_managed.swigValue();
if (resumeFile != null) {
try {
byte[] data = Utils.readFileToByteArray(resumeFile);
p.setResume_data(Vectors.bytes2char_vector(data));
} catch (Throwable e) {
LOG.warn("Unable to set resume data", e);
}
}
p.setFlags(flags);
if (async) {
s.async_add_torrent(p);
return null;
} else {
torrent_handle th = s.add_torrent(p);
return new TorrentHandle(th);
}
}
private void alertsLoop() {
Runnable r = new Runnable() {
@Override
public void run() {
alert_ptr_deque deque = new alert_ptr_deque();
time_duration max_wait = libtorrent.milliseconds(ALERTS_LOOP_WAIT_MILLIS);
while (running) {
alert ptr = s.wait_for_alert(max_wait);
if (ptr != null) {
s.pop_alerts(deque);
long size = deque.size();
for (int i = 0; i < size; i++) {
alert swigAlert = deque.getitem(i);
int type = swigAlert.type();
Alert<?> alert = null;
if (listeners.indexOfKey(type) >= 0) {
alert = castAlert(swigAlert);
fireAlert(alert, type);
}
if (listeners.indexOfKey(-1) >= 0) {
if (alert == null) {
alert = castAlert(swigAlert);
}
fireAlert(alert, -1);
}
}
deque.clear();
}
}
}
};
Thread t = new Thread(r, "Session-alertsLoop");
t.setDaemon(true);
t.start();
}
private void modifyListeners(boolean adding, AlertListener listener) {
if (listener != null) {
int[] types = listener.types();
//all alert-type including listener
if (types == null) {
modifyListeners(adding, -1, listener);
} else {
for (int i = 0; i < types.length; i++) {
if (types[i] == -1) {
throw new IllegalArgumentException("Type can't be the key of all (-1)");
}
modifyListeners(adding, types[i], listener);
}
}
}
}
private void modifyListeners(boolean adding, int type, AlertListener listener) {
ArrayList<AlertListener> l = listeners.get(type);
if (l == null) {
l = new ArrayList<AlertListener>();
listeners.append(type, l);
}
if (adding) {
l.add(listener);
} else {
l.remove(listener);
}
listenerSnapshots.append(type, l.toArray(new AlertListener[0]));
}
private static Map<Integer, CastAlertFunction> buildCastAlertTable() {
Map<Integer, CastAlertFunction> map = new HashMap<Integer, CastAlertFunction>();
CAST_ALERT_METHOD(torrent_alert.class, map);
CAST_ALERT_METHOD(peer_alert.class, map);
CAST_ALERT_METHOD(tracker_alert.class, map);
CAST_ALERT_METHOD(torrent_added_alert.class, map);
CAST_ALERT_METHOD(torrent_removed_alert.class, map);
CAST_ALERT_METHOD(read_piece_alert.class, map);
CAST_ALERT_METHOD(file_completed_alert.class, map);
CAST_ALERT_METHOD(file_renamed_alert.class, map);
CAST_ALERT_METHOD(file_rename_failed_alert.class, map);
CAST_ALERT_METHOD(performance_alert.class, map);
CAST_ALERT_METHOD(state_changed_alert.class, map);
CAST_ALERT_METHOD(tracker_error_alert.class, map);
CAST_ALERT_METHOD(tracker_warning_alert.class, map);
CAST_ALERT_METHOD(scrape_reply_alert.class, map);
CAST_ALERT_METHOD(scrape_failed_alert.class, map);
CAST_ALERT_METHOD(tracker_reply_alert.class, map);
CAST_ALERT_METHOD(dht_reply_alert.class, map);
CAST_ALERT_METHOD(tracker_announce_alert.class, map);
CAST_ALERT_METHOD(hash_failed_alert.class, map);
CAST_ALERT_METHOD(peer_ban_alert.class, map);
CAST_ALERT_METHOD(peer_unsnubbed_alert.class, map);
CAST_ALERT_METHOD(peer_snubbed_alert.class, map);
CAST_ALERT_METHOD(peer_error_alert.class, map);
CAST_ALERT_METHOD(peer_connect_alert.class, map);
CAST_ALERT_METHOD(peer_disconnected_alert.class, map);
CAST_ALERT_METHOD(invalid_request_alert.class, map);
CAST_ALERT_METHOD(torrent_finished_alert.class, map);
CAST_ALERT_METHOD(piece_finished_alert.class, map);
CAST_ALERT_METHOD(request_dropped_alert.class, map);
CAST_ALERT_METHOD(block_timeout_alert.class, map);
CAST_ALERT_METHOD(block_finished_alert.class, map);
CAST_ALERT_METHOD(block_downloading_alert.class, map);
CAST_ALERT_METHOD(unwanted_block_alert.class, map);
CAST_ALERT_METHOD(storage_moved_alert.class, map);
CAST_ALERT_METHOD(storage_moved_failed_alert.class, map);
CAST_ALERT_METHOD(torrent_deleted_alert.class, map);
CAST_ALERT_METHOD(torrent_delete_failed_alert.class, map);
CAST_ALERT_METHOD(save_resume_data_alert.class, map);
CAST_ALERT_METHOD(save_resume_data_failed_alert.class, map);
CAST_ALERT_METHOD(torrent_paused_alert.class, map);
CAST_ALERT_METHOD(torrent_resumed_alert.class, map);
CAST_ALERT_METHOD(torrent_checked_alert.class, map);
CAST_ALERT_METHOD(url_seed_alert.class, map);
CAST_ALERT_METHOD(file_error_alert.class, map);
CAST_ALERT_METHOD(metadata_failed_alert.class, map);
CAST_ALERT_METHOD(metadata_received_alert.class, map);
CAST_ALERT_METHOD(udp_error_alert.class, map);
CAST_ALERT_METHOD(external_ip_alert.class, map);
CAST_ALERT_METHOD(listen_failed_alert.class, map);
CAST_ALERT_METHOD(listen_succeeded_alert.class, map);
CAST_ALERT_METHOD(portmap_error_alert.class, map);
CAST_ALERT_METHOD(portmap_alert.class, map);
CAST_ALERT_METHOD(portmap_log_alert.class, map);
CAST_ALERT_METHOD(fastresume_rejected_alert.class, map);
CAST_ALERT_METHOD(peer_blocked_alert.class, map);
CAST_ALERT_METHOD(dht_announce_alert.class, map);
CAST_ALERT_METHOD(dht_get_peers_alert.class, map);
CAST_ALERT_METHOD(stats_alert.class, map);
CAST_ALERT_METHOD(cache_flushed_alert.class, map);
CAST_ALERT_METHOD(anonymous_mode_alert.class, map);
CAST_ALERT_METHOD(lsd_peer_alert.class, map);
CAST_ALERT_METHOD(trackerid_alert.class, map);
CAST_ALERT_METHOD(dht_bootstrap_alert.class, map);
CAST_ALERT_METHOD(rss_alert.class, map);
CAST_ALERT_METHOD(torrent_error_alert.class, map);
CAST_ALERT_METHOD(torrent_need_cert_alert.class, map);
CAST_ALERT_METHOD(incoming_connection_alert.class, map);
CAST_ALERT_METHOD(add_torrent_alert.class, map);
CAST_ALERT_METHOD(state_update_alert.class, map);
CAST_ALERT_METHOD(torrent_update_alert.class, map);
CAST_ALERT_METHOD(rss_item_alert.class, map);
CAST_ALERT_METHOD(dht_error_alert.class, map);
CAST_ALERT_METHOD(dht_immutable_item_alert.class, map);
CAST_ALERT_METHOD(dht_mutable_item_alert.class, map);
CAST_ALERT_METHOD(dht_put_alert.class, map);
CAST_ALERT_METHOD(i2p_alert.class, map);
CAST_ALERT_METHOD(dht_get_peers_reply_alert.class, map);
return Collections.unmodifiableMap(map);
}
private static void CAST_ALERT_METHOD(Class<? extends alert> clazz, Map<Integer, CastAlertFunction> map) {
try {
Field f = clazz.getDeclaredField("alert_type");
int type = f.getInt(null);
CastAlertFunction function = new CastAlertFunction(clazz);
map.put(type, function);
} catch (Throwable e) {
LOG.warn(e.toString());
}
}
private Alert<?> castAlert(alert a) {
CastAlertFunction function = CAST_TABLE.get(a.type());
Alert<?> r;
if (function != null) {
r = function.cast(a);
} else {
r = new GenericAlert(a);
}
return r;
}
/**
* Flags to be passed in to remove_torrent().
*/
public enum Options {
/**
* Delete the files belonging to the torrent from disk.
*/
DELETE_FILES(options_t.delete_files.swigValue()),
/**
*
*/
UNKNOWN(-1);
private Options(int swigValue) {
this.swigValue = swigValue;
}
private final int swigValue;
public int getSwig() {
return swigValue;
}
}
/**
* protocols used by add_port_mapping().
*/
public enum ProtocolType {
UDP(session.protocol_type.udp),
TCP(session.protocol_type.tcp);
private ProtocolType(session.protocol_type swigObj) {
this.swigObj = swigObj;
}
private final session.protocol_type swigObj;
public session.protocol_type getSwig() {
return swigObj;
}
}
private static final class CastAlertFunction {
private final Method method;
private final Constructor<? extends Alert<?>> constructor;
public CastAlertFunction(Class<? extends alert> swigClazz) throws NoSuchMethodException, ClassNotFoundException {
String swigClazzName = swigClazz.getName().replace("com.frostwire.jlibtorrent.swig.", "");
String libClazzName = "com.frostwire.jlibtorrent.alerts." + capitalizeAlertTypeName(swigClazzName);
@SuppressWarnings("unchecked")
Class<? extends Alert<?>> libClazz = (Class<? extends Alert<?>>) Class.forName(libClazzName);
this.method = alert.class.getDeclaredMethod("cast_to_" + swigClazzName, alert.class);
this.constructor = libClazz.getDeclaredConstructor(swigClazz);
}
public Alert<?> cast(alert a) {
Alert<?> r;
try {
Object obj = method.invoke(null, a);
r = constructor.newInstance(obj);
} catch (Throwable e) {
LOG.warn(e.toString());
r = new GenericAlert(a);
}
return r;
}
private static String capitalizeAlertTypeName(String s) {
StringBuilder sb = new StringBuilder(s.length());
boolean capitalize = true;
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (capitalize) {
sb.append(Character.toUpperCase(ch));
capitalize = false;
} else if (ch == '_') {
capitalize = true;
} else {
sb.append(ch);
}
}
return sb.toString();
}
}
}