Eric Kok
8 years ago
35 changed files with 852 additions and 1132 deletions
@ -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" |
||||||
|
} |
||||||
|
} |
@ -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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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) |
||||||
|
|
||||||
|
} |
@ -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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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 { |
|
||||||
|
|
||||||
} |
|
@ -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 |
@ -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); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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 |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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 |
||||||
|
|
||||||
|
} |
@ -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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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) |
||||||
|
|
||||||
|
} |
@ -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); |
|
||||||
|
|
||||||
} |
|
@ -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 |
||||||
|
|
||||||
|
} |
@ -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; |
|
||||||
|
|
||||||
} |
|
@ -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) |
@ -1,5 +0,0 @@ |
|||||||
package org.transdroid.connect.clients.transmission; |
|
||||||
|
|
||||||
public final class Transmission { |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,3 @@ |
|||||||
|
package org.transdroid.connect.clients.transmission |
||||||
|
|
||||||
|
class Transmission |
@ -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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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" |
||||||
|
|
||||||
|
} |
@ -1,14 +0,0 @@ |
|||||||
package org.transdroid.connect.model; |
|
||||||
|
|
||||||
public enum TorrentStatus { |
|
||||||
|
|
||||||
WAITING, |
|
||||||
CHECKING, |
|
||||||
DOWNLOADING, |
|
||||||
SEEDING, |
|
||||||
PAUSED, |
|
||||||
QUEUED, |
|
||||||
ERROR, |
|
||||||
UNKNOWN; |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,12 @@ |
|||||||
|
package org.transdroid.connect.model |
||||||
|
|
||||||
|
enum class TorrentStatus { |
||||||
|
WAITING, |
||||||
|
CHECKING, |
||||||
|
DOWNLOADING, |
||||||
|
SEEDING, |
||||||
|
PAUSED, |
||||||
|
QUEUED, |
||||||
|
ERROR, |
||||||
|
UNKNOWN |
||||||
|
} |
@ -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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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() |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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() } |
@ -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(""); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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) |
||||||
|
|
||||||
|
} |
@ -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() |
||||||
|
|
||||||
|
} |
@ -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>") |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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…
Reference in new issue