Browse Source

Working on rTorrent adding of torrents.

rewrite-connect
Eric Kok 8 years ago
parent
commit
658033c584
  1. 1
      connect/src/main/java/org/transdroid/connect/Configuration.java
  2. 41
      connect/src/main/java/org/transdroid/connect/clients/ClientDelegate.java
  3. 5
      connect/src/main/java/org/transdroid/connect/clients/ClientSpec.java
  4. 31
      connect/src/main/java/org/transdroid/connect/clients/Feature.java
  5. 109
      connect/src/main/java/org/transdroid/connect/clients/rtorrent/Rtorrent.java
  6. 14
      connect/src/main/java/org/transdroid/connect/clients/rtorrent/Service.java
  7. 210
      connect/src/main/java/org/transdroid/connect/model/Torrent.java
  8. 15
      connect/src/test/java/org/transdroid/connect/clients/rtorrent/RtorrentTest.java

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

@ -52,6 +52,7 @@ public final class Configuration {
Configuration configuration = new Configuration(client, baseUrl); Configuration configuration = new Configuration(client, baseUrl);
configuration.endpoint = this.endpoint; configuration.endpoint = this.endpoint;
configuration.credentials = this.credentials; configuration.credentials = this.credentials;
configuration.loggingEnabled = this.loggingEnabled;
return configuration; return configuration;
} }

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

@ -2,6 +2,8 @@ package org.transdroid.connect.clients;
import org.transdroid.connect.model.Torrent; import org.transdroid.connect.model.Torrent;
import java.io.InputStream;
import io.reactivex.Flowable; import io.reactivex.Flowable;
/** /**
@ -33,10 +35,45 @@ final class ClientDelegate implements ClientSpec {
} }
@Override @Override
public Flowable<Torrent> forceStartTorrent() { 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)) if (client.supports(Feature.FORCE_STARTING))
return ((Feature.ForceStarting) actual).forceStartTorrent(); return ((Feature.ForceStarting) actual).forceStart(torrent);
throw new UnsupportedFeatureException(client, Feature.FORCE_STARTING); 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);
}
} }

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

@ -5,6 +5,9 @@ public interface ClientSpec extends
Feature.Listing, Feature.Listing,
Feature.StartingStopping, Feature.StartingStopping,
Feature.ResumingPausing, Feature.ResumingPausing,
Feature.ForceStarting { Feature.ForceStarting,
Feature.AddByFile,
Feature.AddByUrl,
Feature.AddByMagnet {
} }

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

@ -2,6 +2,8 @@ package org.transdroid.connect.clients;
import org.transdroid.connect.model.Torrent; import org.transdroid.connect.model.Torrent;
import java.io.InputStream;
import io.reactivex.Flowable; import io.reactivex.Flowable;
/** /**
@ -14,7 +16,10 @@ public enum Feature {
LISTING(Listing.class), LISTING(Listing.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),
ADD_BY_FILE(AddByFile.class),
ADD_BY_URL(AddByUrl.class),
ADD_BY_MAGNET(AddByMagnet.class);
private final Class<?> type; private final Class<?> type;
@ -40,6 +45,10 @@ public enum Feature {
public interface StartingStopping { public interface StartingStopping {
Flowable<Torrent> start(Torrent torrent);
Flowable<Torrent> stop(Torrent torrent);
} }
public interface ResumingPausing { public interface ResumingPausing {
@ -48,7 +57,25 @@ public enum Feature {
public interface ForceStarting { public interface ForceStarting {
Flowable<Torrent> forceStartTorrent(); 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);
} }

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

@ -2,6 +2,7 @@ package org.transdroid.connect.clients.rtorrent;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import org.reactivestreams.Publisher;
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;
@ -9,9 +10,11 @@ import org.transdroid.connect.model.TorrentStatus;
import org.transdroid.connect.util.OkHttpBuilder; import org.transdroid.connect.util.OkHttpBuilder;
import org.transdroid.connect.util.RxUtil; import org.transdroid.connect.util.RxUtil;
import java.io.InputStream;
import java.util.Date; import java.util.Date;
import io.reactivex.Flowable; import io.reactivex.Flowable;
import io.reactivex.FlowableTransformer;
import io.reactivex.functions.Function; import io.reactivex.functions.Function;
import nl.nl2312.xmlrpc.Nothing; import nl.nl2312.xmlrpc.Nothing;
import nl.nl2312.xmlrpc.XmlRpcConverterFactory; import nl.nl2312.xmlrpc.XmlRpcConverterFactory;
@ -21,7 +24,10 @@ public final class Rtorrent implements
Feature.Version, Feature.Version,
Feature.Listing, Feature.Listing,
Feature.StartingStopping, Feature.StartingStopping,
Feature.ResumingPausing { Feature.ResumingPausing,
Feature.AddByFile,
Feature.AddByUrl,
Feature.AddByMagnet {
private final Configuration configuration; private final Configuration configuration;
private final Service service; private final Service service;
@ -39,7 +45,8 @@ public final class Rtorrent implements
@Override @Override
public Flowable<String> clientVersion() { public Flowable<String> clientVersion() {
return service.clientVersion(configuration.endpoint(), Nothing.NOTHING); return service.clientVersion(configuration.endpoint(), Nothing.NOTHING)
.cache(); // Cached, as it is often used but 'never' changes
} }
@Override @Override
@ -102,6 +109,104 @@ public final class Rtorrent implements
}); });
} }
@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) { private TorrentStatus torrentStatus(long state, long complete, long active, long checking) {
if (state == 0) { if (state == 0) {
return TorrentStatus.QUEUED; return TorrentStatus.QUEUED;

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

@ -15,6 +15,18 @@ interface Service {
@XmlRpc("d.multicall2") @XmlRpc("d.multicall2")
@POST("{endpoint}") @POST("{endpoint}")
Flowable<TorrentSpec[]> torrents(@Path("endpoint") String endpoint, @Body String... fields); 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);
} }

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

@ -86,7 +86,7 @@ public final class Torrent {
cal.set(1900, Calendar.DECEMBER, 31); cal.set(1900, Calendar.DECEMBER, 31);
this.dateDone = cal.getTime(); this.dateDone = cal.getTime();
} else if (eta == -1 || eta == -2) { } else if (eta == -1 || eta == -2) {
// UNknown eta: move to the top of the list // Unknown eta: move to the top of the list
this.dateDone = new Date(Long.MAX_VALUE); this.dateDone = new Date(Long.MAX_VALUE);
} else { } else {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
@ -97,4 +97,212 @@ public final class Torrent {
this.error = error; 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);
}
} }

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

@ -24,7 +24,8 @@ public final class RtorrentTest {
public void setUp() { public void setUp() {
rtorrent = new Configuration.Builder(Client.RTORRENT) rtorrent = new Configuration.Builder(Client.RTORRENT)
.baseUrl("http://localhost:8008/") .baseUrl("http://localhost:8008/")
.endpoint("/RPC2") .endpoint("RPC2")
.loggingEnabled(true)
.build() .build()
.createClient(); .createClient();
} }
@ -32,9 +33,13 @@ public final class RtorrentTest {
@Test @Test
public void features() { public void features() {
assertThat(Client.RTORRENT.supports(Feature.VERSION)).isTrue(); 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.STARTING_STOPPING)).isTrue();
assertThat(Client.RTORRENT.supports(Feature.RESUMING_PAUSING)).isTrue(); assertThat(Client.RTORRENT.supports(Feature.RESUMING_PAUSING)).isTrue();
assertThat(Client.RTORRENT.supports(Feature.FORCE_STARTING)).isFalse(); 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 @Test
@ -57,9 +62,15 @@ public final class RtorrentTest {
}); });
} }
@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) @Test(expected = UnsupportedFeatureException.class)
public void forceStart() throws IOException { public void forceStart() throws IOException {
rtorrent.forceStartTorrent() rtorrent.forceStart(null)
.test(); .test();
} }

Loading…
Cancel
Save