|
|
@ -45,7 +45,26 @@ 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.task.*; |
|
|
|
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.GetStatsTask; |
|
|
|
|
|
|
|
import org.transdroid.daemon.task.GetStatsTaskSuccessResult; |
|
|
|
|
|
|
|
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.SetDownloadLocationTask; |
|
|
|
|
|
|
|
import org.transdroid.daemon.task.SetFilePriorityTask; |
|
|
|
|
|
|
|
import org.transdroid.daemon.task.SetLabelTask; |
|
|
|
|
|
|
|
import org.transdroid.daemon.task.SetTransferRatesTask; |
|
|
|
import org.transdroid.daemon.util.HttpHelper; |
|
|
|
import org.transdroid.daemon.util.HttpHelper; |
|
|
|
|
|
|
|
|
|
|
|
import java.io.File; |
|
|
|
import java.io.File; |
|
|
@ -61,6 +80,7 @@ import java.util.Map; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* The daemon adapter for the qBittorrent torrent client. |
|
|
|
* The daemon adapter for the qBittorrent torrent client. |
|
|
|
|
|
|
|
* |
|
|
|
* @author erickok |
|
|
|
* @author erickok |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
@ -70,71 +90,28 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
private DaemonSettings settings; |
|
|
|
private DaemonSettings settings; |
|
|
|
private DefaultHttpClient httpclient; |
|
|
|
private DefaultHttpClient httpclient; |
|
|
|
private int version = -1; |
|
|
|
private int version = -1; |
|
|
|
private int apiVersion = -1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public QbittorrentAdapter(DaemonSettings settings) { |
|
|
|
public QbittorrentAdapter(DaemonSettings settings) { |
|
|
|
this.settings = settings; |
|
|
|
this.settings = settings; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private synchronized void ensureVersion(Log log) throws DaemonException { |
|
|
|
private synchronized void ensureVersion(Log log) { |
|
|
|
// Still need to retrieve the API and qBittorrent version numbers from the server?
|
|
|
|
// Still need to retrieve the API and qBittorrent version numbers from the server?
|
|
|
|
if (version > 0 && apiVersion > 0) |
|
|
|
if (version > 0) |
|
|
|
return; |
|
|
|
return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Since 4.1, API v2 is used. Since qBittorrent 3.2, API v1 is used. Otherwise we use unofficial legacy json endpoints.
|
|
|
|
try { |
|
|
|
try { |
|
|
|
|
|
|
|
|
|
|
|
// Since 4.2.0, old API is dropped. Fallback to old one if the new one failed for version <4.2.0
|
|
|
|
|
|
|
|
// The API version is only supported since qBittorrent 3.2, so otherwise we assume version 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
boolean is_v2 = false; |
|
|
|
|
|
|
|
String apiVersionText = ""; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// First, try the v2 api version endpoint
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
apiVersionText = makeRequest(log, "/api/v2/app/webapiVersion"); |
|
|
|
|
|
|
|
} catch (DaemonException e) { |
|
|
|
|
|
|
|
// 403 Forbidden - endpoint exists. Keep trying v2
|
|
|
|
|
|
|
|
is_v2 = e.getType() == ExceptionType.AuthenticationFailure; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Keep trying
|
|
|
|
|
|
|
|
if (apiVersion < 0) { |
|
|
|
|
|
|
|
if (is_v2) { |
|
|
|
|
|
|
|
// Preemptive assumption, for authentication
|
|
|
|
|
|
|
|
apiVersion = 20300; //2.3.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Authenticate, and try v2 again
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
ensureAuthenticated(log); |
|
|
|
|
|
|
|
apiVersionText = makeRequest(log, "/api/v2/app/webapiVersion"); |
|
|
|
|
|
|
|
} catch (DaemonException e) { |
|
|
|
|
|
|
|
apiVersion = 20300; // assume this is new API 2.3.0 since we are forbidden to access API
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// Fall back to old api
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
apiVersionText = makeRequest(log, "/version/api"); |
|
|
|
|
|
|
|
} catch (DaemonException e) { |
|
|
|
|
|
|
|
apiVersion = 1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (apiVersion < 0) { |
|
|
|
|
|
|
|
apiVersion = parseVersionNumber(apiVersionText); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The qBittorent version is only supported since 3.2; for earlier versions we parse the about dialog and parse it
|
|
|
|
|
|
|
|
// Since 4.2.0, new API version is used instead
|
|
|
|
|
|
|
|
String versionText = ""; |
|
|
|
String versionText = ""; |
|
|
|
if (apiVersion >= 20300) { |
|
|
|
try { |
|
|
|
ensureAuthenticated(log); |
|
|
|
// Try v2 API first, which returns version number in 'v4.1.9' format
|
|
|
|
versionText = makeRequest(log, "/api/v2/app/version").substring(1); |
|
|
|
versionText = makeRequest(log, "/api/v2/app/version").substring(1); |
|
|
|
} else if (apiVersion > 10000) { |
|
|
|
} catch (Exception e1) { |
|
|
|
// Format is something like 'v3.2.0'
|
|
|
|
// Try v1 API, which returns version number in 'v3.2.0' format
|
|
|
|
|
|
|
|
try { |
|
|
|
versionText = makeRequest(log, "/version/qbittorrent").substring(1); |
|
|
|
versionText = makeRequest(log, "/version/qbittorrent").substring(1); |
|
|
|
} else { |
|
|
|
} catch (Exception e2) { |
|
|
|
// Format is something like 'qBittorrent v2.9.7 (Web UI)' or 'qBittorrent v3.0.0-alpha5 (Web UI)'
|
|
|
|
// Legacy mode; 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)"; |
|
|
@ -144,13 +121,13 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
versionText = about.substring(aboutStart + aboutStartText.length(), aboutEnd); |
|
|
|
versionText = about.substring(aboutStart + aboutStartText.length(), aboutEnd); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
version = parseVersionNumber(versionText); |
|
|
|
version = parseVersionNumber(versionText); |
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception 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 = 10000; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
@ -162,7 +139,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
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) { |
|
|
|
version += Float.parseFloat(parts[1]) * 100; |
|
|
|
version += Integer.parseInt(parts[1]) * 100; |
|
|
|
if (parts.length > 2) { |
|
|
|
if (parts.length > 2) { |
|
|
|
// For the last part only read until a non-numeric character is read
|
|
|
|
// 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
|
|
|
|
// For example version 3.0.0-alpha5 is read as version code 30000
|
|
|
@ -176,7 +153,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
version += Float.parseFloat(numbers); |
|
|
|
version += Integer.parseInt(numbers); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -186,55 +163,60 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
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.
|
|
|
|
|
|
|
|
if (version != -1 && version < 30200) { |
|
|
|
if (apiVersion < 20000) { |
|
|
|
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Have we already authenticated? Check if we have the cookie that we need
|
|
|
|
// Have we already authenticated? Check if we have the cookie that we need
|
|
|
|
List<Cookie> cookies = httpclient.getCookieStore().getCookies(); |
|
|
|
if (isAuthenticated()) { |
|
|
|
for (Cookie c : cookies) { |
|
|
|
|
|
|
|
if (c.getName().equals("SID")) { |
|
|
|
|
|
|
|
// And here it is! Okay, no need authenticate again.
|
|
|
|
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final BasicNameValuePair usernameParam = new BasicNameValuePair("username", settings.getUsername()); |
|
|
|
|
|
|
|
final BasicNameValuePair passwordParam = new BasicNameValuePair("password", settings.getPassword()); |
|
|
|
|
|
|
|
if (version == -1 || version >= 40100) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
makeRequest(log, "/api/v2/auth/login", usernameParam, passwordParam); |
|
|
|
|
|
|
|
} catch (DaemonException ignored) { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!isAuthenticated()) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
makeRequest(log, "/login", usernameParam, passwordParam); |
|
|
|
|
|
|
|
} catch (DaemonException ignored) { |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (apiVersion >= 20300) { |
|
|
|
if (!isAuthenticated()) { |
|
|
|
makeRequest(log, "/api/v2/auth/login", new BasicNameValuePair("username", settings.getUsername()), |
|
|
|
throw new DaemonException(ExceptionType.AuthenticationFailure, "Server rejected our login"); |
|
|
|
new BasicNameValuePair("password", settings.getPassword())); |
|
|
|
} |
|
|
|
} else { |
|
|
|
|
|
|
|
makeRequest(log, "/login", new BasicNameValuePair("username", settings.getUsername()), |
|
|
|
|
|
|
|
new BasicNameValuePair("password", settings.getPassword())); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
// The HttpClient will automatically remember the cookie for us, no need to parse it out.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// However, we would like to see if authentication was successful or not...
|
|
|
|
private boolean isAuthenticated() { |
|
|
|
cookies = httpclient.getCookieStore().getCookies(); |
|
|
|
List<Cookie> cookies = httpclient.getCookieStore().getCookies(); |
|
|
|
for (Cookie c : cookies) { |
|
|
|
for (Cookie c : cookies) { |
|
|
|
if (c.getName().equals("SID")) { |
|
|
|
if (c.getName().equals("SID")) { |
|
|
|
// Good. Let's get out of here.
|
|
|
|
// And here it is! Okay, no need authenticate again.
|
|
|
|
return; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
// No cookie found, we didn't authenticate.
|
|
|
|
|
|
|
|
throw new DaemonException(ExceptionType.AuthenticationFailure, "Server rejected our login"); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public DaemonTaskResult executeTask(Log log, DaemonTask task) { |
|
|
|
public DaemonTaskResult executeTask(Log log, DaemonTask task) { |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
ensureVersion(log); |
|
|
|
initialise(); |
|
|
|
ensureAuthenticated(log); |
|
|
|
ensureAuthenticated(log); |
|
|
|
|
|
|
|
ensureVersion(log); |
|
|
|
|
|
|
|
|
|
|
|
switch (task.getMethod()) { |
|
|
|
switch (task.getMethod()) { |
|
|
|
case Retrieve: |
|
|
|
case Retrieve: |
|
|
|
|
|
|
|
|
|
|
|
// Request all torrents from server
|
|
|
|
// Request all torrents from server
|
|
|
|
String path; |
|
|
|
String path; |
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/torrents/info"; |
|
|
|
path = "/api/v2/torrents/info"; |
|
|
|
} else if (version >= 30200) { |
|
|
|
} else if (version >= 30200) { |
|
|
|
path = "/query/torrents"; |
|
|
|
path = "/query/torrents"; |
|
|
@ -254,7 +236,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
String mhash = task.getTargetTorrent().getUniqueID(); |
|
|
|
String mhash = task.getTargetTorrent().getUniqueID(); |
|
|
|
JSONArray messages; |
|
|
|
JSONArray messages; |
|
|
|
JSONArray pieces; |
|
|
|
JSONArray pieces; |
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
messages = new JSONArray(makeRequest(log, "/api/v2/torrents/trackers", new BasicNameValuePair("hash", mhash))); |
|
|
|
messages = new JSONArray(makeRequest(log, "/api/v2/torrents/trackers", new BasicNameValuePair("hash", mhash))); |
|
|
|
pieces = new JSONArray(makeRequest(log, "/api/v2/torrents/pieceStates", new BasicNameValuePair("hash", mhash))); |
|
|
|
pieces = new JSONArray(makeRequest(log, "/api/v2/torrents/pieceStates", new BasicNameValuePair("hash", mhash))); |
|
|
|
} else { |
|
|
|
} else { |
|
|
@ -269,7 +251,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
// 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; |
|
|
|
JSONArray files; |
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
files = new JSONArray(makeRequest(log, "/api/v2/torrents/files", new BasicNameValuePair("hash", fhash))); |
|
|
|
files = new JSONArray(makeRequest(log, "/api/v2/torrents/files", new BasicNameValuePair("hash", fhash))); |
|
|
|
} else if (version >= 30200) { |
|
|
|
} else if (version >= 30200) { |
|
|
|
files = new JSONArray(makeRequest(log, "/query/propertiesFiles/" + fhash)); |
|
|
|
files = new JSONArray(makeRequest(log, "/query/propertiesFiles/" + fhash)); |
|
|
@ -282,7 +264,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case AddByFile: |
|
|
|
case AddByFile: |
|
|
|
|
|
|
|
|
|
|
|
// Upload a local .torrent file
|
|
|
|
// Upload a local .torrent file
|
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/torrents/add"; |
|
|
|
path = "/api/v2/torrents/add"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
path = "/command/upload"; |
|
|
|
path = "/command/upload"; |
|
|
@ -296,7 +278,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
|
|
|
|
|
|
|
|
// Request to add a torrent by URL
|
|
|
|
// Request to add a torrent by URL
|
|
|
|
String url = ((AddByUrlTask) task).getUrl(); |
|
|
|
String url = ((AddByUrlTask) task).getUrl(); |
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/torrents/add"; |
|
|
|
path = "/api/v2/torrents/add"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
path = "/command/upload"; |
|
|
|
path = "/command/upload"; |
|
|
@ -309,7 +291,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
|
|
|
|
|
|
|
|
// 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(); |
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/torrents/add"; |
|
|
|
path = "/api/v2/torrents/add"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
path = "/command/download"; |
|
|
|
path = "/command/download"; |
|
|
@ -322,7 +304,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
|
|
|
|
|
|
|
|
// Remove a torrent
|
|
|
|
// Remove a torrent
|
|
|
|
RemoveTask removeTask = (RemoveTask) task; |
|
|
|
RemoveTask removeTask = (RemoveTask) task; |
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
if (removeTask.includingData()) { |
|
|
|
if (removeTask.includingData()) { |
|
|
|
makeRequest(log, "/api/v2/torrents/delete", |
|
|
|
makeRequest(log, "/api/v2/torrents/delete", |
|
|
|
new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID()), |
|
|
|
new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID()), |
|
|
@ -343,7 +325,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case Pause: |
|
|
|
case Pause: |
|
|
|
|
|
|
|
|
|
|
|
// Pause a torrent
|
|
|
|
// Pause a torrent
|
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
makeRequest(log, "/api/v2/torrents/pause", new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID())); |
|
|
|
makeRequest(log, "/api/v2/torrents/pause", new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID())); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
makeRequest(log, "/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); |
|
|
|
makeRequest(log, "/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); |
|
|
@ -354,7 +336,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case PauseAll: |
|
|
|
case PauseAll: |
|
|
|
|
|
|
|
|
|
|
|
// Resume all torrents
|
|
|
|
// Resume all torrents
|
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
makeRequest(log, "/api/v2/torrents/pause", new BasicNameValuePair("hashes", "all")); |
|
|
|
makeRequest(log, "/api/v2/torrents/pause", new BasicNameValuePair("hashes", "all")); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
makeRequest(log, "/command/pauseall"); |
|
|
|
makeRequest(log, "/command/pauseall"); |
|
|
@ -365,7 +347,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case Resume: |
|
|
|
case Resume: |
|
|
|
|
|
|
|
|
|
|
|
// Resume a torrent
|
|
|
|
// Resume a torrent
|
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
makeRequest(log, "/api/v2/torrents/resume", new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID())); |
|
|
|
makeRequest(log, "/api/v2/torrents/resume", new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID())); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
makeRequest(log, "/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); |
|
|
|
makeRequest(log, "/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); |
|
|
@ -376,7 +358,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case ResumeAll: |
|
|
|
case ResumeAll: |
|
|
|
|
|
|
|
|
|
|
|
// Resume all torrents
|
|
|
|
// Resume all torrents
|
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/torrents/resume"; |
|
|
|
path = "/api/v2/torrents/resume"; |
|
|
|
makeRequest(log, path, new BasicNameValuePair("hashes", "all")); |
|
|
|
makeRequest(log, path, new BasicNameValuePair("hashes", "all")); |
|
|
|
} else { |
|
|
|
} else { |
|
|
@ -399,7 +381,7 @@ 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()) { |
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/torrents/filePrio"; |
|
|
|
path = "/api/v2/torrents/filePrio"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
path = "/command/setFilePrio"; |
|
|
|
path = "/command/setFilePrio"; |
|
|
@ -413,7 +395,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case ForceRecheck: |
|
|
|
case ForceRecheck: |
|
|
|
|
|
|
|
|
|
|
|
// Force recheck a torrent
|
|
|
|
// Force recheck a torrent
|
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/torrents/recheck"; |
|
|
|
path = "/api/v2/torrents/recheck"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
path = "/command/recheck"; |
|
|
|
path = "/command/recheck"; |
|
|
@ -424,7 +406,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case ToggleSequentialDownload: |
|
|
|
case ToggleSequentialDownload: |
|
|
|
|
|
|
|
|
|
|
|
// Toggle sequential download mode on a torrent
|
|
|
|
// Toggle sequential download mode on a torrent
|
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/torrents/toggleSequentialDownload"; |
|
|
|
path = "/api/v2/torrents/toggleSequentialDownload"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
path = "/command/toggleSequentialDownload"; |
|
|
|
path = "/command/toggleSequentialDownload"; |
|
|
@ -435,7 +417,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case ToggleFirstLastPieceDownload: |
|
|
|
case ToggleFirstLastPieceDownload: |
|
|
|
|
|
|
|
|
|
|
|
// Set policy for downloading first and last piece first on a torrent
|
|
|
|
// Set policy for downloading first and last piece first on a torrent
|
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/torrents/toggleFirstLastPiecePrio"; |
|
|
|
path = "/api/v2/torrents/toggleFirstLastPiecePrio"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
path = "/command/toggleFirstLastPiecePrio"; |
|
|
|
path = "/command/toggleFirstLastPiecePrio"; |
|
|
@ -446,7 +428,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case SetLabel: |
|
|
|
case SetLabel: |
|
|
|
|
|
|
|
|
|
|
|
SetLabelTask labelTask = (SetLabelTask) task; |
|
|
|
SetLabelTask labelTask = (SetLabelTask) task; |
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/torrents/setCategory"; |
|
|
|
path = "/api/v2/torrents/setCategory"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
path = "/command/setCategory"; |
|
|
|
path = "/command/setCategory"; |
|
|
@ -459,7 +441,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case SetDownloadLocation: |
|
|
|
case SetDownloadLocation: |
|
|
|
|
|
|
|
|
|
|
|
SetDownloadLocationTask setLocationTask = (SetDownloadLocationTask) task; |
|
|
|
SetDownloadLocationTask setLocationTask = (SetDownloadLocationTask) task; |
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/torrents/setLocation"; |
|
|
|
path = "/api/v2/torrents/setLocation"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
path = "/command/setLocation"; |
|
|
|
path = "/command/setLocation"; |
|
|
@ -478,7 +460,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
String dl = (ratesTask.getDownloadRate() == null ? "NaN" : Long.toString(ratesTask.getDownloadRate() * 1024)); |
|
|
|
String dl = (ratesTask.getDownloadRate() == null ? "NaN" : Long.toString(ratesTask.getDownloadRate() * 1024)); |
|
|
|
String ul = (ratesTask.getUploadRate() == null ? "NaN" : Long.toString(ratesTask.getUploadRate() * 1024)); |
|
|
|
String ul = (ratesTask.getUploadRate() == null ? "NaN" : Long.toString(ratesTask.getUploadRate() * 1024)); |
|
|
|
|
|
|
|
|
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
pathDL = "/api/v2/torrents/setDownloadLimit"; |
|
|
|
pathDL = "/api/v2/torrents/setDownloadLimit"; |
|
|
|
pathUL = "/api/v2/torrents/setUploadLimit"; |
|
|
|
pathUL = "/api/v2/torrents/setUploadLimit"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
@ -493,7 +475,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case GetStats: |
|
|
|
case GetStats: |
|
|
|
|
|
|
|
|
|
|
|
// Refresh alternative download speeds setting
|
|
|
|
// Refresh alternative download speeds setting
|
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/sync/maindata?rid=0"; |
|
|
|
path = "/api/v2/sync/maindata?rid=0"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
path = "/sync/maindata?rid=0"; |
|
|
|
path = "/sync/maindata?rid=0"; |
|
|
@ -509,7 +491,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
case SetAlternativeMode: |
|
|
|
case SetAlternativeMode: |
|
|
|
|
|
|
|
|
|
|
|
// Flip alternative speed mode
|
|
|
|
// Flip alternative speed mode
|
|
|
|
if (version >= 40200) { |
|
|
|
if (version >= 40100) { |
|
|
|
path = "/api/v2/transfer/toggleSpeedLimitsMode"; |
|
|
|
path = "/api/v2/transfer/toggleSpeedLimitsMode"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
path = "/command/toggleAlternativeSpeedLimits"; |
|
|
|
path = "/command/toggleAlternativeSpeedLimits"; |
|
|
@ -567,12 +549,6 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
private String makeWebRequest(HttpPost httppost, Log log) throws DaemonException { |
|
|
|
private String makeWebRequest(HttpPost httppost, Log log) throws DaemonException { |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
|
|
|
|
|
|
|
|
// Initialise the HTTP client
|
|
|
|
|
|
|
|
if (httpclient == null) { |
|
|
|
|
|
|
|
initialise(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Execute
|
|
|
|
// Execute
|
|
|
|
HttpResponse response = httpclient.execute(httppost); |
|
|
|
HttpResponse response = httpclient.execute(httppost); |
|
|
|
|
|
|
|
|
|
|
@ -604,8 +580,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
|
|
|
|
|
|
|
|
if (e instanceof DaemonException) { |
|
|
|
if (e instanceof DaemonException) { |
|
|
|
throw (DaemonException) e; |
|
|
|
throw (DaemonException) e; |
|
|
|
} |
|
|
|
} else { |
|
|
|
else { |
|
|
|
|
|
|
|
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); |
|
|
|
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -614,14 +589,18 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Instantiates an HTTP client with proper credentials that can be used for all qBittorrent requests. |
|
|
|
* Instantiates an HTTP client with proper credentials that can be used for all qBittorrent requests. |
|
|
|
|
|
|
|
* |
|
|
|
* @throws DaemonException On conflicting or missing settings |
|
|
|
* @throws DaemonException On conflicting or missing settings |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private void initialise() throws DaemonException { |
|
|
|
private void initialise() throws DaemonException { |
|
|
|
|
|
|
|
if (httpclient == null) { |
|
|
|
httpclient = HttpHelper.createStandardHttpClient(settings, true); |
|
|
|
httpclient = HttpHelper.createStandardHttpClient(settings, true); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Build the URL of the web UI request from the user settings |
|
|
|
* Build the URL of the web UI request from the user settings |
|
|
|
|
|
|
|
* |
|
|
|
* @return The URL to request |
|
|
|
* @return The URL to request |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private String buildWebUIUrl(String path) { |
|
|
|
private String buildWebUIUrl(String path) { |
|
|
@ -667,7 +646,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
Map<String, Label> labels = new HashMap<>(); |
|
|
|
Map<String, Label> labels = new HashMap<>(); |
|
|
|
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); |
|
|
|
if (apiVersion >= 20000) { |
|
|
|
if (version >= 40100) { |
|
|
|
String label = tor.optString("category"); |
|
|
|
String label = tor.optString("category"); |
|
|
|
if (label != null && label.length() > 0) { |
|
|
|
if (label != null && label.length() > 0) { |
|
|
|
final Label labelObject = labels.get(label); |
|
|
|
final Label labelObject = labels.get(label); |
|
|
@ -699,7 +678,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
Date completionOn = null; |
|
|
|
Date completionOn = null; |
|
|
|
String label = null; |
|
|
|
String label = null; |
|
|
|
|
|
|
|
|
|
|
|
if (apiVersion >= 20000) { |
|
|
|
if (version >= 30200) { |
|
|
|
leechers = new int[2]; |
|
|
|
leechers = new int[2]; |
|
|
|
leechers[0] = tor.getInt("num_leechs"); |
|
|
|
leechers[0] = tor.getInt("num_leechs"); |
|
|
|
leechers[1] = tor.getInt("num_complete") + tor.getInt("num_incomplete"); |
|
|
|
leechers[1] = tor.getInt("num_complete") + tor.getInt("num_incomplete"); |
|
|
@ -891,7 +870,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { |
|
|
|
JSONObject file = response.getJSONObject(i); |
|
|
|
JSONObject file = response.getJSONObject(i); |
|
|
|
|
|
|
|
|
|
|
|
long size; |
|
|
|
long size; |
|
|
|
if (apiVersion >= 20000) { |
|
|
|
if (version >= 30200) { |
|
|
|
size = file.getLong("size"); |
|
|
|
size = file.getLong("size"); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
size = parseSize(file.getString("size")); |
|
|
|
size = parseSize(file.getString("size")); |
|
|
|