Browse Source

Converting last classes to Kotlin.

rewrite-connect
Eric Kok 8 years ago
parent
commit
19201632f6
  1. 11
      build.gradle
  2. 20
      connect/build.gradle
  3. 90
      connect/src/main/java/org/transdroid/connect/Configuration.java
  4. 18
      connect/src/main/java/org/transdroid/connect/Configuration.kt
  5. 46
      connect/src/main/java/org/transdroid/connect/clients/Client.java
  6. 34
      connect/src/main/java/org/transdroid/connect/clients/Client.kt
  7. 79
      connect/src/main/java/org/transdroid/connect/clients/ClientDelegate.java
  8. 63
      connect/src/main/java/org/transdroid/connect/clients/ClientDelegate.kt
  9. 13
      connect/src/main/java/org/transdroid/connect/clients/ClientSpec.java
  10. 11
      connect/src/main/java/org/transdroid/connect/clients/ClientSpec.kt
  11. 82
      connect/src/main/java/org/transdroid/connect/clients/Feature.java
  12. 70
      connect/src/main/java/org/transdroid/connect/clients/Feature.kt
  13. 22
      connect/src/main/java/org/transdroid/connect/clients/UnsupportedFeatureException.java
  14. 13
      connect/src/main/java/org/transdroid/connect/clients/UnsupportedFeatureException.kt
  15. 239
      connect/src/main/java/org/transdroid/connect/clients/rtorrent/Rtorrent.java
  16. 182
      connect/src/main/java/org/transdroid/connect/clients/rtorrent/Rtorrent.kt
  17. 32
      connect/src/main/java/org/transdroid/connect/clients/rtorrent/Service.java
  18. 34
      connect/src/main/java/org/transdroid/connect/clients/rtorrent/Service.kt
  19. 29
      connect/src/main/java/org/transdroid/connect/clients/rtorrent/TorrentSpec.java
  20. 26
      connect/src/main/java/org/transdroid/connect/clients/rtorrent/TorrentSpec.kt
  21. 5
      connect/src/main/java/org/transdroid/connect/clients/transmission/Transmission.java
  22. 3
      connect/src/main/java/org/transdroid/connect/clients/transmission/Transmission.kt
  23. 308
      connect/src/main/java/org/transdroid/connect/model/Torrent.java
  24. 122
      connect/src/main/java/org/transdroid/connect/model/Torrent.kt
  25. 14
      connect/src/main/java/org/transdroid/connect/model/TorrentStatus.java
  26. 12
      connect/src/main/java/org/transdroid/connect/model/TorrentStatus.kt
  27. 50
      connect/src/main/java/org/transdroid/connect/util/OkHttpBuilder.java
  28. 37
      connect/src/main/java/org/transdroid/connect/util/OkHttpBuilder.kt
  29. 29
      connect/src/main/java/org/transdroid/connect/util/RxUtil.java
  30. 5
      connect/src/main/java/org/transdroid/connect/util/RxUtil.kt
  31. 11
      connect/src/main/java/org/transdroid/connect/util/StringUtil.java
  32. 17
      connect/src/test/java/org/transdroid/connect/clients/rtorrent/MockTorrent.kt
  33. 90
      connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentLiveTest.kt
  34. 90
      connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentMockTest.kt
  35. 77
      connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentTest.java

11
build.gradle

@ -1,12 +1,15 @@
buildscript { buildscript {
ext.kotlin_version = '1.1.1'
repositories { repositories {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:2.3.1'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
} }
} }
apply plugin: 'kotlin'
allprojects { allprojects {
repositories { repositories {
@ -15,3 +18,9 @@ allprojects {
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
} }
} }
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
}

20
connect/build.gradle

@ -1,14 +1,24 @@
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'kotlin'
dependencies { dependencies {
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile 'com.squareup.okhttp3:logging-interceptor:3.5.0' compile 'com.squareup.okhttp3:logging-interceptor:3.7.0'
compile 'com.github.erickok:retrofit-xmlrpc:master-SNAPSHOT' compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
compile 'com.burgstaller:okhttp-digest:1.10' compile 'com.burgstaller:okhttp-digest:1.10'
compile 'com.github.erickok:retrofit-xmlrpc:6e2c623763'
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
testCompile 'com.google.truth:truth:0.31' testCompile 'com.google.truth:truth:0.31'
testCompile 'com.squareup.okhttp3:mockwebserver:3.7.0'
} }
sourceCompatibility = "1.7" buildscript {
targetCompatibility = "1.7" ext.kotlin_version = '1.1.1'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

90
connect/src/main/java/org/transdroid/connect/Configuration.java

@ -1,90 +0,0 @@
package org.transdroid.connect;
import com.burgstaller.okhttp.digest.Credentials;
import org.transdroid.connect.clients.Client;
import org.transdroid.connect.clients.ClientSpec;
/**
* Configuration settings to connect to a torrent client.
*/
public final class Configuration {
private final Client client;
private final String baseUrl;
private String endpoint;
private Credentials credentials;
private boolean loggingEnabled;
public static class Builder {
private final Client client;
private String baseUrl;
private String endpoint;
private Credentials credentials;
private boolean loggingEnabled;
public Builder(Client client) {
this.client = client;
}
public Builder baseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
public Builder endpoint(String endpoint) {
this.endpoint = endpoint;
return this;
}
public Builder credentials(String user, String password) {
this.credentials = new Credentials(user, password);
return this;
}
public Builder loggingEnabled(boolean loggingEnabled) {
this.loggingEnabled = loggingEnabled;
return this;
}
public Configuration build() {
Configuration configuration = new Configuration(client, baseUrl);
configuration.endpoint = this.endpoint;
configuration.credentials = this.credentials;
configuration.loggingEnabled = this.loggingEnabled;
return configuration;
}
}
private Configuration(Client client, String baseUrl) {
this.client = client;
this.baseUrl = baseUrl;
}
public Client client() {
return client;
}
public String baseUrl() {
return baseUrl;
}
public String endpoint() {
return endpoint;
}
public boolean loggingEnabled() {
return loggingEnabled;
}
public Credentials credentials() {
return credentials;
}
public ClientSpec createClient() {
return client.createClient(this);
}
}

18
connect/src/main/java/org/transdroid/connect/Configuration.kt

@ -0,0 +1,18 @@
package org.transdroid.connect
import com.burgstaller.okhttp.digest.Credentials
import org.transdroid.connect.clients.Client
/**
* Configuration settings to connect to a torrent client.
*/
data class Configuration(
val client: Client,
val baseUrl: String,
var endpoint: String? = null,
var credentials: Credentials? = null,
var loggingEnabled: Boolean = false) {
fun createClient() = client.createClient(this)
}

46
connect/src/main/java/org/transdroid/connect/clients/Client.java

@ -1,46 +0,0 @@
package org.transdroid.connect.clients;
import org.transdroid.connect.Configuration;
import org.transdroid.connect.clients.rtorrent.Rtorrent;
import org.transdroid.connect.clients.transmission.Transmission;
/**
* Support clients enum, allowing you to create instances (given a configuration) and query for feature support.
*/
@SuppressWarnings("unchecked")
public enum Client {
RTORRENT(Rtorrent.class) {
@Override
public Rtorrent create(Configuration configuration) {
return new Rtorrent(configuration);
}
},
TRANSMISSION(Transmission.class) {
@Override
public Transmission create(Configuration configuration) {
return new Transmission();
}
};
final Class<?> type;
Client(Class<?> type) {
this.type = type;
}
public final Class<?> type() {
return type;
}
abstract Object create(Configuration configuration);
public final ClientSpec createClient(Configuration configuration) {
return new ClientDelegate(configuration.client(), create(configuration));
}
public final boolean supports(Feature feature) {
return feature.type().isAssignableFrom(type);
}
}

34
connect/src/main/java/org/transdroid/connect/clients/Client.kt

@ -0,0 +1,34 @@
package org.transdroid.connect.clients
import org.transdroid.connect.Configuration
import org.transdroid.connect.clients.rtorrent.Rtorrent
import org.transdroid.connect.clients.transmission.Transmission
import kotlin.reflect.KClass
/**
* Support clients enum, allowing you to create instances (given a configuration) and query for feature support.
*/
enum class Client constructor(internal val type: KClass<*>) {
RTORRENT(Rtorrent::class) {
override fun create(configuration: Configuration): Rtorrent {
return Rtorrent(configuration)
}
},
TRANSMISSION(Transmission::class) {
override fun create(configuration: Configuration): Transmission {
return Transmission()
}
};
internal abstract fun create(configuration: Configuration): Any
fun createClient(configuration: Configuration): ClientSpec {
return ClientDelegate(configuration.client, create(configuration))
}
fun supports(feature: Feature): Boolean {
return feature.type.java.isAssignableFrom(type.java)
}
}

79
connect/src/main/java/org/transdroid/connect/clients/ClientDelegate.java

@ -1,79 +0,0 @@
package org.transdroid.connect.clients;
import org.transdroid.connect.model.Torrent;
import java.io.InputStream;
import io.reactivex.Flowable;
/**
* Wraps an actual client implementation by calling through the appropriate method only if it is supported. This allows the final
* {@link ClientSpec} API to expose all methods without forcing the individual implementations to implement unsupported featured with a no-op.
*/
final class ClientDelegate implements ClientSpec {
private final Client client;
private final Object actual;
ClientDelegate(Client client, Object actual) {
this.client = client;
this.actual = actual;
}
@Override
public Flowable<Torrent> torrents() {
if (client.supports(Feature.LISTING))
return ((Feature.Listing) actual).torrents();
throw new UnsupportedFeatureException(client, Feature.LISTING);
}
@Override
public Flowable<String> clientVersion() {
if (client.supports(Feature.VERSION))
return ((Feature.Version) actual).clientVersion();
throw new UnsupportedFeatureException(client, Feature.VERSION);
}
@Override
public Flowable<Torrent> start(Torrent torrent) {
if (client.supports(Feature.STARTING_STOPPING))
return ((Feature.StartingStopping) actual).start(torrent);
throw new UnsupportedFeatureException(client, Feature.STARTING_STOPPING);
}
@Override
public Flowable<Torrent> stop(Torrent torrent) {
if (client.supports(Feature.STARTING_STOPPING))
return ((Feature.StartingStopping) actual).stop(torrent);
throw new UnsupportedFeatureException(client, Feature.STARTING_STOPPING);
}
@Override
public Flowable<Torrent> forceStart(Torrent torrent) {
if (client.supports(Feature.FORCE_STARTING))
return ((Feature.ForceStarting) actual).forceStart(torrent);
throw new UnsupportedFeatureException(client, Feature.FORCE_STARTING);
}
@Override
public Flowable<Void> addByFile(InputStream file) {
if (client.supports(Feature.ADD_BY_FILE))
return ((Feature.AddByFile) actual).addByFile(file);
throw new UnsupportedFeatureException(client, Feature.ADD_BY_FILE);
}
@Override
public Flowable<Void> addByUrl(String url) {
if (client.supports(Feature.ADD_BY_URL))
return ((Feature.AddByUrl) actual).addByUrl(url);
throw new UnsupportedFeatureException(client, Feature.ADD_BY_URL);
}
@Override
public Flowable<Void> addByMagnet(String magnet) {
if (client.supports(Feature.ADD_BY_MAGNET))
return ((Feature.AddByMagnet) actual).addByMagnet(magnet);
throw new UnsupportedFeatureException(client, Feature.ADD_BY_MAGNET);
}
}

63
connect/src/main/java/org/transdroid/connect/clients/ClientDelegate.kt

@ -0,0 +1,63 @@
package org.transdroid.connect.clients
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Single
import org.transdroid.connect.model.Torrent
import java.io.InputStream
/**
* Wraps an actual client implementation by calling through the appropriate method only if it is supported. This allows the final
* [ClientSpec] API to expose all methods without forcing the individual implementations to implement unsupported featured with a no-op.
*/
internal class ClientDelegate(private val client: Client, private val actual: Any) : ClientSpec {
override fun torrents(): Flowable<Torrent> {
if (client.supports(Feature.LISTING))
return (actual as Feature.Listing).torrents()
throw UnsupportedFeatureException(client, Feature.LISTING)
}
override fun clientVersion(): Single<String> {
if (client.supports(Feature.VERSION))
return (actual as Feature.Version).clientVersion()
throw UnsupportedFeatureException(client, Feature.VERSION)
}
override fun start(torrent: Torrent): Single<Torrent> {
if (client.supports(Feature.STARTING_STOPPING))
return (actual as Feature.StartingStopping).start(torrent)
throw UnsupportedFeatureException(client, Feature.STARTING_STOPPING)
}
override fun stop(torrent: Torrent): Single<Torrent> {
if (client.supports(Feature.STARTING_STOPPING))
return (actual as Feature.StartingStopping).stop(torrent)
throw UnsupportedFeatureException(client, Feature.STARTING_STOPPING)
}
override fun forceStart(torrent: Torrent): Single<Torrent> {
if (client.supports(Feature.FORCE_STARTING))
return (actual as Feature.ForceStarting).forceStart(torrent)
throw UnsupportedFeatureException(client, Feature.FORCE_STARTING)
}
override fun addByFile(file: InputStream): Completable {
if (client.supports(Feature.ADD_BY_FILE))
return (actual as Feature.AddByFile).addByFile(file)
throw UnsupportedFeatureException(client, Feature.ADD_BY_FILE)
}
override fun addByUrl(url: String): Completable {
if (client.supports(Feature.ADD_BY_URL))
return (actual as Feature.AddByUrl).addByUrl(url)
throw UnsupportedFeatureException(client, Feature.ADD_BY_URL)
}
override fun addByMagnet(magnet: String): Completable {
if (client.supports(Feature.ADD_BY_MAGNET))
return (actual as Feature.AddByMagnet).addByMagnet(magnet)
throw UnsupportedFeatureException(client, Feature.ADD_BY_MAGNET)
}
}

13
connect/src/main/java/org/transdroid/connect/clients/ClientSpec.java

@ -1,13 +0,0 @@
package org.transdroid.connect.clients;
public interface ClientSpec extends
Feature.Version,
Feature.Listing,
Feature.StartingStopping,
Feature.ResumingPausing,
Feature.ForceStarting,
Feature.AddByFile,
Feature.AddByUrl,
Feature.AddByMagnet {
}

11
connect/src/main/java/org/transdroid/connect/clients/ClientSpec.kt

@ -0,0 +1,11 @@
package org.transdroid.connect.clients
interface ClientSpec :
Feature.Version,
Feature.Listing,
Feature.StartingStopping,
Feature.ResumingPausing,
Feature.ForceStarting,
Feature.AddByFile,
Feature.AddByUrl,
Feature.AddByMagnet

82
connect/src/main/java/org/transdroid/connect/clients/Feature.java

@ -1,82 +0,0 @@
package org.transdroid.connect.clients;
import org.transdroid.connect.model.Torrent;
import java.io.InputStream;
import io.reactivex.Flowable;
/**
* Available feature enum which can be implemented by clients. Use {@link Client#supports(Feature)} to see if a certain {@link Client} support a
* {@link Feature}.
*/
public enum Feature {
VERSION(Version.class),
LISTING(Listing.class),
STARTING_STOPPING(StartingStopping.class),
RESUMING_PAUSING(ResumingPausing.class),
FORCE_STARTING(ForceStarting.class),
ADD_BY_FILE(AddByFile.class),
ADD_BY_URL(AddByUrl.class),
ADD_BY_MAGNET(AddByMagnet.class);
private final Class<?> type;
Feature(Class<?> type) {
this.type = type;
}
public Class<?> type() {
return type;
}
public interface Version {
Flowable<String> clientVersion();
}
public interface Listing {
Flowable<Torrent> torrents();
}
public interface StartingStopping {
Flowable<Torrent> start(Torrent torrent);
Flowable<Torrent> stop(Torrent torrent);
}
public interface ResumingPausing {
}
public interface ForceStarting {
Flowable<Torrent> forceStart(Torrent torrent);
}
public interface AddByFile {
Flowable<Void> addByFile(InputStream file);
}
public interface AddByUrl {
Flowable<Void> addByUrl(String url);
}
public interface AddByMagnet {
Flowable<Void> addByMagnet(String magnet);
}
}

70
connect/src/main/java/org/transdroid/connect/clients/Feature.kt

@ -0,0 +1,70 @@
package org.transdroid.connect.clients
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Single
import org.transdroid.connect.model.Torrent
import java.io.InputStream
import kotlin.reflect.KClass
/**
* Available feature enum which can be implemented by clients. Use [Client.supports] to see if a certain [Client] support a [Feature].
*/
enum class Feature constructor(val type: KClass<*>) {
VERSION(Version::class),
LISTING(Listing::class),
STARTING_STOPPING(StartingStopping::class),
RESUMING_PAUSING(ResumingPausing::class),
FORCE_STARTING(ForceStarting::class),
ADD_BY_FILE(AddByFile::class),
ADD_BY_URL(AddByUrl::class),
ADD_BY_MAGNET(AddByMagnet::class);
interface Version {
fun clientVersion(): Single<String>
}
interface Listing {
fun torrents(): Flowable<Torrent>
}
interface StartingStopping {
fun start(torrent: Torrent): Single<Torrent>
fun stop(torrent: Torrent): Single<Torrent>
}
interface ResumingPausing
interface ForceStarting {
fun forceStart(torrent: Torrent): Single<Torrent>
}
interface AddByFile {
fun addByFile(file: InputStream): Completable
}
interface AddByUrl {
fun addByUrl(url: String): Completable
}
interface AddByMagnet {
fun addByMagnet(magnet: String): Completable
}
}

22
connect/src/main/java/org/transdroid/connect/clients/UnsupportedFeatureException.java

@ -1,22 +0,0 @@
package org.transdroid.connect.clients;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
/**
* Thrown when trying to call into a client method for a feature which the client does not support.
*/
public class UnsupportedFeatureException extends NotImplementedException {
private final Client client;
private final Feature feature;
UnsupportedFeatureException(Client client, Feature feature) {
this.client = client;
this.feature = feature;
}
public String getMessage() {
return client.name() + " does not support " + feature.name();
}
}

13
connect/src/main/java/org/transdroid/connect/clients/UnsupportedFeatureException.kt

@ -0,0 +1,13 @@
package org.transdroid.connect.clients
/**
* Thrown when trying to call into a client method for a feature which the client does not support.
*/
class UnsupportedFeatureException internal constructor(
private val client: Client,
private val feature: Feature) : RuntimeException() {
override val message: String?
get() = client.name + " does not support " + feature.name
}

239
connect/src/main/java/org/transdroid/connect/clients/rtorrent/Rtorrent.java

@ -1,239 +0,0 @@
package org.transdroid.connect.clients.rtorrent;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import org.reactivestreams.Publisher;
import org.transdroid.connect.Configuration;
import org.transdroid.connect.clients.Feature;
import org.transdroid.connect.model.Torrent;
import org.transdroid.connect.model.TorrentStatus;
import org.transdroid.connect.util.OkHttpBuilder;
import org.transdroid.connect.util.RxUtil;
import java.io.InputStream;
import java.util.Date;
import io.reactivex.Flowable;
import io.reactivex.FlowableTransformer;
import io.reactivex.functions.Function;
import nl.nl2312.xmlrpc.Nothing;
import nl.nl2312.xmlrpc.XmlRpcConverterFactory;
import retrofit2.Retrofit;
public final class Rtorrent implements
Feature.Version,
Feature.Listing,
Feature.StartingStopping,
Feature.ResumingPausing,
Feature.AddByFile,
Feature.AddByUrl,
Feature.AddByMagnet {
private final Configuration configuration;
private final Service service;
public Rtorrent(Configuration configuration) {
this.configuration = configuration;
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(configuration.baseUrl())
.client(new OkHttpBuilder(configuration).build())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(XmlRpcConverterFactory.create())
.build();
this.service = retrofit.create(Service.class);
}
@Override
public Flowable<String> clientVersion() {
return service.clientVersion(configuration.endpoint(), Nothing.NOTHING)
.cache(); // Cached, as it is often used but 'never' changes
}
@Override
public Flowable<Torrent> torrents() {
return service.torrents(
configuration.endpoint(),
"",
"main",
"d.hash=",
"d.name=",
"d.state=",
"d.down.rate=",
"d.up.rate=",
"d.peers_connected=",
"d.peers_not_connected=",
"d.bytes_done=",
"d.up.total=",
"d.size_bytes=",
"d.left_bytes=",
"d.creation_date=",
"d.complete=",
"d.is_active=",
"d.is_hash_checking=",
"d.base_path=",
"d.base_filename=",
"d.message=",
"d.custom=addtime",
"d.custom=seedingtime",
"d.custom1=",
"d.peers_complete=",
"d.peers_accounted=")
.compose(RxUtil.<TorrentSpec>asList())
.map(new Function<TorrentSpec, Torrent>() {
@Override
public Torrent apply(TorrentSpec torrentSpec) throws Exception {
return new Torrent(
torrentSpec.hash.hashCode(),
torrentSpec.hash,
torrentSpec.name,
torrentStatus(torrentSpec.state, torrentSpec.isComplete, torrentSpec.isActive, torrentSpec.isHashChecking),
torrentSpec.basePath.substring(0, torrentSpec.basePath.indexOf(torrentSpec.baseFilename)),
(int) torrentSpec.downloadRate,
(int) torrentSpec.uploadRate,
(int) torrentSpec.seedersConnected,
(int) (torrentSpec.peersConnected + torrentSpec.peersNotConnected),
(int) torrentSpec.leechersConnected,
(int) (torrentSpec.peersConnected + torrentSpec.peersNotConnected),
torrentSpec.downloadRate > 0 ? (torrentSpec.bytesleft / torrentSpec.downloadRate) : Torrent.UNKNOWN,
torrentSpec.bytesDone,
torrentSpec.bytesUploaded,
torrentSpec.bytesTotal,
torrentSpec.bytesDone / torrentSpec.bytesTotal,
0F,
torrentSpec.label,
torrentTimeAdded(torrentSpec.timeAdded, torrentSpec.timeCreated),
torrentTimeFinished(torrentSpec.timeFinished),
torrentSpec.errorMessage
);
}
});
}
@Override
public Flowable<Torrent> start(final Torrent torrent) {
return service.start(
configuration.endpoint(),
torrent.hash()).map(new Function<Void, Torrent>() {
@Override
public Torrent apply(Void result) throws Exception {
return torrent.mimicStart();
}
});
}
@Override
public Flowable<Torrent> stop(final Torrent torrent) {
return service.stop(
configuration.endpoint(),
torrent.hash()).map(new Function<Void, Torrent>() {
@Override
public Torrent apply(Void result) throws Exception {
return torrent.mimicStart();
}
});
}
@Override
public Flowable<Void> addByFile(InputStream file) {
// TODO
return null;
}
@Override
public Flowable<Void> addByUrl(final String url) {
return clientVersion().compose(clientVersionAsInt).flatMap(new Function<Integer, Publisher<Integer>>() {
@Override
public Publisher<Integer> apply(Integer integer) throws Exception {
if (integer > 904) {
return service.loadStart(
configuration.endpoint(),
"",
url);
} else {
return service.loadStart(
configuration.endpoint(),
url);
}
}
}).map(new Function<Integer, Void>() {
@Override
public Void apply(Integer integer) throws Exception {
return null;
}
});
}
@Override
public Flowable<Void> addByMagnet(final String magnet) {
return clientVersion().compose(clientVersionAsInt).flatMap(new Function<Integer, Publisher<Integer>>() {
@Override
public Publisher<Integer> apply(Integer integer) throws Exception {
if (integer > 904) {
return service.loadStart(
configuration.endpoint(),
"",
magnet);
} else {
return service.loadStart(
configuration.endpoint(),
magnet);
}
}
}).map(new Function<Integer, Void>() {
@Override
public Void apply(Integer integer) throws Exception {
return null;
}
});
}
private FlowableTransformer<String, Integer> clientVersionAsInt = new FlowableTransformer<String, Integer>() {
@Override
public Publisher<Integer> apply(Flowable<String> version) {
return version.map(new Function<String, Integer>() {
@Override
public Integer apply(String version) throws Exception {
if (version == null)
return 10000;
try {
String[] versionParts = version.split("\\.");
return (Integer.parseInt(versionParts[0]) * 10000) + (Integer.parseInt(versionParts[1]) * 100) + Integer.parseInt
(versionParts[2]);
} catch (NumberFormatException e) {
return 10000;
}
}
});
}
};
private TorrentStatus torrentStatus(long state, long complete, long active, long checking) {
if (state == 0) {
return TorrentStatus.QUEUED;
} else if (active == 1) {
if (complete == 1) {
return TorrentStatus.SEEDING;
} else {
return TorrentStatus.DOWNLOADING;
}
} else if (checking == 1) {
return TorrentStatus.CHECKING;
} else {
return TorrentStatus.PAUSED;
}
}
private Date torrentTimeAdded(String timeAdded, long timeCreated) {
if (timeAdded != null || timeAdded.trim().length() != 0) {
return new Date(Long.parseLong(timeAdded.trim()) * 1000L);
}
return new Date(timeCreated * 1000L);
}
private Date torrentTimeFinished(String timeFinished) {
if (timeFinished == null || timeFinished.trim().length() == 0)
return null;
return new Date(Long.parseLong(timeFinished.trim()) * 1000L);
}
}

182
connect/src/main/java/org/transdroid/connect/clients/rtorrent/Rtorrent.kt

@ -0,0 +1,182 @@
package org.transdroid.connect.clients.rtorrent
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Single
import nl.nl2312.xmlrpc.Nothing
import nl.nl2312.xmlrpc.XmlRpcConverterFactory
import org.transdroid.connect.Configuration
import org.transdroid.connect.clients.Feature
import org.transdroid.connect.model.Torrent
import org.transdroid.connect.model.TorrentStatus
import org.transdroid.connect.util.OkHttpBuilder
import org.transdroid.connect.util.flatten
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import java.util.*
class Rtorrent(private val configuration: Configuration) :
Feature.Version,
Feature.Listing,
Feature.StartingStopping,
Feature.ResumingPausing,
//Feature.AddByFile,
Feature.AddByUrl,
Feature.AddByMagnet {
private val service: Service = Retrofit.Builder()
.baseUrl(configuration.baseUrl)
.client(OkHttpBuilder.build(configuration))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(XmlRpcConverterFactory.builder()
.addArrayDeserializer(TorrentSpec::class.java) { arrayValues ->
TorrentSpec(
arrayValues.asString(0),
arrayValues.asString(1),
arrayValues.asLong(2),
arrayValues.asLong(3),
arrayValues.asLong(4),
arrayValues.asLong(5),
arrayValues.asLong(6),
arrayValues.asLong(7),
arrayValues.asLong(8),
arrayValues.asLong(9),
arrayValues.asLong(10),
arrayValues.asLong(11),
arrayValues.asLong(12),
arrayValues.asLong(13),
arrayValues.asLong(14),
arrayValues.asString(15),
arrayValues.asString(16),
arrayValues.asString(17),
arrayValues.asString(18),
arrayValues.asString(19),
arrayValues.asString(20),
arrayValues.asLong(21),
arrayValues.asLong(22)
)
}
.create())
.build().create(Service::class.java)
override fun clientVersion(): Single<String> {
return service.clientVersion(configuration.endpoint, Nothing.NOTHING)
.cache() // Cached, as it is often used but 'never' changes
}
private fun Single<String>.asVersionInt(): Single<Int> {
return this.map {
if (it == null) 10000 else {
val versionParts = it.split(".")
versionParts[0].toInt() * 10000 + versionParts[1].toInt() * 100 + versionParts[2].toInt()
}
}
}
override fun torrents(): Flowable<Torrent> {
return service.torrents(
configuration.endpoint,
"",
"main",
"d.hash=",
"d.name=",
"d.state=",
"d.down.rate=",
"d.up.rate=",
"d.peers_connected=",
"d.peers_not_connected=",
"d.bytes_done=",
"d.up.total=",
"d.size_bytes=",
"d.left_bytes=",
"d.creation_date=",
"d.complete=",
"d.is_active=",
"d.is_hash_checking=",
"d.base_path=",
"d.base_filename=",
"d.message=",
"d.custom=addtime",
"d.custom=seedingtime",
"d.custom1=",
"d.peers_complete=",
"d.peers_accounted=")
.flatten()
.map { (hash, name, state, downloadRate, uploadRate, peersConnected, peersNotConnected, bytesDone, bytesUploaded, bytesTotal, bytesleft, timeCreated, isComplete, isActive, isHashChecking, basePath, baseFilename, errorMessage, timeAdded, timeFinished, label, seedersConnected, leechersConnected) ->
Torrent(
hash.hashCode().toLong(), hash, name,
torrentStatus(state, isComplete, isActive, isHashChecking),
basePath?.substring(0, basePath.indexOf(baseFilename.orEmpty())),
downloadRate.toInt(),
uploadRate.toInt(),
seedersConnected.toInt(),
(peersConnected + peersNotConnected).toInt(),
leechersConnected.toInt(),
(peersConnected + peersNotConnected).toInt(),
if (downloadRate > 0) bytesleft / downloadRate else null,
bytesDone, bytesUploaded, bytesTotal,
(bytesDone / bytesTotal).toFloat(),
null,
label,
torrentTimeAdded(timeAdded, timeCreated),
torrentTimeFinished(timeFinished), errorMessage
)
}
}
override fun start(torrent: Torrent): Single<Torrent> {
return service.start(
configuration.endpoint,
torrent.uniqueId).toSingle { torrent.mimicStart() }
}
override fun stop(torrent: Torrent): Single<Torrent> {
return service.stop(
configuration.endpoint,
torrent.uniqueId).toSingle { torrent.mimicStop() }
}
override fun addByUrl(url: String): Completable {
return clientVersion().asVersionInt().flatMapCompletable { integer ->
if (integer > 904) {
service.loadStart(configuration.endpoint, "", url)
} else {
service.loadStart(configuration.endpoint, url)
}
}
}
override fun addByMagnet(magnet: String): Completable {
return clientVersion().asVersionInt().flatMapCompletable { integer ->
if (integer > 904) {
service.loadStart(configuration.endpoint, "", magnet)
} else {
service.loadStart(configuration.endpoint, magnet)
}
}
}
private fun torrentStatus(state: Long, complete: Long, active: Long, checking: Long): TorrentStatus {
if (state == 0L) {
return TorrentStatus.QUEUED
} else if (active == 1L) {
if (complete == 1L) {
return TorrentStatus.SEEDING
} else {
return TorrentStatus.DOWNLOADING
}
} else if (checking == 1L) {
return TorrentStatus.CHECKING
} else {
return TorrentStatus.PAUSED
}
}
private fun torrentTimeAdded(timeAdded: String?, timeCreated: Long): Date =
if (timeAdded.isNullOrBlank()) Date(timeCreated * 1000L) else Date(timeAdded!!.trim().toLong() * 1000L)
private fun torrentTimeFinished(timeFinished: String?): Date? =
if (timeFinished.isNullOrBlank()) null else Date(timeFinished!!.trim().toLong() * 1000L)
}

32
connect/src/main/java/org/transdroid/connect/clients/rtorrent/Service.java

@ -1,32 +0,0 @@
package org.transdroid.connect.clients.rtorrent;
import io.reactivex.Flowable;
import nl.nl2312.xmlrpc.Nothing;
import nl.nl2312.xmlrpc.XmlRpc;
import retrofit2.http.Body;
import retrofit2.http.POST;
import retrofit2.http.Path;
interface Service {
@XmlRpc("system.client_version")
@POST("{endpoint}")
Flowable<String> clientVersion(@Path("endpoint") String endpoint, @Body Nothing nothing);
@XmlRpc("d.multicall2")
@POST("{endpoint}")
Flowable<TorrentSpec[]> torrents(@Path("endpoint") String endpoint, @Body String... args);
@XmlRpc("d.start")
@POST("{endpoint}")
Flowable<Void> start(@Path("endpoint") String endpoint, @Body String hash);
@XmlRpc("d.stop")
@POST("{endpoint}")
Flowable<Void> stop(@Path("endpoint") String endpoint, @Body String hash);
@XmlRpc("load.start")
@POST("{endpoint}")
Flowable<Integer> loadStart(@Path("endpoint") String endpoint, @Body String... args);
}

34
connect/src/main/java/org/transdroid/connect/clients/rtorrent/Service.kt

@ -0,0 +1,34 @@
package org.transdroid.connect.clients.rtorrent
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Single
import nl.nl2312.xmlrpc.Nothing
import nl.nl2312.xmlrpc.XmlRpc
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Path
internal interface Service {
@XmlRpc("system.client_version")
@POST("{endpoint}")
fun clientVersion(@Path("endpoint") endpoint: String?, @Body nothing: Nothing): Single<String>
@XmlRpc("d.multicall2")
@POST("{endpoint}")
fun torrents(@Path("endpoint") endpoint: String?, @Body vararg args: String): Flowable<Array<TorrentSpec>>
@XmlRpc("d.start")
@POST("{endpoint}")
fun start(@Path("endpoint") endpoint: String?, @Body hash: String): Completable
@XmlRpc("d.stop")
@POST("{endpoint}")
fun stop(@Path("endpoint") endpoint: String?, @Body hash: String): Completable
@XmlRpc("load.start")
@POST("{endpoint}")
fun loadStart(@Path("endpoint") endpoint: String?, @Body vararg args: String): Completable
}

29
connect/src/main/java/org/transdroid/connect/clients/rtorrent/TorrentSpec.java

@ -1,29 +0,0 @@
package org.transdroid.connect.clients.rtorrent;
public final class TorrentSpec {
public String hash;
public String name;
public long state;
public long downloadRate;
public long uploadRate;
public long peersConnected;
public long peersNotConnected;
public long bytesDone;
public long bytesUploaded;
public long bytesTotal;
public long bytesleft;
public long timeCreated;
public long isComplete;
public long isActive;
public long isHashChecking;
public String basePath;
public String baseFilename;
public String errorMessage;
public String timeAdded;
public String timeFinished;
public String label;
public long seedersConnected;
public long leechersConnected;
}

26
connect/src/main/java/org/transdroid/connect/clients/rtorrent/TorrentSpec.kt

@ -0,0 +1,26 @@
package org.transdroid.connect.clients.rtorrent
data class TorrentSpec(
val hash: String,
var name: String,
var state: Long,
var downloadRate: Long,
var uploadRate: Long,
var peersConnected: Long,
var peersNotConnected: Long,
var bytesDone: Long,
var bytesUploaded: Long,
var bytesTotal: Long,
var bytesleft: Long,
var timeCreated: Long,
var isComplete: Long,
var isActive: Long,
var isHashChecking: Long,
var basePath: String?,
var baseFilename: String?,
var errorMessage: String?,
var timeAdded: String?,
var timeFinished: String?,
var label: String?,
var seedersConnected: Long,
var leechersConnected: Long)

5
connect/src/main/java/org/transdroid/connect/clients/transmission/Transmission.java

@ -1,5 +0,0 @@
package org.transdroid.connect.clients.transmission;
public final class Transmission {
}

3
connect/src/main/java/org/transdroid/connect/clients/transmission/Transmission.kt

@ -0,0 +1,3 @@
package org.transdroid.connect.clients.transmission
class Transmission

308
connect/src/main/java/org/transdroid/connect/model/Torrent.java

@ -1,308 +0,0 @@
package org.transdroid.connect.model;
import java.util.Calendar;
import java.util.Date;
public final class Torrent {
public static final long UNKNOWN = -1L;
private final long id;
private final String hash;
private final String name;
private final TorrentStatus statusCode;
private final String locationDir;
private final int rateDownload;
private final int rateUpload;
private final int seedersConnected;
private final int seedersKnown;
private final int leechersConnected;
private final int leechersKnown;
private final long eta;
private final long downloadedEver;
private final long uploadedEver;
private final long totalSize;
private final float partDone;
private final float available;
private final String label;
private final Date dateAdded;
private final Date dateDone;
private final String error;
public Torrent(long id,
String hash,
String name,
TorrentStatus statusCode,
String locationDir,
int rateDownload,
int rateUpload,
int seedersConnected,
int seedersKnown,
int leechersConnected,
int leechersKnown,
long eta,
long downloadedEver,
long uploadedEver,
long totalSize,
float partDone,
float available,
String label,
Date dateAdded,
Date realDateDone,
String error) {
this.id = id;
this.hash = hash;
this.name = name;
this.statusCode = statusCode;
this.locationDir = locationDir;
this.rateDownload = rateDownload;
this.rateUpload = rateUpload;
this.seedersConnected = seedersConnected;
this.seedersKnown = seedersKnown;
this.leechersConnected = leechersConnected;
this.leechersKnown = leechersKnown;
this.eta = eta;
this.downloadedEver = downloadedEver;
this.uploadedEver = uploadedEver;
this.totalSize = totalSize;
this.partDone = partDone;
this.available = available;
this.label = label;
this.dateAdded = dateAdded;
if (realDateDone != null) {
this.dateDone = realDateDone;
} else {
if (this.partDone == 1) {
// Finished but no finished date: set so move to bottom of list
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(1900, Calendar.DECEMBER, 31);
this.dateDone = cal.getTime();
} else if (eta == -1 || eta == -2) {
// Unknown eta: move to the top of the list
this.dateDone = new Date(Long.MAX_VALUE);
} else {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.SECOND, (int) eta);
this.dateDone = cal.getTime();
}
}
this.error = error;
}
public long id() {
return id;
}
public String hash() {
return hash;
}
public String name() {
return name;
}
public TorrentStatus statusCode() {
return statusCode;
}
public String locationDir() {
return locationDir;
}
public int rateDownload() {
return rateDownload;
}
public int rateUpload() {
return rateUpload;
}
public int seedersConnected() {
return seedersConnected;
}
public int seedersKnown() {
return seedersKnown;
}
public int leechersConnected() {
return leechersConnected;
}
public int leechersKnown() {
return leechersKnown;
}
public long eta() {
return eta;
}
public long downloadedEver() {
return downloadedEver;
}
public long uploadedEver() {
return uploadedEver;
}
public long totalSize() {
return totalSize;
}
public float partDone() {
return partDone;
}
public float available() {
return available;
}
public String label() {
return label;
}
public Date dateAdded() {
return dateAdded;
}
public Date dateDone() {
return dateDone;
}
public String error() {
return error;
}
/**
* Returns the unique torrent-specific id, which is the torrent's hash or (if not available) the local index number
* @return The torrent's (session-transient) unique id
*/
public String uniqueId() {
if (this.hash == null) {
return Long.toString(this.id);
} else {
return this.hash;
}
}
/**
* Gives the upload/download seed ratio.
* @return The ratio in range [0,r]
*/
public double ratio() {
return ((double) uploadedEver) / ((double) downloadedEver);
}
/**
* Gives the percentage of the download that is completed
* @return The downloaded percentage in range [0,1]
*/
public float downloadedPercentage() {
return partDone;
}
/**
* Returns whether this torrents is actively downloading or not.
* @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are not considered actively downloading
* @return True if this torrent is to be treated as being in a downloading state, that is, it is trying to finish a download
*/
public boolean isDownloading(boolean dormantAsInactive) {
return statusCode == TorrentStatus.DOWNLOADING && (!dormantAsInactive || rateDownload > 0);
}
/**
* Returns whether this torrents is actively seeding or not.
* @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are not considered actively seeding
* @return True if this torrent is to be treated as being in a seeding state, that is, it is sending data to leechers
*/
public boolean isSeeding(boolean dormantAsInactive) {
return statusCode == TorrentStatus.SEEDING && (!dormantAsInactive || rateUpload > 0);
}
/**
* Indicates if the torrent can be paused at this moment
* @return If it can be paused
*/
public boolean canPause() {
// Can pause when it is downloading or seeding
return statusCode == TorrentStatus.DOWNLOADING || statusCode == TorrentStatus.SEEDING;
}
/**
* Indicates whether the torrent can be resumed
* @return If it can be resumed
*/
public boolean canResume() {
// Can resume when it is paused
return statusCode == TorrentStatus.PAUSED;
}
/**
* Indicates if the torrent can be started at this moment
* @return If it can be started
*/
public boolean canStart() {
// Can start when it is queued
return statusCode == TorrentStatus.QUEUED;
}
/**
* Indicates whether the torrent can be stopped
* @return If it can be stopped
*/
public boolean canStop() {
// Can stop when it is downloading or seeding or paused
return statusCode == TorrentStatus.DOWNLOADING || statusCode == TorrentStatus.SEEDING
|| statusCode == TorrentStatus.PAUSED;
}
public Torrent mimicResume() {
return mimicStatus(downloadedPercentage() >= 1 ? TorrentStatus.SEEDING : TorrentStatus.DOWNLOADING);
}
public Torrent mimicPause() {
return mimicStatus(TorrentStatus.PAUSED);
}
public Torrent mimicStart() {
return mimicStatus(downloadedPercentage() >= 1 ? TorrentStatus.SEEDING : TorrentStatus.DOWNLOADING);
}
public Torrent mimicStop() {
return mimicStatus(TorrentStatus.QUEUED);
}
public Torrent mimicNewLabel(String newLabel) {
return new Torrent(id, hash, name, statusCode, locationDir, rateDownload, rateUpload, seedersConnected, seedersKnown, leechersConnected,
leechersKnown, eta, downloadedEver, uploadedEver, totalSize, partDone, available, newLabel, dateAdded, dateDone, error);
}
public Torrent mimicChecking() {
return mimicStatus(TorrentStatus.CHECKING);
}
public Torrent mimicNewLocation(String newLocation) {
return new Torrent(id, hash, name, statusCode, newLocation, rateDownload, rateUpload, seedersConnected, seedersKnown, leechersConnected,
leechersKnown, eta, downloadedEver, uploadedEver, totalSize, partDone, available, label, dateAdded, dateDone, error);
}
@Override
public String toString() {
// (HASH_OR_ID) NAME
return "(" + uniqueId() + ") " + name;
}
private Torrent mimicStatus(TorrentStatus newStatus) {
return new Torrent(id, hash, name, newStatus, locationDir, rateDownload, rateUpload, seedersConnected, seedersKnown, leechersConnected,
leechersKnown, eta, downloadedEver, uploadedEver, totalSize, partDone, available, label, dateAdded, dateDone, error);
}
}

122
connect/src/main/java/org/transdroid/connect/model/Torrent.kt

@ -0,0 +1,122 @@
package org.transdroid.connect.model
import java.util.*
data class Torrent(
val id: Long,
val hash: String?,
val name: String,
val statusCode: TorrentStatus,
val locationDir: String?,
val rateDownload: Int,
val rateUpload: Int,
val seedersConnected: Int,
val seedersKnown: Int,
val leechersConnected: Int,
val leechersKnown: Int,
val eta: Long?,
val downloadedEver: Long,
val uploadedEver: Long,
val totalSize: Long,
val partDone: Float,
val available: Float?,
val label: String?,
val dateAdded: Date,
val realDateDone: Date?,
val error: String?) {
val dateDone: Date
init {
if (realDateDone != null) {
this.dateDone = realDateDone
} else {
if (this.partDone == 1f) {
// Finished but no finished date: set so move to bottom of list
this.dateDone = Calendar.getInstance().apply {
clear()
set(1900, Calendar.DECEMBER, 31)
}.time
} else if (eta == null || eta == -1L || eta == -2L) {
// Unknown eta: move to the top of the list
this.dateDone = Date(java.lang.Long.MAX_VALUE)
} else {
this.dateDone = Calendar.getInstance().apply {
add(Calendar.SECOND, eta.toInt())
}.time
}
}
}
/**
* The unique torrent-specific id, which is the torrent's hash or (if not available) the local index number
*/
val uniqueId = this.hash ?: this.id.toString()
/**
* The upload/download seed ratio in range [0,r]
*/
val ratio = uploadedEver.toDouble() / downloadedEver.toDouble()
/**
* Whether this torrents is actively downloading or not.
* @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are not considered actively downloading
* @return True if this torrent is to be treated as being in a downloading state, that is, it is trying to finish a download
*/
fun isDownloading(dormantAsInactive: Boolean) = statusCode === TorrentStatus.DOWNLOADING && (!dormantAsInactive || rateDownload > 0)
/**
* Whether this torrents is actively seeding or not.
* @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are not considered actively seeding
* @return True if this torrent is to be treated as being in a seeding state, that is, it is sending data to leechers
*/
fun isSeeding(dormantAsInactive: Boolean) = statusCode === TorrentStatus.SEEDING && (!dormantAsInactive || rateUpload > 0)
/**
* If the torrent can be paused at this moment
*/
val canPause = statusCode === TorrentStatus.DOWNLOADING || statusCode === TorrentStatus.SEEDING
/**
* If the torrent can be resumed at this moment
*/
val canResume = statusCode === TorrentStatus.PAUSED
/**
* If the torrent can be started at this moment
*/
val canStart = statusCode === TorrentStatus.QUEUED
/**
* If the torrent can be stopped at this moment
*/
val canStop: Boolean = statusCode === TorrentStatus.DOWNLOADING || statusCode === TorrentStatus.SEEDING || statusCode === TorrentStatus.PAUSED
fun mimicResume(): Torrent = mimicStatus(if (partDone >= 1) TorrentStatus.SEEDING else TorrentStatus.DOWNLOADING)
fun mimicPause(): Torrent = mimicStatus(TorrentStatus.PAUSED)
fun mimicStart(): Torrent = mimicStatus(if (partDone >= 1) TorrentStatus.SEEDING else TorrentStatus.DOWNLOADING)
fun mimicStop(): Torrent = mimicStatus(TorrentStatus.QUEUED)
fun mimicNewLabel(newLabel: String): Torrent = Torrent(id, hash, name, statusCode, locationDir, rateDownload, rateUpload, seedersConnected,
seedersKnown, leechersConnected, leechersKnown, eta, downloadedEver, uploadedEver, totalSize, partDone, available, newLabel, dateAdded,
dateDone, error)
fun mimicChecking(): Torrent {
return mimicStatus(TorrentStatus.CHECKING)
}
fun mimicNewLocation(newLocation: String): Torrent {
return Torrent(id, hash, name, statusCode, newLocation, rateDownload, rateUpload, seedersConnected, seedersKnown, leechersConnected,
leechersKnown, eta, downloadedEver, uploadedEver, totalSize, partDone, available, label, dateAdded, dateDone, error)
}
private fun mimicStatus(newStatus: TorrentStatus): Torrent = Torrent(id, hash, name, newStatus, locationDir, rateDownload, rateUpload,
seedersConnected, seedersKnown, leechersConnected, leechersKnown, eta, downloadedEver, uploadedEver, totalSize, partDone, available,
label, dateAdded, dateDone, error)
override fun toString(): String = "($uniqueId) $name"
}

14
connect/src/main/java/org/transdroid/connect/model/TorrentStatus.java

@ -1,14 +0,0 @@
package org.transdroid.connect.model;
public enum TorrentStatus {
WAITING,
CHECKING,
DOWNLOADING,
SEEDING,
PAUSED,
QUEUED,
ERROR,
UNKNOWN;
}

12
connect/src/main/java/org/transdroid/connect/model/TorrentStatus.kt

@ -0,0 +1,12 @@
package org.transdroid.connect.model
enum class TorrentStatus {
WAITING,
CHECKING,
DOWNLOADING,
SEEDING,
PAUSED,
QUEUED,
ERROR,
UNKNOWN
}

50
connect/src/main/java/org/transdroid/connect/util/OkHttpBuilder.java

@ -1,50 +0,0 @@
package org.transdroid.connect.util;
import com.burgstaller.okhttp.AuthenticationCacheInterceptor;
import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
import com.burgstaller.okhttp.DispatchingAuthenticator;
import com.burgstaller.okhttp.basic.BasicAuthenticator;
import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.DigestAuthenticator;
import org.transdroid.connect.Configuration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
public final class OkHttpBuilder {
private final Configuration configuration;
public OkHttpBuilder(Configuration configuration) {
this.configuration = configuration;
}
public OkHttpClient build() {
OkHttpClient.Builder okhttp = new OkHttpClient.Builder();
if (configuration.loggingEnabled()) {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
okhttp.addInterceptor(loggingInterceptor);
}
if (configuration.credentials() != null) {
BasicAuthenticator basicAuthenticator = new BasicAuthenticator(configuration.credentials());
DigestAuthenticator digestAuthenticator = new DigestAuthenticator(configuration.credentials());
DispatchingAuthenticator authenticator = new DispatchingAuthenticator.Builder()
.with("digest", digestAuthenticator)
.with("basic", basicAuthenticator)
.build();
Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
okhttp.authenticator(new CachingAuthenticatorDecorator(authenticator, authCache));
okhttp.addInterceptor(new AuthenticationCacheInterceptor(authCache));
}
return okhttp.build();
}
}

37
connect/src/main/java/org/transdroid/connect/util/OkHttpBuilder.kt

@ -0,0 +1,37 @@
package org.transdroid.connect.util
import com.burgstaller.okhttp.AuthenticationCacheInterceptor
import com.burgstaller.okhttp.CachingAuthenticatorDecorator
import com.burgstaller.okhttp.DispatchingAuthenticator
import com.burgstaller.okhttp.basic.BasicAuthenticator
import com.burgstaller.okhttp.digest.CachingAuthenticator
import com.burgstaller.okhttp.digest.DigestAuthenticator
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.transdroid.connect.Configuration
import java.util.concurrent.ConcurrentHashMap
object OkHttpBuilder {
fun build(configuration: Configuration): OkHttpClient {
val okhttp = OkHttpClient.Builder()
if (configuration.loggingEnabled) {
okhttp.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
}
if (configuration.credentials != null) {
val authenticator = DispatchingAuthenticator.Builder()
.with("digest", DigestAuthenticator(configuration.credentials))
.with("basic", BasicAuthenticator(configuration.credentials))
.build()
val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
okhttp.authenticator(CachingAuthenticatorDecorator(authenticator, authCache))
okhttp.addInterceptor(AuthenticationCacheInterceptor(authCache))
}
return okhttp.build()
}
}

29
connect/src/main/java/org/transdroid/connect/util/RxUtil.java

@ -1,29 +0,0 @@
package org.transdroid.connect.util;
import org.reactivestreams.Publisher;
import java.util.Arrays;
import io.reactivex.Flowable;
import io.reactivex.FlowableTransformer;
import io.reactivex.functions.Function;
public final class RxUtil {
private RxUtil() {}
public static <T> FlowableTransformer<T[], T> asList() {
return new FlowableTransformer<T[], T>() {
@Override
public Publisher<T> apply(Flowable<T[]> upstream) {
return upstream.flatMapIterable(new Function<T[], Iterable<T>>() {
@Override
public Iterable<T> apply(T[] ts) throws Exception {
return Arrays.asList(ts);
}
});
}
};
}
}

5
connect/src/main/java/org/transdroid/connect/util/RxUtil.kt

@ -0,0 +1,5 @@
package org.transdroid.connect.util
import io.reactivex.Flowable
fun <T : Any> Flowable<Array<T>>.flatten(): Flowable<T> = this.flatMapIterable { items -> items.toList() }

11
connect/src/main/java/org/transdroid/connect/util/StringUtil.java

@ -1,11 +0,0 @@
package org.transdroid.connect.util;
public final class StringUtil {
private StringUtil() {}
public static boolean isEmpty(String string) {
return string == null || string.equals("");
}
}

17
connect/src/test/java/org/transdroid/connect/clients/rtorrent/MockTorrent.kt

@ -0,0 +1,17 @@
package org.transdroid.connect.clients.rtorrent
import org.transdroid.connect.model.Torrent
import org.transdroid.connect.model.TorrentStatus
import java.util.*
object MockTorrent {
val torrentUrl = "http://releases.ubuntu.com/17.04/ubuntu-17.04-desktop-amd64.iso.torrent"
val magnetUrl = "http://torrent.ubuntu.com:6969/file?info_hash=%04%03%FBG%28%BDx%8F%BC%B6%7E%87%D6%FE%B2A%EF8%C7Z"
val downloading = Torrent(0, "59066769B9AD42DA2E508611C33D7C4480B3857B", "ubuntu-17.04-desktop-amd64.iso", TorrentStatus.DOWNLOADING,
"/downloads/", 1000000, 200000, 20, 20, 2, 50, null, 804519936, 160903987, 1609039872, 0.5F, 0.8F, "distros", Date(1492681983), null, null)
val seeding = Torrent(0, "59066769B9AD42DA2E508611C33D7C4480B3857B", "ubuntu-17.04-desktop-amd64.iso", TorrentStatus.SEEDING,
"/downloads/", 0, 1000000, 0, 24, 10, 50, null, 1609039872, 2609039872, 1609039872, 1F, 1F, "distros", Date(1492681983), Date(1492781983), null)
}

90
connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentLiveTest.kt

@ -0,0 +1,90 @@
package org.transdroid.connect.clients.rtorrent
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.transdroid.connect.Configuration
import org.transdroid.connect.clients.Client
import org.transdroid.connect.clients.ClientSpec
import org.transdroid.connect.clients.Feature
import org.transdroid.connect.clients.UnsupportedFeatureException
import org.transdroid.connect.model.Torrent
class RtorrentLiveTest {
private lateinit var rtorrent: ClientSpec
@Before
fun setUp() {
rtorrent = Configuration(Client.RTORRENT,
"http://localhost:8008/",
"RPC2",
loggingEnabled = true)
.createClient()
}
@Test
fun features() {
assertThat(Client.RTORRENT.supports(Feature.VERSION)).isTrue()
assertThat(Client.RTORRENT.supports(Feature.LISTING)).isTrue()
assertThat(Client.RTORRENT.supports(Feature.STARTING_STOPPING)).isTrue()
assertThat(Client.RTORRENT.supports(Feature.RESUMING_PAUSING)).isTrue()
assertThat(Client.RTORRENT.supports(Feature.FORCE_STARTING)).isFalse()
assertThat(Client.RTORRENT.supports(Feature.ADD_BY_FILE)).isTrue()
assertThat(Client.RTORRENT.supports(Feature.ADD_BY_URL)).isTrue()
assertThat(Client.RTORRENT.supports(Feature.ADD_BY_MAGNET)).isTrue()
}
@Test
fun clientVersion() {
rtorrent.clientVersion()
.test()
.assertValue("0.9.6")
}
@Test
fun torrents() {
rtorrent.torrents()
.toList()
.test()
.assertValue { torrents -> torrents.size > 0 }
}
@Test
fun addByUrl() {
rtorrent.addByUrl(MockTorrent.torrentUrl)
.test()
.assertNoErrors()
}
@Test
fun addByMagnet() {
rtorrent.addByMagnet(MockTorrent.magnetUrl)
.test()
.assertNoErrors()
}
@Test
fun start() {
rtorrent.start(firstLiveTorrent())
.test()
.assertValue({ it.canStop })
}
@Test
fun stop() {
rtorrent.stop(firstLiveTorrent())
.test()
.assertValue({ it.canStart })
}
@Test(expected = UnsupportedFeatureException::class)
fun forceStart() {
rtorrent.forceStart(firstLiveTorrent())
.test()
.assertValue({ it.canStop })
}
private fun firstLiveTorrent(): Torrent = rtorrent.torrents().blockingFirst()
}

90
connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentMockTest.kt

@ -0,0 +1,90 @@
package org.transdroid.connect.clients.rtorrent
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.Before
import org.junit.Test
import org.transdroid.connect.Configuration
import org.transdroid.connect.clients.Client
class RtorrentMockTest {
private lateinit var server: MockWebServer
private lateinit var rtorrent: Rtorrent
@Before
fun setUp() {
server = MockWebServer()
rtorrent = Rtorrent(Configuration(Client.RTORRENT, server.url("/").toString(), "/RPC2"))
}
@Test
fun clientVersion() {
server.enqueue(mock("<param><value><string>0.9.6</string></value></param>"))
rtorrent.clientVersion()
.test()
.assertValue("0.9.6")
server.takeRequest()
}
@Test
fun torrents() {
server.enqueue(mock("<param><value><array><data><value><array><data><value><string>59066769B9AD42DA2E508611C33D7C4480B3857B</string></value><value><string>ubuntu-17.04-desktop-amd64.iso</string></value><value><i8>0</i8></value><value><i8>0</i8></value><value><i8>0</i8></value><value><i8>0</i8></value><value><i8>0</i8></value><value><i8>0</i8></value><value><i8>0</i8></value><value><i8>1609039872</i8></value><value><i8>1609039872</i8></value><value><i8>1492077159</i8></value><value><i8>0</i8></value><value><i8>0</i8></value><value><i8>0</i8></value><value><string></string></value><value><string></string></value><value><string></string></value><value><string></string></value><value><string></string></value><value><string></string></value><value><i8>0</i8></value><value><i8>0</i8></value></data></array></value></data></array></value></param>"))
rtorrent.torrents()
.test()
.assertValue { it.hash == "59066769B9AD42DA2E508611C33D7C4480B3857B" }
server.takeRequest()
}
@Test
fun addByUrl() {
server.enqueue(mock("<param><value><string>0.9.6</string></value></param>"))
server.enqueue(mock("<param><value><i4>0</i4></value></param>"))
rtorrent.addByUrl("http://releases.ubuntu.com/17.04/ubuntu-17.04-desktop-amd64.iso.torrent")
.test()
.assertNoErrors()
server.takeRequest()
server.takeRequest()
}
@Test
fun addByMagnet() {
server.enqueue(mock("<param><value><string>0.9.6</string></value></param>"))
server.enqueue(mock("<param><value><i4>0</i4></value></param>"))
rtorrent.addByMagnet("http://torrent.ubuntu.com:6969/file?info_hash=%04%03%FBG%28%BDx%8F%BC%B6%7E%87%D6%FE%B2A%EF8%C7Z")
.test()
.assertNoErrors()
server.takeRequest()
server.takeRequest()
}
@Test
fun start() {
server.enqueue(mock("<param><value><i4>0</i4></value></param>"))
rtorrent.start(MockTorrent.downloading)
.test()
.assertValue { it.canStop }
server.takeRequest()
}
@Test
fun stop() {
server.enqueue(mock("<param><value><i4>0</i4></value></param>"))
rtorrent.stop(MockTorrent.seeding)
.test()
.assertValue { it.canStart }
server.takeRequest()
}
private fun mock(params: String): MockResponse? {
return MockResponse()
.addHeader("Content-Type", "application/xml; charset=UTF-8")
.setBody("<?xml version=\"1.0\"?>\n" +
"<methodResponse>\n" +
" <params>\n" +
" {$params}\n" +
" </params>\n" +
"</methodResponse>")
}
}

77
connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentTest.java

@ -1,77 +0,0 @@
package org.transdroid.connect.clients.rtorrent;
import org.junit.Before;
import org.junit.Test;
import org.transdroid.connect.Configuration;
import org.transdroid.connect.clients.Client;
import org.transdroid.connect.clients.ClientSpec;
import org.transdroid.connect.clients.Feature;
import org.transdroid.connect.clients.UnsupportedFeatureException;
import org.transdroid.connect.model.Torrent;
import java.io.IOException;
import java.util.List;
import io.reactivex.functions.Predicate;
import static com.google.common.truth.Truth.assertThat;
public final class RtorrentTest {
private ClientSpec rtorrent;
@Before
public void setUp() {
rtorrent = new Configuration.Builder(Client.RTORRENT)
.baseUrl("http://localhost:8008/")
.endpoint("RPC2")
.loggingEnabled(true)
.build()
.createClient();
}
@Test
public void features() {
assertThat(Client.RTORRENT.supports(Feature.VERSION)).isTrue();
assertThat(Client.RTORRENT.supports(Feature.LISTING)).isTrue();
assertThat(Client.RTORRENT.supports(Feature.STARTING_STOPPING)).isTrue();
assertThat(Client.RTORRENT.supports(Feature.RESUMING_PAUSING)).isTrue();
assertThat(Client.RTORRENT.supports(Feature.FORCE_STARTING)).isFalse();
assertThat(Client.RTORRENT.supports(Feature.ADD_BY_FILE)).isTrue();
assertThat(Client.RTORRENT.supports(Feature.ADD_BY_URL)).isTrue();
assertThat(Client.RTORRENT.supports(Feature.ADD_BY_MAGNET)).isTrue();
}
@Test
public void clientVersion() throws IOException {
rtorrent.clientVersion()
.test()
.assertValue("0.9.6");
}
@Test
public void torrents() throws IOException {
rtorrent.torrents()
.toList()
.test()
.assertValue(new Predicate<List<Torrent>>() {
@Override
public boolean test(List<Torrent> torrents) throws Exception {
return torrents.size() > 0;
}
});
}
@Test
public void addByMagnet() throws IOException {
rtorrent.addByMagnet("http://torrent.ubuntu.com:6969/file?info_hash=%04%03%FBG%28%BDx%8F%BC%B6%7E%87%D6%FE%B2A%EF8%C7Z")
.test();
}
@Test(expected = UnsupportedFeatureException.class)
public void forceStart() throws IOException {
rtorrent.forceStart(null)
.test();
}
}
Loading…
Cancel
Save