diff --git a/app/build.gradle b/app/build.gradle index ad4305d9..75597b9b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,9 +7,9 @@ android { defaultConfig { minSdkVersion 15 - targetSdkVersion 28 - versionCode 236 - versionName '2.5.16' + targetSdkVersion 29 + versionCode 237 + versionName '2.5.17' javaCompileOptions { annotationProcessorOptions { diff --git a/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java b/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java index 8a0da758..c67d1f13 100644 --- a/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java @@ -56,6 +56,8 @@ import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.task.DaemonTaskFailureResult; import org.transdroid.daemon.task.DaemonTaskResult; import org.transdroid.daemon.task.DaemonTaskSuccessResult; +import org.transdroid.daemon.task.ToggleSequentialDownloadTask; +import org.transdroid.daemon.task.ToggleFirstLastPieceDownloadTask; import org.transdroid.daemon.task.ForceRecheckTask; import org.transdroid.daemon.task.GetFileListTask; import org.transdroid.daemon.task.GetFileListTaskSuccessResult; @@ -278,6 +280,36 @@ public class DetailsActivity extends AppCompatActivity implements TorrentTasksEx } } + @Background + @Override + public void toggleSequentialDownload(Torrent torrent, boolean sequentialState) { + torrent.mimicSequentialDownload(sequentialState); + String onState = getString(R.string.result_togglesequential_onstate); + String offState = getString(R.string.result_togglesequential_offstate); + String stateString = sequentialState ? onState : offState; + DaemonTaskResult result = ToggleSequentialDownloadTask.create(currentConnection, torrent).execute(log); + if (result instanceof DaemonTaskSuccessResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_togglesequential, torrent.getName(), stateString)); + } else { + onCommunicationError((DaemonTaskFailureResult) result, false); + } + } + + @Background + @Override + public void toggleFirstLastPieceDownload(Torrent torrent, boolean firstLastPieceState) { + torrent.mimicFirstLastPieceDownload(firstLastPieceState); + String onState = getString(R.string.result_togglefirstlastpiece_onstate); + String offState = getString(R.string.result_togglefirstlastpiece_offstate); + String stateString = firstLastPieceState ? onState : offState; + DaemonTaskResult result = ToggleFirstLastPieceDownloadTask.create(currentConnection, torrent).execute(log); + if (result instanceof DaemonTaskSuccessResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_togglefirstlastpiece, torrent.getName(), stateString)); + } else { + onCommunicationError((DaemonTaskFailureResult) result, false); + } + } + @Background @Override public void forceRecheckTorrent(Torrent torrent) { @@ -330,7 +362,7 @@ public class DetailsActivity extends AppCompatActivity implements TorrentTasksEx // Refresh the screen as well refreshTorrent(); refreshTorrentDetails(torrent); - SnackbarManager.show(Snackbar.with(this).text(successMessage)); + SnackbarManager.show(Snackbar.with(this).text(successMessage).duration(Snackbar.SnackbarDuration.LENGTH_SHORT)); } @UiThread diff --git a/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java b/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java index 79843c00..3039ae71 100644 --- a/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java +++ b/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java @@ -203,6 +203,8 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen .updateTrackers(SimpleListItemAdapter.SimpleStringItem.wrapStringsList(newTorrentDetails.getTrackers())); ((DetailsAdapter) detailsList.getAdapter()) .updateErrors(SimpleListItemAdapter.SimpleStringItem.wrapStringsList(newTorrentDetails.getErrors())); + ((DetailsAdapter) detailsList.getAdapter()) + .updatePieces(newTorrentDetails.getPieces()); } /** @@ -301,6 +303,12 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen case R.id.action_stop: stopTorrent(); return true; + case R.id.action_toggle_sequential: + toggleSequentialDownload(menuItem); + return true; + case R.id.action_toggle_firstlastpiece: + toggleFirstLastPieceDownload(menuItem); + return true; case R.id.action_forcerecheck: setForceRecheck(); return true; @@ -359,6 +367,15 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen detailsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(setLabel); boolean forceRecheck = Daemon.supportsForceRecheck(torrent.getDaemon()); detailsMenu.getMenu().findItem(R.id.action_forcerecheck).setVisible(forceRecheck); + boolean sequentialdl = Daemon.supportsSequentialDownload(torrent.getDaemon()); + MenuItem seqMenuItem = detailsMenu.getMenu().findItem(R.id.action_toggle_sequential); + seqMenuItem.setVisible(sequentialdl); + seqMenuItem.setChecked(torrent.isSequentiallyDownloading()); + boolean firstlastpiecedl = Daemon.supportsFirstLastPiece(torrent.getDaemon()); + MenuItem flpMenuItem = detailsMenu.getMenu().findItem(R.id.action_toggle_firstlastpiece); + flpMenuItem.setVisible(firstlastpiecedl); + flpMenuItem.setChecked(torrent.isDownloadingFirstLastPieceFirst()); + detailsMenu.getMenu().findItem(R.id.action_download_mode).setVisible(!torrent.isFinished() && (firstlastpiecedl || sequentialdl)); boolean setTrackers = Daemon.supportsSetTrackers(torrent.getDaemon()); detailsMenu.getMenu().findItem(R.id.action_updatetrackers).setVisible(setTrackers); boolean setLocation = Daemon.supportsSetDownloadLocation(torrent.getDaemon()); @@ -421,6 +438,18 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen } } + @OptionsItem(R.id.action_toggle_sequential) + protected void toggleSequentialDownload(MenuItem menuItem) { + if (getTasksExecutor() != null) + getTasksExecutor().toggleSequentialDownload(torrent, !menuItem.isChecked()); + } + + @OptionsItem(R.id.action_toggle_firstlastpiece) + protected void toggleFirstLastPieceDownload(MenuItem menuItem) { + if (getTasksExecutor() != null) + getTasksExecutor().toggleFirstLastPieceDownload(torrent, !menuItem.isChecked()); + } + @OptionsItem(R.id.action_forcerecheck) protected void setForceRecheck() { if (getTasksExecutor() != null) diff --git a/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java b/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java index b9418bb9..dd7c5036 100644 --- a/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java +++ b/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java @@ -40,6 +40,10 @@ public interface TorrentTasksExecutor { void removeTorrent(Torrent torrent, boolean withData); + void toggleSequentialDownload(Torrent torrent, boolean sequentialState); + + void toggleFirstLastPieceDownload(Torrent torrent, boolean firstLastPieceState); + void forceRecheckTorrent(Torrent torrent); void updateLabel(Torrent torrent, String newLabel); diff --git a/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java b/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java index 2956cf2b..a0d259e2 100644 --- a/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java @@ -103,6 +103,8 @@ import org.transdroid.daemon.task.AddByUrlTask; import org.transdroid.daemon.task.DaemonTaskFailureResult; import org.transdroid.daemon.task.DaemonTaskResult; import org.transdroid.daemon.task.DaemonTaskSuccessResult; +import org.transdroid.daemon.task.ToggleSequentialDownloadTask; +import org.transdroid.daemon.task.ToggleFirstLastPieceDownloadTask; import org.transdroid.daemon.task.ForceRecheckTask; import org.transdroid.daemon.task.GetFileListTask; import org.transdroid.daemon.task.GetFileListTaskSuccessResult; @@ -1225,6 +1227,30 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE } } + @Background + @Override + public void toggleSequentialDownload(Torrent torrent, boolean sequentialState) { + torrent.mimicSequentialDownload(sequentialState); + DaemonTaskResult result = ToggleSequentialDownloadTask.create(currentConnection, torrent).execute(log); + if (result instanceof DaemonTaskSuccessResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_togglesequential)); + } else { + onCommunicationError((DaemonTaskFailureResult) result, false); + } + } + + @Background + @Override + public void toggleFirstLastPieceDownload(Torrent torrent, boolean firstLastPieceState) { + torrent.mimicFirstLastPieceDownload(firstLastPieceState); + DaemonTaskResult result = ToggleFirstLastPieceDownloadTask.create(currentConnection, torrent).execute(log); + if (result instanceof DaemonTaskSuccessResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.action_toggle_firstlastpiece)); + } else { + onCommunicationError((DaemonTaskFailureResult) result, false); + } + } + @Background @Override public void forceRecheckTorrent(Torrent torrent) { diff --git a/app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java b/app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java index d1d374fe..d04927b7 100644 --- a/app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java +++ b/app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java @@ -21,6 +21,7 @@ import java.util.List; import org.transdroid.R; import org.transdroid.core.gui.navigation.*; +import org.transdroid.core.gui.lists.PiecesMapView; import org.transdroid.daemon.Torrent; import org.transdroid.daemon.TorrentFile; @@ -38,6 +39,9 @@ public class DetailsAdapter extends MergeAdapter { private ViewHolderAdapter torrentDetailsViewAdapter = null; private TorrentDetailsView torrentDetailsView = null; + private ViewHolderAdapter piecesSeparatorAdapter = null; + private ViewHolderAdapter piecesMapViewAdapter = null; + private PiecesMapView piecesMapView = null; private ViewHolderAdapter trackersSeparatorAdapter = null; private SimpleListItemAdapter trackersAdapter = null; private ViewHolderAdapter errorsSeparatorAdapter = null; @@ -56,6 +60,18 @@ public class DetailsAdapter extends MergeAdapter { torrentDetailsViewAdapter.setViewVisibility(View.GONE); addAdapter(torrentDetailsViewAdapter); + // Pieces map + piecesSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText( + context.getString(R.string.status_pieces))); + piecesSeparatorAdapter.setViewEnabled(false); + piecesSeparatorAdapter.setViewVisibility(View.GONE); + addAdapter(piecesSeparatorAdapter); + piecesMapView = new PiecesMapView(context); + piecesMapViewAdapter = new ViewHolderAdapter(piecesMapView); + piecesMapViewAdapter.setViewEnabled(false); + piecesMapViewAdapter.setViewVisibility(View.GONE); + addAdapter(piecesMapViewAdapter); + // Tracker errors errorsSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText( context.getString(R.string.status_errors))); @@ -137,6 +153,22 @@ public class DetailsAdapter extends MergeAdapter { } } + public void updatePieces(List pieces) { + if (pieces == null || pieces.isEmpty()) { + piecesSeparatorAdapter.setViewEnabled(false); + piecesSeparatorAdapter.setViewVisibility(View.GONE); + piecesMapViewAdapter.setViewEnabled(false); + piecesMapViewAdapter.setViewVisibility(View.GONE); + } else { + piecesMapView.setPieces(pieces); + + piecesMapViewAdapter.setViewEnabled(true); + piecesMapViewAdapter.setViewVisibility(View.VISIBLE); + piecesSeparatorAdapter.setViewEnabled(true); + piecesSeparatorAdapter.setViewVisibility(View.VISIBLE); + } + } + /** * Clear currently visible torrent, including header and shown lists */ diff --git a/app/src/main/java/org/transdroid/core/gui/lists/LocalTorrent.java b/app/src/main/java/org/transdroid/core/gui/lists/LocalTorrent.java index 17a18339..51c5c8b0 100644 --- a/app/src/main/java/org/transdroid/core/gui/lists/LocalTorrent.java +++ b/app/src/main/java/org/transdroid/core/gui/lists/LocalTorrent.java @@ -82,10 +82,11 @@ public class LocalTorrent { switch (t.getStatusCode()) { case Waiting: - case Checking: case Error: // Not downloading yet return r.getString(R.string.status_waitingtodl, FileSizeConverter.getSize(t.getTotalSize())); + case Checking: + return r.getString(R.string.status_checking); case Downloading: // Downloading return r.getString( diff --git a/app/src/main/java/org/transdroid/core/gui/lists/PiecesMapView.java b/app/src/main/java/org/transdroid/core/gui/lists/PiecesMapView.java new file mode 100644 index 00000000..805a3cbd --- /dev/null +++ b/app/src/main/java/org/transdroid/core/gui/lists/PiecesMapView.java @@ -0,0 +1,150 @@ +package org.transdroid.core.gui.lists; + +import org.transdroid.R; + +import android.content.Context; +import android.view.View; +import android.graphics.Canvas; +import android.graphics.Paint; + +import java.util.ArrayList; +import java.util.List; +import java.lang.Math; + +class PiecesMapView extends View { + + private final float scale = getContext().getResources().getDisplayMetrics().density; + private final int MINIMUM_HEIGHT = (int) (25 * scale); + private final int MINIMUM_PIECE_WIDTH = (int) (2 * scale); + + private ArrayList pieces = null; + + private final Paint downloadingPaint = new Paint(); + private final Paint donePaint = new Paint(); + private final Paint partialDonePaint = new Paint(); + + public PiecesMapView(Context context) { + super(context); + initPaints(); + } + + private void initPaints() { + downloadingPaint.setColor(getResources().getColor(R.color.torrent_downloading)); + donePaint.setColor(getResources().getColor(R.color.torrent_seeding)); + partialDonePaint.setColor(getResources().getColor(R.color.file_low)); + } + + public void setPieces(List pieces) { + this.pieces = new ArrayList(pieces); + invalidate(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int ws = MeasureSpec.getSize(widthMeasureSpec); + int hs = Math.max(getHeight(), MINIMUM_HEIGHT); + setMeasuredDimension(ws, hs); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (this.pieces == null) { + return; + } + + int height = getHeight(); + int width = getWidth(); + + // downscale + ArrayList piecesScaled; + int pieceWidth; + + pieceWidth = MINIMUM_PIECE_WIDTH; + piecesScaled = new ArrayList(); + + int bucketCount = (int) Math.ceil((double) width / (double) pieceWidth); + int bucketSize = (int) Math.floor((double)this.pieces.size() / (double) bucketCount); + + // loop buckets + for (int i = 0; i < bucketCount; i++) { + + // Get segment of pieces that fall into bucket + int start = i * bucketSize; + + // If this is the last bucket, throw the remainder of the pieces array into it + int end = (i == bucketCount-1) ? this.pieces.size() : (i+1) * bucketSize; + + ArrayList bucket = new ArrayList(this.pieces.subList(start, end)); + + int doneCount = 0; + int downloadingCount = 0; + + // loop pieces in bucket + for(int j = 0; j < bucket.size(); j++) { + // Count downloading pieces + if (bucket.get(j) == 1) { + downloadingCount++; + } + // Count finished pieces + else if (bucket.get(j) == 2) { + doneCount++; + } + } + + int state; + // If a piece is downloading show bucket as downloading + if (downloadingCount > 0) { + state = 1; + } + // If all pieces are done, show bucket as done + else if (doneCount == bucket.size()) { + state = 2; + } + // Some done pieces, show bucket as partially done + else if (doneCount > 0) { + state = 3; + } + // bucket is not downloaded + else { + state = 0; + } + + piecesScaled.add(state); + } + + String scaledPiecesString = ""; + for (int s : piecesScaled) + { + scaledPiecesString += s; + } + + // Draw downscaled peices + for (int i = 0; i < piecesScaled.size(); i++) { + int piece = piecesScaled.get(i); + + if (piece == 0) { + continue; + } + + Paint paint = new Paint(); + switch (piece) { + case 1: + paint = downloadingPaint; + break; + case 2: + paint = donePaint; + break; + case 3: + paint = partialDonePaint; + break; + } + int x = i * pieceWidth; + + canvas.drawRect(x, 0, x + pieceWidth, height, paint); + } + } + +} + diff --git a/app/src/main/java/org/transdroid/daemon/Daemon.java b/app/src/main/java/org/transdroid/daemon/Daemon.java index 69e92121..121f6674 100644 --- a/app/src/main/java/org/transdroid/daemon/Daemon.java +++ b/app/src/main/java/org/transdroid/daemon/Daemon.java @@ -363,12 +363,12 @@ public enum Daemon { public static boolean supportsAddByMagnetUrl(Daemon type) { return type == uTorrent || type == BitTorrent || type == Transmission || type == Synology || type == Deluge || type == DelugeRpc - || type == Bitflu || type == KTorrent || type == rTorrent || type == qBittorrent || type == BitComet || type == Aria2 - || type == tTorrent || type == Dummy; + || type == Deluge2Rpc || type == Bitflu || type == KTorrent || type == rTorrent || type == qBittorrent || type == BitComet + || type == Aria2 || type == tTorrent || type == Dummy; } public static boolean supportsRemoveWithData(Daemon type) { - return type == uTorrent || type == Vuze || type == Transmission || type == Deluge || type == DelugeRpc + return type == uTorrent || type == Vuze || type == Transmission || type == Deluge || type == DelugeRpc || type == Deluge2Rpc || type == BitTorrent || type == Tfb4rt || type == DLinkRouterBT || type == Bitflu || type == qBittorrent || type == BuffaloNas || type == BitComet || type == rTorrent || type == Aria2 || type == tTorrent || type == Dummy; } @@ -396,7 +396,7 @@ public enum Daemon { } public static boolean supportsSetDownloadLocation(Daemon type) { - return type == Transmission || type == Deluge || type == DelugeRpc || type == Deluge2Rpc|| type == qBittorrent || type == Dummy; + return type == Transmission || type == Deluge || type == DelugeRpc || type == Deluge2Rpc || type == qBittorrent || type == Dummy; } public static boolean supportsSetAlternativeMode(Daemon type) { @@ -404,7 +404,7 @@ public enum Daemon { } public static boolean supportsSetTrackers(Daemon type) { - return type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeRpc || type == Deluge2Rpc|| type == Dummy; + return type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeRpc || type == Deluge2Rpc || type == Dummy; } public static boolean supportsForceRecheck(Daemon type) { @@ -412,6 +412,14 @@ public enum Daemon { || type == Transmission || type == Dummy || type == qBittorrent; } + public static boolean supportsSequentialDownload(Daemon type) { + return type == qBittorrent; + } + + public static boolean supportsFirstLastPiece(Daemon type) { + return type == qBittorrent; + } + public static boolean supportsExtraPassword(Daemon type) { return type == Deluge || type == Aria2; } diff --git a/app/src/main/java/org/transdroid/daemon/DaemonMethod.java b/app/src/main/java/org/transdroid/daemon/DaemonMethod.java index f9ec9b7b..e0e242ef 100644 --- a/app/src/main/java/org/transdroid/daemon/DaemonMethod.java +++ b/app/src/main/java/org/transdroid/daemon/DaemonMethod.java @@ -44,7 +44,9 @@ public enum DaemonMethod { SetTrackers (19), SetAlternativeMode (20), GetStats (21), - ForceRecheck (22); + ForceRecheck (22), + ToggleSequentialDownload(23), + ToggleFirstLastPieceDownload(24); private int code; private static final Map lookup = new HashMap<>(); 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 0982053d..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,660 +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; - - public QbittorrentAdapter(DaemonSettings settings) { - this.settings = settings; - } - - 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; - - try { - - // 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); - - // 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; - } - } - } - - } catch (Exception e) { - // 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 { - // 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) { - if (c.getName().equals("SID")) { - // And here it is! Okay, no need authenticate again. - return; - } - } - - 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: - 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), parseJsonLabels(result)); - - case GetTorrentDetails: - - // Request tracker and error details for a specific teacher - String mhash = task.getTargetTorrent().getUniqueID(); - JSONArray messages = - new JSONArray(makeRequest(log, (version >= 30200 ? "/query/propertiesTrackers/" : "/json/propertiesTrackers/") + mhash)); - return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(messages)); - - 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)); - - case AddByFile: - - // Upload a local .torrent file - String ufile = ((AddByFileTask) task).getFile(); - makeUploadRequest("/command/upload", ufile, log); - return new DaemonTaskSuccessResult(task); - - case AddByUrl: - - // Request to add a torrent by URL - String url = ((AddByUrlTask) task).getUrl(); - makeRequest(log, "/command/download", new BasicNameValuePair("urls", url)); - return new DaemonTaskSuccessResult(task); - - case AddByMagnetUrl: - - // Request to add a magnet link by URL - String magnet = ((AddByMagnetUrlTask) task).getUrl(); - makeRequest(log, "/command/download", new BasicNameValuePair("urls", magnet)); - return new DaemonTaskSuccessResult(task); - - case Remove: - - // Remove a torrent - RemoveTask removeTask = (RemoveTask) task; - makeRequest(log, (removeTask.includingData() ? "/command/deletePerm" : "/command/delete"), - new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID())); - return new DaemonTaskSuccessResult(task); - - case Pause: - - // Pause a torrent - makeRequest(log, "/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); - return new DaemonTaskSuccessResult(task); - - case PauseAll: - - // Resume all torrents - makeRequest(log, "/command/pauseall"); - return new DaemonTaskSuccessResult(task); - - case Resume: - - // Resume a torrent - makeRequest(log, "/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); - return new DaemonTaskSuccessResult(task); - - case ResumeAll: - - // Resume all torrents - 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()) { - makeRequest(log, "/command/setFilePrio", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()), - new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair("priority", newPrio)); - } - return new DaemonTaskSuccessResult(task); + 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); + } + } + } + + version = parseVersionNumber(versionText); + + } catch (Exception e) { + // Unable to establish version number; assume an old version by setting it to version 1 + version = 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 += 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; + } + + // Have we already authenticated? Check if we have the cookie that we need + if (isAuthenticated()) { + return; + } + + 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) { + } + } + + 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 ForceRecheck: + case AddByUrl: - // Force recheck a torrent - makeRequest(log, "/command/recheck", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); + // 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 SetLabel: + 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"; + } - SetLabelTask labelTask = (SetLabelTask) task; - makeRequest(log, "/command/setCategory", - new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()), - new BasicNameValuePair("category", labelTask.getNewLabel())); - return new DaemonTaskSuccessResult(task); + makeRequest(log, path, new BasicNameValuePair("urls", magnet)); + return new DaemonTaskSuccessResult(task); - case SetDownloadLocation: + 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")); + } + + } else { + path = (removeTask.includingData() ? "/command/deletePerm" : "/command/delete"); + makeRequest(log, path, new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID())); + } - SetDownloadLocationTask setLocationTask = (SetDownloadLocationTask) task; - makeRequest(log, "/command/setLocation", - new BasicNameValuePair("hashes", task.getTargetTorrent().getUniqueID()), - new BasicNameValuePair("location", setLocationTask.getNewLocation())); - return new DaemonTaskSuccessResult(task); + return new DaemonTaskSuccessResult(task); - case SetTransferRates: + case Pause: - // Request to set the maximum transfer rates - 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)); + // 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())); + } - makeRequest(log, "/command/setGlobalDlLimit", new BasicNameValuePair("limit", dl)); - makeRequest(log, "/command/setGlobalUpLimit", new BasicNameValuePair("limit", ul)); - return new DaemonTaskSuccessResult(task); + return new DaemonTaskSuccessResult(task); - case GetStats: + case PauseAll: - // Refresh alternative download speeds setting - JSONObject stats = new JSONObject(makeRequest(log, "/sync/maindata?rid=0")); - 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); + // 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 SetAlternativeMode: + 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); - // Flip alternative speed mode - makeRequest(log, "/command/toggleAlternativeSpeedLimits"); - 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 - HttpPost httppost = new HttpPost(buildWebUIUrl(path)); - 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()); - } + 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); - } - - 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); - - 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()); - 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) 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); - } - } - - // Return the list - return new TorrentDetails(trackers, errors); - - } - - private List