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