Manage your torrents from your Android device
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

675 lines
24 KiB

/*
* This file is part of Transdroid <http://www.transdroid.org>
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.transdroid.daemon.Utorrent;
import com.android.internalcopy.http.multipart.FilePart;
import com.android.internalcopy.http.multipart.MultipartEntity;
import com.android.internalcopy.http.multipart.Part;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.core.gui.remoterss.data.RemoteRssChannel;
import org.transdroid.core.gui.remoterss.data.RemoteRssItem;
import org.transdroid.core.gui.remoterss.data.RemoteRssSupplier;
import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Label;
import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentDetails;
import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.Utorrent.data.UTorrentRemoteRssChannel;
import org.transdroid.daemon.task.AddByFileTask;
import org.transdroid.daemon.task.AddByMagnetUrlTask;
import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.DaemonTask;
import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.DaemonTaskSuccessResult;
import org.transdroid.daemon.task.GetFileListTask;
import org.transdroid.daemon.task.GetFileListTaskSuccessResult;
import org.transdroid.daemon.task.GetTorrentDetailsTask;
import org.transdroid.daemon.task.GetTorrentDetailsTaskSuccessResult;
import org.transdroid.daemon.task.RemoveTask;
import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.task.SetLabelTask;
import org.transdroid.daemon.task.SetTrackersTask;
import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.task.StartTask;
import org.transdroid.daemon.util.HttpHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
/**
* An adapter that allows for easy access to uTorrent torrent data. Communication is handled via authenticated JSON-RPC
* HTTP GET requests and responses.
* @author erickok
*/
public class UtorrentAdapter implements IDaemonAdapter, RemoteRssSupplier {
private static final String LOG_NAME = "uTorrent daemon";
private static final String RPC_URL_HASH = "&hash=";
private static final int NAME_IDX = 0;
private static final int COUNT_IDX = 1;
// These are the positions inside the JSON response array of a torrent
// See http://forum.utorrent.com/viewtopic.php?id=25661
private static final int RPC_HASH_IDX = 0;
private static final int RPC_STATUS_IDX = 1;
private static final int RPC_NAME_IDX = 2;
private static final int RPC_SIZE_IDX = 3;
private static final int RPC_PARTDONE = 4;
private static final int RPC_DOWNLOADED_IDX = 5;
private static final int RPC_UPLOADED_IDX = 6;
private static final int RPC_DOWNLOADSPEED_IDX = 9;
private static final int RPC_UPLOADSPEED_IDX = 8;
private static final int RPC_ETA_IDX = 10;
private static final int RPC_LABEL_IDX = 11;
private static final int RPC_PEERSCONNECTED_IDX = 12;
private static final int RPC_PEERSINSWARM_IDX = 13;
private static final int RPC_SEEDSCONNECTED_IDX = 14;
private static final int RPC_SEEDSINSWARM_IDX = 15;
private static final int RPC_AVAILABILITY_IDX = 16;
private static final int RPC_ADDEDON_IDX = 23;
private static final int RPC_COMPLETEDON_IDX = 24;
// These are the positions inside the JSON response array of a torrent
// See http://forum.utorrent.com/viewtopic.php?id=25661
private static final int RPC_FILENAME_IDX = 0;
private static final int RPC_FILESIZE_IDX = 1;
private static final int RPC_FILEDOWNLOADED_IDX = 2;
private static final int RPC_FILEPRIORITY_IDX = 3;
private static String authtoken;
private DaemonSettings settings;
private DefaultHttpClient httpclient;
private static ArrayList<RemoteRssChannel> remoteRssChannels = new ArrayList<>();
/**
* Initialises an adapter that provides operations to the uTorrent web daemon
*/
public UtorrentAdapter(DaemonSettings settings) {
this.settings = settings;
}
@Override
public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try {
switch (task.getMethod()) {
case Retrieve:
// Request all torrents from server
JSONObject result = makeUtorrentRequest(log, "&list=1");
if (result.has("rssfeeds")) {
parseJsonRemoteRssLists(result.getJSONArray("rssfeeds"));
}
return new RetrieveTaskSuccessResult((RetrieveTask) task,
parseJsonRetrieveTorrents(result.getJSONArray("torrents")),
parseJsonRetrieveGetLabels(result.getJSONArray("label")));
case GetTorrentDetails:
// Request fine details of a specific torrent
JSONObject dresult = makeUtorrentRequest(log,
"&action=getprops" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task,
parseJsonTorrentDetails(dresult.getJSONArray("props")));
case GetFileList:
// Get the file listing of a torrent
JSONObject files = makeUtorrentRequest(log,
"&action=getfiles" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new GetFileListTaskSuccessResult((GetFileListTask) task,
parseJsonFileListing(files.getJSONArray("files").getJSONArray(1), task.getTargetTorrent()));
case AddByFile:
// Add a torrent to the server by sending the contents of a local .torrent file
String file = ((AddByFileTask) task).getFile();
uploadTorrentFile(file);
return new DaemonTaskSuccessResult(task);
case AddByUrl:
// Request to add a torrent by URL
String url = ((AddByUrlTask) task).getUrl();
if (url == null || url.equals("")) {
throw new DaemonException(DaemonException.ExceptionType.ParsingFailed, "No url specified");
}
makeUtorrentRequest(log, "&action=add-url&s=" + URLEncoder.encode(url, "UTF-8"));
return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl:
// Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask) task).getUrl();
makeUtorrentRequest(log, "&action=add-url&s=" + URLEncoder.encode(magnet, "UTF-8"));
return new DaemonTaskSuccessResult(task);
case Remove:
// Remove a torrent
RemoveTask removeTask = (RemoveTask) task;
if (removeTask.includingData()) {
makeUtorrentRequest(log,
"&action=removedata" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
} else {
makeUtorrentRequest(log,
"&action=remove" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
}
return new DaemonTaskSuccessResult(task);
case Pause:
// Pause a torrent
makeUtorrentRequest(log, "&action=pause" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task);
case PauseAll:
// Pause all torrents
makeUtorrentRequest(log, "&action=pause" + getAllHashes(log));
return new DaemonTaskSuccessResult(task);
case Resume:
// Resume a torrent
makeUtorrentRequest(log, "&action=unpause" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task);
case ResumeAll:
// Resume all torrents
makeUtorrentRequest(log, "&action=unpause" + getAllHashes(log));
return new DaemonTaskSuccessResult(task);
case Stop:
// Stop a torrent
makeUtorrentRequest(log, "&action=stop" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task);
case StopAll:
// Stop all torrents
makeUtorrentRequest(log, "&action=stop" + getAllHashes(log));
return new DaemonTaskSuccessResult(task);
case Start:
// Start a torrent (maybe forced)
StartTask startTask = (StartTask) task;
if (startTask.isForced()) {
makeUtorrentRequest(log,
"&action=forcestart" + RPC_URL_HASH + startTask.getTargetTorrent().getUniqueID());
} else {
makeUtorrentRequest(log,
"&action=start" + RPC_URL_HASH + startTask.getTargetTorrent().getUniqueID());
}
return new DaemonTaskSuccessResult(task);
case StartAll:
// Start all torrents
makeUtorrentRequest(log, "&action=start" + getAllHashes(log));
return new DaemonTaskSuccessResult(task);
case SetFilePriorities:
// Set priorities of the files of some torrent
SetFilePriorityTask prioTask = (SetFilePriorityTask) task;
String prioUrl = "&p=" + convertPriority(prioTask.getNewPriority());
for (TorrentFile forFile : prioTask.getForFiles()) {
prioUrl += "&f=" + forFile.getKey();
}
makeUtorrentRequest(log,
"&action=setprio" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID() + prioUrl);
return new DaemonTaskSuccessResult(task);
case SetTransferRates:
// Request to set the maximum transfer rates
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
makeUtorrentRequest(log, "&action=setsetting&s=ul_auto_throttle&v=0&s=max_ul_rate&v=" +
(ratesTask.getUploadRate() == null ? 0 : ratesTask.getUploadRate()) +
"&s=max_dl_rate&v=" +
(ratesTask.getDownloadRate() == null ? 0 : ratesTask.getDownloadRate()));
return new DaemonTaskSuccessResult(task);
case SetLabel:
// Set the label of some torrent
SetLabelTask labelTask = (SetLabelTask) task;
makeUtorrentRequest(log,
"&action=setprops" + RPC_URL_HASH + labelTask.getTargetTorrent().getUniqueID() +
"&s=label&v=" + URLEncoder.encode(labelTask.getNewLabel(), "UTF-8"));
return new DaemonTaskSuccessResult(task);
case SetTrackers:
// Set the trackers of some torrent
SetTrackersTask trackersTask = (SetTrackersTask) task;
// Build list of tracker lines, separated by a \r\n
String newTrackersText = "";
for (String tracker : trackersTask.getNewTrackers()) {
newTrackersText += (newTrackersText.length() == 0 ? "" : "\r\n") + tracker;
}
makeUtorrentRequest(log,
"&action=setprops" + RPC_URL_HASH + trackersTask.getTargetTorrent().getUniqueID() +
"&s=trackers&v=" + URLEncoder.encode(newTrackersText, "UTF-8"));
return new DaemonTaskSuccessResult(task);
case ForceRecheck:
// Force re-check of data on a torrent
makeUtorrentRequest(log, "&action=recheck" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task);
default:
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported,
task.getMethod() + " is not supported by " + getType()));
}
} catch (JSONException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString()));
} catch (DaemonException e) {
return new DaemonTaskFailureResult(task, e);
} catch (FileNotFoundException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.FileAccessError, e.toString()));
} catch (UnsupportedEncodingException e) {
return new DaemonTaskFailureResult(task,
new DaemonException(ExceptionType.MethodUnsupported, e.toString()));
} catch (IOException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ConnectionError, e.toString()));
}
}
private void parseJsonRemoteRssLists(JSONArray results) {
remoteRssChannels = new ArrayList<>();
RemoteRssChannel item;
for (int i = 0; i < results.length(); i++) {
try {
item = new UTorrentRemoteRssChannel(results.getJSONArray(i));
remoteRssChannels.add(item);
} catch (JSONException e) {
// Ignore unparseable items so app doesn't crash.
// Haven't run into a case where this fails, yet.
e.printStackTrace();
}
}
Collections.sort(remoteRssChannels, new Comparator<RemoteRssChannel>() {
@Override
public int compare(RemoteRssChannel lhs, RemoteRssChannel rhs) {
return lhs.getName().compareToIgnoreCase(rhs.getName());
}
});
}
private ArrayList<Label> parseJsonRetrieveGetLabels(JSONArray lresults) throws JSONException {
// Parse response
ArrayList<Label> labels = new ArrayList<Label>();
for (int i = 0; i < lresults.length(); i++) {
JSONArray lab = lresults.getJSONArray(i);
String name = lab.getString(NAME_IDX);
int count = lab.getInt(COUNT_IDX);
labels.add(new Label(name, count));
}
return labels;
}
private JSONObject makeUtorrentRequest(Log log, String addToUrl) throws DaemonException {
return makeUtorrentRequest(log, addToUrl, 0);
}
private JSONObject makeUtorrentRequest(Log log, String addToUrl, int retried) throws DaemonException {
try {
// Initialise the HTTP client
if (httpclient == null) {
initialise();
}
ensureToken();
// Make request
HttpGet httpget = new HttpGet(buildWebUIUrl() + "?token=" + authtoken + addToUrl);
HttpResponse response = httpclient.execute(httpget);
// Read JSON response
InputStream instream = response.getEntity().getContent();
String result = HttpHelper.convertStreamToString(instream);
if ((result.equals("") || result.trim().equals("invalid request"))) {
// Auth token was invalidated; retry at max 3 times
authtoken = null; // So that ensureToken() will request a new token on the next try
if (retried < 2) {
return makeUtorrentRequest(log, addToUrl, ++retried);
}
throw new DaemonException(ExceptionType.AuthenticationFailure,
"Response was '" + result.replace("\n", "") +
"' instead of a proper JSON object (and we used auth token '" + authtoken + "')");
}
JSONObject json = new JSONObject(result);
instream.close();
return json;
} catch (DaemonException e) {
throw e;
} catch (JSONException e) {
log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ParsingFailed, e.toString());
} catch (Exception e) {
log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString());
}
}
private synchronized void ensureToken() throws IOException, DaemonException {
// Make sure we have a valid token
if (authtoken == null) {
// Make a request to /gui/token.html
// See https://github.com/bittorrent/webui/wiki/TokenSystem
HttpGet httpget = new HttpGet(buildWebUIUrl() + "token.html");
// Parse the response HTML
HttpResponse response = httpclient.execute(httpget);
if (response.getStatusLine().getStatusCode() == 401) {
throw new DaemonException(ExceptionType.AuthenticationFailure,
"Auth denied (401) on token.html retrieval");
}
if (response.getStatusLine().getStatusCode() == 404) {
throw new DaemonException(ExceptionType.ConnectionError,
"Not found (404); server doesn't exist or is inaccessible");
}
InputStream instream = response.getEntity().getContent();
String result = HttpHelper.convertStreamToString(instream);
authtoken = result.replaceAll("<.*?>", "").trim();
}
}
public JSONObject uploadTorrentFile(String file) throws DaemonException, IOException, JSONException {
// Initialise the HTTP client
if (httpclient == null) {
initialise();
}
ensureToken();
// Build and make request
HttpPost httppost = new HttpPost(buildWebUIUrl() + "?token=" + authtoken + "&action=add-file");
File upload = new File(URI.create(file));
Part[] parts = {new FilePart("torrent_file", upload, FilePart.DEFAULT_CONTENT_TYPE, null)};
httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));
HttpResponse response = httpclient.execute(httppost);
// Read JSON response
InputStream instream = response.getEntity().getContent();
String result = HttpHelper.convertStreamToString(instream);
JSONObject json = new JSONObject(result);
instream.close();
return json;
}
/**
* Instantiates an HTTP client with proper credentials that can be used for all Transmission requests.
* @throws DaemonException On conflicting or missing settings
*/
private void initialise() throws DaemonException {
this.httpclient = HttpHelper.createStandardHttpClient(settings, true);
}
/**
* Build the URL of the Transmission web UI from the user settings.
* @return The URL of the RPC API
*/
private String buildWebUIUrl() {
String folder = settings.getFolder() == null ? "" : settings.getFolder().trim();
if (!folder.startsWith("/")) {
// Add leading slash
folder = "/" + folder;
}
if (folder.endsWith("/")) {
// Strip trailing slash
folder = folder.substring(0, folder.length() - 1);
}
return (settings.getSsl() ? "https://" : "http://") + settings.getAddress().trim() + ":" + settings.getPort() + folder + "/gui/";
}
private TorrentStatus convertUtorrentStatus(int uStatus, boolean finished) {
// Convert bitwise int to uTorrent status codes
// Now based on http://forum.utorrent.com/viewtopic.php?id=50779
if ((uStatus & 1) == 1) {
// Started
if ((uStatus & 32) == 32) {
// Paused
return TorrentStatus.Paused;
} else if (finished) {
return TorrentStatus.Seeding;
} else {
return TorrentStatus.Downloading;
}
} else if ((uStatus & 2) == 2) {
// Checking
return TorrentStatus.Checking;
} else if ((uStatus & 16) == 16) {
// Error
return TorrentStatus.Error;
} else if ((uStatus & 128) == 128) {
// Queued
return TorrentStatus.Queued;
} else {
return TorrentStatus.Waiting;
}
}
private Priority convertUtorrentPriority(int code) {
switch (code) {
case 0:
return Priority.Off;
case 1:
return Priority.Low;
case 3:
return Priority.High;
default:
return Priority.Normal;
}
}
private int convertPriority(Priority newPriority) {
if (newPriority == null) {
return 2;
}
switch (newPriority) {
case Off:
return 0;
case Low:
return 1;
case High:
return 3;
default:
return 2;
}
}
private ArrayList<Torrent> parseJsonRetrieveTorrents(JSONArray results) throws JSONException {
// Parse response
ArrayList<Torrent> torrents = new ArrayList<Torrent>();
boolean createPaths = !(settings.getDownloadDir() == null || settings.getDownloadDir().equals(""));
for (int i = 0; i < results.length(); i++) {
JSONArray tor = results.getJSONArray(i);
String name = tor.getString(RPC_NAME_IDX);
boolean downloaded = (tor.getLong(RPC_PARTDONE) == 1000l);
float available = ((float) tor.getInt(RPC_AVAILABILITY_IDX)) / 65536f; // Integer in 1/65536ths
// The full torrent path is not available in uTorrent web UI API
// Guess the torrent's directory based on the user-specific default download dir and the torrent name
String dir = null;
if (createPaths) {
dir = settings.getDownloadDir();
if (name.length() < 4 || name.charAt(name.length() - 4) != '.') {
// Assume this is a directory rather than a single-file torrent
dir += name + settings.getOS().getPathSeperator();
}
}
// Add the parsed torrent to the list
TorrentStatus status = convertUtorrentStatus(tor.getInt(RPC_STATUS_IDX), downloaded);
long addedOn = tor.optInt(RPC_ADDEDON_IDX, -1);
long completedOn = tor.optInt(RPC_COMPLETEDON_IDX, -1);
Date addedOnDate = addedOn == -1 ? null : new Date(addedOn * 1000L);
Date completedOnDate = completedOn == -1 ? null : new Date(completedOn * 1000L);
torrents.add(new Torrent(i, // No ID but a hash is used
tor.getString(RPC_HASH_IDX), name, status, dir, tor.getInt(RPC_DOWNLOADSPEED_IDX),
tor.getInt(RPC_UPLOADSPEED_IDX), tor.getInt(RPC_SEEDSCONNECTED_IDX),
tor.getInt(RPC_SEEDSINSWARM_IDX), tor.getInt(RPC_PEERSCONNECTED_IDX),
tor.getInt(RPC_PEERSINSWARM_IDX), tor.getInt(RPC_ETA_IDX), tor.getLong(RPC_DOWNLOADED_IDX),
tor.getLong(RPC_UPLOADED_IDX), tor.getLong(RPC_SIZE_IDX),
((float) tor.getLong(RPC_PARTDONE)) / 1000f, // Integer in promille
Math.min(available, 1f), // Can be > 100% if multiple peers have 100%
tor.getString(RPC_LABEL_IDX).trim(), addedOnDate, completedOnDate,
// uTorrent doesn't give the error message, so just remind that there is some error
status == TorrentStatus.Error ? "See GUI for error message" : null, settings.getType()));
}
return torrents;
}
private TorrentDetails parseJsonTorrentDetails(JSONArray results) throws JSONException {
// Parse response
// NOTE: Assumes only details for one torrent are requested at a time
if (results.length() > 0) {
JSONObject tor = results.getJSONObject(0);
List<String> trackers = new ArrayList<String>();
for (String tracker : tor.getString("trackers").split("\\r\\n")) {
// Ignore any blank lines
if (!tracker.trim().equals("")) {
trackers.add(tracker.trim());
}
}
// uTorrent doesn't support tracker error messages in the web UI
// See http://forum.utorrent.com/viewtopic.php?pid=553340#p553340
return new TorrentDetails(trackers, null);
}
return null;
}
private ArrayList<TorrentFile> parseJsonFileListing(JSONArray results, Torrent torrent) throws JSONException {
// Parse response
ArrayList<TorrentFile> files = new ArrayList<TorrentFile>();
boolean createPaths =
torrent != null && torrent.getLocationDir() != null && !torrent.getLocationDir().equals("");
final String pathSep = settings.getOS().getPathSeperator();
for (int i = 0; i < results.length(); i++) {
JSONArray file = results.getJSONArray(i);
// Add the parsed torrent to the list
files.add(new TorrentFile("" + i, file.getString(RPC_FILENAME_IDX), // Name
(createPaths ?
file.getString(RPC_FILENAME_IDX).replace((pathSep.equals("/") ? "\\" : "/"), pathSep) :
null), // Relative path; 'wrong' path slashes will be replaced
(createPaths ? torrent.getLocationDir() +
file.getString(RPC_FILENAME_IDX).replace((pathSep.equals("/") ? "\\" : "/"), pathSep) :
null), // Full path; 'wrong' path slashes will be replaced
file.getLong(RPC_FILESIZE_IDX), // Total size
file.getLong(RPC_FILEDOWNLOADED_IDX), // Part done
convertUtorrentPriority(file.getInt(RPC_FILEPRIORITY_IDX)))); // Priority
}
return files;
}
private String getAllHashes(Log log) throws DaemonException, JSONException {
// Make a retrieve torrents call first to gather all hashes
JSONObject result = makeUtorrentRequest(log, "&list=1");
ArrayList<Torrent> torrents = parseJsonRetrieveTorrents(result.getJSONArray("torrents"));
// Build a string of hashes of all the torrents
String hashes = "";
for (Torrent torrent : torrents) {
hashes += RPC_URL_HASH + torrent.getUniqueID();
}
return hashes;
}
@Override
public Daemon getType() {
return settings.getType();
}
@Override
public DaemonSettings getSettings() {
return this.settings;
}
public ArrayList<RemoteRssChannel> getRemoteRssChannels(Log log) {
return remoteRssChannels;
}
@Override
public void downloadRemoteRssItem(Log log, RemoteRssItem rssItem, RemoteRssChannel rssChannel) throws DaemonException {
final String link = rssItem.getLink();
try {
makeUtorrentRequest(log, "&action=add-url&s=" + URLEncoder.encode(link, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new DaemonException(ExceptionType.ParsingFailed, "Invalid URL: " + link);
}
}
}