From 2cf4581932bfa93ec5b92f72aac3a9aadc0274b1 Mon Sep 17 00:00:00 2001 From: Hadley Canine Date: Mon, 18 May 2015 02:42:41 -0500 Subject: [PATCH 1/4] QBitTorrent can now authenticate, and browsing torrents appears to work again. Need to do some cleanup to get API version 1 going still, though, and only browsing torrents probably works. Everything else is probably still broken. --- .../Qbittorrent/QbittorrentAdapter.java | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java index d543f705..3dc5fefa 100644 --- a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java @@ -33,6 +33,8 @@ import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; +import org.apache.http.impl.cookie.BasicClientCookie; +import org.apache.http.client.CookieStore; import org.apache.http.protocol.HTTP; import org.json.JSONArray; import org.json.JSONException; @@ -129,16 +131,38 @@ public class QbittorrentAdapter implements IDaemonAdapter { version = 10000; } + private synchronized void ensureAuthenticated(Log log) throws DaemonException { + // org.apache.http.impl.client.DefaultHttpClient + // private DefaultHttpClient httpclient; + // API changed in 3.2.0, you now login and are given a cookie. + // If we don't have that cookie, let's try and get it. + + if (version < 30200) { + return; + } + + // TODO: Don't do this if we already have the "SID" cookie set + + makeRequest(log, "/login", + new BasicNameValuePair("username", settings.getUsername()), + new BasicNameValuePair("password", settings.getPassword())); + + } + @Override public DaemonTaskResult executeTask(Log log, DaemonTask task) { try { ensureVersion(log); + ensureAuthenticated(log); + switch (task.getMethod()) { case Retrieve: // Request all torrents from server - JSONArray result = new JSONArray(makeRequest(log, version >= 30000 ? "/json/torrents" : "/json/events")); + JSONArray result = new JSONArray(makeRequest(log, + version >= 30200 ? "/query/torrents" : + version >= 30000 ? "/json/torrents" : "/json/events")); return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null); case GetTorrentDetails: @@ -374,10 +398,10 @@ public class QbittorrentAdapter implements IDaemonAdapter { JSONObject tor = response.getJSONObject(i); int leechers[] = parsePeers(tor.getString("num_leechs")); int seeders[] = parsePeers(tor.getString("num_seeds")); - long size = parseSize(tor.getString("size")); + long size = tor.getLong("size"); // parseSize(tor.get("size")); double ratio = parseRatio(tor.getString("ratio")); double progress = tor.getDouble("progress"); - int dlspeed = parseSpeed(tor.getString("dlspeed")); + int dlspeed = tor.getInt("dlspeed"); // parseSpeed(tor.getString("dlspeed")); long eta = -1L; if (dlspeed > 0) eta = (long) (size - (size * progress)) / dlspeed; @@ -391,7 +415,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { parseStatus(tor.getString("state")), null, dlspeed, - parseSpeed(tor.getString("upspeed")), + tor.getInt("upspeed"), // parseSpeed(tor.getString("upspeed")), seeders[0], seeders[1], leechers[0], @@ -525,7 +549,9 @@ public class QbittorrentAdapter implements IDaemonAdapter { ArrayList torrentfiles = new ArrayList(); for (int i = 0; i < response.length(); i++) { JSONObject file = response.getJSONObject(i); - long size = parseSize(file.getString("size")); + + long size = file.getLong("size"); // parseSize(file.getString("size")); + torrentfiles.add(new TorrentFile("" + i, file.getString("name"), null, null, size, (long) (size * file .getDouble("progress")), parsePriority(file.getInt("priority")))); } From a892154ca56ace2ff2851e99afbd8a35d151f714 Mon Sep 17 00:00:00 2001 From: Hadley Canine Date: Mon, 18 May 2015 03:57:56 -0500 Subject: [PATCH 2/4] Don't authenticate if we don't have to. Fetch API version information and adjust our behaviour accordingly. Still some crash bugs to sort out in API ver 2. --- .../Qbittorrent/QbittorrentAdapter.java | 114 ++++++++++++++---- 1 file changed, 88 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java index 3dc5fefa..0c8d1343 100644 --- a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java @@ -33,8 +33,8 @@ import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; -import org.apache.http.impl.cookie.BasicClientCookie; import org.apache.http.client.CookieStore; +import org.apache.http.cookie.Cookie; import org.apache.http.protocol.HTTP; import org.json.JSONArray; import org.json.JSONException; @@ -82,6 +82,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { private DaemonSettings settings; private DefaultHttpClient httpclient; private int version = -1; + private int apiVersion = -1; public QbittorrentAdapter(DaemonSettings settings) { this.settings = settings; @@ -93,6 +94,24 @@ public class QbittorrentAdapter implements IDaemonAdapter { // 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 { + String apiVerText = makeRequest(log, "/version/api"); + apiVersion = Integer.parseInt(apiVerText.trim()); + } + catch (DaemonException e) { + apiVersion = 1; + log.d(LOG_NAME, e.toString()); + } + catch (NumberFormatException e) { + apiVersion = 1; + log.d(LOG_NAME, e.toString()); + } + + log.d(LOG_NAME, "qBittorrent API version is " + apiVersion); + + // TODO: In API ver 2, query this information from /version/qbittorrent instead. + // For now at least this works fine, though String about = makeRequest(log, "/about.html"); String aboutStartText = "qBittorrent v"; String aboutEndText = " (Web UI)"; @@ -129,47 +148,72 @@ public class QbittorrentAdapter implements IDaemonAdapter { } // 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 { - // org.apache.http.impl.client.DefaultHttpClient - // private DefaultHttpClient httpclient; - // API changed in 3.2.0, you now login and are given a cookie. - // If we don't have that cookie, let's try and get it. - - if (version < 30200) { - return; - } - - // TODO: Don't do this if we already have the "SID" cookie set - - makeRequest(log, "/login", - new BasicNameValuePair("username", settings.getUsername()), - new BasicNameValuePair("password", settings.getPassword())); - - } + // 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) { + return; + } + + // Have we already authenticated? Check if we have the cookie that we need + List cookies = httpclient.getCookieStore().getCookies(); + for (Cookie c : cookies) { + log.d(LOG_NAME, "Looking at this cookie: " + c.getName()); + if (c.getName().equals("SID")) { + // And here it is! Okay, no need authenticate again. + log.d(LOG_NAME, "We're already authed, no need to do it again."); + return; + } + } + + log.d(LOG_NAME, "Authenticating..."); + + 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... + cookies = httpclient.getCookieStore().getCookies(); + for (Cookie c : cookies) { + log.d(LOG_NAME, "post auth looking at this cookie: " + c.getName()); + if (c.getName().equals("SID")) { + // Good. Let's get out of here. + log.d(LOG_NAME, "Authentication success!"); + return; + } + } + + // No cookie found, we didn't authenticate. + throw new DaemonException(ExceptionType.AuthenticationFailure, "Server rejected our login"); + } @Override public DaemonTaskResult executeTask(Log log, DaemonTask task) { try { ensureVersion(log); - ensureAuthenticated(log); + ensureAuthenticated(log); switch (task.getMethod()) { case Retrieve: // Request all torrents from server JSONArray result = new JSONArray(makeRequest(log, - version >= 30200 ? "/query/torrents" : - version >= 30000 ? "/json/torrents" : "/json/events")); + version >= 30200 ? "/query/torrents" : + version >= 30000 ? "/json/torrents" : "/json/events")); return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null); case GetTorrentDetails: // Request tracker and error details for a specific teacher String mhash = task.getTargetTorrent().getUniqueID(); - JSONArray messages = new JSONArray(makeRequest(log, "/json/propertiesTrackers/" + mhash)); + JSONArray messages = new JSONArray(makeRequest(log, + (version >= 30200 ? "/query/propertiesTrackers/" : "/json/propertiesTrackers/") + mhash)); return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(messages)); @@ -177,7 +221,8 @@ public class QbittorrentAdapter implements IDaemonAdapter { // Request files listing for a specific torrent String fhash = task.getTargetTorrent().getUniqueID(); - JSONArray files = new JSONArray(makeRequest(log, "/json/propertiesFiles/" + fhash)); + JSONArray files = new JSONArray(makeRequest(log, + (version >= 30200 ? "/query/propertiesFiles/" : "/json/propertiesFiles/") + fhash)); return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files)); case AddByFile: @@ -398,10 +443,22 @@ public class QbittorrentAdapter implements IDaemonAdapter { JSONObject tor = response.getJSONObject(i); int leechers[] = parsePeers(tor.getString("num_leechs")); int seeders[] = parsePeers(tor.getString("num_seeds")); - long size = tor.getLong("size"); // parseSize(tor.get("size")); double ratio = parseRatio(tor.getString("ratio")); double progress = tor.getDouble("progress"); - int dlspeed = tor.getInt("dlspeed"); // parseSpeed(tor.getString("dlspeed")); + long size; + int dlspeed; + int upspeed; + + if (apiVersion >= 2) { + size = tor.getLong("size"); + dlspeed = tor.getInt("dlspeed"); + upspeed = tor.getInt("upspeed"); + } else { + size = parseSize(tor.getString("size")); + dlspeed = parseSpeed(tor.getString("dlspeed")); + upspeed = parseSpeed(tor.getString("upspeed")); + } + long eta = -1L; if (dlspeed > 0) eta = (long) (size - (size * progress)) / dlspeed; @@ -415,7 +472,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { parseStatus(tor.getString("state")), null, dlspeed, - tor.getInt("upspeed"), // parseSpeed(tor.getString("upspeed")), + upspeed, seeders[0], seeders[1], leechers[0], @@ -550,7 +607,12 @@ public class QbittorrentAdapter implements IDaemonAdapter { for (int i = 0; i < response.length(); i++) { JSONObject file = response.getJSONObject(i); - long size = file.getLong("size"); // parseSize(file.getString("size")); + long size; + if (apiVersion >= 2) { + size = parseSize(file.getString("size")); + } else { + size = file.getLong("size"); + } torrentfiles.add(new TorrentFile("" + i, file.getString("name"), null, null, size, (long) (size * file .getDouble("progress")), parsePriority(file.getInt("priority")))); From a101d3df9a8036d9435bf547a690ecb3368c204f Mon Sep 17 00:00:00 2001 From: Hadley Canine Date: Mon, 18 May 2015 04:07:51 -0500 Subject: [PATCH 3/4] Crash bug fixed. Annoying verbose debug output removed. Everything appears to work again in API version 2. --- .../daemon/Qbittorrent/QbittorrentAdapter.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java index 0c8d1343..9fa20836 100644 --- a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java @@ -101,11 +101,9 @@ public class QbittorrentAdapter implements IDaemonAdapter { } catch (DaemonException e) { apiVersion = 1; - log.d(LOG_NAME, e.toString()); } catch (NumberFormatException e) { apiVersion = 1; - log.d(LOG_NAME, e.toString()); } log.d(LOG_NAME, "qBittorrent API version is " + apiVersion); @@ -162,16 +160,12 @@ public class QbittorrentAdapter implements IDaemonAdapter { // Have we already authenticated? Check if we have the cookie that we need List cookies = httpclient.getCookieStore().getCookies(); for (Cookie c : cookies) { - log.d(LOG_NAME, "Looking at this cookie: " + c.getName()); if (c.getName().equals("SID")) { // And here it is! Okay, no need authenticate again. - log.d(LOG_NAME, "We're already authed, no need to do it again."); return; } } - log.d(LOG_NAME, "Authenticating..."); - makeRequest(log, "/login", new BasicNameValuePair("username", settings.getUsername()), new BasicNameValuePair("password", settings.getPassword())); @@ -180,10 +174,8 @@ public class QbittorrentAdapter implements IDaemonAdapter { // However, we would like to see if authentication was successful or not... cookies = httpclient.getCookieStore().getCookies(); for (Cookie c : cookies) { - log.d(LOG_NAME, "post auth looking at this cookie: " + c.getName()); if (c.getName().equals("SID")) { // Good. Let's get out of here. - log.d(LOG_NAME, "Authentication success!"); return; } } @@ -609,9 +601,9 @@ public class QbittorrentAdapter implements IDaemonAdapter { long size; if (apiVersion >= 2) { - size = parseSize(file.getString("size")); - } else { size = file.getLong("size"); + } else { + size = parseSize(file.getString("size")); } torrentfiles.add(new TorrentFile("" + i, file.getString("name"), null, null, size, (long) (size * file From 75d2ac65c4428961dcaa8f647198b65e9c563d54 Mon Sep 17 00:00:00 2001 From: Hadley Canine Date: Mon, 18 May 2015 05:25:59 -0500 Subject: [PATCH 4/4] Fixed compatibility with 3.1 (API version 1) --- .../daemon/Qbittorrent/QbittorrentAdapter.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java index 9fa20836..cb51cee2 100644 --- a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java @@ -193,11 +193,17 @@ public class QbittorrentAdapter implements IDaemonAdapter { switch (task.getMethod()) { case Retrieve: + String path; + if (version >= 30200) { + path = "/query/torrents"; + } else if (version >= 30000) { + path = "/json/torrents"; + } else { + path = "/json/events"; + } // Request all torrents from server - JSONArray result = new JSONArray(makeRequest(log, - version >= 30200 ? "/query/torrents" : - version >= 30000 ? "/json/torrents" : "/json/events")); + JSONArray result = new JSONArray(makeRequest(log, path)); return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null); case GetTorrentDetails: