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
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); |
|
} |
|
} |
|
|
|
}
|
|
|