Alon Albert
7 years ago
30 changed files with 3046 additions and 14 deletions
@ -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); |
||||
} |
||||
} |
||||
} |
@ -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 + ")"; |
||||
} |
||||
} |
@ -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(); |
||||
} |
||||
} |
@ -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(); |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
package deluge.api.response; |
||||
|
||||
public enum ReturnType |
||||
{ |
||||
INTEGER, TORRENTS_STATUS |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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() |
||||
{ |
||||
|
||||
} |
||||
|
||||
} |
@ -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; |
||||
|
||||
} |
@ -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(); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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>(); |
||||
|
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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"); |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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(); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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()); |
||||
} |
||||
} |
@ -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)); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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'; |
||||
} |
||||
} |
Loading…
Reference in new issue