Browse Source

Added support for torrent (tracker) details.

rewrite-connect
Eric Kok 8 years ago
parent
commit
468cea70d2
  1. 15
      connect/src/main/java/org/transdroid/connect/clients/ClientDelegate.kt
  2. 1
      connect/src/main/java/org/transdroid/connect/clients/ClientSpec.kt
  3. 8
      connect/src/main/java/org/transdroid/connect/clients/Feature.kt
  4. 16
      connect/src/main/java/org/transdroid/connect/clients/rtorrent/Rtorrent.kt
  5. 8
      connect/src/main/java/org/transdroid/connect/clients/rtorrent/Service.kt
  6. 3
      connect/src/main/java/org/transdroid/connect/clients/rtorrent/TrackerSpec.kt
  7. 23
      connect/src/main/java/org/transdroid/connect/model/Torrent.kt
  8. 5
      connect/src/main/java/org/transdroid/connect/model/TorrentDetails.kt
  9. 3
      connect/src/main/java/org/transdroid/connect/util/RxUtil.kt
  10. 8
      connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentLiveTest.kt
  11. 16
      connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentMockTest.kt
  12. 9
      connect/src/test/java/org/transdroid/connect/mock/MockTorrent.kt

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

@ -4,6 +4,7 @@ import io.reactivex.Completable
import io.reactivex.Flowable import io.reactivex.Flowable
import io.reactivex.Single import io.reactivex.Single
import org.transdroid.connect.model.Torrent import org.transdroid.connect.model.Torrent
import org.transdroid.connect.model.TorrentDetails
import java.io.InputStream import java.io.InputStream
/** /**
@ -12,16 +13,22 @@ import java.io.InputStream
*/ */
internal class ClientDelegate(private val client: Client, private val actual: Any) : ClientSpec { internal class ClientDelegate(private val client: Client, private val actual: Any) : ClientSpec {
override fun clientVersion(): Single<String> {
if (client.supports(Feature.VERSION))
return (actual as Feature.Version).clientVersion()
throw UnsupportedFeatureException(client, Feature.VERSION)
}
override fun torrents(): Flowable<Torrent> { override fun torrents(): Flowable<Torrent> {
if (client.supports(Feature.LISTING)) if (client.supports(Feature.LISTING))
return (actual as Feature.Listing).torrents() return (actual as Feature.Listing).torrents()
throw UnsupportedFeatureException(client, Feature.LISTING) throw UnsupportedFeatureException(client, Feature.LISTING)
} }
override fun clientVersion(): Single<String> { override fun details(torrent: Torrent): Single<TorrentDetails> {
if (client.supports(Feature.VERSION)) if (client.supports(Feature.DETAILS))
return (actual as Feature.Version).clientVersion() return (actual as Feature.Details).details(torrent)
throw UnsupportedFeatureException(client, Feature.VERSION) throw UnsupportedFeatureException(client, Feature.DETAILS)
} }
override fun resume(torrent: Torrent): Single<Torrent> { override fun resume(torrent: Torrent): Single<Torrent> {

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

@ -3,6 +3,7 @@ package org.transdroid.connect.clients
interface ClientSpec : interface ClientSpec :
Feature.Version, Feature.Version,
Feature.Listing, Feature.Listing,
Feature.Details,
Feature.StartingStopping, Feature.StartingStopping,
Feature.ResumingPausing, Feature.ResumingPausing,
Feature.ForceStarting, Feature.ForceStarting,

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

@ -4,6 +4,7 @@ import io.reactivex.Completable
import io.reactivex.Flowable import io.reactivex.Flowable
import io.reactivex.Single import io.reactivex.Single
import org.transdroid.connect.model.Torrent import org.transdroid.connect.model.Torrent
import org.transdroid.connect.model.TorrentDetails
import java.io.InputStream import java.io.InputStream
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -14,6 +15,7 @@ enum class Feature constructor(val type: KClass<*>) {
VERSION(Version::class), VERSION(Version::class),
LISTING(Listing::class), LISTING(Listing::class),
DETAILS(Details::class),
STARTING_STOPPING(StartingStopping::class), STARTING_STOPPING(StartingStopping::class),
RESUMING_PAUSING(ResumingPausing::class), RESUMING_PAUSING(ResumingPausing::class),
FORCE_STARTING(ForceStarting::class), FORCE_STARTING(ForceStarting::class),
@ -33,6 +35,12 @@ enum class Feature constructor(val type: KClass<*>) {
} }
interface Details {
fun details(torrent: Torrent): Single<TorrentDetails>
}
interface StartingStopping { interface StartingStopping {
fun start(torrent: Torrent): Single<Torrent> fun start(torrent: Torrent): Single<Torrent>

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

@ -7,6 +7,7 @@ import nl.nl2312.xmlrpc.XmlRpcConverterFactory
import org.transdroid.connect.Configuration import org.transdroid.connect.Configuration
import org.transdroid.connect.clients.Feature import org.transdroid.connect.clients.Feature
import org.transdroid.connect.model.Torrent import org.transdroid.connect.model.Torrent
import org.transdroid.connect.model.TorrentDetails
import org.transdroid.connect.model.TorrentStatus import org.transdroid.connect.model.TorrentStatus
import org.transdroid.connect.util.OkHttpBuilder import org.transdroid.connect.util.OkHttpBuilder
import org.transdroid.connect.util.flatten import org.transdroid.connect.util.flatten
@ -18,6 +19,7 @@ import java.util.*
class Rtorrent(private val configuration: Configuration) : class Rtorrent(private val configuration: Configuration) :
Feature.Version, Feature.Version,
Feature.Listing, Feature.Listing,
Feature.Details,
Feature.StartingStopping, Feature.StartingStopping,
Feature.ResumingPausing, Feature.ResumingPausing,
Feature.AddByFile, Feature.AddByFile,
@ -60,6 +62,9 @@ class Rtorrent(private val configuration: Configuration) :
) )
} }
.addArrayDeserializer(TrackerSpec::class.java) { arrayValues ->
TrackerSpec(arrayValues.asString(0))
}
.create()) .create())
.build().create(Service::class.java) .build().create(Service::class.java)
@ -123,11 +128,20 @@ class Rtorrent(private val configuration: Configuration) :
null, null,
label, label,
torrentTimeAdded(timeAdded, timeCreated), torrentTimeAdded(timeAdded, timeCreated),
torrentTimeFinished(timeFinished), errorMessage torrentTimeFinished(timeFinished),
errorMessage
) )
} }
} }
override fun details(torrent: Torrent): Single<TorrentDetails> {
return service.trackers(configuration.endpoint, torrent.uniqueId, "", "t.url=")
.flatten()
.map { (url) -> url }
.toList()
.map { trackers -> TorrentDetails(trackers, listOfNotNull(torrent.error)) }
}
override fun start(torrent: Torrent): Single<Torrent> { override fun start(torrent: Torrent): Single<Torrent> {
return service.open(configuration.endpoint, torrent.uniqueId) return service.open(configuration.endpoint, torrent.uniqueId)
.flatMap { service.start(configuration.endpoint, torrent.uniqueId) } .flatMap { service.start(configuration.endpoint, torrent.uniqueId) }

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

@ -1,7 +1,5 @@
package org.transdroid.connect.clients.rtorrent package org.transdroid.connect.clients.rtorrent
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Single import io.reactivex.Single
import nl.nl2312.xmlrpc.Nothing import nl.nl2312.xmlrpc.Nothing
import nl.nl2312.xmlrpc.XmlRpc import nl.nl2312.xmlrpc.XmlRpc
@ -17,7 +15,11 @@ internal interface Service {
@XmlRpc("d.multicall2") @XmlRpc("d.multicall2")
@POST("{endpoint}") @POST("{endpoint}")
fun torrents(@Path("endpoint") endpoint: String?, @Body vararg args: String): Flowable<Array<TorrentSpec>> fun torrents(@Path("endpoint") endpoint: String?, @Body vararg args: String): Single<Array<TorrentSpec>>
@XmlRpc("t.multicall")
@POST("{endpoint}")
fun trackers(@Path("endpoint") endpoint: String?, @Body vararg args: String): Single<Array<TrackerSpec>>
@XmlRpc("d.start") @XmlRpc("d.start")
@POST("{endpoint}") @POST("{endpoint}")

3
connect/src/main/java/org/transdroid/connect/clients/rtorrent/TrackerSpec.kt

@ -0,0 +1,3 @@
package org.transdroid.connect.clients.rtorrent
data class TrackerSpec(val url: String)

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

@ -22,31 +22,18 @@ data class Torrent(
val available: Float?, val available: Float?,
val label: String?, val label: String?,
val dateAdded: Date, val dateAdded: Date,
val realDateDone: Date?, private val realDateDone: Date?,
val error: String?) { val error: String?) {
val dateDone: Date val dateDone: Date = realDateDone ?:
if (this.partDone == 1f) Calendar.getInstance().apply {
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() clear()
set(1900, Calendar.DECEMBER, 31) set(1900, Calendar.DECEMBER, 31)
}.time }.time
} else if (eta == null || eta == -1L || eta == -2L) { else if (eta == null || eta == -1L || eta == -2L) Date(java.lang.Long.MAX_VALUE)
// Unknown eta: move to the top of the list else Calendar.getInstance().apply {
this.dateDone = Date(java.lang.Long.MAX_VALUE)
} else {
this.dateDone = Calendar.getInstance().apply {
add(Calendar.SECOND, eta.toInt()) add(Calendar.SECOND, eta.toInt())
}.time }.time
}
}
}
/** /**
* The unique torrent-specific id, which is the torrent's hash or (if not available) the local index number * The unique torrent-specific id, which is the torrent's hash or (if not available) the local index number

5
connect/src/main/java/org/transdroid/connect/model/TorrentDetails.kt

@ -0,0 +1,5 @@
package org.transdroid.connect.model
data class TorrentDetails(
val trackers: List<String>,
var errors: List<String>)

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

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

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

@ -1,6 +1,5 @@
package org.transdroid.connect.clients.rtorrent package org.transdroid.connect.clients.rtorrent
import com.burgstaller.okhttp.digest.Credentials
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -43,6 +42,13 @@ class RtorrentLiveTest {
.assertValue("0.9.6") .assertValue("0.9.6")
} }
@Test
fun details() {
rtorrent.details(firstLiveTorrent())
.test()
.assertValue { it.trackers.isNotEmpty() }
}
@Test @Test
fun torrents() { fun torrents() {
rtorrent.torrents() rtorrent.torrents()

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

@ -7,7 +7,6 @@ import org.junit.Test
import org.transdroid.connect.Configuration import org.transdroid.connect.Configuration
import org.transdroid.connect.clients.Client import org.transdroid.connect.clients.Client
import org.transdroid.connect.mock.MockTorrent import org.transdroid.connect.mock.MockTorrent
import java.io.File
class RtorrentMockTest { class RtorrentMockTest {
@ -38,6 +37,21 @@ class RtorrentMockTest {
server.takeRequest() server.takeRequest()
} }
@Test
fun details() {
server.enqueue(mock("<array><data><value><array><data><value><string>http://torrent.ubuntu.com:6969/announce</string></value></data></array></value><value><array><data><value><string>http://ipv6.torrent.ubuntu.com:6969/announce</string></value></data></array></value></data></array>"))
rtorrent.details(MockTorrent.error)
.test()
.assertValue {
it.trackers.size == 2 &&
it.trackers[0] == "http://torrent.ubuntu.com:6969/announce" &&
it.trackers[1] == "http://ipv6.torrent.ubuntu.com:6969/announce" &&
it.errors.size == 1 &&
it.errors[0] == "tracker error"
}
server.takeRequest()
}
@Test @Test
fun addByUrl() { fun addByUrl() {
server.enqueue(mock("<string>0.9.6</string>")) server.enqueue(mock("<string>0.9.6</string>"))

9
connect/src/test/java/org/transdroid/connect/mock/MockTorrent.kt

@ -12,8 +12,13 @@ object MockTorrent {
val torrentFile = File("connect/src/test/resources/test/ubuntu.torrent").inputStream() val torrentFile = File("connect/src/test/resources/test/ubuntu.torrent").inputStream()
val downloading = Torrent(0, "59066769B9AD42DA2E508611C33D7C4480B3857B", "ubuntu-17.04-desktop-amd64.iso", TorrentStatus.DOWNLOADING, 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) "/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, 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) "/downloads/", 0, 1000000, 0, 24, 10, 50, null, 1609039872, 2609039872, 1609039872, 1F, 1F, "distros", Date(1492681983),
Date(1492781983), null)
val error = Torrent(0, "59066769B9AD42DA2E508611C33D7C4480B3857B", "ubuntu-17.04-desktop-amd64.iso", TorrentStatus.ERROR,
"/downloads/", 0, 1000000, 0, 0, 0, 0, null, 1609039872, 2609039872, 1609039872, 1F, 1F, "distros", Date(1492681983),
Date(1492781983), "tracker error")
} }
Loading…
Cancel
Save