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 a0266f8a..57fdc592 100644 --- a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java @@ -1,19 +1,19 @@ /* * This file is part of Transdroid - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . - * + * */ package org.transdroid.daemon.Qbittorrent; @@ -45,7 +45,26 @@ import org.transdroid.daemon.Torrent; import org.transdroid.daemon.TorrentDetails; import org.transdroid.daemon.TorrentFile; 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 java.io.File; @@ -61,872 +80,832 @@ import java.util.Map; /** * The daemon adapter for the qBittorrent torrent client. + * * @author erickok */ public class QbittorrentAdapter implements IDaemonAdapter { - private static final String LOG_NAME = "qBittorrent daemon"; - - private DaemonSettings settings; - private DefaultHttpClient httpclient; - private int version = -1; - private int apiVersion = -1; + private static final String LOG_NAME = "qBittorrent daemon"; + + private DaemonSettings settings; + private DefaultHttpClient httpclient; + private int version = -1; + + public QbittorrentAdapter(DaemonSettings settings) { + this.settings = settings; + } + + private synchronized void ensureVersion(Log log) { + // Still need to retrieve the API and qBittorrent version numbers from the server? + if (version > 0) + return; + + // Since 4.1, API v2 is used. Since qBittorrent 3.2, API v1 is used. Otherwise we use unofficial legacy json endpoints. + try { + String versionText = ""; + try { + // Try v2 API first, which returns version number in 'v4.1.9' format + versionText = makeRequest(log, "/api/v2/app/version").substring(1); + } catch (Exception e1) { + // Try v1 API, which returns version number in 'v3.2.0' format + try { + versionText = makeRequest(log, "/version/qbittorrent").substring(1); + } catch (Exception e2) { + // 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 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); + } + } + } - public QbittorrentAdapter(DaemonSettings settings) { - this.settings = settings; - } + version = parseVersionNumber(versionText); - private synchronized void ensureVersion(Log log) throws DaemonException { - // Still need to retrieve the API and qBittorrent version numbers from the server? - if (version > 0 && apiVersion > 0) - return; + } catch (Exception e) { + // Unable to establish version number; assume an old version by setting it to version 1 + version = 10000; + } - try { + } + + 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 += 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; + } + + 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 (version != -1 && version < 30200) { + return; + } - // 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 + // Have we already authenticated? Check if we have the cookie that we need + if (isAuthenticated()) { + return; + } - boolean is_v2 = false; - String apiVersionText = ""; + 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) { + } + } - // 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; + if (!isAuthenticated()) { + throw new DaemonException(ExceptionType.AuthenticationFailure, "Server rejected our login"); + } + } + + private boolean isAuthenticated() { + List cookies = httpclient.getCookieStore().getCookies(); + for (Cookie c : cookies) { + if (c.getName().equals("SID")) { + // And here it is! Okay, no need authenticate again. + return true; + } + } + return false; + } + + @Override + public DaemonTaskResult executeTask(Log log, DaemonTask task) { + + try { + initialise(); + ensureAuthenticated(log); + ensureVersion(log); + + switch (task.getMethod()) { + case Retrieve: + + // Request all torrents from server + String path; + if (version >= 40100) { + path = "/api/v2/torrents/info"; + } else if (version >= 30200) { + path = "/query/torrents"; + } else if (version >= 30000) { + path = "/json/torrents"; + } else { + path = "/json/events"; + } + + 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; + JSONArray pieces; + if (version >= 40100) { + 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; + if (version >= 40100) { + 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 >= 40100) { + path = "/api/v2/torrents/add"; + } else { + path = "/command/upload"; + } + + String ufile = ((AddByFileTask) task).getFile(); + makeUploadRequest(path, ufile, log); + return new DaemonTaskSuccessResult(task); + + case AddByUrl: + + // Request to add a torrent by URL + String url = ((AddByUrlTask) task).getUrl(); + if (version >= 40100) { + 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(); + if (version >= 40100) { + 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; + if (version >= 40100) { + 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")); } - // 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; - } - } + } 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 + if (version >= 40100) { + 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 + if (version >= 40100) { + makeRequest(log, "/api/v2/torrents/pause", new BasicNameValuePair("hashes", "all")); + } else { + makeRequest(log, "/command/pauseall"); + } + + return new DaemonTaskSuccessResult(task); + + case Resume: + + // Resume a torrent + if (version >= 40100) { + 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 + if (version >= 40100) { + path = "/api/v2/torrents/resume"; + makeRequest(log, path, new BasicNameValuePair("hashes", "all")); + } else { + makeRequest(log, "/command/resumeall"); + } + + return new DaemonTaskSuccessResult(task); + + 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()) { + if (version >= 40100) { + 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: + + // Force recheck a torrent + if (version >= 40100) { + path = "/api/v2/torrents/recheck"; + } else { + path = "/command/recheck"; + } + makeRequest(log, path, new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID())); + return new DaemonTaskSuccessResult(task); + + case ToggleSequentialDownload: + + // Toggle sequential download mode on a torrent + if (version >= 40100) { + path = "/api/v2/torrents/toggleSequentialDownload"; + } else { + path = "/command/toggleSequentialDownload"; + } + makeRequest(log, path, new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID())); + return new DaemonTaskSuccessResult(task); + + case ToggleFirstLastPieceDownload: + + // Set policy for downloading first and last piece first on a torrent + if (version >= 40100) { + 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; + if (version >= 40100) { + 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); + + case SetDownloadLocation: + + SetDownloadLocationTask setLocationTask = (SetDownloadLocationTask) task; + if (version >= 40100) { + 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); + + 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)); + + if (version >= 40100) { + 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 + if (version >= 40100) { + 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) { + alternativeSpeeds = serverStats.optBoolean("use_alt_speed_limits"); + } + return new GetStatsTaskSuccessResult((GetStatsTask) task, alternativeSpeeds, -1); + + case SetAlternativeMode: + + // Flip alternative speed mode + if (version >= 40100) { + path = "/api/v2/transfer/toggleSpeedLimitsMode"; + } else { + path = "/command/toggleAlternativeSpeedLimits"; + } + makeRequest(log, path); + 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())); + } catch (DaemonException e) { + return new DaemonTaskFailureResult(task, e); + } + } - if (apiVersion < 0) { - apiVersion = parseVersionNumber(apiVersionText); - } + private String makeRequest(Log log, String path, NameValuePair... params) throws DaemonException { - // 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 >= 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 { - // 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); - } - } - - version = parseVersionNumber(versionText); - - } catch (Exception e) { - // Unable to establish version number; assume an old version by setting it to version 1 - version = 10000; - 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; + try { + + // Setup request using POST + String url_to_request = buildWebUIUrl(path); + HttpPost httppost = new HttpPost(url_to_request); + + List nvps = new ArrayList<>(); + Collections.addAll(nvps, params); + httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); + return makeWebRequest(httppost, log); + + } catch (UnsupportedEncodingException e) { + throw new DaemonException(ExceptionType.ConnectionError, e.toString()); } - 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 < 20000) { - return; - } - - // Have we already authenticated? Check if we have the cookie that we need - List cookies = httpclient.getCookieStore().getCookies(); - for (Cookie c : cookies) { - if (c.getName().equals("SID")) { - // And here it is! Okay, no need authenticate again. - return; - } - } - - 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... - cookies = httpclient.getCookieStore().getCookies(); - for (Cookie c : cookies) { - if (c.getName().equals("SID")) { - // Good. Let's get out of here. - 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); - - switch (task.getMethod()) { - case Retrieve: - - // Request all torrents from server - String path; - if (version >= 40200) { - path = "/api/v2/torrents/info"; - } else if (version >= 30200) { - path = "/query/torrents"; - } else if (version >= 30000) { - path = "/json/torrents"; - } else { - path = "/json/events"; - } - - 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; - 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; - 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(path, ufile, log); - return new DaemonTaskSuccessResult(task); - - case AddByUrl: - - // Request to add a torrent by URL - String url = ((AddByUrlTask) task).getUrl(); - 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(); - 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; - 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 - 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 - 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 - 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 - 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: - - // 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()) { - 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: - - // 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: - - // 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: - - // 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; - 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); - - case SetDownloadLocation: - - SetDownloadLocationTask setLocationTask = (SetDownloadLocationTask) task; - 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); - - 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)); - - 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 - 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) { - alternativeSpeeds = serverStats.optBoolean("use_alt_speed_limits"); - } - return new GetStatsTaskSuccessResult((GetStatsTask) task, alternativeSpeeds, -1); - - case SetAlternativeMode: - - // Flip alternative speed mode - if (version >= 40200) { - path = "/api/v2/transfer/toggleSpeedLimitsMode"; - } else { - path = "/command/toggleAlternativeSpeedLimits"; - } - makeRequest(log, path); - 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())); - } catch (DaemonException e) { - return new DaemonTaskFailureResult(task, e); - } - } - - private String makeRequest(Log log, String path, NameValuePair... params) throws DaemonException { - - try { - - // Setup request using POST - String url_to_request = buildWebUIUrl(path); - HttpPost httppost = new HttpPost(url_to_request); - - List nvps = new ArrayList<>(); - Collections.addAll(nvps, params); - httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); - return makeWebRequest(httppost, log); - - } catch (UnsupportedEncodingException e) { - throw new DaemonException(ExceptionType.ConnectionError, e.toString()); - } - - } - - private String makeUploadRequest(String path, String file, Log log) throws DaemonException { - - try { - - // Setup request using POST - HttpPost httppost = new HttpPost(buildWebUIUrl(path)); - File upload = new File(URI.create(file)); - Part[] parts = {new FilePart("torrentfile", upload)}; - httppost.setEntity(new MultipartEntity(parts, httppost.getParams())); - return makeWebRequest(httppost, log); - - } catch (FileNotFoundException e) { - throw new DaemonException(ExceptionType.FileAccessError, e.toString()); - } - - } - - private String makeWebRequest(HttpPost httppost, Log log) throws DaemonException { - - try { - - // Initialise the HTTP client - if (httpclient == null) { - initialise(); - } - - // 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) { + private String makeUploadRequest(String path, String file, Log log) throws DaemonException { - // Read JSON response - java.io.InputStream instream = entity.getContent(); - String result = HttpHelper.convertStreamToString(instream); - instream.close(); + try { - // TLog.d(LOG_NAME, "Success: " + (result.length() > 300? result.substring(0, 300) + "... (" + - // result.length() + " chars)": result)); + // Setup request using POST + HttpPost httppost = new HttpPost(buildWebUIUrl(path)); + File upload = new File(URI.create(file)); + Part[] parts = {new FilePart("torrentfile", upload)}; + httppost.setEntity(new MultipartEntity(parts, httppost.getParams())); + return makeWebRequest(httppost, log); - // Return raw result - return result; - } + } catch (FileNotFoundException e) { + throw new DaemonException(ExceptionType.FileAccessError, e.toString()); + } - log.d(LOG_NAME, "Error: No entity in HTTP response"); - throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response."); + } - } catch (Exception e) { - log.d(LOG_NAME, "Error: " + e.toString()); + private String makeWebRequest(HttpPost httppost, Log log) throws DaemonException { - if (e instanceof DaemonException) { - throw (DaemonException) e; - } - else { - throw new DaemonException(ExceptionType.ConnectionError, e.toString()); - } - } - - } - - /** - * Instantiates an HTTP client with proper credentials that can be used for all qBittorrent requests. - * @throws DaemonException On conflicting or missing settings - */ - private void initialise() throws DaemonException { - httpclient = HttpHelper.createStandardHttpClient(settings, true); - } - - /** - * Build the URL of the web UI request from the user settings - * @return The URL to request - */ - private String buildWebUIUrl(String path) { - String proxyFolder = settings.getFolder(); - if (proxyFolder == null) - proxyFolder = ""; - else if (proxyFolder.endsWith("/")) - proxyFolder = proxyFolder.substring(0, proxyFolder.length() - 1); - return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + proxyFolder + path; - } - - private TorrentDetails parseJsonTorrentDetails(JSONArray messages, JSONArray pieceStates) throws JSONException { - - ArrayList trackers = new ArrayList<>(); - ArrayList errors = new ArrayList<>(); - - // Parse response - if (messages.length() > 0) { - for (int i = 0; i < messages.length(); i++) { - JSONObject tor = messages.getJSONObject(i); - trackers.add(tor.getString("url")); - String msg = tor.getString("msg"); - if (msg != null && !msg.equals("")) - errors.add(msg); - } - } - - ArrayList pieces = new ArrayList<>(); - if (pieceStates.length() > 0) { - for (int i = 0; i < pieceStates.length(); i++) { - pieces.add(pieceStates.getInt(i)); - } + try { + // 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) { + + // Read JSON response + java.io.InputStream instream = entity.getContent(); + String result = HttpHelper.convertStreamToString(instream); + instream.close(); + + // TLog.d(LOG_NAME, "Success: " + (result.length() > 300? result.substring(0, 300) + "... (" + + // result.length() + " chars)": result)); + + // Return raw result + return result; + } + + log.d(LOG_NAME, "Error: No entity in HTTP response"); + throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response."); + + } catch (Exception e) { + log.d(LOG_NAME, "Error: " + e.toString()); + + if (e instanceof DaemonException) { + throw (DaemonException) e; + } else { + throw new DaemonException(ExceptionType.ConnectionError, e.toString()); + } + } + + } + + /** + * Instantiates an HTTP client with proper credentials that can be used for all qBittorrent requests. + * + * @throws DaemonException On conflicting or missing settings + */ + private void initialise() throws DaemonException { + if (httpclient == null) { + httpclient = HttpHelper.createStandardHttpClient(settings, true); + } + } + + /** + * Build the URL of the web UI request from the user settings + * + * @return The URL to request + */ + private String buildWebUIUrl(String path) { + String proxyFolder = settings.getFolder(); + if (proxyFolder == null) + proxyFolder = ""; + else if (proxyFolder.endsWith("/")) + proxyFolder = proxyFolder.substring(0, proxyFolder.length() - 1); + return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + proxyFolder + path; + } + + private TorrentDetails parseJsonTorrentDetails(JSONArray messages, JSONArray pieceStates) throws JSONException { + + ArrayList trackers = new ArrayList<>(); + ArrayList errors = new ArrayList<>(); + + // Parse response + if (messages.length() > 0) { + for (int i = 0; i < messages.length(); i++) { + JSONObject tor = messages.getJSONObject(i); + trackers.add(tor.getString("url")); + String msg = tor.getString("msg"); + if (msg != null && !msg.equals("")) + errors.add(msg); + } + } + + ArrayList pieces = new ArrayList<>(); + if (pieceStates.length() > 0) { + for (int i = 0; i < pieceStates.length(); i++) { + pieces.add(pieceStates.getInt(i)); + } + } + + // Return the list + return new TorrentDetails(trackers, errors, pieces); + + } + + private List