|
|
@ -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,36 +89,40 @@ 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 { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The API version is only supported since qBittorrent 3.2, so otherwise we assume version 1
|
|
|
|
try { |
|
|
|
try { |
|
|
|
String apiVerText = makeRequest(log, "/version/api"); |
|
|
|
String apiVerText = makeRequest(log, "/version/api"); |
|
|
|
apiVersion = Integer.parseInt(apiVerText.trim()); |
|
|
|
apiVersion = Integer.parseInt(apiVerText.trim()); |
|
|
|
} |
|
|
|
} catch (DaemonException | NumberFormatException e) { |
|
|
|
catch (DaemonException e) { |
|
|
|
|
|
|
|
apiVersion = 1; |
|
|
|
apiVersion = 1; |
|
|
|
} |
|
|
|
} |
|
|
|
catch (NumberFormatException e) { |
|
|
|
|
|
|
|
apiVersion = 1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log.d(LOG_NAME, "qBittorrent API version is " + apiVersion); |
|
|
|
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 = ""; |
|
|
|
|
|
|
|
if (apiVersion > 1) { |
|
|
|
|
|
|
|
// Format is something like 'v3.2.0'
|
|
|
|
|
|
|
|
versionText = makeRequest(log, "/version/qbittorrent").substring(1); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// Format is something like 'qBittorrent v2.9.7 (Web UI)' or 'qBittorrent v3.0.0-alpha5 (Web UI)'
|
|
|
|
String about = makeRequest(log, "/about.html"); |
|
|
|
String about = makeRequest(log, "/about.html"); |
|
|
|
String aboutStartText = "qBittorrent v"; |
|
|
|
String aboutStartText = "qBittorrent v"; |
|
|
|
String aboutEndText = " (Web UI)"; |
|
|
|
String aboutEndText = " (Web UI)"; |
|
|
|
int aboutStart = about.indexOf(aboutStartText); |
|
|
|
int aboutStart = about.indexOf(aboutStartText); |
|
|
|
int aboutEnd = about.indexOf(aboutEndText); |
|
|
|
int aboutEnd = about.indexOf(aboutEndText); |
|
|
|
try { |
|
|
|
|
|
|
|
if (aboutStart >= 0 && aboutEnd > aboutStart) { |
|
|
|
if (aboutStart >= 0 && aboutEnd > aboutStart) { |
|
|
|
|
|
|
|
versionText = about.substring(aboutStart + aboutStartText.length(), aboutEnd); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// String found: now parse a version like 2.9.7 as a number like 20907 (allowing 10 places for each .)
|
|
|
|
// String found: now parse a version like 2.9.7 as a number like 20907 (allowing 10 places for each .)
|
|
|
|
String[] parts = about.substring(aboutStart + aboutStartText.length(), aboutEnd).split("\\."); |
|
|
|
String[] parts = versionText.split("\\."); |
|
|
|
if (parts.length > 0) { |
|
|
|
if (parts.length > 0) { |
|
|
|
version = Integer.parseInt(parts[0]) * 100 * 100; |
|
|
|
version = Integer.parseInt(parts[0]) * 100 * 100; |
|
|
|
if (parts.length > 1) { |
|
|
|
if (parts.length > 1) { |
|
|
@ -141,14 +145,15 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} catch (NumberFormatException e) { |
|
|
|
} catch (Exception e) { |
|
|
|
} |
|
|
|
|
|
|
|
// Unable to establish version number; assume an old version by setting it to version 1
|
|
|
|
// Unable to establish version number; assume an old version by setting it to version 1
|
|
|
|
version = 10000; |
|
|
|
version = 10000; |
|
|
|
apiVersion = 1; |
|
|
|
apiVersion = 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private synchronized void ensureAuthenticated(Log log) throws DaemonException { |
|
|
|
private synchronized void ensureAuthenticated(Log log) throws DaemonException { |
|
|
|
// API changed in 3.2.0, login is now handled by its own request, which provides you a cookie.
|
|
|
|
// API changed in 3.2.0, login is now handled by its own request, which provides you a cookie.
|
|
|
|
// If we don't have that cookie, let's try and get it.
|
|
|
|
// If we don't have that cookie, let's try and get it.
|
|
|
@ -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.
|
|
|
|
|
|
|
|
|
|
|
@ -210,17 +214,16 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
|
|
|
|
|
|
|
|
// 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: |
|
|
@ -290,9 +293,8 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
} |
|
|
|
} |
|
|
|
// 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); |
|
|
|
|
|
|
|
|
|
|
@ -308,13 +310,12 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
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", |
|
|
|
makeRequest(log, "/command/setPreferences", new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8))); |
|
|
|
new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8))); |
|
|
|
|
|
|
|
return new DaemonTaskSuccessResult(task); |
|
|
|
return new DaemonTaskSuccessResult(task); |
|
|
|
|
|
|
|
|
|
|
|
default: |
|
|
|
default: |
|
|
|
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, |
|
|
|
return new DaemonTaskFailureResult(task, |
|
|
|
task.getMethod() + " is not supported by " + getType())); |
|
|
|
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
|
|
|
|