Browse Source

Merging qBittorrent adapter fixes with support for 3.2+, added proper version number retrieval.

material
Eric Kok 9 years ago
parent
commit
5d68d06122
  1. 346
      app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java

346
app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java

@ -17,24 +17,18 @@
*/ */
package org.transdroid.daemon.Qbittorrent; package org.transdroid.daemon.Qbittorrent;
import java.io.File; import com.android.internalcopy.http.multipart.FilePart;
import java.io.FileNotFoundException; import com.android.internalcopy.http.multipart.MultipartEntity;
import java.io.UnsupportedEncodingException; import com.android.internalcopy.http.multipart.Part;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.cookie.Cookie;
import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HTTP;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -42,6 +36,7 @@ import org.json.JSONObject;
import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Priority; import org.transdroid.daemon.Priority;
@ -49,7 +44,6 @@ import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentDetails; import org.transdroid.daemon.TorrentDetails;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.TorrentStatus; import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.task.AddByFileTask; import org.transdroid.daemon.task.AddByFileTask;
import org.transdroid.daemon.task.AddByMagnetUrlTask; import org.transdroid.daemon.task.AddByMagnetUrlTask;
import org.transdroid.daemon.task.AddByUrlTask; import org.transdroid.daemon.task.AddByUrlTask;
@ -67,9 +61,15 @@ import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetFilePriorityTask; import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import com.android.internalcopy.http.multipart.FilePart;
import com.android.internalcopy.http.multipart.MultipartEntity; import java.io.File;
import com.android.internalcopy.http.multipart.Part; import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** /**
* The daemon adapter for the qBittorrent torrent client. * The daemon adapter for the qBittorrent torrent client.
@ -89,64 +89,69 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
private synchronized void ensureVersion(Log log) throws DaemonException { private synchronized void ensureVersion(Log log) throws DaemonException {
if (version > 0) // Still need to retrieve the API and qBittorrent version numbers from the server?
if (version > 0 && apiVersion > 0)
return; return;
// We still need to retrieve the version number from the server
// Do this by getting the web interface about page and trying to parse the version number
// Format is something like 'qBittorrent v2.9.7 (Web UI)'
try { try {
String apiVerText = makeRequest(log, "/version/api");
apiVersion = Integer.parseInt(apiVerText.trim());
}
catch (DaemonException e) {
apiVersion = 1;
}
catch (NumberFormatException e) {
apiVersion = 1;
}
log.d(LOG_NAME, "qBittorrent API version is " + apiVersion); // The API version is only supported since qBittorrent 3.2, so otherwise we assume version 1
try {
String apiVerText = makeRequest(log, "/version/api");
apiVersion = Integer.parseInt(apiVerText.trim());
} catch (DaemonException | NumberFormatException e) {
apiVersion = 1;
}
log.d(LOG_NAME, "qBittorrent API version is " + apiVersion);
// TODO: In API ver 2, query this information from /version/qbittorrent instead. // The qBittorent version is only supported since 3.2; for earlier versions we parse the about dialog and parse it
// For now at least this works fine, though String versionText = "";
String about = makeRequest(log, "/about.html"); if (apiVersion > 1) {
String aboutStartText = "qBittorrent v"; // Format is something like 'v3.2.0'
String aboutEndText = " (Web UI)"; versionText = makeRequest(log, "/version/qbittorrent").substring(1);
int aboutStart = about.indexOf(aboutStartText); } else {
int aboutEnd = about.indexOf(aboutEndText); // Format is something like 'qBittorrent v2.9.7 (Web UI)' or 'qBittorrent v3.0.0-alpha5 (Web UI)'
try { String about = makeRequest(log, "/about.html");
if (aboutStart >= 0 && aboutEnd > aboutStart) { String aboutStartText = "qBittorrent v";
// String found: now parse a version like 2.9.7 as a number like 20907 (allowing 10 places for each .) String aboutEndText = " (Web UI)";
String[] parts = about.substring(aboutStart + aboutStartText.length(), aboutEnd).split("\\."); int aboutStart = about.indexOf(aboutStartText);
if (parts.length > 0) { int aboutEnd = about.indexOf(aboutEndText);
version = Integer.parseInt(parts[0]) * 100 * 100; if (aboutStart >= 0 && aboutEnd > aboutStart) {
if (parts.length > 1) { versionText = about.substring(aboutStart + aboutStartText.length(), aboutEnd);
version += Integer.parseInt(parts[1]) * 100; }
if (parts.length > 2) { }
// For the last part only read until a non-numeric character is read
// For example version 3.0.0-alpha5 is read as version code 30000 // String found: now parse a version like 2.9.7 as a number like 20907 (allowing 10 places for each .)
String numbers = ""; String[] parts = versionText.split("\\.");
for (char c : parts[2].toCharArray()) { if (parts.length > 0) {
if (Character.isDigit(c)) version = Integer.parseInt(parts[0]) * 100 * 100;
// Still a number; add it to the numbers string if (parts.length > 1) {
numbers += Character.toString(c); version += Integer.parseInt(parts[1]) * 100;
else { if (parts.length > 2) {
// No longer reading numbers; stop reading // For the last part only read until a non-numeric character is read
break; // For example version 3.0.0-alpha5 is read as version code 30000
} String numbers = "";
for (char c : parts[2].toCharArray()) {
if (Character.isDigit(c))
// Still a number; add it to the numbers string
numbers += Character.toString(c);
else {
// No longer reading numbers; stop reading
break;
} }
version += Integer.parseInt(numbers);
return;
} }
version += Integer.parseInt(numbers);
return;
} }
} }
} }
} catch (NumberFormatException e) {
} catch (Exception e) {
// Unable to establish version number; assume an old version by setting it to version 1
version = 10000;
apiVersion = 1;
} }
// Unable to establish version number; assume an old version by setting it to version 1
version = 10000;
apiVersion = 1;
} }
private synchronized void ensureAuthenticated(Log log) throws DaemonException { private synchronized void ensureAuthenticated(Log log) throws DaemonException {
@ -166,8 +171,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
} }
makeRequest(log, "/login", makeRequest(log, "/login", new BasicNameValuePair("username", settings.getUsername()),
new BasicNameValuePair("username", settings.getUsername()),
new BasicNameValuePair("password", settings.getPassword())); new BasicNameValuePair("password", settings.getPassword()));
// The HttpClient will automatically remember the cookie for us, no need to parse it out. // The HttpClient will automatically remember the cookie for us, no need to parse it out.
@ -192,129 +196,126 @@ public class QbittorrentAdapter implements IDaemonAdapter {
ensureAuthenticated(log); ensureAuthenticated(log);
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
String path; String path;
if (version >= 30200) { if (version >= 30200) {
path = "/query/torrents"; path = "/query/torrents";
} else if (version >= 30000) { } else if (version >= 30000) {
path = "/json/torrents"; path = "/json/torrents";
} else { } else {
path = "/json/events"; path = "/json/events";
} }
// Request all torrents from server // Request all torrents from server
JSONArray result = new JSONArray(makeRequest(log, path)); JSONArray result = new JSONArray(makeRequest(log, path));
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null); return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null);
case GetTorrentDetails: case GetTorrentDetails:
// Request tracker and error details for a specific teacher // Request tracker and error details for a specific teacher
String mhash = task.getTargetTorrent().getUniqueID(); String mhash = task.getTargetTorrent().getUniqueID();
JSONArray messages = new JSONArray(makeRequest(log, JSONArray messages =
(version >= 30200 ? "/query/propertiesTrackers/" : "/json/propertiesTrackers/") + mhash)); new JSONArray(makeRequest(log, (version >= 30200 ? "/query/propertiesTrackers/" : "/json/propertiesTrackers/") + mhash));
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(messages));
parseJsonTorrentDetails(messages));
case GetFileList: case GetFileList:
// Request files listing for a specific torrent // Request files listing for a specific torrent
String fhash = task.getTargetTorrent().getUniqueID(); String fhash = task.getTargetTorrent().getUniqueID();
JSONArray files = new JSONArray(makeRequest(log, JSONArray files =
(version >= 30200 ? "/query/propertiesFiles/" : "/json/propertiesFiles/") + fhash)); new JSONArray(makeRequest(log, (version >= 30200 ? "/query/propertiesFiles/" : "/json/propertiesFiles/") + fhash));
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files)); return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files));
case AddByFile: case AddByFile:
// Upload a local .torrent file // Upload a local .torrent file
String ufile = ((AddByFileTask) task).getFile(); String ufile = ((AddByFileTask) task).getFile();
makeUploadRequest("/command/upload", ufile, log); makeUploadRequest("/command/upload", ufile, log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByUrl: case AddByUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask) task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
makeRequest(log, "/command/download", new BasicNameValuePair("urls", url)); makeRequest(log, "/command/download", new BasicNameValuePair("urls", url));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
// Request to add a magnet link by URL // Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask) task).getUrl(); String magnet = ((AddByMagnetUrlTask) task).getUrl();
makeRequest(log, "/command/download", new BasicNameValuePair("urls", magnet)); makeRequest(log, "/command/download", new BasicNameValuePair("urls", magnet));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
makeRequest(log, (removeTask.includingData() ? "/command/deletePerm" : "/command/delete"), makeRequest(log, (removeTask.includingData() ? "/command/deletePerm" : "/command/delete"),
new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID())); new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Pause: case Pause:
// Pause a torrent // Pause a torrent
makeRequest(log, "/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); makeRequest(log, "/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
// Resume all torrents // Resume all torrents
makeRequest(log, "/command/pauseall"); makeRequest(log, "/command/pauseall");
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // Resume a torrent
makeRequest(log, "/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); makeRequest(log, "/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
// Resume all torrents // Resume all torrents
makeRequest(log, "/command/resumeall"); makeRequest(log, "/command/resumeall");
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetFilePriorities: case SetFilePriorities:
// Update the priorities to a set of files // Update the priorities to a set of files
SetFilePriorityTask setPrio = (SetFilePriorityTask) task; SetFilePriorityTask setPrio = (SetFilePriorityTask) task;
String newPrio = "0"; String newPrio = "0";
if (setPrio.getNewPriority() == Priority.Low) { if (setPrio.getNewPriority() == Priority.Low) {
newPrio = "1"; newPrio = "1";
} else if (setPrio.getNewPriority() == Priority.Normal) { } else if (setPrio.getNewPriority() == Priority.Normal) {
newPrio = "2"; newPrio = "2";
} else if (setPrio.getNewPriority() == Priority.High) { } else if (setPrio.getNewPriority() == Priority.High) {
newPrio = "7"; newPrio = "7";
} }
// We have to make a separate request per file, it seems // We have to make a separate request per file, it seems
for (TorrentFile file : setPrio.getForFiles()) { for (TorrentFile file : setPrio.getForFiles()) {
makeRequest(log, "/command/setFilePrio", new BasicNameValuePair("hash", task.getTargetTorrent() makeRequest(log, "/command/setFilePrio", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()),
.getUniqueID()), new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair( new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair("priority", newPrio));
"priority", newPrio)); }
} return new DaemonTaskSuccessResult(task);
return new DaemonTaskSuccessResult(task);
case SetTransferRates:
case SetTransferRates:
// TODO: This doesn't seem to work yet
// TODO: This doesn't seem to work yet // Request to set the maximum transfer rates
// Request to set the maximum transfer rates SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; int dl = (ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate());
int dl = (ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate()); int ul = (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate());
int ul = (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate());
// First get the preferences
// First get the preferences JSONObject prefs = new JSONObject(makeRequest(log, "/json/preferences"));
JSONObject prefs = new JSONObject(makeRequest(log, "/json/preferences")); prefs.put("dl_limit", dl);
prefs.put("dl_limit", dl); prefs.put("up_limit", ul);
prefs.put("up_limit", ul); makeRequest(log, "/command/setPreferences", new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8)));
makeRequest(log, "/command/setPreferences", return new DaemonTaskSuccessResult(task);
new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8)));
return new DaemonTaskSuccessResult(task); default:
return new DaemonTaskFailureResult(task,
default: new DaemonException(ExceptionType.MethodUnsupported, task.getMethod() + " is not supported by " + getType()));
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported,
task.getMethod() + " is not supported by " + getType()));
} }
} catch (JSONException e) { } catch (JSONException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString()));
@ -331,7 +332,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// Setup request using POST // Setup request using POST
HttpPost httppost = new HttpPost(buildWebUIUrl(path)); HttpPost httppost = new HttpPost(buildWebUIUrl(path));
List<NameValuePair> nvps = new ArrayList<NameValuePair>(); List<NameValuePair> nvps = new ArrayList<>();
Collections.addAll(nvps, params); Collections.addAll(nvps, params);
httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
return makeWebRequest(httppost, log); return makeWebRequest(httppost, log);
@ -349,7 +350,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// Setup request using POST // Setup request using POST
HttpPost httppost = new HttpPost(buildWebUIUrl(path)); HttpPost httppost = new HttpPost(buildWebUIUrl(path));
File upload = new File(URI.create(file)); File upload = new File(URI.create(file));
Part[] parts = { new FilePart("torrentfile", upload) }; Part[] parts = {new FilePart("torrentfile", upload)};
httppost.setEntity(new MultipartEntity(parts, httppost.getParams())); httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));
return makeWebRequest(httppost, log); return makeWebRequest(httppost, log);
@ -414,8 +415,8 @@ public class QbittorrentAdapter implements IDaemonAdapter {
private TorrentDetails parseJsonTorrentDetails(JSONArray messages) throws JSONException { private TorrentDetails parseJsonTorrentDetails(JSONArray messages) throws JSONException {
ArrayList<String> trackers = new ArrayList<String>(); ArrayList<String> trackers = new ArrayList<>();
ArrayList<String> errors = new ArrayList<String>(); ArrayList<String> errors = new ArrayList<>();
// Parse response // Parse response
if (messages.length() > 0) { if (messages.length() > 0) {
@ -436,7 +437,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
private ArrayList<Torrent> parseJsonTorrents(JSONArray response) throws JSONException { private ArrayList<Torrent> parseJsonTorrents(JSONArray response) throws JSONException {
// Parse response // Parse response
ArrayList<Torrent> torrents = new ArrayList<Torrent>(); ArrayList<Torrent> torrents = new ArrayList<>();
for (int i = 0; i < response.length(); i++) { for (int i = 0; i < response.length(); i++) {
JSONObject tor = response.getJSONObject(i); JSONObject tor = response.getJSONObject(i);
int leechers[] = parsePeers(tor.getString("num_leechs")); int leechers[] = parsePeers(tor.getString("num_leechs"));
@ -533,10 +534,9 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// In some situations it it just a "6" string // In some situations it it just a "6" string
String[] parts = seeds.split(" "); String[] parts = seeds.split(" ");
if (parts.length > 1) { if (parts.length > 1) {
return new int[] { Integer.parseInt(parts[0]), return new int[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[1].substring(1, parts[1].length() - 1))};
Integer.parseInt(parts[1].substring(1, parts[1].length() - 1)) };
} }
return new int[] { Integer.parseInt(parts[0]), Integer.parseInt(parts[0]) }; return new int[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[0])};
} }
private int parseSpeed(String speed) { private int parseSpeed(String speed) {
@ -612,8 +612,8 @@ public class QbittorrentAdapter implements IDaemonAdapter {
size = parseSize(file.getString("size")); size = parseSize(file.getString("size"));
} }
torrentfiles.add(new TorrentFile("" + i, file.getString("name"), null, null, size, (long) (size * file torrentfiles.add(new TorrentFile("" + i, file.getString("name"), null, null, size, (long) (size * file.getDouble("progress")),
.getDouble("progress")), parsePriority(file.getInt("priority")))); parsePriority(file.getInt("priority"))));
} }
// Return the list // Return the list

Loading…
Cancel
Save