Browse Source

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

New api support
pull/527/head
Eric Kok 5 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 {
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 // 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 { try {
String apiVerText = makeRequest(log, "/version/api"); apiVersionText = makeRequest(log, "/api/v2/app/webapiVersion");
apiVersion = Integer.parseInt(apiVerText.trim()); } catch (DaemonException e) {
} catch (DaemonException | NumberFormatException e) { // 403 Forbidden - endpoint exists. Keep trying v2
apiVersion = 1; is_v2 = e.getType() == ExceptionType.AuthenticationFailure;
} }
log.d(LOG_NAME, "qBittorrent API version is " + apiVersion);
// 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 // 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 > 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' // Format is something like 'v3.2.0'
versionText = makeRequest(log, "/version/qbittorrent").substring(1); versionText = makeRequest(log, "/version/qbittorrent").substring(1);
} else { } else {
@ -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 .) version = parseVersionNumber(versionText);
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;
}
}
}
} 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 = 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 { 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 (apiVersion < 2) { if (apiVersion < 20000) {
return; return;
} }
@ -159,8 +200,13 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
} }
makeRequest(log, "/login", new BasicNameValuePair("username", settings.getUsername()), if (apiVersion >= 20300) {
new BasicNameValuePair("password", settings.getPassword())); 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. // 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... // However, we would like to see if authentication was successful or not...
@ -185,8 +231,12 @@ public class QbittorrentAdapter implements IDaemonAdapter {
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
// Request all torrents from server
String path; String path;
if (version >= 30200) { if (version >= 40200) {
path = "/api/v2/torrents/info";
} else if (version >= 30200) {
path = "/query/torrents"; path = "/query/torrents";
} else if (version >= 30000) { } else if (version >= 30000) {
path = "/json/torrents"; path = "/json/torrents";
@ -194,78 +244,145 @@ public class QbittorrentAdapter implements IDaemonAdapter {
path = "/json/events"; path = "/json/events";
} }
// 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), parseJsonLabels(result)); return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), parseJsonLabels(result));
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 = JSONArray messages;
new JSONArray(makeRequest(log, (version >= 30200 ? "/query/propertiesTrackers/" : "/json/propertiesTrackers/") + mhash)); JSONArray pieces;
JSONArray pieces = new JSONArray(makeRequest(log, "/query/getPieceStates/" + mhash)); 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)); return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(messages, pieces));
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 = JSONArray files;
new JSONArray(makeRequest(log, (version >= 30200 ? "/query/propertiesFiles/" : "/json/propertiesFiles/") + fhash)); 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)); return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files));
case AddByFile: case AddByFile:
// Upload a local .torrent file // Upload a local .torrent file
if (version >= 40200) {
path = "/api/v2/torrents/add";
} else {
path = "/command/upload";
}
String ufile = ((AddByFileTask) task).getFile(); String ufile = ((AddByFileTask) task).getFile();
makeUploadRequest("/command/upload", ufile, log); makeUploadRequest(path, 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)); if (version >= 40200) {
path = "/api/v2/torrents/add";
} else {
path = "/command/upload";
}
makeRequest(log, path, 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)); if (version >= 40200) {
path = "/api/v2/torrents/add";
} else {
path = "/command/download";
}
makeRequest(log, path, 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"), if (version >= 40200) {
new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID())); 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); return new DaemonTaskSuccessResult(task);
case Pause: case Pause:
// Pause a torrent // 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); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
// Resume all torrents // 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); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // 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); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
// Resume all torrents // 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); return new DaemonTaskSuccessResult(task);
case SetFilePriorities: case SetFilePriorities:
@ -282,33 +399,59 @@ 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().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)); new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair("priority", newPrio));
} }
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ForceRecheck: case ForceRecheck:
// Force recheck a torrent // Force recheck a torrent
makeRequest(log, "/command/recheck", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); if (version >= 40200) {
return new DaemonTaskSuccessResult(task); 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 // Toggle sequential download mode on a torrent
makeRequest(log, "/command/toggleSequentialDownload", new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID())); if (version >= 40200) {
return new DaemonTaskSuccessResult(task); 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 // Set policy for downloading first and last piece first on a torrent
makeRequest(log, "/command/toggleFirstLastPiecePrio", new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID())); if (version >= 40200) {
return new DaemonTaskSuccessResult(task); path = "/api/v2/torrents/toggleFirstLastPiecePrio";
} else {
path = "/command/toggleFirstLastPiecePrio";
}
makeRequest(log, path, new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task);
case SetLabel: case SetLabel:
SetLabelTask labelTask = (SetLabelTask) task; 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("hashes", task.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("category", labelTask.getNewLabel())); new BasicNameValuePair("category", labelTask.getNewLabel()));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
@ -316,7 +459,12 @@ public class QbittorrentAdapter implements IDaemonAdapter {
case SetDownloadLocation: case SetDownloadLocation:
SetDownloadLocationTask setLocationTask = (SetDownloadLocationTask) task; 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("hashes", task.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("location", setLocationTask.getNewLocation())); new BasicNameValuePair("location", setLocationTask.getNewLocation()));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
@ -324,18 +472,33 @@ public class QbittorrentAdapter implements IDaemonAdapter {
case SetTransferRates: case SetTransferRates:
// Request to set the maximum transfer rates // Request to set the maximum transfer rates
String pathDL;
String pathUL;
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
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));
makeRequest(log, "/command/setGlobalDlLimit", new BasicNameValuePair("limit", dl)); if (version >= 40200) {
makeRequest(log, "/command/setGlobalUpLimit", new BasicNameValuePair("limit", ul)); 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); return new DaemonTaskSuccessResult(task);
case GetStats: case GetStats:
// Refresh alternative download speeds setting // 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"); JSONObject serverStats = stats.optJSONObject("server_state");
boolean alternativeSpeeds = false; boolean alternativeSpeeds = false;
if (serverStats != null) { if (serverStats != null) {
@ -346,7 +509,12 @@ public class QbittorrentAdapter implements IDaemonAdapter {
case SetAlternativeMode: case SetAlternativeMode:
// Flip alternative speed mode // 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); return new DaemonTaskSuccessResult(task);
default: default:
@ -365,7 +533,9 @@ public class QbittorrentAdapter implements IDaemonAdapter {
try { try {
// Setup request using POST // 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<>(); 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));
@ -406,6 +576,11 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// Execute // Execute
HttpResponse response = httpclient.execute(httppost); 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(); HttpEntity entity = response.getEntity();
if (entity != null) { if (entity != null) {
@ -426,7 +601,13 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} catch (Exception e) { } catch (Exception e) {
log.d(LOG_NAME, "Error: " + e.toString()); 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 {
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 >= 2) { if (apiVersion >= 20000) {
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);
@ -518,7 +699,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
Date completionOn = null; Date completionOn = null;
String label = null; String label = null;
if (apiVersion >= 2) { if (apiVersion >= 20000) {
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");
@ -710,7 +891,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
JSONObject file = response.getJSONObject(i); JSONObject file = response.getJSONObject(i);
long size; long size;
if (apiVersion >= 2) { if (apiVersion >= 20000) {
size = file.getLong("size"); size = file.getLong("size");
} else { } else {
size = parseSize(file.getString("size")); size = parseSize(file.getString("size"));

Loading…
Cancel
Save