From da00a7c95869c7e8b4c657d5184c3e1502944eeb Mon Sep 17 00:00:00 2001 From: Adam Peck Date: Tue, 10 Sep 2019 00:54:18 -0600 Subject: [PATCH] Add support for Deluge V2 RPC. Fixes #502. Unfortunatley there is no good way to detect the version, so added another choice. --- .../java/org/transdroid/daemon/Daemon.java | 47 +++++++++++------- .../daemon/Deluge/DelugeRpcAdapter.java | 16 ++++--- .../daemon/Deluge/DelugeRpcClient.java | 48 +++++++++++++++++-- app/src/main/res/values/strings.xml | 2 + 4 files changed, 84 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/org/transdroid/daemon/Daemon.java b/app/src/main/java/org/transdroid/daemon/Daemon.java index 1024efe3..69e92121 100644 --- a/app/src/main/java/org/transdroid/daemon/Daemon.java +++ b/app/src/main/java/org/transdroid/daemon/Daemon.java @@ -69,7 +69,12 @@ public enum Daemon { }, DelugeRpc { public IDaemonAdapter createAdapter(DaemonSettings settings) { - return new DelugeRpcAdapter(settings); + return new DelugeRpcAdapter(settings, false); + } + }, + Deluge2Rpc { + public IDaemonAdapter createAdapter(DaemonSettings settings) { + return new DelugeRpcAdapter(settings, true); } }, Dummy { @@ -155,8 +160,10 @@ public enum Daemon { return "daemon_buffalonas"; case Deluge: return "daemon_deluge"; - case DelugeRpc: + case DelugeRpc: return "daemon_deluge_rpc"; + case Deluge2Rpc: + return "daemon_deluge2_rpc"; case DLinkRouterBT: return "daemon_dlinkrouterbt"; case Dummy: @@ -214,6 +221,9 @@ public enum Daemon { if (daemonCode.equals("daemon_deluge_rpc")) { return DelugeRpc; } + if (daemonCode.equals("daemon_deluge2_rpc")) { + return Deluge2Rpc; + } if (daemonCode.equals("daemon_dlinkrouterbt")) { return DLinkRouterBT; } @@ -275,6 +285,7 @@ public enum Daemon { return 8112; case DelugeRpc: + case Deluge2Rpc: return DelugeRpcAdapter.DEFAULT_PORT; case Synology: @@ -308,13 +319,13 @@ public enum Daemon { public static boolean supportsFileListing(Daemon type) { return type == Synology || type == Transmission || type == uTorrent || type == BitTorrent || type == KTorrent || type == Deluge - || type == DelugeRpc || type == rTorrent || type == Vuze || type == DLinkRouterBT || type == Bitflu || type == qBittorrent - || type == BuffaloNas || type == BitComet || type == Aria2 || type == tTorrent || type == Dummy; + || type == DelugeRpc || type == Deluge2Rpc || type == rTorrent || type == Vuze || type == DLinkRouterBT || type == Bitflu + || type == qBittorrent || type == BuffaloNas || type == BitComet || type == Aria2 || type == tTorrent || type == Dummy; } public static boolean supportsFineDetails(Daemon type) { return type == uTorrent || type == BitTorrent || type == Daemon.Transmission || type == Deluge || type == DelugeRpc - || type == rTorrent || type == qBittorrent || type == Aria2 || type == Dummy; + || type == Deluge2Rpc || type == rTorrent || type == qBittorrent || type == Aria2 || type == Dummy; } public static boolean needsManualPathSpecified(Daemon type) { @@ -322,7 +333,7 @@ public enum Daemon { } public static boolean supportsFilePaths(Daemon type) { - return type == uTorrent || type == BitTorrent || type == Vuze || type == Deluge || type == DelugeRpc + return type == uTorrent || type == BitTorrent || type == Vuze || type == Deluge || type == DelugeRpc || type == Deluge2Rpc || type == Transmission || type == rTorrent || type == KTorrent || type == BuffaloNas || type == Aria2 || type == Dummy; } @@ -335,12 +346,12 @@ public enum Daemon { } public static boolean supportsCustomFolder(Daemon type) { - return type == rTorrent || type == Tfb4rt || type == Bitflu || type == Deluge || type == DelugeRpc || type == Aria2 + return type == rTorrent || type == Tfb4rt || type == Bitflu || type == Deluge || type == DelugeRpc || type == Deluge2Rpc || type == Aria2 || type == Transmission || type == BitTorrent || type == uTorrent || type == qBittorrent || type == Dummy; } public static boolean supportsSetTransferRates(Daemon type) { - return type == Deluge || type == DelugeRpc + return type == Deluge || type == DelugeRpc || type == Deluge2Rpc || type == Transmission || type == uTorrent || type == BitTorrent || type == rTorrent || type == Vuze || type == BuffaloNas || type == BitComet || type == Aria2 || type == qBittorrent || type == Dummy; } @@ -364,28 +375,28 @@ public enum Daemon { public static boolean supportsFilePrioritySetting(Daemon type) { return type == BitTorrent || type == uTorrent || type == Transmission || type == KTorrent || type == rTorrent || type == Vuze - || type == Deluge || type == DelugeRpc + || type == Deluge || type == DelugeRpc || type == Deluge2Rpc || type == qBittorrent || type == tTorrent || type == Dummy; } public static boolean supportsDateAdded(Daemon type) { return type == Vuze || type == Transmission || type == rTorrent || type == Bitflu || type == BitComet || type == uTorrent - || type == BitTorrent || type == Deluge || type == DelugeRpc + || type == BitTorrent || type == Deluge || type == DelugeRpc || type == Deluge2Rpc || type == qBittorrent || type == Dummy; } public static boolean supportsLabels(Daemon type) { - return type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeRpc || type == BitComet || type == rTorrent - || type == qBittorrent || type == Dummy; + return type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeRpc || type == Deluge2Rpc || type == BitComet + || type == rTorrent || type == qBittorrent || type == Dummy; } public static boolean supportsSetLabel(Daemon type) { - return type == uTorrent || type == BitTorrent || type == rTorrent || type == Deluge || type == DelugeRpc + return type == uTorrent || type == BitTorrent || type == rTorrent || type == Deluge || type == DelugeRpc || type == Deluge2Rpc || type == qBittorrent || type == Dummy; } public static boolean supportsSetDownloadLocation(Daemon type) { - return type == Transmission || type == Deluge || type == DelugeRpc || type == qBittorrent || type == Dummy; + return type == Transmission || type == Deluge || type == DelugeRpc || type == Deluge2Rpc|| type == qBittorrent || type == Dummy; } public static boolean supportsSetAlternativeMode(Daemon type) { @@ -393,12 +404,12 @@ public enum Daemon { } public static boolean supportsSetTrackers(Daemon type) { - return type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeRpc || type == Dummy; + return type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeRpc || type == Deluge2Rpc|| type == Dummy; } public static boolean supportsForceRecheck(Daemon type) { - return type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeRpc || type == rTorrent || type == Transmission - || type == Dummy || type == qBittorrent; + return type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeRpc || type == Deluge2Rpc || type == rTorrent + || type == Transmission || type == Dummy || type == qBittorrent; } public static boolean supportsExtraPassword(Daemon type) { @@ -406,7 +417,7 @@ public enum Daemon { } public static boolean supportsRemoteRssManagement(Daemon type) { - return type == uTorrent || type == DelugeRpc; + return type == uTorrent || type == DelugeRpc || type == Deluge2Rpc; } } diff --git a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java index 40386719..252cab19 100644 --- a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java @@ -49,16 +49,18 @@ public class DelugeRpcAdapter implements IDaemonAdapter, RemoteRssSupplier { public static final int DEFAULT_PORT = 58846; private final DaemonSettings settings; + private final boolean isVersion2; private int version = -1; - public DelugeRpcAdapter(DaemonSettings settings) { + public DelugeRpcAdapter(DaemonSettings settings, boolean isVersion2) { this.settings = settings; + this.isVersion2 = isVersion2; } @Override public DaemonTaskResult executeTask(Log log, DaemonTask task) { - final DelugeRpcClient client = new DelugeRpcClient(); + final DelugeRpcClient client = new DelugeRpcClient(isVersion2); try { client.connect(settings); switch (task.getMethod()) { @@ -109,7 +111,7 @@ public class DelugeRpcAdapter implements IDaemonAdapter, RemoteRssSupplier { @Override public Daemon getType() { - return Daemon.DelugeRpc; + return isVersion2 ? Daemon.Deluge2Rpc : Daemon.DelugeRpc; } @Override @@ -120,7 +122,7 @@ public class DelugeRpcAdapter implements IDaemonAdapter, RemoteRssSupplier { @Override public ArrayList getRemoteRssChannels(Log log) throws DaemonException { final long now = System.currentTimeMillis(); - final DelugeRpcClient client = new DelugeRpcClient(); + final DelugeRpcClient client = new DelugeRpcClient(isVersion2); try { client.connect(settings); @@ -197,7 +199,7 @@ public class DelugeRpcAdapter implements IDaemonAdapter, RemoteRssSupplier { } else { label = null; } - final DelugeRpcClient client = new DelugeRpcClient(); + final DelugeRpcClient client = new DelugeRpcClient(isVersion2); try { client.connect(settings); @@ -369,10 +371,10 @@ public class DelugeRpcAdapter implements IDaemonAdapter, RemoteRssSupplier { final List torrents = new ArrayList<>(); int id = 0; for (Map torrentMap : torrentMaps) { - final Object timeAdded = torrentMap.get(RPC_TIMEADDED); + final Number timeAdded = (Number) torrentMap.get(RPC_TIMEADDED); final Date timeAddedDate; if (timeAdded != null) { - final long seconds = (long) (float) timeAdded; + final long seconds = timeAdded.longValue(); timeAddedDate = new Date(seconds * 1000L); } else { timeAddedDate = null; diff --git a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcClient.java b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcClient.java index 834d084a..9906d560 100644 --- a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcClient.java +++ b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcClient.java @@ -27,8 +27,10 @@ import se.dimovski.rencode.Rencode; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; +import java.io.InputStream; import java.net.Socket; import java.net.UnknownHostException; +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -36,6 +38,7 @@ import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; import static org.transdroid.daemon.Deluge.DelugeCommon.RPC_METHOD_DAEMON_LOGIN; +import static org.transdroid.daemon.Deluge.DelugeCommon.RPC_METHOD_INFO; /** * A Deluge RPC API Client. @@ -45,13 +48,23 @@ class DelugeRpcClient implements Closeable { private static final int RESPONSE_TYPE_INDEX = 0; private static final int RESPONSE_RETURN_VALUE_INDEX = 2; private static final int RPC_ERROR = 2; + private static final byte V2_PROTOCOL_VERSION = 1; + private static final int V2_HEADER_SIZE = 5; private Socket socket; + private final boolean isVersion2; private static AtomicInteger requestId = new AtomicInteger(); + DelugeRpcClient(boolean isVersion2) { + this.isVersion2 = isVersion2; + } + void connect(DaemonSettings settings) throws DaemonException { try { socket = openSocket(settings); + if (isVersion2) { + sendRequest(RPC_METHOD_INFO); + } if (settings.shouldUseAuthentication()) { sendRequest(RPC_METHOD_DAEMON_LOGIN, settings.getUsername(), settings.getPassword()); } @@ -75,12 +88,26 @@ class DelugeRpcClient implements Closeable { Object sendRequest(String method, Object... args) throws DaemonException { final byte[] requestBytes; try { - requestBytes = compress(Rencode.encode(new Object[]{new Object[]{requestId.getAndIncrement(), method, args, new HashMap<>()}})); + HashMap kwargs = new HashMap<>(); + if (isVersion2 && RPC_METHOD_DAEMON_LOGIN.equals(method)) { + kwargs.put("client_version", "" + V2_PROTOCOL_VERSION); + } + requestBytes = compress(Rencode.encode(new Object[]{new Object[]{requestId.getAndIncrement(), method, args, kwargs}})); } catch (IOException e) { throw new DaemonException(ExceptionType.ConnectionError, "Failed to encode request: " + e.getMessage()); } try { - socket.getOutputStream().write(requestBytes); + if (isVersion2) { + socket.getOutputStream().write( + ByteBuffer.allocate(V2_HEADER_SIZE + requestBytes.length) + .put(V2_PROTOCOL_VERSION) + .putInt(requestBytes.length) + .put(requestBytes) + .array() + ); + } else { + socket.getOutputStream().write(requestBytes); + } return readResponse(); } catch (IOException e) { throw new DaemonException(ExceptionType.ConnectionError, e.getMessage()); @@ -106,9 +133,22 @@ class DelugeRpcClient implements Closeable { @NonNull private Object readResponse() throws DaemonException, IOException { - final InflaterInputStream inflater = new InflaterInputStream(socket.getInputStream()); + final InputStream in = socket.getInputStream(); + final InflaterInputStream inflater = new InflaterInputStream(in); final ByteArrayOutputStream out = new ByteArrayOutputStream(); - final byte[] buffer = new byte[1024]; + + final byte[] buffer; + if (isVersion2) { + final byte[] header = new byte[V2_HEADER_SIZE]; + in.read(header, 0, V2_HEADER_SIZE); + if (header[0] != V2_PROTOCOL_VERSION) { + throw new DaemonException(ExceptionType.ConnectionError, "Unexpected protocol version: " + header[0]); + } + buffer = new byte[ByteBuffer.wrap(header).getInt(1)]; + } else { + buffer = new byte[1024]; + } + while (inflater.available() > 0) { final int n = inflater.read(buffer); if (n > 0) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bbc973c4..7eb5dc56 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -389,6 +389,7 @@ Buffalo NAS -1.31 Deluge 1.2+ Deluge RPC + Deluge 2 RPC DLink Router BT Ktorrent qBittorrent @@ -408,6 +409,7 @@ daemon_buffalonas daemon_deluge daemon_deluge_rpc + daemon_deluge2_rpc daemon_dlinkrouterbt daemon_ktorrent daemon_qbittorrent