diff --git a/build.gradle b/build.gradle index 6944a5dc..c434c152 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,15 @@ buildscript { + ext.kotlin_version = '1.1.1' repositories { jcenter() } 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' } } +apply plugin: 'kotlin' allprojects { repositories { @@ -15,3 +18,9 @@ allprojects { maven { url "https://jitpack.io" } } } +repositories { + mavenCentral() +} +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" +} diff --git a/connect/build.gradle b/connect/build.gradle index 530b406c..d6da617b 100644 --- a/connect/build.gradle +++ b/connect/build.gradle @@ -1,14 +1,24 @@ apply plugin: 'java' +apply plugin: 'kotlin' dependencies { - compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' - compile 'com.squareup.okhttp3:logging-interceptor:3.5.0' - compile 'com.github.erickok:retrofit-xmlrpc:master-SNAPSHOT' + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" + compile 'com.squareup.okhttp3:logging-interceptor:3.7.0' + compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0' compile 'com.burgstaller:okhttp-digest:1.10' + compile 'com.github.erickok:retrofit-xmlrpc:6e2c623763' testCompile 'junit:junit:4.12' testCompile 'com.google.truth:truth:0.31' + testCompile 'com.squareup.okhttp3:mockwebserver:3.7.0' } -sourceCompatibility = "1.7" -targetCompatibility = "1.7" +buildscript { + ext.kotlin_version = '1.1.1' + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} \ No newline at end of file diff --git a/connect/src/main/java/org/transdroid/connect/Configuration.java b/connect/src/main/java/org/transdroid/connect/Configuration.java deleted file mode 100644 index 2af13409..00000000 --- a/connect/src/main/java/org/transdroid/connect/Configuration.java +++ /dev/null @@ -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); - } - -} diff --git a/connect/src/main/java/org/transdroid/connect/Configuration.kt b/connect/src/main/java/org/transdroid/connect/Configuration.kt new file mode 100644 index 00000000..917d519c --- /dev/null +++ b/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) + +} diff --git a/connect/src/main/java/org/transdroid/connect/clients/Client.java b/connect/src/main/java/org/transdroid/connect/clients/Client.java deleted file mode 100644 index 7e8abfbd..00000000 --- a/connect/src/main/java/org/transdroid/connect/clients/Client.java +++ /dev/null @@ -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); - } - -} diff --git a/connect/src/main/java/org/transdroid/connect/clients/Client.kt b/connect/src/main/java/org/transdroid/connect/clients/Client.kt new file mode 100644 index 00000000..2dbb8cae --- /dev/null +++ b/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) + } + +} diff --git a/connect/src/main/java/org/transdroid/connect/clients/ClientDelegate.java b/connect/src/main/java/org/transdroid/connect/clients/ClientDelegate.java deleted file mode 100644 index dde37139..00000000 --- a/connect/src/main/java/org/transdroid/connect/clients/ClientDelegate.java +++ /dev/null @@ -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 torrents() { - if (client.supports(Feature.LISTING)) - return ((Feature.Listing) actual).torrents(); - throw new UnsupportedFeatureException(client, Feature.LISTING); - } - - @Override - public Flowable clientVersion() { - if (client.supports(Feature.VERSION)) - return ((Feature.Version) actual).clientVersion(); - throw new UnsupportedFeatureException(client, Feature.VERSION); - } - - @Override - public Flowable 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 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 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 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 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 addByMagnet(String magnet) { - if (client.supports(Feature.ADD_BY_MAGNET)) - return ((Feature.AddByMagnet) actual).addByMagnet(magnet); - throw new UnsupportedFeatureException(client, Feature.ADD_BY_MAGNET); - } - -} diff --git a/connect/src/main/java/org/transdroid/connect/clients/ClientDelegate.kt b/connect/src/main/java/org/transdroid/connect/clients/ClientDelegate.kt new file mode 100644 index 00000000..86d86846 --- /dev/null +++ b/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 { + if (client.supports(Feature.LISTING)) + return (actual as Feature.Listing).torrents() + throw UnsupportedFeatureException(client, Feature.LISTING) + } + + override fun clientVersion(): Single { + if (client.supports(Feature.VERSION)) + return (actual as Feature.Version).clientVersion() + throw UnsupportedFeatureException(client, Feature.VERSION) + } + + override fun start(torrent: Torrent): Single { + 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 { + 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 { + 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) + } + +} diff --git a/connect/src/main/java/org/transdroid/connect/clients/ClientSpec.java b/connect/src/main/java/org/transdroid/connect/clients/ClientSpec.java deleted file mode 100644 index 204ba201..00000000 --- a/connect/src/main/java/org/transdroid/connect/clients/ClientSpec.java +++ /dev/null @@ -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 { - -} diff --git a/connect/src/main/java/org/transdroid/connect/clients/ClientSpec.kt b/connect/src/main/java/org/transdroid/connect/clients/ClientSpec.kt new file mode 100644 index 00000000..905db401 --- /dev/null +++ b/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 diff --git a/connect/src/main/java/org/transdroid/connect/clients/Feature.java b/connect/src/main/java/org/transdroid/connect/clients/Feature.java deleted file mode 100644 index 9607470b..00000000 --- a/connect/src/main/java/org/transdroid/connect/clients/Feature.java +++ /dev/null @@ -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 clientVersion(); - - } - - public interface Listing { - - Flowable torrents(); - - } - - public interface StartingStopping { - - Flowable start(Torrent torrent); - - Flowable stop(Torrent torrent); - - } - - public interface ResumingPausing { - - } - - public interface ForceStarting { - - Flowable forceStart(Torrent torrent); - - } - - public interface AddByFile { - - Flowable addByFile(InputStream file); - - } - - public interface AddByUrl { - - Flowable addByUrl(String url); - - } - - public interface AddByMagnet { - - Flowable addByMagnet(String magnet); - - } - -} diff --git a/connect/src/main/java/org/transdroid/connect/clients/Feature.kt b/connect/src/main/java/org/transdroid/connect/clients/Feature.kt new file mode 100644 index 00000000..16c530ac --- /dev/null +++ b/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 + + } + + interface Listing { + + fun torrents(): Flowable + + } + + interface StartingStopping { + + fun start(torrent: Torrent): Single + + fun stop(torrent: Torrent): Single + + } + + interface ResumingPausing + + interface ForceStarting { + + fun forceStart(torrent: Torrent): Single + + } + + interface AddByFile { + + fun addByFile(file: InputStream): Completable + + } + + interface AddByUrl { + + fun addByUrl(url: String): Completable + + } + + interface AddByMagnet { + + fun addByMagnet(magnet: String): Completable + + } + +} diff --git a/connect/src/main/java/org/transdroid/connect/clients/UnsupportedFeatureException.java b/connect/src/main/java/org/transdroid/connect/clients/UnsupportedFeatureException.java deleted file mode 100644 index fade06db..00000000 --- a/connect/src/main/java/org/transdroid/connect/clients/UnsupportedFeatureException.java +++ /dev/null @@ -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(); - } - -} diff --git a/connect/src/main/java/org/transdroid/connect/clients/UnsupportedFeatureException.kt b/connect/src/main/java/org/transdroid/connect/clients/UnsupportedFeatureException.kt new file mode 100644 index 00000000..40f7e858 --- /dev/null +++ b/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 + +} diff --git a/connect/src/main/java/org/transdroid/connect/clients/rtorrent/Rtorrent.java b/connect/src/main/java/org/transdroid/connect/clients/rtorrent/Rtorrent.java deleted file mode 100644 index 439c96fe..00000000 --- a/connect/src/main/java/org/transdroid/connect/clients/rtorrent/Rtorrent.java +++ /dev/null @@ -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 clientVersion() { - return service.clientVersion(configuration.endpoint(), Nothing.NOTHING) - .cache(); // Cached, as it is often used but 'never' changes - } - - @Override - public Flowable 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.asList()) - .map(new Function() { - @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 start(final Torrent torrent) { - return service.start( - configuration.endpoint(), - torrent.hash()).map(new Function() { - @Override - public Torrent apply(Void result) throws Exception { - return torrent.mimicStart(); - } - }); - } - - @Override - public Flowable stop(final Torrent torrent) { - return service.stop( - configuration.endpoint(), - torrent.hash()).map(new Function() { - @Override - public Torrent apply(Void result) throws Exception { - return torrent.mimicStart(); - } - }); - } - - @Override - public Flowable addByFile(InputStream file) { - // TODO - return null; - } - - @Override - public Flowable addByUrl(final String url) { - return clientVersion().compose(clientVersionAsInt).flatMap(new Function>() { - @Override - public Publisher apply(Integer integer) throws Exception { - if (integer > 904) { - return service.loadStart( - configuration.endpoint(), - "", - url); - } else { - return service.loadStart( - configuration.endpoint(), - url); - } - } - }).map(new Function() { - @Override - public Void apply(Integer integer) throws Exception { - return null; - } - }); - } - - @Override - public Flowable addByMagnet(final String magnet) { - return clientVersion().compose(clientVersionAsInt).flatMap(new Function>() { - @Override - public Publisher apply(Integer integer) throws Exception { - if (integer > 904) { - return service.loadStart( - configuration.endpoint(), - "", - magnet); - } else { - return service.loadStart( - configuration.endpoint(), - magnet); - } - } - }).map(new Function() { - @Override - public Void apply(Integer integer) throws Exception { - return null; - } - }); - } - - private FlowableTransformer clientVersionAsInt = new FlowableTransformer() { - @Override - public Publisher apply(Flowable version) { - return version.map(new Function() { - @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); - } - -} diff --git a/connect/src/main/java/org/transdroid/connect/clients/rtorrent/Rtorrent.kt b/connect/src/main/java/org/transdroid/connect/clients/rtorrent/Rtorrent.kt new file mode 100644 index 00000000..11f359db --- /dev/null +++ b/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 { + return service.clientVersion(configuration.endpoint, Nothing.NOTHING) + .cache() // Cached, as it is often used but 'never' changes + } + + private fun Single.asVersionInt(): Single { + 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 { + 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 { + return service.start( + configuration.endpoint, + torrent.uniqueId).toSingle { torrent.mimicStart() } + } + + override fun stop(torrent: Torrent): Single { + 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) + +} diff --git a/connect/src/main/java/org/transdroid/connect/clients/rtorrent/Service.java b/connect/src/main/java/org/transdroid/connect/clients/rtorrent/Service.java deleted file mode 100644 index 2e1c5d6b..00000000 --- a/connect/src/main/java/org/transdroid/connect/clients/rtorrent/Service.java +++ /dev/null @@ -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 clientVersion(@Path("endpoint") String endpoint, @Body Nothing nothing); - - @XmlRpc("d.multicall2") - @POST("{endpoint}") - Flowable torrents(@Path("endpoint") String endpoint, @Body String... args); - - @XmlRpc("d.start") - @POST("{endpoint}") - Flowable start(@Path("endpoint") String endpoint, @Body String hash); - - @XmlRpc("d.stop") - @POST("{endpoint}") - Flowable stop(@Path("endpoint") String endpoint, @Body String hash); - - @XmlRpc("load.start") - @POST("{endpoint}") - Flowable loadStart(@Path("endpoint") String endpoint, @Body String... args); - -} diff --git a/connect/src/main/java/org/transdroid/connect/clients/rtorrent/Service.kt b/connect/src/main/java/org/transdroid/connect/clients/rtorrent/Service.kt new file mode 100644 index 00000000..c7197480 --- /dev/null +++ b/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 + + @XmlRpc("d.multicall2") + @POST("{endpoint}") + fun torrents(@Path("endpoint") endpoint: String?, @Body vararg args: String): Flowable> + + @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 + +} diff --git a/connect/src/main/java/org/transdroid/connect/clients/rtorrent/TorrentSpec.java b/connect/src/main/java/org/transdroid/connect/clients/rtorrent/TorrentSpec.java deleted file mode 100644 index 8cb08678..00000000 --- a/connect/src/main/java/org/transdroid/connect/clients/rtorrent/TorrentSpec.java +++ /dev/null @@ -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; - -} diff --git a/connect/src/main/java/org/transdroid/connect/clients/rtorrent/TorrentSpec.kt b/connect/src/main/java/org/transdroid/connect/clients/rtorrent/TorrentSpec.kt new file mode 100644 index 00000000..119762ca --- /dev/null +++ b/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) \ No newline at end of file diff --git a/connect/src/main/java/org/transdroid/connect/clients/transmission/Transmission.java b/connect/src/main/java/org/transdroid/connect/clients/transmission/Transmission.java deleted file mode 100644 index bde548a4..00000000 --- a/connect/src/main/java/org/transdroid/connect/clients/transmission/Transmission.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.transdroid.connect.clients.transmission; - -public final class Transmission { - -} diff --git a/connect/src/main/java/org/transdroid/connect/clients/transmission/Transmission.kt b/connect/src/main/java/org/transdroid/connect/clients/transmission/Transmission.kt new file mode 100644 index 00000000..bdcd2b52 --- /dev/null +++ b/connect/src/main/java/org/transdroid/connect/clients/transmission/Transmission.kt @@ -0,0 +1,3 @@ +package org.transdroid.connect.clients.transmission + +class Transmission diff --git a/connect/src/main/java/org/transdroid/connect/model/Torrent.java b/connect/src/main/java/org/transdroid/connect/model/Torrent.java deleted file mode 100644 index 75a6a238..00000000 --- a/connect/src/main/java/org/transdroid/connect/model/Torrent.java +++ /dev/null @@ -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); - } - -} diff --git a/connect/src/main/java/org/transdroid/connect/model/Torrent.kt b/connect/src/main/java/org/transdroid/connect/model/Torrent.kt new file mode 100644 index 00000000..2f6fbdf9 --- /dev/null +++ b/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" + +} diff --git a/connect/src/main/java/org/transdroid/connect/model/TorrentStatus.java b/connect/src/main/java/org/transdroid/connect/model/TorrentStatus.java deleted file mode 100644 index 047478b3..00000000 --- a/connect/src/main/java/org/transdroid/connect/model/TorrentStatus.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.transdroid.connect.model; - -public enum TorrentStatus { - - WAITING, - CHECKING, - DOWNLOADING, - SEEDING, - PAUSED, - QUEUED, - ERROR, - UNKNOWN; - -} diff --git a/connect/src/main/java/org/transdroid/connect/model/TorrentStatus.kt b/connect/src/main/java/org/transdroid/connect/model/TorrentStatus.kt new file mode 100644 index 00000000..e02ecaeb --- /dev/null +++ b/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 +} diff --git a/connect/src/main/java/org/transdroid/connect/util/OkHttpBuilder.java b/connect/src/main/java/org/transdroid/connect/util/OkHttpBuilder.java deleted file mode 100644 index 79b2fae9..00000000 --- a/connect/src/main/java/org/transdroid/connect/util/OkHttpBuilder.java +++ /dev/null @@ -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 authCache = new ConcurrentHashMap<>(); - okhttp.authenticator(new CachingAuthenticatorDecorator(authenticator, authCache)); - okhttp.addInterceptor(new AuthenticationCacheInterceptor(authCache)); - } - - return okhttp.build(); - } - -} diff --git a/connect/src/main/java/org/transdroid/connect/util/OkHttpBuilder.kt b/connect/src/main/java/org/transdroid/connect/util/OkHttpBuilder.kt new file mode 100644 index 00000000..8e93cb4a --- /dev/null +++ b/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() + okhttp.authenticator(CachingAuthenticatorDecorator(authenticator, authCache)) + okhttp.addInterceptor(AuthenticationCacheInterceptor(authCache)) + } + + return okhttp.build() + } + +} diff --git a/connect/src/main/java/org/transdroid/connect/util/RxUtil.java b/connect/src/main/java/org/transdroid/connect/util/RxUtil.java deleted file mode 100644 index 85b4bd18..00000000 --- a/connect/src/main/java/org/transdroid/connect/util/RxUtil.java +++ /dev/null @@ -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 FlowableTransformer asList() { - return new FlowableTransformer() { - @Override - public Publisher apply(Flowable upstream) { - return upstream.flatMapIterable(new Function>() { - @Override - public Iterable apply(T[] ts) throws Exception { - return Arrays.asList(ts); - } - }); - } - }; - } - -} diff --git a/connect/src/main/java/org/transdroid/connect/util/RxUtil.kt b/connect/src/main/java/org/transdroid/connect/util/RxUtil.kt new file mode 100644 index 00000000..25e8cbba --- /dev/null +++ b/connect/src/main/java/org/transdroid/connect/util/RxUtil.kt @@ -0,0 +1,5 @@ +package org.transdroid.connect.util + +import io.reactivex.Flowable + +fun Flowable>.flatten(): Flowable = this.flatMapIterable { items -> items.toList() } diff --git a/connect/src/main/java/org/transdroid/connect/util/StringUtil.java b/connect/src/main/java/org/transdroid/connect/util/StringUtil.java deleted file mode 100644 index 0d52529a..00000000 --- a/connect/src/main/java/org/transdroid/connect/util/StringUtil.java +++ /dev/null @@ -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(""); - } - -} diff --git a/connect/src/test/java/org/transdroid/connect/clients/rtorrent/MockTorrent.kt b/connect/src/test/java/org/transdroid/connect/clients/rtorrent/MockTorrent.kt new file mode 100644 index 00000000..6bfb7385 --- /dev/null +++ b/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) + +} \ No newline at end of file diff --git a/connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentLiveTest.kt b/connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentLiveTest.kt new file mode 100644 index 00000000..bcf001ac --- /dev/null +++ b/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() + +} diff --git a/connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentMockTest.kt b/connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentMockTest.kt new file mode 100644 index 00000000..326833f6 --- /dev/null +++ b/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("0.9.6")) + rtorrent.clientVersion() + .test() + .assertValue("0.9.6") + server.takeRequest() + } + + @Test + fun torrents() { + server.enqueue(mock("59066769B9AD42DA2E508611C33D7C4480B3857Bubuntu-17.04-desktop-amd64.iso000000016090398721609039872149207715900000")) + rtorrent.torrents() + .test() + .assertValue { it.hash == "59066769B9AD42DA2E508611C33D7C4480B3857B" } + server.takeRequest() + } + + @Test + fun addByUrl() { + server.enqueue(mock("0.9.6")) + server.enqueue(mock("0")) + 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("0.9.6")) + server.enqueue(mock("0")) + 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("0")) + rtorrent.start(MockTorrent.downloading) + .test() + .assertValue { it.canStop } + server.takeRequest() + } + + @Test + fun stop() { + server.enqueue(mock("0")) + 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("\n" + + "\n" + + " \n" + + " {$params}\n" + + " \n" + + "") + } + +} diff --git a/connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentTest.java b/connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentTest.java deleted file mode 100644 index 3df7d981..00000000 --- a/connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentTest.java +++ /dev/null @@ -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>() { - @Override - public boolean test(List 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(); - } - -}