Browse Source

Initial connit

pull/407/head
Alon Albert 6 years ago
parent
commit
e54cc6e1a0
  1. 50
      app/src/main/java/deluge/Util.java
  2. 30
      app/src/main/java/deluge/api/DelugeException.java
  3. 78
      app/src/main/java/deluge/api/DelugeFuture.java
  4. 16
      app/src/main/java/deluge/api/ResponseCallback.java
  5. 20
      app/src/main/java/deluge/api/response/IntegerResponse.java
  6. 62
      app/src/main/java/deluge/api/response/Response.java
  7. 6
      app/src/main/java/deluge/api/response/ReturnType.java
  8. 23
      app/src/main/java/deluge/api/response/TorrentsStatusResponse.java
  9. 58
      app/src/main/java/deluge/impl/AsyncResponse.java
  10. 81
      app/src/main/java/deluge/impl/DataHandler.java
  11. 24
      app/src/main/java/deluge/impl/DelugeClient.java
  12. 100
      app/src/main/java/deluge/impl/DelugeSession.java
  13. 26
      app/src/main/java/deluge/impl/OngoingRequest.java
  14. 23
      app/src/main/java/deluge/impl/OngoingRequests.java
  15. 56
      app/src/main/java/deluge/impl/Request.java
  16. 44
      app/src/main/java/deluge/impl/RequestFactory.java
  17. 16
      app/src/main/java/deluge/impl/Util.java
  18. 23
      app/src/main/java/deluge/impl/net/AcceptAllTrustManager.java
  19. 32
      app/src/main/java/deluge/impl/net/ResponseExecutor.java
  20. 41
      app/src/main/java/deluge/impl/net/SSL3Socket.java
  21. 245
      app/src/main/java/deluge/impl/net/Session.java
  22. 34
      app/src/main/java/deluge/impl/net/TorrentField.java
  23. 43
      app/src/main/java/org/transdroid/daemon/Daemon.java
  24. 876
      app/src/main/java/org/transdroid/daemon/Deluge/DelugeDirectAdapter.java
  25. 32
      app/src/main/java/se/dimovski/rencode/Rencode.java
  26. 494
      app/src/main/java/se/dimovski/rencode/RencodeInputStream.java
  27. 404
      app/src/main/java/se/dimovski/rencode/RencodeOutputStream.java
  28. 47
      app/src/main/java/se/dimovski/rencode/TypeCode.java
  29. 74
      app/src/main/java/se/dimovski/rencode/Utils.java
  30. 2
      app/src/main/res/values/strings.xml

50
app/src/main/java/deluge/Util.java

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
package deluge;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Util
{
public static void close(Closeable closeable) throws IOException
{
if (closeable == null)
{
return;
}
closeable.close();
}
public static long copy(InputStream from, OutputStream to) throws IOException
{
try
{
try
{
byte[] buf = new byte[4000];
long total = 0;
while (true)
{
int r = from.read(buf);
if (r == -1)
{
break;
}
to.write(buf, 0, r);
total += r;
}
return total;
}
finally
{
Util.close(to);
}
}
finally
{
Util.close(from);
}
}
}

30
app/src/main/java/deluge/api/DelugeException.java

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
package deluge.api;
public class DelugeException extends Exception
{
private static final long serialVersionUID = 1L;
final public String exceptionType;
final public String exceptionMsg;
final public String traceback;
public DelugeException(String type, String msg, String trace)
{
this.exceptionType = type;
this.exceptionMsg = msg;
this.traceback = trace;
}
@Override
public void printStackTrace()
{
System.err.println(toString());
System.err.println(this.traceback);
}
@Override
public String toString()
{
return DelugeException.class.getCanonicalName() + " " + this.exceptionType + " (" + this.exceptionMsg + ")";
}
}

78
app/src/main/java/deluge/api/DelugeFuture.java

@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
package deluge.api;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import deluge.impl.AsyncResponse;
public class DelugeFuture<V> extends AsyncResponse<V, DelugeException> implements Future<V>
{
private final CountDownLatch latch = new CountDownLatch(1);
private V value;
public DelugeFuture()
{
}
public boolean cancel(boolean mayInterruptIfRunning)
{
// TODO Auto-generated method stub
return false;
}
public V get() throws InterruptedException, ExecutionException
{
this.latch.await();
return this.value;
}
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
if (!this.latch.await(timeout, unit))
{
throw new TimeoutException();
}
return this.value;
}
public boolean isCancelled()
{
// TODO Auto-generated method stub
return false;
}
public boolean isDone()
{
return this.value != null;
}
@Override
public void onError(DelugeException error)
{
setValue(null);
super.onError(error);
}
@Override
public void onResponse(V response)
{
setValue(response);
super.onResponse(response);
}
@Override
public void onServerError(Exception exception)
{
setValue(null);
super.onServerError(exception);
}
public void setValue(V val)
{
this.value = val;
this.latch.countDown();
}
}

16
app/src/main/java/deluge/api/ResponseCallback.java

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
package deluge.api;
public abstract class ResponseCallback<R, E extends Exception>
{
public void onError(E error)
{
error.printStackTrace();
}
public abstract void onResponse(R response);
public void onServerError(Exception exception)
{
exception.printStackTrace();
}
}

20
app/src/main/java/deluge/api/response/IntegerResponse.java

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
package deluge.api.response;
import java.io.IOException;
import java.util.List;
import deluge.api.DelugeException;
public class IntegerResponse extends Response
{
public IntegerResponse(List<Object> data) throws IOException, DelugeException
{
super(data);
}
public Integer getReturnValue()
{
return (Integer) this.returnValue.get(2);
}
}

62
app/src/main/java/deluge/api/response/Response.java

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
package deluge.api.response;
import java.util.List;
import deluge.api.DelugeException;
public abstract class Response
{
protected List<Object> returnValue;
protected final int RPC_RESPONSE = 1;
protected final int RPC_ERROR = 2;
protected final int RPC_EVENT = 3;
public Response(List<Object> decodedObj) throws DelugeException
{
rawData(decodedObj);
}
public int getMessageType()
{
return (Integer) this.returnValue.get(0);
}
public int getRequestId()
{
return (Integer) this.returnValue.get(1);
}
public abstract Object getReturnValue();
private void process() throws DelugeException
{
if (getMessageType() == this.RPC_ERROR)
{
@SuppressWarnings("unchecked")
final List<String> params = (List<String>) this.returnValue.get(2);
final String type = params.get(0);
final String msg = params.get(1);
final String trace = params.get(2);
throw new DelugeException(type, msg, trace);
}
}
public void rawData(List<Object> decodedObj) throws DelugeException
{
this.returnValue = decodedObj;
process();
}
@Override
public String toString()
{
String str = "Response { ";
str += this.returnValue.toString();
str += " }";
return str;
}
}

6
app/src/main/java/deluge/api/response/ReturnType.java

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
package deluge.api.response;
public enum ReturnType
{
INTEGER, TORRENTS_STATUS
}

23
app/src/main/java/deluge/api/response/TorrentsStatusResponse.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
package deluge.api.response;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import deluge.api.DelugeException;
public class TorrentsStatusResponse extends Response
{
public TorrentsStatusResponse(List<Object> data) throws IOException, DelugeException
{
super(data);
}
@Override
@SuppressWarnings("unchecked")
public Map<String, Map<String, Object>> getReturnValue()
{
return (Map<String, Map<String, Object>>) this.returnValue.get(2);
}
}

58
app/src/main/java/deluge/impl/AsyncResponse.java

@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
package deluge.impl;
import java.util.ArrayList;
import java.util.List;
import deluge.api.ResponseCallback;
public class AsyncResponse<R, E extends Exception> extends ResponseCallback<R, E>
{
private final List<ResponseCallback<R, E>> callbacks;
public AsyncResponse()
{
this.callbacks = new ArrayList<ResponseCallback<R, E>>();
}
public void addCallback(ResponseCallback<R, E> callback)
{
this.callbacks.add(callback);
}
@Override
public void onError(E error)
{
for (final ResponseCallback<R, E> cb : this.callbacks)
{
cb.onError(error);
}
}
@Override
public void onResponse(R response)
{
for (final ResponseCallback<R, E> cb : this.callbacks)
{
cb.onResponse(response);
}
}
@Override
public void onServerError(Exception exception)
{
for (final ResponseCallback<R, E> cb : this.callbacks)
{
cb.onServerError(exception);
}
}
public void removeCallback(ResponseCallback<R, E> callback)
{
this.callbacks.remove(callback);
}
public void then(ResponseCallback<R, E> callback)
{
addCallback(callback);
}
}

81
app/src/main/java/deluge/impl/DataHandler.java

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
package deluge.impl;
import java.io.IOException;
import java.util.List;
import se.dimovski.rencode.Rencode;
import deluge.api.DelugeException;
import deluge.api.DelugeFuture;
import deluge.api.response.IntegerResponse;
import deluge.api.response.Response;
import deluge.api.response.TorrentsStatusResponse;
import deluge.impl.net.Session.DataCallback;
public class DataHandler implements DataCallback
{
@SuppressWarnings("unchecked")
public void dataRecived(byte[] data)
{
Integer requestId = null;
List<Object> decodedObj;
try
{
decodedObj = (List<Object>) Rencode.decode(data);
requestId = (Integer) decodedObj.get(1);
sendSpecificResponse(requestId, decodedObj);
}
catch (final IOException e)
{
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
private void sendSpecificResponse(Integer requestId, List<Object> decodedObj)
{
final OngoingRequest req = OngoingRequests.remove(requestId);
try
{
switch (req.getType())
{
case INTEGER:
{
final DelugeFuture<IntegerResponse> fut = (DelugeFuture<IntegerResponse>) req.getFuture();
fut.onResponse(new IntegerResponse(decodedObj));
}
break;
case TORRENTS_STATUS:
{
final DelugeFuture<TorrentsStatusResponse> fut = (DelugeFuture<TorrentsStatusResponse>) req
.getFuture();
fut.onResponse(new TorrentsStatusResponse(decodedObj));
}
break;
default:
{
throw new UnsupportedOperationException("Unknown Request: " + req.getType());
}
}
}
catch (final DelugeException e)
{
final DelugeFuture<Response> fut = (DelugeFuture<Response>) req.getFuture();
fut.onError(e);
}
catch (final Exception e)
{
final DelugeFuture<Response> fut = (DelugeFuture<Response>) req.getFuture();
fut.onServerError(e);
}
}
private void onError()
{
}
}

24
app/src/main/java/deluge/impl/DelugeClient.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
package deluge.impl;
public class DelugeClient
{
public static DelugeSession getSession(String host)
{
final String[] parts = host.split(":");
final int port = parts.length < 2 ? DelugeClient.DEFAULT_PORT : Integer.parseInt(parts[1]);
return DelugeClient.getSession(parts[0], port);
}
public static DelugeSession getSession(String host, int port)
{
return DelugeSession.connect(host, port);
}
public static DelugeSession getSessionDefault()
{
return DelugeClient.getSession("127.0.0.1", DelugeClient.DEFAULT_PORT);
}
public static final int DEFAULT_PORT = 58846;
}

100
app/src/main/java/deluge/impl/DelugeSession.java

@ -0,0 +1,100 @@ @@ -0,0 +1,100 @@
package deluge.impl;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import deluge.api.DelugeFuture;
import deluge.api.response.IntegerResponse;
import deluge.api.response.ReturnType;
import deluge.api.response.TorrentsStatusResponse;
import deluge.impl.net.Session;
import deluge.impl.net.TorrentField;
public class DelugeSession
{
public static DelugeSession connect(String host, int port)
{
final Session session = new Session(host, port);
try
{
session.listen(new DataHandler());
}
catch (final IOException e)
{
e.printStackTrace();
}
return new DelugeSession(session);
}
private final Session session;
private DelugeSession(Session session)
{
this.session = session;
}
public DelugeFuture<TorrentsStatusResponse> getTorrentsStatus(Map<Object, Object> filter, TorrentField[] fields)
{
final DelugeFuture<TorrentsStatusResponse> future = new DelugeFuture<TorrentsStatusResponse>();
final Request request = RequestFactory.getTorrentsStatus(filter, fields);
send(request, ReturnType.TORRENTS_STATUS, future);
return future;
}
public DelugeFuture<IntegerResponse> login(String username, String password)
{
final DelugeFuture<IntegerResponse> future = new DelugeFuture<IntegerResponse>();
final Request request = new Request("daemon.login", Util.objects(username, password));
send(request, ReturnType.INTEGER, future);
return future;
}
public DelugeFuture<IntegerResponse> pauseTorrent(List<String> torrentIds)
{
final DelugeFuture<IntegerResponse> future = new DelugeFuture<IntegerResponse>();
final Request request = new Request("core.pause_torrent", Util.objects(torrentIds));
send(request, ReturnType.INTEGER, future);
return future;
}
public DelugeFuture<IntegerResponse> resumeTorrent(List<String> torrentIds)
{
final DelugeFuture<IntegerResponse> future = new DelugeFuture<IntegerResponse>();
final Request request = new Request("core.resume_torrent", Util.objects(torrentIds));
send(request, ReturnType.INTEGER, future);
return future;
}
public DelugeFuture<IntegerResponse> addTorrentFile(String name, String encodedContents, Map<String, Object> options)
{
final DelugeFuture<IntegerResponse> future = new DelugeFuture<IntegerResponse>();
Request request = new Request("core.add_torrent_file", Util.objects(name, encodedContents, options));
send(request, ReturnType.INTEGER, future);
return future;
}
public DelugeFuture<IntegerResponse> removeTorrent(String torrentId, Boolean removeData)
{
final DelugeFuture<IntegerResponse> future = new DelugeFuture<IntegerResponse>();
Request request = new Request("core.remove_torrent", Util.objects(torrentId, removeData));
send(request, ReturnType.INTEGER, future);
return future;
}
private void send(Request request, ReturnType type, Object future)
{
OngoingRequests.put(request.getRequestId(), type, future);
try
{
this.session.send(request.toByteArray());
}
catch (final IOException e)
{
e.printStackTrace();
}
}
}

26
app/src/main/java/deluge/impl/OngoingRequest.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
package deluge.impl;
import deluge.api.response.ReturnType;
public class OngoingRequest
{
private final ReturnType type;
private final Object future;
public OngoingRequest(ReturnType type, Object future)
{
this.type = type;
this.future = future;
}
public Object getFuture()
{
return this.future;
}
public ReturnType getType()
{
return this.type;
}
}

23
app/src/main/java/deluge/impl/OngoingRequests.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
package deluge.impl;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import deluge.api.response.ReturnType;
public class OngoingRequests
{
public static void put(int requestId, ReturnType type, Object future)
{
final OngoingRequest ongoing = new OngoingRequest(type, future);
OngoingRequests.mOngoingRequests.put(requestId, ongoing);
}
public static OngoingRequest remove(int requestId)
{
return OngoingRequests.mOngoingRequests.remove(requestId);
}
private static Map<Integer, OngoingRequest> mOngoingRequests = new ConcurrentHashMap<Integer, OngoingRequest>();
}

56
app/src/main/java/deluge/impl/Request.java

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
package deluge.impl;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import se.dimovski.rencode.Rencode;
public class Request
{
private static AtomicInteger requestCounter = new AtomicInteger();
private final Integer requestId;
private final String method;
private final Object[] args;
private final Map<Object, Object> kwargs;
public Request(String method)
{
this(method, new Object[0]);
}
public Request(String method, Object[] args)
{
this(method, args, new HashMap<Object, Object>());
}
public Request(String method, Object[] args, Map<Object, Object> kwargs)
{
this.requestId = Request.requestCounter.getAndIncrement();
this.method = method;
this.args = args;
this.kwargs = kwargs;
}
public Integer getRequestId()
{
return this.requestId;
}
public byte[] toByteArray()
{
final Object obj = new Object[] { new Object[] { this.requestId, this.method, this.args, this.kwargs } };
try
{
return Rencode.encode(obj);
}
catch (final IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}

44
app/src/main/java/deluge/impl/RequestFactory.java

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
package deluge.impl;
import java.util.Map;
import deluge.impl.net.TorrentField;
public class RequestFactory
{
public static Request getSessionState()
{
return new Request("core.get_session_state");
}
public static Request getTorrentsStatus()
{
return RequestFactory.getTorrentsStatus(null, null);
}
public static Request getTorrentsStatus(Map<Object, Object> filter)
{
return RequestFactory.getTorrentsStatus(filter, null);
}
public static Request getTorrentsStatus(Map<Object, Object> filter, TorrentField[] fields)
{
Object[] fieldNames = new Object[0];
if (fields != null)
{
fieldNames = new Object[fields.length];
for (int i = 0; i < fields.length; i++)
{
fieldNames[i] = fields[i].toString();
}
}
return new Request("core.get_torrents_status", Util.objects(filter, fieldNames));
}
public static Request getTorrentsStatus(TorrentField[] fields)
{
return RequestFactory.getTorrentsStatus(null, null);
}
}

16
app/src/main/java/deluge/impl/Util.java

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
package deluge.impl;
import deluge.impl.net.TorrentField;
public class Util
{
public static TorrentField[] fields(TorrentField... fields)
{
return fields;
}
public static Object[] objects(Object... objects)
{
return objects;
}
}

23
app/src/main/java/deluge/impl/net/AcceptAllTrustManager.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
package deluge.impl.net;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class AcceptAllTrustManager implements X509TrustManager
{
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
}
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
}

32
app/src/main/java/deluge/impl/net/ResponseExecutor.java

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
package deluge.impl.net;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ResponseExecutor
{
ExecutorService mExecutor;
public ResponseExecutor()
{
this.mExecutor = Executors.newFixedThreadPool(20);
}
public void execute(Runnable task)
{
this.mExecutor.execute(task);
}
public void shutdown()
{
if (this.mExecutor != null)
{
this.mExecutor.shutdown();
while (!this.mExecutor.isTerminated())
{
}
System.out.println("Finished all threads");
}
}
}

41
app/src/main/java/deluge/impl/net/SSL3Socket.java

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
package deluge.impl.net;
import java.io.IOException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
public class SSL3Socket
{
public static SSLSocket createSSLv3Socket(String address, int port) throws KeyManagementException,
UnknownHostException, IOException, NoSuchAlgorithmException
{
final TrustManager[] trustAllCerts = new TrustManager[] { new AcceptAllTrustManager() };
final SSLContext sc = SSLContext.getInstance("SSLv3");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
final SSLSocket mySocket = (SSLSocket) sc.getSocketFactory().createSocket(address, port);
final String[] protocols = { "SSLv3", "TLSv1" };
mySocket.setEnabledProtocols(protocols);
mySocket.addHandshakeCompletedListener(new HandshakeCompletedListener()
{
public void handshakeCompleted(HandshakeCompletedEvent event)
{
System.out.println("Handshake complete");
}
});
return mySocket;
}
}

245
app/src/main/java/deluge/impl/net/Session.java

@ -0,0 +1,245 @@ @@ -0,0 +1,245 @@
package deluge.impl.net;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import javax.net.ssl.SSLSocket;
import deluge.Util;
public class Session
{
public interface DataCallback
{
public void dataRecived(byte[] data);
}
public static byte[] decompressByteArray(byte[] data) throws IOException
{
final InputStream from = new InflaterInputStream(new ByteArrayInputStream(data));
final ByteArrayOutputStream to = new ByteArrayOutputStream();
Util.copy(from, to);
byte[] output = to.toByteArray();
from.close();
to.close();
return output;
}
private static byte[] decompress(byte[] input) throws DataFormatException
{
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final Inflater decompresser = new Inflater(false);
decompresser.setInput(input, 0, input.length);
final byte[] result = new byte[1024];
while (!decompresser.finished())
{
final int resultLength = decompresser.inflate(result);
baos.write(result, 0, resultLength);
}
decompresser.end();
final byte[] returnValue = baos.toByteArray();
try
{
baos.close();
}
catch (final IOException e)
{
}
return returnValue;
}
private final BlockingQueue<byte[]> queue = new ArrayBlockingQueue<byte[]>(50);
private SSLSocket mySocket;
String myAddress;
int myPort;
Thread sender = null;
public final CountDownLatch latch = new CountDownLatch(1);
public Session(String address, int port)
{
this.myAddress = address;
this.myPort = port;
}
public byte[] compress(byte[] data) throws IOException
{
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final Deflater d = new Deflater();
final DeflaterOutputStream dout = new DeflaterOutputStream(baos, d);
dout.write(data);
dout.close();
final byte[] output = baos.toByteArray();
baos.close();
return output;
}
private void createSocket()
{
if (this.mySocket == null)
{
try
{
this.mySocket = SSL3Socket.createSSLv3Socket(this.myAddress, this.myPort);
this.mySocket.startHandshake();
}
catch (final Exception e1)
{
e1.printStackTrace();
this.mySocket = null;
}
}
this.latch.countDown();
}
public void listen(final DataCallback cb) throws IOException
{
new Thread(new Runnable()
{
public void run()
{
createSocket();
System.out.println("Listening Thread started");
try
{
while (Session.this.mySocket != null)
{
final InputStream inputStream = new BufferedInputStream(Session.this.mySocket.getInputStream());
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bytesRead;
final byte[] buffer = new byte[1024];
while ((bytesRead = inputStream.read(buffer)) != -1)
{
baos.write(buffer);
if (bytesRead < 1024)
{
final byte[] unpacked = Session.decompressByteArray(baos.toByteArray());
baos.reset();
cb.dataRecived(unpacked);
}
}
}
}
catch (final UnsupportedEncodingException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (final IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
try
{
this.latch.await(3, TimeUnit.SECONDS);
}
catch (final InterruptedException e)
{
}
if (this.mySocket == null)
{
throw new IOException();
}
}
public void send(byte[] request) throws IOException
{
if (this.sender == null)
{
sender();
}
try
{
this.queue.put(request);
}
catch (final InterruptedException e)
{
e.printStackTrace();
}
}
public void sender() throws IOException
{
this.sender = new Thread(new Runnable()
{
public void run()
{
createSocket();
System.out.println("Sending Thread started");
try
{
while (Session.this.mySocket != null)
{
byte[] packedData;
try
{
final byte[] x = Session.this.queue.take();
packedData = compress(x);
final OutputStream out = new BufferedOutputStream(Session.this.mySocket.getOutputStream());
out.write(packedData);
out.flush();
}
catch (final InterruptedException e)
{
e.printStackTrace();
}
}
}
catch (final IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
this.sender.start();
try
{
this.latch.await(3, TimeUnit.SECONDS);
}
catch (final InterruptedException e)
{
}
if (this.mySocket == null)
{
throw new IOException();
}
}
}

34
app/src/main/java/deluge/impl/net/TorrentField.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
package deluge.impl.net;
public enum TorrentField
{
ACTIVE_TIME("active_time"), ALL_TIME_DOWNLOAD("all_time_download"), COMPACT("compact"), DISTRIBUTED_COPIES(
"distributed_copies"), DOWNLOAD_PAYLOAD_RATE("download_payload_rate"), FILE_PRIORITIES("file_priorities"), HASH(
"hash"), IS_AUTO_MANAGED("is_auto_managed"), IS_FINISHED("is_finished"), MAX_CONNECTIONS("max_connections"), MAX_DOWNLOAD_SPEED(
"max_download_speed"), MAX_UPLOAD_SLOTS("max_upload_slots"), MAX_UPLOAD_SPEED("max_upload_speed"), MESSAGE(
"message"), MOVE_ON_COMPLETED_PATH("move_on_completed_path"), MOVE_ON_COMPLETED("move_on_completed"), MOVE_COMPLETED_PATH(
"move_completed_path"), MOVE_COMPLETED("move_completed"), NEXT_ANNOUNCE("next_announce"), NUM_PEERS(
"num_peers"), NUM_SEEDS("num_seeds"), PAUSED("paused"), PRIORITIZE_FIRST_LAST("prioritize_first_last"), PROGRESS(
"progress"), REMOVE_AT_RATIO("remove_at_ratio"), SAVE_PATH("save_path"), SEEDING_TIME("seeding_time"), SEEDS_PEERS_RATIO(
"seeds_peers_ratio"), SEED_RANK("seed_rank"), STATE("state"), STOP_AT_RATIO("stop_at_ratio"), STOP_RATIO(
"stop_ratio"), TIME_ADDED("time_added"), TOTAL_DONE("total_done"), TOTAL_PAYLOAD_DOWNLOAD(
"total_payload_download"), TOTAL_PAYLOAD_UPLOAD("total_payload_upload"), TOTAL_PEERS("total_peers"), TOTAL_SEEDS(
"total_seeds"), TOTAL_UPLOADED("total_uploaded"), TOTAL_WANTED("total_wanted"), TRACKER("tracker"), TRACKERS(
"trackers"), TRACKER_STATUS("tracker_status"), UPLOAD_PAYLOAD_RATE("upload_payload_rate"), COMMENT(
"comment"), ETA("eta"), FILE_PROGRESS("file_progress"), FILES("files"), IS_SEED("is_seed"), NAME("name"), NUM_FILES(
"num_files"), NUM_PIECES("num_pieces"), PEERS("peers"), PIECE_LENGTH("piece_length"), PRIVATE("private"), QUEUE(
"queue"), RATIO("ratio"), TOTAL_SIZE("total_size"), TRACKER_HOST("tracker_host"), LABEL("label");
private final String value;
TorrentField(String str)
{
this.value = str;
}
@Override
public String toString()
{
return this.value;
}
}

43
app/src/main/java/org/transdroid/daemon/Daemon.java

@ -23,6 +23,7 @@ import org.transdroid.daemon.Bitflu.BitfluAdapter; @@ -23,6 +23,7 @@ import org.transdroid.daemon.Bitflu.BitfluAdapter;
import org.transdroid.daemon.BuffaloNas.BuffaloNasAdapter;
import org.transdroid.daemon.DLinkRouterBT.DLinkRouterBTAdapter;
import org.transdroid.daemon.Deluge.DelugeAdapter;
import org.transdroid.daemon.Deluge.DelugeDirectAdapter;
import org.transdroid.daemon.Ktorrent.KtorrentAdapter;
import org.transdroid.daemon.Qbittorrent.QbittorrentAdapter;
import org.transdroid.daemon.Rtorrent.RtorrentAdapter;
@ -66,6 +67,11 @@ public enum Daemon { @@ -66,6 +67,11 @@ public enum Daemon {
return new DelugeAdapter(settings);
}
},
DelugeDirect {
public IDaemonAdapter createAdapter(DaemonSettings settings) {
return new DelugeDirectAdapter(settings);
}
},
Dummy {
public IDaemonAdapter createAdapter(DaemonSettings settings) {
return new DummyAdapter(settings);
@ -149,6 +155,8 @@ public enum Daemon { @@ -149,6 +155,8 @@ public enum Daemon {
return "daemon_buffalonas";
case Deluge:
return "daemon_deluge";
case DelugeDirect:
return "daemon_deluge_direct";
case DLinkRouterBT:
return "daemon_dlinkrouterbt";
case Dummy:
@ -203,6 +211,9 @@ public enum Daemon { @@ -203,6 +211,9 @@ public enum Daemon {
if (daemonCode.equals("daemon_deluge")) {
return Deluge;
}
if (daemonCode.equals("daemon_deluge_direct")) {
return DelugeDirect;
}
if (daemonCode.equals("daemon_dlinkrouterbt")) {
return DLinkRouterBT;
}
@ -262,6 +273,10 @@ public enum Daemon { @@ -262,6 +273,10 @@ public enum Daemon {
}
case Deluge:
return 8112;
case DelugeDirect:
return 8112;
case Synology:
if (ssl) {
return 5001;
@ -291,11 +306,11 @@ public enum Daemon { @@ -291,11 +306,11 @@ public enum Daemon {
}
public static boolean supportsFileListing(Daemon type) {
return type == Synology || type == Transmission || type == uTorrent || type == BitTorrent || type == KTorrent || type == Deluge || type == rTorrent || type == Vuze || type == DLinkRouterBT || type == Bitflu || type == qBittorrent || type == BuffaloNas || type == BitComet || type == Aria2 || type == tTorrent || type == Dummy;
return type == Synology || type == Transmission || type == uTorrent || type == BitTorrent || type == KTorrent || type == Deluge || type == DelugeDirect || 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 == rTorrent || type == qBittorrent || type == Aria2 || type == Dummy;
return type == uTorrent || type == BitTorrent || type == Daemon.Transmission || type == Deluge || type == DelugeDirect || type == rTorrent || type == qBittorrent || type == Aria2 || type == Dummy;
}
public static boolean needsManualPathSpecified(Daemon type) {
@ -303,7 +318,7 @@ public enum Daemon { @@ -303,7 +318,7 @@ public enum Daemon {
}
public static boolean supportsFilePaths(Daemon type) {
return type == uTorrent || type == BitTorrent || type == Vuze || type == Deluge || type == Transmission || type == rTorrent || type == KTorrent || type == BuffaloNas || type == Aria2 || type == Dummy;
return type == uTorrent || type == BitTorrent || type == Vuze || type == Deluge || type == DelugeDirect || type == Transmission || type == rTorrent || type == KTorrent || type == BuffaloNas || type == Aria2 || type == Dummy;
}
public static boolean supportsStoppingStarting(Daemon type) {
@ -315,11 +330,11 @@ public enum Daemon { @@ -315,11 +330,11 @@ public enum Daemon {
}
public static boolean supportsCustomFolder(Daemon type) {
return type == rTorrent || type == Tfb4rt || type == Bitflu || type == Deluge || type == Transmission || type == BitTorrent || type == uTorrent || type == qBittorrent || type == Dummy;
return type == rTorrent || type == Tfb4rt || type == Bitflu || type == Deluge || type == DelugeDirect || type == Transmission || type == BitTorrent || type == uTorrent || type == qBittorrent || type == Dummy;
}
public static boolean supportsSetTransferRates(Daemon type) {
return type == Deluge || type == Transmission || type == uTorrent || type == BitTorrent || type == rTorrent || type == Vuze || type == BuffaloNas || type == BitComet || type == Aria2 || type == qBittorrent || type == Dummy;
return type == Deluge || type == DelugeDirect || type == Transmission || type == uTorrent || type == BitTorrent || type == rTorrent || type == Vuze || type == BuffaloNas || type == BitComet || type == Aria2 || type == qBittorrent || type == Dummy;
}
public static boolean supportsAddByFile(Daemon type) {
@ -328,31 +343,31 @@ public enum Daemon { @@ -328,31 +343,31 @@ public enum Daemon {
}
public static boolean supportsAddByMagnetUrl(Daemon type) {
return type == uTorrent || type == BitTorrent || type == Transmission || type == Synology || type == Deluge || type == Bitflu || type == KTorrent || type == rTorrent || type == qBittorrent || type == BitComet || type == Aria2 || type == tTorrent || type == Dummy;
return type == uTorrent || type == BitTorrent || type == Transmission || type == Synology || type == Deluge || type == DelugeDirect || 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 == BitTorrent || type == Tfb4rt || type == DLinkRouterBT || type == Bitflu || type == qBittorrent || type == BuffaloNas || type == BitComet || type == rTorrent || type == Aria2 || type == tTorrent || type == Dummy;
return type == uTorrent || type == Vuze || type == Transmission || type == Deluge || type == DelugeDirect || type == BitTorrent || type == Tfb4rt || type == DLinkRouterBT || type == Bitflu || type == qBittorrent || type == BuffaloNas || type == BitComet || type == rTorrent || type == Aria2 || type == tTorrent || type == Dummy;
}
public static boolean supportsFilePrioritySetting(Daemon type) {
return type == BitTorrent || type == uTorrent || type == Transmission || type == KTorrent || type == rTorrent || type == Vuze || type == Deluge || type == qBittorrent || type == tTorrent || type == Dummy;
return type == BitTorrent || type == uTorrent || type == Transmission || type == KTorrent || type == rTorrent || type == Vuze || type == Deluge || type == DelugeDirect || 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 == qBittorrent || type == Dummy;
return type == Vuze || type == Transmission || type == rTorrent || type == Bitflu || type == BitComet || type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeDirect || type == qBittorrent || type == Dummy;
}
public static boolean supportsLabels(Daemon type) {
return type == uTorrent || type == BitTorrent || type == Deluge || type == BitComet || type == rTorrent || type == qBittorrent || type == Dummy;
return type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeDirect || type == BitComet || type == rTorrent || type == qBittorrent || type == Dummy;
}
public static boolean supportsSetLabel(Daemon type) {
return type == uTorrent || type == BitTorrent || type == rTorrent || type == Deluge || type == qBittorrent || type == Dummy;
return type == uTorrent || type == BitTorrent || type == rTorrent || type == Deluge || type == DelugeDirect || type == qBittorrent || type == Dummy;
}
public static boolean supportsSetDownloadLocation(Daemon type) {
return type == Transmission || type == Deluge || type == Dummy;
return type == Transmission || type == Deluge || type == DelugeDirect || type == Dummy;
}
public static boolean supportsSetAlternativeMode(Daemon type) {
@ -360,11 +375,11 @@ public enum Daemon { @@ -360,11 +375,11 @@ public enum Daemon {
}
public static boolean supportsSetTrackers(Daemon type) {
return type == uTorrent || type == BitTorrent || type == Deluge || type == Dummy;
return type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeDirect || type == Dummy;
}
public static boolean supportsForceRecheck(Daemon type) {
return type == uTorrent || type == BitTorrent || type == Deluge || type == rTorrent || type == Transmission || type == Dummy || type == qBittorrent;
return type == uTorrent || type == BitTorrent || type == Deluge || type == DelugeDirect || type == rTorrent || type == Transmission || type == Dummy || type == qBittorrent;
}
public static boolean supportsExtraPassword(Daemon type) {

876
app/src/main/java/org/transdroid/daemon/Deluge/DelugeDirectAdapter.java

@ -0,0 +1,876 @@ @@ -0,0 +1,876 @@
/*
* This file is part of Transdroid <http://www.transdroid.org>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.transdroid.daemon.Deluge;
import com.android.internalcopy.http.multipart.FilePart;
import com.android.internalcopy.http.multipart.MultipartEntity;
import com.android.internalcopy.http.multipart.Part;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HTTP;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Label;
import org.transdroid.daemon.Priority;
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.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.GetTorrentDetailsTask;
import org.transdroid.daemon.task.GetTorrentDetailsTaskSuccessResult;
import org.transdroid.daemon.task.PauseTask;
import org.transdroid.daemon.task.RemoveTask;
import org.transdroid.daemon.task.ResumeTask;
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.SetTrackersTask;
import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.HttpHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import deluge.api.DelugeFuture;
import deluge.api.response.TorrentsStatusResponse;
import deluge.impl.DelugeClient;
import deluge.impl.DelugeSession;
import deluge.impl.net.TorrentField;
/**
* The daemon adapter from the Deluge torrent client using deluged API directly.
* @author alon.albert
*/
public class DelugeDirectAdapter implements IDaemonAdapter {
private static final String LOG_NAME = "Deluge daemon";
private static final String PATH_TO_RPC = "/json";
private static final String PATH_TO_UPLOAD = "/upload";
private static final String RPC_ID = "id";
private static final String RPC_METHOD = "method";
private static final String RPC_PARAMS = "params";
private static final String RPC_RESULT = "result";
private static final String RPC_TORRENTS = "torrents";
private static final String RPC_FILE = "file";
private static final String RPC_FILES = "files";
private static final String RPC_SESSION_ID = "_session_id";
private static final String RPC_METHOD_LOGIN = "auth.login";
private static final String RPC_METHOD_GET = "web.update_ui";
private static final String RPC_METHOD_STATUS = "core.get_torrent_status";
private static final String RPC_METHOD_ADD = "core.add_torrent_url";
private static final String RPC_METHOD_ADD_MAGNET = "core.add_torrent_magnet";
private static final String RPC_METHOD_ADD_FILE = "web.add_torrents";
private static final String RPC_METHOD_REMOVE = "core.remove_torrent";
private static final String RPC_METHOD_PAUSE = "core.pause_torrent";
private static final String RPC_METHOD_PAUSE_ALL = "core.pause_all_torrents";
private static final String RPC_METHOD_RESUME = "core.resume_torrent";
private static final String RPC_METHOD_RESUME_ALL = "core.resume_all_torrents";
private static final String RPC_METHOD_SETCONFIG = "core.set_config";
private static final String RPC_METHOD_SETFILE = "core.set_torrent_file_priorities";
//private static final String RPC_METHOD_SETOPTIONS = "core.set_torrent_options";
private static final String RPC_METHOD_MOVESTORAGE = "core.move_storage";
private static final String RPC_METHOD_SETTRACKERS = "core.set_torrent_trackers";
private static final String RPC_METHOD_FORCERECHECK = "core.force_recheck";
private static final String RPC_METHOD_SETLABEL = "label.set_torrent";
private static final String RPC_NAME = "name";
private static final String RPC_STATUS = "state";
private static final String RPC_MESSAGE = "message";
private static final String RPC_SAVEPATH = "save_path";
private static final String RPC_MAXDOWNLOAD = "max_download_speed";
private static final String RPC_MAXUPLOAD = "max_upload_speed";
private static final String RPC_RATEDOWNLOAD = "download_payload_rate";
private static final String RPC_RATEUPLOAD = "upload_payload_rate";
private static final String RPC_NUMSEEDS = "num_seeds";
private static final String RPC_TOTALSEEDS = "total_seeds";
private static final String RPC_NUMPEERS = "num_peers";
private static final String RPC_TOTALPEERS = "total_peers";
private static final String RPC_ETA = "eta";
private static final String RPC_TIMEADDED = "time_added";
private static final String RPC_DOWNLOADEDEVER = "total_done";
private static final String RPC_UPLOADEDEVER = "total_uploaded";
private static final String RPC_TOTALSIZE = "total_size";
private static final String RPC_PARTDONE = "progress";
private static final String RPC_LABEL = "label";
private static final String RPC_TRACKERS = "trackers";
private static final String RPC_TRACKER_STATUS = "tracker_status";
private static final TorrentField[] RETREIVE_FIELDS = new TorrentField[] {
TorrentField.NAME,
TorrentField.STATE,
TorrentField.SAVE_PATH,
TorrentField.DOWNLOAD_PAYLOAD_RATE,
TorrentField.UPLOAD_PAYLOAD_RATE,
TorrentField.NUM_PEERS,
TorrentField.NUM_SEEDS,
TorrentField.TOTAL_PEERS,
TorrentField.TOTAL_SEEDS,
TorrentField.ETA,
TorrentField.TOTAL_DONE,
TorrentField.TOTAL_UPLOADED,
TorrentField.TOTAL_SIZE,
TorrentField.PROGRESS,
TorrentField.LABEL,
TorrentField.MESSAGE,
TorrentField.TIME_ADDED,
TorrentField.TRACKER_STATUS,
};
private static final String[] RPC_FIELDS_ARRAY =
new String[]{RPC_NAME, RPC_STATUS, RPC_SAVEPATH, RPC_RATEDOWNLOAD, RPC_RATEUPLOAD, RPC_NUMPEERS,
RPC_NUMSEEDS, RPC_TOTALPEERS, RPC_TOTALSEEDS, RPC_ETA, RPC_DOWNLOADEDEVER, RPC_UPLOADEDEVER,
RPC_TOTALSIZE, RPC_PARTDONE, RPC_LABEL, RPC_MESSAGE, RPC_TIMEADDED, RPC_TRACKER_STATUS};
private static final String RPC_DETAILS = "files";
private static final String RPC_INDEX = "index";
private static final String RPC_PATH = "path";
private static final String RPC_SIZE = "size";
private static final String RPC_FILEPROGRESS = "file_progress";
private static final String RPC_FILEPRIORITIES = "file_priorities";
private DaemonSettings settings;
private DefaultHttpClient httpclient;
private DelugeSession delugeSession;
private Cookie sessionCookie;
private int version = -1;
public DelugeDirectAdapter(DaemonSettings settings) {
this.settings = settings;
}
public JSONArray addTorrentByFile(String file, Log log) throws JSONException, IOException, DaemonException {
String url = buildWebUIUrl() + PATH_TO_UPLOAD;
log.d(LOG_NAME, "Uploading a file to the Deluge daemon: " + url);
// Initialise the HTTP client
if (httpclient == null) {
initialise();
}
// Setup client using POST
HttpPost httppost = new HttpPost(url);
File upload = new File(URI.create(file));
Part[] parts = {new FilePart(RPC_FILE, upload)};
httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));
// Make request
HttpResponse response = httpclient.execute(httppost);
// Read JSON response
InputStream instream = response.getEntity().getContent();
String result = HttpHelper.convertStreamToString(instream);
// If the upload succeeded, add the torrent file on the server
// For this we need the file name, which is now send as a JSON object like:
// {"files": ["/tmp/delugeweb/tmp00000.torrent"], "success": true}
String remoteFile = (new JSONObject(result)).getJSONArray(RPC_FILES).getString(0);
JSONArray params = new JSONArray();
JSONArray files = new JSONArray();
JSONObject fileu = new JSONObject();
fileu.put("path", remoteFile);
fileu.put("options", new JSONArray());
files.put(fileu);
params.put(files);
return params;
}
@Override
public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try {
ensureVersion(log);
JSONArray params = new JSONArray();
// Array of the fields needed for files listing calls
JSONArray ffields = new JSONArray();
ffields.put(RPC_DETAILS);
ffields.put(RPC_FILEPROGRESS);
ffields.put(RPC_FILEPRIORITIES);
switch (task.getMethod()) {
case Retrieve:
final TorrentsStatusResponse torrents = delugeSession.getTorrentsStatus(null, RETREIVE_FIELDS).get();
int id = 0;
for (Map.Entry<String, Map<String, Object>> entry : torrents.getReturnValue().entrySet()) {
final String hash = entry.getKey();
final Map<String, Object> values = entry.getValue();
new Torrent(
id,
hash,
(String) values.get(TorrentField.NAME.toString()),
)
}
torrents.get().getReturnValue()
return new RetrieveTaskSuccessResult((RetrieveTask) task,
parseJsonRetrieveTorrents(result.getJSONObject(RPC_RESULT)),
parseJsonRetrieveLabels(result.getJSONObject(RPC_RESULT)));
case GetTorrentDetails:
// Array of the fields needed for files listing calls
JSONArray dfields = new JSONArray();
dfields.put(RPC_TRACKERS);
dfields.put(RPC_TRACKER_STATUS);
// Request file listing of a torrent
params.put(task.getTargetTorrent().getUniqueID()); // torrent_id
params.put(dfields); // keys
JSONObject dinfo = makeRequest(buildRequest(RPC_METHOD_STATUS, params), log);
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task,
parseJsonTorrentDetails(dinfo.getJSONObject(RPC_RESULT)));
case GetFileList:
// Request file listing of a torrent
params.put(task.getTargetTorrent().getUniqueID()); // torrent_id
params.put(ffields); // keys
JSONObject finfo = makeRequest(buildRequest(RPC_METHOD_STATUS, params), log);
return new GetFileListTaskSuccessResult((GetFileListTask) task,
parseJsonFileListing(finfo.getJSONObject(RPC_RESULT), task.getTargetTorrent()));
case AddByFile:
// Request to add a torrent by local .torrent file
String file = ((AddByFileTask) task).getFile();
makeRequest(buildRequest(RPC_METHOD_ADD_FILE, addTorrentByFile(file, log)), log);
return new DaemonTaskSuccessResult(task);
case AddByUrl:
// Request to add a torrent by URL
String url = ((AddByUrlTask) task).getUrl();
params.put(url);
params.put(new JSONArray());
makeRequest(buildRequest(RPC_METHOD_ADD, params), log);
return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl:
// Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask) task).getUrl();
params.put(magnet);
params.put(new JSONArray());
makeRequest(buildRequest(RPC_METHOD_ADD_MAGNET, params), log);
return new DaemonTaskSuccessResult(task);
case Remove:
// Remove a torrent
RemoveTask removeTask = (RemoveTask) task;
params.put(removeTask.getTargetTorrent().getUniqueID());
params.put(removeTask.includingData());
makeRequest(buildRequest(RPC_METHOD_REMOVE, params), log);
return new DaemonTaskSuccessResult(task);
case Pause:
// Pause a torrent
PauseTask pauseTask = (PauseTask) task;
makeRequest(buildRequest(RPC_METHOD_PAUSE, ((new JSONArray())
.put((new JSONArray()).put(pauseTask.getTargetTorrent().getUniqueID())))), log);
return new DaemonTaskSuccessResult(task);
case PauseAll:
// Resume all torrents
makeRequest(buildRequest(RPC_METHOD_PAUSE_ALL, null), log);
return new DaemonTaskSuccessResult(task);
case Resume:
// Resume a torrent
ResumeTask resumeTask = (ResumeTask) task;
makeRequest(buildRequest(RPC_METHOD_RESUME, ((new JSONArray())
.put((new JSONArray()).put(resumeTask.getTargetTorrent().getUniqueID())))), log);
return new DaemonTaskSuccessResult(task);
case ResumeAll:
// Resume all torrents
makeRequest(buildRequest(RPC_METHOD_RESUME_ALL, null), log);
return new DaemonTaskSuccessResult(task);
case SetFilePriorities:
// Set the priorities of files in a specific torrent
SetFilePriorityTask prioTask = (SetFilePriorityTask) task;
// We first need a listing of all the files (because we can only set the priorities all at once)
params.put(task.getTargetTorrent().getUniqueID()); // torrent_id
params.put(ffields); // keys
JSONObject pinfo = makeRequest(buildRequest(RPC_METHOD_STATUS, params), log);
ArrayList<TorrentFile> pfiles =
parseJsonFileListing(pinfo.getJSONObject(RPC_RESULT), prioTask.getTargetTorrent());
// Now prepare the new list of priorities
params = new JSONArray();
params.put(task.getTargetTorrent().getUniqueID()); // torrent_id
JSONArray pfields = new JSONArray();
// Override the priorities in the just retrieved list of all files
for (TorrentFile pfile : pfiles) {
Priority newPriority = pfile.getPriority();
for (TorrentFile forFile : prioTask.getForFiles()) {
if (forFile.getKey().equals(pfile.getKey())) {
// This is a file that we want to assign a new priority to
newPriority = prioTask.getNewPriority();
break;
}
}
pfields.put(convertPriority(newPriority));
}
params.put(pfields); // keys
// Make a single call to set the priorities on all files at once
makeRequest(buildRequest(RPC_METHOD_SETFILE, params), log);
return new DaemonTaskSuccessResult(task);
case SetDownloadLocation:
// Set the download location of some torrent
SetDownloadLocationTask sdlTask = (SetDownloadLocationTask) task;
// This works, but does not move the torrent
//makeRequest(buildRequest(RPC_METHOD_SETOPTIONS, buildSetTorrentOptions(
// sdlTask.getTargetTorrent().getUniqueID(), RPC_DOWNLOADLOCATION, sdlTask.getNewLocation())));
params.put(new JSONArray().put(task.getTargetTorrent().getUniqueID()));
params.put(sdlTask.getNewLocation());
makeRequest(buildRequest(RPC_METHOD_MOVESTORAGE, params), log);
return new DaemonTaskSuccessResult(task);
case SetTransferRates:
// Request to set the maximum transfer rates
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
JSONObject map = new JSONObject();
map.put(RPC_MAXUPLOAD, (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate()));
map.put(RPC_MAXDOWNLOAD, (ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate()));
makeRequest(buildRequest(RPC_METHOD_SETCONFIG, (new JSONArray()).put(map)), log);
return new DaemonTaskSuccessResult(task);
case SetLabel:
// Request to set the label
SetLabelTask labelTask = (SetLabelTask) task;
params.put(task.getTargetTorrent().getUniqueID());
params.put(labelTask.getNewLabel() == null ? "" : labelTask.getNewLabel());
makeRequest(buildRequest(RPC_METHOD_SETLABEL, params), log);
return new DaemonTaskSuccessResult(task);
case SetTrackers:
// Set the trackers of some torrent
SetTrackersTask trackersTask = (SetTrackersTask) task;
JSONArray trackers = new JSONArray();
// Build an JSON arrays of objcts that each have a tier (order) number and an url
for (int i = 0; i < trackersTask.getNewTrackers().size(); i++) {
JSONObject trackerObj = new JSONObject();
trackerObj.put("tier", i);
trackerObj.put("url", trackersTask.getNewTrackers().get(i));
trackers.put(trackerObj);
}
params.put(new JSONArray().put(task.getTargetTorrent().getUniqueID()));
params.put(trackers);
makeRequest(buildRequest(RPC_METHOD_SETTRACKERS, params), log);
return new DaemonTaskSuccessResult(task);
case ForceRecheck:
// Pause a torrent
makeRequest(buildRequest(RPC_METHOD_FORCERECHECK,
((new JSONArray()).put((new JSONArray()).put(task.getTargetTorrent().getUniqueID())))),
log);
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);
} catch (FileNotFoundException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.FileAccessError, e.toString()));
} catch (IOException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.FileAccessError, e.toString()));
} catch (InterruptedException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.FileAccessError, e.toString()));
} catch (ExecutionException e) {
e.printStackTrace();
}
}
/*private JSONArray buildSetTorrentOptions(String torrent, String key, String value) throws JSONException {
JSONArray params = new JSONArray();
params.put(new JSONArray().put(torrent)); // torrent_id
JSONObject sdlmap = new JSONObject();
// Set the option setting to the torrent
sdlmap.put(key, value);
params.put(sdlmap); // options
return params;
}*/
private void ensureVersion(Log log) throws DaemonException {
if (version > 0) {
return;
}
// We still need to retrieve the version number from the server
// Do this by getting the web interface main html page and trying to parse the version number
// Format is something like '<title>Deluge: Web UI 1.3.6</title>'
if (httpclient == null) {
initialise();
}
try {
HttpResponse response = httpclient.execute(new HttpGet(buildWebUIUrl() + "/"));
String main = HttpHelper.convertStreamToString(response.getEntity().getContent());
String titleStartText = "<title>Deluge: Web UI ";
String titleEndText = "</title>";
int titleStart = main.indexOf(titleStartText);
int titleEnd = main.indexOf(titleEndText, titleStart);
if (titleStart >= 0 && titleEnd > titleStart) {
// String found: now parse a version like 2.9.7 as a number like 20907 (allowing 10 places for each .)
String[] parts = main.substring(titleStart + titleStartText.length(), titleEnd).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 (NumberFormatException e) {
log.d(LOG_NAME, "Error parsing the Deluge version code as number: " + e.toString());
// Continue though, ignoring the version number
} catch (Exception e) {
log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString());
}
// Unable to establish version number; assume an old version by setting it to version 1
version = 10000;
}
private JSONObject buildRequest(String sendMethod, JSONArray params) throws JSONException {
// Build request for method
JSONObject request = new JSONObject();
request.put(RPC_METHOD, sendMethod);
request.put(RPC_PARAMS, (params == null) ? new JSONArray() : params);
request.put(RPC_ID, 2);
return request;
}
private synchronized JSONObject makeRequest(JSONObject data, Log log) throws DaemonException {
try {
// Initialise the HTTP client
if (httpclient == null) {
initialise();
}
// Login first?
if (sessionCookie == null) {
// Build login object
String extraPass = settings.getExtraPassword();
if (extraPass == null) {
extraPass = "";
}
JSONObject loginRequest = new JSONObject();
loginRequest.put(RPC_METHOD, RPC_METHOD_LOGIN);
loginRequest.put(RPC_PARAMS, (new JSONArray()).put(extraPass));
loginRequest.put(RPC_ID, 1);
// Set POST URL and data
HttpPost httppost = new HttpPost(buildWebUIUrl() + PATH_TO_RPC);
httppost.setHeader("content-type", "application/json");
StringEntity se = new StringEntity(loginRequest.toString());
httppost.setEntity(se);
// Execute
HttpResponse response = httpclient.execute(httppost);
InputStream instream = response.getEntity().getContent();
// Retrieve session ID
if (!httpclient.getCookieStore().getCookies().isEmpty()) {
for (Cookie cookie : httpclient.getCookieStore().getCookies()) {
if (cookie.getName().equals(RPC_SESSION_ID)) {
sessionCookie = cookie;
break;
}
}
}
// Still no session cookie?
if (sessionCookie == null) {
// Set error message and cancel the action that was requested
throw new DaemonException(ExceptionType.AuthenticationFailure,
"Password error? Server time difference? No (valid) cookie in response and JSON was: " +
HttpHelper.convertStreamToString(instream));
}
}
// Regular action
// Set POST URL and data
HttpPost httppost = new HttpPost(buildWebUIUrl() + PATH_TO_RPC);
httppost.setHeader("content-type", "application/json");
StringEntity se = new StringEntity(data.toString(), HTTP.UTF_8);
httppost.setEntity(se);
// Set session cookie, if it was not in the httpclient object yet
boolean cookiePresent = false;
for (Cookie cookie : httpclient.getCookieStore().getCookies()) {
if (cookie.getName().equals(RPC_SESSION_ID)) {
cookiePresent = true;
break;
}
}
if (!cookiePresent) {
httpclient.getCookieStore().addCookie(sessionCookie);
}
// Execute
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
if (entity != null) {
// Read JSON response
InputStream instream = entity.getContent();
String result = HttpHelper.convertStreamToString(instream);
JSONObject json = new JSONObject(result);
instream.close();
log.d(LOG_NAME, "Success: " +
(result.length() > 300 ? result.substring(0, 300) + "... (" + result.length() + " chars)" :
result));
// Return JSON object
return json;
}
// No result?
throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity in response object.");
} catch (JSONException e) {
log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString());
} 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 Transmission requests.
* @throws DaemonException On missing settings
*/
private void initialise() throws DaemonException {
httpclient = HttpHelper.createStandardHttpClient(settings,
settings.getUsername() != null && !settings.getUsername().equals(""));
httpclient.addRequestInterceptor(HttpHelper.gzipRequestInterceptor);
httpclient.addResponseInterceptor(HttpHelper.gzipResponseInterceptor);
}
/**
* Build the URL of the Transmission web UI from the user settings.
* @return The URL of the RPC API
*/
private String buildWebUIUrl() {
return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() +
(settings.getFolder() == null ? "" : settings.getFolder());
}
private ArrayList<Torrent> parseJsonRetrieveTorrents(JSONObject response) throws JSONException, DaemonException {
// Parse response
ArrayList<Torrent> torrents = new ArrayList<Torrent>();
if (response.isNull(RPC_TORRENTS)) {
throw new DaemonException(ExceptionType.NotConnected,
"Web interface probably not connected to a daemon yet, because 'torrents' is null: " +
response.toString());
}
JSONObject objects = response.getJSONObject(RPC_TORRENTS);
JSONArray names = objects.names();
if (names != null) {
for (int j = 0; j < names.length(); j++) {
JSONObject tor = objects.getJSONObject(names.getString(j));
// Add the parsed torrent to the list
TorrentStatus status = convertDelugeState(tor.getString(RPC_STATUS));
String error = tor.getString(RPC_MESSAGE);
if (tor.getString(RPC_TRACKER_STATUS).indexOf("Error") > 0) {
error += (error.length() > 0 ? "\n" : "") + tor.getString(RPC_TRACKER_STATUS);
//status = TorrentStatus.Error; // Don't report this as blocking error
}
// @formatter:off
torrents.add(new Torrent(j,
names.getString(j),
tor.getString(RPC_NAME),
status,
tor.getString(RPC_SAVEPATH) + settings.getOS().getPathSeperator(),
tor.getInt(RPC_RATEDOWNLOAD),
tor.getInt(RPC_RATEUPLOAD),
tor.getInt(RPC_NUMSEEDS),
tor.getInt(RPC_TOTALSEEDS),
tor.getInt(RPC_NUMPEERS),
tor.getInt(RPC_TOTALPEERS),
tor.getInt(RPC_ETA),
tor.getLong(RPC_DOWNLOADEDEVER),
tor.getLong(RPC_UPLOADEDEVER),
tor.getLong(RPC_TOTALSIZE),
((float) tor.getDouble(RPC_PARTDONE)) / 100f, // Percentage to [0..1]
0f, // Not available
tor.has(RPC_LABEL)? tor.getString(RPC_LABEL): null,
tor.has(RPC_TIMEADDED)? new Date((long) (tor.getDouble(RPC_TIMEADDED) * 1000L)): null,
null, // Not available
error,
settings.getType()));
// @formatter:on
}
}
// Return the list
return torrents;
}
private ArrayList<Label> parseJsonRetrieveLabels(JSONObject response) throws JSONException {
// Get the labels, of they exist (which is dependent on the plugin)
if (!response.has("filters")) {
return null;
}
JSONObject filters = response.getJSONObject("filters");
if (!filters.has("label")) {
return null;
}
JSONArray labels = filters.getJSONArray("label");
// Parse response
ArrayList<Label> allLabels = new ArrayList<Label>();
for (int i = 0; i < labels.length(); i++) {
JSONArray labelAndCount = labels.getJSONArray(i);
if (labelAndCount.getString(0).equals("All")) {
continue; // Ignore the 'All' filter, which is not an actual label
}
allLabels.add(new Label(labelAndCount.getString(0), labelAndCount.getInt(1)));
}
return allLabels;
}
private ArrayList<TorrentFile> parseJsonFileListing(JSONObject response, Torrent torrent) throws JSONException {
// Parse response
ArrayList<TorrentFile> files = new ArrayList<TorrentFile>();
JSONArray objects = response.getJSONArray(RPC_DETAILS);
JSONArray progress = response.getJSONArray(RPC_FILEPROGRESS);
JSONArray priorities = response.getJSONArray(RPC_FILEPRIORITIES);
if (objects != null) {
for (int j = 0; j < objects.length(); j++) {
JSONObject file = objects.getJSONObject(j);
// Add the parsed torrent to the list
// @formatter:off
files.add(new TorrentFile(
"" + file.getInt(RPC_INDEX),
file.getString(RPC_PATH),
file.getString(RPC_PATH),
torrent.getLocationDir() + file.getString(RPC_PATH),
file.getLong(RPC_SIZE),
(long) (progress.getDouble(j) * file.getLong(RPC_SIZE)),
convertDelugePriority(priorities.getInt(j))));
// @formatter:on
}
}
// Return the list
return files;
}
private Priority convertDelugePriority(int priority) {
if (version >= 10303) {
// Priority codes changes from Deluge 1.3.3 onwards
switch (priority) {
case 0:
return Priority.Off;
case 1:
return Priority.Low;
case 7:
return Priority.High;
default:
return Priority.Normal;
}
} else {
switch (priority) {
case 0:
return Priority.Off;
case 2:
return Priority.Normal;
case 5:
return Priority.High;
default:
return Priority.Low;
}
}
}
private int convertPriority(Priority priority) {
if (version >= 10303) {
// Priority codes changes from Deluge 1.3.3 onwards
switch (priority) {
case Off:
return 0;
case Low:
return 1;
case High:
return 7;
default:
return 5;
}
} else {
switch (priority) {
case Off:
return 0;
case Normal:
return 2;
case High:
return 5;
default:
return 1;
}
}
}
private TorrentStatus convertDelugeState(String state) {
// Deluge sends a string with status code
if (state.compareTo("Paused") == 0) {
return TorrentStatus.Paused;
} else if (state.compareTo("Seeding") == 0) {
return TorrentStatus.Seeding;
} else if (state.compareTo("Downloading") == 0 || state.compareTo("Active") == 0) {
return TorrentStatus.Downloading;
} else if (state.compareTo("Checking") == 0) {
return TorrentStatus.Checking;
} else if (state.compareTo("Queued") == 0) {
return TorrentStatus.Queued;
}
return TorrentStatus.Unknown;
}
private TorrentDetails parseJsonTorrentDetails(JSONObject response) throws JSONException {
// Parse response
List<String> trackers = new ArrayList<String>();
JSONArray trackerObjects = response.getJSONArray(RPC_TRACKERS);
if (trackerObjects != null && trackerObjects.length() > 0) {
for (int i = 0; i < trackerObjects.length(); i++) {
trackers.add(trackerObjects.getJSONObject(i).getString("url"));
}
}
List<String> errors = new ArrayList<String>();
String trackerStatus = response.getString(RPC_TRACKER_STATUS);
errors.add(trackerStatus);
return new TorrentDetails(trackers, errors);
}
@Override
public Daemon getType() {
return settings.getType();
}
@Override
public DaemonSettings getSettings() {
return this.settings;
}
}

32
app/src/main/java/se/dimovski/rencode/Rencode.java

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
package se.dimovski.rencode;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class Rencode
{
public static Object decode(byte[] data) throws IOException
{
final InputStream is = new ByteArrayInputStream(data);
final RencodeInputStream inputStream = new RencodeInputStream(is);
final Object decoded = inputStream.readObject();
inputStream.close();
return decoded;
}
public static byte[] encode(Object obj) throws IOException
{
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final RencodeOutputStream output = new RencodeOutputStream(baos);
output.writeObject(obj);
final byte[] encoded = baos.toByteArray();
output.close();
return encoded;
}
}

494
app/src/main/java/se/dimovski/rencode/RencodeInputStream.java

@ -0,0 +1,494 @@ @@ -0,0 +1,494 @@
package se.dimovski.rencode;
import java.io.DataInput;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class RencodeInputStream extends FilterInputStream implements DataInput
{
/**
* The charset that is being used for {@link String}s.
*/
private final String charset;
/**
* Whether or not all byte-Arrays should be decoded as {@link String}s.
*/
private final boolean decodeAsString;
/**
* Creates a {@link RencodeInputStream} with the default encoding.
*/
public RencodeInputStream(InputStream in)
{
this(in, Utils.UTF_8, false);
}
/**
* Creates a {@link RencodeInputStream} with the given encoding.
*/
public RencodeInputStream(InputStream in, String charset)
{
this(in, charset, false);
}
/**
* Creates a {@link RencodeInputStream} with the default encoding.
*/
public RencodeInputStream(InputStream in, boolean decodeAsString)
{
this(in, Utils.UTF_8, decodeAsString);
}
/**
* Creates a {@link RencodeInputStream} with the given encoding.
*/
public RencodeInputStream(InputStream in, String charset, boolean decodeAsString)
{
super(in);
if (charset == null)
{
throw new IllegalArgumentException("charset is null");
}
this.charset = charset;
this.decodeAsString = decodeAsString;
}
/**
* Returns the charset that is used to decode {@link String}s. The default
* value is UTF-8.
*/
public String getCharset()
{
return charset;
}
/**
* Returns true if all byte-Arrays are being turned into {@link String}s.
*/
public boolean isDecodeAsString()
{
return decodeAsString;
}
/**
* Reads and returns an {@link Object}.
*/
public Object readObject() throws IOException
{
int token = readToken();
return readObject(token);
}
/**
* Reads and returns an {@link Object}.
*/
protected Object readObject(int token) throws IOException
{
if (token == TypeCode.DICTIONARY)
{
return readMap0(Object.class);
}
else if (Utils.isFixedDictionary(token))
{
return readMap0(Object.class, token);
}
else if (token == TypeCode.LIST)
{
return readList0(Object.class);
}
else if (Utils.isFixedList(token))
{
return readList0(Object.class, token);
}
else if (Utils.isNumber(token))
{
return readNumber0(token);
}
else if (token == TypeCode.FALSE || token == TypeCode.TRUE)
{
return readBoolean0(token);
}
else if (token == TypeCode.NULL)
{
return null;
}
else if (Utils.isDigit(token) || Utils.isFixedString(token))
{
return readString(token, charset);
}
throw new IOException("Not implemented: " + token);
}
/**
* Reads and returns a {@link Map}.
*/
public Map<String, ?> readMap() throws IOException
{
return readMap(Object.class);
}
/**
* Reads and returns a {@link Map}.
*/
public <T> Map<String, T> readMap(Class<T> clazz) throws IOException
{
int token = readToken();
if (token != TypeCode.DICTIONARY)
{
throw new IOException();
}
return readMap0(clazz);
}
private <T> Map<String, T> readMap0(Class<T> clazz) throws IOException
{
Map<String, T> map = new TreeMap<String, T>();
int token = -1;
while ((token = readToken()) != TypeCode.END)
{
readMapItem(clazz, token, map);
}
return map;
}
private <T> Map<String, T> readMap0(Class<T> clazz, int token) throws IOException
{
Map<String, T> map = new TreeMap<String, T>();
int count = token - TypeCode.EMBEDDED.DICT_START;
for (int i = 0; i < count; i++)
{
readMapItem(clazz, readToken(), map);
}
return map;
}
private <T> void readMapItem(Class<T> clazz, int token, Map<String, T> map) throws UnsupportedEncodingException,
IOException
{
String key = readString(token, charset);
T value = clazz.cast(readObject());
map.put(key, value);
}
public int readToken() throws IOException
{
int token = super.read();
if (token == -1)
{
throw new EOFException();
}
return token;
}
/**
* Reads and returns a {@link List}.
*/
public List<?> readList() throws IOException
{
return readList(Object.class);
}
/**
* Reads and returns a {@link List}.
*/
public <T> List<T> readList(Class<T> clazz) throws IOException
{
int token = readToken();
if (token != TypeCode.LIST)
{
throw new IOException();
}
return readList0(clazz);
}
private <T> List<T> readList0(Class<T> clazz) throws IOException
{
List<T> list = new ArrayList<T>();
int token = -1;
while ((token = readToken()) != TypeCode.END)
{
list.add(clazz.cast(readObject(token)));
}
return list;
}
private <T> List<T> readList0(Class<T> clazz, int token) throws IOException
{
List<T> list = new ArrayList<T>();
int length = token - TypeCode.EMBEDDED.LIST_START;
for (int i = 0; i < length; i++)
{
list.add(clazz.cast(readObject()));
}
return list;
}
public boolean readBoolean() throws IOException
{
return readBoolean0(readToken());
}
public boolean readBoolean0(int token) throws IOException
{
if (token == TypeCode.FALSE)
{
return false;
}
else if (token == TypeCode.TRUE)
{
return true;
}
throw new IOException();
}
public byte readByte() throws IOException
{
return (byte) readToken();
}
public char readChar() throws IOException
{
return (char) readToken();
}
public double readDouble() throws IOException
{
return readNumber().doubleValue();
}
public float readFloat() throws IOException
{
return readNumber().floatValue();
}
public void readFully(byte[] dst) throws IOException
{
readFully(dst, 0, dst.length);
}
public void readFully(byte[] dst, int off, int len) throws IOException
{
int total = 0;
while (total < len)
{
int r = read(dst, total, len - total);
if (r == -1)
{
throw new EOFException();
}
total += r;
}
}
public int readInt() throws IOException
{
return readNumber().intValue();
}
public String readLine() throws IOException
{
return readString();
}
public long readLong() throws IOException
{
return readNumber().longValue();
}
public short readShort() throws IOException
{
return readNumber().shortValue();
}
public String readUTF() throws IOException
{
return readString(Utils.UTF_8);
}
public int readUnsignedByte() throws IOException
{
return readByte() & 0xFF;
}
public int readUnsignedShort() throws IOException
{
return readShort() & 0xFFFF;
}
/**
* Reads and returns a {@link Number}.
*/
public Number readNumber() throws IOException
{
int token = readToken();
if (!Utils.isNumber(token))
{
throw new IOException();
}
return readNumber0(token);
}
private Number readNumber0(int token) throws IOException
{
switch (token)
{
case TypeCode.BYTE:
return (int) readToBuffer(1).get();
case TypeCode.SHORT:
return (int) readToBuffer(2).getShort();
case TypeCode.INT:
return readToBuffer(4).getInt();
case TypeCode.LONG:
return readToBuffer(8).getLong();
case TypeCode.FLOAT:
return readToBuffer(4).getFloat();
case TypeCode.DOUBLE:
return readToBuffer(8).getDouble();
case TypeCode.NUMBER:
return readNumber0();
}
if (Utils.isNegativeFixedNumber(token))
{
return TypeCode.EMBEDDED.INT_NEG_START - 1 - token;
}
else if (Utils.isPositiveFixedNumber(token))
{
return TypeCode.EMBEDDED.INT_POS_START + token;
}
throw new IOException("Unknown number. TypeCode: " + token);
}
private ByteBuffer readToBuffer(int count) throws IOException
{
return ByteBuffer.wrap(readBytesFixed(count));
}
private Number readNumber0() throws IOException
{
StringBuilder buffer = new StringBuilder();
boolean decimal = false;
int token = -1;
while ((token = readToken()) != TypeCode.END)
{
if (token == '.')
{
decimal = true;
}
buffer.append((char) token);
}
try
{
if (decimal)
{
return new BigDecimal(buffer.toString());
}
else
{
return new BigInteger(buffer.toString());
}
}
catch (NumberFormatException err)
{
throw new IOException("NumberFormatException", err);
}
}
public int skipBytes(int n) throws IOException
{
return (int) skip(n);
}
/**
* Reads and returns a byte-Array.
*/
public byte[] readBytes() throws IOException
{
int token = readToken();
return readBytes(token);
}
/**
* Reads and returns a {@link String}.
*/
public String readString() throws IOException
{
return readString(charset);
}
private String readString(String encoding) throws IOException
{
return readString(readToken(), encoding);
}
private String readString(int token, String charset) throws IOException
{
if (Utils.isFixedString(token))
{
int length = token - TypeCode.EMBEDDED.STR_START;
return new String(readBytesFixed(length), charset);
}
return new String(readBytes(token), charset);
}
private byte[] readBytes(int token) throws IOException
{
int length = readLength(token);
return readBytesFixed(length);
}
private byte[] readBytesFixed(int count) throws IOException
{
byte[] data = new byte[count];
readFully(data);
return data;
}
private int readLength(int token) throws IOException
{
StringBuilder buffer = new StringBuilder();
buffer.append((char) token);
while ((token = readToken()) != TypeCode.LENGTH_DELIM)
{
buffer.append((char) token);
}
return Integer.parseInt(buffer.toString());
}
}

404
app/src/main/java/se/dimovski/rencode/RencodeOutputStream.java

@ -0,0 +1,404 @@ @@ -0,0 +1,404 @@
package se.dimovski.rencode;
import java.io.DataOutput;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
public class RencodeOutputStream extends FilterOutputStream implements DataOutput
{
/**
* The {@link String} charset.
*/
private final String charset;
/**
* Creates a {@link RencodeOutputStream} with the default charset.
*/
public RencodeOutputStream(OutputStream out)
{
this(out, Utils.UTF_8);
}
/**
* Creates a {@link RencodeOutputStream} with the given encoding.
*/
public RencodeOutputStream(OutputStream out, String charset)
{
super(out);
if (charset == null)
{
throw new NullPointerException("charset");
}
this.charset = charset;
}
/**
* Returns the charset that is used to encode {@link String}s. The default
* value is UTF-8.
*/
public String getCharset()
{
return charset;
}
/**
* Writes an {@link Object}.
*/
public void writeObject(Object value) throws IOException
{
if (value == null)
{
writeNull();
}
else if (value instanceof byte[])
{
writeBytes((byte[]) value);
}
else if (value instanceof Boolean)
{
writeBoolean((Boolean) value);
}
else if (value instanceof Character)
{
writeChar((Character) value);
}
else if (value instanceof Number)
{
writeNumber((Number) value);
}
else if (value instanceof String)
{
writeString((String) value);
}
else if (value instanceof Collection<?>)
{
writeCollection((Collection<?>) value);
}
else if (value instanceof Map<?, ?>)
{
writeMap((Map<?, ?>) value);
}
else if (value instanceof Enum<?>)
{
writeEnum((Enum<?>) value);
}
else if (value.getClass().isArray())
{
writeArray(value);
}
else
{
writeCustom(value);
}
}
/**
* Writes a null value
*/
public void writeNull() throws IOException
{
write(TypeCode.NULL);
}
/**
* Overwrite this method to write custom objects. The default implementation
* throws an {@link IOException}.
*/
protected void writeCustom(Object value) throws IOException
{
throw new IOException("Cannot encode " + value);
}
/**
* Writes the given byte-Array
*/
public void writeBytes(byte[] value) throws IOException
{
writeBytes(value, 0, value.length);
}
/**
* Writes the given byte-Array
*/
public void writeBytes(byte[] value, int offset, int length) throws IOException
{
write(value, offset, length);
}
/**
* Writes a boolean
*/
public void writeBoolean(boolean value) throws IOException
{
write(value ? TypeCode.TRUE : TypeCode.FALSE);
}
/**
* Writes a char
*/
public void writeChar(int value) throws IOException
{
writeByte(value);
}
/**
* Writes a byte
*/
public void writeByte(int value) throws IOException
{
write(TypeCode.BYTE);
write(value);
}
/**
* Writes a short
*/
public void writeShort(int value) throws IOException
{
write(TypeCode.SHORT);
ByteBuffer buffer = ByteBuffer.allocate(Utils.SHORT_BYTES).putShort((short) value);
write(buffer.array());
}
/**
* Writes an int
*/
public void writeInt(int value) throws IOException
{
write(TypeCode.INT);
ByteBuffer buffer = ByteBuffer.allocate(Utils.INTEGER_BYTES).putInt(value);
write(buffer.array());
}
/**
* Writes a long
*/
public void writeLong(long value) throws IOException
{
write(TypeCode.LONG);
ByteBuffer buffer = ByteBuffer.allocate(Utils.LONG_BYTES).putLong(value);
write(buffer.array());
}
/**
* Writes a float
*/
public void writeFloat(float value) throws IOException
{
write(TypeCode.FLOAT);
ByteBuffer buffer = ByteBuffer.allocate(Utils.FLOAT_BYTES).putFloat(value);
write(buffer.array());
}
/**
* Writes a double
*/
public void writeDouble(double value) throws IOException
{
write(TypeCode.DOUBLE);
ByteBuffer buffer = ByteBuffer.allocate(Utils.DOUBLE_BYTES).putDouble(value);
write(buffer.array());
}
/**
* Writes a {@link Number}
*/
public void writeNumber(Number num) throws IOException
{
if (num instanceof Float)
{
writeFloat(num.floatValue());
}
else if (num instanceof Double)
{
writeDouble(num.doubleValue());
}
if (0 <= num.intValue() && num.intValue() < TypeCode.EMBEDDED.INT_POS_COUNT)
{
write(TypeCode.EMBEDDED.INT_POS_START + num.intValue());
}
else if (-TypeCode.EMBEDDED.INT_NEG_COUNT <= num.intValue() && num.intValue() < 0)
{
write(TypeCode.EMBEDDED.INT_NEG_START - 1 - num.intValue());
}
else if (Byte.MIN_VALUE <= num.intValue() && num.intValue() < Byte.MAX_VALUE)
{
writeByte(num.byteValue());
}
else if (Short.MIN_VALUE <= num.intValue() && num.intValue() < Short.MAX_VALUE)
{
writeShort(num.shortValue());
}
else if (Integer.MIN_VALUE <= num.longValue() && num.longValue() < Integer.MAX_VALUE)
{
writeInt(num.intValue());
}
else if (Long.MIN_VALUE <= num.longValue() && num.longValue() < Long.MAX_VALUE)
{
writeLong(num.longValue());
}
else
{
String number = num.toString();
write(TypeCode.NUMBER);
write(number.getBytes(charset));
write(TypeCode.END);
}
}
/**
* Writes a {@link String}
*/
public void writeString(String value) throws IOException
{
int len = value.length();
if (len < TypeCode.EMBEDDED.STR_COUNT)
{
write(TypeCode.EMBEDDED.STR_START + len);
}
else
{
String lenString = Integer.toString(len);
writeBytes(lenString.getBytes(charset));
write(TypeCode.LENGTH_DELIM);
}
writeBytes(value.getBytes(charset));
}
/**
* Writes a {@link Collection}.
*/
public void writeCollection(Collection<?> value) throws IOException
{
boolean useEndToken = value.size() >= TypeCode.EMBEDDED.LIST_COUNT;
if (useEndToken)
{
write(TypeCode.LIST);
}
else
{
write(TypeCode.EMBEDDED.LIST_START + value.size());
}
for (Object element : value)
{
writeObject(element);
}
if (useEndToken)
{
write(TypeCode.END);
}
}
/**
* Writes a {@link Map}.
*/
public void writeMap(Map<?, ?> map) throws IOException
{
if (!(map instanceof SortedMap<?, ?>))
{
map = new TreeMap<Object, Object>(map);
}
boolean untilEnd = map.size() >= TypeCode.EMBEDDED.DICT_COUNT;
if (untilEnd)
{
write(TypeCode.DICTIONARY);
}
else
{
write(TypeCode.EMBEDDED.DICT_START + map.size());
}
for (Map.Entry<?, ?> entry : map.entrySet())
{
writeObject(entry.getKey());
writeObject(entry.getValue());
}
if (untilEnd)
{
write(TypeCode.END);
}
}
/**
* Writes an {@link Enum}.
*/
public void writeEnum(Enum<?> value) throws IOException
{
writeString(value.name());
}
/**
* Writes an array
*/
public void writeArray(Object value) throws IOException
{
int length = Array.getLength(value);
boolean useEndToken = length >= TypeCode.EMBEDDED.LIST_COUNT;
if (useEndToken)
{
write(TypeCode.LIST);
}
else
{
write(TypeCode.EMBEDDED.LIST_START + length);
}
for (int i = 0; i < length; i++)
{
writeObject(Array.get(value, i));
}
if (useEndToken)
{
write(TypeCode.END);
}
}
/**
* Writes the given {@link String}
*/
public void writeBytes(String value) throws IOException
{
writeString(value);
}
/**
* Writes the given {@link String}
*/
public void writeChars(String value) throws IOException
{
writeString(value);
}
/**
* Writes an UTF encoded {@link String}
*/
public void writeUTF(String value) throws IOException
{
writeBytes(value.getBytes(Utils.UTF_8));
}
}

47
app/src/main/java/se/dimovski/rencode/TypeCode.java

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
package se.dimovski.rencode;
public class TypeCode
{
// The bencode 'typecodes' such as i, d, etc have been
// extended and relocated on the base-256 character set.
public static final char LIST = 59;
public static final char DICTIONARY = 60;
public static final char NUMBER = 61;
public static final char BYTE = 62;
public static final char SHORT = 63;
public static final char INT = 64;
public static final char LONG = 65;
public static final char FLOAT = 66;
public static final char DOUBLE = 44;
public static final char TRUE = 67;
public static final char FALSE = 68;
public static final char NULL = 69;
public static final char END = 127;
public static final char LENGTH_DELIM = ':';
/*
* TypeCodes with embedded values/lengths
*/
public static class EMBEDDED
{
// Positive integers
public static final int INT_POS_START = 0;
public static final int INT_POS_COUNT = 44;
// Negative integers
public static final int INT_NEG_START = 70;
public static final int INT_NEG_COUNT = 32;
// Dictionaries
public static final int DICT_START = 102;
public static final int DICT_COUNT = 25;
// Strings
public static final int STR_START = 128;
public static final int STR_COUNT = 64;
// Lists
public static final int LIST_START = STR_START + STR_COUNT;
public static final int LIST_COUNT = 64;
}
}

74
app/src/main/java/se/dimovski/rencode/Utils.java

@ -0,0 +1,74 @@ @@ -0,0 +1,74 @@
package se.dimovski.rencode;
public class Utils
{
// Character Encodings
public final static String UTF_8 = "UTF-8";
public final static String ISO_8859 = "ISO-8859-1";
// Byte-lengths for types
public static final int SHORT_BYTES = Short.SIZE / Byte.SIZE;
public static final int INTEGER_BYTES = Integer.SIZE / Byte.SIZE;
public static final int LONG_BYTES = Long.SIZE / Byte.SIZE;
public static final int FLOAT_BYTES = Float.SIZE / Byte.SIZE;
public static final int DOUBLE_BYTES = Double.SIZE / Byte.SIZE;
// Maximum length of integer when written as base 10 string.
public static final int MAX_INT_LENGTH = 64;
private static boolean tokenInRange(int token, int start, int count)
{
return start <= token && token < (start + count);
}
public static boolean isNumber(int token)
{
switch (token)
{
case TypeCode.NUMBER:
case TypeCode.BYTE:
case TypeCode.SHORT:
case TypeCode.INT:
case TypeCode.LONG:
case TypeCode.FLOAT:
case TypeCode.DOUBLE:
return true;
}
return isFixedNumber(token);
}
public static boolean isFixedNumber(int token)
{
return isPositiveFixedNumber(token) || isNegativeFixedNumber(token);
}
public static boolean isPositiveFixedNumber(int token)
{
return tokenInRange(token, TypeCode.EMBEDDED.INT_POS_START, TypeCode.EMBEDDED.INT_POS_COUNT);
}
public static boolean isNegativeFixedNumber(int token)
{
return tokenInRange(token, TypeCode.EMBEDDED.INT_NEG_START, TypeCode.EMBEDDED.INT_NEG_COUNT);
}
public static boolean isFixedList(int token)
{
return tokenInRange(token, TypeCode.EMBEDDED.LIST_START, TypeCode.EMBEDDED.LIST_COUNT);
}
public static boolean isFixedDictionary(int token)
{
return tokenInRange(token, TypeCode.EMBEDDED.DICT_START, TypeCode.EMBEDDED.DICT_COUNT);
}
public static boolean isFixedString(int token)
{
return tokenInRange(token, TypeCode.EMBEDDED.STR_START, TypeCode.EMBEDDED.STR_COUNT);
}
public static boolean isDigit(int token)
{
return '0' <= token && token <= '9';
}
}

2
app/src/main/res/values/strings.xml

@ -382,6 +382,7 @@ @@ -382,6 +382,7 @@
<item>BitTorrent 6+</item>
<item>Buffalo NAS -1.31</item>
<item>Deluge 1.2+</item>
<item>Deluge Direct API</item>
<item>DLink Router BT</item>
<item>Ktorrent</item>
<item>qBittorrent</item>
@ -400,6 +401,7 @@ @@ -400,6 +401,7 @@
<item>daemon_bittorrent</item>
<item>daemon_buffalonas</item>
<item>daemon_deluge</item>
<item>daemon_deluge_direct</item>
<item>daemon_dlinkrouterbt</item>
<item>daemon_ktorrent</item>
<item>daemon_qbittorrent</item>

Loading…
Cancel
Save