Browse Source

Merge pull request #523 from H3mul/new-api-support

New api support
pull/527/head
Eric Kok 4 years ago committed by GitHub
parent
commit
cf0fcef0b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 331
      app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java

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

@ -83,18 +83,54 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -83,18 +83,54 @@ public class QbittorrentAdapter implements IDaemonAdapter {
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 {
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);
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 = "";
if (apiVersion > 1) {
if (apiVersion >= 20300) {
ensureAuthenticated(log);
versionText = makeRequest(log, "/api/v2/app/version").substring(1);
} else if (apiVersion > 10000) {
// Format is something like 'v3.2.0'
versionText = makeRequest(log, "/version/qbittorrent").substring(1);
} else {
@ -109,44 +145,49 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -109,44 +145,49 @@ public class QbittorrentAdapter implements IDaemonAdapter {
}
}
// String found: now parse a version like 2.9.7 as a number like 20907 (allowing 10 places for each .)
String[] parts = versionText.split("\\.");
if (parts.length > 0) {
version = Integer.parseInt(parts[0]) * 100 * 100;
if (parts.length > 1) {
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 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 = parseVersionNumber(versionText);
} catch (Exception e) {
// Unable to establish version number; assume an old version by setting it to version 1
version = 10000;
apiVersion = 1;
apiVersion = 10000;
}
}
private int parseVersionNumber(String versionText) {
// String found: now parse a version like 2.9.7 as a number like 20907 (allowing 10 places for each .)
int version = -1;
String[] parts = versionText.split("\\.");
if (parts.length > 0) {
version = Integer.parseInt(parts[0]) * 100 * 100;
if (parts.length > 1) {
version += Float.parseFloat(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 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 += Float.parseFloat(numbers);
}
}
}
return version;
}
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.
// If we don't have that cookie, let's try and get it.
if (apiVersion < 2) {
if (apiVersion < 20000) {
return;
}
@ -159,8 +200,13 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -159,8 +200,13 @@ public class QbittorrentAdapter implements IDaemonAdapter {
}
}
makeRequest(log, "/login", new BasicNameValuePair("username", settings.getUsername()),
new BasicNameValuePair("password", settings.getPassword()));
if (apiVersion >= 20300) {
makeRequest(log, "/api/v2/auth/login", new BasicNameValuePair("username", settings.getUsername()),
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...
@ -185,8 +231,12 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -185,8 +231,12 @@ public class QbittorrentAdapter implements IDaemonAdapter {
switch (task.getMethod()) {
case Retrieve:
// Request all torrents from server
String path;
if (version >= 30200) {
if (version >= 40200) {
path = "/api/v2/torrents/info";
} else if (version >= 30200) {
path = "/query/torrents";
} else if (version >= 30000) {
path = "/json/torrents";
@ -194,78 +244,145 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -194,78 +244,145 @@ public class QbittorrentAdapter implements IDaemonAdapter {
path = "/json/events";
}
// Request all torrents from server
JSONArray result = new JSONArray(makeRequest(log, path));
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), parseJsonLabels(result));
case GetTorrentDetails:
// Request tracker and error details for a specific teacher
String mhash = task.getTargetTorrent().getUniqueID();
JSONArray messages =
new JSONArray(makeRequest(log, (version >= 30200 ? "/query/propertiesTrackers/" : "/json/propertiesTrackers/") + mhash));
JSONArray pieces = new JSONArray(makeRequest(log, "/query/getPieceStates/" + mhash));
JSONArray messages;
JSONArray pieces;
if (version >= 40200) {
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)));
} else {
messages = new JSONArray(makeRequest(log, "/query/propertiesTrackers/" + mhash));
pieces = new JSONArray(makeRequest(log, "/query/getPieceStates/" + mhash));
}
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(messages, pieces));
case GetFileList:
// Request files listing for a specific torrent
String fhash = task.getTargetTorrent().getUniqueID();
JSONArray files =
new JSONArray(makeRequest(log, (version >= 30200 ? "/query/propertiesFiles/" : "/json/propertiesFiles/") + fhash));
JSONArray files;
if (version >= 40200) {
files = new JSONArray(makeRequest(log, "/api/v2/torrents/files", new BasicNameValuePair("hash", fhash)));
} else if (version >= 30200) {
files = new JSONArray(makeRequest(log, "/query/propertiesFiles/" + fhash));
} else {
files = new JSONArray(makeRequest(log, "/json/propertiesFiles/" + fhash));
}
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files));
case AddByFile:
// Upload a local .torrent file
if (version >= 40200) {
path = "/api/v2/torrents/add";
} else {
path = "/command/upload";
}
String ufile = ((AddByFileTask) task).getFile();
makeUploadRequest("/command/upload", ufile, log);
makeUploadRequest(path, ufile, log);
return new DaemonTaskSuccessResult(task);
case AddByUrl:
// Request to add a torrent by URL
String url = ((AddByUrlTask) task).getUrl();
makeRequest(log, "/command/download", new BasicNameValuePair("urls", url));
if (version >= 40200) {
path = "/api/v2/torrents/add";
} else {
path = "/command/upload";
}
makeRequest(log, path, new BasicNameValuePair("urls", url));
return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl:
// Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask) task).getUrl();
makeRequest(log, "/command/download", new BasicNameValuePair("urls", magnet));
if (version >= 40200) {
path = "/api/v2/torrents/add";
} else {
path = "/command/download";
}
makeRequest(log, path, new BasicNameValuePair("urls", magnet));
return new DaemonTaskSuccessResult(task);
case Remove:
// Remove a torrent
RemoveTask removeTask = (RemoveTask) task;
makeRequest(log, (removeTask.includingData() ? "/command/deletePerm" : "/command/delete"),
new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID()));
if (version >= 40200) {
if (removeTask.includingData()) {
makeRequest(log, "/api/v2/torrents/delete",
new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("deleteFiles", "true"));
} else {
makeRequest(log, "/api/v2/torrents/delete",
new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("deleteFiles", "false"));
}
} else {
path = (removeTask.includingData() ? "/command/deletePerm" : "/command/delete");
makeRequest(log, path, new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID()));
}
return new DaemonTaskSuccessResult(task);
case Pause:
// Pause a torrent
makeRequest(log, "/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
if (version >= 40200) {
makeRequest(log, "/api/v2/torrents/pause", new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()));
} else {
makeRequest(log, "/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
}
return new DaemonTaskSuccessResult(task);
case PauseAll:
// Resume all torrents
makeRequest(log, "/command/pauseall");
if (version >= 40200) {
makeRequest(log, "/api/v2/torrents/pause", new BasicNameValuePair("hashes", "all"));
} else {
makeRequest(log, "/command/pauseall");
}
return new DaemonTaskSuccessResult(task);
case Resume:
// Resume a torrent
makeRequest(log, "/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
if (version >= 40200) {
makeRequest(log, "/api/v2/torrents/resume", new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()));
} else {
makeRequest(log, "/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
}
return new DaemonTaskSuccessResult(task);
case ResumeAll:
// Resume all torrents
makeRequest(log, "/command/resumeall");
if (version >= 40200) {
path = "/api/v2/torrents/resume";
makeRequest(log, path, new BasicNameValuePair("hashes", "all"));
} else {
makeRequest(log, "/command/resumeall");
}
return new DaemonTaskSuccessResult(task);
case SetFilePriorities:
@ -282,33 +399,59 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -282,33 +399,59 @@ public class QbittorrentAdapter implements IDaemonAdapter {
}
// We have to make a separate request per file, it seems
for (TorrentFile file : setPrio.getForFiles()) {
makeRequest(log, "/command/setFilePrio", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()),
if (version >= 40200) {
path = "/api/v2/torrents/filePrio";
} else {
path = "/command/setFilePrio";
}
makeRequest(log, path, new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair("priority", newPrio));
}
return new DaemonTaskSuccessResult(task);
case ForceRecheck:
case ForceRecheck:
// Force recheck a torrent
makeRequest(log, "/command/recheck", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task);
// Force recheck a torrent
if (version >= 40200) {
path = "/api/v2/torrents/recheck";
} else {
path = "/command/recheck";
}
makeRequest(log, path, new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task);
case ToggleSequentialDownload:
case ToggleSequentialDownload:
// Toggle sequential download mode on a torrent
makeRequest(log, "/command/toggleSequentialDownload", new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task);
// Toggle sequential download mode on a torrent
if (version >= 40200) {
path = "/api/v2/torrents/toggleSequentialDownload";
} else {
path = "/command/toggleSequentialDownload";
}
makeRequest(log, path, new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task);
case ToggleFirstLastPieceDownload:
case ToggleFirstLastPieceDownload:
// Set policy for downloading first and last piece first on a torrent
makeRequest(log, "/command/toggleFirstLastPiecePrio", new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task);
// Set policy for downloading first and last piece first on a torrent
if (version >= 40200) {
path = "/api/v2/torrents/toggleFirstLastPiecePrio";
} else {
path = "/command/toggleFirstLastPiecePrio";
}
makeRequest(log, path, new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task);
case SetLabel:
SetLabelTask labelTask = (SetLabelTask) task;
makeRequest(log, "/command/setCategory",
if (version >= 40200) {
path = "/api/v2/torrents/setCategory";
} else {
path = "/command/setCategory";
}
makeRequest(log, path,
new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("category", labelTask.getNewLabel()));
return new DaemonTaskSuccessResult(task);
@ -316,7 +459,12 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -316,7 +459,12 @@ public class QbittorrentAdapter implements IDaemonAdapter {
case SetDownloadLocation:
SetDownloadLocationTask setLocationTask = (SetDownloadLocationTask) task;
makeRequest(log, "/command/setLocation",
if (version >= 40200) {
path = "/api/v2/torrents/setLocation";
} else {
path = "/command/setLocation";
}
makeRequest(log, path,
new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("location", setLocationTask.getNewLocation()));
return new DaemonTaskSuccessResult(task);
@ -324,18 +472,33 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -324,18 +472,33 @@ public class QbittorrentAdapter implements IDaemonAdapter {
case SetTransferRates:
// Request to set the maximum transfer rates
String pathDL;
String pathUL;
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
String dl = (ratesTask.getDownloadRate() == null ? "NaN" : Long.toString(ratesTask.getDownloadRate() * 1024));
String ul = (ratesTask.getUploadRate() == null ? "NaN" : Long.toString(ratesTask.getUploadRate() * 1024));
makeRequest(log, "/command/setGlobalDlLimit", new BasicNameValuePair("limit", dl));
makeRequest(log, "/command/setGlobalUpLimit", new BasicNameValuePair("limit", ul));
if (version >= 40200) {
pathDL = "/api/v2/torrents/setDownloadLimit";
pathUL = "/api/v2/torrents/setUploadLimit";
} else {
pathDL = "/command/setGlobalDlLimit";
pathUL = "/command/setGlobalUpLimit";
}
makeRequest(log, pathDL, new BasicNameValuePair("limit", dl));
makeRequest(log, pathUL, new BasicNameValuePair("limit", ul));
return new DaemonTaskSuccessResult(task);
case GetStats:
// Refresh alternative download speeds setting
JSONObject stats = new JSONObject(makeRequest(log, "/sync/maindata?rid=0"));
if (version >= 40200) {
path = "/api/v2/sync/maindata?rid=0";
} else {
path = "/sync/maindata?rid=0";
}
JSONObject stats = new JSONObject(makeRequest(log, path));
JSONObject serverStats = stats.optJSONObject("server_state");
boolean alternativeSpeeds = false;
if (serverStats != null) {
@ -346,7 +509,12 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -346,7 +509,12 @@ public class QbittorrentAdapter implements IDaemonAdapter {
case SetAlternativeMode:
// Flip alternative speed mode
makeRequest(log, "/command/toggleAlternativeSpeedLimits");
if (version >= 40200) {
path = "/api/v2/transfer/toggleSpeedLimitsMode";
} else {
path = "/command/toggleAlternativeSpeedLimits";
}
makeRequest(log, path);
return new DaemonTaskSuccessResult(task);
default:
@ -365,7 +533,9 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -365,7 +533,9 @@ public class QbittorrentAdapter implements IDaemonAdapter {
try {
// Setup request using POST
HttpPost httppost = new HttpPost(buildWebUIUrl(path));
String url_to_request = buildWebUIUrl(path);
HttpPost httppost = new HttpPost(url_to_request);
List<NameValuePair> nvps = new ArrayList<>();
Collections.addAll(nvps, params);
httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
@ -406,6 +576,11 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -406,6 +576,11 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// Execute
HttpResponse response = httpclient.execute(httppost);
// Throw exception on 403
if (response.getStatusLine().getStatusCode() == 403) {
throw new DaemonException(ExceptionType.AuthenticationFailure, "Response code 403");
}
HttpEntity entity = response.getEntity();
if (entity != null) {
@ -426,7 +601,13 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -426,7 +601,13 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} catch (Exception e) {
log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString());
if (e instanceof DaemonException) {
throw (DaemonException) e;
}
else {
throw new DaemonException(ExceptionType.ConnectionError, e.toString());
}
}
}
@ -486,7 +667,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -486,7 +667,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
Map<String, Label> labels = new HashMap<>();
for (int i = 0; i < response.length(); i++) {
JSONObject tor = response.getJSONObject(i);
if (apiVersion >= 2) {
if (apiVersion >= 20000) {
String label = tor.optString("category");
if (label != null && label.length() > 0) {
final Label labelObject = labels.get(label);
@ -518,7 +699,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -518,7 +699,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
Date completionOn = null;
String label = null;
if (apiVersion >= 2) {
if (apiVersion >= 20000) {
leechers = new int[2];
leechers[0] = tor.getInt("num_leechs");
leechers[1] = tor.getInt("num_complete") + tor.getInt("num_incomplete");
@ -710,7 +891,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { @@ -710,7 +891,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
JSONObject file = response.getJSONObject(i);
long size;
if (apiVersion >= 2) {
if (apiVersion >= 20000) {
size = file.getLong("size");
} else {
size = parseSize(file.getString("size"));

Loading…
Cancel
Save