From 5d68d061228142aef7a0958efa70cf012a0f0bcc Mon Sep 17 00:00:00 2001 From: Eric Kok Date: Mon, 1 Jun 2015 12:29:06 +0200 Subject: [PATCH] Merging qBittorrent adapter fixes with support for 3.2+, added proper version number retrieval. --- .../Qbittorrent/QbittorrentAdapter.java | 346 +++++++++--------- 1 file changed, 173 insertions(+), 173 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 cb51cee2..7bfb100d 100644 --- a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java @@ -17,24 +17,18 @@ */ package org.transdroid.daemon.Qbittorrent; -import java.io.File; -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; +import com.android.internalcopy.http.multipart.FilePart; +import com.android.internalcopy.http.multipart.MultipartEntity; +import com.android.internalcopy.http.multipart.Part; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; +import org.apache.http.cookie.Cookie; import org.apache.http.impl.client.DefaultHttpClient; 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.json.JSONArray; import org.json.JSONException; @@ -42,6 +36,7 @@ import org.json.JSONObject; import org.transdroid.core.gui.log.Log; import org.transdroid.daemon.Daemon; import org.transdroid.daemon.DaemonException; +import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.Priority; @@ -49,7 +44,6 @@ import org.transdroid.daemon.Torrent; import org.transdroid.daemon.TorrentDetails; import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentStatus; -import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.task.AddByFileTask; import org.transdroid.daemon.task.AddByMagnetUrlTask; 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.SetTransferRatesTask; import org.transdroid.daemon.util.HttpHelper; -import com.android.internalcopy.http.multipart.FilePart; -import com.android.internalcopy.http.multipart.MultipartEntity; -import com.android.internalcopy.http.multipart.Part; + +import java.io.File; +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. @@ -89,64 +89,69 @@ public class QbittorrentAdapter implements IDaemonAdapter { } 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; - // 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; - } - catch (NumberFormatException e) { - apiVersion = 1; - } - log.d(LOG_NAME, "qBittorrent API version is " + apiVersion); + // The API version is only supported since qBittorrent 3.2, so otherwise we assume version 1 + 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); - // 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)"; - int aboutStart = about.indexOf(aboutStartText); - int aboutEnd = about.indexOf(aboutEndText); - try { - if (aboutStart >= 0 && aboutEnd > aboutStart) { - // 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("\\."); - 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; - } + // The qBittorent version is only supported since 3.2; for earlier versions we parse the about dialog and parse it + 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 aboutStartText = "qBittorrent v"; + String aboutEndText = " (Web UI)"; + int aboutStart = about.indexOf(aboutStartText); + int aboutEnd = about.indexOf(aboutEndText); + 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[] 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 += Integer.parseInt(numbers); + return; } } } - } catch (NumberFormatException e) { + + } catch (Exception e) { + // Unable to establish version number; assume an old version by setting it to version 1 + version = 10000; + apiVersion = 1; } - // 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 { @@ -166,8 +171,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { } } - makeRequest(log, "/login", - new BasicNameValuePair("username", settings.getUsername()), + 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. @@ -192,129 +196,126 @@ public class QbittorrentAdapter implements IDaemonAdapter { ensureAuthenticated(log); switch (task.getMethod()) { - case Retrieve: - String path; - if (version >= 30200) { - path = "/query/torrents"; - } else if (version >= 30000) { - path = "/json/torrents"; - } else { - path = "/json/events"; - } + 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, path)); - return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null); + // Request all torrents from server + JSONArray result = new JSONArray(makeRequest(log, path)); + return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null); - case GetTorrentDetails: + 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)); - return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, - parseJsonTorrentDetails(messages)); + // 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)); + return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(messages)); - case GetFileList: + 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)); - return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files)); + // 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)); + return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files)); - case AddByFile: + case AddByFile: - // Upload a local .torrent file - String ufile = ((AddByFileTask) task).getFile(); - makeUploadRequest("/command/upload", ufile, log); - return new DaemonTaskSuccessResult(task); + // Upload a local .torrent file + String ufile = ((AddByFileTask) task).getFile(); + makeUploadRequest("/command/upload", ufile, log); + return new DaemonTaskSuccessResult(task); - case AddByUrl: + case AddByUrl: - // Request to add a torrent by URL - String url = ((AddByUrlTask) task).getUrl(); - makeRequest(log, "/command/download", new BasicNameValuePair("urls", url)); - return new DaemonTaskSuccessResult(task); + // Request to add a torrent by URL + String url = ((AddByUrlTask) task).getUrl(); + makeRequest(log, "/command/download", new BasicNameValuePair("urls", url)); + return new DaemonTaskSuccessResult(task); - case AddByMagnetUrl: + case AddByMagnetUrl: - // Request to add a magnet link by URL - String magnet = ((AddByMagnetUrlTask) task).getUrl(); - makeRequest(log, "/command/download", new BasicNameValuePair("urls", magnet)); - return new DaemonTaskSuccessResult(task); + // Request to add a magnet link by URL + String magnet = ((AddByMagnetUrlTask) task).getUrl(); + makeRequest(log, "/command/download", new BasicNameValuePair("urls", magnet)); + return new DaemonTaskSuccessResult(task); - case Remove: + case Remove: - // Remove a torrent - RemoveTask removeTask = (RemoveTask) task; - makeRequest(log, (removeTask.includingData() ? "/command/deletePerm" : "/command/delete"), - new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID())); - return new DaemonTaskSuccessResult(task); + // Remove a torrent + RemoveTask removeTask = (RemoveTask) task; + makeRequest(log, (removeTask.includingData() ? "/command/deletePerm" : "/command/delete"), + new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID())); + return new DaemonTaskSuccessResult(task); - case Pause: + case Pause: - // Pause a torrent - makeRequest(log, "/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); - return new DaemonTaskSuccessResult(task); + // Pause a torrent + makeRequest(log, "/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); + return new DaemonTaskSuccessResult(task); - case PauseAll: + case PauseAll: - // Resume all torrents - makeRequest(log, "/command/pauseall"); - return new DaemonTaskSuccessResult(task); + // Resume all torrents + makeRequest(log, "/command/pauseall"); + return new DaemonTaskSuccessResult(task); - case Resume: + case Resume: - // Resume a torrent - makeRequest(log, "/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); - return new DaemonTaskSuccessResult(task); + // Resume a torrent + makeRequest(log, "/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); + return new DaemonTaskSuccessResult(task); - case ResumeAll: + case ResumeAll: - // Resume all torrents - makeRequest(log, "/command/resumeall"); - return new DaemonTaskSuccessResult(task); + // Resume all torrents + makeRequest(log, "/command/resumeall"); + return new DaemonTaskSuccessResult(task); - case SetFilePriorities: + case SetFilePriorities: - // Update the priorities to a set of files - SetFilePriorityTask setPrio = (SetFilePriorityTask) task; - String newPrio = "0"; - if (setPrio.getNewPriority() == Priority.Low) { - newPrio = "1"; - } else if (setPrio.getNewPriority() == Priority.Normal) { - newPrio = "2"; - } else if (setPrio.getNewPriority() == Priority.High) { - newPrio = "7"; - } - // 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()), new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair( - "priority", newPrio)); - } - return new DaemonTaskSuccessResult(task); - - case SetTransferRates: - - // TODO: This doesn't seem to work yet - // Request to set the maximum transfer rates - SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; - int dl = (ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate()); - int ul = (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate()); - - // First get the preferences - JSONObject prefs = new JSONObject(makeRequest(log, "/json/preferences")); - prefs.put("dl_limit", dl); - prefs.put("up_limit", ul); - makeRequest(log, "/command/setPreferences", - new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8))); - return new DaemonTaskSuccessResult(task); - - default: - return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, - task.getMethod() + " is not supported by " + getType())); + // Update the priorities to a set of files + SetFilePriorityTask setPrio = (SetFilePriorityTask) task; + String newPrio = "0"; + if (setPrio.getNewPriority() == Priority.Low) { + newPrio = "1"; + } else if (setPrio.getNewPriority() == Priority.Normal) { + newPrio = "2"; + } else if (setPrio.getNewPriority() == Priority.High) { + newPrio = "7"; + } + // 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()), + new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair("priority", newPrio)); + } + return new DaemonTaskSuccessResult(task); + + case SetTransferRates: + + // TODO: This doesn't seem to work yet + // Request to set the maximum transfer rates + SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; + int dl = (ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate()); + int ul = (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate()); + + // First get the preferences + JSONObject prefs = new JSONObject(makeRequest(log, "/json/preferences")); + prefs.put("dl_limit", dl); + prefs.put("up_limit", ul); + makeRequest(log, "/command/setPreferences", new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8))); + return new DaemonTaskSuccessResult(task); + + default: + return new DaemonTaskFailureResult(task, + new DaemonException(ExceptionType.MethodUnsupported, task.getMethod() + " is not supported by " + getType())); } } catch (JSONException e) { return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString())); @@ -331,7 +332,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { // Setup request using POST HttpPost httppost = new HttpPost(buildWebUIUrl(path)); - List nvps = new ArrayList(); + List nvps = new ArrayList<>(); Collections.addAll(nvps, params); httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); return makeWebRequest(httppost, log); @@ -349,7 +350,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { // Setup request using POST HttpPost httppost = new HttpPost(buildWebUIUrl(path)); 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())); return makeWebRequest(httppost, log); @@ -414,8 +415,8 @@ public class QbittorrentAdapter implements IDaemonAdapter { private TorrentDetails parseJsonTorrentDetails(JSONArray messages) throws JSONException { - ArrayList trackers = new ArrayList(); - ArrayList errors = new ArrayList(); + ArrayList trackers = new ArrayList<>(); + ArrayList errors = new ArrayList<>(); // Parse response if (messages.length() > 0) { @@ -436,7 +437,7 @@ public class QbittorrentAdapter implements IDaemonAdapter { private ArrayList parseJsonTorrents(JSONArray response) throws JSONException { // Parse response - ArrayList torrents = new ArrayList(); + ArrayList torrents = new ArrayList<>(); for (int i = 0; i < response.length(); i++) { JSONObject tor = response.getJSONObject(i); 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 String[] parts = seeds.split(" "); if (parts.length > 1) { - return new int[] { Integer.parseInt(parts[0]), - Integer.parseInt(parts[1].substring(1, parts[1].length() - 1)) }; + return new int[]{Integer.parseInt(parts[0]), 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) { @@ -612,8 +612,8 @@ public class QbittorrentAdapter implements IDaemonAdapter { 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")))); + torrentfiles.add(new TorrentFile("" + i, file.getString("name"), null, null, size, (long) (size * file.getDouble("progress")), + parsePriority(file.getInt("priority")))); } // Return the list